UART使用参考
1. 概述¶
本文讲述Sigmastar UART驱动使用方法,UART驱动采用标准的Linux框架,能够使用统一的接口操作UART,同时在Linux框架外添加符合Sigmastar IP功能的附加设置。当前平台总共有4组带 URDMA的FUART,其中UART0默认作为console使用。
2. kernel代码及配置¶
2.1. 设备树节点¶
UART对应的别名和dtsi节点如下所示,别名可以让我们更方便访问节点。
1. sliases {
2. console = &uart0;
3. serial0 = &uart0;
4. serial1 = &uart1;
5. serial2 = &fuart;
6. serial3 = &uart2;
7. };
8.
9. uart0: uart@1F221000 {
10. compatible = "sstar,uart";
11. reg = <0x1F221000 0x100>;
12. interrupts = <GIC_SPI INT_IRQ_UART_0 IRQ_TYPE_LEVEL_HIGH>;
13. clocks = <&CLK_uart0>;
14. status = "ok";
15. }
16.
17. fuart: fuart@1F220400 {
18. compatible = "sstar,uart";
19. reg = <0x1F220400 0x100>, <0x1F220600 0x100>;
20. interrupts = <GIC_SPI INT_IRQ_FUART IRQ_TYPE_LEVEL_HIGH>,
21. <GIC_SPI INT_IRQ_URDMA_TYPE_LEVEL_HIGH>;
22. clocks = <&CLK_fuart>;
23. sctp_enable = <0>;
24. dma = <0>;
25. tolerance = <3>;
26. status = "ok";
27. }
2.2. 节点属性含义¶
DTSI节点各属性含义如表2-1所示,其中sctp_enable和dma属性为fuart独有的属性,tolerance全部uart都可以添加但是一般只有fuart需要特别设定。
表2-1
| 属性值 | 描述 | 备注 |
|---|---|---|
| Compatible | 用于匹配驱动进行驱动的注册 | 禁止修改 |
| Reg | 用于指定UART(和URDMA)所在的Bank及范围 | 不需要修改 |
| Interrupts | 指定UART(和URDMA)的中断及触发方式 | 不需要修改 |
| Clocks | 指定UART的时钟源 | 不需要修改 |
| Sctp_enable | 使能FUART的硬件流控 | 需要时写为1 |
| Dma | 使能FUART的DMA功能 | 需要时写为1 |
| Tolerance | 设置UART设置波特率可以允许的误差 | 不能设置为3 |
| Status | 选择是否使能UART驱动 | 根据需要修改 |
2.3. KERNEL CONFIG¶
要将UART驱动编译进kernel中需要在命令行键入make menuconfig进入kernel配置界面,之后打开Serial / UART driver即可,默认是打开的。
Device Drivers-->
SStar SoC platform drivers-->
[*] Serial / UART driver
将光标移到Serial / UART driver之后按下空格即可操作:
-
M为将UART driver编译为ko; -
*为将UART driver直接编入kernel。
2.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>,
3. 波特率¶
3.1. 波特率的计算¶
波特率是指数据信号对载波的调制速率,它用单位时间内载波调制状态改变的次数来表示,是UART的一个重要的指标。目前的硬件设计UATR实际输出的波特率由输入到UART的Clk source和设置的分频值共同确定。波特率(BAUD)、分频值(DIV)以及输入的CLK频率(CLK)三者的关系如下:
DIV=CLK/(BAUD×16)
由于给到UART的CLK rate并不是连续的,根据公式得出UART可以支持的波特率(误差3%)也不是连续的。目前可以支持1M以下的常用波特率,其他的波特率误差需要计算误差是否符合标准。
波特率分频计算:
| CLK频率 | 设置波特率 | 分频 | 分频(整数) | 分频(Hex) | 实际波特率 | 误差(%) |
|---|---|---|---|---|---|---|
| 172800000 | 115200 | 93.75 | 94 | 5E | 114893.617 | -0.265957447 |
3.2. 波特率的修改¶
UART的波特率可以通过stty命令修改,以将UART1的波特率修改为115200为例:
stty -F /dev/sttyS1 ospeed 115200
4. 测试¶
4.1. 测试方法¶
测试UART功能常用方式是将UART的TX脚和RX脚短接,配置好padmux设置波特率之后使用测试程序进行自发自收测试,量取UART TX脚上的波形解析波特率看是否和设置的相同,并查看测试程序打印接收到的数据是否和发送的数据相同。
4.2. 测试代码¶
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <sys/types.h>
5. #include <sys/stat.h>
6. #include <fcntl.h>
7. #include <errno.h>
8. #include <sys/time.h>
9. #include <time.h>
10. #include <string.h>
11. #include <sys/ioctl.h>
12. #include <termios.h>
13. #include <stdint.h>
14. #include <stdio.h>
15. #include <fcntl.h>
16. #include <sys/ioctl.h>
17. #include <linux/spi/spidev.h>
18. #include <string.h>
19. #include <assert.h>
20. #include <netinet/in.h>
21. #include <sys/types.h>
22. #include <signal.h>
23. #include <stdlib.h>
24. #include <sys/time.h>
25.
26. #define msleep(x) usleep(x*1000)
27.
28. void my_printf(const char* head_l, unsigned char*date_in, int length_l)
29. {
30. int k;
31. printf("%s ", head_l);
32. for ( k = 0; k < length_l; k++)
33. {
34. printf("%02x ", date_in[k]);
35. }
36. printf("\n");
37. }
38.
39. int select_serial_device(int dev_id, char *name)
40. {
41. switch (dev_id)
42. {
43. case 1:
44. strcpy(name, "/dev/ttyS1");
45. break;
46.
47. case 2:
48. strcpy(name, "/dev/ttyS2");
49. break;
50.
51. case 3:
52. strcpy(name, "/dev/ttyS3");
53. break;
54.
55. case 4:
56. strcpy(name, "/dev/ttyS4");
57. break;
58.
59. case 5:
60. strcpy(name, "/dev/ttyS5");
61. break;
62.
63. case 6:
64. strcpy(name, "/dev/ttyS6");
65. break;
66.
67. case 7:
68. strcpy(name, "/dev/ttyS7");
69. break;
70.
71. case 8:
72. strcpy(name, "/dev/ttyS8");
73. break;
74.
75. case 9:
76. strcpy(name, "/dev/ttyS9");
77. break;
78. case 10:
79. strcpy(name, "/dev/ttyS10");
80. break;
81.
82. case 11:
83. strcpy(name, "/dev/ttyS11");
84. break;
85.
86. default:
87. return -1;
88. }
89. return 0;
90. }
91. unsigned int choose_baud_rate(int br_id)
92. {
93. unsigned int baud;
94. switch (br_id)
95. {
96. case 0:
97. baud = B0;
98. break;
99. case 50:
100. baud = B50;
101. break;
102. case 75:
103. baud = B75;
104. break;
105. case 110:
106. baud = B110;
107. break;
108. case 134:
109. baud = B134;
110. break;
111. case 150:
112. baud = B150;
113. break;
114. case 200:
115. baud = B200;
116. break;
117. case 300:
118. baud = B300;
119. break;
120. case 600:
121. baud = B600;
122. break;
123. case 1200:
124. baud = B1200;
125. break;
126. case 1800:
127. baud = B1800;
128. break;
129. case 2400:
130. baud = B2400;
131. break;
132. case 9600:
133. baud = B9600;
134. break;
135. case 19200:
136. baud = B19200;
137. break;
138. case 38400:
139. baud = B38400;
140. break;
141. case 57600:
142. baud = B57600;
143. break;
144. case 115200:
145. baud = B115200;
146. break;
147. case 460800:
148. baud = B460800;
149. break;
150. case 921600:
151. baud = B921600;
152. break;
153. case 1000000:
154. baud = B1000000;
155. break;
156. default:
157. printf("input error: baud rate not correct");
158. return -1;
159. }
160. return baud;
161. }
162. int init_serial_device(char *name, int baud)
163. {
164. int fd;
165. int ret;
166. struct termios options;
167.
168. fd = open(name, O_RDWR | O_NDELAY | O_NOCTTY);
169.
170. if (fd == -1){
171. printf("%s: open error\n", name);
172. return -1;
173. }
174. ret = tcgetattr(fd, &options);
175. if (-1 == ret)
176. return -1;
177.
178. options.c_cflag &= ~CSIZE; //屏蔽其他标志
179. options.c_cflag |= CS8; //将数据位修改为8bit
180. options.c_cflag &= ~PARENB; //无校验
181. options.c_cflag &= ~CSTOPB; // 设置一位停止位;
182.
183. options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
184. cfsetispeed(&options, baud);
185. cfsetospeed(&options, baud);
186. options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
187. ret = tcsetattr(fd, TCSANOW, &options);
188. if (-1 == ret)
189. return -1;
190. return fd;
191. }
192.
193. void process_serial_data(int fd)
194. {
195. char sendbuf[64] = "123456789123456789123456789123456789";
196. char recvbuf[64] = "";
197. int ret;
198.
199. while (1){
200. ret = write(fd, sendbuf,36); //发送数组
201. my_printf("send:",sendbuf,ret);
202. msleep(100);
203.
204. while(1){
205. memset(recvbuf, 0, sizeof(recvbuf));
206. ret = read(fd, recvbuf, 255);
207. if (ret == 0)
208. continue;
209. if(ret>0){
210. my_printf("recv:",recvbuf,ret);
211. break;
212. }
213. }
214. msleep(200);
215. }
216. }
217.
218. int main(int argc, char **argv)
219. {
220. int fd;
221. int bn;
222. int ret;
223. char name[12];
224. char sn;
225. speed_t baud;
226.
227. if (argc < 2){
228. printf("input error:*argv=1-11\n");
229. exit(-1);
230. }
231. if (argc > 3){
232. printf("input error: less than 4 parameters\n");
233. exit(-1);
234. }
235.
236. ret = sscanf(argv[1], "%d", &sn);
237.
238. ret = select_serial_device(sn,name);
239. if (-1 == ret)
240. return -1;
241.
242. ret = sscanf(argv[2], "%d", &bn);
243.
244. baud = choose_baud_rate(bn);
245. if (-1 == ret)
246. return -1;
247.
248. fd = init_serial_device(name,baud);
249. if (-1 == fd)
250. return -1;
251.
252. process_serial_data(fd);
253.
254. return 0;
255. }
5. FUART¶
5.1. FUART的特殊性¶
FUART相对于普通的UART增加了DMA以及硬件流控,所以理论上fuart是可以支持更大的波特率范围且可以在更高的波特率运行保证数据不丢失。M6P、I6C平台所有uart已经都支持dma。硬件流控仅FUART支持。
5.2. URDMA¶
URDMA的功能是在UART接收到数据之后自动将数据读出放到内存的指定地址中,将我们写入到指定内存地址的数据分多次放入到UART FIFO中由UART TX发出。
将FUART的dtsi节点中的dma 属性设置为1可以开启FUART的DMA功能。DMA功能可以降低UART驱动的CPU占用率以及支持更高的波特率不丢失数据。
1. fuart: fuart@1F220400 {
2. compatible = "sstar,uart";
3. reg = <0x1F220400 0x100>, <0x1F220600 0x100>;
4. interrupts = <GIC_SPI INT_IRQ_FUART IRQ_TYPE_LEVEL_HIGH>,
5. <GIC_SPI INT_IRQ_URDMA_TYPE_LEVEL_HIGH>;
6. clocks = <&CLK_fuart>;
7. sctp_enable = <0>;
8. dma = <1>;
9. tolerance = <3>;
10. status = "ok";
11. }
5.3. 硬件流控¶
硬件流控功能也是一种防止数据丢失的方法,需要将FUART的dtsi节点中的sctp_enable属性设置为1开启。
1. fuart: fuart@1F220400 {
2. compatible = "sstar,uart";
3. reg = <0x1F220400 0x100>, <0x1F220600 0x100>;
4. interrupts = <GIC_SPI INT_IRQ_FUART IRQ_TYPE_LEVEL_HIGH>,
5. <GIC_SPI INT_IRQ_URDMA_TYPE_LEVEL_HIGH>;
6. clocks = <&CLK_fuart>;
7. sctp_enable = <1>;
8. dma = <0>;
9. tolerance = <3>;
10. status = "ok";
11. }
5.4. FUART测试¶
开启fuart硬件流控的测试代码如下:
1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <sys/types.h>
5. #include <sys/stat.h>
6. #include <fcntl.h>
7. #include <termios.h>
8. #include <errno.h>
9.
10. int enable_rtscts(int fd)
11. {
12. struct termios options;
13. if(tcgetattr(fd, &options) != 0)
14. {
15. perror("SetupSerial 1");
16. return -1;
17. }
18. options.c_cflag |= CRTSCTS;
19. tcflush(fd, TCIFLUSH);
20. if(tcsetattr(fd,TCSANOW, &opyions) != 0)
21. {
22. perror("SetupSerial 3");
23. return -1;
24. }
25. return 0;
26. }
27.
28. int main(int argc, char **argv)
29. {
30. int fd;
31. fd = open("/dev/ttyS2", O_RDWR|O_NOCTTY|O_NDELAY);
32. if(fd <= 0)
33. {
34. printf("Can't Open Serial Port!\n");
35. return -1;
36. }
37. if(enable_rtscts(fd) == -1)
38. {
39. printf("Set Parity Error\n");
40. exit(1);
41. }
42. printf("ebable rts cts\n");
43. close(fd);
44. return 0;
45. }