UART使用参考


REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 08/26/2024

    1. 概述

    1.1 UART

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

    1.2 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资源

    共提供了2组UART与1组FUART,全部支持DMA mode,但只有FUART支持HW CTS/RTS。

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

    UART IP FUART UART0 UART1
    BANK ADDR 1102 1108 1109
    URDMA IP URDMA URDMA0 URDMA1
    BANK ADDR 1103 1106 1107

    3.2 功能支持

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

    功能 FIFO mode FIFO buffer size(byte) DMA mode DMA buffer size(byte) HW CTS/RTS baudrate protocol
    支持情况 32 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

    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为例:

    1.  stty -F /dev/sttyS1 ospeed 115200
    

    4. 硬件连接介绍


    图4-1 UART connect


    5. Uboot使用介绍

    5.1 Uboot DM-arch调用框架


    图5-1 UART uboot arch

    5.2 config配置

    uboot下使用UART,需要开启SigmaStar UART驱动编译选项,这个默认build-in,因为console driver也在uart driver里面。另外需要打开CONFIG_DM_SERIAL。如下:

    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
    

    只要打开'Enable Driver Model for serial drivers','UART used for console'就会被自动选上。

    5.3 驱动路径

    drivers/sstar/uart/drv_uart.c
    drivers/sstar/uart/drv_uart.h
    drivers/sstar/uart/os/os_uart.h
    drivers/sstar/uart/ifado/hal_uart.c
    drivers/sstar/uart/ifado/hal_uart.h
    drivers/sstar/uart/ifado/hal_uart_cfg.h
    

    5.4 DTS定义

        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";
        };
    
    属性
    说明
    备注
    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.5 引脚复用

    即PADMUX,用于将UART信号连接到某组具体的引脚上。设定修改在ifado-padmux.dtsi当中,以便统一管理。第一列为引脚号定义,第二列为引脚复用的padmod编号,第三列puse为功能定义。需要注意的是,第一列的引脚号不能重复,若重复则编译检测失败;对应的第二列的padmod编号与引脚号要匹配的上,如果匹配不上,也编译失败。

    arch/arm/dts/ifado-padmux.dtsi
    
    padmux {
            compatible = "sstar,padmux";
            schematic =
            ...
            <PAD_PM_GPIO3        PINMUX_FOR_UART1_MODE_6   MDRV_PUSE_FUART_RX>,
            <PAD_PM_GPIO7        PINMUX_FOR_FUART_2W_MODE_2   MDRV_PUSE_FUART_TX>,
            ...
            };
    

    5.6 软件使用流程

    DM架构,在启动流程当中,会自动完成device与driver的匹配,若匹配成功,自动执行'.of_to_plat'与'.probe'方法,完成软硬件的初始化与设定。因此在使用时,我们需要做的是:获取uclass device,获取ops方法,调用相对应的函数指针执行操作。

    // uclass_id 必须为 UCLASS_SERIAL
    ret = uclass_get_device_by_seq(UCLASS_SERIAL, seq, &udev);
    ...
    // 获取执行方法
    ops = serial_get_ops(udev);
    ...
    // 设定通信波特率
    if (ops && ops->setbrg)
        ops->setbrg(udev, baud);
    ...
    // 发送字符
    if (udev && ops && ops->putc)
        ops->putc(udev, c);
    ....
    // 接收字符
    if (udev && ops && ops->getc)
        printf("%x\r\n", ops->getc(udev));
    

    如果对协议格式有需求,可以在ifado.dtsi当中对应的uart节点当中修改属性值。

    注意,在要使用对应uart时,它在dtsi文件当中的节点,一定要使能!

    需要说明的是,在获取uclass device时,参数'int seq'在这里指代serial number,因为我们在dtsi文件中,定义了aliases,所有uart device与serial number对应注册进管理链表中。

    // arch/arm/dts/ifado.dtsi
    ...
    aliases {
        console = &uart0;
        serial0 = &uart0;
    };
    

    比如要进行uart1的操作,那uart1就填入'1',这里并没有uart1,可以自行添加serial1 = &uart1,同时在设备树文件中添加uart1的node。

    5.7 uboot 测试命令

    参考文件:

    cmd/sstar/uart.c
    

    这里面提供了uart的测试命令,可以在uboot命令行下操作uart,同时也是作为使用范例,可供使用者参考。

    命令如下:

    SigmaStar # uart init 1  115200 // 获取serial1即uart1的设备
    SigmaStar # uart putchar c // 发送字符'c'
    SigmaStar # uart getchar // 接收字符
    

    6. Kernel使用介绍

    6.1 kernel调用框架


    图6-1 UART kernel arch

    6.2 config配置

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

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

    将光标移到Serial / UART driver之后按下空格即可操作:

    *号为将UART driver直接编入kernel。

    6.3 驱动路径

    drivers/sstar/uart/drv/drv_uart.c
    drivers/sstar/uart/drv/drv_uart_ioctl.h
    drivers/sstar/uart/hal/ifado/hal_uart.c
    drivers/sstar/uart/hal/ifado/hal_uart.h
    

    6.4 DTS定义

    ifado.dtsi中,uart与fuart节点段落如下:

        aliases {
            console = &uart0;
            serial0 = &uart0;
            serial1 = &uart1;
            serial2 = &fuart;
        };
    ......
        uart0: uart0@1F221000 {
            compatible = "sstar,uart";
            reg = <0x1F221000 0x100>, <0x1F220C00 0x100>;
            interrupts= <GIC_SPI INT_IRQ_FUART_0 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_URDMA_0 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_FUART0_EMPTY IRQ_TYPE_LEVEL_HIGH>;
            dma = <1>;
            status = "ok";
            clocks = <&CLK_fuart0>;
        };
    ......
        fuart: fuart@1F220400 {
            compatible = "sstar,uart";
            reg = <0x1F220400 0x100>, <0x1F220600 0x100>;
            interrupts= <GIC_SPI INT_IRQ_FUART IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_URDMA IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI INT_IRQ_FUART_EMPTY IRQ_TYPE_LEVEL_HIGH>;
            dma = <1>;
            sctp_enable = <0>;
            rx_fifo_level = <0>;
            tx_fifo_level = <0>;
            digmux = <0xFF>;
            tolerance = <3>;
            status = "ok";
            clocks = <&CLK_fuart>;
        };
    

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

    节点中属性的说明如下:

    属性
    说明
    备注
    compatible 用于匹配驱动注册 禁止修改
    reg IO_address Address_size 无需修改
    interrupts UART中断号于中断类型说明 一个uart有3个中断,顺序为:
    1. UART TX/RX中断
    2. URDMA TX/RX中断
    3. UART 移位寄存器清空的中断
    dma 是否使用DMA mode 1: enable
    0: disable
    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 digmux设定 默认0xFF,无需修改
    tolerance 波特率允许误差范围百分比 如:
    =<3> 表示 3%
    clocks 时钟节点 禁止修改
    status 节点使能,用于是否使能该UART "ok": enable
    "disabled": disable

    6.5 PADMUX设置

    在xxx-padmux.dtsi中配置mode格式如下图的,"<>"中第一个值为PAD值,第二个值为要设置的mode,第三个值为PAD在该mode下对应的功能。根据tmux表中的对应关系配置xxx-padmux.dtsi配置PADMUX,UART0的padmux一般不需要配置,下面是配置FUART padmux的范例。

    1. <PAD_FUART_TX      PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_TX>,
    2. <PAD_FUART_RX      PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_RX>,
    3. <PAD_FUART_RTS     PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_RTS>,
    4. <PAD_FUART_CTS     PINMUX_FOR_FUART_MODE_1     MDRV_PUSE_FUART_CTS>,
    

    6.6 测试用例介绍

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

    1. 打开设备节点
    2. uart配置
    3. 读写调用

    测试UART功能常用方式是将UART的TX脚和RX脚短接,配置好后使用测试程序进行自发自收测试,量取UART TX脚上的波形解析波特率看是否和设置的相同,并查看测试程序打印接收到的数据是否和发送的数据相同。

    截取部分测试用例如下:

        int init_serial_device(char *name, int baud)
        {
            int fd;
            int ret;
            struct termios options;
    
            fd = open(name, O_RDWR | O_NDELAY | O_NOCTTY);
    
    
            ret = tcgetattr(fd, &options);
            if (-1 == ret)
                return -1;
    
            options.c_cflag &= ~CSIZE;    //屏蔽其他标志
            options.c_cflag |= CS8;        //将数据位修改为8bit
            options.c_cflag &= ~PARENB; //无校验
            options.c_cflag &= ~CSTOPB; // 设置一位停止位;
    
            options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
            cfsetispeed(&options, baud);
            cfsetospeed(&options, baud);
            options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
            ret = tcsetattr(fd, TCSANOW, &options);
            if (-1 == ret)
                return -1;
            return fd;
        }
    
        void *ST_ReadProcess(void *pArgs)
        {
            int *fd = (int *)pArgs;
            int ret = 0;
            char szBuf[1024];
            int len = 0;
            int i = 0;
    
            fd_set read_fds;
            struct timeval TimeoutVal;
    
            while(1)
            {
                FD_ZERO(&read_fds);
                FD_SET(*fd, &read_fds);
    
                TimeoutVal.tv_sec  = 1;
                TimeoutVal.tv_usec = 0;
    
                ret = select(*fd + 1, &read_fds, NULL, NULL, &TimeoutVal);
                if(ret < 0)
                ......
                else
                {
                    if(FD_ISSET(*fd, &read_fds))
                    {
                        memset(szBuf, 0, sizeof(szBuf));
    
                        len = read(*fd, szBuf, sizeof(szBuf) -1);
                        ......
                    }
                }
            }
    
            return NULL;
        }
    

    开启fuart硬件流控的测试代码如下:

        int enable_rtscts(int fd)
        {
            struct termios options;
            if(tcgetattr(fd, &options) != 0)
            {
                perror("SetupSerial 1");
                return -1;
            }
            options.c_cflag |= CRTSCTS;
            tcflush(fd, TCIFLUSH);
            if(tcsetattr(fd,TCSANOW, &opyions) != 0)
            {
                perror("SetupSerial 3");
                return -1;
            }
            return 0;
        }
    
        int main(int argc, char **argv)
        {
            int fd;
            fd = open("/dev/ttyS2",  O_RDWR|O_NOCTTY|O_NDELAY);
            if(fd <= 0)
            {
                printf("Can't Open Serial Port!\n");
                return -1;
            }
            if(enable_rtscts(fd) == -1)
            {
                printf("Set Parity Error\n");
                exit(1);
            }
            printf("ebable rts cts\n");
            close(fd);
            return 0;
        }