STM32按键输入,上拉还是下拉
前言
书接上回,咱们在之前提到,要想配置GPIO的工作模式需要配置其对应端口的CRL或者CRH寄存器,但是对于输入而言有一种工作模式叫做上拉/下拉输入
,那它到底是上拉还是下拉呢?本文将会继续讨论GPIO的输出配置问题,分析一下GPIO_Init()
这个函数的内容。
寄存器
对于stm32F103系列的单片机,GPIO口的相关寄存器并不止之前提到的那两个,之前提到的高位寄存器CRH
和低位寄存器CRL
有一个统一的名字叫做配置寄存器
。
每个GPIO端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH)
,两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR)
,一个32位置位/复位寄存器(GPIOx_BSRR)
,一个16位复位寄存器(GPIOx_BRR)
和一个32位锁定寄存器(GPIOx_LCKR)
。
GPIO_Init()
GPIO_Init()
这个函数大家经常会使用,但是你知道它究竟在干什么事情么?
它的内容如下(内容来自stm32f10x_gpio.c这个文件):
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO Mode Configuration -----------------------*/
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding high control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}
咱们来慢慢分析这段代码。
首先数据进来之后先是对输入的数据进行检测:
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
这种代码assert_param
叫做断言,其作用是用于检验用户输入的数据是否合法,当你传进来的参数不合法时,编译器在编译过程中就会警告提示你输入的参数有问题。这种断言代码有利于确保工程的稳定性,所以在实际工程中有很广泛的使用。
比如当你在初始化GPIO时使用下面的代码(瞎写的参数):
GPIO_InitTypeDef GPIOx_Init;
GPIOx_Init.GPIO_Mode=1;
GPIOx_Init.GPIO_Pin= 1;
GPIOx_Init.GPIO_Speed=1;
GPIO_Init(GPIOC,&GPIOx_Init);
编译时,编译器会提示你一下信息:
在进入下一段代码前,前让我们来看看下面的枚举列表和一个宏定义。
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
可以看到,模拟输入、浮空输入、上拉/下拉输入模式的前4位都不是0x1,而输出模式则都以0x1开头,于是GPIO_Init()
里:
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
使用这个代码来配置输出模式下的输出速度。
接下来对引脚进行配置,分别配置了配置寄存器
的CRL和CRH。
由于咱们通过库函数的方式配置引脚时可能会需要一次性配置多个引脚,所以配置过程是通过一个for循环语句来配置的。
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
通过查看引脚宏定义我们可以知道
pos = (((uint32_t)0x01) << (pinpos + 0x08));
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
这两句代码确定了引脚的位置(这句代码是我从配置寄存器的高位寄存器配置代码里复制来的,所以起始引脚是8号引脚
对应#define GPIO_Pin_8 ((uint16_t)0x0100)
这一句,所以移位时有一个pinpos + 0x08
,而对于低位配置寄存器而言就不需要额外加8)。
然后判断一下在选择引脚时是否选择了当前的这个引脚,如果选择了,那么就进行相关配置,具体细节留给读者您来分析吧,我想着重强调的是下面的代码:
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
在这里,我们看到了,当你配置引脚为上拉输入和下拉输入的区别,下拉输入需要额外配置复位寄存器(GPIOx_BRR)
,上拉输入时需要配置置位/复位寄存器(GPIOx_BSRR)
查看手册后发现,其实这些都是在配置端口输出数据寄存器(GPIOx_ODR)
为啥要配置ODR寄存器呢?因为要配置上拉还是下拉。
如果希望配置为上拉则ODR相应位应该被置1,下拉则置0。
回到标题
让我们查看一下GPIO端口的结构
当你把引脚配置为上拉输入,那么IO相应的上拉开关(图里上面的那个开/关)闭合,由数字高电平(VDD)通过一个电阻连向IO口,反之则是数字地(VSS)通过一个电阻与IO引脚相连。
对于按键输入而言,我们可以很轻松地通过库函数配置实现,不过阅读本文后,你将更加清楚你所写的代码将会如何操控寄存器来实现你需要的功能。
感谢您的阅读
以上是本文的全部内容了,欢迎各位交流,也欢迎各位大佬们勘误。
最后附上一段按键控制LED灯的代码和视频
//key.h文件
#define KEY_PRESSED 1 //按键按下
#define KEY_UNPRESSED 0 //按键未按下
#define KEY_PORT GPIOC
#define KEY_Pin GPIO_Pin_9
#define LED_ON 0
#define LED_OFF 1
void KEY_GPIO_Init(void);
u8 Read_Key_State(void);
//key.c文件
void KEY_GPIO_Init(void)//IO初始化
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitTypeDef GPIOx_Init;
GPIOx_Init.GPIO_Mode=GPIO_Mode_IPU;
GPIOx_Init.GPIO_Pin= GPIO_Pin_9;
GPIOx_Init.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIOx_Init);
}
u8 Read_Key_State(void)
{
if(GPIO_ReadInputDataBit(KEY_PORT,KEY_Pin)==(uint8_t)Bit_SET)
{
//Ïû¶¶
delay_ms(5);
if(GPIO_ReadInputDataBit(KEY_PORT,KEY_Pin)==(uint8_t)Bit_SET)
{
//µÈ´ýËÉÊÖ
while(GPIO_ReadInputDataBit(KEY_PORT,KEY_Pin)==(uint8_t)Bit_SET);
return KEY_PRESSED;
}
else
{
return KEY_UNPRESSED;
}
}
else
{
return KEY_UNPRESSED;
}
}
//main.c
int main(void)
{
u8 LED_state = LED_ON;
delay_init();
KEY_GPIO_Init();
LED_Init();
while (1)
{
if(Read_Key_State()==KEY_PRESSED)
{
LED_state = 1 - LED_state;
LED_Set(LED_state);
}
}
}
stm32按键控制LED