4.3.3. UART 驱动调试指南

4.3.3.1. 概述

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

  • UART1 和 UART7 支持硬件自动流控。

  • 这些串口支持多种比特率,包括 115200 bps、 230400 bps、 460800 bps、 921600 bps、 1500000 bps、 2000000 bps 和 4000000 bps。

  • 支持基于中断或基于 DMA 的传输模式。

4.3.3.2. 功能描述

典型应用

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

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

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

功能原理

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

uart_driver_single_link

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

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

instruction_diagram_of_frame_data_1

说明如下:

  • 起始位:发送 1 位逻辑 0 (低电平),开始传输数据。

  • 数据位:可以是 5~8 位的数据,先发低位,再发高位,一般常见的就是 8 位( 1 个字节),其他的如 7 位的 ASCII 码。

  • 校验位:奇偶校验,将数据位加上校验位, 1 的位数为偶数(偶校验), 1 的位数 4 为奇数(奇校验)。

  • 停止位:停止位是数据传输结束的标志,可以是 1/1.5/2 位的逻辑 1 (高电平)。

  • 空闲位:空闲时数据线为高电平状态,代表无数据传输。

instruction_diagram_of_frame_data_2

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

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

工作方式

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

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

4.3.3.3. 驱动代码

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

设备树节点配置

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>;
};
...

备注: x5.dtsi 中的节点主要声明 SoC 共有特性,和具体电路板无关,一般情况下不用修改。

DTS 配置 DMA 绑定

X5 所有 UART 均支持使用 DMA 搬运。
以 UART7 为例:

&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用于发送*/
}

注意:

  • 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

4.3.3.4. 功能使用

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

编译 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 具体代码如下

#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

关键函数功能说明

函数 / 代码片段 功能描述
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[]) 解析命令行参数,打开串口设备,设置串口属性,初始化缓冲区,启动发送、接收和校验线程,并等待这些线程结束。

4.3.3.5. 常见问题

  • 由于 UART 协议除了波特率,有些模块或者通信协议需要对停止位、校验位等都有设置,需要逐一对齐。

  • 在使用串口调试的时候,有些串口调试工具有 Flow control 的选项,可以选择 Xon/Xoff,如果选择 None,则可能敲击键盘调试的时候微控器没有反应。

  • 使用前,检查 /dev/ttySx 等节点是否真实存在。

  • 使用前,检查 /dev/ttySx 等节点是否被占用( lsof /dev/ttySx)。