硬體測驗平臺:正點原子潘多拉STM32L4開發板
內核版本:4.0.0
注意:下面的示例代碼是從原子提供的例程中摘錄,因此可能與最新的RT-Thread原始碼有出入(因為RT-Thread原始碼在不斷的開發維護中)
首先看main.c,可見main函式主要實作了LED閃爍,以及列印LED狀態的功能
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
/* using RED LED in RGB */
#define LED_PIN PIN_LED_R
int 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在硬體驅動層的drv_gpio.h中定義了
#define PIN_LED_R 38 // PE7 : LED_R --> LED
剖析順序從上到下,從應用層深入到驅動層,(pin驅動相關的源檔案主要包括drv_gpio.c 、pin.c、 device.c)
代碼框架如下圖:

介面層的pin.c往上對接用戶,往下對接底層驅動,
對于不同芯片,用戶層的介面是統一的,而對于驅動層來說,只需要對接好相應的回呼函式,
通過統一的介面,應用開發不需要知道底層驅動,減少重復造輪子的時間,
按照點燈裸機的編程思路,先是開啟GPIO時鐘,然后初始化控制LED的GPIO為輸出,最后寫GPIO輸出高或低電平,main函式中先是rt_pin_mode函式,從字面上看也知道這是設定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);
}
結構體_hw_pin 定義在pin.c中
static struct rt_device_pin _hw_pin;
追蹤struct rt_device_pin 這個型別
/* 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_OPS
const 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;
#endif
void *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,是介面層與硬體驅動層的媒介,
從struct rt_pin_ops型別可以看到里面是六個函式指標分別對應設定pin模式,寫pin,讀pin,以及三個與中斷有關的,
那問題是,在哪里把ops變數初始化了,也就是把pin介面層和底層連接起來呢?
在初始化階段里面實作了
$ Sub $ $main(void) -> rtthread_startup() -> rt_hw_board_init() -> 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;
}
從上面的代碼可以看出,底層驅動只要實作_stm32_pin_ops 里的6個介面函式即可,
點燈主要關注stm32_pin_mode和stm32_pin_write這兩個函式:(這都是在驅動層也就是drv_gpio.c中)
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);//呼叫HAL庫函式控制GPIO輸出高低電平
}
關于stm32_pin_mode函式有一個問題,我們知道GPIO_InitStruct需要初始化四個成員變數分別是選擇pin,選擇GPIO模式,選擇是否加上下拉,選擇GPIO速度,上述代碼從對上層的通用性考慮(不是每個芯片都可以控制速率等),只往上提供了mode,而速度固定在了GPIO_SPEED_FREQ_HIGH,上拉下拉則根據mode固定變化,如果有特殊需求對某個GPIO要做一些特殊配置,比如要降低某個GPIO的速率以降低功耗,這就得另外去改了,
關于stm32_pin_write()函式單獨拉出來看一下:
#define LED_PIN PIN_LED_R
#define PIN_LED_R 38 // PE7 : LED_R --> LED
#define PIN_LOW 0x00
#define PIN_HIGH 0x01
rt_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函式
}
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);//呼叫HAL庫函式控制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)這里用了強制轉換是防止上層傳下來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 \
}
呼叫rt_pin_write時第一個引數傳入38實際上就在struct pin_index pins[] 這里面索引到__STM32_PIN(38, E, 7),
查看原理圖發現,這個表對應了芯片的引腳序號,比如38腳就是PE7,也就是我們要用的紅色LED控制腳

rt_pin_write(38, 1)實際到了底層就是HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7, (GPIO_PinState)1);
//(GPIO_PinState)這里用了強制轉換是防止上層傳下來0或1會編譯報警 細節到位
還剩一個問題,在哪里使能了該GPIO時鐘?
在stm32_pin_mode函式里面使能了對應的GPIO時鐘,
/* GPIO Periph clock enable */
index->rcc();
rcc是一個函式指標,實際上是執行了 GPIOE_CLK_ENABLE();
全部分析完畢,寫完這篇文章大概用時兩個小時,
嘮一句:
RT-Thread的代碼還是不錯的,初學者可能會對這種分層思想有點懵,但是實際專案中,這種思想一定要運用起來,只有真正的去解耦合,把應用層和驅動層盡可能分開,才能把應用層的代碼做到方便在不同平臺移植,這可能把這種工程代碼移植一下平臺,再對比一下不分層,應用和驅動相互交錯的工程移植一下,就明白到底這種編程思想強在哪里了,重復地造輪子只會讓你越來越累,降低作業效率,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/254820.html
標籤:其他
下一篇:潘多拉開發板SFUD初始化失敗問題[SFUD] Error W25Q128 flash device is initialize fail
