按键控制LED灯
我所使用的神舟3号开发板除了RESET按钮外,还有4个实体按钮。按照通用教程的习惯顺序,在学习LED跑马灯后一般是选择学习蜂鸣器的使用。但是蜂鸣器实在是太吵了(笑),且和LED相比难度甚至更低,这里就先做一个跳过。
现在让我们来通过开发板上的实体按钮来控制4个LED灯,任务是做到按住按钮可以使某个灯常亮。
硬件部分
首先我们看电路图
得到的对应关系如下:
而上一节里也提到了LED灯的电路关系,这里再重复一次:
代码部分
头文件及定义
定义其实与上一篇中的LED等地方基本相同,此处就不再赘述:
#include "stm32f10x.h"
#include "stm32_eval.h"
#include <stdio.h>
#define RCC_GPIO_LED RCC_APB2Periph_GPIOF
#define LEDn 4
#define GPIO_LED GPIOF
#define DS1_PIN GPIO_Pin_6
#define DS2_PIN GPIO_Pin_7
#define DS3_PIN GPIO_Pin_8
#define DS4_PIN GPIO_Pin_9
#define GPIO_LED_ALL DS1_PIN |DS2_PIN |DS3_PIN |DS4_PIN
#define RCC_KEY1 RCC_APB2Periph_GPIOD
#define GPIO_KEY1_PORT GPIOD
#define GPIO_KEY1 GPIO_Pin_3
#define RCC_KEY2 RCC_APB2Periph_GPIOA
#define GPIO_KEY2_PORT GPIOA
#define GPIO_KEY2 GPIO_Pin_8
#define RCC_KEY3 RCC_APB2Periph_GPIOC
#define GPIO_KEY3_PORT GPIOC
#define GPIO_KEY3 GPIO_Pin_13
#define RCC_KEY4 RCC_APB2Periph_GPIOA
#define GPIO_KEY4_PORT GPIOA
#define GPIO_KEY4 GPIO_Pin_0
#define GPIO_KEY_ANTI_TAMP GPIO_KEY3
#define GPIO_KEY_WEAK_UP GPIO_KEY4
/* Values magic to the Board keys */
#define NOKEY 0
#define KEY1 1
#define KEY2 2
#define KEY3 3
#define KEY4 4
功能函数模块
首先我们要写一个延时函数,这里的延时函数是不精准的,如果要做到精准模拟时间会涉及到CPU频率等问题,后面遇到了会再解释。
static void Delay(__IO uint32_t nCount)
{
for (; nCount != 0; nCount--);
}
这里的__IO
在嵌入式编程中非常常见,可以追一下代码:
#define __IO volatile
可以看到,这里__IO
的定义是C语言中的volatile,volatile的作用简而言之就是精确编译,禁止使用优化,因为编辑器往往会因为追求效率等原因,从缓存栈中直接读取参数,但是设置了volatile后每一次遇到都会直接从内存中读取,保证了编译中的精确度。
接着我们来对按键进行初始化操作,由于按键按下后是输出低电平,所以我们需要给IO口默认设置为高电平,这下按下按键后才能有电平的反馈。这里我们设置为上拉输出GPIO_Mode_IPU
,就可以有这种效果:
void GPIO_KEY_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure KEY1 Button */
RCC_APB2PeriphClockCmd(RCC_KEY1, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_KEY1;
GPIO_Init(GPIO_KEY1_PORT, &GPIO_InitStructure);
/* Configure KEY2 Button */
RCC_APB2PeriphClockCmd(RCC_KEY2, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_KEY2;
GPIO_Init(GPIO_KEY2_PORT, &GPIO_InitStructure);
/* Configure KEY3 Button */
RCC_APB2PeriphClockCmd(RCC_KEY3, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_KEY3;
GPIO_Init(GPIO_KEY3_PORT, &GPIO_InitStructure);
/* Configure KEY4 Button */
RCC_APB2PeriphClockCmd(RCC_KEY4, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_KEY4;
GPIO_Init(GPIO_KEY4_PORT, &GPIO_InitStructure);
}
这里我们可以观察到,GPIO_InitStructure
实际上被使用了4次。而我们后续的代码中实际使用的都是初始化完毕的GPIO_KEYx_PORT
,对于GPIO_InitStructure
只是为了端口的初始化起到一个传参的作用。
接下来我们看按键的监听与读取:
u8 ReadKeyDown(void)
{
/* 1 key is pressed */
if(!GPIO_ReadInputDataBit(GPIO_KEY1_PORT, GPIO_KEY1))
{
return KEY1;
}
/* 2 key is pressed */
if(!GPIO_ReadInputDataBit(GPIO_KEY2_PORT, GPIO_KEY2))
{
return KEY2;
}
/* 3 key is pressed */
if(!GPIO_ReadInputDataBit(GPIO_KEY3_PORT, GPIO_KEY3))
{
return KEY3;
}
/* 4 key is pressed */
if(!GPIO_ReadInputDataBit(GPIO_KEY4_PORT, GPIO_KEY4))
{
return KEY4;
}
/* No key is pressed */
else
{
return NOKEY;
}
}
可以注意到这个函数的返回值是一个u8类型的,如果读取到某个按键按下,将会返回对应的KEY值。当中值得注意的是GPIO_ReadInputDataBit( )
这个函数,这里这个函数的用法是读取是否有电平输入,当你设置KEY为上拉输入,去读IO口状态的时候,若你没有按下按键,你读出来的IO值是1,(因为上拉输入把IO口拉高),当你按下按键的时候,你读出来的IO值是0,(因为按下按键把IO拉低) 。设置为下拉输入时结果相反。如果对于此处有不理解的话,我们可以跟进一下这个函数:
typedef enum
{ Bit_RESET = 0,
Bit_SET //枚举类型中,如果本行没设置参数,那么默认为上一行的值+1
}BitAction;
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
uint8_t bitstatus = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GET_GPIO_PIN(GPIO_Pin));
if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET)
{
bitstatus = (uint8_t)Bit_SET;
}
else
{
bitstatus = (uint8_t)Bit_RESET;
}
return bitstatus;
}
assert_param()
是用于检查数据类型的函数。结合上面的代码,如果采用上拉输出,那么在不按按键的情况下,输入状态寄存器IDR应该有一个输入状态,同时GPIO_Pin也保持高电平,所以根据逻辑运算,bitstatus
将被置1。而在按下按键以后,不满足上述逻辑表达式,bitstatus
将被置0。
可以总结出GPIO_ReadInputDataBit( )
这个函数实际上逻辑可以简化为读取这个IO口的电位,如果是高电平则返回1,低电平则返回0。非常好记。
而在按键读取状态的代码中,由于按键是输出低电平的,所以需要加!
进行取反,这样读取到按键按下以后,逻辑表达式就会取true而读取到相应按键。
LED灯的相关设置如下:
void LED_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_GPIO_LED | RCC_APB2Periph_AFIO , ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_LED_ALL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIO_LED, &GPIO_InitStructure);
}
void Led_Turn_on_all(void)
{
GPIO_ResetBits(GPIO_LED, GPIO_LED_ALL);
}
void Led_Turn_on_1(void)
{
GPIO_ResetBits(GPIO_LED, DS1_PIN);
}
void Led_Turn_on_2(void)
{
GPIO_ResetBits(GPIO_LED, DS2_PIN );
}
void Led_Turn_on_3(void)
{
GPIO_ResetBits(GPIO_LED, DS3_PIN);
}
void Led_Turn_on_4(void)
{
GPIO_ResetBits(GPIO_LED, DS4_PIN);
}
void Led_Turn_off_all(void)
{
GPIO_SetBits(GPIO_LED, GPIO_LED_ALL);
}
void Led_Turn_on(u8 led)
{
Led_Turn_off_all();
switch(led)
{
case 0:
Led_Turn_on_1();
break;
case 1:
Led_Turn_on_2();
break;
case 2:
Led_Turn_on_3();
break;
case 3:
Led_Turn_on_4();
break;
default:
Led_Turn_off_all();
break;
}
}
主函数
搞定上面几个功能函数后,主函数的逻辑就非常简单了:
int main(void)
{
u8 KeyNum = 0;
LED_config(); //启动LED灯初始化
Led_Turn_on_all(); //全部开启
Delay(6000000); //先让灯亮一会
Led_Turn_off_all(); //全部关闭
GPIO_KEY_Config(); //按键初始化
while (1)
{
KeyNum = ReadKeyDown(); //读取按键
Led_Turn_on(KeyNum-1); //点亮相应的灯
}
}