UART使用参考


REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 04/18/2023
    1.1
  • 增加第七章FAQ&修改文档格式
  • 04/08/2025

    1. 概述

    UART一般指通用异步通讯收发器,通讯特征为异步,串行。UART总线有两条数据线,TX和RX,实现全双工的发送和接收。收发双方通过各自设定的帧格式和波特率解析接收到的信号,因此在通讯过程中要求收发双方使用相同的帧格式和波特率。

    SigmaStar UART符合标准串口通信协议的模块。当中含有多个UART,其中会有FUART、UART的不同称呼,其区别在于FUART相比UART,多了CTS/RTS硬件流控功能。所有UART均支持DMA mode。一般默认使用UART0作为console port。


    2. 关键字说明

    • TX

      数据发送功能/引脚,按照设定的帧格式和波特率发出UART信号。

    • RX

      数据接收功能/引脚,接收到的信号会被UART以设定的帧格式和波特率解析,TX和RX共用同一套设定。

    • CTS

      流控引脚/信号,输入信号,解释为“发送允许”。用于判断是否可以向对方发送数据,低电平有效。

    • RTS

      流控引脚/信号,输出信号,解释为“发送请求”。用于指示本设备准备好可接收数据,低电平有效。

    • FIFO mode

      每一帧数据都需要通过CPU传递给UART硬件发送缓存寄存器,再由UART自行从发送缓存拿走往外发送。硬件发送缓存为32字节。或者接收时CPU从UART硬件接收缓存寄存器读取,硬件接收缓存为32字节。

    • DMA mode

      每一帧的数据不再需要CPU逐个下发或读取,只需要在触发通讯之前,将要发送的数据一次性写入DMA指定的存储位置当中,再触发通讯;或者从指定存储当中一次性拿走接收到的所有数据;通讯执行期间,于UART之间的数据交互由URDMA自行完成,无需CPU再参与。
      DMA mode可以使得传输更加连贯,减少CPU loading,减少UART通讯中断数量,同时接收发送各提供的4096字节存储空间,能极大减少数据丢失的可能性。

    • URDMA

      专门用于为UART提供数据搬运服务的模块,DMA mode时需要启用。启用后,UART不再发生中断,由URDMA发生中断;且DMA enable时,CPU不能再去访问UART寄存器,否则会导致卡死。

    • CONSOLE PORT

      console是一个缓冲的概念,专为内核提供打印与busybox接收shell命令使用。PC与Console Port连接,通过PC的终端应用,显示打印信息或输入操作指令。

    • PADMUX

      引脚复用,将UART PAD与实际引脚导通,让信号能通过引脚传递。

    • DIGMUX

      用于导通UART TX/RX digital message与UART PAD。不同的UART PAD可以接入到不同的UART模块。但是默认作为CONSOLE PORT的PAD_PM_UART_TX与PAD_PM_UART_RX这组PAD无法切换digmux。

      例如:当硬件layout固定时,假设原先使用UART1的功能,此时需要HW CTS/RTS的支持,而FUART又没有相应的PADMUX可以切到这组硬件引脚来。则可以通过切换DIGMUX,把UART1与FUART做切换,此时FUART的TX/RX与UART1的TX/RX信号连接互换了,但CTS/RTS则还是原先FUART设定的引脚,满足对HW CTS/RTS的使用。


    3. 功能描述

    3.1 UART资源

    PCUPID共提供了6组标准UART、4组FUART(硬件流控的uart模块,支持CTS/RTS,为fuart、uart1、uart2、uart3,可以在dts中配置sctp_enable开启流控功能)和一组PM_UAR(不支持DMA mode)。

    各UART/URDMA与bank address对应如下,UART与URDMA唯一绑定,例如FUART和URDMA绑定,UART0和URDMA0绑定:

    UART IP FUART UART0 UART1 UART2 UART3 UART4 UART5 UART6 UART7 UART8 PM_UART
    BANK ADDR 1102 1108 1109 110A 110B 110C 110D 1140 1142 1144 0035
    URDMA IP URDMA URDMA0 URDMA1 URDMA2 URDMA3 URDMA4 URDMA5 URDMA6 URDMA7 URDMA8
    BANK ADDR 1103 1107 110E 110F 1111 1112 1113 1141 1143 1145

    3.2 功能支持

    下表提供各UART对各功能的支持情况

    功能 FIFO mode FIFO buffer size(byte) DMA mode DMA buffer size(byte) HW CTS/RTS baudrate protocol
    支持情况 32 ✔(除PM_UART) 4096 only fuart

    波特率支持情况如下表:

    UART
    BAUDRATE(bps)
    ALL UART 1200
    ALL UART 1800
    ALL UART 2400
    ALL UART 4800
    ALL UART 9600
    ALL UART 19200
    ALL UART 38400
    ALL UART 57600
    ALL UART 115200
    ALL UART 230400
    ALL UART 460800
    ALL UART 500000
    ALL UART 576000
    ALL UART 921600
    ALL UART 1000000
    ALL UART 1152000
    ALL UART 1500000
    ALL UART 2000000
    ALL UART 2500000
    ALL UART 3000000
    ALL UART 3500000

    UART通讯协议支持情况如下:

    UART
    start bits char bits even parity stop bits
    ALL UART 1 bit 5 bits;
    6 bits;
    7 bits;
    8 bits
    Y/N 1 bit;
    1.5 bits

    通讯时序图如下图3-1:


    图3-1 UART timing procotol

    3.3 注意事项

    • 外部上拉

      RX一定要接外部上拉,TX建议接外部上拉。

    • FIFO mode

      使用FIFO mode,HW buffer size仅有32byte,当UART无法在buffer被填满之前,及时响应UART中断,进而从HW buffer当中的数据读走,就会出现接收数据丢失的情况。

    3.4 波特率的计算

    波特率是指数据信号对载波的调制速率,它用单位时间内载波调制状态改变的次数来表示,是UART的一个重要的指标。目前的硬件设计UART实际输出的波特率由输入到UART的Clk source和设置的分频值共同确定。波特率(BAUD)、分频值(DIV)以及输入的CLK频率(CLK)三者的关系如下:

    DIV = CLK / (BAUD × 16)
    

    由于给到UART的CLK rate并不是连续的,根据公式得出UART可以支持的波特率(误差3%)也不是连续的。

    波特率的修改

    可以在用户空间的应用程序当中设定。

    也可以通过stty命令修改,以将UART1的波特率修改为115200为例:

     stty -F /dev/sttyS1 ospeed 115200
    

    4. 硬件连接

    1. 标准UART是指不包含任何硬件流控制机制的UART通信方式。它仅依赖于起始位、数据位、奇偶校验位(可选)和停止位来进行数据帧的界定。只是用TX接对端RX,RX接对端TX。

    2. FUART指的是在标准UART的基础上增加了硬件或软件流控制机制,以防止数据丢失并提高通信的可靠性。在标准UART基础上,增加CTX连接对端CTS、CTS连接对端RTS。


      图4-1 UART connect


    5. Uboot用法介绍

    5.1. Uboot Config配置

    在编译Uboot时需要选择的配置如下:

    Device Drivers --->
    Serial drivers --->
        (1) UART used for console
        [*] Enable Driver Model for serial drivers
        [ ]   Enable RX buffer for serial input
        [ ]   Search for serial devices after default one failed
        [ ]   Probe all available serial devices
    

    5.2 Uboot DTS配置

    aliases {
        console = &uart0;
        serial0 = &uart0;
        serial1 = &uart1;
        serial2 = &fuart;
        serial3 = &uart2;
    };
    
    uart0: uart0@1F221000 {
        compatible = "sstar,uart";
        reg = <0x1F221000 0x100>;
        group = <0>;
        char-bits = <8>;
        stop-bits = <1>;
        parity-en = <0>; // 0-disable; 1-odd; 2-even.
        tolerance = <3>;
        status = "okay";
    };
    
    uart1: uart1@1F221200 {
        compatible = "sstar,uart";
        reg = <0x1F221200 0x200>;
        group = <1>;
        char-bits = <8>;
        stop-bits = <1>;
        parity-en = <0>; // 0-disable; 1-odd; 2-even.
        tolerance = <3>;
        status = "okay";
    };
    

    UART DTS配置说明:

    属性
    描述
    备注
    compatible 用于和驱动匹配 禁止修改
    reg IO_address Address_size 禁止修改
    group UART组号 无需修改
    rate 波特率 若需要指定,可以在节点中添加属性,或者在使用过程中调用API动态调节
    char-bits 数据位 若没有这个属性,则默认 8 bits
    stop-bits 停止位 若没有这个属性,则默认 1 bits
    parity-en 奇偶校验 若没有这个属性,默认无校验
    0 : no parity
    1 : odd parity
    2 : even parity
    tolerance 波特率允许误差百分比 数字N代表 N%
    若没有这个属性,则默认3%
    status 节点使能,用于是否使能该UART "okay": 使能
    "disabled": 不使能

    5.3 Uboot cmd参数说明

    参考文件:

    cmd/sstar/uart.c
    

    这里面提供了uart的测试命令,可以在uboot命令行下操作uart,必须先init uart,才能使用putchar发送数据或getchar接收数据。

    1. 初始化uart:

      uart init [port] [baudrate]
      
      参数名称 描述
      port uart序号,对应dts的serial*序号
      baudrate 波特率
    2. 发送数据:

      uart putchar [char]
      
      参数名称 描述
      char 发送的字符串
    3. 接收数据:

      uart getchars size
      
      参数名称 描述
      size 获取数据的个数Bytes

    5.4 Uboot cmd 使用实例

    SigmaStar # uart init 2  115200 // 获取serial2即uart2的设备
    SigmaStar # uart putchar c // 发送字符'c'
    SigmaStar # uart getchar // 接收字符
    

    6. Kernel用法介绍

    6.1. Kernel Config配置

    要将SigmaStar UART驱动编译进kernel中需要在命令行键入make menuconfig进入kernel配置界面,之后打开Serial / UART driver即可,默认是打开的。

    Device Drivers-->
    
        SStar SoC platform drivers-->
    
            [*] SSTAR UART driver
    

    6.2 DTS定义

    pcupid.dtsi中,fuart0与fuart节点段落如下:

    aliases {
        console = &fuart0;
        serial0 = &fuart0;
        serial1 = &fuart1;
        serial2 = &fuart2;
        serial3 = &uart3;
        serial4 = &uart4;
        serial5 = &uart5;
        serial6 = &uart6;
        serial7 = &uart7;
        serial8 = &uart8;
        serial9 = &fuart;
        serial10 = &pm_uart;
    };
    
    fuart0: fuart0@1F221000 {
            compatible = "sstar,uart";
            reg = <0x00 0x1F221000 0x00 0x100>, <0x00 0x1F220E00 0x00 0x100>;
            interrupts= <GIC_SPI INT_IRQ_FUART_0 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_UART0_MERGE IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_UART0_MERGE IRQ_TYPE_LEVEL_HIGH>;
            //dma-enable;
            sctp_enable = <0>;
            rx_fifo_level = <0>;
            tx_fifo_level = <0>;
            digmux = <0>;
            clocks = <&CLK_fuart0>;
            status = "ok";
        };
    
    fuart: fuart@1F220400 {
            compatible = "sstar,uart";
            reg = <0x00 0x1F220400 0x00 0x100>, <0x00 0x1F220600 0x00 0x100>;
            interrupts= <GIC_SPI INT_IRQ_FUART IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_UART_MERGE IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_UART_MERGE IRQ_TYPE_LEVEL_HIGH>;
            dma-enable;
            sctp_enable = <0>;
            rx_fifo_level = <0>;
            tx_fifo_level = <0>;
            digmux = <1>;
            clocks = <&CLK_fuart>;
            status = "ok";
        };
    

    在上面的段落中,aliases定义的uart别名,如serial0最终会注册为/dev/ttyS0,一一绑定,如/dev/ttyS9绑定FUART。

    节点中属性的说明如下:

    属性
    说明
    备注
    compatible 用于匹配驱动注册 禁止修改
    reg IO_address Address_size 无需修改
    interrupts UART中断号于中断类型说明 一个uart有3个中断,顺序为:
    1. UART TX/RX中断
    2. URDMA TX/RX中断
    3. UART 移位寄存器清空的中断
    dma-enable 是否使用DMA mode 注释表示不开启
    sctp_enable HW CTS/RTS enable 1: enable
    0: disable
    rx_fifo_level 接收中断水位设定 FIFO mode生效;水位设定如下:
    0: 1 character in FIFO
    1: ¼ FIFO full
    2: ½ FIFO full
    3: FIFO 2 less than full
    tx_fifo_level 发送中断水位设定 FIFO mode生效;水位设定如下:
    0: FIFO empty
    1: 2 characters in FIFO
    2: ¼ FIFO full
    3: ½ FIFO full
    digmux 用于交换不同uart的pad 默认不需要修改,如果uart1的pad给uart2使用,uart2的pad给uart1使用,只需要将uart1与uart2的digmux值互换
    clocks 时钟节点 禁止修改
    status 节点使能,用于是否使能该UART "ok": enable
    "disabled": disable

    注意: pm_uart没有digmux,所有uart的digmux值不能重复。

    6.3 PADMUX配置

    UART/FUART在Uboot及Kernel环境下的padmux配置方法一致,只需要根据选择的引脚在对应的padmux.dtsi中,UART0的padmux一般不需要配置,下面是配置FUART padmux的范例。

    <PAD_GPIOE_11     PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_TX>,
    <PAD_GPIOE_10     PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_RX>,
    <PAD_GPIOE_13     PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_RTS>,
    <PAD_GPIOE_12     PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_CTS>,
    

    第一列为引脚索引号,可以在/drivers/sstar/inlcude/{chipname}/gpio.h中查到;

    第二列为模式定义,在/drivers/sstar/gpio/{chipname}/hal_pinmux.c中m_hal_gpio_st_padmode_info_tbl数组里,罗列了所有引脚的复用关系,查询该引脚支持哪些复用功能可以查询该数组;

    第三列为引脚及搭配模式的索引名称,可在/drivers/sstar/include/drv_puse.h里查询。

    6.4 模块使用介绍

    用户空间应用程序的使用,基本流程为:

    1. 打开设备节点

    2. uart配置

    3. 读写调用

    举例:串口连接主机pc,使用串口工具测试发送与接收数据。源码参考6.5.Sample code章节

    /customer # echo 987654 > 1.txt
    /customer # ./uart_ut  -D /dev/ttyS1 -B 115200 -p 1.txt
    


    图6-4 UART ut test

    6.5 Sample code

    此demo提供参考读写uart操作,可根据需求更改,可配置参数:

    属性 说明
    -D 用于指定设备节点;如 "-D /dev/ttyS1"
    -B 用于指定波特率,如"-B 9600"
    -C 用于开启流控控制功能
    -p 用于指定发送的数据
    -g 用于指定接收数据保存到文件
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/time.h>
    #include <time.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <termios.h>
    #include <stdint.h>
    #include <assert.h>
    #include <netinet/in.h>
    #include <signal.h>
    #include <getopt.h>
    
    #define msleep(x) usleep(x * 1000)
    #define TX_SIZE   64
    #define RX_SIZE   256
    
    static char *  device = "/dev/ttyS1";
    static int     options_flag;
    static speed_t speed;
    static char *  put_file = NULL;
    static char *  get_file = NULL;
    
    typedef unsigned int u32;
    #define TABLE_LEN 31
    
    static const u32 baud_bits[TABLE_LEN] = {B4000000, B3500000, B3000000, B2500000, B2000000, B1500000, B1152000, B1000000,
                                            B921600,  B576000,  B500000,  B460800,  B230400,  B115200,  B57600,   B38400,
                                            B19200,   B9600,    B4800,    B2400,    B1800,    B1200,    B600,     B300,
                                            B200,     B150,     B134,     B110,     B75,      B50,      B0};
    
    static const u32 baud_table[TABLE_LEN] = {4000000, 3500000, 3000000, 2500000, 2000000, 1500000, 1152000, 1000000,
                                            921600,  576000,  500000,  460800,  230400,  115200,  57600,   38400,
                                            19200,   9600,    4800,    2400,    1800,    1200,    600,     300,
                                            200,     150,     134,     110,     75,      50,      0};
    
    /* Print all baud rates */
    void buad_help(void)
    {
        int i = 0;
        int j = 0;
    
        printf("support buad: \n");
    
        for (i = 0; i < TABLE_LEN; i++)
        {
            j = (i + 1) % 8;
            if (j == 0)
                printf("\n");
            printf(" %8d ", baud_table[i]);
        }
        printf("\n");
    }
    
    /* ut supports commands */
    static void print_usage(const char *prog)
    {
        printf("Usage: %s [-DBCpg]\n", prog);
        puts(
            "  -D --device   device to use (default /dev/ttyS1)\n"
            "  -B --baud     baud rate (bps)\n"
            "  -C --flow     flow Control\n"
            "  -p --put_file put file \n"
            "  -g --get_file get file\n");
        exit(1);
    }
    
    /* Get the configured baud rate */
    static int get_baud_rate(void)
    {
        int i = 0;
        for (i = 0; i < TABLE_LEN; i++)
        {
            if (speed == baud_table[i])
            {
                printf("baud_table[i]: %d\n", baud_table[i]);
                return baud_bits[i];
            }
        }
    
        buad_help();
        return -1;
    }
    
    /* Open the uart node and configure uart */
    int uart_open(char *name, int baud, int flag, int timeout_ms)
    {
        int            fd;
        int            ret;
        struct termios options;
    
        fd = open(name, flag);
    
        if (fd == -1)
        {
            printf("%s: open error\n", name);
            return -1;
        }
        ret = tcgetattr(fd, &options);
        if (-1 == ret)
        {
            return -1;
        }
    
        options.c_cflag &= ~CSIZE;
        options.c_cflag |= CS8;
        options.c_cflag |= CLOCAL | CREAD;
        options.c_cflag |= PARENB;
        options.c_iflag &= ~PARODD;
        options.c_iflag &= ~INPCK;
        options.c_cflag &= ~CSTOPB;
    
        options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    
        if (!timeout_ms)
        {
            options.c_cc[VMIN]  = 1;
            options.c_cc[VTIME] = 0;
        }
        else
        {
            options.c_cc[VMIN]  = 0;
            options.c_cc[VTIME] = timeout_ms / 100;
        }
    
        ret = cfsetispeed(&options, baud);
        if (ret)
        {
            printf("cfsetispeed err: %d\n", ret);
        }
        ret = cfsetospeed(&options, baud);
        if (ret)
        {
            printf("cfsetispeed err:%d \n", ret);
        }
        options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    
        options.c_cflag |= options_flag;
        tcflush(fd, TCIFLUSH);
    
        ret = tcsetattr(fd, TCSANOW, &options);
        if (-1 == ret)
        {
            return -1;
        }
    
        return fd;
    }
    
    /* close */
    static int uart_close(int fd)
    {
        return close(fd);
    }
    
    /* uart write */
    static size_t uart_write(int fd, void *buf, size_t count)
    {
        size_t  left_count = count;
        ssize_t real_count = 0;
    
        do
        {
            real_count = write(fd, buf, left_count);
    
            if (0 == real_count)
            {
                printf("write timeout !!!\n");
                break;
            }
    
            if (0 > real_count)
            {
                printf("write fail !!!\n");
                break;
            }
    
            buf += real_count;
            left_count -= real_count;
        } while (left_count);
    
        count -= left_count;
    
        return count;
    }
    
    /* uart read */
    static size_t uart_read(int fd, void *buf, size_t count)
    {
        size_t  left_count = count;
        ssize_t real_count = 0;
    
        do
        {
            real_count = read(fd, buf, left_count);
    
            if (0 == real_count)
            {
                break;
            }
    
            if (0 > real_count)
            {
                printf("read fail !!!\n");
                break;
            }
    
            buf += real_count;
            left_count -= real_count;
        } while (left_count);
    
        count -= left_count;
    
        return count;
    }
    
    /* Parsing parameters */
    static void parse_opts(int argc, char *argv[])
    {
        int c;
        options_flag = 0;
    
        while (1)
        {
            static const struct option lopts[] = {
                {"device", 1, 0, 'D'},   {"baud", 1, 0, 'B'},     {"flow", 0, 0, 'C'},
                {"put_file", 1, 0, 'p'}, {"get_file", 1, 0, 'g'}, {NULL, 0, 0, 0},
            };
    
            c = getopt_long(argc, argv, "D:B:Cp:g:", lopts, NULL);
    
            if (c == -1)
                break;
    
            switch (c)
            {
                case 'D':
                    device = optarg;
                    break;
                case 'B':
                    speed = atoi(optarg);
                    break;
                case 'C':
                    options_flag |= CRTSCTS;
                    break;
                case 'p':
                    put_file = optarg;
                    break;
                case 'g':
                    get_file = optarg;
                    break;
                default:
                    print_usage(argv[0]);
            }
        }
    }
    
    int main(int argc, char **argv)
    {
        int         fd           = 0;
        int         ret          = 0;
        u32         baud         = 0;
        long        filesize     = 0;
        FILE *      stream       = NULL;
        char *      write_buffer = NULL;
        char *      read_buffer  = NULL;
        struct stat put_statbuf;
        char *      ack_buf = "uart";
    
        parse_opts(argc, argv);
    
        ret = get_baud_rate();
        if (-1 == ret)
        {
            return -1;
        }
        else
        {
            baud = ret;
        }
    
        fd = uart_open(device, baud, O_RDWR | O_NOCTTY | O_SYNC, 3000);
        if (-1 == fd)
        {
            return -1;
        }
    
        if (put_file)
        {
            stat(put_file, &put_statbuf);
    
            filesize = put_statbuf.st_size;
    
            printf("put file size : %ld\n", filesize);
    
            write_buffer = malloc(filesize);
            if (!write_buffer)
            {
                printf("malloc write_buffer fail !!!\n");
                goto out;
            }
    
            stream = fopen(put_file, "r");
            if (!stream)
            {
                printf("fopen %s fail !!!\n", put_file);
                goto out;
            }
    
            fread(write_buffer, 1, filesize, stream);
    
            ret = uart_write(fd, write_buffer, filesize);
            if (filesize != ret)
            {
                printf("write %s fail !!!\n", put_file);
                goto out;
            }
    
            memset(write_buffer, 0, filesize);
    
            printf("Wait for UartBurnTool transfer binary to SOC\n");
            uart_read(fd, write_buffer, strlen(ack_buf));
    
            ret = strcmp(write_buffer, ack_buf);
            if (!ret)
            {
                printf("ack, compare PASS\n");
            }
            else
            {
                printf("ng, compare FAIL\n");
            }
    
            goto out;
        }
    
        if (get_file)
        {
            printf("get file test of 32KBytes ... \n");
            read_buffer = malloc(0xA000);
            if (!read_buffer)
            {
                printf("malloc read_buffer fail !!!\n");
                goto out;
            }
    
            stream = fopen(get_file, "w");
            if (!stream)
            {
                printf("fopen %s fail !!!\n", get_file);
                goto out;
            }
    
            printf("Wait for UartBurnTool transfer binary to SOC\n");
            filesize = uart_read(fd, read_buffer, 0x8000);
            while (0 != filesize)
            {
                fwrite(read_buffer, 1, filesize, stream);
                if (filesize == 0x8000)
                {
                    break;
                }
            }
            printf("\r\n [fwrite done] \r\n");
            // uart_write(tty_fd, read_buffer, 0x800);
    
            goto out;
        }
    
    out:
    
        if (write_buffer)
        {
            free(write_buffer);
        }
    
        if (read_buffer)
        {
            free(read_buffer);
        }
    
        if (stream)
        {
            fclose(stream);
        }
    
        if (fd)
        {
            uart_close(fd);
        }
    
        return 0;
    }
    

    7. FAQ

    Q1:URDMA-接收数据被分段

    1. 复现uart2 tx和rx 自环,在soc板子上执行如下命令:

      stty -F /dev/ttyS2 speed 9600 -icanon -echo
      
      cat /dev/ttyS2 &
      
      echo "0123456789101112" > /dev/ttyS2
      


      图7-1 UART rx not continuous

    2. 原因分析

      DMA rx的中断机制:

      1、rx buff数据量达到threshold的设定,触发中断并将buff中的数据搬走;

      2、rx等待数据量达到threshold的时间超时,也会触发中断将buff中的数据搬走。

      分析实验,可以看到,发送的总的数据量不大,远低于预设的0x500 threshold,也就是说,是第二步触发的中断过快,导致一批数据,被分成两段发送到tty层。

    3. 解决

      由于是rx timeout时间小于波特率码元传输间隔,导致数据分段。因此,不同的波特率下,应该设置不同的rx timeout时间,使得rx timeout value > 码元传输间隔

      公式:

      cycle_time(时钟单位)  = 1/clk_mcu * 1000000000 ns
      urdma timeout value: = N
      

      则:

      t1 = cycle_time * 2 ^ N   :rx等待超时时间
      
      t2 = 1000000000 / request_baud * 10(bits) 传输一个码元的时间
      

      通过调整urdma rx timeout 寄存器(设置N),只要满足t1 > t2,即可解决分段问题。

    Q2: 接收数据丢失

    1. UART 接收到的数据和发送给UART的数据对比会差一部分数据,分为2种情况:1、丢失的数据有规律,只丢失0x11和0x13且0x0A变成了0x0D;2、丢失的数据没有规律。第一种情况只需要在初始化串口时加上options.c_iflag &= ~(ICRNL | IXON);

    2. UART RX接收数据丢失的第二种情况原因是UART发出的Received Data Available中断没有被CPU及时处理,有2种情况会导致UART中断没有得到及时的处理:

      1、UART出中断的时候恰好有其他的中断正在处理,因为Linux没有中断抢占机制所以UART的中断会延时被处理;

      2、UART出中断的时候恰好Linux中断被关了(spin_lock_irqsave等),这样UART的中断也会被延时处理。一般都是第二情况导致的RX接收数据丢失,只有在波特率很高时才会遇到第一种情况引起UART RX丢数据。可以通过在UART中断和Linux中断处理入口处拉选取的GPIO来确定,用LA抓UARTRX和选取的GPIO波形。正常情况的波形如图7-2,可以看到RX脚接收到一个Byte数据就会唤醒一次RX中断将FIFO中数据取走,出错的情况如图7-3,这时候RX中断间隔了16MS才被唤醒一次,我们硬件的RX FIFO长度是32Byte,在典型的115200波特率的情况下不间断接收数据只要2.78ms中断没有被处理就会因为FIFO满而丢数据,所以只要系统中存在超过2.78ms以上的关中断理论上就会引起UART接收丢数据。


      图7-2 UART no data loss


      图7-3 UART data loss

      计算方法:

      8N1协议的情况下,8bit 数据位,1bit停止位,1bit起始位,无校验位,即通讯1byte数据需要占用10bit的位宽。

      波特率-概念:串口通信的速率,单位bps(bits per second),即每秒钟可以传输的二进制位的个数。

      比如115200,就是每秒钟可以传输115200各二进制位,也就是115200bit

      在115200的波特率下,32byte的fifo深度,可等待时间的计算 :

      time = (fifo深度 * 每byte占用的位宽) / 波特率
      
              = (32 * 10) / (115200) S
      
              = 2.78ms
      
    3. 解决方法

      如果发生丢数据的UART波特率很高,这时候处理其他中断也有可能造成挡到UART RX中断太久导致丢数据,这时候开启urdma才能保证数据不会丢失。

    Q3: stty设波特率大于1M失败

    1. 使用stty命令设置波特率大于1M时设置失败

      / # stty -F /dev/ttyS2 1000000
      
      stty: invalid argument '1000000'
      
    2. 说明

      busybox的问题,在busybox版本1.26.x之后就可以设置大于1M的波特率。

    3. 解决方法

      如果需要设定大于1M的波特率,可使用C PROGRAM设定。