Zephyr Pinctrl -- 1基础配置使用

Creative Commons
本作品采用知识共享署名

本文简要说明Zephyr Pinctrl和配置使用方法.

历史

在zephyr很早期阶段(2016年)年就引入了pinmux, 但由于早期没有统一的抽象,有少部分soc的设备树支持pin配置(nrf),绝大部分soc都是通过board下面的pinmux直接调用hal的PIN MUX接口(imx),因此Zephyr中的pinmux并不统一,导致添加不同soc的board的时候需要有不同的处理方式。Zephyr project在https://github.com/zephyrproject-rtos/zephyr/issues/39740正式提出修改:

到目前的状态,zephyr已经完全移除了pinmux,并启用了pinctrl,所有的soc均可以通过设备树进行引脚配置,因此掌握pinctrl是变成了使用zephyr的一项基础要求。

背景知识

pinctrl主要有两种功能:

  1. soc的引脚多路复用, 例如某个引脚是作为I2C输出还是作为SPI输出
  2. 引脚硬件的配置,例如某个引脚是上拉还是下拉等等

不同的soc多路复用的方式不一样常见的复用和配置方式如下面两图(源之zephyr文档)
一种是集中方式,一个alt硬件控制一组复用功能,NXP和ST的soc采用这种方式

另一种是分布方式,复用影视和配置由多个硬件控制,ESP和NRF的soc采用这种方式

以上两种方式对使用者最直观的感受就是NXP/ST的soc的一个PIN只能按照spec映射到特定的几种片上设备上的特定功能,例如只能映射连接到SPI/I2C/UART的TX,不能映射到RX或者是SDHC控制器上。
而ESP和NRF的SOC就更为灵活一个PIN可以映射到任意片上设备上。

不同的soc其pin的硬件配置也不一样,例如一些支持硬件消抖动,有一些不支持

Zephyr Pinctrl的简介

在Zephyr中将不同的soc的引脚控制差异全部局限在设备树中
没有pinctrl前rt1062需要一个pinmux.c文件,里面放了大量的代码来配置硬件的复用,例如一个串口的就长这样:

1
2
3
4
5
6
7
8
9
10
11
12
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_12_LPUART1_TX, 0);
IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B0_13_LPUART1_RX, 0);

IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_12_LPUART1_TX,
IOMUXC_SW_PAD_CTL_PAD_PKE_MASK |
IOMUXC_SW_PAD_CTL_PAD_SPEED(2) |
IOMUXC_SW_PAD_CTL_PAD_DSE(6));

IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B0_13_LPUART1_RX,
IOMUXC_SW_PAD_CTL_PAD_PKE_MASK |
IOMUXC_SW_PAD_CTL_PAD_SPEED(2) |
IOMUXC_SW_PAD_CTL_PAD_DSE(6));

有了pinctrl后,只需要修改设备树即可,就变成了这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pinmux_lpuart1: pinmux_lpuart1 {
group0 {
pinmux = <&iomuxc_gpio_ad_b0_13_lpuart1_rx>,
<&iomuxc_gpio_ad_b0_12_lpuart1_tx>;
drive-strength = "r0-6";
slew-rate = "slow";
nxp,speed = "100-mhz";
};
};
&lpuart1 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&pinmux_lpuart1>;
pinctrl-1 = <&pinmux_lpuart1_sleep>;
pinctrl-names = "default", "sleep";
};

设备树中将pinmux和引脚配置分离为不同的node,另外设备树中pinctrl和片上设备绑定要更为简便,设备更能支持多种状态,可以在运行时切换。

设备树说明分析

NXP RT1062 UART示例

参考zephyr/boards/arm/mm_feather目录中设备树文件mm_feather.dts和mm_feather-pinctrl.dtsi

mm_feather-pinctrl.dtsi中定义了引脚的复用和配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pinmux_lpuart1: pinmux_lpuart1 {
group0 {
pinmux = <&iomuxc_gpio_ad_b0_13_lpuart1_rx>,
<&iomuxc_gpio_ad_b0_12_lpuart1_tx>;
drive-strength = "r0-6";
slew-rate = "slow";
nxp,speed = "100-mhz";
};
};

pinmux_lpuart1_sleep: pinmux_lpuart1_sleep {
group0 {
pinmux = <&iomuxc_gpio_ad_b0_13_gpio1_io13>;
drive-strength = "r0-6";
bias-pull-up;
bias-pull-up-value = "100k";
slew-rate = "slow";
nxp,speed = "100-mhz";
};
group1 {
pinmux = <&iomuxc_gpio_ad_b0_12_lpuart1_tx>;
drive-strength = "r0-6";
slew-rate = "slow";
nxp,speed = "100-mhz";
};
;

上面设备树定义了两组不同的状态
pinmux_lpuart1为正常的工作状态:
URART1的RX使用GPIO_AD_B0_13, TX使用GPIO_AD_B0_12, RX/TX翻转速率(slew-rate)都为slow, PIN的反应速度(nxp,speed)为100M, 驱动能力(drive-strength)为r0-6
pinmux_lpuart1_sleep为soc休眠时的工作状态:
此时TX配置维持不变,RX变为GPIO,使用100K上拉(bias-pull-up/bias-pull-up-value),为的是在休眠期间可以被RX中断唤醒。
上面pinmux_lpuart1中的rx和tx拥有相同的pin配置可以放到同一个group中,这样可以减少设备树的长度。而pinmux_lpuart1_sleep中rx和tx的pin配置不一样,需要分为两个group配置
mm_feather.dts中将pinctrl的节点和lpuart1绑定

1
2
3
4
5
6
7
&lpuart1 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&pinmux_lpuart1>;
pinctrl-1 = <&pinmux_lpuart1_sleep>;
pinctrl-names = "default", "sleep";
};

pinctrl-0和pinctrl-1分别绑定pinmux_lpuart1和pinmux_lpuart1_sleep,在uart的设备驱动代码中通过pinctrl-names指定的default和sleep对Pinctrl节点的状态进行引用

ESP32 UART示例

参考zephyr/boards/riscv/esp32c3_devkitm中设备树文件:esp32c3_devkitm.dts和esp32c3_devkitm-pinctrl.dtsi
esp32c3_devkitm-pinctrl.dtsi中定义了引脚的复用和和配置

1
2
3
4
5
6
7
8
9
uart0_default: uart0_default {
group1 {
pinmux = <UART0_TX_GPIO21>;
};
group2 {
pinmux = <UART0_RX_GPIO20>;
bias-pull-up;
};
};

上面设备树只定义了一种状态即工作状态,这意味着串口驱动在睡眠模式下并不需要改变Pin的配置
UART0的TX口使用GPIO21, RX口使用GPIO20, RX口使用上拉
esp32c3_devkitm.dts中将pinctrl的节点和uart0绑定

1
2
3
4
5
6
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};

设备树的形式

从前面的说明可以看到设备树被拆分开来,dtsi中对引脚进行复用和配置,dts中进行设备绑定。这样做在dts中比较单一,当有硬件上的改动时,只用专注修改Pinctrl的dtsi。

参考

https://docs.zephyrproject.org/3.3.0/hardware/pinctrl/index.html