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

2023-09-17 12:07:11

前言

1.之前做按键,在中断判断并进入回调函数,但是经常会导致其他任务来不及处理,或者是按键触发了但没有执行回调,即用户操作时感觉按键失灵。

2.这里更新了一下代码,思路是这样的:中断进入按键扫描,有消抖,不阻塞,如果按键事件触发时即入列,然后操作系统每隔10ms进行一次轮询,若队列不为空,则出列并执行按键回调。

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

key.c


#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

#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

#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

更多推荐

OpenMMLab AI 实战营笔记4——MMPreTrain算法库:构建高效、灵活、可扩展的深度学习模型

文章目录摘要一、工具箱介绍二、丰富的模型三、推理API四、环境搭建——OpenMMLab软件栈五、OpenMMLab重要概念——配置文件六、代码框架七、配置及运作方式经典主干网络残差网络VisonTransformer(VIT)注意力机制自监督学习常见类型SimCLRMAE自监督学习多模态CLIPBLIPOthers总

【ELFK】之zookeeper

本章主要内容:1、zookeeper简介2、zookeeper工作机制3、zookeeper数据结构4、应用场景5、选举机制6、非第一次启动选举机制7、zookeeper部署实验一、zookeeper简介zookeeper是一个开源的分布式的,为分布式框架提供协调服务的Apache项目。管理节点服务器,完成对节点的调用

LVS+Keepalived群集

1、keepalived概述及其功能·故障自动切换failover·实现lvs群集中节点健康检查·节点服务器的高可用性keepalived软件就是通过vrrp协议来实现高可用功能Keepalived是一个基于VRRP协议来实现的LVS服务高可用方案,可以解决静态路由出现的单点故障问题。在一个LVS服务集群中通常有主服务

YOLOv5算法改进(20)— 引入 RepVGG 重参数化模块

前言:Hello大家好,我是小哥谈。RepVGG重参数化模块是一种用于深度卷积神经网络的模块化设计方法,旨在通过将卷积层和全连接层统一为卷积层来简化网络结构并提高计算效率。该方法通过重参数化,将常规的卷积层分解为一个轻量级的卷积层和一个恒等映射层,从而达到降低计算复杂度的目的。本节课就简单介绍一下如何在YOLOv5中引

最新ChatGPT网站源码+支持GPT4.0+支持Midjourney绘画+支持国内全AI模型

一、智能创作系统SparkAi创作系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧!SparkAi程序使用Nestjs和Vu

【uni-app】uni-app内置组件和扩展组件

内置组件和扩展组件的关系先引用uni-app官网原文:uni-app是有内置组件的。这和web开发不一样。web开发基本上不用基础组件,都是找一个三方ui库,全套组件都包含。那是因为html的基础组件默认样式不适配手机风格。但uni-app体系不是这样,内置组件就是为手机优化的。但内置组件只能满足基础需求,更多场景,需

想要精通算法和SQL的成长之路 - 最长回文子序列

想要精通算法和SQL的成长之路-最长回文子序列前言一.最长回文子序列前言想要精通算法和SQL的成长之路-系列导航一.最长回文子序列原题链接首先,我们看下动态规划方程的定义,我们用dp[i][j]来代表:字符串s在下标区间为[i,j]之间的最长回文子序列。那么请问,最终的返回结果,就是我们要求得字符串s的最长回文子序列,

Day66|图part5:130. 被围绕的区域、827.最大人工岛

130.被围绕的区域leetcode链接:题目链接这题看起来很复杂,其实跟之前找飞地和找边缘地区的是差不多的,主要分三步:使用dfs将边缘的岛都找出来,然后用A代替防止混淆;再用dfs找中间不与任何岛相连的飞地;最后把之前的A替换成O。最终代码:classSolution{public:voiddfs(vector<v

ChatGPT 或其它 AI,能用在文书创作上吗?

新的申请季已经正式开始,一些热门项目的ED截止日期也不再遥远,因此很多准留学生们都已经开始了关于文书的创作。而随着科技的不断发展,以ChatGPT为首的一众AI工具也作为一种辅助手段愈发融入了我们的生活。那么不免就会有一些同学在准备申请时“动起了歪脑筋”,如果选择用ChatGPT来帮自己写文书的话,岂不是又省事又省力还

6.2 Sunday搜索内存特征

Sunday算法是一种字符串搜索算法,由DanielM.Sunday于1990年开发,该算法用于在较长的字符串中查找子字符串的位置。算法通过将要搜索的模式的字符与要搜索的字符串的字符进行比较,从模式的最左侧位置开始。如果发现不匹配,则算法将模式向右滑动一定数量的位置。这个数字是由当前文本中当前模式位置的最右侧字符确定的

Vue模板语法【下】事件处理器,表单、自定义组件、通信组件

目录一、事件处理器1.1常用的事件修饰符1.2常用的按键修饰符二,vue中的表单三、自定义组件四,通信组件一、事件处理器1.1常用的事件修饰符Vue的事件修饰符是用来改变事件的默认行为或者添加额外的功能。以下是一些常用的事件修饰符及其作用:.stop:阻止事件冒泡,相当于调用event.stopPropagation(

热文推荐