总结一次为R329开启uart的经历
sipeed出的maix sense开发板虽然宣称有5个串口,但是uart0用作了debug,uart1用作的Bluetooth,uart2没引出来,uart3只引出了RX和CTS,需要完整使用串口的话,只能使用ruart,并且maixsense上面预留的4Pin uart也接的也是这个。
好巧不巧的是,sipeed发布的第一版armbian镜像并没有启用其他串口,对ttyS2-ttyS5操作没有任何用,只能手动编辑设备树。
查看设备树
maix sense的kernel官方已经上传到github上,先clone下来
git clone -b r329-wip --depth 1 https://github.com/sipeed/linux.git
设备树在/linux/arch/arm64/boot/dts/allwinner/
路径下,和r329相关的设备树有
sun50i-r329-maix-iia.dtsi
sun50i-r329-maixsense.dts
sun50i-r329.dtsi
三个。
其中uart的描述在中sun50i-r329.dtsi
,如下:
uart0: serial@2500000 {
compatible = "snps,dw-apb-uart";
reg = <0x02500000 0x400>;
interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART0>;
resets = <&ccu RST_BUS_UART0>;
status = "disabled";
};
uart1: serial@2500400 {
compatible = "snps,dw-apb-uart";
reg = <0x02500400 0x400>;
interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART1>;
resets = <&ccu RST_BUS_UART1>;
status = "disabled";
};
uart2: serial@2500800 {
compatible = "snps,dw-apb-uart";
reg = <0x02500800 0x400>;
interrupts = <GIC_SPI 4 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART2>;
resets = <&ccu RST_BUS_UART2>;
status = "disabled";
};
uart3: serial@2500c00 {
compatible = "snps,dw-apb-uart";
reg = <0x02500c00 0x400>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART3>;
resets = <&ccu RST_BUS_UART3>;
status = "disabled";
};
引脚定义也在该文件中
uart0_pb_pins: uart0-pb-pins {
pins = "PB4", "PB5";
function = "uart0";
};
uart1_pg_pins: uart1-pg-pins {
pins = "PG6", "PG7";
function = "uart1";
};
uart fuction在另外两个文件中,其中uart0被用做了stdout-path
,uart1被用作了bluetooth
。
aliases {
serial0 = &uart0;
mmc0 = &mmc0;
};
chosen {
stdout-path = "serial0:115200n8";
};
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_pb_pins>;
status = "okay";
};
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&uart1_pg_pins>, <&uart1_pg_rts_cts_pins>;
uart-has-rtscts;
status = "okay";
bluetooth {
compatible = "realtek,rtl8723ds-bt";
device-wake-gpios = <&r_pio 1 1 GPIO_ACTIVE_HIGH>; /* PM1 */
host-wake-gpios = <&r_pio 1 3 GPIO_ACTIVE_HIGH>; /* PM3 */
enable-gpios = <&r_pio 1 2 GPIO_ACTIVE_HIGH>; /* PM2 */
max-speed = <1500000>;
};
};
添加uart3节点
可以看到,只有uart0和uart1定义了pinctrl-0和function,
其他uart没有定义,所以在linux中没法使用。
uart3已经描述,要开启uart3,只需要添加uart3的pinctrl-0和function即可。
照葫芦画瓢,在sun50i-r329.dtsi
文件中添加pinctrl-0
uart3_ph_pins: uart3-ph-pins {
pins = "PH4", "PH5";
function = "uart3";
};
在sun50i-r329-maixsense.dts
中添加uart3 function
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&uart3_ph_pins>;
status = "okay";
};
即可。
编译dtb make dtbs
,放入maixsense的boot/dtb/allwinner/文件夹中,即可使用。
测试uart3
上电,PH5引脚接RXD。
查看ttyS*
maixsense:~:# ls /dev/ttyS*
/dev/ttyS0 /dev/ttyS2 /dev/ttyS3 /dev/ttyS4 /dev/ttyS5
没有出现ttyS1,可能为某个设备保留,测试ttyS2
使用minicom接收数据 minicom -D /dev/ttyS2
Welcome to minicom 2.8
OPTIONS: I18n
Port /dev/ttyS2, 16:00:56
Press CTRL-A Z for help on special keys
hello sipeed
UART RXD正常。
但是发现lcd屏幕无法刷新了,查看原理图发现PH4连接了LCD的DC,导致了功能冲突。
修改sun50i-r329.dtsi
,设置uart3_tx_pin为缺省值。
uart3_ph_pins: uart3-ph-pins {
pins = "", "PH5";
function = "uart3";
};
重新编译,上电,一切正常。
添加s_uart
uart3虽然能用了,但是只能收不能发,若要使用全功能uart,还需要启用s_uart。
设备树中并没有s_uart相关的描述,因此需要自行添加。
参考uart0的描述,其中reg,interrupts,clocks和resets我们都不知道,因此添加s_uart的重点就在于查找这些参数。
uart0: serial@2500000 {
compatible = "snps,dw-apb-uart";
reg = <0x02500000 0x400>;
interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
clocks = <&ccu CLK_BUS_UART0>;
resets = <&ccu RST_BUS_UART0>;
status = "disabled";
};
查找技术手册
翻开R329 User Manual,找到7.2 UART一章
在7.2.5 Register List小节中,提供了uart的reg基址。
Module Name | Base Address |
---|---|
UART0 | 0x02500000 |
UART1 | 0x02500400 |
UART2 | 0x02500800 |
UART3 | 0x02500C00 |
R_UART0 | 0x07080000 |
其中r_uart0就是我们需要的s_uart(为啥是?因为没别的了。为啥两个代号?我也不清楚) |
reg get!
r_uart: serial@7080000 {
compatible = "snps,dw-apb-uart";
reg = <0x0708000 0x400>;
reg-shift = <2>;
reg-io-width = <4>;
接下来需要查找的参数是interrupts,找到3.8 Generic Interrupt Controller (GIC) 一章。
在Table 3-10 Interrupt Sources 表格中,描述了所有的中断号。
Module Name | Interrupt Number |
---|---|
34 | UART0 |
35 | UART1 |
36 | UART2 |
37 | UART3 |
143 | R_UART0 |
参考标准串口使用的GIC_SPI (SPI:shared processor interrupts 中断号 32 ~32+224), |
|
得到r_uart的中断号143-32=111 |
Interrupt get!
r_uart: serial@7080000 {
compatible = "snps,dw-apb-uart";
reg = <0x0708000 0x400>;
reg-shift = <2>;
reg-io-width = <4>;
接下来就是clocks和resets。
参考设备树中其他信息,CPUS Domain Related
的资源,也就是中断号120以后的资源,使用的是r_ccu
lradc: lradc@7030800 {
compatible = "allwinner,sun50i-r329-lradc";
reg = <0x07030800 0x400>;
interrupts = <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_R_BUS_LRADC>;
resets = <&r_ccu RST_R_BUS_LRADC>;
status = "disabled";
};
其定义在
#include <dt-bindings/clock/sun50i-r329-r-ccu.h>
#include <dt-bindings/reset/sun50i-r329-r-ccu.h>
中。
All get!
r_uart: serial@7080000 {
compatible = "snps,dw-apb-uart";
reg = <0x07080000 0x400>;
interrupts = <GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>;
reg-shift = <2>;
reg-io-width = <4>;
clocks = <&r_ccu CLK_R_BUS_UART>;
resets = <&r_ccu RST_R_BUS_UART>;
};
将s_uart的描述插入sun50i-r329.dtsi中。
配置s_uart
s_uart由cpus控制,因此要把相关描述放入r_pio下
r_pio: pinctrl@7022000 {
compatible = "allwinner,sun50i-r329-r-pinctrl";
reg = <0x07022000 0x400>;
interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&r_ccu CLK_R_APB1>, <&osc24M>, <&rtc 0>;
clock-names = "apb", "hosc", "losc";
gpio-controller;
#gpio-cells = <3>;
interrupt-controller;
#interrupt-cells = <3>;
r_uart_pins: r-uart-pins {
pins = "PL8", "PL9";
function = "s_uart";
};
然后在sun50i-r329-maixsense.dts中开启r_uart
&r_uart{
status = "okay";
};
r_uart的设备树配置就完成了。
但是实际上,在此时的版本中,r_uart还是无法使用。因为在pinctrl中,并没有cpus和cpux下的控制器定义,在Icenowy提交了该次pr后,才能正常使用。
新增的相关引脚配置如下
SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 7),
SUNXI_FUNCTION(0x0, "gpio_in"),
SUNXI_FUNCTION(0x1, "gpio_out"),
SUNXI_FUNCTION(0x2, "s_ir"), /* RX */
SUNXI_FUNCTION(0x4, "clock"), /* X32KFOUT */
SUNXI_FUNCTION(0x5, "s_pwm"), /* PWM5 */
SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 7)), /* PL_EINT7 */
SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 8),
SUNXI_FUNCTION(0x0, "gpio_in"),
SUNXI_FUNCTION(0x1, "gpio_out"),
SUNXI_FUNCTION(0x2, "s_uart"), /* TX */
SUNXI_FUNCTION(0x3, "s_i2c"), /* SDA */
SUNXI_FUNCTION(0x4, "s_ir"), /* RX */
SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 8)), /* PL_EINT8 */
可以发现实际上function = "s_uart";
调用的function由SUNXI_FUNCTION(0x2, "s_uart")
定义。
测试s_uart
上电测试时,发现了一个新问题,启动uboot时,log打印在uart0上,但是启动kernel以后,数据飞了。开始以为是内核卡死了,但是lcd有输出,于是使用ssh连接上,使用minicom操作串口后发现,ttyS0被连接到r_uart上了,uart0此时变成了ttyS2。
最后的解决办法是在aliases中给r_uart设置别名
aliases {
serial0 = &uart0;
serial1 = &r_uart;
mmc0 = &mmc0;
};
经过测试,/dev/ttyS1只有经过aliases分配编号后才能使用,并且只有r_uart可以使用,并且r_uart只会使用/dev/ttyS0和/dev/ttyS1。
当未给r_uart分配编号时,r_uart会抢占/dev/ttyS0,因此所有信息由r_uart输出。
此时uartx会被分配到/dev/ttyS2以后。并且aliases只能分配serial0-serial3之间的编号,即/dev/ttyS0到/dev/ttyS3之间。在给uart分配serial4及以后的编号时,ttyS*不会有任何反应。(目前原因未知)
重新编译后,串口就能正常使用了。
maixsense:~:# ls /dev/ttyS*
/dev/ttyS0 /dev/ttyS1 /dev/ttyS3 /dev/ttyS4 /dev/ttyS5
|/dev/ttyS0| /dev/ttyS1 |/dev/ttyS3 |
| ---- | ---- |---- |---- |
| uart0|r_uart |uart3 |