UART - 通用异步收发传输器
通用异步收发器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种串行、异步、全双工的通信协议,在嵌入式领域应用的非常广泛。在19世纪60年代,为了解决计算机和电传打字机通信,Bell发明了UART协议,将并行输入信号转换成串行输出信号。因为UART简单实用的特性,其已经成为一种使用非常广泛的通讯协议。UART 控制器支持以下功能:
- 兼容标准16550串口
- 支持发送接收FIFO
- 支持DMA控制接口
- 支持软件/硬件流控配置
- 支持IrDA 1.0 SIR
- 支持2/4/8线串口接口
- 支持5-8数据位和1/1.5/2停止位
- 支持奇偶校验,无校验位
模块配置
驱动配置
驱动位于
UART Drivers ->
	<*> UART Support for Allwinner SoCs    # 使用 UART 驱动
	< > UART NG Support for Allwinner SoCs # 使用 UART NG 驱动
	[ ] Enable DMA for UART                # UART 启用 DMA 传输
	[*] Enable Console on UART             # UART 用于显示终端
	[ ] Enable Earlycon                    # 启用 Earlycon
	[*] Enable Early-Printk                # 启用 Early Printk
	[ ] Enable GPIO based Serial           # 启用基于 GPIO 的 UART
	[*] Enable uart dynamic debug          # UART 动态调试
	< > Enable Soft-Uart                   # 启用软件 UART

设备树配置
在 Device Tree 中对每一个 UART 控制器进行配置,一个 UART 控制器对应一个 UART 节点,节点属性的含义见注释。为了在UART 驱动代码中区分每一个UART 控制器,需要在 Device Tree 中的 aliases 节点中未每一个。
UART 节点指定别名,如下 aliases 节点所示。别名形式为字符串“serial”加连续编号的数字,在UART 驱动程序中可以通过 of_alias_get_id() 函数获取对应的 UART 控制器的数字编号,从而区分每一个 UART 控制器。
aliases {
	serial0 = &uart0;
	serial1 = &uart1;
	serial2 = &uart2;
	serial3 = &uart3;
	serial4 = &uart4;
	serial5 = &uart5;
	serial6 = &uart6;
	serial7 = &uart7;
};
二线串口配置
在 SoC 的 dtsi 文件中提炼了内存基地址、中断控制、时钟等共性信息,是该类芯片所有平台的模块配置
uart0: uart@2500000 {
	compatible = "allwinner,sun55i-uart";
	reg = <0x0 0x02500000 0x0 0x400>;     /* 设备使用的基地址 */
	interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>; /* 设备使用的中断 */
	clocks = <&ccu CLK_BUS_UART0>;  /* 设备使用的时钟 */
	resets = <&ccu RST_BUS_UART0>;  /* 设备reset时钟 */
	uart0_port = <0>;               /* 端口号 */
	uart0_type = <2>;               /* 该串口支持的模式,通常有2/4/8 */
    sunxi,uart-fifosize = <64>;	    /* 模块FIFO深度 */
	status = "disabled";
};
在设备端,配置了 UART 的引脚,配  置位于 pio 节点下
&pio {
    uart0_pins_a: uart0_pins@0 {
		pins = "PB9", "PB10";
		function = "uart0";
	};
	uart0_pins_b: uart0_pins@1 {
		pins = "PB9", "PB10";
		function = "gpio_in";
	};
};
配置引脚,功能等
&uart0 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&uart0_pins_a>;
	pinctrl-1 = <&uart0_pins_b>;
	uart-supply = <®_cldo3>;
	status = "okay";
};
四线串口配置
在设备树中配置如下:
在 SoC 的 dtsi 文件中提炼了内存基地址、中断控制、时钟等共性信息,是该类芯片所有平台的模块配置
uart1: uart@2500400 {
	compatible = "allwinner,sun55i-uart";
	device_type = "uart1";
	reg = <0x0 0x02500400 0x0 0x400>;
	interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
	sunxi,uart-fifosize = <64>;
	clocks = <&ccu CLK_BUS_UART1>;
	clock-names = "uart1";
	resets = <&ccu RST_BUS_UART1>;
	pinctrl-names = "default", "sleep";
	uart1_port = <1>;
	uart1_type = <4>;
	status = "disabled";
};
在设备端,配置了 UART 的引脚,配置位于 pio 节点下
&pio {
    uart1_pins_a: uart1@0 {
        pins = "PG6", "PG7", "PG8", "PG9";
        function = "uart1";
        drive-strength = <10>;
        bias-pull-up;
    };
    uart1_pins_b: uart1@1 {
        pins = "PG6", "PG7", "PG8", "PG9";
        function = "gpio_in";
    };
};
配置引脚,功能等
&uart1 {
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&uart1_pins_a>;
	pinctrl-1 = <&uart1_pins_b>;
	status = "okay";
};
DMA 模式配置
在内核打开 CONFIG_AW_SERIAL_DMA 配置后可以使用 DMA 模式
uart6: uart@2501800 {
	compatible = "allwinner,sun55i-uart";
	reg = <0x0 0x2501800 0x0 0x400>;
	interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&ccu CLK_UART6>;
	resets = <&ccu RST_BUS_UART6>;
	use_dma = <3>;	/* 是否采用DMA 方式传输,0:不启用,1:只启用TX,2:只启用RX,3:启用TX 与RX */
	dmas = <&dma 20>, <&dma 20>;	/* 20 表示DRQ, 查看手册得知 */
	dma-names = "tx", "rx";
	uart6_port = <6>;
	uart6_type = <2>;
	sunxi,uart-fifosize = <128>;
	status = "disabled";
};
其中的 dma 通道号可以查看数据手册得知
在设备端,配置了 UART 的引脚,配置位于 pio 节点下
&pio {
	uart6_pins_a: uart6_pins@0 {
		pins = "PI6", "PI7";
		function = "uart6";
	};
	uart6_pins_b: uart6_pins@1 {
		pins = "PI6", "PI7";
		function = "gpio_in";
	};
};
模块使用说明
UART驱动会注册生成串口设备 /dev/ttySx,应用层的使用只需遵循 Linux 系统中的标准串口编程方法即可。
UART驱动会注册生成串口设备/dev/ttySx,应用层的使用只需遵循Linux系统中的标准串口编程方法即可。
打开/关闭串口
使用标准的文件打开函数:
int open(const char *pathname, int flags);
int close(int fd);
需要引用头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
读/写串口
同样使用标准的文件读写函数:
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
需要引用头文件:
#include <unistd.h>
设置串口属性
串口属性包括波特率、数据位、停止位、校验位、流控等,这部分是串口设备特有的接口。串口属性的数据结构termios定义如下:(terminos.h)。
#define NCCS 19
struct termios {
	tcflag_t c_iflag;		/* input mode flags */
	tcflag_t c_oflag;		/* output mode flags */
	tcflag_t c_cflag;		/* control mode flags */
	tcflag_t c_lflag;		/* local mode flags */
	cc_t c_line;			/* line discipline */
	cc_t c_cc[NCCS];		/* control characters */
};
其中,c_iflag的标志常量定义如下:
| 标志 | 说明 | 
|---|---|
| IGNBRK | 忽略输入中的 BREAK 状态。 | 
| BRKINT | 如果设置了 IGNBRK,将忽略 BREAK。如果没有设置,但是设置了 BRKINT,那么 BREAK 将使得输入和输出队列被刷新,如果终端是一个前台进程组的控制终端,这个进程组中所有进程将收到 SIGINT 信号。如果既未设置 IGNBRK 也未设置 BRKINT,BREAK 将视为与 NUL 字符同义,除非设置了 PARMRK,这种情况下它被视为序列 \377 \0 \0。 | 
| IGNPAR | 忽略桢错误和奇偶校验错。 | 
| PARMRK | 如果没有设置 IGNPAR,在有奇偶校验错或桢错误的字符前插入 \377 \0。如果既没有设置 IGNPAR 也没有设置 PARMRK,将有奇偶校验错或桢错误的字符视为 \0。 | 
| INPCK | 启用输入奇偶检测。 | 
| ISTRIP | 去掉第八位。 | 
| INLCR | 将输入中的 NL 翻译为 CR。 | 
| IGNCR | 忽略输入中的回车。 | 
| ICRNL | 将输入中的回车翻译为新行 (除非设置了 IGNCR)。 | 
| IUCLC | (不属于 POSIX) 将输入中的大写字母映射为小写字母。 | 
| IXON | 启用输出的 XON/XOFF 流控制。 | 
| IXANY | (不属于 POSIX.1;XSI) 允许任何字符来重新开始输出。 | 
| IXOFF | 启用输入的 XON/XOFF 流控制。 | 
| IMAXBEL | (不属于 POSIX) 当输入队列满时响零。Linux 没有实现这一位,总是将它视为已设置。 | 
c_oflag 的标志常量定义如下:
| 标志 | 说明 | 
|---|---|
| OLCUC | (不属于 POSIX) 将输出中的小写字母映射为大写字母。 | 
| ONLCR | (XSI) 将输出中的新行符映射为回车-换行。 | 
| OCRNL | 将输出中的回车映射为新行符。 | 
| ONOCR | 不在第 0 列输出回车。 | 
| ONLRET | 不输出回车。 | 
| OFILL | 发送填充字符作为延时,而不是使用定时来延时。 | 
| OFDEL | (不属于 POSIX) 填充字符是 ASCII DEL (0177)。如果不设置,填充字符 则是 ASCII NUL。 | 
| NLDLY | 新行延时掩码。取值为 NL0 和 NL1。 | 
| CRDLY | 回车延时掩码。取值为 CR0, CR1, CR2, 或 CR3。 | 
| TABDLY | 水平跳格延时掩码。取值为 TAB0, TAB1, TAB2, TAB3 (或 XTABS)。取值为 TAB3,即 XTABS,将扩展跳格为空格 (每个跳格符填充 8 个空格)。 | 
| BSDLY | 回退延时掩码。取值为 BS0 或 BS1。(从来没有被实现过)。 | 
| VTDLY | 竖直跳格延时掩码。取值为 VT0 或 VT1。 | 
| FFDLY | 进表延时掩码。取值为 FF0 或 FF1。 | 
c_cflag 的标志常量定义如下:
| 标志 | 说明 | 
|---|---|
| CBAUD | (不属于 POSIX) 波特率掩码 (4+1 位)。 | 
| CBAUDEX | (不属于 POSIX) 扩展的波特率掩码 (1 位),包含在 CBAUD 中。(POSIX 规定波特率存储在 termios 结构中,并未精确指定它的位置,而是提供了函数 cfgetispeed() 和 cfsetispeed() 来存取它。一些系统使用 c_cflag 中 CBAUD 选择的位,其他系统使用单独的变量,例如 sg_ispeed 和 sg_ospeed 。) | 
| CSIZE | 字符长度掩码。取值为 CS5, CS6, CS7, 或 CS8。 | 
| CSTOPB | 设置两个停止位,而不是一个。 | 
| CREAD | 打开接受者。 | 
| PARENB | 允许输出产生奇偶信息以及输入的奇偶校验。 | 
| PARODD | 输入和输出是奇校验。 | 
| HUPCL | 在最后一个进程关闭设备后,降低 modem 控制线 (挂断)。 | 
| CLOCAL | 忽略 modem 控制线。 | 
| LOBLK | (不属于 POSIX) 从非当前 shell 层阻塞输出(用于 shl )。 | 
| CIBAUD | (不属于 POSIX) 输入速度的掩码。CIBAUD 各位的值与 CBAUD 各位相同,左移了 IBSHIFT 位。 | 
| CRTSCTS | (不属于 POSIX) 启用 RTS/CTS (硬件) 流控制。 | 
c_lflag 的标志常量定义如下:
| 标志 | 说明 | 
|---|---|
| ISIG | 当接受到字符 INTR, QUIT, SUSP, 或 DSUSP 时,产生相应的信号。 | 
| ICANON | 启用标准模式 (canonical mode)。允许使用特殊字符 EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和 WERASE,以及按行的缓冲。 | 
| XCASE | (不属于 POSIX; Linux 下不被支持) 如果同时设置了 ICANON,终端只有大写。输入被转换为小写,除了以 \ 前缀的字符。输出时,大写字符被前缀 \,小写字符被转换成大写。 | 
| ECHO | 回显输入字符。 | 
| ECHOE | 如果同时设置了 ICANON,字符 ERASE 擦除前一个输入字符,WERASE 擦除前一个词。 | 
| ECHOK | 如果同时设置了 ICANON,字符 KILL 删除当前行。 | 
| ECHONL | 如果同时设置了 ICANON,回显字符 NL,即使没有设置 ECHO。 | 
| ECHOCTL | (不属于 POSIX) 如果同时设置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制信号被回显为 ^X, 这里 X 是比控制信号大 0x40 的 ASCII 码。例如,字符 0x08 (BS) 被回显为 ^H。 | 
| ECHOPRT | (不属于 POSIX) 如果同时设置了 ICANON 和 IECHO,字符在删除的同时被打印。 | 
| ECHOKE | (不属于 POSIX) 如果同时设置了 ICANON,回显 KILL 时将删除一行中的每个字符,如同指定了 ECHOE 和 ECHOPRT 一样。 | 
| DEFECHO | (不属于 POSIX) 只在一个进程读的时候回显。 | 
| FLUSHO | (不属于 POSIX; Linux 下不被支持) 输出被刷新。这个标志可以通过键入字符 DISCARD 来开关。 | 
| NOFLSH | 禁止在产生 SIGINT, SIGQUIT 和 SIGSUSP 信号时刷新输入和输出队列。 | 
| PENDIN | (不属于 POSIX; Linux 下不被支持) 在读入下一个字符时,输入队列中所有字符被重新输出。(bash 用它来处理 typeahead) | 
| TOSTOP | 向试图写控制终端的后台进程组发送 SIGTTOU 信号。 | 
| IEXTEN | 启用实现自定义的输入处理。这个标志必须与 ICANON 同时使用,才能解释特殊字符 EOL2,LNEXT,REPRINT 和 WERASE,IUCLC 标志才有效。 | 
c_cc 数组定义了特殊的控制字符。符号下标 (初始值) 和意义为:
| 标志 | 说明 | 
|---|---|
| VINTR | (003, ETX, Ctrl-C, or also 0177, DEL, rubout) 中断字符。发出 SIGINT 信号。当设置 ISIG 时可被识别,不再作为输入传递。 | 
| VQUIT | (034, FS, Ctrl-) 退出字符。发出 SIGQUIT 信号。当设置 ISIG 时可被识别,不再作为输入传递。 | 
| VERASE | (0177, DEL, rubout, or 010, BS, Ctrl-H, or also #) 删除字符。删除上一个还没有删掉的字符,但不删除上一个 EOF 或行首。当设置 ICANON 时可被识别,不再作为输入传递。 | 
| VKILL | (025, NAK, Ctrl-U, or Ctrl-X, or also @) 终止字符。删除自上一个 EOF 或行首以来的输入。当设置 ICANON 时可被识别,不再作为输入传递。 | 
| VEOF | (004, EOT, Ctrl-D) 文件尾字符。更精确地说,这个字符使得 tty 缓冲中的内容被送到等待输入的用户程序中,而不必等到 EOL。如果它是一行的第一个字符,那么用户程序的 read() 将返回 0,指示读到了 EOF。当设置 ICANON 时可被识别,不再作为输入传递。 | 
| VMIN | 非 canonical 模式读的最小字符数。 | 
| VEOL | (0, NUL) 附加的行尾字符。当设置 ICANON 时可被识别。 | 
| VTIME | 非 canonical 模式读时的延时,以十分之一秒为单位。 | 
| VEOL2 | (not in POSIX; 0, NUL) 另一个行尾字符。当设置 ICANON 时可被识别。 | 
| VSTART | (021, DC1, Ctrl-Q) 开始字符。重新开始被 Stop 字符中止的输出。当设置 IXON 时可被识别,不再作为输入传递。 | 
| VSTOP | (023, DC3, Ctrl-S) 停止字符。停止输出,直到键入 Start 字符。当设置 IXON 时可被识别,不再作为输入传递。 | 
| VSUSP | (032, SUB, Ctrl-Z) 挂起字符。发送 SIGTSTP 信号。当设置 ISIG 时可被识别,不再作为输入传递。 | 
| VLNEXT | (not in POSIX; 026, SYN, Ctrl-V) 字面上的下一个。引用下一个输入字符,取消它的任何特殊含义。当设置 IEXTEN 时可被识别,不再作为输入传递。 | 
| VWERASE | (not in POSIX; 027, ETB, Ctrl-W) 删除词。当设置 ICANON 和 IEXTEN 时可被识别,不再作为输入传递。 | 
| VREPRINT | (not in POSIX; 022, DC2, Ctrl-R) 重新输出未读的字符。当设置 ICANON 和 IEXTEN 时可被识别,不再作为输入传递。 | 
tcgetattr
- 作用:获取串口设备的属性。
- 参数:
- fd,串口设备的文件描述符。
- termios_p,用于保存串口属性。
 
- 返回:
- 成功,返回0。
- 失败,返回-1,errnor给出具体错误码。
 
tcsetattr
- 作用:设置串口设备的属性。
- 参数:
- fd,串口设备的文件描述符。
- optional_actions,本次设置什么时候生效。
- termios_p,指向要设置的属性结构。
 
- 返回:
- 成功,返回0。
- 失败,返回-1,errnor给出具体错误码
 
其中,optional_actions的取值有:
- 
TCSANOW:会立即生效。 
- 
TCSADRAIN:当前的输出数据完成传输后生效,适用于修改了输出相关的参数。 
- 
TCSAFLUSH:当前的输出数据完成传输,如果输入有数据可读但没有读就会被丢弃。 
cfgetispeed
- 作用:返回串口属性中的输入波特率。
- 参数:
- termios_p,指向保存有串口属性的结构。
 
- 返回:
- 成功,返回波特率,取值是一组宏,定义在terminos.h。
- 失败,返回-1,errnor给出具体错误码。
 
波特率定义如下所示:
| 宏定义 | 值 | 
|---|---|
| B0 | 0000000 | 
| B50 | 0000001 | 
| B75 | 0000002 | 
| B110 | 0000003 | 
| B134 | 0000004 | 
| B150 | 0000005 | 
| B200 | 0000006 | 
| B300 | 0000007 | 
| B600 | 0000010 | 
| B1200 | 0000011 | 
| B1800 | 0000012 | 
| B2400 | 0000013 | 
| B4800 | 0000014 | 
| B9600 | 0000015 | 
| B19200 | 0000016 | 
| B38400 | 0000017 | 
| B57600 | 0010001 | 
| B115200 | 0010002 | 
| B230400 | 0010003 | 
| B460800 | 0010004 | 
| B500000 | 0010005 | 
| B576000 | 0010006 | 
| B921600 | 0010007 | 
| B1000000 | 0010010 | 
| B1152000 | 0010011 | 
| B1500000 | 0010012 | 
| B2000000 | 0010013 | 
| B2500000 | 0010014 | 
| B3000000 | 0010015 | 
| B3500000 | 0010016 | 
| B4000000 | 0010017 | 
cfgetospeed
- 作用:返回串口属性中的输出波特率。
- 参数:
- termios_p,指向保存有串口属性的结构。
 
- 返回:
- 成功,返回波特率,取值是一组宏,定义在terminos.h。
- 失败,返回-1,errnor给出具体错误码。
 
cfsetispeed
- 作用:设置输入波特率到属性结构中。
- 参数:
- termios_p,指向保存有串口属性的结构。
- speed,波特率。
 
- 返回:
- 成功,返回0。
- 失败,返回-1,errnor给出具体错误码。
 
cfsetospeed
- 作用:设置输出波特率到属性结构中。
- 参数:
- termios_p,指向保存有串口属性的结构。
- speed,波特率。
 
- 返回:
- 成功,返回0。
- 失败,返回-1,errnor给出具体错误码
 
cfsetspeed
- 作用:同时设置输入和输出波特率到属性结构中。
- 参数:
- termios_p,指向保存有串口属性的结构。
- speed,波特率。
 
- 返回:
- 成功,返回0。
- 失败,返回-1,errnor给出具体错误码
 
tcflush
- 作用:清空输出缓冲区、或输入缓冲区的数据,具体取决于参数queue_selector。
- 参数:
- fd,串口设备的文件描述符。
- queue_selector,清空数据的操作。
 
- 返回:
- 成功,返回0。
- 失败,返回-1,errnor给出具体错误码。
 
参数queue_selector的取值有三个:
- 
TCIFLUSH:清空输入缓冲区的数据。 
- 
TCOFLUSH:清空输出缓冲区的数据。 
- 
TCIOFLUSH:同时清空输入/输出缓冲区的数据。 
使用 DEMO
此 demo 程序是打开一个串口设备,然后侦听这个设备,如果有数据可 读就读出来并打印。设备名称、侦听的循环次数都可以由参数指定。
#include <errno.h>  /*错误号定义*/
#include <fcntl.h>  /*文件控制定义*/
#include <stdio.h>  /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h> /*PPSIX 终端控制定义*/
#include <unistd.h>  /*Unix 标准函数定义*/
enum parameter_type {
    PT_PROGRAM_NAME = 0,
    PT_DEV_NAME,
    PT_CYCLE,
    PT_NUM
};
#define DBG(string, args...)                                       \
    do {                                                           \
        printf("%s, %s()%u---", __FILE__, __FUNCTION__, __LINE__); \
        printf(string, ##args);                                    \
        printf("\n");                                              \
    } while (0)
void usage(void) {
    printf("You should input as: \n");
    printf("\t select_test [/dev/name] [Cycle Cnt]\n");
}
int OpenDev(char *name) {
    int fd = open(name, O_RDWR);//| O_NOCTTY | O_NDELAY
    if (-1 == fd)
        DBG("Can't Open(%s)!", name);
    return fd;
}
/**
*@brief  设置串口通信速率
*@param  fd     类型 int  打开串口的文件句柄
*@param  speed  类型 int  串口速度
*@return  void
*/
void set_speed(int fd, int speed) {
    int i;
    int status;
    struct termios Opt = {0};
    int speed_arr[] = {
            B38400,
            B19200,
            B9600,
            B4800,
            B2400,
            B1200,
            B300,
            B38400,
            B19200,
            B9600,
            B4800,
            B2400,
            B1200,
            B300,
    };
    int name_arr[] = {
            38400,
            19200,
            9600,
            4800,
            2400,
            1200,
            300,
            38400,
            19200,
            9600,
            4800,
            2400,
            1200,
            300,
    };
    tcgetattr(fd, &Opt);
    for (i = 0; i < sizeof(speed_arr) / sizeof(int); i++) {
        if (speed == name_arr[i])
            break;
    }
    tcflush(fd, TCIOFLUSH);
    cfsetispeed(&Opt, speed_arr[i]);
    cfsetospeed(&Opt, speed_arr[i]);
    Opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
    Opt.c_oflag &= ~OPOST;                          /*Output*/
    status = tcsetattr(fd, TCSANOW, &Opt);
    if (status != 0) {
        DBG("tcsetattr fd");
        return;
    }
    tcflush(fd, TCIOFLUSH);
}
/**
*@brief   设置串口数据位,停止位和效验位
*@param  fd     类型  int  打开的串口文件句柄
*@param  databits 类型  int 数据位   取值 为 7 或者8
*@param  stopbits 类型  int 停止位   取值为 1 或者2
*@param  parity  类型  int  效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd, int databits, int stopbits, int parity) {
    struct termios options;
    if (tcgetattr(fd, &options) != 0) {
        perror("SetupSerial 1");
        return -1;
    }
    options.c_cflag &= ~CSIZE;
    switch (databits) /*设置数据位数*/
    {
        case 7:
            options.c_cflag |= CS7;
            break;
        case 8:
            options.c_cflag |= CS8;
            break;
        default:
            fprintf(stderr, "Unsupported data size\n");
            return -1;
    }
    switch (parity) {
        case 'n':
        case 'N':
            options.c_cflag &= ~PARENB; /* Clear parity enable */
            options.c_iflag &= ~INPCK;  /* Enable parity checking */
            break;
        case 'o':
        case 'O':
            options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
            options.c_iflag |= INPCK;             /* Disnable parity checking */
            break;
        case 'e':
        case 'E':
            options.c_cflag |= PARENB;  /* Enable parity */
            options.c_cflag &= ~PARODD; /* 转换为偶效验*/
            options.c_iflag |= INPCK;   /* Disnable parity checking */
            break;
        case 'S':
        case 's': /*as no parity*/
            options.c_cflag &= ~PARENB;
            options.c_cflag &= ~CSTOPB;
            break;
        default:
            fprintf(stderr, "Unsupported parity\n");
            return -1;
    }
    /* 设置停止位*/
    switch (stopbits) {
        case 1:
            options.c_cflag &= ~CSTOPB;
            break;
        case 2:
            options.c_cflag |= CSTOPB;
            break;
        default:
            fprintf(stderr, "Unsupported stop bits\n");
            return -1;
    }
    /* Set input parity option */
    if (parity != 'n')
        options.c_iflag |= INPCK;
    tcflush(fd, TCIFLUSH);
    options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/
    options.c_cc[VMIN] = 0;    /* Update the options and do it NOW */
    if (tcsetattr(fd, TCSANOW, &options) != 0) {
        perror("SetupSerial 3");
        return -1;
    }
    return 0;
}
void str_print(char *buf, int len) {
    int i;
    for (i = 0; i < len; i++) {
        if (i % 10 == 0)
            printf("\n");
        printf("0x%02x ", buf[i]);
    }
    printf("\n");
}
int main(int argc, char **argv) {
    int i = 0;
    int fd = 0;
    int cnt = 0;
    char buf[256];
    int ret;
    fd_set rd_fdset;
    struct timeval dly_tm;// delay time in select()
    if (argc != PT_NUM) {
        usage();
        return -1;
    }
    sscanf(argv[PT_CYCLE], "%d", &cnt);
    if (cnt == 0)
        cnt = 0xFFFF;
    fd = OpenDev(argv[PT_DEV_NAME]);
    if (fd < 0)
        return -1;
    set_speed(fd, 19200);
    if (set_Parity(fd, 8, 1, 'N') == -1) {
        printf("Set Parity Error\n");
        exit(0);
    }
    printf("Select(%s), Cnt %d. \n", argv[PT_DEV_NAME], cnt);
    while (i < cnt) {
        FD_ZERO(&rd_fdset);
        FD_SET(fd, &rd_fdset);
        dly_tm.tv_sec = 5;
        dly_tm.tv_usec = 0;
        memset(buf, 0, 256);
        ret = select(fd + 1, &rd_fdset, NULL, NULL, &dly_tm);
        //		DBG("select() return %d, fd = %d", ret, fd);
        if (ret == 0)
            continue;
        if (ret < 0) {
            printf("select(%s) return %d. [%d]: %s \n", argv[PT_DEV_NAME], ret, errno, strerror(errno));
            continue;
        }
        i++;
        ret = read(fd, buf, 256);
        printf("Cnt%d: read(%s) return %d.\n", i, argv[PT_DEV_NAME], ret);
        str_print(buf, ret);
    }
    close(fd);
    return 0;
}
- OpenDev函数用于打开串口设备。
- set_speed函数用于设置串口通信速率。
- set_Parity函数用于设置数据位、停止位和效验位。
- str_print函 数用于以十六进制格式打印字符串。
- main函数是程序的入口,首先解析命令行参数,然后根据参数打开串口设备并配置串口参数,最后通过循环使用 select 函数读取串口数据。
调试接口
动态调试接口
勾选内核 CONFIG_DYNAMIC_DEBUG 功能,开启 [*] Enable uart dynamic debug 功能
- 
挂载 debugfs 文件系统: mount -t debugfs none /sys/kernel/debug这条命令将 debugfs 文件系 统挂载到 /sys/kernel/debug 目录,以便进行动态调试操作。 
- 
打开 uart 模块所有打印: echo "module sunxi_uart +p" > /mnt/dynamic_debug/control这条命令表示打开名为 sunxi_uart 模块的所有打印信息,并将其输出级别设置为最高(p)。 
- 
打开指定文件的所有打印: echo "file sunxi-uart.c +p" > /mnt/dynamic_debug/control这条命令表示打开名为 sunxi-uart.c 文件的所有打印信息,并将其输出级别设置为最高(p)。 
- 
打开指定文件指定行的打印: echo "file sunxi-uart.c line 615 +p" > /mnt/dynamic_debug/control这条命令表示打开名为 sunxi-uart.c 文件的第 615 行的打印信息,并将其输出级别设置为最高(p)。 
- 
打开指定函数名的打印: echo "func sw_uart_set_termios +p" > /mnt/dynamic_debug/control这条命令表示打开名为 sw_uart_set_termios 函数的打印信息,并将其输出级别设置为最高(p)。 
- 
关闭打印: echo "func sw_uart_set_termios -p" > /mnt/dynamic_debug/control
调试节点
UART端口的一些硬件资源信息:
/sys/devices/platform/soc*/*uart*/dev_info
UART端口的一些运行状态信息,包括控制器的各寄存器值
/sys/devices/platform/soc*/*uart*/status
当前UART端口的寄存器值、收发数据的统计等
/sys/devices/platform/soc*/*uart*/ctrl_info
FAQ
设置 UART 波特率
UART控制器会对输入的时钟源进行分频,最终输出一个频率满足(或近似)UART波特率的时钟信号。UART常用的标准波特率有:
UART时钟的分频比是16的整数倍,分频难免会有误差,所以输出UART Device通信的波特率是否足够精准,很大程度取决于输入的时钟源频率。 考虑到功耗,UART驱动中一般默认使用24M时钟源,但是根据应用场景我们有时候需要切换别的时钟源,基于两个原因:
- 
24MHz/16=1.5MHz,这个最大频率满足不了1.5M以上的波特率应用;
- 
24M分频后得到波特率误差可能太大,也满足不了某些UART外设的冗余要求(一般要求2%或5%以内,由外设决定)。 
UART时钟源来自APB,在不同平台上可能是APB1或APB2,APB的时钟源有多个,包括24MHz(HOSC)和PLL_PERIPH(即驱动中的PLL_PERIPH_CLK),系统默认配置APB的时钟源是24M,如果要提高UART的时钟就要将APB的时钟源设置为PLL_PERIPH,一般挑选600M的PLL_PERIPH时钟。同时要注意到APB也是TWI的时钟源,所以需要兼顾TWI时钟。
各个 UART 波特率对应频点关系如下:

例如需要配置 uart2 的波特率为 460800,在上述关系表中可以看出,对应的APB时钟为30M、37.5M、42.857M、46.153M和50M等,所以需要在设备树里修改 uart2 时钟源:
支持460800波特率需要修改的dts内容如下,注意uart0及其他在用时钟也需要同步修改保持一致性,避免乱码:
			device_type = "uart2";
			reg = <0x0 0x05000800 0x0 0x400>;
			interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
-			clocks = <&clk_uart2>;
+			clocks = <&ccu CLK_BUS_UART0>,
+					<&ccu CLK_APB2>,
+					<&ccu CLK_PSI_AHB1_AHB2>;
+			clock-frequency = <50000000>;
			pinctrl-names = "default", "sleep";
UART 回环测试使 用说明
回环测试分为硬件回环与软件回环:
若需要使用硬件回环,需要短接uart的tx与rx引脚
若需要使用软件回环,可以在/sys/devices/platform路径下找到uart的loopback节点,使用如下指令即可配置软件回环:
echo enable > loopback
注意:除了 enable,其他所有字符都表示不开启回环