# UART 驱动调试指南

## 概述
芯片提供了 8 路串口，分别为 UART0 至 UART7 ，其中 UART0 用作调试串口，

- UART1 和 UART7 支持硬件自动流控。
- 这些串口支持多种比特率，包括 115200 bps、 230400 bps、 460800 bps、 921600 bps、 1500000 bps、 2000000 bps 和 4000000 bps。
- 支持基于中断或基于 DMA 的传输模式。


## 功能描述
### 典型应用
UART 通常用于设备之间的异步通信，例如连接微控制器与计算机、传感器与数据采集设备等。 UART 可以用于调试、数据传输、设备控制等多种场景。

我们常遇到需要使用串口调试的情况，即计算机与微控制器通过串口进行信息交换。调试时，常用的配置如下：

| 参数     | 值       |
|----------|----------|
| 波特率   | 115200   |
| 数据位   | 8        |
| 校验位   | 无       |
| 停止位   | 1        |
| 流控制   | 无       |

### 功能原理

UART 一般需要三根线， TX、 RX、 GND， TX 作为数据发送， RX 作为数据接收， GND 提供基准电位。

![uart_driver_single_link](_static/_images/27-Uart_Driver_Debug_Guide/uart_driver_single_link.png)

需要注意的是每个设备的 TX 和 RX 是以当前设备为主体，也就是当前设备的 RX 负责是收数据，需要和其他设备的 TX 连接才能正常通信。

UART 通过串行接口发送和接收数据，数据以帧的形式传输，每帧包含起始位、数据位、可选的校验位和停止位等等。


![instruction_diagram_of_frame_data_1](_static/_images/27-Uart_Driver_Debug_Guide/instruction_diagram_of_frame_data_1.png)

说明如下：

- 起始位：发送 1 位逻辑 0 （低电平），开始传输数据。
- 数据位：可以是 5~8 位的数据，先发低位，再发高位，一般常见的就是 8 位（ 1 个字节），其他的如 7 位的 ASCII 码。
- 校验位：奇偶校验，将数据位加上校验位， 1 的位数为偶数（偶校验）， 1 的位数 4 为奇数（奇校验）。
- 停止位：停止位是数据传输结束的标志，可以是 1/1.5/2 位的逻辑 1 （高电平）。
- 空闲位：空闲时数据线为高电平状态，代表无数据传输。


![instruction_diagram_of_frame_data_2](_static/_images/27-Uart_Driver_Debug_Guide/instruction_diagram_of_frame_data_2.png)


如果我们传输数据 0X33 （ 00110011 ），那么对应的波形就是如上这样，因为是 LSB 在前，所以 8 位数据依次是 11001100

如果再发其他数据，再依次循环这个过程即可。



### 工作方式

- 中断模式：数据接收和发送通过中断处理，适用于数据量较小或对实时性要求较高的场景。

- DMA 模式：数据通过 DMA 搬运，适用于数据量较大或需要高吞吐量的场景。
除 UART0 作为内核的默认 UART 输出， Linux 禁止使用 DMA 搬运 ; 其他 UART 均支持使用 DMA 搬运。
详细可以查看 **设备树节点配置** 部分。

## 驱动代码
### uboot 下驱动代码
dtsi 文件位置 :\
uboot/arch/arm/dts/x5.dtsi
```
dsp_uart: serial@32120000 {
	compatible = "ns16550a";  /*指定 UART 控制器的兼容类型*/
	reg = <0x32120000 0x10000>;  /*定义 UART 寄存器的基地址和地址空间大小*/
	clock-frequency = <UART_CLK_FREQ>;  /*指定 UART 的输入时钟频率*/
	reg-shift = <2>;  /*寄存器访问的偏移量*/
	current-speed = <115200>;  /*UART 使用的波特率115200*/
	u-boot,dm-pre-reloc;  /*表示此设备在 u-boot 的早期阶段需要初始化*/
	status = "okay";  /*表示启用该设备*/
};
```

驱动文件：\
uboot/drivers/serial/ns16550.c

### Kernel 下驱动代码以及配置、设备树

#### 代码路径

```
drivers/tty/serial/8250/8250_dw.c
drivers/tty/serial/8250/8250_dwlib.c
drivers/tty/serial/8250/8250_dwlib.h
```


#### 内核配置

SERIAL_8250_DW

SERIAL_8250_DWLIB

![Kernel_deconfig](_static/_images/27-Uart_Driver_Debug_Guide/kernel_deconfig.png)


#### 设备树节点配置

UART 控制器的设备树定义位于 SDK 包的 Kernel 文件夹下的 `arch/arm64/boot/dts/hobot/x5.dtsi` 文件内。

X5 的 UART 控制器默认关闭，当需要使能对应的串口时，可以到具体的板子配置设备树中修改、添加自定义配置。 \
例如在 x5-evb.dts 文件内使能 uart0 、 2 、 5 ：
```
/* arch/arm64/boot/dts/hobot/x5-evb.dts */
...
&uart0 {
	status = "okay";
};

&uart2 {
	status = "okay";
	pinctrl-names = "default"; /* 定义管脚控制的名称组，默认为 "default" */
	pinctrl-0 = <&pinctrl_uart2>;  /* 将 pinctrl_uart2 的管脚配置应用到 UART2 */
	...
};

&uart5 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_uart5>;
};
...
```
<font color=red> 备注：</font>
x5.dtsi 中的节点主要声明 SoC 共有特性，和具体电路板无关，一般情况下不用修改。

##### DTS 配置 DMA 绑定
X5 所有 UART 均支持使用 DMA 搬运。\
以 UART7 为例：
```dts
&uart7 {
	status = "okay";  /*启用该设备*/
	pinctrl-names = "default";  /*定义管脚控制的名称组，默认为 "default*/
	pinctrl-0 = <&pinctrl_uart7>;  /*将 pinctrl_uart7 的管脚配置应用到 UART7*/
	dma-names = "tx", "rx";  /*定义 DMA 通道的名称，分别为 tx 和 rx*/
	dmas = <&axi_dmac 1>, <&axi_dmac 0>;  /*分别为 UART7 配置发送和接收的 DMA 通道 ， 0用于接收，1用于发送*/
}
```
<font color=red> 注意：</font>
- UART7 在 EVB 上默认功能为 GPIO，如果需要 UART7 的话，需要首先从 `ls_gpio0_porta` 内将 UART7 对应的 PIN（ `lsio_gpio0_0`~`lsio_gpio0_3` ）删掉；
- UART0 作为内核的默认 UART 输出， Linux **禁止** 使用 DMA 搬运。

UART DMA 握手绑定列表如下：

|                | RX | TX |
|----------------|----|----|
| UART1           |  2 |  3 |
| UART2           |  4 |  5 |
| UART3           |  6 |  7 |
| UART4           |  8 |  9 |
| UART5           | 35 | 36 |
| UART6           | 37 | 38 |
| UART7           |  0 |  1 |

## 功能使用
### uboot 阶段使用
查看波特率：
```
printenv baudrate
```
设置波特率 :
```
setenv bootargs "console=tty1 console=ttyS0,115200"
saveenv
```

### kenel 阶段使用
Kernel 阶段的使用主要是通过配置设备树，以及查询 Kernel 启动参数等形式。

例如：
修改设备树，关闭某些串口
```
&uart5 {
	status = "disabled"; /*关闭 UART5*/
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_uart5>;
};
```

或者通过如下命令查看 Kernel 启动参数，可以看到 `console=ttyS0,115200n8` 这部分制定了系统启动时内核的控制台输出使用串口设备 ttyS0 ，波特率为 115200 ，无奇偶校验，每帧数据包含 8 位数据。
```
root@buildroot:~# cat /proc/cmdline
console=ttyS0,115200n8 root=/dev/mmcblk0p9 ro rootwait hobotboot.slot_suffix=_a hobotboot.reason=COLD_BOOT hobotboot.medium=MMC hobotboot.mode=normal hobotboot.ab_switch_reason=normal hobotboot.pmic_type=single-pmic
```

### 用户态阶段使用
该阶段主要是通过访问 Linux 标准串口设备文件的形式来使用的


Linux 系统中的串口通常被表示为设备文件，一般路径如下：

- /dev/ttySx：标准串口， x 表示编号，从 0 开始。
- /dev/ttyUSBx： USB 转串口设备。


我们可以使用串口的用户态工具来使用串口，也可以通过软件 API 的形式使用。


#### 常用工具 microcom


首先确保文件设备节点是存在、正常使用且没有被占用的。\
`lsof /dev/ttyS1`\
检查没有被占用的情况下，使用如下命令打开 UART1\
`microcom -s 115200 /dev/ttyS1`

然后在终端里面输入想要发送的字符
比如：`Hello D-Robotics`

观察对端是否收到。

在对端输入字符，观察是否有收到对端给的字符。

#### 软件 API（以回环测试举例）

硬件上把 UART2 的 TX 和 RX 进行连接。\
可以参考如下跳线帽进行连接：\
![loopback_test_pin_connect](_static/_images/27-Uart_Driver_Debug_Guide/loopback_test_pin_connect.png)

编译 uart_duplex.c 代码，编译前请注意修改下列命令中的交叉编译工具链路径。

我们可以新建一个目录，比如 `mkdir uart_duplex`\
`cd uart_duplex` 后使用如下命令进行构建：
```
/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc -o uart_duplex uart_duplex.c  -lpthread
```
构建成功之后我们可以看到目录下有可执行文件和 uart_duplex.c 源文件
```
.
├── uart_duplex
└── uart_duplex.c
```
将可执行程序推送到板端，比如放到可读写的分区 `/userdata/uart_duplex`\
uart_duplex.c 具体代码如下
```c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <sys/time.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>

#define BUFF_SIZE (20 * 1024 * 1024)
pthread_t recv_thread_id;
pthread_t recv_check_thread_id;
pthread_t send_thread_id;
char send_buffer[BUFF_SIZE] = {0};
char recv_buffer[BUFF_SIZE] = {0};
static uint32_t test_size = 1024;
static uint32_t baud = 4000000;
static uint32_t test_count = 0;
int g_fd;
uint64_t recv_total = 0;
sem_t sem_check;

#define FRAME_LEN 512
#if 1
static void dump_recv_data(uint32_t sum, uint32_t len)
{
	int ii = 0;
	printf("dump receive data:\n");
	for (ii = 0; ii < len; ii += 4) {
		printf("0x%x: 0x%x, 0x%x, 0x%x, 0x%x\n", sum + ii,
				recv_buffer[sum + ii],
				recv_buffer[sum + ii + 1],
				recv_buffer[sum + ii + 2],
				recv_buffer[sum + ii + 3]);

	}

}

static void dump_send_data(uint32_t sum, uint32_t len)
{
	int ii = 0;
	printf("dump send data:\n");
	for (ii = 0; ii < len; ii += 4) {
		printf("0x%x: 0x%x, 0x%x, 0x%x, 0x%x\n", sum + ii,
				send_buffer[sum + ii],
				send_buffer[sum + ii + 1],
				send_buffer[sum + ii + 2],
				send_buffer[sum + ii + 3]);

	}

}
#endif

static void set_baudrate(int fd, int nSpeed)
{
	struct termios newtio;

	tcgetattr(fd, &newtio);

	switch (nSpeed) {
	case 2400:
		cfsetispeed(&newtio, B2400);
		cfsetospeed(&newtio, B2400);
		break;

	case 4800:
		cfsetispeed(&newtio, B4800);
		cfsetospeed(&newtio, B4800);
		break;

	case 9600:
		cfsetispeed(&newtio, B9600);
		cfsetospeed(&newtio, B9600);
		break;

	case 19200:
		cfsetispeed(&newtio, B19200);
		cfsetospeed(&newtio, B19200);
		break;

	case 38400:
		cfsetispeed(&newtio, B38400);
		cfsetospeed(&newtio, B38400);
		break;

	case 57600:
		cfsetispeed(&newtio, B57600);
		cfsetospeed(&newtio, B57600);
		break;

	case 115200:
		cfsetispeed(&newtio, B115200);
		cfsetospeed(&newtio, B115200);
		break;
	case 230400:
		cfsetispeed(&newtio, B230400);
		cfsetospeed(&newtio, B230400);
		break;
	case 921600:
		cfsetispeed(&newtio, B921600);
		cfsetospeed(&newtio, B921600);
		break;
	case 1000000:
		cfsetispeed(&newtio, B1000000);
		cfsetospeed(&newtio, B1000000);
		break;

	case 1152000:
		cfsetispeed(&newtio, B1152000);
		cfsetospeed(&newtio, B1152000);
		break;
	case 1500000:
		cfsetispeed(&newtio, B1500000);
		cfsetospeed(&newtio, B1500000);
		break;
	case 2000000:
		cfsetispeed(&newtio, B2000000);
		cfsetospeed(&newtio, B2000000);
		break;
	case 2500000:
		cfsetispeed(&newtio, B2500000);
		cfsetospeed(&newtio, B2500000);
		break;
	case 3000000:
		cfsetispeed(&newtio, B3000000);
		cfsetospeed(&newtio, B3000000);
		break;
	case 3500000:
		cfsetispeed(&newtio, B3500000);
		cfsetospeed(&newtio, B3500000);
		break;

	case 4000000:
		cfsetispeed(&newtio, B4000000);
		cfsetospeed(&newtio, B4000000);
		break;

	default:
		printf("\tSorry, Unsupported baud rate, use previous baudrate!\n\n");
		break;
	}
	tcsetattr(fd,TCSANOW,&newtio);
}

static void set_termios(int fd)
{
	struct termios term;

	tcgetattr(fd, &term);
	term.c_cflag &= ~(CSIZE | CSTOPB | PARENB | INPCK);
	term.c_cflag |= (CS8 | CLOCAL | CREAD);
	term.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
	term.c_oflag &= ~(OPOST | ONLCR | OCRNL);
	term.c_iflag &= ~(ICRNL |INLCR | IXON | IXOFF | IXANY);
	term.c_cc[VTIME] = 0;
	term.c_cc[VMIN] = 1;
	tcsetattr(fd, TCSAFLUSH, &term);
}

static void *send_test(void *times)
{
	/*send thread*/
	struct timeval start, end;
	int32_t i = 0;
	uint32_t j = 0;
	uint32_t tmp = 0;
	uint32_t exe_count = 0;
	int32_t ret = 0;
	float ts = 0;

	printf("Start send thread\n");

	sleep(1);
	if (test_count == 0) {
		tmp = 10;
	} else
		tmp = test_count;
	for (j = 0; j < tmp; j++) {
		if (test_count == 0)
			j = 0;
		sleep(1);
		printf("This is uart send %d times\n", ++exe_count);
		gettimeofday(&start, NULL);
		for (i = 0; i <  test_size * 1024; i = i + FRAME_LEN) {
			ret = write(g_fd, &send_buffer[i], FRAME_LEN);
			if (ret < FRAME_LEN) {
				printf("write ttyS2 error\n");
				return NULL;
			}
		}
#if 1
		gettimeofday(&end, NULL);
		//		printf("start %ld sec, %ld usec, end %ld sec, %ld usec\n", start.tv_sec, start.tv_usec, end.tv_sec, end.tv_usec);
		ts = ((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec)) / 1000;
		printf("send %dKbytes,time:%fms, BPS:%f\n", test_size, ts, test_size * 1000 / (ts / 1000));
#endif
	}
	// close(g_fd);
	return NULL;
}

static void *recv_test(void *times)
{
	int32_t j = 0;
	uint32_t exe_count = 0;
	int tmp = 0;
	int size = 0;
	int sum = 0;
	int last_count = 0;
	int len = 0;
	int len_frame = 0; /*use to get correct frame len*/

	printf("Start receive thread\n");

	memset(recv_buffer, 0, sizeof(recv_buffer));

	if (test_count == 0) {
		tmp = 10;
	} else
		tmp = test_count;
	for (j = 0; j < tmp; j++) {
		sum = 0;
		last_count = 0;
		if (test_count == 0)
			j = 0;
		printf("This is receive test %d times\n", ++exe_count);
		//gettimeofday(&start, NULL);
		size = test_size * 1024;
		while (size > 0) {
			len = read(g_fd, &recv_buffer[sum], FRAME_LEN);
			if (len < 0) {
				if (errno == EAGAIN || errno == EWOULDBLOCK) {
					usleep(1000); // 等待 1ms 后重试
					continue;
				} else {
					perror("read error");
					return NULL;
				}
			}
			recv_total += len;
			len_frame += len;
			if (len_frame >= FRAME_LEN) {
				len_frame -= FRAME_LEN;
				sem_post(&sem_check);
			}

#if 0
			ret = memcmp(&recv_buffer[sum], &send_buffer[sum], len);
			if (ret != 0) {
				printf("data compare error\n");
				return NULL;
			}
#endif
			sum +=len;
			size -= len;
			if ((sum - last_count) > 100 * 1024) {
				printf("receive sum:%d bytes\n", sum);
				last_count = sum;
			}
		}
#if 0
		gettimeofday(&end, NULL);
		printf("start %ld sec, %ld usec, end %ld sec, %ld usec\n", start.tv_sec, start.tv_usec, end.tv_sec, end.tv_usec);
		ts = ((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec)) / 1000;

		printf("receive %dKbytes,time:%fms, BPS:%f\n", test_size, ts, test_size * 1000 / (ts / 1000));
#endif
	}
	// close(g_fd);
	return NULL;
}

int32_t error_bit(uint64_t *data1, uint64_t *data2, int32_t len)
{
	uint64_t c=0;
	int32_t sum = 0;
	int i = 0;
	for(i = 0; i < len / 8; i++) {
		c = data1[i] ^ data2[i];
		while(c!=0) {
			c &= (c - 1);
			sum++;
		}
	}
	return sum;
}

static void *recv_check_test(void *times)
{
	int32_t check_pos = 0;
	uint32_t *cur_frame = NULL;
	int32_t error_bit_cnt = 0;
	printf("Start recv_check thread\n");
	while (1) {
		sem_wait(&sem_check);
		/*check data*/
		cur_frame = (uint32_t *)&recv_buffer[check_pos];
		if (*cur_frame != check_pos / FRAME_LEN) {
			printf("error: may lost frame, curruent frame is %d, expected frame is %d position: 0x%x\n",
					*cur_frame, check_pos / FRAME_LEN, check_pos);
			//dump_recv_data(check_pos, FRAME_LEN);
			//dump_send_data(check_pos, FRAME_LEN);
			error_bit_cnt = 0;
			error_bit_cnt = error_bit((uint64_t *)&recv_buffer[check_pos],
					(uint64_t *)&send_buffer[check_pos],
					FRAME_LEN / 8);
			check_pos += FRAME_LEN;
			printf("test total data: 0x%lx, error bit count:%d\n", recv_total, error_bit_cnt);
			if (check_pos == test_size * 1024) {
				//exit(1);
				printf("uart: frame head error\n");

			}
			continue;
		}
		error_bit_cnt = 0;
		error_bit_cnt = error_bit((uint64_t *)&recv_buffer[check_pos],
				(uint64_t *)&send_buffer[check_pos],
				FRAME_LEN / 8);
		if (error_bit_cnt) {
			printf("test total data: 0x%lx!!!!!!!, error bit count:%d\n", recv_total, error_bit_cnt);
			//dump_recv_data(check_pos, FRAME_LEN);
			//dump_send_data(check_pos, FRAME_LEN);
			check_pos += FRAME_LEN;
			if (check_pos == test_size * 1024) {
				//exit(1);
				printf("uart: frame data error\n");
			}
			continue;
		}
		memset(&recv_buffer[check_pos], 0, FRAME_LEN);
		check_pos += FRAME_LEN;
		if (check_pos == test_size * 1024) {
			check_pos = 0;
			printf("### Check the received data is correct ###\n");
		}
	}
	return NULL;
}

static const char short_options[] = "s:u:c:b:d:h";
static const struct option long_options[] = {
	{"size", required_argument, NULL, 's'},
	{"baudrate", required_argument, NULL, 'b'},
	{"count", required_argument, NULL, 'c'},
	{"device", required_argument, NULL, 'd'},
	{"help", no_argument, NULL, 'h'},
	{0, 0, 0, 0}};
int main(int argc, char *argv[])
{
	int ret = 0;
	char *pDevice = NULL;
	int i = 0;
	int32_t cmd_parser_ret = 0;
	uint32_t *frame_num = NULL;
	uint32_t *frame_value = NULL;

	while ((cmd_parser_ret = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
		switch (cmd_parser_ret) {
		case 's':
			test_size = atoi(optarg);
			break;

		case 'b':
			baud = atoi(optarg);
			break;

		case 'c':
			test_count = atoi(optarg);
			break;
		case 'd':
			pDevice = optarg;
			break;

		case 'h':
			printf("**********UART STRESS TEST HELP INFORMATION*********\n");
			printf(">>> -s/--size     [test size,unit--Kbytes,default is 1M, MAX is 20M]\n");
			printf(">>> -b/--baudrate  [baud,default is 4M]\n");
			printf(">>> -c/--count  [test count,default is forever]\n");
			printf(">>> -d/--uart  [uart device, user must set this]\n");
			return 0;
		}
	}
	if (baud > 4000000) {
		printf("baud is larger than max baud\n");
		return -1;
	}
	g_fd = open(pDevice, O_RDWR | O_NOCTTY);
	if (0 > g_fd) {
		printf("open fail\n");
		return -1;
	}
	set_baudrate(g_fd, baud);
	set_termios(g_fd);
	printf("test size:%d Kbytes, baud:%d\n", test_size, baud);
	for (i = 0; i < test_size * 1024; i+=4) {
		if (i % FRAME_LEN) {
			frame_value = (uint32_t *)&send_buffer[i];
			*frame_value = rand();
		}

	}
	for (i = 0; i < test_size * 1024 / FRAME_LEN; i++) {
		frame_num = (uint32_t *)&send_buffer[i * FRAME_LEN];
		*frame_num = i;
		//        printf("pos:0x%x, value:0x%x\n", i * FRAME_LEN, *frame_num);
	}

	sem_init(&sem_check, 0, 0);
	ret = pthread_create(&recv_thread_id,
			NULL,
			recv_test,
			NULL);
	if (ret < 0) {
		printf("create uart1 test thread failed\n");
		return -1;
	}
	ret = pthread_create(&send_thread_id,
			NULL,
			send_test,
			NULL);
	if (ret < 0) {
		printf("create uart2 test thread failed\n");
		return -1;
	}
	ret = pthread_create(&recv_check_thread_id,
			NULL,
			recv_check_test,
			NULL);
	if (ret < 0) {
		printf("create receive check thread failed\n");
		return -1;
	}
	pthread_join(recv_thread_id, NULL);
	pthread_join(recv_check_thread_id, NULL);
	pthread_join(send_thread_id, NULL);
	close(g_fd);
	return 0;
}
```

使用回环测试 100 轮（-c 代表的参数）命令示例如下：

```
# ./uart_duplex -c 100 -d /dev/ttyS2
test size:1024 Kbytes, baud:4000000
Start receive thread
Start send thread
Start recv_check thread
This is receive test 1 times
This is uart send 1 times
receive sum:102416 bytes
receive sum:204832 bytes
...
receive sum:819328 bytes
receive sum:921744 bytes
receive sum:1024160 bytes
send 1024Kbytes,time:4507.000000ms, BPS:227202.125000
This is receive test 2 times
### Check the received data is correct ###
This is uart send 2 times
receive sum:102416 bytes
receive sum:204832 bytes
...
receive sum:921744 bytes
receive sum:1024160 bytes
send 1024Kbytes,time:4609.000000ms, BPS:222174.000000
This is receive test 3 times
### Check the received data is correct ###
This is uart send 3 times
receive sum:102416 bytes
receive sum:204832 bytes
...

```

**命令说明**：打开 /dev/ttyS2 ，默认波特率 4Mbps，默认每轮测试 1MB 数据，测试 100 轮，读写同时进行，每发、收 512 字节做一轮数据校验，完整一轮测试结束后，如果没有出错则打印校验正确。\
有需要可以阅读它的帮助信息获取更多使用方法。


**这里也对该示例程序做一个简单的说明**：\
该示例程序通过 UART 接口与串口设备进行数据交互，实现数据的发送、接收和校验。程序使用多线程来分别处理发送、接收和校验功能，并通过信号量实现线程间的同步。具体可以参考如下流程图：\
![uart_loopback_test_flowchart](_static/_images/27-Uart_Driver_Debug_Guide/uart_loopback_test_flowchart.png)

**关键函数功能说明**

| 函数 / 代码片段 | 功能描述 |
| --- | --- |
| `g_fd = open(pDevice, O_RDWR ...)` | 打开串口设备文件。 |
| `set_baudrate(int fd, int nSpeed)` | 设置串口的波特率。 |
| `set_termios(int fd)` | 配置串口的其他属性，如字符大小、停止位、奇偶校验等。 |
| `len = read(g_fd, &recv_buffer[sum], FRAME_LEN)` | 从串口读取数据，存储到 `recv_buffer` 中，每次读取 `FRAME_LEN` 长度的数据。 |
| `ret = write(g_fd, &send_buffer[i], FRAME_LEN)` | 向串口发送数据，从 `send_buffer` 中读取数据，每次发送 `FRAME_LEN` 长度的数据。 |
| `send_test(void *times)` | 发送线程的入口函数，负责将数据发送到串口。 |
| `recv_test(void *times)` | 接收线程的入口函数，负责从串口读取数据。 |
| `recv_check_test(void *times)` | 校验线程的入口函数，负责校验接收到的数据。 |
| `error_bit(uint64_t *data1, uint64_t *data2, int32_t len)` | 计算两个数据块之间的差异位数。 |
| `主函数 main(int argc, char *argv[])` | 解析命令行参数，打开串口设备，设置串口属性，初始化缓冲区，启动发送、接收和校验线程，并等待这些线程结束。 |

## 常见问题
- 由于 UART 协议除了波特率，有些模块或者通信协议需要对停止位、校验位等都有设置，需要逐一对齐。
- 在使用串口调试的时候，有些串口调试工具有 Flow control 的选项，可以选择 Xon/Xoff，如果选择 None，则可能敲击键盘调试的时候微控器没有反应。
- 使用前，检查 /dev/ttySx 等节点是否真实存在。
- 使用前，检查 /dev/ttySx 等节点是否被占用（ lsof /dev/ttySx）。


