IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 百问网七天物联网智能家居第5篇 -> 正文阅读

[嵌入式]百问网七天物联网智能家居第5篇

1.?环形队列

? ? ? ?队列就是一种先入先出的数据结构。实际应用中,譬如按键检测,把按键信息存入环形队列,然后,在循环中再读出进行处理。环形队列的元素,不一定是简单的字节数据,也可以是抽象的结构体数据,譬如按键的事件信息结构体,里面包含按键的ID,键值等等。

? ? ? ?结合到动态分配,可以将队列的长度进行灵活的设置。环形队列的结构体如下:

typedef struct {
	uint8_t   * fifo;				// 环形fifot头地址
	uint16_t	size;				// 环形fifo大小
	uint16_t	pWrite;				// 写位置
	uint16_t	pRead;				// 读位置
}ringBuffer,* pRingBuffer;

抽象出这个结构体目的为了指定操作对象,本质是对里面的元素fifo指向的数组进行操作,pWrite指向待写入的数据位置,pRead指向待读出的数据位置。这里需要注意,pWrite指向的地方,还没有数据写入,实际操作过程中,如果队列有空间,则先写入数据,再把pWrite位置后移。pRead指向的位置,已经写入了数据,读出该有效数据之后,再把pRead位置后移。故读和写,都是先操作数据,然后再操作位置。

队列为空的条件为?读和写的位置相同。队列为满的条件,是写的位置处于读的位置后面,及(pWrite+1)%队列长度 ==?pRead ,?这里需要说明,为了避免读写位置相等时候,是队列满还是空的歧义,才指定上述的队列满的条件。以此指导,队列的大小如果为N,则队列里面最大只能够储存N-1个数据。相当于牺牲了一个存储空间。环形队列的初始化函数如下:

int driver_ringFIFO_Init(pRingBuffer pBuffer, uint16_t size)
{
	if((pBuffer==NULL)||(size==0))	return -1;
	pBuffer->size = size;
	pBuffer->fifo = (uint8_t *)malloc(pBuffer->size);		// 包含于stdlib.h
	pBuffer->pWrite = 0;
	pBuffer->pRead  = 0;
	return 0;
}

写入1个字节的函数:

int driver_ringFIFO_writeByte(pRingBuffer pBuffer, uint8_t  dat)
{
	if((pBuffer==NULL)||(pBuffer->fifo==NULL)) 	return -1;	
	if((pBuffer->pWrite+1)%pBuffer->size == pBuffer->pRead)
	{	
		return -2;		// fifo满了,无法写入
	}
	else
	{
		pBuffer->fifo[pBuffer->pWrite] = dat;
		pBuffer->pWrite = (pBuffer->pWrite + 1)%pBuffer->size;	// 移动到写一个待写入位置
	}
	return 0;
}

读出1个字节的函数:

int driver_ringFIFO_readByte(pRingBuffer pBuffer, uint8_t * dat)
{
	if((pBuffer==NULL)||(pBuffer->fifo==NULL)||(dat==NULL)) 	return -1;	
	if(pBuffer->pWrite == pBuffer->pRead)
	{	
		return -2;		// fifo空了,无法读出数据
	}
	else
	{
		*dat = (pBuffer->fifo[pBuffer->pRead]);
		pBuffer->pRead = (pBuffer->pRead + 1)%pBuffer->size;  // 移动到写一个待读出位置
	}
	return 0;
}

读或者写多个字节的数据,就是调用上述读写1个字节的函数,不再赘述。

2.?按键检测

不同于以前的软件延时,这次视频中的方法配合了?外部中断和定时器中断的方法。当按键按下或者弹起的时候,会有多次的整栋,如果此时按键对应的IO开启了上升沿和下降沿触发的中断,会触发多次中断。开一个变量记录触发时间,每次中断的时候,就更新触发时间,触发时间往后加50ms(也可以是其它值)相当于按键检测判断的时刻,在定时器中断中,不断的检测时间,如果时间等于按键检测判断时间,则读按键的电平状态,如果是0,则是按下,记录按下时刻。如果是1,则是弹起,记录弹起时刻。这两个时间都不为0,则发生了一次按键按下弹起的事件。故可以抽象一个按键事件的结构体:

typedef struct _keyEvent
{
	uint16_t	ID;			// 按键ID
	uint16_t	time;		// 按键持续时间	
}KeyEvent,*pKeyEvent;

其实按键结构体可以根据实际,有更加复杂的元素。譬如可以加入按键处理的回调函数,如加入函数指针。当然也可以定义一个虚函数,作为有按键事件的处理函数。

结合上面的环形队列,可以定义一个按键事件的对象:

static	ringBuffer		keyEventFIFO={0};			// KeyEventFIFO对象

大小定义可以保存16个按键事件的结构体

	driver_ringFIFO_Init(&keyEventFIFO, sizeof(KeyEvent)<<4);	// 16个 KeyEvent结构体的大小

这里需要说明,实际中无法正好同时保存16个按键事件,原因是虽然开辟了16*4 = 64个字节的空间,但是只能够保存63个数据,队列就满了。当有按键事件时候,写入环形队列:

void driver_key_tickScan(void)
{
	KeyEvent			keyPressEvent = {0};
	static 	uint32_t 	keyDownTick = 0;		// 按键按下的tick
	static	uint32_t	keyUpTick = 0;			// 按键松开的tick
			uint32_t	temp = 0;
			temp = HAL_GetTick();
	if(keyTriggerTick==temp)
	{
		if(0==HAL_GPIO_ReadPin(KEY_GPIO_PORT,KEY_GPIO_PIN))		// 按键按下
		{
			keyDownTick = temp;
		}
		else													// 按键松开
		{
			keyUpTick = temp;				
		}
		if((keyUpTick!=0)&&(keyDownTick!=0))
		{
			keyPressEvent.ID = 'A';								// 按键ID
			keyPressEvent.time = keyUpTick - keyDownTick;
			keyUpTick = keyDownTick = 0;						// 清零,方便下次使用	
			driver_ringFIFO_writeNBytes(&keyEventFIFO, (uint8_t *)&keyPressEvent.ID, sizeof(keyPressEvent));			
		}
	}
}

该按键扫描函数,可以放入1ms的定时器中断中,如systick中断中。上面写入环形队列的函数中,传入的是结构体第一个元素的地址,也可以是结构体的地址,每次大小就是结构体的大小。

大循环中执行的函数如下:

/* 放到大循转之中,按键处理函数  */
void driver_key_exe(void)
{
	KeyEvent	keyPressEvent = {0};
	// 1. 按键队列中取出按键事件
	driver_ringFIFO_readNBytes(&keyEventFIFO, (uint8_t *)&keyPressEvent, sizeof(keyPressEvent));
	// 2. 执行按键处理函数 
	driver_key_Callback(&keyPressEvent);
}

这里看到读出函数是将环形队列中的数据,整体读出到结构体中,这样非常方便。最后调用按键处理函数,并将刚才读出的按键信息传入。该回调函数定义为虚函数,方便用户自己实现具体内容:

/* 按键处理函数 */
__weak void driver_key_Callback(KeyEvent * pKeyEvent)
{
  UNUSED(pKeyEvent);
}

这样子,调用该按键的方式就很简单了,首先定义一个?按键事件环形队列,然后将driver_key_exe()放入大循环中,最后自己重定义按键处理的回调函数,如本例

/* 按键回调函数 */
void driver_key_Callback(KeyEvent * pKeyEvent)
{
	uint16_t i = 0;
	if(pKeyEvent->ID=='A')
	{
		driver_led_writeStatus(LED_TOGGLE);
		printf("ID = %c , time = 0x%x\r\n",pKeyEvent->ID,pKeyEvent->time);
	}
}

如果是按键‘A’,则小灯电平反转,并输出按键的ID和time信息。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 19:22:30  更:2022-08-19 19:25:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 13:25:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码