【智能家居项目】FreeRTOS版本——将裸机程序改造成FreeRTOS程序 | DHT11温湿度传感器

news2025/1/17 19:05:02

🐱作者:一只大喵咪1201
🐱专栏:《智能家居项目》
🔥格言:你只管努力,剩下的交给时间!
图

图
如上图所示是裸机版本的智能家居项目总体框架结构,这篇文章开始,本喵要带着大家逐渐将智能家居项目从逻辑版本改为FreeRTOS版本,并且要增加温湿度显示和网络时间获取两个功能。

目录

  • 😸裸机程序的不足
  • 😸合并代码
  • 😸改造
    • 😹思路
    • 😹将环形缓冲区改为队列
    • 😹队列调试
  • 😸DHT11温湿度传感器
    • 😹时序及驱动层代码
    • 😹其他层代码
  • 😸总结

😸裸机程序的不足

int main()
{
	while(1)
	{
		if(按键按下)
		{
			点亮LED灯();
		}
		
		if(网络输入)
		{
			OLED显示();
			LED控制();
			风扇控制();
			...
		}
	}
}

上面就是本喵前面用裸机方式实现智能家居本质逻辑的伪代码,在while(1)不停检测是否有输入事件产生,如果有则判断是按键输入还是网络输入,不同类型的输入事件对应不同的处理方式。

按键输入时,点亮LED灯的逻辑很简单,所以执行的非常快,当网络输入时,OLED显示等处理则比较复杂,执行速度相对较慢。

此时程序是顺序执行的,如果在处理网络输入时,又产生了按键输入,此时CPU并不会立刻去处理按键,而是需要等处理完网络输入后再去处理按键,这样一来就会感觉整个系统比较卡顿,延时比较高。

  • 缺点非常明显:不同任务模块之间相互影响。

int main()
{
	while(1)
	{
		检测输入事件();	
	}
}

void USART3_IRQHandler()
{
	构造输入事件();
}

如上面伪代码,在裸机版本智能家居中,输入事件是在中断函数中构造的,尤其是在处理ESP8266的串口3中断函数中,构造事件比较复杂。

当在中断函数中构造网络输入事件时,后台程序(main函数)是处于中断状态不执行的,如果此时按键输入发生了,后台程序也无法处理按键,只有构造完网络数据从中断函数中退出才能处理按键。

  • 缺点非常明显:中断函数执行时间太长,就会导致:更低优先级的中断无法得到及时处理,后台程序无法及时执行。

用软件定时器解决:

创建一个硬件定时器(TIMx),依据该定时器设计软件定时器,软件定时器:

  • 软件定时器周期性触发。
  • 每个软件定时器都有一个回调函数。

图
如上图所示实现了两个软件定时器:

  • 软件定时器1:每4个tick,执行一次f1()
  • 软件定时器2:每2个tick,执行一次f2()

如果f1()f2()执行时间很短,不超过1个tick时,效果很好。一旦f1()f2()执行的时间比较长,就会出问题。

比如f2()需要执行3个tick,当它执行到第二个tick的时候,下一次f2()该执行了,但是当前的f2()还没有结束,所以下一次的f2()就得不到执行。

  • 缺点:周期性执行的函数执行时间不能太长。

总的来说,裸机程序的主要缺点就是实时性不够高。

😸合并代码

在文章CubeMX对FreeRTOS的适配中本喵介绍了如何使用CubeMX创建FreeRTOS工程,此时打开前面裸机版本智能家居项目的CubeMX工程:

图
如上图,然后选择FREERTOS并且选择CMSIS_V2接口,然后生成代码:

图
如上图红色框,此时原本的裸机项目工程中就有了FreeRTOS了,文件多了一个Middlewares/FreeRTOS,代码中多了内核以及FreeRTOS初始化部分代码。

在FreeRTOS执行之前,调用SmartHomeTask函数,此时仍然没有使用到FreeRTOS,并且效果和裸机版本一样。

此时就实现了将裸机版本代码合并到FreeRTOS中,只是还没有使用任何和RTOS有关的东西。


接下来创建一个任务用来执行SmartHomeTask函数:

tu

如上图,使用xTaskCreate创建一个新任务来执行智能家居函数SmartHome,此时就将原本的裸机程序简单地变成了一个FreeRTOS程序了,执行效果和之前的裸机程序一样。

😸改造

😹思路

完全没有必要重新使用FreeRTOS实现一遍智能家居项目,只需要在裸机版本的基础上进行改造即可。

图
如上图所示,左边黑色框是智能家居系统的输入,中间红色是业务子系统,业务子系统任务和输入方通过输入队列来通信,右边黑色框是输出。

输入:

  • 按键输入:使用中断来处理,把按键输入事件写入"输入队列"。
    • 按键输入执行快,耗时短,不会影响其他任务的执行,所以直接操作即可。
  • 网络输入:使用网络任务来处理,把网络输入写入到“输入队列”。
    • 微信小程序发来的控制命令,解析等操作耗时长,裸机方式会影响其他任务的实时性。
  • 温湿度输入:使用温湿度任务来处理,把温湿度结果写入"输入队列"。
    • 温湿度的获取比较耗时,裸机方式会影响其他任务。
  • 时间输入:使用时钟任务来处理,把获取的时间结果写入"输入队列"。
    • 从互联网获取时间比较耗时,裸机方式会影响其他任务。

业务子系统:

  • 从"输入队列"获取按键输入、微信控制信息,从而直接控制设备。
  • 从"输入队列"获取温湿度、时间,发送给OLED任务显示内容。

为了保证整个业务子系统能够实时处理输入队列中的数据,毫无疑问,业务子系统也是一个任务。

输出:

  • LDE灯和风扇:直接控制外设来控制设备。
    • 只需要改变相关引脚的电平状态,执行时间很短,不会对其他任务造成影响。
  • OLED任务:使用OLED任务来处理。
    • OLED上要显示的内容很多,有IP地址、温湿度、时间
    • 需要一个统筹管理的OLED任务:它负责操作OLED
    • 其他任务只能向OLED任务发出显示请求

以上就整个项目的改造思路。

😹将环形缓冲区改为队列

裸机版本的智能家居中,输入事件会被放入一个环形缓冲区中,业务子系统从这个环形缓冲区中读取事件并作出判断。

现在将原本的环形缓冲区改成队列,用来存放输入事件:

图
如上图,将input_buffer.c中原本的环形缓冲区改成一个队列g_xQueueInput,并且增加一个InitInputQueue函数用来初始化输入队列,在其内部调用xQueueCreate创建队列,创建成功返回0,失败返回-1并打印错误信息。


图
如上图所示,在写入输入事件的函数PutInputEvent中,原本是将输入事件写入到环形缓冲区中,此时改成使用xQueueSendFromISR写入到输入队列中。

按键输入:

图
如上图代码,当按键中断发生以后,会触发定时器消抖,定时器超时后会调用回调函数使用PutInputEvent将输入事件放入到输入队列中。

  • 输入事件是在中断中放入输入队列的。

网络输入:

tu
如上图代码所示,当网络数据到来时,会产生串口3中断,在中断函数中调用回调函数,回调函数中再调用PutInputEvent将输入事件放入到输入队列中。

  • 输入事件也是在中断中放入队列的。

可以看到,站在输入队列的角度来看,输入事件都来自中断,所以在PutInputEvent函数中使用xQueueSendFromISR将输入事件放入到输入队列中。


图
如上图代码,SmartHomeTask是一个普通任务,在该任务中调用GetInputEvent来读取输入队列中的输入事件,所以在该函数中使用的就是xQueueReceive来读取。

😹队列调试

此时虽然改造完毕了,编译也没有报错,但是烧录到板子里并不能正常运行,本喵带着大家来调试一下:

tu
如上图,在FreeRTOSConfig.h中改造一下宏函数configASSERT,在里面加一个printf函数打印代码信息,再将程序烧录开发板中。

图

如上图,使用sscom工具当作服务端和开发板建立UDP连接,然后发送控制指令。

图
如上图,此时在串口助手中将会看到configASSERT中打印的代码信息,然后程序死机,也没有产生预想的动作。

图
如上图,定位到configASSERT打印的代码信息处,发现这里调用了configASSERT,条件是ucCurrentPriority >= ucMaxSysCallPriority

  • ucCurrentPriority是当前中断的优先级。
  • ucMaxSysCallPriority,是允许使用系统调用中断的最高优先级。

既然程序死机了,说明执行了configASSERT陷入了for(;;)循环中,进而说明条件不满足,也就是当前中断的优先级在编号上小于允许使用系统调用中断的优先级编号。

  • 当前中断的优先级高于允许使用系统调用中断的优先级,这是不被允许的,所以死机。

图
如上图所示,在调用xQueueGenericSendFromISR向队列中写数据时,会调用portASSERT_IF_INTERRUPT_PRIORITY_INVALID,这是一个宏函数,会调用vPortValidateInterruptPriority,在该函数内会调用configASSERT来判断当前中断优先级和允许使用系统调用优先级的关系。

图
如上图,当开发板收到网络数据后会产生串口3中断,然后在中断函数中调用回调函数来将输入事件写入到队列中,所以此时当前中断就是串口3中断。

  • 串口3中断的优先级高于允许使用系统调用的中断优先级,所以不被允许,程序死机。

图
如上图,将串口3中的优先级改成14,仅比Tick中断15高一级,但是比允许使用系统调用的中断优先级低,此时就允许在串口3中断函数中使用xQueueSnendFormISR向队列中写数据了。

此时程序就可以正常运行了,configASSERT中的打印信息也不再打印,程序也不会死机,而且会产生预想的动作。

  • 凡是会在中断中使用系统调用的,都必须把它的优先级设置成低于允许使用系统调用的中断优先级。

😸DHT11温湿度传感器

图
如上图是DHT11的接线图,只需要三根线,VDD,GND,以及DATA,传感器和MCU共用一根数据线来传送数据。

图
如上图,本喵的开发板用PF6与DHT11的DATA相连。

😹时序及驱动层代码

初始化:
图
如上图所示通讯过程示意图,用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后DHT11发送响应信号。

然后DHT11发送出40bit的数据,用户可选择读取部分数据.从模式下DHT11接收到开始信号触发一次温湿度采集如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集,采集数据后转换到低速模式。

这个过程中:

  • 主机发送开始信号后将DATA线拉高,释放总线控制权。
  • DHT11拉低总线发出响应信号后,主动拉高总线准备输出数据。
  • DHT11开始发送40bit(5 bytes)数据。
  • 从机释放总线,等待下一次开始。

对于MCU而言:

  • 主机GPIO输出低电平,然后拉高,转为输入模式。
  • 主机读取GPIO电平,读到低电平之后再等待读取到高电平,确认收到DHT11的响应。
  • 主机确认得到响应后,通过读取到GPIO高低电平的时长,来判断收到的是逻辑1还是逻辑0,来组成真正的数据。
  • 主机连续读取到40个bit之后,转为输出模式,准备为下一次读取数据发出开始信号。

可以看到,主机的GPIO存在输入模式和输出模式的转换,所以该IO口需要设置成开漏输出模式,当将IO电平拉高后就成为了输入模式。


图
如上图所示,总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。

DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号。

主机发送开始信号结束后,将总线拉高保持20-40us,释放总线控制权,然后读取DHT11的响应信号。

总线为低电平,说明 DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据。

  • 这里的延时是微妙级别的,而TICK中断一次的时间是1ms,所以需要配置一个定时器进行微妙级别的延时。

图
如上图,使用CubeMX配置定时器3作为微秒级别的定时器,分频系数是72,此时定时器的频率为72M/72=1MHZCNT计数值增加一次,用时1us。

图
如上图,在driver_dht11.c中定义DHT11_TIM_Init函数来初始微秒定时器,并且定义微秒延时函数DHT11_usDelay,传入的us值是多少,就延时多长时间。

图
如上图,定义HDT11_Init函数来初始化DHT11引脚,设置为开漏输出模式,同时初始化微秒定时器。

图
如上图,在driver_dht11.h中定义宏DHT11_H用来拉高总线,DHT11_L用来拉低总线,DHT11_IN用来读取总线上的电平。

图
如上图,定义DHT11_Start来发出起始信号,主机将总线拉低20ms,大于18ms

图
如上图所示,主机按照该流程图进行操作,操作完毕后就可以开始读取DHT11发送来的数据了。

图
如上图代码所示,主机发出起始信号后,将总线拉高保持40us释放总线控制权,然后读取总线电平,读取到低电平说明从机开始应答,然后进入第一个while不断读取总线电平,当电平变为高以后说明从机结束应答。

再进入第二个while,当电平变成低以后说明这是从机开始发送一个数据,接下来就是判断该bit的数据是0还是1。

读数据:

图
如上图,当DHT11开始控制总线后,每一bit数据都以 50us低电平时隙开始,高电平的长短定了数据位是0还是1。

  • 50us低电平过后,高电平保持的时间在26us~28us之间表示该bit是低电平。

图

  • 50us过后,高电平保持时间是70us表示该bit是高电平。

如上图,在读数据时,从机会先发维持50us的低电平,然后再维持一定时间的高电平,所以需要读取总线上电平变化:
图
如上图代码,val是要等待的目标总线电平状态,timeout是超时时间,单位是us

  • val是1时,说明在调用该函数时,总线电平状态是0,当总线电平状态变为1以后返回。
  • val是0时,说明在调用该函数时,总线电平状态是1,当总线电平状态变为0以后返回。
  • 接收数据时以字节为单位,先接收到的bit是高位,后接收的是低位。

图
如上图所示,开始接收一个字节时,总线处于低电平,所以要先等其变为高,然后再延时40us,如果总线电平仍然是高,已经超过0所规定的28us,说明该bit是1。

由于先收到的bit是高位,所以每次接收一个bit时需要将data左移一位,当前bit是1则与data或等,是0的话则不用,只需要左移即可。

bit数据组合到data中后,再等待总线电平为低,开始下一个bit的接收。


数据格式:

一次完整的数据传输为40bit,先接收到的是高位bit。

  • 数据格式:
  • 8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和。

数据传送正确时校验和数据等于8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据所得结果的末8位。

DHT11中小数部分是保留不用的,所以这两个字节的数据始终为0。

图
如上图代码所示,使用DHT11_Read来读取温湿度数据,使用一个5个字节的数组来接收温湿度数据。

当主机发送完开始信号接收数据时,将接收到的5个字节数据放入到数组中,然后进行校验,如果校验通过则给输出型参数湿度和温度赋值。

😹其他层代码

设备层:

图
如上图所示,在dht11_device.h中定义温湿度数据结构体DHT11_VAL,以及描述DHT11设备结构体DHT11Dev,包含设备号,温湿度数据,设备初始化方法,以及获取数据的方法。

图
如上图所示,在dht11_device.c中,创建全局的DHT11结构体变量并初始化。

内核抽象层:

tu
如上图所示是内核抽象层的DHT11设备初始化和获取数据的方法。

芯片抽象层:

tu
如上图所示是芯片抽象层的DHT11设备初始化和获取温湿度数据的方法,其本质是在调用驱动层的DHT11设备初始化和获取数据的方法。

tu
如上图所示,在dht11_test.c中定义dht11_test函数来测试DHT11模块,将接收到的温湿度数据通过串口打印出来。

图
如上图,在main.c中创建一个任务来执行DHT11的测试函数。
图
如上图所示,可以看到本喵所在位置的温度T=18℃,湿度H=49%。和天气预报上面的大致相符。

😸总结

本篇文章主要介绍了如何将之前的裸机版本智能家居改造成FreeRTOS版本,要注意使用系统调用的中断优先级,不能太高。

除此之外还介绍了DHT11温湿度传感器的使用方法,以及使用代码实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1215829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenAI 的 CEO Sam Altman :OpenAI 正在研发下一代 AI 模型 GPT-5 但没有承诺发布时间

本心、输入输出、结果 文章目录 OpenAI 的 CEO Sam Altman :OpenAI 正在研发下一代 AI 模型 GPT-5 但没有承诺发布时间前言Sam Altman 接受《金融时报》的采访 消息汇总GPT-4 turboOpenAI 目标 AGI (通用人工智能)弘扬爱国精神OpenAI 的 CEO Sam Altman :OpenAI 正在研发下…

RS485接线方式

用2个触点连接RS485设备——RS485引脚半双工分配: 用4个触点连接RS485设备——RS485引脚全双工分配: 参考文章:RS485引脚说明及接口说明 文章目录 RS485 接线方式引言RS485通信标准简介基本特性差分信号:RS485使用差分信号传输&am…

怎样备份电脑文件比较安全

域智盾软件是一款功能强大的电脑监控软件,它不仅具备实时屏幕监控、行为审计等功能,还能够对电脑文件进行备份和管理。下面将介绍域智盾软件如何备份电脑文件,以确保数据安全。 1、开启文档备份功能 部署后台,然后点击文档安全&a…

前后端分离项目在Linux的部署方法、一台Nginx如何部署多个Web应用

需求场景:目前有三个前后端分离项目(vue+springboot),Linux服务器一台,nginx一个,比如服务器地址为www.xxxxxxx.com 我想通过80端口访问服务①(即访问www.xxxxxxx.com);通过81端口访问服务②(即www.xxxxxxx.com:81);通过82端口访问服务③(即www.xxxxxxx.com:82) ①部…

「软件设计」权威领域驱动设计(DDD)简介

「软件设计」权威领域驱动设计(DDD)简介 今天的企业应用程序无疑是复杂的,并依赖一些专门技术(持久性,AJAX,Web服务等)来完成它们的工作。作为开发人员,我们倾向于关注这些技术细节是可以理解的。但事实是…

MatrixOne 支持多样化生态工具,持续提升开发者体验

概述 在选择一款数据库产品时,对数据库上下游生态组件的打通是大数据开发工程师需要面对的一致难题。 MatrixOne提出了“One Size Fits Most”理念,旨在用全新HSTAP技术架构打破数据孤岛,其中,与各生态组件的“无缝衔接”也是Ma…

用哈希表封装unordered_map(以及set)【C++】

目录 一,前言 二,封装层框架(哈希底层以哈希桶为例) 三,迭代器 1. operator 2. operator[] 3. 仿函数优化 3. 解决unordered_set中Key可以修改的Bug 代码区 Hash_map_set.h HashTable.h 下节预告&#xff1…

Notion汉化

Notion真无语,汉化版都没有。真的无力吐槽。 2023.11.7汉化经历 教程链接:github Reamd7/notion-zh_CN at 2.4.20-handmade (github.com) 网页版: 油猴下载插件。 Notion中文汉化 浏览器插件下载 windows: github realse 这…

Ubuntu22.04 部署Mqtt服务器

1、打开Download EMQX 下载mqtt服务器版本 2、Download the EMQX repository curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash 3.Install EMQX sudo apt-get install emqx 4.Run EMQX sudo systemctl start emqx

Js:获取最近6个月的月份(包含本月、不包含本月)

一、需求 获取最近6个月的月份(不包含本月),比如现在是11月份,则需要获取到的月份是:10、9、8、7、6、5将月份从小到大排列 二、解决 1、获取最近的6个月份(不包含本月) var monthALL[]; …

在3+1的方向上展开结构加法4a3+4a14

4a3 4a14 - - 1 - - - - - - - - - - - - - 1 1 1 - 1 1 - 1 - - 1 - - - 要求得到的图片只能有4个点,并且需要最大限度的保留4a3和4a14两张图片的内在结构特征。 4个点的结构总可以认为是3个点的结构1合成的 - - 1 - - …

Java内存模型(JMM) ----多线程/并发编程

在介绍 Java 内存模型之前,先来看一下到底什么是计算机内存模型。 计算机结构简介 冯诺依曼,提出计算机由五大组成部分,输入设备,输出设备存储器,控制器,运算器 CPU 中央处理器,是计算机的…

完整时间线!李开复Yi大模型套壳争议;第二届AI故事大赛;AI算命GPTs;LLM应用全栈开发笔记;GPT-5提上日程 | ShowMeAI日报

👀日报&周刊合集 | 🎡生产力工具与行业应用大全 | 🧡 点赞关注评论拜托啦! 👀 李开复「零一万物」大模型陷套壳争议,事件时间线完整梳理 https://huggingface.co/01-ai/Yi-34B/discussions/11#65531458…

2022CCPC绵阳 ACGHM

Dashboard - 2022 China Collegiate Programming Contest (CCPC) Mianyang Onsite - Codeforces C.Catch You Catch Me 题意 思路 首先注意到贡献可以按深度统计,对于每个深度dep,贡献是在dep深度中属于的子树种类数,如果在该深度中子树存在…

结合scss实现黑白主题切换

是看了袁老师的视频后,自己做了一下练习。原视频地址: b站地址https://www.bilibili.com/video/BV15z4y1N7jB/?spm_id_from333.1007.top_right_bar_window_history.content.click&vd_sourcec6cf63302f28d94ebc02cbedcecc57ea首先创建一个全局的scs…

VSCode配置ESP-IDF

参考其他 文章即可 如果编译时遇到问题,就去找环境变量,多半是环境变量没有配置好。根据自己安装的idf的目录重新配置 环境变量. 如果电脑上有python环境,但是编译时出现找不到python解释器,需要执行下面命令,另外重…

MATLAB 模糊设计器 构建 模糊系统

系列文章目录 文章目录 系列文章目录前言一、创建 FIS 结构二、定义输入变量三、定义输出变量四、定义成员函数五、定义规则库六、设计分析七、存储和修改设计八、导出 FIS总结 前言 本例演示如何使用 Fuzzy Logic Designer 应用程序交互式创建 1 型 Mamdani 模糊推理系统&…

【自动化测试】Appium环境搭建与配置-详细步骤,一篇带你打通...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、Node.js环境搭…

easyExcle单元格合并

自定义单元格合并策略: /*** 自定义单元格合并策略** create: 2023-11-15 13:41**/ Data NoArgsConstructor AllArgsConstructor Slf4j public class EasyExcelCustomMergeStrategy implements RowWriteHandler {/*** 总数*/private Integer totalNum;//合并行计数…

【ARM Trace32(劳特巴赫) 使用介绍 5 -- Trace32 scan dump 详细介绍】

文章目录 1.1 JTAG 测试逻辑架构1.2 D型扫描触发器1.2.1 全扫描介绍1.3 IR 寄存器1.4 TDR(Test data registers)1.4.1 TDR 的实现1.4.1.1 Bypass Register1.4.1.2 Boundary-scan register1.5 Scan Dump1.5.1 soft fusion1.1 JTAG 测试逻辑架构 图 1-1 片上测试逻辑概念图 如前面…