Zephyr GPIO逻辑电平

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

本文简要介绍zephyr GPIO的驱动logic level的原理和实现

Zephyr大约在2019年底merge了对disk_access_usdhc.c的改写,其中detect gpio使用了gpio logic level,由于mm_swiftio的dts还是沿用原来的无logic level设置,导致检测卡失败。借此契机顺便分析了一下gpio logic level的实现机制。

原理

一般情况下我们将0当做gpio的低电平,1当做gpio的高电平,这是物理世界的对应。在软件上由于逻辑实现上的可移植或是代码的易读性我们会引入logic level。例如对于A型的SD Card检测电路,被设计插卡后detect GPIO低电平, 而对于B型的SD Card检测电路,被设计插卡后detect GPIO高电平,反映到软件流程上将会对于不同的gpio level判断。
但对于插卡检测的软件来说,只希望得到的是gpio反映卡有插入或者没有插入。于是zephyr gpio引入了gpio logic level的做法,针对具体的某一个gpio可以被配置为logic level 1是对应物理的低电平或者是物理高电平,这样对于插卡检测软件来说只需要检测到逻辑电平为高就可以判断是有卡插入。

使用

在gpio.h中用下面两个宏flag表示logic 1对应的物理电平

1
2
#define GPIO_ACTIVE_LOW         (1 << 0)
#define GPIO_ACTIVE_HIGH (0 << 0)

当gpio_config(struct device *port, gpio_pin_t pin, gpio_flags_t flags);通过flags设置logic level的模式
包含GPIO_ACTIVE_LOW 时:gpio上出现低电平时表示逻辑1

gpio level gpio logic level
0 1
1 0

包含GPIO_ACTIVE_HIGH 时:gpio上出现高电平时表示逻辑1

gpio level gpio logic level
0 0
1 1

配置logic level完成后,gpio_port_get,gpio_pin_get读出的就是逻辑电平。
通过gpio_port_clear_bits,gpio_port_set_bits,gpio_port_set_clr_bits,gpio_port_set_masked写入逻辑电平。
gpio也有下面的api直接读写物理电平:
gpio_pin_get_raw,gpio_port_get_raw,gpio_pin_set_raw,gpio_port_clear_bits_raw,gpio_port_set_bits_raw,gpio_port_set_clr_bits_raw,gpio_port_set_masked_raw

对于中断level来说定义有flag

1
#define GPIO_INT_LEVELS_LOGICAL        (1U << 15)

当gpio_pin_interrupt_configure传入的flags中有GPIO_INT_LEVELS_LOGICAL,时,中断配置时会将传入的高低电平触发做为逻辑电平处理。

注:如果在gpio_config时GPIO_ACTIVE_LOW 和GPIO_ACTIVE_HIGH都没设置,将会使用GPIO_ACTIVE_HIGH,也就是物理电平和逻辑电平一致。

实现

配置时

只要flags当中带有GPIO_ACTIVE_LOW,所以低物理电平对应高逻辑电平,两者是取反的关系,关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static inline int gpio_pin_configure(struct device *port, gpio_pin_t pin,
gpio_flags_t flags)
{
struct gpio_driver_data *data =
(struct gpio_driver_data *)port->driver_data;

...
//如果低物理电平被当做高逻辑电平,将对应的invert位置1
if ((flags & GPIO_ACTIVE_LOW) != 0) {
data->invert |= (gpio_port_pins_t)BIT(pin);
} else {
data->invert &= ~(gpio_port_pins_t)BIT(pin);
}

...
}

gpio_pin_get其实就是包装gpio_port_get,这里只分析gpio_port_get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline int gpio_port_get(struct device *port, gpio_port_value_t *value)
{
const struct gpio_driver_data *const data =
(const struct gpio_driver_data *)port->driver_data;
int ret;
//读出物理电平
ret = gpio_port_get_raw(port, value);
//将物理电平和invert异或,如果invert对应bit为1,说明value对应的bit要取反
if (ret == 0) {
*value ^= data->invert;
}

return ret;
}

写逻辑电平的api都是包装gpio_port_set_masked和gpio_pin_set,只分析这两个API的代码

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
27
28
29
30
31
32
33
static inline int gpio_pin_set(struct device *port, gpio_pin_t pin, int value)
{
const struct gpio_driver_config *const cfg =
(const struct gpio_driver_config *)port->config->config_info;
const struct gpio_driver_data *const data =
(const struct gpio_driver_data *)port->driver_data;

(void)cfg;
__ASSERT((cfg->port_pin_mask & (gpio_port_pins_t)BIT(pin)) != 0U,
"Unsupported pin");

//通过invert将逻辑电平转为物理电平
if (data->invert & (gpio_port_pins_t)BIT(pin)) {
value = (value != 0) ? 0 : 1;
}

//写入物理电平
return gpio_pin_set_raw(port, pin, value);
}


static inline int gpio_port_set_masked(struct device *port,
gpio_port_pins_t mask, gpio_port_value_t value)
{
const struct gpio_driver_data *const data =
(const struct gpio_driver_data *)port->driver_data;

//通过invert将逻辑电平转为物理电平
value ^= data->invert;

//写入物理电平
return gpio_port_set_masked_raw(port, mask, value);
}

中断配置

只摘取逻辑电平相关的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static inline int z_impl_gpio_pin_interrupt_configure(struct device *port,
gpio_pin_t pin,
gpio_flags_t flags)
{
//如果中断配置时,按逻辑电平相应,将电平触发中断level从逻辑电平转换成物理电平
if (((flags & GPIO_INT_LEVELS_LOGICAL) != 0) &&
((data->invert & (gpio_port_pins_t)BIT(pin)) != 0)) {
/* Invert signal bits */
flags ^= (GPIO_INT_LOW_0 | GPIO_INT_HIGH_1);
}

//设置触发中断的物理电平
trig = (enum gpio_int_trig)(flags & (GPIO_INT_LOW_0 | GPIO_INT_HIGH_1));
return api->pin_interrupt_configure(port, pin, mode, trig);
}