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 在不同子型号不同封装下的编号也不一样。
结束语 大家有什么意见和想法,一块儿聊聊啊。
> 把不同芯片的差异性进行封装,提供给应用层语义明确的接口,是一个操作系统的职责之一。
  复杂事情简单化,简单的事情保留那一点儿纯粹。这也是封装的基本原则。
  写应用程序代码时,我们不想关心底层的实现,这是另一个操作系统的职责之一。


CAMX宣布GEMX®阴极许可证的转让
超声波发生器是干什么用的_超声波发生器怎么调试
CentOS联合创始人成立公司赞助Rocky Linux
基于TTCAN和动态晋升机制的CAN总线调度算法
国联易安:聚焦数字政府安全 防患于未然
rt-thread 驱动篇(七)GPIO驱动
基于工业物联网的中药生产过程监控和故障监测系统
水质在线电导率仪/酸碱盐浓度变送器介绍
晶圆厂为何要进攻先进封装?
从车流到数据流 | 智慧交通时代,Hisan智能激光屏推动交通大数据化发展
信号发生器主要作用是什么?安泰测试为您解答
荣耀8X评测 千元换机的最佳选择之一
怎么解决蓝牙自动断开的原因
舌簧继电器是测试测量应用开关系统解决方案的最佳选择
5G带来的机遇将是全球性的
明匠智能工厂系统介绍
2020年最受欢迎的人力资源管理系统排名
MM32L073单片机的产品特性与应用概述
CR-V锐混动车型拉开东风Honda进击新能源车市场的序幕
适合于矿用测流的污水流量计电路的设计