STM32 嵌入式学习入门(3)—— STM32F103 按键输入控制LED灯
按键是单片机上一个很重要的输入设备,也很容易掌握,只要明白了IO口最基本的使用,就可以操作按键了。
我们的目的是控制开发板上板载的三个按键来操作开发板上板载的两个LED灯实现亮或灭(按键第一次按下时灯亮,再按下时灯灭,以此类推)。
博主所用的开发板是正点原子的mini板(STM32F103RCT6)和战舰板(STM32F103ZET6),因此下面的内容的例子以这两款开发板为例,但是基本的原理对任何开发板来说都是一样的,只要自己的开发板上板载了按键和LED灯(这两个资源应该是所有开发板上都有的资源吧),然后查看自己开发板的数据手册和硬件电路图、原理图,找到按键和LED灯对应的IO口,就可以按照本文所介绍的流程使用按键控制LED灯了。
在开发板上,板载硬件资源(比如我们这里用到的按键和LED灯)都是和确定的GPIO相连的,要使用这些硬件资源,实际上可以理解成对这些IO口的操作。
下面先大致说一下整个流程。
1.首先要使用这些硬件资源,就要对其进行初始化。初始化包括使能IO口时钟和初始化IO口参数两个部分,这两点在上一篇博文中有提到了。
2.初始化完成后就可以操作IO口了,就是写相关的逻辑代码。这里是按键控制LED灯,所以我们首先要扫描与按键相连的IO口的电平。如果IO电平发生变化,那么说明按键被按下了,我们就要让灯的状态发生反转。如果IO电平没有发生变化,那么说明按键没有被按下,我们可以过一个很小的时间间隔再去扫描与按键相连的IO口看是否发生变化,这样形成了一个死循环。
下面上一段代码,是正点原子Mini STM32F103RCT6开发板(mini板)按键实验的主函数部分:
#define LED0 PAout(8) // PA8
#define LED1 PDout(2) // PD2
#define KEY0_PRES 1 //KEY0
#define KEY1_PRES 2 //KEY1
#define WKUP_PRES 3 //WK_UP
int main(void)
{
u8 t=0;
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //初始化与按键连接的硬件接口
LED0=0; //点亮LED0
while(1){
t=KEY_Scan(0); //得到键值
KEY_Scan(0);即不支持连续有效。如果需要支持连续按,这里参数设置为1.
switch(t){
case KEY0_PRES: LED0=!LED0; break;
case KEY1_PRES: LED1=!LED1; break;
case WKUP_PRES: LED0=!LED0; LED1=!LED1; break;
default: delay_ms(10);
}
}//while(1)
}
代码比较长,但整体逻辑还是很容易理解的,首先解释一下前两行的宏定义,这里是通过位操作,实现了对PA8/PD2的电平输出的操作的,这一行代码其实挺复杂的,准确说是博主也讲不出原理(如果你去查看stm32的官方库函数,会发现这里的PAout(n)是通过很多层宏定义得到的),但是我会调用。总之,这样定义了以后,下面的代码你对LED0赋1对应着设置IO口为高电平,赋0就是设置IO口为低电平。下面的三个宏定义是为下面switch-case服务的,增强了代码的可读性。
接下来就是主函数了,首先是调用三个初始化函数,这个等下后面具体说。初始化完成后,然后LED0=0;这一句,点亮LED0。接着就是while(1)循环了,死循环里面先调用KEY_Scan(0);函数对按键进行检测,该函数的返回值有四种情况,即KEY0_PRES、KEY1_PRES、WKUP_PRES、0。前三个返回值表明相应的按键被按下了,返回0表明当前没有按键按下。然后是switch-case结构了,如果有按键按下,执行相应分支,对LED上的电平进行反转,实现按一次亮,再按一次灭,以此类推,这样的效果。如果没有按键按下,那么延时10ms,然后继续检测有没有按键按下。整体程序的执行过程就是这样。
下面的问题就是三个初始化函数和KEY_Scan(0);函数是怎么实现的了。
1.延时函数
这里用到了delay_init();和delay_ms(10);两个函数。延时函数要想完全搞清楚其中的原理需要了解STM32内核中定时器的知识,不是三两句可以解释清楚的,以后有时间再写一篇详细介绍一下延时函数,写完后贴到这里来。对于刚刚接触STM32的同学来说,我建议刚开始就会调用这个函数就好了,不要深究其实现原理,因为涉及其它内容比较多,零基础刚开始接触STM32,去看那些,如果看不懂挺打击人积极性的。这两个函数的调用方法:delay_ms(10);调用时候传递一个整形参数n,表示要延时n毫秒,如果程序中用到了delay_ms(n);函数,那么在调用延时函数前要调用延时初始化函数delay_init();就是这样无参调用就好了。另外,delay_ms(n);函数调用时候n的值也不能过大,n的值是有一个上限的,这就像int所能表示的最大值也是有上限的一样。具体的这个值是多少,是和时钟频率有关的,对时钟频率为72M的条件下(这算是个默认条件了),n<=1864。
上面说的两个延时函数都不是自己写的,正点原子的每个实验代码的SYSTEM文件夹的delay.c文件中都有这个代码,所以博主就拿来主义了,很方便。如果是别的开发板,可以查查手册看怎样能实现延时的功能,实在不行,下面的代码也能实现延时……
void Delay(u32 count)
{
u32 i=0;
for(;i
2.LED_Init();和KEY_Init();函数
这两个函数是自己写的,就是对IO口的相关参数进行配置,先上代码:
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); //使能PA,PD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置, 推挽输出
GPIO_Init(GPIOD, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
}
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; //PA15
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //PC5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化GPIOC5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
关于GPIO口的初始化,可以参考博主的上一篇文章,STM32 嵌入式学习入门(2)——STM32的GPIO介绍
至于这里配置为什么是这些参数,是根据开发板硬件连接确定的,上面的代码是正点原子MiniSTM32F103RCT6开发板的代码,这款开发板的原理图如下图:
从这里可以看出来按键对应的IO口以及它们是与高电平相连还是低电平相连。比如两个LED,硬件上,它们都是与高电平相连的,所以你把另一端设置为低电平的时候它们就被点亮,如果另一端设置为高电平,那么它们就灭。另外IO口的模式也是从硬件连接上确定的。
3.KEY_Scan(0);函数的实现
这个函数的实现其实不难,和STM32的知识没多大关系,就是C语言的逻辑问题,代码如下:
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//返回值:
//0,没有任何按键按下
//KEY0_PRES,KEY0按下
//KEY1_PRES,KEY1按下
//WKUP_PRES,WK_UP按下
//注意此函数有响应优先级,KEY0>KEY1>WK_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(10);//去抖动
key_up=0;
if(KEY0==0)
return KEY0_PRES;
else
if(KEY1==0)
return KEY1_PRES;
else
if(WK_UP==1)
return WKUP_PRES;
}else
if(KEY0==1&&KEY1==1&&WK_UP==0)
key_up=1;
return 0;// 无按键按下
}