• Banana Pi BPI-W3(Armsom W3)RK3588开当板之调试UART


    前言

    • 本文主要讲解如何关于RK3588开发板UART的使用和调试方法,包括UART作为普通串口和控制台两种不同使用场景

    一. 功能特点

    Rockchip UART (Universal Asynchronous Receiver/Transmitter) 基于16550A串口标准,完整模块支持以下功能:

    • 支持5、6、7、8 bits数据位。
    • 支持1、1.5、2 bits停止位。
    • 支持奇校验和偶校验,不支持mark校验和space校验。
    • 支持接收FIFO和发送FIFO,一般为32字节或者64字节。
    • 支持最高4M波特率,实际支持波特率需要芯片时钟分频策略配合。
    • 支持中断传输模式和DMA传输模式。 支持硬件自动流控,RTS+CTS。

    二、代码位置

    Linux kernel 中,使用8250串口通用驱动,以下为主要驱动文件:

    drivers/tty/serial/8250/8250_core.c # 8250串口驱动核心 drivers/tty/serial/8250/8250_dw.c # Synopsis DesignWare 8250串口驱动 drivers/tty/serial/8250/8250_dma.c # 8250串口DMA驱动 drivers/tty/serial/8250/8250_port.c # 8250串口端口操作 drivers/tty/serial/8250/8250_early.c # 8250串口early console驱动

    SDK中提供的UART默认配置已经使用了8250驱动我们就不需要修改

    三、硬件原理图

    串口功能的硬件上比较简单,这是只附上调试串口的原理图

    四、设备树配置

    rk平台的设备树修改路径都是在kernel\arch\arm64\boot\dts\rockchip下面,具体哪个文件根据对应开发板来决定,通常描述设备硬件配置在rkxxxx.dtsi中,比如在rk3588s.dtsi中:

    1. uart2: serial@feb50000 {
    2. compatible = "rockchip,rk3588-uart", "snps,dw-apb-uart";
    3. reg = <0x0 0xfeb50000 0x0 0x100>;
    4. interrupts = <GIC_SPI 333 IRQ_TYPE_LEVEL_HIGH>;
    5. clocks = <&cru SCLK_UART2>, <&cru PCLK_UART2>;
    6. clock-names = "baudclk", "apb_pclk";
    7. reg-shift = <2>;
    8. reg-io-width = <4>;
    9. dmas = <&dmac0 10>, <&dmac0 11>;
    10. pinctrl-names = "default";
    11. pinctrl-0 = <&uart2m1_xfer>;
    12. status = "disabled";
    13. };

    4.1作为普通串口

    假入我们想使用w3开发板上40PIN上的uart7 我们在dts可以使用如下配置打开

    1. &uart7 {
    2. status = "okay";
    3. pinctrl-names = "default";
    4. pinctrl-0 = <&uart7m1_xfer>;
    5. };

    4.2作为调试串口

    Rockchip UART作为控制台,使用fiq_debugger流程。 在dts中fiq_debugger节点配置如下。由于fiq_debugger和普通串口互斥,在使能fiq_debugger节点后必须禁用对应的普通串口uart节点。

    1. chosen: chosen {
    2. bootargs = "earlycon=uart8250,mmio32,0xfe660000 console=ttyFIQ0";
    3. };
    4. fiq-debugger {
    5. compatible = "rockchip,fiq-debugger";
    6. rockchip,serial-id = <2>;
    7. rockchip,wake-irq = <0>;
    8. /* If enable uart uses irq instead of fiq */
    9. rockchip,irq-mode-enable = <1>;
    10. rockchip,baudrate = <1500000>; /* Only 115200 and 1500000 */
    11. interrupts = <GIC_SPI 252 IRQ_TYPE_LEVEL_LOW>;
    12. pinctrl-names = "default";
    13. pinctrl-0 = <&uart2m0_xfer>;
    14. status = "okay";
    15. };
    16. &uart2 {
    17. status = "disabled";
    18. };
    • rockchip,serial-id:使用的UART编号。修改serial-id到不同UART,fiq_debugger设备也会注册成
    • ttyFIQ0设备。 rockchip,irq-mode-enable:配置为1使用irq中断,配置为0使用fiq中断。
    • interrupts:配置的辅助中断,保持默认即可。
    • pinctrl-0:使用的串口引脚
    • rockchip,baudrate:波特率配置

    五、串口相关问题

    5.1设备注册

    普通串口设备将会根据dts中的aliase来对串口进行编号,对应注册成ttySx设备。注册的节点为/dev/ttyS4,命名规则是通过dts中的aliases来的。

    1. aliases {
    2. serial0 = &uart0;
    3. serial1 = &uart1;
    4. serial2 = &uart2;
    5. serial3 = &uart3;
    6. }

    对应uart0注册为ttyS0,uart0注册为ttyS1,如果需要把uart3注册成ttyS1,可以进行以下修改

    1. serial1 = &uart3;
    2. serial3 = &uart1;

    5.2控制台打印相关

    Rockchip UART打印通常包括DDR阶段、Miniloader阶段、TF-A (Trusted Firmware-A)阶段、OP-TEE阶段、Uboot阶段和Kernel阶段,我们平时主要关注的是uboot阶段和kernel阶段的打印,在这两个阶段我们可以尝试关闭所有打印或切换所有打印到其他UART,RK平台默认的调试串口是uart2_m0这一组引脚,假如现在我将打印换成其他串口,可以尝试以下做法。

    5.2.1DDR Loader修改方法

    DDR Loader中关闭或切换打印,需要修改DDR Loader中的UART打印配置,修改文件rkbin/tools/ddrbin_param.txt中的以下参数:

    uart id= # UART控制器id,配置为0xf为关闭打印 uart iomux= # 复用的IOMUX引脚 uart baudrate= # 115200 or 1500000

    修改完成后,使用以下命令重新生成ddr.bin固件。

    ./ddrbin_tool ddrbin_param.txt rk3588_ddr_lp4_2112MHz_lp5_2736MHz_v1.09.bin

    5.2.2Uboot修改方法

    Uboot中关闭打印,需要在menuconfig中,打开配CONFIG_DISABLE_CONSOLE,保存到.config文件 Uboot中切换打印,由传参机制决定,不需要进行额外修改。uboot解析传参机制相关代码在arch/arm/mach-rockchip/board.c的board_init_f_init_serial()函数中。

    5.2.3kernel修改方法

    去掉打印需要在menuconfig中,关闭配置CONFIG_SERIAL_8250_CONSOLE。

    Device Drivers ---> Character devices ---> Serial drivers ---> [ ]Console on 8250/16550 and compatible serial port

    在dts配置中找到类似以下内容,并去掉UART基地址和console相关配置参数

    1. chosen: chosen {
    2. bootargs = "earlycon=uart8250,mmio32,0xfeb50000 console=ttyFIQ0 irqchip.gicv3_pseudo_nmi=0 root=PARTUUID=614e0000-0000 rw rootwait";
    3. };

    将0xfeb50000 console=ttyFIQ0 去掉,然后找到fiq-debugger节点,修改serial-id为0xffffffff,去掉UART引脚复用相关配置。注意,需要保持fiq�debugger节点使能,保持fiq-debugger流程系统才能正常启动

    1. fiq_debugger: fiq-debugger {
    2. compatible = "rockchip,fiq-debugger";
    3. rockchip,serial-id = <0xffffffff>;
    4. rockchip,wake-irq = <0>;
    5. /* If enable uart uses irq instead of fiq */
    6. rockchip,irq-mode-enable = <1>;
    7. rockchip,baudrate = <1500000>; /* Only 115200 and 1500000 */
    8. interrupts = <GIC_SPI 423 IRQ_TYPE_LEVEL_LOW>;
    9. status = "okay";
    10. };

    切换打印串口例如将Kernel打印从UART2切换到UART3,在dts配置中找到类似以下内容,将UART基地址由UART2改为UART3.

    bootargs = "earlycon=uart8250,mmio32,0xfe670000 console=ttyFIQ0";

    0xfe670000是UART3基地址,然后找到fiq-debugger节点,修改serial-id为3,修改UART3引脚复用配置pinctrl-0 = <&uart3m0_xfer>。注意,同时需要将切换为打印串口的UART3作为普通串口的节点禁用。

    六、串口测试

    在开发板上跑一套应用程序,可以发送数据,可以接收数据,测试方法可以短接TX_RX

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <errno.h>
    4. #include <unistd.h>
    5. #include <fcntl.h>
    6. #include <string.h>
    7. #include <termio.h>
    8. #include <time.h>
    9. #include <pthread.h>
    10. int read_data(int fd, void *buf, int len);
    11. int write_data(int fd, void *buf, int len);
    12. int setup_port(int fd, int baud, int databits, int parity, int stopbits);
    13. void print_usage(char *program_name);
    14. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    15. pthread_cond_t data_ready = PTHREAD_COND_INITIALIZER;
    16. int data_available = 0;
    17. void *read_thread(void *arg) {
    18. int fd = *(int *)arg;
    19. char buffer[1024]; // 存储读取的数据
    20. while (1) {
    21. int bytes_read = read_data(fd, buffer, sizeof(buffer));
    22. if (bytes_read > 0) {
    23. printf("Read Thread: Read %d bytes: %s\n", bytes_read, buffer);
    24. } else {
    25. // 处理读取错误或设备关闭的情况
    26. break;
    27. }
    28. }
    29. pthread_exit(NULL);
    30. }
    31. void *write_thread(void *arg) {
    32. int fd = *(int *)arg;
    33. char input[1024]; // 存储用户输入的数据
    34. while (1) {
    35. printf("Enter data to write (or 'q' to quit): ");
    36. fgets(input, sizeof(input), stdin);
    37. if (strcmp(input, "q\n") == 0 || strcmp(input, "Q\n") == 0) {
    38. // 用户输入 'q''Q',退出循环
    39. break;
    40. }
    41. int len = strlen(input);
    42. int bytes_written = write_data(fd, input, len);
    43. if (bytes_written > 0) {
    44. printf("Write Thread: Wrote %d bytes: %s\n", bytes_written, input);
    45. }
    46. }
    47. pthread_exit(NULL);
    48. }
    49. int main(int argc, char *argv[]) //./a.out /dev/ttyS4 115200 8 0 1
    50. {
    51. int fd;
    52. int baud;
    53. int len;
    54. int count;
    55. int i;
    56. int databits;
    57. int stopbits;
    58. int parity;
    59. if (argc != 6) {
    60. print_usage(argv[0]);
    61. return 1;
    62. }
    63. baud = atoi(argv[2]);
    64. if ((baud < 0) || (baud > 921600)) {
    65. fprintf(stderr, "Invalid baudrate!\n");
    66. return 1;
    67. }
    68. databits = atoi(argv[3]);
    69. if ((databits < 5) || (databits > 8)) {
    70. fprintf(stderr, "Invalid databits!\n");
    71. return 1;
    72. }
    73. parity = atoi(argv[4]);
    74. if ((parity < 0) || (parity > 2)) {
    75. fprintf(stderr, "Invalid parity!\n");
    76. return 1;
    77. }
    78. stopbits = atoi(argv[5]);
    79. if ((stopbits < 1) || (stopbits > 2)) {
    80. fprintf(stderr, "Invalid stopbits!\n");
    81. return 1;
    82. }
    83. fd = open(argv[1], O_RDWR, 0);
    84. if (fd < 0) {
    85. fprintf(stderr, "open <%s> error %s\n", argv[1], strerror(errno));
    86. return 1;
    87. }
    88. if (setup_port(fd, baud, databits, parity, stopbits)) {
    89. fprintf(stderr, "setup_port error %s\n", strerror(errno));
    90. close(fd);
    91. return 1;
    92. }
    93. pthread_t read_tid, write_tid;
    94. int ret;
    95. // 创建读取线程
    96. ret = pthread_create(&read_tid, NULL, read_thread, &fd);
    97. if (ret != 0) {
    98. fprintf(stderr, "Failed to create read thread\n");
    99. return 1;
    100. }
    101. // 创建写入线程
    102. ret = pthread_create(&write_tid, NULL, write_thread, &fd);
    103. if (ret != 0) {
    104. fprintf(stderr, "Failed to create write thread\n");
    105. return 1;
    106. }
    107. // 等待读取线程和写入线程结束
    108. pthread_join(read_tid, NULL);
    109. pthread_join(write_tid, NULL);
    110. close(fd);
    111. return 0;
    112. }
    113. static int baudflag_arr[] = {
    114. B921600, B460800, B230400, B115200, B57600, B38400,
    115. B19200, B9600, B4800, B2400, B1800, B1200,
    116. B600, B300, B150, B110, B75, B50
    117. };
    118. static int speed_arr[] = {
    119. 921600, 460800, 230400, 115200, 57600, 38400,
    120. 19200, 9600, 4800, 2400, 1800, 1200,
    121. 600, 300, 150, 110, 75, 50
    122. };
    123. int speed_to_flag(int speed)
    124. {
    125. int i;
    126. for (i = 0; i < sizeof(speed_arr)/sizeof(int); i++) {
    127. if (speed == speed_arr[i]) {
    128. return baudflag_arr[i];
    129. }
    130. }
    131. fprintf(stderr, "Unsupported baudrate, use 9600 instead!\n");
    132. return B9600;
    133. }
    134. static struct termio oterm_attr;
    135. int setup_port(int fd, int baud, int databits, int parity, int stopbits)
    136. {
    137. struct termio term_attr;
    138. if (ioctl(fd, TCGETA, &term_attr) < 0) {
    139. return -1;
    140. }
    141. memcpy(&oterm_attr, &term_attr, sizeof(struct termio));
    142. term_attr.c_iflag &= ~(INLCR | IGNCR | ICRNL | ISTRIP);
    143. term_attr.c_oflag &= ~(OPOST | ONLCR | OCRNL);
    144. term_attr.c_lflag &= ~(ISIG | ECHO | ICANON | NOFLSH);
    145. term_attr.c_cflag &= ~CBAUD;
    146. term_attr.c_cflag |= CREAD | speed_to_flag(baud);
    147. term_attr.c_cflag &= ~(CSIZE);
    148. switch (databits) {
    149. case 5:
    150. term_attr.c_cflag |= CS5;
    151. break;
    152. case 6:
    153. term_attr.c_cflag |= CS6;
    154. break;
    155. case 7:
    156. term_attr.c_cflag |= CS7;
    157. break;
    158. case 8:
    159. default:
    160. term_attr.c_cflag |= CS8;
    161. break;
    162. }
    163. switch (parity) {
    164. case 1:
    165. term_attr.c_cflag |= (PARENB | PARODD);
    166. break;
    167. case 2:
    168. term_attr.c_cflag |= PARENB;
    169. term_attr.c_cflag &= ~(PARODD);
    170. break;
    171. case 0:
    172. default:
    173. term_attr.c_cflag &= ~(PARENB);
    174. break;
    175. }
    176. switch (stopbits) {
    177. case 2:
    178. term_attr.c_cflag |= CSTOPB;
    179. break;
    180. case 1:
    181. default:
    182. term_attr.c_cflag &= ~CSTOPB;
    183. break;
    184. }
    185. term_attr.c_cc[VMIN] = 1;
    186. term_attr.c_cc[VTIME] = 0;
    187. if (ioctl(fd, TCSETAW, &term_attr) < 0) {
    188. return -1;
    189. }
    190. if (ioctl(fd, TCFLSH, 2) < 0) {
    191. return -1;
    192. }
    193. return 0;
    194. }
    195. int read_data(int fd, void *buf, int len)
    196. {
    197. int count;
    198. int ret;
    199. ret = 0;
    200. count = 0;
    201. //while (len > 0) {
    202. ret = read(fd, (char*)buf + count, len);
    203. if (ret < 1) {
    204. fprintf(stderr, "Read error %s\n", strerror(errno));
    205. //break;
    206. }
    207. count += ret;
    208. len = len - ret;
    209. //}
    210. *((char*)buf + count) = 0;
    211. return count;
    212. }
    213. int write_data(int fd, void *buf, int len)
    214. {
    215. int count;
    216. int ret;
    217. ret = 0;
    218. count = 0;
    219. while (len > 0) {
    220. ret = write(fd, (char*)buf + count, len);
    221. if (ret < 1) {
    222. fprintf(stderr, "Write error %s\n", strerror(errno));
    223. break;
    224. }
    225. count += ret;
    226. len = len - ret;
    227. }
    228. return count;
    229. }
    230. void print_usage(char *program_name)
    231. {
    232. fprintf(stderr,
    233. "*************************************\n"
    234. " A Simple Serial Port Test Utility\n"
    235. "*************************************\n\n"
    236. "Usage:\n %s \n"
    237. " databits: 5, 6, 7, 8\n"
    238. " parity: 0(None), 1(Odd), 2(Even)\n"
    239. " stopbits: 1, 2\n"
    240. "Example:\n %s /dev/ttyS4 115200 8 0 1\n\n",
    241. program_name, program_name
    242. );
    243. }

    运行效果如下:

  • 相关阅读:
    【2023 · CANN训练营第一季】应用开发(初级)——第一章 AscendCL概述
    抖音小店无货源,正处于红利期内的电商项目,新手能操作吗?
    G1D5-Intriguing properties of neural networks
    精密空调里面kfb和kfd、kfc 三块pcb板子都负责什么功能?
    Node.js初体验
    Fluent Facede Pattern(外观模式)
    第四节:Vben Admin登录对接后端getUserInfo接口
    Rust学习02:推荐一本入门书,免费的
    力扣第1005题 K 次取反后最大化的数组和 c++ 贪心 双思维
    Monaco Editor教程(八): 实现添加自定义命令,添加自定义菜单action功能
  • 原文地址:https://blog.csdn.net/sinovoip/article/details/134008668