【备赛蓝桥杯嵌入式(G431-HAL库)】Led 与按键
实训平台:STM32G431RBT6
辅助工具:STM32CubeMx
01 基本简介
Led和按键是GPIO口输入和输出的应用。
Led考察GPIO口的输出,按键则考察GPIO口电平的读取,即输入。
需要使用HAL库里的读写函数,如下:
//HAL_GPIO_WritePin(GPIO口,引脚,引脚状态)
//HAL_GPIO_ReadPin(GPIO口,引脚)
//引脚状态如下:GPIO_PIN_RESET【让引脚输出低电平】
// GPIO_PIN_SET【让引脚输出高电平】
02 LED
2.1 电路介绍
① 锁存器:简单理解成一个开关,需要开启才能进行之后的操作。更详细知识百度。
(2023年2月更正)理解成开关加存储器的结合体更为贴切,当锁存器使能时,可以用于数据交换;交换后的数据就被关起来了,暂时无法更改,直到下一次锁存器使能进行数据交换。
② 由图可知,LED拥有公共的VDD,而为了让二极管导通,PCx引脚需要输入低电平(即0)
③由锁存器的知识可知,PD2引脚需要是有效电平,才能让PCx引脚工作,如图可得,PD2需要输入高电平(即1)
2.2 代码参考
2.2.0 CubeMx配置
- RCC时间配置
-
时钟树配置
频率是80MHz,系数为2 20 2,选择PLLCLK为时钟源即可,如图
- LED引脚配置
其余的默认即可
2.2.1 基本操作
(以GPIO_PIN_8,即LD1为例)
/* 点亮 */
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
/* 熄灭 */
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);
/* 翻转 */
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8);
也可以直接对其寄存器GPIOC->ODR
进行操作,但可能用到位操作,理解和实际操作更容易出错
/* GPIO_PIN_8 地址为0x0100 */
/* 想点亮第几个灯就位移几位,i需要从0开始,上限为7 */
GPIOC->ODR = ~(0x0100 << i);
GPIOC->ODR = (0x0100 << i);
2.2.2 其他操作
-
初始化
//GPIOC->ODR = 0xff00; //直接的对寄存器操作 HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_RESET);
-
LED状态附加
/* 假如需要让LD1每隔0.5s闪烁一次,LD2每隔1s闪烁一次 */ void Led_Disp(uint8_t ucLed){ GPIOC->ODR = ~(ucLed << 8); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_SET); HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_RESET); } /* time 用于计算时间,单位是ms */ void LedFunc(){ if(time == 500) ucLed ^= (1<<0); if(time == 1000) ucLed ^= (1<<1); Led_Disp(ucLed); } /* 格式: ucLed ^= (1 << n); n为LD的编号,从0开始 */
03 按键
3.1电路介绍
如图所示,可以看到按键输入端需要输入低电平,才能让按键电路导通。
并且电路图里每个按键都配有10k的电阻,即上拉电阻
3.2 消抖
为什么要消除抖动:
为了让按键能实现按下一次就一个按键响应。
3.3 代码参考
3.3.0 Cube配置
- 引脚配置
3.3.1 普通版
/* 按键功能:按下B1,让LD7状态翻转 */
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == 0){
HAL_Delay(10);
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == 0){
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_14);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
}
思路:延时段时间(尽可能小)后再判断按键是否按下
缺点:延时采用了阻塞模式,容易造成按键按下一次实际产生两次的效果或按下后较长时间才反应过来
3.3.2 状态机
一般和定时器结合。
详细了解:状态机_百度百科 (baidu.com)
//宏定义 读取各个按键键值
#define B1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define B2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define B3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define B4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
//定义按键状态:0-未按下,1-已经按下,2-持续按下
#define KEY_STATE_0 0
#define KEY_STATE_1 1
#define KEY_STATE_2 2
//按键扫描函数
uint8_t key_scanf(void){
uint8_t key_return = 0;
static uint8_t key_state;
// if(B1 && B2 && B3 && B4) key_state = KEY_STATE_0;
switch(key_state){
case 0:
if(B1 && B2 && B3 && B4){ //所有按键都未按下,保持状态0
key_state = KEY_STATE_0;
}
else{ //有按键按下
key_state = KEY_STATE_1;
}
break;
case 1:
if(B1 && B2 && B3 && B4){ //用于软件消抖
key_state = KEY_STATE_0;
}
else{ //判断键值
if(!B1) key_return = 1;
else if(!B2) key_return = 2;
else if(!B3) key_return = 3;
else if(!B4) key_return = 4;
key_state = KEY_STATE_2;
}
break;
case 2:
if(B1 && B2 && B3 && B4){
key_state = KEY_STATE_0;
}
break;
}
return key_return;
}
void key_switch(){
uint8_t key_val;
/* 对该值需要有限定,即只能取相应的键值编码
随意取值可能造成按键功能混乱
尽量要有初始值
*/
key_val = key_scanf();//一般10ms读取一次
/*
设置flag,如下:
if(key_flag){
key_flag = 0;
key_val = key_scanf();
}
*/
if(key_val == 1){
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_14);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
else if(key_val == 2){
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_15);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
else if(key_val == 3){
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
else if(key_val == 4){
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_9);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
}
3.3.3 长短按
当一个按键有长按和短按两个状态时,按键的短按不会影响到长按。
//宏定义 读取各个按键键值
#define B1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define B2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define B3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define B4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
//定义按键状态:0-未按下,1-已经按下,2-持续按下
#define KEY_STATE_0 0
#define KEY_STATE_1 1
#define KEY_STATE_2 2
uint8_t keyOld = 0,keyLong = 0;
uint16_t keyTime = 0;
extern unsigned int ulTick_ms;//用滴答计时器计时
extern uint8_t keyFlag;
//按键扫描函数
uint8_t key_scanf(void){
uint8_t key_return = 0;
static uint8_t key_state;
switch(key_state){
case 0:
if(B1 && B2 && B3 && B4){ //所有按键都未按下,保持状态0
key_state = KEY_STATE_0;
}
else{ //有按键按下
key_state = KEY_STATE_1;
}
break;
case 1:
if(B1 && B2 && B3 && B4){ //用于软件消抖
key_state = KEY_STATE_0;
}
else{ //判断键值
if(!B1) keyOld = 1;//暂时不赋返回值
else if(!B2) keyOld = 2;
else if(!B3) keyOld = 3;
else if(!B4) keyOld = 4;
keyTime = ulTick_ms;
key_state = KEY_STATE_2;
}
break;
case 2:
if(B1 && B2 && B3 && B4){
key_state = KEY_STATE_0;
if(ulTick_ms - keyTime > 800){
keyLong = 1;
ulTick_ms = 0;
keyTime = 0;
}
key_return = keyOld;//长短按已经判断完毕,赋值
}
break;
}
return key_return;
}
void key_switch(){
uint8_t key_val;
if(keyFlag){ keyFlag = 0;key_val = key_scanf();}
if(keyLong == 1){
if(key_val == 1){
keyLong = 0;
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_10);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
}
else{
//短按
if(key_val == 1){
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_14);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
}
}