RT-Thread在STM32F4上BSP移植(一)——GPIO的使用

本文介绍STM32 GPIO配置方法,包括宏定义解析、驱动层实现及应用层使用,并详细阐述了RT-Thread操作系统如何与STM32 GPIO集成。

一、关于GPIO的定义
1、用户定义使用
2、驱动层如何定义GET_PIN
(1)简短拆分一下宏定义
(2)GPIOx_BASE宏定义
(3)所以可以计算出
3、应用层如何使用PIN
(1)PIN函数的使用
(2)PIN函数的声明
二、驱动层如何实现PIN相关的操作
1、PIN函数的实现
(1)驱动层的函数接口定义
2、驱动层注册PIN设备
(1)注册的声明实现
(2)注册pin设备
3、驱动层实现PIN操作函数
(1)调用HAL库实现
(2)引脚的描述差异
(3)rt-thread项目和bsp项目的差异

一、关于GPIO的定义

1、用户通过宏定义使用

#define LED0_PIN    GET_PIN(C, 13)
rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);

2、驱动层如何定义GET_PIN

#define __STM32_PORT(port)  GPIO##port##_BASE

#if defined(SOC_SERIES_STM32MP1)
#define GET_PIN(PORTx,PIN) (GPIO##PORTx == GPIOZ) ? (176 + PIN) : ((rt_base_t)((16 * ( ((rt_base_t)__STM32_PORT(PORTx) - (rt_base_t)GPIOA_BASE)/(0x1000UL) )) + PIN))
#else
#define GET_PIN(PORTx,PIN) (rt_base_t)((16 * ( ((rt_base_t)__STM32_PORT(PORTx) - (rt_base_t)GPIOA_BASE)/(0x0400UL) )) + PIN)
#endif

(1)简短拆分一下宏定义

#define GET_PIN(PORTx,PIN)

(rt_base_t) ( __1__ + PIN )

(16 * ( __2__ /(0x0400UL) ))

(
    (rt_base_t)__STM32_PORT(PORTx)
    -
    (rt_base_t)GPIOA_BASE
)

(2)GPIOx_BASE宏定义

include芯片类别头文件
// board/board.h
#include <stm32f4xx.h>
在编译脚本中定义芯片相关的宏开关
// board/SConscript
CPPDEFINES = ['STM32F401xC']
group = DefineGroup('Drivers', src, depend = ['']
    , CPPPATH = path, CPPDEFINES = CPPDEFINES)
// 自动构建脚本在cmake中会生成如下
ADD_DEFINITIONS(-DSTM32F401xC)
根据芯片相关的宏定义include相关的头文件
// libraries/STM32F4xx_HAL/CMSIS/Device/ST/STM32F4xx/Include/
//     stm32f4xx.h
#if defined(STM32F405xx)
    #include "stm32f405xx.h"
// ...
#elif defined(STM32F401xC)
    #include "stm32f401xc.h"
// ...
#else
 #error "Please select first the target
     STM32F4xx device used in your application (in stm32f4xx.h file)"
#endif
在具体的芯片定义头文件中定义具体的引脚信息
// libraries/STM32F4xx_HAL/CMSIS/Device/ST/STM32F4xx/Include/
//     stm32f401xc.h
#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000UL)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400UL)
#define GPIOC_BASE            (AHB1PERIPH_BASE + 0x0800UL)
#define GPIOD_BASE            (AHB1PERIPH_BASE + 0x0C00UL)
#define GPIOE_BASE            (AHB1PERIPH_BASE + 0x1000UL)
#define GPIOH_BASE            (AHB1PERIPH_BASE + 0x1C00UL)

(3)所以可以计算出

#define GET_PIN(PORTx,PIN) <-- ([PORTx]{0,1,2,3,4,5}) * 16 + PIN

3、应用层如何使用PIN

(1)PIN函数的使用

rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);
rt_pin_write(LED0_PIN, PIN_HIGH);

(2)PIN函数的声明

使用时引入rtdevice.h
// rt-thread/components/drivers/include/rtdevice.h
#ifdef RT_USING_PIN
#include "drivers/pin.h"
#endif /* RT_USING_PIN */
具体的结构体定义了PIN函数指针,在头文件中也声明了PIN函数
// rt-thread/components/drivers/include
// 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);
}

void rt_pin_mode(rt_base_t pin, rt_base_t mode);
void rt_pin_write(rt_base_t pin, rt_base_t value);
int rt_device_pin_register(
    const char *name, const struct rt_pin_ops *ops, void *user_data);

二、驱动层如何实现PIN相关的操作

1、PIN函数的实现

(1)驱动层的函数接口定义

接口定义如下
// rt-thread/components/drivers/misc/pin.c
#include <drivers/pin.h>
static struct rt_device_pin _hw_pin;

void rt_pin_mode(rt_base_t pin, rt_base_t mode)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);
    _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode);
}

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);
}

2、驱动层注册PIN设备

(1)注册的声明实现

PIN函数内部使用了结构体内的函数指针,具体的赋值由下面的函数完成
// rt-thread/components/drivers/include
// pin.h
int rt_device_pin_register(
    const char *name, const struct rt_pin_ops *ops, void *user_data);
// rt-thread/components/drivers/misc/pin.c
int rt_device_pin_register(
    const char *name, const struct rt_pin_ops *ops, void *user_data)
{
    // ...
    _hw_pin.ops                 = ops;
    // ...
    /* register a character device */
    rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR);
    return 0;
}

(2)注册pin设备

调用注册pin设备的函数如下
// libraries/HAL_Drivers/drv_gpio.c
int rt_hw_pin_init(void)
{
    return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
}
注册PIN设备的ops实际传入的结构体内容如下
static const 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,
    stm32_pin_get,
};

3、驱动层实现PIN操作函数

(1)调用HAL库实现

实际传入rthhread引脚操作的结构中,函数指针指向的函数定义如下
// libraries/HAL_Drivers/drv_gpio.c
static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    // ...
    GPIO_InitStruct.Pin = PIN_STPIN(pin);
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    // ...
    switch(mode) -> {}
    // ...
    HAL_GPIO_Init(PIN_STPORT(pin), &GPIO_InitStruct);
}

(2)引脚的描述差异

简短拆分一下上述使用到的宏定义
([PORTx]{0,1,2,3,4,5}) * 16 + PIN
==> 0x[0-5]_PIN
在RT-Thread中使用一个字节8位描述引脚,其中高4位对应引脚的分组PORT,低4位表示引脚的编号PIN。STM32HAL使用两个字节数据的16位来描述对应引脚。所以如下两个宏展开后分别求解引脚的端口PORT和引脚的编号PIN。
#define PIN_STPIN(pin)
    (
        (uint16_t)
        (1u << PIN_NO(pin))
    )
#define PIN_NO(pin)
    (
        (uint8_t)
        ((pin) & 0xFu)
    )


#define PIN_STPORT(pin)
    ( (GPIO_TypeDef *)
        (
        GPIOA_BASE
        +
        (0x400u * PIN_PORT(pin))
        )
    )

#define PIN_PORT(pin)
    ( (uint8_t)
        (
        ((pin) >> 4)
        &
        0xFu
        )
    )

(3)rt-thread项目和bsp项目的差异

在rt-thread studio项目中,通过一个将一个字节的数据铺开到数组,数组的每一个元素都是对唯一端口PORT上引脚PIN。
struct pin_index {}
    int index;
    GPIO_TypeDef* gpio;
    uint32_t pin;
}

#define __STM32_PIN(index, gpio, gpio_index)
    {index, GPIO##gpio##gpio_index}

static const struct pin_index pins[] = {
#if defined(GPIOA)
    __STM32_PIN(0, A, 0)
    __STM32_PIN(1, A, 0)
    // ...
#if defined(GPIOB)
    __STM32_PIN(16, B, 0)
    // ...
#endif /* defined(GPIOB) */
#endif /* defined(GPIOA) */
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值