UART使用参考¶
REVISION HISTORY¶
| Revision No. | Description |
Date |
|---|---|---|
| 1.0 | 07/21/2023 | |
| 1.1 | 11/08/2025 |
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 | 1102 | 1107 | 110E |
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.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. 硬件连接¶
5. Uboot用法介绍¶
5.1 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.2 驱动路径¶
drivers/sstar/uart/drv_uart.c
drivers/sstar/uart/drv_uart.h
drivers/sstar/uart/os/os_uart.h
drivers/sstar/uart/iford/hal_uart.c
drivers/sstar/uart/iford/hal_uart.h
drivers/sstar/uart/iford/hal_uart_cfg.h
5.3 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.4 引脚复用¶
即PADMUX,用于将UART信号连接到某组具体的引脚上。设定修改在iford-xxx-padmux.dtsi当中,以便统一管理。第一列为引脚号定义,第二列为引脚复用的padmod编号,第三列puse为功能定义。需要注意的是,第一列的引脚号不能重复,若重复则编译检测失败;对应的第二列的padmod编号与引脚号要匹配的上,如果匹配不上,也编译失败。
arch/arm/dts/iford-ssc029a-s01a-padmux.dtsi
padmux {
compatible = "sstar,padmux";
schematic =
...
<PAD_PM_FUART_RX PINMUX_FOR_FUART_2W_MODE_2 MDRV_PUSE_FUART_RX>,
<PAD_PM_FUART_TX PINMUX_FOR_FUART_2W_MODE_2 MDRV_PUSE_FUART_TX>,
...
};
5.5 软件使用流程¶
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));
如果对协议格式有需求,可以在iford.dtsi当中对应的uart节点当中修改属性值。
注意,在要使用对应uart时,它在dtsi文件当中的节点,一定要使能!
需要说明的是,在获取uclass device时,参数'int seq'在这里指代serial number,因为我们在dtsi文件中,定义了aliases,所有uart device与serial number对应注册进管理链表中。
// arch/arm/dts/iford.dtsi
...
aliases {
console = &uart0;
serial0 = &uart0;
};
比如要进行uart1的操作,那uart1就填入'1',这里并没有uart1,可以自行添加serial1 = &uart1,同时在设备树文件中添加uart1的node。
5.6 Uboot cmd参数说明¶
参考文件:
cmd/sstar/uart.c
这里面提供了uart的测试命令,可以在uboot命令行下操作uart,同时也是作为使用范例,可供使用者参考。
命令如下:
-
初始化uart:
uart init [port] [baudrate]参数名称 描述 port uart序号,对应dts的serial*序号 baudrate 波特率 -
发送数据:
uart putchar [char]参数名称 描述 char 发送的字符串 -
接收数据:
uart getchars size参数名称 描述 size 获取数据的个数Bytes
5.7 Uboot cmd 使用实例¶
SigmaStar # uart init 1 115200 // 获取serial1即uart1的设备
SigmaStar # uart putchar c // 发送字符'c'
SigmaStar # uart getchar // 接收字符
6. Kernel用法介绍¶
6.1 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.2 驱动路径¶
drivers/sstar/uart/drv/drv_uart.c
drivers/sstar/uart/drv/drv_uart_ioctl.h
drivers/sstar/uart/hal/iford/hal_uart.c
drivers/sstar/uart/hal/iford/hal_uart.h
6.3 DTS定义¶
iford.dtsi中,uart与fuart节点段落如下:
aliases {
console = &uart0;
serial0 = &uart0;
serial1 = &uart1;
serial2 = &fuart;
};
uart0: uart0@1F221000 {
compatible = "sstar,uart";
reg = <0x1F221000 0x100>, <0x1F220E00 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.4 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.5 模块使用介绍¶
用户空间应用程序的使用,基本流程为:
- 打开设备节点
- uart配置
- 读写调用
举例:串口连接主机pc,使用串口工具测试发送与接收数据。源码参考6.6.Sample code章节
/customer # echo 987654 > 1.txt
/customer # ./uart_ut -D /dev/ttyS1 -B 115200 -p 1.txt
6.6 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-接收数据被分段
-
复现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原因分析
DMA rx的中断机制:
1、rx buff数据量达到threshold的设定,触发中断并将buff中的数据搬走;
2、rx等待数据量达到threshold的时间超时,也会触发中断将buff中的数据搬走。
分析实验,可以看到,发送的总的数据量不大,远低于预设的0x500 threshold,也就是说,是第二步触发的中断过快,导致一批数据,被分成两段发送到tty层。
解决
由于是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: 接收数据丢失
-
UART 接收到的数据和发送给UART的数据对比会差一部分数据,分为2种情况:1、丢失的数据有规律,只丢失0x11和0x13且0x0A变成了0x0D;2、丢失的数据没有规律。第一种情况只需要在初始化串口时加上options.c_iflag &= ~(ICRNL | IXON);
-
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解决方法
如果发生丢数据的UART波特率很高,这时候处理其他中断也有可能造成挡到UART RX中断太久导致丢数据,这时候开启urdma才能保证数据不会丢失。
Q3: stty设波特率大于1M失败
-
使用stty命令设置波特率大于1M时设置失败
/ # stty -F /dev/ttyS2 1000000 stty: invalid argument '1000000' -
说明
busybox的问题,在busybox版本1.26.x之后就可以设置大于1M的波特率。
-
解决方法
如果需要设定大于1M的波特率,可使用C PROGRAM设定。