单片机操作系统,按键与FIFO

前言

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

相关推荐
scan16 小时前
单片机串口接收状态机STM32
stm32·单片机·串口·51·串口接收
Qingniu016 小时前
【青牛科技】应用方案 | RTC实时时钟芯片D8563和D1302
科技·单片机·嵌入式硬件·实时音视频·安防·工控·储能
深圳市青牛科技实业有限公司8 小时前
【青牛科技】应用方案|D2587A高压大电流DC-DC
人工智能·科技·单片机·嵌入式硬件·机器人·安防监控
Mr.谢尔比9 小时前
电赛入门之软件stm32keil+cubemx
stm32·单片机·嵌入式硬件·mcu·信息与通信·信号处理
LightningJie9 小时前
STM32中ARR(自动重装寄存器)为什么要减1
stm32·单片机·嵌入式硬件
西瓜籽@9 小时前
STM32——毕设基于单片机的多功能节能窗控制系统
stm32·单片机·课程设计
远翔调光芯片^1382879887212 小时前
远翔升压恒流芯片FP7209X与FP7209M什么区别?做以下应用市场摄影补光灯、便携灯、智能家居(调光)市场、太阳能、车灯、洗墙灯、舞台灯必看!
科技·单片机·智能家居·能源
极客小张12 小时前
基于STM32的智能充电桩:集成RTOS、MQTT与SQLite的先进管理系统设计思路
stm32·单片机·嵌入式硬件·mqtt·sqlite·毕业设计·智能充电桩
m0_7393128715 小时前
【STM32】项目实战——OV7725/OV2604摄像头颜色识别检测(开源)
stm32·单片机·嵌入式硬件
嵌入式小章15 小时前
基于STM32的实时时钟(RTC)教学
stm32·嵌入式硬件·实时音视频