第1章 GPIO驱动
如果需要其它实现方法及商业合作,邮件联系[email protected]
1.1 GPIO封装需求
对于初使化一个GPIO引脚,比如说一个LED灯的控制引脚GPIOA上的第GPIO_PIN_1,通常做法是这样子:
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
在这里插入代码片`如果硬件发生变化,现在LED灯的控制引脚改放在GPIOB上的第GPIO_PIN_2上,我们至少需要修改三处地方,这样非常不利于项目的移植。那么,我们一起尝试着能不能只更改一次地方就能满足上述需求。
对于初使化配置来说,我们其实只关心初使化引脚的位置、模式、上拉、速度、复用(F4支持)这5个参数。
1.2 GPIO 引脚编号
为使所有的GPIO引脚唯一,我们需要对GPIOA至GPIOB共计32个引脚(可以扩展)进行统一编号。
typedef enum {
wPIN_NO = (wuint)0u,
#define wPIN_PIN_MASK (wuint)0x000fu
#define wPIN_GPIO_MASK (wuint)0x00f0u
wPIN_A0 = (wuint)0x0010u + 0,
wPIN_A1 = (wuint)0x0010u + 1,
wPIN_A2 = (wuint)0x0010u + 2,
wPIN_A3 = (wuint)0x0010u + 3,
wPIN_A4 = (wuint)0x0010u + 4,
wPIN_A5 = (wuint)0x0010u + 5,
wPIN_A6 = (wuint)0x0010u + 6,
wPIN_A7 = (wuint)0x0010u + 7,
wPIN_A8 = (wuint)0x0010u + 8,
wPIN_A9 = (wuint)0x0010u + 9,
wPIN_A10 = (wuint)0x0010u + 10,
wPIN_A11 = (wuint)0x0010u + 11,
wPIN_A12 = (wuint)0x0010u + 12,
wPIN_A13 = (wuint)0x0010u + 13,
wPIN_A14 = (wuint)0x0010u + 14,
wPIN_A15 = (wuint)0x0010u + 15,
wPIN_B0 = (wuint)0x0020u + 0,
wPIN_B1 = (wuint)0x0020u + 1,
wPIN_B2 = (wuint)0x0020u + 2,
wPIN_B3 = (wuint)0x0020u + 3,
wPIN_B4 = (wuint)0x0020u + 4,
wPIN_B5 = (wuint)0x0020u + 5,
wPIN_B6 = (wuint)0x0020u + 6,
wPIN_B7 = (wuint)0x0020u + 7,
wPIN_B8 = (wuint)0x0020u + 8,
wPIN_B9 = (wuint)0x0020u + 9,
wPIN_B10 = (wuint)0x0020u + 10,
wPIN_B11 = (wuint)0x0020u + 11,
wPIN_B12 = (wuint)0x0020u + 12,
wPIN_B13 = (wuint)0x0020u + 13,
wPIN_B14 = (wuint)0x0020u + 14,
wPIN_B15 = (wuint)0x0020u + 15,
…… …… ……
}
1.3 GPIO 引脚直接初使化函数
uint16_t wGpio_Init(wPin pin,uint32_t Mode,uint32_t Pull,uint32_t Speed)
{/*>>直接初使化GPIO引脚*/
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_TypeDef *GPIO;
switch(wPIN_GPIO_MASK & pin)
{
case wPIN_A0:{GPIO=GPIOA;__HAL_RCC_GPIOA_CLK_ENABLE();}break;
case wPIN_B0:{GPIO=GPIOB;__HAL_RCC_GPIOB_CLK_ENABLE();}break;
case wPIN_C0:{GPIO=GPIOC;__HAL_RCC_GPIOC_CLK_ENABLE();}break;
case wPIN_D0:{GPIO=GPIOD;__HAL_RCC_GPIOD_CLK_ENABLE();}break;
…… …… ……//省略部分代码
default:return NULL ;
}
switch(wPIN_PIN_MASK & pin)
{
case wPIN_0:{GPIO_InitStruct.Pin = GPIO_PIN_0;}break;
case wPIN_1:{GPIO_InitStruct.Pin = GPIO_PIN_1;}break;
case wPIN_2:{GPIO_InitStruct.Pin = GPIO_PIN_2;}break;
case wPIN_3:{GPIO_InitStruct.Pin = GPIO_PIN_3;}break;
case wPIN_4:{GPIO_InitStruct.Pin = GPIO_PIN_4;}break;
case wPIN_5:{GPIO_InitStruct.Pin = GPIO_PIN_5;}break;
case wPIN_6:{GPIO_InitStruct.Pin = GPIO_PIN_6;}break;
case wPIN_7:{GPIO_InitStruct.Pin = GPIO_PIN_7;}break;
case wPIN_8:{GPIO_InitStruct.Pin = GPIO_PIN_8;}break;
case wPIN_9:{GPIO_InitStruct.Pin = GPIO_PIN_9;}break;
case wPIN_10:{GPIO_InitStruct.Pin = GPIO_PIN_10;}break;
case wPIN_11:{GPIO_InitStruct.Pin = GPIO_PIN_11;}break;
case wPIN_12:{GPIO_InitStruct.Pin = GPIO_PIN_12;}break;
case wPIN_13:{GPIO_InitStruct.Pin = GPIO_PIN_13;}break;
case wPIN_14:{GPIO_InitStruct.Pin = GPIO_PIN_14;}break;
case wPIN_15:{GPIO_InitStruct.Pin = GPIO_PIN_15;}break;
default: return NULL;
}
GPIO_InitStruct.Mode = Mode;
if(IS_GPIO_PULL(Pull)) { GPIO_InitStruct.Pull = Pull;}
else {GPIO_InitStruct.Pull = GPIO_NOPULL;}
if(IS_GPIO_SPEED(Speed)){GPIO_InitStruct.Speed = Speed;}
else { GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;}
HAL_GPIO_Init(GPIO, &GPIO_InitStruct);
return GPIO_InitStruct.Pin ;
}
对于前面的初使化可以直接调用此函数实现。初使化一个GPIO引脚,比如说一个LED灯的控制引脚GPIOA上的第GPIO_PIN_1,高级做法是这样子:
wGpio_Init(wPIN_A1,GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_HIGH);
当硬件发生变化,现在LED灯的控制引脚改放在GPIOB上的第GPIO_PIN_2上,则初使化函数可改为:
wGpio_Init(wPIN_B2,GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_HIGH);
可以看出,此次引脚更改,只需更改一次地方。我们接着思考,我们现在想点亮这个LED灯,遇到了问题?我们先假设输出低电平时led亮,高电平不亮。通常的做法是
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);//点亮led灯
这里又遇到了GPIOA 以及GPIO_PIN_1的地方,而我们使用的却是wPIN_A1。这应该如何调用呢?
1.4 GPIO结构体引用
对于1个Led灯,我们主要关心是哪个引脚控制,有效电平(点亮电平)是什么电平,我们可以用如下方式定义一个结构体。
typedef struct {
GPIO_TypeDef* GPIOx; //引脚句柄
uint16_t GPIO_Pin; //引脚
wFun EN; //使能开关
wGpio_Level Level; //有效电平
}wGpio_Def;
1.5 GPIO 引脚带句柄式初使化函数
void wGpio_putInit(wGpio_Def* wGPIO,wPin pin,uint32_t Mode,uint32_t Pull, uint32_t Speed, wGpio_Level level)
{/*>>带句柄方式初使化GPIO引脚,并增加了有效电平位*/
switch(wPIN_GPIO_MASK & pin)
{
case wPIN_A0:{wGPIO->GPIOx=GPIOA;}break;
case wPIN_B0:{wGPIO->GPIOx=GPIOB;}break;
case wPIN_C0:{wGPIO->GPIOx=GPIOC;}break;
…… …… ……//省略部分代码
default:return ;
}
wGPIO->GPIO_Pin = wGpio_Init(pin,Mode,Pull,Speed,NULL);
wGPIO->Level = level ;
wGPIO->EN = wENABLE ;
}
对于类似于led的GPIO控制引脚,为了能保存GPIO_TypeDef句柄以及GPIO_PIN引脚号,我们需要先新建一个wGpio_Def结构体,这样就可以在任何时候能获取GPIO引脚所对应的具体参数。
1.6 GPIO 引脚带句柄式读函数
wGpio_State wGpio_putRead(wGpio_Def* wGPIO)
{/*>>读取电平的有效性*/
wGpio_State State; //当前状态
if(wGPIO->EN)
{
if(HAL_GPIO_ReadPin(wGPIO->GPIOx, wGPIO->GPIO_Pin))
{//读取到正限位引脚为高电平
if(wGPIO->Level==wHigh) // 如果定义高电平有效
{
State = wACTIVE;
}
else // 如果定义低电平有效
{
State = wINACTIVE;
}
}
else// 读取到正限位引脚为低电平
{
if(wGPIO->Level==wHigh)
{
State = wINACTIVE;
}
else
{
State = wACTIVE;
}
}
}
return State;
}
1.7 GPIO 引脚带句柄式写函数
void wGpio_putWrite(wGpio_Def* wGPIO,wGpio_Set CurState)
{/*>>设置电平的有效性*/
if(wGPIO->EN == ENABLE)
{
if(CurState == Set_ACTIVE)
{//设定为有效电平
if(wGPIO->Level==wHigh)
{// 如果定义高电平有效
HAL_GPIO_WritePin(wGPIO->GPIOx, wGPIO->GPIO_Pin,GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(wGPIO->GPIOx, wGPIO->GPIO_Pin,GPIO_PIN_RESET);
}
}
else if(CurState == Set_INACTIVE)
{//设定为无效电平
if(wGPIO->Level==wHigh)
{
HAL_GPIO_WritePin(wGPIO->GPIOx, wGPIO->GPIO_Pin,GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(wGPIO->GPIOx, wGPIO->GPIO_Pin,GPIO_PIN_SET);
}
}
else
{//切换电平
HAL_GPIO_TogglePin(wGPIO->GPIOx, wGPIO->GPIO_Pin);
}
}
}
现在有了带句柄式的写函数,我们就可以很好的处理原先抛出的问题。我们不用直接的方式初使化Gpio引脚,而是引用句柄的方式初使化。再通过调用wGpio_putWrite函数使能引脚输出有效电平,由于我们初使化时定义有效电平为低电平,所以此引脚最终会输出低电平,例如:
wGpio_Def LedCss;//定义一个句柄
wGpio_putInit (&LedCss,wPIN_A1,GPIO_MODE_OUTPUT_PP,GPIO_NOPULL,
GPIO_SPEED_FREQ_HIGH,wLow); //低电平有效
wGpio_putWrite(&LedCss, Set_ACTIVE);//点亮led灯