前言
1.之前做按键,在中断判断并进入回调函数,但是经常会导致其他任务来不及处理,或者是按键触发了但没有执行回调,即用户操作时感觉按键失灵。
2.这里更新了一下代码,思路是这样的:中断进入按键扫描,有消抖,不阻塞,如果按键事件触发时即入列,然后操作系统每隔10ms进行一次轮询,若队列不为空,则出列并执行按键回调。
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
key.c
cpp
#include "includes.h"
KEY_S key1;
KEY_S key2;
//按键平时为高电平,按下为低电平
static void Key_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK, ENABLE);
RCC_APB2PeriphClockCmd(KEY2_GPIO_CLK, ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = KEY1_GPIO_PIN;
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = KEY2_GPIO_PIN;
GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStruct);
}
void KeyOsInit(void)
{
Key_GPIO_Config();
key1.keyNum = keyNum1;
key1.state = keyNone;
key1.pfnKeyCallBack = key1TaskProc;
key2.keyNum = keyNum2;
key2.state = keyNone;
key2.pfnKeyCallBack = key2TaskProc;
}
static bool readKeyGpioLevel(u8 keyNum)
{
bool keyRes = KEY_OFF;
switch ( keyNum ){
case keyNum1:
keyRes = GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN);
break;
case keyNum2:
keyRes = GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN);
break;
default:
break;
}
return keyRes;
}
void keyScanLoop(KEY_S *keyx)
{
if ( keyx->state == keyLong ){
if (KEY_ON == readKeyGpioLevel(keyx->keyNum)){
stKeyData.keyNum = keyx->keyNum;
stKeyData.keyState = keyLong;
KeyEnQueue(&stKeyQueue,&stKeyData);
// keyx->pfnKeyCallBack(); //如果是执行这里的话,那么就是在中断进入回调了
return;
} else {
keyx->preState = keyx->state;
keyx->state = keyNone;
}
}
// 不在检测状态时按键按下
if ( 0 == keyx->bIsChecking ){
if (KEY_ON == readKeyGpioLevel(keyx->keyNum)){
keyx->readKeyLevel = KEY_ON;
keyx->bIsChecking = 1;
keyx->keyDownTimes = 0;
keyx->preState = keyx->state;
keyx->state = keyNone;
keyx->timedReadRes = 0;
keyx->checkInterval = checkIntervalMs;
keyx->shakeInterval = shakeIntervalMs;
}
} else {
// 按键周期倒计时
if ( keyx->checkInterval ){
keyx->checkInterval--;
// 定时读取
if ( keyx->timedReadInterval ){
keyx->timedReadInterval--;
} else {
keyx->timedReadInterval = timedReadIntervalMs;
if( KEY_ON == readKeyGpioLevel(keyx->keyNum) ){
keyx->timedReadRes++;
}
}
// 检测状态时按键按下
if ( KEY_ON == keyx->readKeyLevel ) {
// 按键软件消抖
if ( keyx->shakeInterval ){
keyx->shakeInterval--;
} else {
keyx->shakeInterval = shakeIntervalMs;
if (KEY_ON == readKeyGpioLevel(keyx->keyNum)){
keyx->keyDownTimes++;
// 读取的电平反转
keyx->readKeyLevel = KEY_OFF;
}
}
} else {
if (KEY_OFF == readKeyGpioLevel(keyx->keyNum)){
keyx->readKeyLevel = KEY_ON;
}
}
}
// 按键倒计时结束,通过按下次数和定时读取次数判断按键键值
else {
keyx->bIsChecking = 0;
switch (keyx->keyDownTimes){
case keyNone:
keyx->state = keyNone;
return;
case keyShort:
keyx->state = keyShort;
break;
case keyDouble:
case keyTriple: //按下三次也算双击
keyx->state = keyDouble;
break;
default :
keyx->state = keyLong;
break;
}
if ( keyx->timedReadRes > keyLongTimes ){ //可自定义读取次数
keyx->state = keyLong;
}
stKeyData.keyNum = keyx->keyNum;
stKeyData.keyState = keyx->state;
KeyEnQueue(&stKeyQueue,&stKeyData);
// keyx->pfnKeyCallBack(); //如果是执行这里的话,那么就是在中断进入回调了
}
}
}
void KeyTaskProc(void)
{
if(stKeyQueue.state != KeyQueueEmpty)
{
if(KeyDeQueue(&stKeyQueue) != KeyDataEmpty)
{
stKeyData = stKeyQueue.dataDeQueue;
switch(stKeyData.keyNum)
{
case keyNum1:
key1TaskProc();
break;
case keyNum2:
key2TaskProc();
break;
}
}
}
}
//定时器设置为1ms进一次中断
void TIM3_IRQHandler()
{
if ( TIM_GetITStatus( TIM3, TIM_IT_Update) != RESET )
{
keyScanLoop(&key1);
keyScanLoop(&key2);
TIM_ClearITPendingBit(TIM3 , TIM_FLAG_Update);
}
}
u32 key1Cnt=0;
u32 key2Cnt=0;
void key1TaskProc(void)
{
if(key1.state == keyShort){
key1Cnt++;
}
if(key1.state == keyLong){
if( key1.keyLongExeInterval ){
key1.keyLongExeInterval--;
} else {
key1.keyLongExeInterval = keyLongExeIntervalMs;
key1Cnt++;
}
}
}
void key2TaskProc(void)
{
if(key2.state == keyShort){
key2Cnt++;
}
if(key2.state == keyLong){
if( key2.keyLongExeInterval ){
key2.keyLongExeInterval--;
} else {
key2.keyLongExeInterval = keyLongExeIntervalMs;
key2Cnt++;
}
}
}
/**************************************** 按键队列处理 ****************************************/
KeyQueue_S stKeyQueue;
KeyQueueData_S stKeyData;
/********************************************
* @函数名 QueueInit
* @描述 队列初始化
* @参数 需要初始化的队列
* @返回值 无
* @注意 无
********************************************/
void KeyQueueInit(KeyQueue_S *queue)
{
memset(&queue, 0, sizeof(queue));
}
/********************************************
* @函数名 QueueStateDetermine
* @描述 检查队列当前状态
* @参数 需要检查的队列
* @返回值 队列当前状态
* @注意 无
********************************************/
KeyQueueState KeyQueueStateDetermine(KeyQueue_S *queue)
{
if(queue->size == 0)
{
return KeyQueueEmpty;
}
else if(queue->size == KeyQueueMaxSize)
{
return KeyQueueFull;
}
else
{
return KeyQueueNormal;
}
}
/********************************************
* @函数名 EnQueue
* @描述 入列
* @参数
queue 有入列需要的队列
data 需要入列的数据
* @返回值 无
* @注意 当队列满时无法入列
********************************************/
void KeyEnQueue(KeyQueue_S *queue, KeyQueueData_S *data)
{
if(queue->size == KeyQueueMaxSize)
{
return;
}
queue->dataEnQueue = *data;
queue->data[queue->rear] = queue->dataEnQueue;
queue->size++;
queue->rear++;
if(queue->rear == KeyQueueMaxSize)
{
queue->rear = 0;
}
queue->state = KeyQueueStateDetermine(queue);
}
/********************************************
* @函数名 DeQueue
* @描述 出列
* @参数 queue 有出列需要的队列
* @返回值 返回的数据是否为空
* @注意 实际出列的数据放在 queue->dataDeQueue
********************************************/
KeyDeQueueState KeyDeQueue(KeyQueue_S *queue)
{
if(queue->size == 0)
{
return KeyDataEmpty;
}
queue->dataDeQueue = queue->data[queue->front];
memset(&queue->data[queue->front], 0, sizeof(queue->data[queue->front]));
queue->size--;
queue->front++;
if(queue->front == KeyQueueMaxSize)
{
queue->front = 0;
}
queue->state = KeyQueueStateDetermine(queue);
return KeyDataNormal;
}
key.h
cpp
#ifndef __KEY_H
#define __KEY_H
#include "includes.h"
typedef enum {
keyNum1,
keyNum2,
keySum,
} keyOrder;
typedef enum {
keyNone = 0,
keyShort,
keyDouble,
keyTriple,
keyLong
} keyState;
typedef struct key_s {
keyOrder keyNum;
bool bIsChecking; //正在检测
bool bTaskProcessing;
bool readKeyLevel;
keyState preState; //上一个按键状态
keyState state;
u8 timedReadInterval; //定时读取的间隔
u8 keyDownTimes; //一个检测周期按下的次数
u16 timedReadRes; //定时读取,如果在一个检测周期按下的次数为1或2,而每隔n ms读取的低电平次数大于某个值,可认为是长按
u16 shakeInterval; //抖动多少时间后进行检测
u16 checkInterval; //整个检测的持续时间
u16 keyLongInterval; //长按后隔一段时间再检测,避免检测到短按
u16 keyLongExeInterval;
void (*pfnKeyCallBack)(void);
} KEY_S;
extern KEY_S key1;
extern KEY_S key2;
#define keyLongTimes 40
#define timedReadIntervalMs 10
#define shakeIntervalMs 10
#define checkIntervalMs 500
#define keyLongExeIntervalMs 10 //OS 10ms进一次,10次触发一次长按,故为100ms
#define KEY_ON 1 //按键平时为低电平,按下为高电平
#define KEY_OFF 0
#define KEY1_GPIO_PIN GPIO_Pin_0
#define KEY1_GPIO_PORT GPIOA
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOA
#define KEY2_GPIO_PIN GPIO_Pin_13
#define KEY2_GPIO_PORT GPIOC
#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOC
void KeyOsInit(void);
void keyScanLoop(KEY_S *keyx);
void KeyTaskProc(void);
void key1TaskProc(void);
void key2TaskProc(void);
/**************************************** 按键队列处理 ****************************************/
#define KeyQueueMaxSize 5 //队列最大存放的数据个数
typedef enum {
KeyQueueEmpty, //队列为空
KeyQueueNormal, //队列不为空
KeyQueueFull, //队列已满
} KeyQueueState; //队列当前状态
typedef enum {
KeyDataEmpty, //数据为空
KeyDataNormal, //数据不为空
} KeyDeQueueState; //出列的数据情况
typedef struct {
keyOrder keyNum;
keyState keyState;
} KeyQueueData_S; //队列存放的数据类型
// 定义队列结构
typedef struct {
KeyQueueData_S data[KeyQueueMaxSize]; //队列缓存
KeyQueueData_S dataEnQueue; //入列数据
KeyQueueData_S dataDeQueue; //出列数据
KeyQueueState state; //队列当前状态
u8 front; //队头
u8 rear; //队尾
u8 size; //队列大小
} KeyQueue_S;
void KeyQueueInit(KeyQueue_S *queue);
KeyQueueState KeyQueueStateDetermine(KeyQueue_S *queue);
void KeyEnQueue(KeyQueue_S *queue, KeyQueueData_S *data);
KeyDeQueueState KeyDeQueue(KeyQueue_S *queue);
extern KeyQueue_S stKeyQueue;
extern KeyQueueData_S stKeyData;
#endif
main.c
cpp
#include "includes.h"
int main(void)
{
TimerOsInit();
LED_Init();
UartInit();
AdcOsInit();
OS_TaskInit();
SysTickInit();
KeyOsInit();
OS_TaskCreat(Task1, UaetSendHeartBeat, 100);
OS_TaskCreat(Task2, KeyTaskProc, 10);
while (1){
AdcTask();
UartLoopTask();
testPro();
ledLoopTask();
OS_TaskRun();
}
}
演示效果
STM32单片机操作系统、按键与FIFO_哔哩哔哩_bilibili
STM32单片机操作系统、按键与FIFO