日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

RT-Thread pin设备驱动代码结构剖析

發(fā)布時(shí)間:2024/1/23 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 RT-Thread pin设备驱动代码结构剖析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

硬件測試平臺:正點(diǎn)原子潘多拉STM32L4開發(fā)板
OS內(nèi)核版本:4.0.0

注意:下面的示例代碼是從原子提供的例程中摘錄,因此可能與最新的RT-Thread源碼有出入(因?yàn)镽T-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ù)主要實(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ù)造輪子的時(shí)間。

按照點(diǎn)燈裸機(jī)的編程思路,先是開啟GPIO時(shí)鐘,然后初始化控制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 這個(gè)結(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 這個(gè)類型中的成員ops,是接口層與硬件驅(qū)動層的媒介。
從struct rt_pin_ops類型可以看到里面是六個(gè)函數(shù)指針分別對應(yīng)設(shè)置pin模式,寫pin,讀pin,以及三個(gè)與中斷有關(guān)的。

那問題是,在哪里把ops變量初始化了,也就是把pin接口層和底層連接起來呢?
答案是在初始化階段里面實(shí)現(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() 開始看:

//這是drv_gpio.c的 const static struct rt_pin_ops _stm32_pin_ops = {stm32_pin_mode,stm32_pin_write,stm32_pin_read,stm32_pin_attach_irq,stm32_pin_dettach_irq,stm32_pin_irq_enable, }; int rt_hw_pin_init(void) {return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL); }//這是pin.c的 int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data) {_hw_pin.parent.type = RT_Device_Class_Miscellaneous;_hw_pin.parent.rx_indicate = RT_NULL;_hw_pin.parent.tx_complete = RT_NULL;#ifdef RT_USING_DEVICE_OPS_hw_pin.parent.ops = &pin_ops; #else_hw_pin.parent.init = RT_NULL;_hw_pin.parent.open = RT_NULL;_hw_pin.parent.close = RT_NULL;_hw_pin.parent.read = _pin_read;_hw_pin.parent.write = _pin_write;_hw_pin.parent.control = _pin_control; #endif_hw_pin.ops = ops; //這里把_stm32_pin_ops和 _hw_pin.ops 連接起來了_hw_pin.parent.user_data = user_data;/* register a character device */rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR);return 0; }

從上面的代碼可以看出,底層驅(qū)動只要實(shí)現(xiàn)_stm32_pin_ops 里的6個(gè)接口函數(shù)即可。

至此我們已初步理清了RT-Thread Pin設(shè)備驅(qū)動的框架關(guān)系了:

如上圖,我們在應(yīng)用層需要學(xué)會rt_pin_mode等6個(gè)API接口的使用,在底層需要實(shí)現(xiàn)GPIO驅(qū)動的回調(diào)函數(shù)。
從上圖可以直觀地看到 Pin 設(shè)備驅(qū)動框架和 GPIO 驅(qū)動是如果對接到一起的。
如上圖所示:

  • 左側(cè)為 Pin 設(shè)備驅(qū)動框架封裝的 API 接口,向上提供給應(yīng)用層使用;
  • 右側(cè)為具體芯片平臺對接 Pin 設(shè)備的驅(qū)動程序,與 Pin 設(shè)備框架提供的 API 接口一一對應(yīng),通過 rt_pin_ops 結(jié)構(gòu)體關(guān)聯(lián)到一起,驅(qū)動框架層接口最終會回調(diào)驅(qū)動層接口;
  • 最后通過 rt_device_pin_register 函數(shù)接口,將底層驅(qū)動與 Pin 設(shè)備框架綁定到一起,注冊到 RT-Thread 的設(shè)備框架中。
  • 在對接 RT-Thread Pin 設(shè)備框架的時(shí)候,僅需要實(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)用。

    點(diǎn)燈主要關(guān)注stm32_pin_mode和stm32_pin_write這兩個(gè)函數(shù)即可實(shí)現(xiàn):(兩個(gè)函數(shù)都在驅(qū)動層也就是drv_gpio.c中實(shí)現(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ù)有一個(gè)問題,我們知道GPIO_InitStruct需要初始化四個(gè)成員變量分別是選擇pin,選擇GPIO模式,選擇是否加上下拉,選擇GPIO速度,上述代碼從對上層的通用性考慮(不是每個(gè)芯片都可以控制速率等),只往上提供了mode,而速度固定在了GPIO_SPEED_FREQ_HIGH,上拉下拉則根據(jù)mode固定變化。如果有特殊需求對某個(gè)GPIO要做一些特殊配置,比如要降低某個(gè)GPIO的速率以降低功耗,這就得另外去改了。

    下面關(guān)于stm32_pin_write()函數(shù)單獨(dú)拉出來對相關(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實(shí)際上就是指向了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)這里用了強(qiáng)制轉(zhuǎn)換是防止上層傳下來0或1會編譯報(bào)警 }#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; };//這里用到了##連接符 這個(gè)符號在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í)第一個(gè)參數(shù)傳入38實(shí)際上就在struct pin_index pins[] 這里面索引到__STM32_PIN(38, E, 7),
    查看原理圖發(fā)現(xiàn),這個(gè)表對應(yīng)了芯片的引腳序號,比如38腳就是PE7,也就是我們要用的紅色LED控制腳

    rt_pin_write(38, 1)實(shí)際到了底層就是HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7, (GPIO_PinState)1);
    //(GPIO_PinState)這里用了強(qiáng)制轉(zhuǎn)換是防止上層傳下來0或1會編譯報(bào)警 (細(xì)節(jié)到位)

    最后還剩一個(gè)問題,在哪里使能了該GPIO時(shí)鐘?
    在stm32_pin_mode函數(shù)里面使能了對應(yīng)的GPIO時(shí)鐘,
    /* GPIO Periph clock enable */
    index->rcc();
    rcc是一個(gè)函數(shù)指針,實(shí)際上是執(zhí)行了 GPIOE_CLK_ENABLE();
    看到struct pin_index類型第二個(gè)成員void (*rcc)(void);
    pins是struct pin_index類型的結(jié)構(gòu)體數(shù)組
    我們用到的紅色LED對應(yīng)的__STM32_PIN(38, E, 7)
    第二個(gè)宏參數(shù)E通過GPIO##gpio##_CLK_ENABLE ==》就變成了GPIOE_CLK_ENABLE
    對##和#的用法不熟悉可參考: 詳解C語言中 ##和# 的用法

    通過設(shè)備驅(qū)動框架訪問pin設(shè)備

  • RTT設(shè)備驅(qū)動框架提供了統(tǒng)一的API:
  • API說明
    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è)計(jì)思路,對設(shè)備的訪問都通過如下接口open,read,write,close等來完成操作。
    其中,pin設(shè)備的接口關(guān)系如下:

    目前我的這套代碼中_pin_control()只實(shí)現(xiàn)了GPIO的模式設(shè)置,如果要使用這組API需要自己增加相關(guān)代碼實(shí)現(xiàn)對中斷的控制:

    static rt_err_t _pin_control(rt_device_t dev, int cmd, void *args) {struct rt_device_pin_mode *mode;struct rt_device_pin *pin = (struct rt_device_pin *)dev;/* check parameters */RT_ASSERT(pin != RT_NULL);mode = (struct rt_device_pin_mode *) args;if (mode == RT_NULL) return -RT_ERROR;pin->ops->pin_mode(dev, (rt_base_t)mode->pin, (rt_base_t)mode->mode);return 0; }

    下面的示例代碼從調(diào)用設(shè)備驅(qū)動框架的接口來實(shí)現(xiàn)對LED的控制

    int main(void) {int count = 1;struct rt_device_pin *pin_dev = RT_NULL;struct rt_device_pin_mode rt_device_pin_mode_led = {LED_PIN, PIN_MODE_OUTPUT};struct rt_device_pin_status rt_device_pin_status_led = {LED_PIN, PIN_HIGH};pin_dev = (struct rt_device_pin *)rt_device_find("pin");rt_device_open((rt_device_t)pin_dev, RT_DEVICE_OFLAG_RDWR);rt_device_control(&pin_dev->parent, 0, &rt_device_pin_mode_led);while (count++){rt_device_pin_status_led.status = PIN_HIGH;rt_device_write((rt_device_t)pin_dev, 0, &rt_device_pin_status_led, sizeof(rt_device_pin_status_led));rt_thread_mdelay(1000);rt_device_pin_status_led.status = PIN_LOW;rt_device_write((rt_device_t)pin_dev, 0, &rt_device_pin_status_led, sizeof(rt_device_pin_status_led));rt_thread_mdelay(1000);}return RT_EOK; }

    最后

    嘮一句:
    RT-Thread的代碼還是不錯的,初學(xué)者可能會對這種分層思想有點(diǎn)懵,但是實(shí)際項(xiàng)目中,這種思想一定要運(yùn)用起來,只有真正的去解耦合,把應(yīng)用層和驅(qū)動層盡可能分開,才能把應(yīng)用層的代碼做到方便在不同平臺移植,這可能試著把這種工程代碼移植一下平臺,再對比一下不分層,應(yīng)用和驅(qū)動相互交錯的工程移植一下,就明白到底這種編程思想強(qiáng)在哪里了。重復(fù)地造輪子只會讓人越來越累,降低工作效率。

    總結(jié)

    以上是生活随笔為你收集整理的RT-Thread pin设备驱动代码结构剖析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。