瑞芯微|rk3568 uart快速上手
一、调试环境
平台:rk3568
kernel: 4.19.232
SDK: rk_android11.0_sdk
Board: rk3568-evb1-ddr4-v10
二、 rk3568 uart控制器
1. 特性:
rk3568 UART控制器特性如下:
- UART控制器通道:UART0~UART8 【datasheet好像写的有问题】
- 包含2组64字节的 FIFO,用于接收和传输
- 支持流控
- 支持速率 115.2Kbps, 460.8Kbps, 921.6Kbps, 1.5Mbps, 3Mbps, 4Mbps
- 支持5、6、7、8 bits数据位。
- 支持1、1.5、2 bits停止位。
- 支持奇校验和偶校验。
- 支持基于中断/DMA 模式
2. UART控制器架构
-
APB INTERFACE
处理器通过APB接口访问UART的数据,做控制,以及状态信息。
UART支持8、16和32位的APB数据总线宽度。 -
Register block
负责UART的主要功能,包括控制、状态和中断产生。 -
Modem Synchronization block
同步modem输入信号. -
FIFO block
负责FIFO控制和存储或向发送信号以控制外部RAM。 -
Baud Clock Generator
收发比特率设置。 -
Serial Transmitter
数据发送模块。 -
Serial Receiver
数据接收模块。
3. 控制器驱动
瑞芯微提供sdk中已经提供了8250uart驱动。
以下为主要驱动文件:
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驱动
4. 设备树:
普通串口设备将会根据dts中的aliase来对串口进行编号,对应注册成ttySx设备。
dts中的aliases如下:
aliases {
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
serial3 = &uart3;
......
@kernel\arch\arm64\boot\dts\rockchip\rk3568.dtsi
uart6: serial@fe6a0000 {
compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
reg = <0x0 0xfe6a0000 0x0 0x100>;
interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru SCLK_UART6>, <&cru PCLK_UART6>;
clock-names = "baudclk", "apb_pclk";
reg-shift = <2>;
reg-io-width = <4>;
dmas = <&dmac0 12>, <&dmac0 13>;
pinctrl-names = "default";
pinctrl-0 = <&uart6m0_xfer>;
status = "disabled";
};
UART的板级dts配置只有以下参数允许修改:
- dma-names:
"tx" 打开tx dma
"rx" 打开rx dma
"!tx" 关闭tx dma
"!rx" 关闭rx dma - pinctrl-0:
&uart1m0_xfer 配置tx和rx引脚为iomux group 0
&uart1m1_xfer 配置tx和rx引脚为iomux group 1
&uart1m0_ctsn和&uart1m0_rtsn 配置硬件自动流控cts和rts引脚为iomux group 0
&uart1m1_ctsn和&uart1m1_rtsn 配置硬件自动流控cts和rts引脚为iomux group 1 - status:
"okay" 打开
"disabled" 关闭
引脚说明在下面定义:
以UART6为例:
@kernel\arch\arm64\boot\dts\rockchip\rk3568-pinctrl.dtsi
uart6 {
/omit-if-no-ref/
uart6m0_xfer: uart6m0-xfer {
rockchip,pins =
/* uart6_rxm0 */
<2 RK_PA3 3 &pcfg_pull_up>,
/* uart6_txm0 */
<2 RK_PA4 3 &pcfg_pull_up>;
};
/omit-if-no-ref/
uart6m0_ctsn: uart6m0-ctsn {
rockchip,pins =
/* uart6m0_ctsn */
<2 RK_PC0 3 &pcfg_pull_none>;
};
/omit-if-no-ref/
uart6m0_rtsn: uart6m0-rtsn {
rockchip,pins =
/* uart6m0_rtsn */
<2 RK_PB7 3 &pcfg_pull_none>;
};
/omit-if-no-ref/
uart6m1_xfer: uart6m1-xfer {
rockchip,pins =
/* uart6_rxm1 */
<1 RK_PD6 3 &pcfg_pull_up>,
/* uart6_txm1 */
<1 RK_PD5 3 &pcfg_pull_up>;
};
};
5. 使用硬件自动流控
UART使用硬件自动流控时,需要确保UART驱动使能硬件自动流控功能,且在dts中已经切换cts和rts流控引脚的iomux。
建议在高波特率(1.5M波特率及以上)、大数据量的场景下都使用硬件自动流控,即使用四线UART。
6. 使用串口唤醒系统
串口唤醒系统功能是在系统待机时串口保持打开,并且把串口中断设置为唤醒源。使用时需要在dts中增
加以下参数:
&uart1 {
wakeup-source;
};
注意,串口唤醒系统需要同时修改trust固件,请联系Rockchip以获取支持。
三、 移植
1. 修改设备树
sdk中UART默认并没有打开,所以我们只需要修改设备树就可以了。
下面以uart6为例,带流控:
/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi
&uart6{
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart6m1_xfer >;
};
注意:
引脚选择有两种配置: m0、m1;
编写设备树之前,查看电路图先确认,
公板是m1。
只有m0支持流控,如果需要支持设置 pinctrl-0:
pinctrl-0 = <&uart6m0_xfer &uart6m0_ctsn &uart6m0_rtsn>;
重新编译烧录boot.img即可。
查看设备文件
rk3568_r:/ # ls /dev/ttyS*
/dev/ttyS6 /dev/ttyS8
其中ttyS8是给蓝牙使用。
2. 引脚复用问题:uart6与gmac0冲突
uart6引脚与以太网口Gmac0会有冲突:
@kernel\arch\arm64\boot\dts\rockchip\rk3568-pinctrl.dtsi
gmac0 {
/omit-if-no-ref/
gmac0_miim: gmac0-miim {
rockchip,pins =
/* gmac0_mdc */
<2 RK_PC3 2 &pcfg_pull_none>,
/* gmac0_mdio */
<2 RK_PC4 2 &pcfg_pull_none>;
};
…………………………
/omit-if-no-ref/
gmac0_rgmii_bus: gmac0-rgmii-bus {
rockchip,pins =
/* gmac0_rxd2 */
<2 RK_PA3 2 &pcfg_pull_none>,
/* gmac0_rxd3 */
<2 RK_PA4 2 &pcfg_pull_none>,
/* gmac0_txd2 */
<2 RK_PA6 2 &pcfg_pull_none_drv_level_2>,
/* gmac0_txd3 */
<2 RK_PA7 2 &pcfg_pull_none_drv_level_2>;
};
};
只需要禁用gmac0即可
/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi
&gmac0 {
………………
phy-handle = <&rgmii_phy0>;
status = "disable";
};
3. 通过寄存器,查看引脚复用配置情况
uart6使用到的引脚如下:
1) GRF_GPIO1D_IOMUX_H
Address: Operational Base + offset (0x001C)
2) GRF_GPIO2A_IOMUX_L
Address: Operational Base + offset (0x0020)
3) GRF_GPIO2A_IOMUX_H
Address: Operational Base + offset (0x0024)
4) GRF_GPIO2B_IOMUX_H
Address: Operational Base + offset (0x002C)
5) GRF_GPIO2C_IOMUX_L
Address: Operational Base + offset (0x0030)
uart6寄存器配置对应位位置如下图所示:,
我们只设置m1引脚为uart6的收发引脚,m0引脚未设置
所以只有寄存器 0xFDC60000+0x1c 的 bit[11:4]为 33
四、测试
公板预留了UART2~UART7的接口(4根线),
一口君不会焊接线,所以直接找的硬件工程师把线连好,
我只负责测试。
板子上的测试程序,瑞芯微官方已经提供了: ts_uart.uart
该工具获取,见文章底部。
1. 移植ts_uart.uart
adb root
adb remount
adb push ts_uart.uart /bin
adb push send_0x55 /bin
adb push send_00_ff /bin
2. ts_uart.uart实用
1) 查看ts_uart.uart帮助信息:
rk3568_r:/ # ts_uart.uart
Use the following format to run the HS-UART TEST PROGRAM
ts_uart v1.1
For sending data:
./ts_uart <tx_rx(s/r)> <file_name> <baudrate> <flow_control(0/1)> <max_delay(0-100)> <random_size(0/1)>
tx_rx : send data from file (s) or receive data (r) to put in file
file_name : file name to send data from or place data in
baudrate : baud rate used for TX/RX
flow_control : enables (1) or disables (0) Hardware flow control using RTS/CTS lines
max_delay : defines delay in seconds between each data burst when sending. Choose 0 for continuous stream.
random_size : enables (1) or disables (0) random size data bursts when sending. Choose 0 for max size.
max_delay and random_size are useful for sleep/wakeup over UART testing. ONLY meaningful when sending data
Examples:
Sending data (no delays)
ts_uart s init.rc 1500000 0 0 0 /dev/ttyS0
loop back mode:
ts_uart m init.rc 1500000 0 0 0 /dev/ttyS0
receive, data must be 0x55
ts_uart r init.rc 1500000 0 0 0 /dev/ttyS0
2) 非流控read:
ts_uart.uart r init.rc 115200 0 0 0 /dev/ttyS6
3) 流控read:
ts_uart.uart r init.rc 115200 1 0 0 /dev/ttyS6
4) 非流控write:
ts_uart.uart s /data/send_0x55.0x55 115200 0 0 0 /dev/ttyS6
5) 流控write:
ts_uart.uart s /data/send_0x55.0x55 115200 1 0 0 /dev/ttyS6
五、编写自己的测试程序
下面是一口君自己编写的测试程序,可以实现简单的数据收发,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<termios.h>
#include<string.h>
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0)
{
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
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 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
case 460800:
cfsetispeed(&newtio, B460800);
cfsetospeed(&newtio, B460800);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
// printf("set done!\n\r");
return 0;
}
int main(void)
{
int fd1;
char data[10] = "yikoupeng";
char buf[100]={0};
fd1 = open( "/dev/ttyS6", O_RDWR);
if (fd1 == -1)
exit(1);
nset = set_opt(fd1, 115200, 8, 'N', 1);
if (nset == -1)
{
exit(1);
}
printf("write start!\n");
write(fd1,data,strlen(data));
read(fd1,buf,sizeof(buf));
printf("rcv:%s\n",buf);
close(fd1);
return 0;
}
工具ts_uart.uart s获取
公众号后台回复:rxw