前言
一提 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文章
1213瀏覽量
52210 -
PIN
+關注
關注
1文章
305瀏覽量
24351 -
RT-Thread
+關注
關注
31文章
1299瀏覽量
40263 -
驅動框架
+關注
關注
0文章
14瀏覽量
4069
發布評論請先 登錄
相關推薦
評論