STM32单片机开发:按键控制LED灯实验

在控制LED灯仿真实验中,我们初步了解了GPIO的功能和使用方法。本文通过按键控制LED灯实验,进一步介绍GPIO的模式配置。

GPIO的八种工作模式

要让GPIO按照设定的需求工作,必须要把GPIO配置为相应的模式,如在控制LED灯仿真实验中,如果将GPIOA配置为输入模式,实验就不会正常工作。下面结合GPIO基本结构图,来探讨GPIO的8种模式及其应用场合。
GPIO基本结构图最右端为I/O引脚,左端的器件位于芯片内部,在I/O引脚两侧并联了两个用于保护的二极管。GPIO基本结构图按照输入模式和输出模式,分为上下两个部分:上半部分为输入模式结构;下半部分为输出模式结构。
输入模式结构
输入模式分为上拉输入模式、下拉输入模式、浮空输入模式和模拟输入模式。四种输入模式主要通过上拉电阻、下拉电阻、TTL施密特触发器、两个连接上拉和下拉电阻的开关实现。与VDD相连的为上拉电阻,与VSS相连的为下拉电阻,TTL施密特触发器主要把输入的电压信号转换为0、1数字信号存储到输入数据寄存器,两个开关的断开与闭合可以通过GPIO配置寄存器(CRL、CRH)控制。
I/O引脚配置为上拉输入模式时,与上拉电阻连接的开关闭合,读取的I/O引脚电压在无输入默认状态下为高电平;I/O引脚配置为下拉输入模式时,与下拉电阻连接的开关闭合,读取的I/O引脚电压在无输入默认状态下为低电平;I/O引脚配置浮空输入模式时,在芯片内部既没有接上拉电阻,也没有接下拉电阻,经由触发器输入,在该模式下I/O引脚电压不确定;I/O引脚配置为模拟输入模式时,施密特触发器关闭,也不接上拉和下拉电阻,经另一条线路将输入的电压信号传输到片上外设模块,如传送至ADC模块。
输出模式结构
输出模式分为推挽输出模式、开漏输出模式、复用推挽输出模式和复用开漏输出模式。四种输出模式主要通过P-MOS管和N-MOS管实现。I/O引脚配置为推挽输出模式时,若引脚输出高电平3.3V,P-MOS管导通,若引脚输出低电平0V,则N-MOS导通,两个管子轮流导通,一个负责灌电流,一个负责拉电流;I/O引脚配置为开漏输出模式时,当控制I/0引脚输出0时,N-MOS管导通使输出接地,该引脚输出低电平,当控制I/0引脚输出1时,该引脚既不输出高电平,也不输出低电平,为高阻状态。
普通推挽输出模式一般应用在输出电平为0v和3.3V的场合,普通开漏输出模式一般应用在电平不匹配的场合,如需要输出5V的高电平,就需要在外部接一个上拉电阻,电源为5V,由上拉电阻和电源对外输出5V的高电平。

设计按键控制发光二极管电路

启动proteus8,新建仿真工程,绘制如下电路图。
按键一端接PC0引脚,另一端接地。PC0引脚同时接入一个上拉电阻R3,R3的一端接电源,将PC0端口钳位在5V。按键按下后,PC0端口通过按键接地,端口电压降至0V,程序不断轮询PCO端口电平,当PC0端口电平变化为0V时,说明按键被按下。
为方便观察PC0端口的电平变化,引入一个虚拟直流电压表,随时监测PC0端口的电平变化。虚拟直流电压表的名称为“DC VOLTMETER”,单击下图所示的工具栏图标可以选择虚拟仪器。

编写程序

PC0端口应配置那种工作模式?按键的一端连接PC0端口,另一端接地,与按键连接的PC0端口会被上拉电阻钳位在高电平,键盘检测程序会持续检测PC0端口的电平,若检测到该端口由高电平变为低电平,说明与该端口连接的按键被按下,因为按键闭合后,相当于PC0端口通过按键与地直接连接,导致PC0端口变为低电平。因此PC0端口应配置为上拉输入模式,默认为高电平。
启动STM32CubeIDE,新建STM32工程,固件选择STM32F103R6T6A,工程名称为STM32ButtonLed。
STM32CubeIDE已经为我们创建了基本代码,具体实现代码还需要手动编写。本实验主要编辑工程内main.c文件,驱动单片机的GPIOA的PA8和PA9端口,GPIOC的PC0端口。PA8和PA9端口配置为推挽输出模式,PC0端口配置为上拉输入模式。
宏定义
#define  PC0_VALUE   GPIOC->IDR & 0x0001
PC0_VALUE读取GPIOC端口的输入数据寄存器(IDR)的低位(最低位,即第0位)。通过与0x0001进行按位与操作,可以只获取到PC0引脚的状态(0或1)。如果与PC0引脚连接的按键被被按下,则PC0_VALUE的结果为0;如果未被按下,则结果为1。
定义按键扫描函数
void KeyScan()
{
   unsigned short key_one = PC0_VALUE;  
   if( key_one == 0 )
   {
      delay(10);
      key_one = PC0_VALUE;
      if( key_one == 0 )
      {
         ControlLed(1);
      }
   }
   else
   {
      ControlLed(0);
   }
}
函数用于扫描按键状态,并根据状态控制LED。函数首先读取PC0引脚的状态到变量key_one,若key_one为0(意味着按键被按下),则执行一个延时操作delay(10),延时操作的目的是消抖,即防止按键在按下时由于机械或电气原因导致的多次快速触发。延时后,再次读取PC0引脚的状态到key_one。如果此时仍为0,则确认按键确实被按下,调用ControlLed(1)函数来点亮LED。若最初读取的key_one不为0,直接调用ControlLed(0)函数来熄灭LED。
控制LED函数
void ControlLed(int open)
{
// 打开LED
if( open )
{
// PA9设置为低电平
GPIOA->ODR &= ~(1<<9);
// PA8设置为低电平
GPIOA->ODR &= ~(1<<8);


}
else
{
// PA9设置为高电平
GPIOA->ODR |= 1<<9;
// PA8设置为高电平
GPIOA->ODR |= 1<<8;


}
}
函数用于设置GPIOA的PA8和PA9引脚的输出电平,从而控制与其引脚连接的LED状态。
GPIOA->ODR &= ~(1<<9);将GPIOA的输出数据寄存器(ODR)的第9位清零(设置为低电平)。在STM32中,如果某个GPIO引脚配置为输出模式,并且ODR的相应位被清零,则该引脚将输出低电平。
GPIOA->ODR |= 1<<9;将GPIOA的输出数据寄存器(ODR)的第9位设置为1(因为1<<9的结果在第9位上有一个1,其他位都是0)。由于按位或运算的性质,若ODR的第9位原本就是1,这条语句不会改变它;如果原本是0,这条语句会将其设置为1。该语句的作用是确保GPIOA端口的第9位输出高电平。
初始化GPIO函数
void InitGPIO()
{
// 使能GPIOA
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 使能GPIOC
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
// 0到7位清零
GPIOA->CRH &= 0xffffff00;  
    // 位0置1,位4置1
    GPIOA->CRH |= 0x00000011;  


//PC0端口配置为上拉输入模式
GPIOC->CRL &= 0xfffffff0;  
    GPIOC->CRL |= 0x00000008;  
GPIOC->ODR |= 0x0001;


}
函数使能GPIOA和GPIOC端口,并配置GPIOA的PA8和PA9引脚为推挽输出模式,GPIOC的PC0引脚为上拉输入模式。
main函数
int main()
{
// 初始化GPIO
InitGPIO();
// 关闭发光二极管
ControlLed(0);
while(1)
{
KeyScan();
}
}
函数调用InitGPIO()初始化GPIOA和GPIOC端口,调用ControlLed()函数初始化LED状态。然后在循环内部调用KeyScan()函数。

实验结果

仿真程序运行后,与PC0端口连接的直流电压表显示为35V,按下按键,D1和D2会被点亮,直流电压表显示为0.0V,松开按键,D1和D2熄灭,直流电压表显示为5V。