RT-Thread pin设备驱动代码结构剖析
硬件測試平臺:正點原子潘多拉STM32L4開發(fā)板
OS內(nèi)核版本:4.0.0
注意:下面的示例代碼是從原子提供的例程中摘錄,因此可能與最新的RT-Thread源碼有出入(因為RT-Thread源碼在不斷的開發(fā)維護(hù)中)
下面摘錄的例程中,關(guān)鍵位置我給出了注釋
本文涉及內(nèi)容對初學(xué)者可能較為晦澀難懂,如只是想先簡單學(xué)習(xí)Pin設(shè)備驅(qū)動的API使用可以參考:RT-Thread Pin設(shè)備驅(qū)動API應(yīng)用介紹
首先看main.c,可見main函數(shù)主要實現(xiàn)了LED閃爍,以及打印LED狀態(tài)的功能
#include <rtthread.h> #include <rtdevice.h> #include <board.h>/* using RED LED in RGB */ #define LED_PIN PIN_LED_Rint main(void) {unsigned int count = 1;/* set LED pin mode to output */rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);while (count > 0){/* led on */rt_pin_write(LED_PIN, PIN_LOW);rt_kprintf("led on, count: %d\n", count);rt_thread_mdelay(500);/* led off */rt_pin_write(LED_PIN, PIN_HIGH);rt_kprintf("led off\n");rt_thread_mdelay(500);count++;}return 0; }PIN_LED_R在硬件驅(qū)動層的drv_gpio.h中定義了
#define PIN_LED_R 38 // PE7 : LED_R --> LED剖析順序從上到下,從應(yīng)用層深入到驅(qū)動層。(pin驅(qū)動相關(guān)的源文件主要包括drv_gpio.c 、pin.c、 device.c)
代碼框架如下圖:
接口層的pin.c往上對接用戶,往下對接底層驅(qū)動。
對于不同芯片,用戶層的接口是統(tǒng)一的,而對于驅(qū)動層來說,只需要對接好相應(yīng)的回調(diào)函數(shù)。
通過統(tǒng)一的接口,應(yīng)用開發(fā)不需要知道底層驅(qū)動,減少重復(fù)造輪子的時間。
按照點燈裸機的編程思路,先是開啟GPIO時鐘,然后初始化控制LED的GPIO為輸出,最后寫GPIO輸出高或低電平。
main函數(shù)中先是rt_pin_mode函數(shù),從字面上看也知道這是設(shè)置pin工作模式。下面追蹤代碼:
/* RT-Thread Hardware PIN APIs */ void rt_pin_mode(rt_base_t pin, rt_base_t mode) {RT_ASSERT(_hw_pin.ops != RT_NULL); //斷言 檢查_hw_pin.ops不為空_hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode); //調(diào)用底層回調(diào)函數(shù) }結(jié)構(gòu)體_hw_pin 定義在pin.c中
static struct rt_device_pin _hw_pin;進(jìn)一步追蹤struct rt_device_pin 這個結(jié)構(gòu)體類型
/* pin device and operations for RT-Thread */ struct rt_device_pin {struct rt_device parent;const struct rt_pin_ops *ops; };//在rtdef.h /*** Device structure*/ struct rt_device {struct rt_object parent; /**< inherit from rt_object */enum rt_device_class_type type; /**< device type */rt_uint16_t flag; /**< device flag */rt_uint16_t open_flag; /**< device open flag */rt_uint8_t ref_count; /**< reference count */rt_uint8_t device_id; /**< 0 - 255 *//* device call back */rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);#ifdef RT_USING_DEVICE_OPSconst struct rt_device_ops *ops; #else/* common device interface */rt_err_t (*init) (rt_device_t dev);rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);rt_err_t (*close) (rt_device_t dev);rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);rt_err_t (*control)(rt_device_t dev, int cmd, void *args); #endif#if defined(RT_USING_POSIX)const struct dfs_file_ops *fops;struct rt_wqueue wait_queue; #endifvoid *user_data; /**< device private data */ };//在pin.h struct rt_pin_ops {void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);int (*pin_read)(struct rt_device *device, rt_base_t pin);/* TODO: add GPIO interrupt */rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_int32_t pin,rt_uint32_t mode, void (*hdr)(void *args), void *args);rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_int32_t pin);rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled); };struct rt_device_pin 這個類型中的成員ops,是接口層與硬件驅(qū)動層的媒介。
從struct rt_pin_ops類型可以看到里面是六個函數(shù)指針分別對應(yīng)設(shè)置pin模式,寫pin,讀pin,以及三個與中斷有關(guān)的。
那問題是,在哪里把ops變量初始化了,也就是把pin接口層和底層連接起來呢?
答案是在初始化階段里面實現(xiàn)了,關(guān)于RT-Thread的初始化流程可查看:RT-Thread啟動流程
初始化流程中的調(diào)用關(guān)系如下:
$ Sub $ $main(void) =》 rtthread_startup() =》 rt_hw_board_init() =》 rt_hw_pin_init()
(對 $ Sub $ $不明白的可以參考:關(guān)于 $ Super $ $ 和 $ Sub $ $ 的用法)
前面的函數(shù)就不放出來了,直接從rt_hw_pin_init() 開始看:
從上面的代碼可以看出,底層驅(qū)動只要實現(xiàn)_stm32_pin_ops 里的6個接口函數(shù)即可。
至此我們已初步理清了RT-Thread Pin設(shè)備驅(qū)動的框架關(guān)系了:
如上圖,我們在應(yīng)用層需要學(xué)會rt_pin_mode等6個API接口的使用,在底層需要實現(xiàn)GPIO驅(qū)動的回調(diào)函數(shù)。
從上圖可以直觀地看到 Pin 設(shè)備驅(qū)動框架和 GPIO 驅(qū)動是如果對接到一起的。
如上圖所示:
在對接 RT-Thread Pin 設(shè)備框架的時候,僅需要實現(xiàn)上圖右側(cè) rt_pin_ops 結(jié)構(gòu)體中的所有 callback 回調(diào)函數(shù)即可,然后通過 rt_device_pin_register 注冊到系統(tǒng),最后使用 rt_hw_pin_init 調(diào)用以進(jìn)行初始化。
rt_hw_pin_init 在系統(tǒng)啟動過程中被 rt_hw_board_init 調(diào)用。
點燈主要關(guān)注stm32_pin_mode和stm32_pin_write這兩個函數(shù)即可實現(xiàn):(兩個函數(shù)都在驅(qū)動層也就是drv_gpio.c中實現(xiàn))
static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode) {const struct pin_index *index;GPIO_InitTypeDef GPIO_InitStruct;index = get_pin(pin);if (index == RT_NULL){return;}/* GPIO Periph clock enable */index->rcc();/* Configure GPIO_InitStructure */GPIO_InitStruct.Pin = index->pin;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;if (mode == PIN_MODE_OUTPUT){/* output setting */GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;}else if (mode == PIN_MODE_INPUT){/* input setting: not pull. */GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;}else if (mode == PIN_MODE_INPUT_PULLUP){/* input setting: pull up. */GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP;}else if (mode == PIN_MODE_INPUT_PULLDOWN){/* input setting: pull down. */GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLDOWN;}else if (mode == PIN_MODE_OUTPUT_OD){/* output setting: od. */GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;GPIO_InitStruct.Pull = GPIO_NOPULL;}HAL_GPIO_Init(index->gpio, &GPIO_InitStruct); }static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value) {const struct pin_index *index;index = get_pin(pin);if (index == RT_NULL){return;}HAL_GPIO_WritePin(index->gpio, index->pin, (GPIO_PinState)value);//調(diào)用HAL庫函數(shù)控制GPIO輸出高低電平 }關(guān)于stm32_pin_mode函數(shù)有一個問題,我們知道GPIO_InitStruct需要初始化四個成員變量分別是選擇pin,選擇GPIO模式,選擇是否加上下拉,選擇GPIO速度,上述代碼從對上層的通用性考慮(不是每個芯片都可以控制速率等),只往上提供了mode,而速度固定在了GPIO_SPEED_FREQ_HIGH,上拉下拉則根據(jù)mode固定變化。如果有特殊需求對某個GPIO要做一些特殊配置,比如要降低某個GPIO的速率以降低功耗,這就得另外去改了。
下面關(guān)于stm32_pin_write()函數(shù)單獨拉出來對相關(guān)代碼分析一下:
#define LED_PIN PIN_LED_R #define PIN_LED_R 38 // PE7 : LED_R --> LED#define PIN_LOW 0x00 #define PIN_HIGH 0x01rt_pin_write(LED_PIN, PIN_LOW);void rt_pin_write(rt_base_t pin, rt_base_t value) {RT_ASSERT(_hw_pin.ops != RT_NULL);_hw_pin.ops->pin_write(&_hw_pin.parent, pin, value); //以上分析我們知道,pin_write實際上就是指向了stm32_pin_write函數(shù) }static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value) {const struct pin_index *index;index = get_pin(pin);if (index == RT_NULL){return;}HAL_GPIO_WritePin(index->gpio, index->pin, (GPIO_PinState)value);//調(diào)用HAL庫函數(shù)控制GPIO輸出高低電平 }static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value) {const struct pin_index *index;index = get_pin(pin);if (index == RT_NULL){return;}HAL_GPIO_WritePin(index->gpio, index->pin, (GPIO_PinState)value);//(GPIO_PinState)這里用了強制轉(zhuǎn)換是防止上層傳下來0或1會編譯報警 }#define ITEM_NUM(items) sizeof(items) / sizeof(items[0]) static const struct pin_index *get_pin(uint8_t pin) {const struct pin_index *index;if (pin < ITEM_NUM(pins)){index = &pins[pin];if (index->index == -1)index = RT_NULL;}else{index = RT_NULL;}return index; };static const struct pin_index pins[] = {__STM32_PIN_DEFAULT,__STM32_PIN(1, E, 2), // PE2 : SAI1_MCLK_A --> ES8388__STM32_PIN(2, E, 3), // PE3 : SAI1_SD_B --> ES8388...//省略__STM32_PIN(38, E, 7), // PE7 : LED_R --> LED //這是我們要用到的紅色LED腳...//省略__STM32_PIN(98, E, 1), // PE1 : IO_PE1 --> EXTERNAL MODULE__STM32_PIN_DEFAULT, // : VSS__STM32_PIN_DEFAULT, // : VDD };/* STM32 GPIO driver */ struct pin_index {int index;void (*rcc)(void);GPIO_TypeDef *gpio;uint32_t pin; };//這里用到了##連接符 這個符號在RT-Thread里用得很多 #define __STM32_PIN(index, gpio, gpio_index) \{ \index, GPIO##gpio##_CLK_ENABLE, GPIO##gpio, GPIO_PIN_##gpio_index \}調(diào)用rt_pin_write時第一個參數(shù)傳入38實際上就在struct pin_index pins[] 這里面索引到__STM32_PIN(38, E, 7),
查看原理圖發(fā)現(xiàn),這個表對應(yīng)了芯片的引腳序號,比如38腳就是PE7,也就是我們要用的紅色LED控制腳
rt_pin_write(38, 1)實際到了底層就是HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7, (GPIO_PinState)1);
//(GPIO_PinState)這里用了強制轉(zhuǎn)換是防止上層傳下來0或1會編譯報警 (細(xì)節(jié)到位)
最后還剩一個問題,在哪里使能了該GPIO時鐘?
在stm32_pin_mode函數(shù)里面使能了對應(yīng)的GPIO時鐘,
/* GPIO Periph clock enable */
index->rcc();
rcc是一個函數(shù)指針,實際上是執(zhí)行了 GPIOE_CLK_ENABLE();
看到struct pin_index類型第二個成員void (*rcc)(void);
pins是struct pin_index類型的結(jié)構(gòu)體數(shù)組
我們用到的紅色LED對應(yīng)的__STM32_PIN(38, E, 7)
第二個宏參數(shù)E通過GPIO##gpio##_CLK_ENABLE ==》就變成了GPIOE_CLK_ENABLE
對##和#的用法不熟悉可參考: 詳解C語言中 ##和# 的用法
通過設(shè)備驅(qū)動框架訪問pin設(shè)備
| rt_err_t rt_device_init (rt_device_t dev) | 設(shè)備初始化 |
| rt_err_t rt_device_open (rt_device_t dev, rt_uint16_t oflag) | 打開設(shè)備 |
| rt_err_t rt_device_close(rt_device_t dev) | 關(guān)閉設(shè)備 |
| rt_size_t rt_device_read (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) | 讀設(shè)備 |
| rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) | 寫設(shè)備 |
| rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg) | 控制設(shè)備 |
RTT提供的設(shè)備驅(qū)動API使用了類似Linux的設(shè)計思路,對設(shè)備的訪問都通過如下接口open,read,write,close等來完成操作。
其中,pin設(shè)備的接口關(guān)系如下:
目前我的這套代碼中_pin_control()只實現(xiàn)了GPIO的模式設(shè)置,如果要使用這組API需要自己增加相關(guān)代碼實現(xiàn)對中斷的控制:
下面的示例代碼從調(diào)用設(shè)備驅(qū)動框架的接口來實現(xiàn)對LED的控制:
最后
嘮一句:
RT-Thread的代碼還是不錯的,初學(xué)者可能會對這種分層思想有點懵,但是實際項目中,這種思想一定要運用起來,只有真正的去解耦合,把應(yīng)用層和驅(qū)動層盡可能分開,才能把應(yīng)用層的代碼做到方便在不同平臺移植,這可能試著把這種工程代碼移植一下平臺,再對比一下不分層,應(yīng)用和驅(qū)動相互交錯的工程移植一下,就明白到底這種編程思想強在哪里了。重復(fù)地造輪子只會讓人越來越累,降低工作效率。
總結(jié)
以上是生活随笔為你收集整理的RT-Thread pin设备驱动代码结构剖析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RT-Thread工程代码框架分析——(
- 下一篇: 详解C语言中 # 和 ## 的用法