rt-thread 驱动篇(七)GPIO驱动

描述

前言

一提 GPIO 可能会让很多人觉得不屑,这么简单的东西有什么可说的,也就是一个拉低拉高,谁不会呢。

今天我们不讲推挽开漏、不提上拉下拉。大家来头脑风暴一下 GPIO 相关的几个问题。笔者说的不一定对,仅代表个人的一点儿小想法。

提出两个问题

有两个问题,需要考虑一下:
1. pin 设备驱动框架存在的必要性。
2. gpio 底层驱动使用官方提供的 api 还是直接寄存器操作?

在单片机里,pin 引脚操作往往很简单,写一个寄存器就成。但是在一个操作系统里,为了方便移植、便于阅读,对 pin 进行封装,在不同芯片上使用同一套 api 也还是有必要的。而这也正是一个操作系统的职责之一。

第二个问题,留着大家自己想吧。

pin 驱动框架

我们大家都知道,在我们使用 pin 的时候,没有谁先用 `rt_device_find` 查找 pin 设备,然后使用用 `rt_device_open` `rt_device_read` `rt_device_write` 去控制芯片引脚。而都是直接调用的 `rt_pin_xxx` c 函数簇。

pin 驱动框架,先把所有的 gpio 看作一个设备进行注册,然后提供了三个 `rt_pin_mode` `rt_pin_read` `rt_pin_write` c 函数,而不是 `rt_device_xxx` api 去访问某个 pin。
`rt_pin_mode` `rt_pin_read` `rt_pin_write` 这一套函数,可以不用考虑当前使用的是什么芯片,不用考虑芯片厂商提供的外设驱动库 api 是怎么写的。但是,真的是这样吗?

可以把驱动框架删掉,`rt_pin_xxx` 函数直接对接底层驱动吗?

当笔者阅读模拟 iic 驱动源码时,看到在控制 SCL SDA 高低电平切换时使用的 `rt_pin_write` 操作,一时间脑子一阵晕眩。为什么?我们先捋顺一下拉低 SDA 的函数调用过程。

以 `SDA_L` 为例
1. `SDA_L` 宏是 `ops->set_sda(ops->data, val)` 通过操作符指针调用底层接口,
2. `set_sda` 调用 `rt_pin_write`
3. `rt_pin_write` 通过 pin 设备执行 `_hw_pin.ops->pin_write` 调用的 pin 底层接口
4. 在 stm32 平台上 `_hw_pin.ops->pin_write` 等于调用 `stm32_pin_write` 函数
5. `stm32_pin_write` 调用 `HAL_GPIO_WritePin`
6. `HAL_GPIO_WritePin` 函数写寄存器。

弯弯绕绕,想控制 pin 引脚电平变化还真是煞费苦心了。

可以压缩上述调用过程吗?
`SDA_L` 宏直接定义调用 `stm32_pin_write` ,`stm32_pin_write` 内部直接操作寄存器。

soft iic 驱动

软件模拟 iic 驱动需要软件代码控制 SCL SDA 两根线时序,如前所述,拉低 SDA 线的过程被繁冗化了。

裸机能达到的 iic 时钟速度,在使用 rt-thread 的模拟 iic 时根本达不到,在多级指针和函数调用过程中,效率被极大降低了。

有没有一种策略,使 `SDA_L` 宏直接定义成 `stm32_pin_write` 或者 `gd32_pin_write` 等等。

笔者尝试把 i2c-bit-ops.c 文件和 drv_soft_i2c.c 两个文件进行合并,省掉了一级 `struct rt_i2c_bit_ops`,然后 `SDA_L` 也不使用 `rt_pin_write` 又跳过了多次指针调用。目前感觉良好。

GET_PIN

有哪位能告诉大家, `rt_pin_write(17, PIN_HIGH)` 这句代码有明确的语义吗?

函数调用中的第一个参数值 “17” 表示了什么?
可能啥也不代表。
首先,它肯定不是芯片引脚编号。
大多数芯片,GPIO 编码采用的类似如下方式:
- 以端口编码,一颗芯片上的 GPIO 可以分成若干个端口,用字母 A B C ... 命名(也有 1 2 3 编号命名的,比如 RA6M4)。我们称之为 PA PB PC ...
- 每个端口有8/16个 io 。分别编码 0-7 或者 0-15。有些芯片上的某个端口只有 15 个 io ,那就只有 0-14 有效。我们称之为 PA0 PA15

为了不使用魔数 “17” ,这种模棱两可,含义不明的写法,rt-thread 针对每种芯片要求定义一个 `GET_PIN` 宏,它可以从一种直观的引脚编号写法中返回一个数字。比如 `GET_PIN(G, 1)` 的结果是 97。

使用 `GET_PIN` `rt_pin_write(17, PIN_HIGH)` 可以写成  `rt_pin_write(GET_PIN(B, 1), PIN_HIGH)` ,这样是不是更直观了?

但是,有一种情况,不允许我们用 `GET_PIN` 。那就是在 menuconfig 或者 RT-Studio 的 Settings 里配置模拟 iic 两个引脚号的时候。它只支持输入数字,这个时候我们必须知道 `GET_PIN` 的数学含义,心算把 `GET_PIN(B, 1)` 转成 17 。

`GET_PIN` 的数学含义是确定的吗?是放之四海而皆准的吗?在每一款芯片上可以使用同一个数学公式演算吗?

**这个可以是,但实际却不是**。

另类的 AB32 RA6M4 N32

我们仍然以 17 这个编号为例,下面来看看 AB32 RA6M4 上面它分别代表哪个 GPIO 。

AB32 上应该是 PE4。
AB32 版 `GET_PIN` 是这样的:

#define __AB32_PORT(port)   GPIO##port
#define __AB32_GET_PIN_A(PIN)  PIN
#define __AB32_GET_PIN_B(PIN)  8 + PIN
#define __AB32_GET_PIN_E(PIN)  13 + PIN
#define __AB32_GET_PIN_F(PIN)  21 + PIN

几个端口不通用,各自为战

RA6M4 上不存在。因为 RA6M4 的 P100 对应的是 256 ;P015 对应的是 15 。没有 17 这个编号。
AB32 版 `GET_PIN` 未实现。

还有 N32,上面笔者说了句“它肯定不是芯片引脚编号”。但是,我又发现在 N32 的drv_gpio.c 中,定义成了芯片引脚号。打脸了...
N32 版 `GET_PIN` 也未实现。

还有其它芯片是上述三种情况之外的吗?欢迎大家讲出来。

明确的应用层语义

不失一般性,假设可能存在某芯片端口编号不是连续的,中间缺失端口B。同时端口 A 也只有 12 个 io。我们把所有的端口和 io 进行排序编号。PA0 是 0 号、PA1 是 1 号 ... PA11 是 11 号。那么,PC0 编号是多少?12吗?

假如有一同系列芯片,它是有端口 B 的。那么 PB0 编号该定义成多少合适呢?也是 12 吗?

> 或者,干脆我们就假定所有的芯片端口都是连续无缺失的,每个端口也是满满当当 16 个 io 。这样 PB0 总是 16,PC0 总是 32。

从理论上讲,所有的芯片 gpio 编号系统是可以用一个公式实现的,这个公式可以在 rt-thread 使用宝典(2022-0516更新)中找到。

RA6M4 上,应用程序层可以使用 17 表示 P101,因为它的端口编号从 0 开始;
N32 上,应用程序层可以使用 17 表示 PB01,因为它的端口编号从 A 开始;
STM32 上,应用程序层可以使用 17 表示 PB01,因为它的端口编号从 A 开始;
AB32 上,应用程序层可以使用 17 表示 PB01,因为它的端口编号从 A 开始

无论用的哪家芯片,无论是哪个系列芯片,无论是哪款型号,它有多少引脚。我们希望 17 这个值能对应一个明确的引脚名。不会因为某系列芯片中某子型号因为其中某个端口 io 数量少一个导致后面所有 io 的编号都变了。又或者同样的 PB01 在不同子型号不同封装下的编号也不一样。

结束语

大家有什么意见和想法,一块儿聊聊啊。

> 把不同芯片的差异性进行封装,提供给应用层语义明确的接口,是一个操作系统的职责之一。
  复杂事情简单化,简单的事情保留那一点儿纯粹。这也是封装的基本原则。
  写应用程序代码时,我们不想关心底层的实现,这是另一个操作系统的职责之一。

审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
评论(0)
发评论
jf_85813864 2023-07-24
0 回复 举报
你看用 0x11 是不是比 17 更直观了?高4位:A-->0, B-->1, C-->2...低4位:0~15-->0~F比如:PC15-->0x2F 收起回复
LZJ314825348 2022-06-28
0 回复 举报
我觉得是不是要加一个 pin_close 的函数,进入低功耗时需要对引脚的模式进行复位或者配置,加一个 close 对相应引脚的寄存器配置进行复位 1条回复 收起回复
jf_59205847 0
GPIO 关 clk ,关时钟树就好了吧。但是,不是每一个 pin 脚可以单独关时钟的,都是按组开关的,把这个加到pin里面可能出现重复操作。

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分