前言专栏总目录本文主要讲解如何关于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中:
uart2: serial@feb50000 { compatible = rockchip,rk3588-uart, snps,dw-apb-uart; reg = ; interrupts = ; clocks = , ; clock-names = baudclk, apb_pclk; reg-shift = ; reg-io-width = ; dmas = , ; pinctrl-names = default; pinctrl-0 = ; status = disabled; };4.1作为普通串口
假入我们想使用w3开发板上40pin上的uart7
我们在dts可以使用如下配置打开
&uart7 {status = okay;pinctrl-names = default;pinctrl-0 = ;};4.2作为调试串口rockchip uart作为控制台,使用fiq_debugger流程。
在dts中fiq_debugger节点配置如下。由于fiq_debugger和普通串口互斥,在使能fiq_debugger节点后必须禁用对应的普通串口uart节点。
chosen: chosen { bootargs = earlycon=uart8250,mmio32,0xfe660000 console=ttyfiq0;};fiq-debugger { compatible = rockchip,fiq-debugger; rockchip,serial-id = ; rockchip,wake-irq = ; /* if enable uart uses irq instead of fiq */ rockchip,irq-mode-enable = ; rockchip,baudrate = ; /* only 115200 and 1500000 */ interrupts = ; pinctrl-names = default; pinctrl-0 = ; status = okay;};&uart2 { status = disabled;};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来的。
aliases {serial0 = &uart0;serial1 = &uart1;serial2 = &uart2;serial3 = &uart3;}对应uart0注册为ttys0,uart0注册为ttys1,如果需要把uart3注册成ttys1,可以进行以下修改
serial1 = &uart3; 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相关配置参数
chosen: chosen { bootargs = earlycon=uart8250,mmio32,0xfeb50000 console=ttyfiq0 irqchip.gicv3_pseudo_nmi=0 root=partuuid=614e0000-0000 rw rootwait; };将0xfeb50000 console=ttyfiq0 去掉,然后找到fiq-debugger节点,修改serial-id为0xffffffff,去掉uart引脚复用相关配置。注意,需要保持fiqdebugger节点使能,保持fiq-debugger流程系统才能正常启动
fiq_debugger: fiq-debugger { compatible = rockchip,fiq-debugger; rockchip,serial-id = ; rockchip,wake-irq = ; /* if enable uart uses irq instead of fiq */ rockchip,irq-mode-enable = ; rockchip,baudrate = ; /* only 115200 and 1500000 */ interrupts = ; status = okay; };切换打印串口例如将kernel打印从uart2切换到uart3,在dts配置中找到类似以下内容,将uart基地址由uart2改为uart3.
bootargs = earlycon=uart8250,mmio32,0xfe670000 console=ttyfiq0;
0xfe670000是uart3基地址,然后找到fiq-debugger节点,修改serial-id为3,修改uart3引脚复用配置pinctrl-0 = 。注意,同时需要将切换为打印串口的uart3作为普通串口的节点禁用。
六、串口测试在开发板上跑一套应用程序,可以发送数据,可以接收数据,测试方法可以短接tx_rx
#include #include #include #include #include #include #include #include #include int read_data(int fd, void *buf, int len);int write_data(int fd, void *buf, int len);int setup_port(int fd, int baud, int databits, int parity, int stopbits);void print_usage(char *program_name);pthread_mutex_t mutex = pthread_mutex_initializer;pthread_cond_t data_ready = pthread_cond_initializer;int data_available = 0;void *read_thread(void *arg) { int fd = *(int *)arg; char buffer[1024]; // 存储读取的数据 while (1) { int bytes_read = read_data(fd, buffer, sizeof(buffer)); if (bytes_read > 0) { printf(read thread: read %d bytes: %s\\n, bytes_read, buffer); } else { // 处理读取错误或设备关闭的情况 break; } } pthread_exit(null);}void *write_thread(void *arg) { int fd = *(int *)arg; char input[1024]; // 存储用户输入的数据 while (1) { printf(enter data to write (or 'q' to quit): ); fgets(input, sizeof(input), stdin); if (strcmp(input, q\\n) == 0 || strcmp(input, q\\n) == 0) { // 用户输入 'q' 或 'q',退出循环 break; } int len = strlen(input); int bytes_written = write_data(fd, input, len); if (bytes_written > 0) { printf(write thread: wrote %d bytes: %s\\n, bytes_written, input); } } pthread_exit(null);}int main(int argc, char *argv[]) //./a.out /dev/ttys4 115200 8 0 1{ int fd; int baud; int len; int count; int i; int databits; int stopbits; int parity; if (argc != 6) { print_usage(argv[0]); return 1; } baud = atoi(argv[2]); if ((baud 921600)) { fprintf(stderr, invalid baudrate!\\n); return 1; } databits = atoi(argv[3]); if ((databits 8)) { fprintf(stderr, invalid databits!\\n); return 1; } parity = atoi(argv[4]); if ((parity 2)) { fprintf(stderr, invalid parity!\\n); return 1; } stopbits = atoi(argv[5]); if ((stopbits 2)) { fprintf(stderr, invalid stopbits!\\n); return 1; } fd = open(argv[1], o_rdwr, 0); if (fd < 0) { fprintf(stderr, open error %s\\n, argv[1], strerror(errno)); return 1; } if (setup_port(fd, baud, databits, parity, stopbits)) { fprintf(stderr, setup_port error %s\\n, strerror(errno)); close(fd); return 1; } pthread_t read_tid, write_tid; int ret; // 创建读取线程 ret = pthread_create(&read_tid, null, read_thread, &fd); if (ret != 0) { fprintf(stderr, failed to create read thread\\n); return 1; } // 创建写入线程 ret = pthread_create(&write_tid, null, write_thread, &fd); if (ret != 0) { fprintf(stderr, failed to create write thread\\n); return 1; } // 等待读取线程和写入线程结束 pthread_join(read_tid, null); pthread_join(write_tid, null); close(fd); return 0;}static int baudflag_arr[] = { b921600, b460800, b230400, b115200, b57600, b38400, b19200, b9600, b4800, b2400, b1800, b1200, b600, b300, b150, b110, b75, b50};static int speed_arr[] = { 921600, 460800, 230400, 115200, 57600, 38400, 19200, 9600, 4800, 2400, 1800, 1200, 600, 300, 150, 110, 75, 50};int speed_to_flag(int speed){ int i; for (i = 0; i < sizeof(speed_arr)/sizeof(int); i++) { if (speed == speed_arr[i]) { return baudflag_arr[i]; } } fprintf(stderr, unsupported baudrate, use 9600 instead!\\n); return b9600;}static struct termio oterm_attr;int setup_port(int fd, int baud, int databits, int parity, int stopbits){ struct termio term_attr; if (ioctl(fd, tcgeta, &term_attr) < 0) { return -1; } memcpy(&oterm_attr, &term_attr, sizeof(struct termio)); term_attr.c_iflag &= ~(inlcr | igncr | icrnl | istrip); term_attr.c_oflag &= ~(opost | onlcr | ocrnl); term_attr.c_lflag &= ~(isig | echo | icanon | noflsh); term_attr.c_cflag &= ~cbaud; term_attr.c_cflag |= cread | speed_to_flag(baud); term_attr.c_cflag &= ~(csize); switch (databits) { case 5: term_attr.c_cflag |= cs5; break; case 6: term_attr.c_cflag |= cs6; break; case 7: term_attr.c_cflag |= cs7; break; case 8: default: term_attr.c_cflag |= cs8; break; } switch (parity) { case 1: term_attr.c_cflag |= (parenb | parodd); break; case 2: term_attr.c_cflag |= parenb; term_attr.c_cflag &= ~(parodd); break; case 0: default: term_attr.c_cflag &= ~(parenb); break; } switch (stopbits) { case 2: term_attr.c_cflag |= cstopb; break; case 1: default: term_attr.c_cflag &= ~cstopb; break; } term_attr.c_cc[vmin] = 1; term_attr.c_cc[vtime] = 0; if (ioctl(fd, tcsetaw, &term_attr) < 0) { return -1; } if (ioctl(fd, tcflsh, 2) 0) { ret = read(fd, (char*)buf + count, len); if (ret 0) { ret = write(fd, (char*)buf + count, len); if (ret < 1) { fprintf(stderr, write error %s\\n, strerror(errno)); break; } count += ret; len = len - ret; } return count;}void print_usage(char *program_name){ fprintf(stderr, *************************************\\n a simple serial port test utility\\n *************************************\\n\\n usage:\\n %s \\n databits: 5, 6, 7, 8\\n parity: 0(none), 1(odd), 2(even)\\n stopbits: 1, 2\\n example:\\n %s /dev/ttys4 115200 8 0 1\\n\\n, program_name, program_name );}运行效果如下:
思特威宣布完成近15亿元人民币新一轮融资
PCB中via与pad有什么区别
TYPE-C接口是否会代替Micro USB接口
一种用于电动汽车的新型无线充电系统
5G开启云VR教育新篇章
RK3588-UART
激光复合焊技术生产工艺有哪些要领
电子最新价格动态及电子行业近期走势分析:传感器涨幅居首
DP光纤线和HDMI光纤线该选择哪一个
AM Transmitter
vivo正式对外官宣自研V1影像芯片
区块链在互联网金融领域有哪一些应用
夏普的LCD白山工厂什么时候会重新开启
亥姆霍兹自由能判据
森萨塔推出首款经UL认证的A2L制冷剂泄漏检测传感器
Pickering Interfaces将在Electronica China慕尼黑上海电子展上,重点展出PXI自动测试模块
示波器探头各种作用及工作原理
基于NumPy从头搭建神经网络,分步骤详细讲解
常见的齿轮减速机有哪些
清华系为中国AI芯片注入更多活力 两大派系将共同壮大中国的AI事业