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

  • Post author:
  • Post category:其他


一、关于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) */
}



版权声明:本文为sudo_yuyan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。