新闻  |   论坛  |   博客  |   在线研讨会
简单实用IO输入输出框架
鱼鹰谈单片机 | 2021-05-12 09:36:38    阅读:384   发布文章

在一个嵌入式系统中,可能存在许多输入或输出的IO口,输入有霍尔传感器、红外对管等,输出有LED、电源控制开关等。

如果说硬件可以一次成型,那么随便一份代码都可以完成IO的配置工作,但研发阶段的产品,硬件各种修改是难免的,每一次 IO 的修改,对于底层开发人员来说,可能都是一次挑战。

因为一旦有某一个 IO 配置错误,或者原来的配置没有修改正确(比如一个 IO 在原来的硬件适配中是输入,之后的硬件需要修改成输出),那么你很难查出来这是什么问题,因为这个时候不仅硬件修改了,软件也修改了,你需要先定位到底是软件问题还是硬件问题,所以一个好用的 IO 的配置框架就显得很有必要了。

有道友会说,不如使用 CubeMx 软件进行开发吧。


1、这个软件适用于 ST 单片机,以前还能用,现在,除非你家里有矿,不然谁用的起STM32?基本上都国产化了(虽然有些单片机号称兼容,但到底还是有些差异的)。

2、公司原本的代码就是使用标准库,只是因为IO 的变化,你就需要把整个库换掉吗?时间上允许吗?你确定修改后不会出现大问题?

3、国产化的芯片可没有所谓的标准库和HAL库供你选择,每一家都有各自的库,如果你的产品临时换方案怎么办?

4、HAL 效率问题。

今天鱼鹰介绍一个简单实用的框架,可用于快速增加或修改IO配置,甚至修改底层库。

假设有3个 LED 作为输出、3 个霍尔传感器作为输入:

输入配置代码:

#define GPIOx_Def           GPIO_TypeDef*
#define GPIOMode_Def        GPIOMode_TypeDef
typedef struct
{
    GPIOx_Def       gpio; 
    uint16_t        msk;
    GPIOMode_Def    pull_up_down;     
} bsp_input_pin_def; 
#define  _GPIO_PIN_INPUT(id, pull, gpiox, pinx)   [id].gpio = (GPIOx_Def)gpiox, [id].msk = (1 << pinx), [id].pull_up_down = (GPIOMode_Def)pull
#define  GPIO_PIN_INPUT(id, pull, gpiox, pinx)    _GPIO_PIN_INPUT(id, pull, gpiox, pinx)
#define bsp_pin_get_port(gpiox)             ((uint16_t)((GPIO_TypeDef *)gpiox)->IDR)
#define bsp_pin_get_value(variable,id)      do{ bsp_pin_get_port(bsp_input_pin[id].gpio) & bsp_input_pin[id].msk ? variable |= (1 << id) : 0;} while(0)
#define BSP_GPIO_PUPD_NONE                                          GPIO_Mode_IN_FLOATING
#define BSP_GPIO_PUPD_PULLUP                                        GPIO_Mode_IPU
#define BSP_GPIO_PUPD_PULLDOWN                                      GPIO_Mode_IPD
typedef enum
{
    PIN_INPUT_HALL_0 = 0,  // 输入 IO 定义
    PIN_INPUT_HALL_1,   
    PIN_INPUT_HALL_2,                    
    PIN_INPUT_MAX
}bsp_pin_input_id_def;
static const bsp_input_pin_def  bsp_input_pin [PIN_INPUT_MAX] = 
{
    GPIO_PIN_INPUT(PIN_INPUT_HALL_0,          BSP_GPIO_PUPD_NONE, GPIOA, 0),
    GPIO_PIN_INPUT(PIN_INPUT_HALL_1,          BSP_GPIO_PUPD_NONE, GPIOB, 8),    
    GPIO_PIN_INPUT(PIN_INPUT_HALL_2,          BSP_GPIO_PUPD_NONE, GPIOE, 9),   
};
// 单个 IO 初始化函数  
void bsp_pin_init_input(GPIOx_Def gpiox, uint32_t msk, GPIOMode_TypeDef pull_up_down)
{
    uint32_t temp;
    assert_param((msk & 0xffff0000) == 0 && gpiox != 0);
    temp = ((uint32_t) gpiox - (uint32_t) GPIOA) / ( (uint32_t) GPIOB - (uint32_t) GPIOA);
    /* enable the led clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA << temp, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode  = (GPIOMode_Def)pull_up_down;
    GPIO_InitStruct.GPIO_Pin   = msk;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init((GPIO_TypeDef*)gpiox, &GPIO_InitStruct);
}
// 所有 IO 初始化
void gpio_input_init()
{    
    bsp_input_pin_def  *info;
    info = (bsp_input_pin_def *)&bsp_input_pin;
    for(int i = 0; i < sizeof(bsp_input_pin)/sizeof(bsp_input_pin[0]); i++)
    {
        bsp_pin_init_input(info->gpio, info->msk, info->pull_up_down);
        info++;
    }   
}
// 最多支持 32 个 IO 输入
uint32_t bsp_input_all(void)
{
    uint32_t temp = 0;
    bsp_pin_get_value(temp, PIN_INPUT_HALL_0);
    bsp_pin_get_value(temp, PIN_INPUT_HALL_1);
    bsp_pin_get_value(temp, PIN_INPUT_HALL_2);
    return temp;
}
// 读取单个 IO 状态
uint32_t bsp_input_level(bsp_pin_input_id_def id)
{
    return (bsp_pin_get_port(bsp_input_pin[id].gpio) & bsp_input_pin[id].msk) ? 1 : 0;
}
typedef enum
{
    HW_HAL_LEVEL_ACTIVE = 0, // 可直接修改为 0 或 1,另一个枚举值自动修改为相反值
    HW_HAL_LEVEL_NO_ACTIVE = !HW_HAL_LEVEL_ACTIVE,
}hw_input_hal_status_def;
typedef struct  
{
    hw_input_hal_status_def hal_level0; 
    uint8_t                 hal_level1;
    uint8_t                 hal_level2;
}bsp_input_status_def;
bsp_input_status_def bsp_input_status;
int main(void)
{  
    USRAT_Init(9600);//必须,进入调试模式后点击全速运行
    gpio_input_init();
    while(1)
    {
        uint32_t temp = bsp_input_all();
        bsp_input_status.hal_level0 = (hw_input_hal_status_def)((temp >> PIN_INPUT_HALL_0) & 1);
        bsp_input_status.hal_level1 = ((temp >> PIN_INPUT_HALL_1) & 1);
        bsp_input_status.hal_level2 = ((temp >> PIN_INPUT_HALL_2) & 1);
    }                      
}
调试的时候,我们可以很方便的查看每个 IO 的状态是怎样的,而不用管 0 或 1 到底代表什么意思:
图片
输出配置代码:
#define GPIOx_Def           GPIO_TypeDef*
#define GPIOMode_Def        GPIOMode_TypeDef
typedef struct
{
    GPIOx_Def  gpio; 
    uint32_t   msk; 
    uint32_t   init_value; 
} bsp_output_pin_def; 
#define  _GPIO_PIN_OUT(id, gpiox, pinx, init)                        [id].gpio = gpiox, [id].msk = (1 << pinx), [id].init_value = init
#define  GPIO_PIN_OUT(id, gpiox, pinx, init)                         _GPIO_PIN_OUT(id, gpiox, pinx, init)
#define _bsp_pin_output_set(gpiox, pin)                              (gpiox)->BSRR = pin
#define bsp_pin_output_set(gpiox, pin)                               _bsp_pin_output_set(gpiox, pin)
#define _bsp_pin_output_clr(gpiox, pin)                              (gpiox)->BRR = pin
#define bsp_pin_output_clr(gpiox, pin)                               _bsp_pin_output_clr(gpiox, pin)
typedef enum
{
    PIN_OUTPUT_LED_G,
    PIN_OUTPUT_LED_R,  
    PIN_OUTPUT_LED_B,
    PIN_OUTPUT_MAX
}bsp_pin_output_id_def;
static const bsp_output_pin_def  bsp_output_pin [PIN_OUTPUT_MAX] = 
{
    GPIO_PIN_OUT(PIN_OUTPUT_LED_G,          GPIOA,  0, 0),
    GPIO_PIN_OUT(PIN_OUTPUT_LED_R,          GPIOF, 15, 0),
    GPIO_PIN_OUT(PIN_OUTPUT_LED_B,          GPIOD, 10, 0),
};
void bsp_pin_init_output(GPIOx_Def gpiox, uint32_t msk, uint32_t init)
{
    uint32_t temp;
    assert_param((msk & 0xffff0000) == 0 && gpiox != 0);
    temp = ((uint32_t) gpiox - (uint32_t) GPIOA) / ( (uint32_t) GPIOB - (uint32_t) GPIOA);
    /* enable the led clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA << temp, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode  = (GPIOMode_Def)GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Pin   = msk;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init((GPIO_TypeDef*)gpiox, &GPIO_InitStruct);
    if(init == 0)
    {
        bsp_pin_output_clr(gpiox, msk);
    }
    else
    {
        bsp_pin_output_set(gpiox, msk);
    }
}
void bsp_output_init()
{
    bsp_output_pin_def  *info;
    info = (bsp_output_pin_def *)&bsp_output_pin;
    for(int i = 0; i < sizeof(bsp_output_pin)/sizeof(bsp_output_pin[0]); i++)
    {
        bsp_pin_init_output(info->gpio, info->msk, info->init_value);
        info++;
    }
}
void bsp_output(bsp_pin_output_id_def id, uint32_t value)
{
    assert_param(id < PIN_OUTPUT_MAX);
    if(value == 0)
    {
        bsp_pin_output_clr(bsp_output_pin[id].gpio, bsp_output_pin[id].msk);
    }
    else
    {
        bsp_pin_output_set(bsp_output_pin[id].gpio, bsp_output_pin[id].msk);
    }
}
int main(void)
{  
    USRAT_Init(9600);//必须,进入调试模式后点击全速运行
    bsp_output_init();
    while(1)
    {
        bsp_output(PIN_OUTPUT_LED_G, 1);
        bsp_output(PIN_OUTPUT_LED_B, 0);
        bsp_output(PIN_OUTPUT_LED_R, 1);
    }                      
}

这个框架有啥好处呢?

1、自动完成 GPIO 的时钟初始化工作,也就是说你只需要修改引脚即可,不必关心时钟配置,但对于特殊引脚(比如PB3),还是得另外配置才行。

2、应用和底层具体 IO 分离,这样一旦修改了 IO,应用代码不需要进行任何修改。

3、增加或删减 IO 变得很简单,增加 IO时,首先加入对应枚举,然后就可以添加对应的 IO 了。删除 IO时,只要屏蔽对应枚举值和引脚即可。

4、参数检查功能, IO 删除时,因为屏蔽了对应的枚举,所以编译时可以帮你发现问题,而增加 IO 时,它可以帮你在运行时检查该 IO是否进行配置了,可以防止因为失误导致的问题。

1.jpg

5、更改库时可以很方便,只需要修改对应的宏即可,目前可以顺利在 GD32 和 STM32 库进行快速更换。

6、对于输入 IO 而言,可以方便的修改有效和无效状态,防止硬件修改有效电平。对于输出 IO 而言,可以设定初始 IO 电平状态。

7、代码简单高效,尽可能的复用代码,增加一个 IO 只需要很少的空间。

8、缺点就是,只对同种配置的 IO 可以这样用。


好好看看,或许能学到不少技巧哦。


*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客