前言
一提 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 在不同子型号不同封装下的编号也不一样。
结束语
大家有什么意见和想法,一块儿聊聊啊。
> 把不同芯片的差异性进行封装,提供给应用层语义明确的接口,是一个操作系统的职责之一。
复杂事情简单化,简单的事情保留那一点儿纯粹。这也是封装的基本原则。
写应用程序代码时,我们不想关心底层的实现,这是另一个操作系统的职责之一。
审核编辑:汤梓红
-
GPIO
+关注
关注
16文章
1196浏览量
51896 -
PIN
+关注
关注
1文章
303浏览量
24225 -
RT-Thread
+关注
关注
31文章
1271浏览量
39901 -
驱动框架
+关注
关注
0文章
14浏览量
4025
发布评论请先 登录
相关推荐
评论