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

🐱作者:一只大喵咪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.mzph.cn/news/153433.shtml

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

相关文章

基于GB28181搭建流媒体服务器--1.概念解析

什么是GB28181 GB28181(国标28181),全称为《中华人民共和国公共安全视频监控联网系统技术要求》,是中国国家标准委员会发布的一个针对公共安全视频监控领域的标准框架。该标准指导了视频监控设备之间的联网互通,统一管理和控制,并…

git拉取普通idea Java项目module没有build的问题

在不断完成一个项目的时候,会有不断新加的module,我们用git拉取时会发生没有识别新module的情况。 解决方法是右键项目名称,然后点击Open Module Settings 接下来,点击Module,加号,新建Module的名字就是在g…

【数据结构】【版本2.0】【树形深渊】——二叉树入侵

目录 引言 一、树的概念与结构 1.1 树的概念 1.2 树的相关概念 1.3 树的表示 1.4 树在实际中的运用 二、二叉树的概念与结构 2.1 二叉树的概念 2.2 特殊二叉树 满二叉树 完全二叉树 2.3 现实中的二叉树 2.4 二叉树的性质 2.5 二叉树的存储结构 顺序存储 链式…

深度学习到智能小车(1)深度学习框架

0.前提 最近新开了一门叫机器学习的课程,老师一直在跟我们讲一些有关这方面的知识,告诉我们一定要学好数学,因为数学是算法的基础。我手上的donkeycar刚好也涉及到Keras深度神经网络,所以出于好奇我去图书馆借回了一本叫《Keras深…

python urllib open 头部信息错误

header 有些字符在 lighttpd server 中无法正常解析,需要转换 quteo 可以转换 就跨平台而言,Rust 和 python 一样优秀,看了在stm32 上使用 Rust 进行编程,从一定程度上,而言,稳定和安全性要比C 开发的好的多,说出来可能不信,在单片机上是可以对空指针进行…

【MySQL】聚合函数、group by、update、delete

聚合函数、group by、update、delete 前言正式开始update将孙悟空同学的数学成绩变更为 80 分将曹孟德同学的数学成绩变更为 60 分,语文成绩变更为 70 分将总成绩倒数前三的 3 位同学的数学成绩加上 30 分将所有同学的语文成绩更新为原来的 2 倍 delete删除孙悟空同…

【C++上层应用】1. 异常处理

文章目录 【 1. C的标准异常 】【 2. 异常转移处理 】2.1 throw 抛出异常2.2 try 捕获异常2.3 catch 捕获异常2.4 实例 【 3. 定义新的异常 】 异常是程序在执行期间产生的问题,比如编译报错、链接错误等。 【 1. C的标准异常 】 C 提供了一系列标准的异常&#xf…

[Spring Cloud] Nacos 实战 + Aws云服务器

文章目录 前言一、拥有一台Aws Linux服务器1.1、选择Ubuntu版本Linux系统1.2、创建新密钥对1.3、网络设置1.4、配置成功,启动实例1.5、回到实例区域1.6、进入具体的实例1.7、设置安全组 二、在Mac上连接Aws云服务,并安装配置JDK112.1、解决离奇的错误2.2…

Zynq-Linux移植学习笔记之66- 国产ZYNQ通过裕太PHY8521连接国产交换芯片

1、背景介绍 ZYNQ通过裕太PHY 8521主要连接两种国产交换芯片,一种为盛科的CTC8096,另一种为32所的JEM5396。框图示意如下: 2、硬件状态确认 首先检查phy的模式,确认为SGMII_MAC-RGMII_PHY 可通过读出A001寄存器确认状态 读出来应…

RESTful API 设计指南——开篇词

引言 十年后的今天,我终于学会了RESTful API。 以上,就是我最近一个月的心路历程。入职新公司不到2周,自己都还没完全理解RESTful API就要求给校招应届生培训,着实压力山大。培训结束后也感觉收获颇丰,遂总结分享出来&…

牛客——OR36 链表的回文结构(C语言,配图,快慢指针)

目录 思路一:链表翻转 思路二:快慢指针,分别从头和尾间开始比较 本题是没有对C的支持的,但因为CPP支持C,所以这里就用C写了,可以面向更多用户 链表的回文结构_牛客题霸_牛客网 (nowcoder.com) 思路一&am…

redis数据结构

redis数据结构 redis全名(Remote Dictionary Server),即远程字典服务 redis的值的数据结构类型有String、List、Set、Hash、zset(sorted set,有序集合)、Bitmaps(位图)、HyperLogLogs 注意:我使用的版本是6.0.10,不同版本可能略有…

YOLOv8-seg改进:重新思考轻量化视觉Transformer中的局部感知CloFormer,提升上下文感知权重来增强局部特征 |2023清华

🚀🚀🚀本文改进:CloFormertAttention利用共享权重和上下文感知权重有效地提取高频局部特征表示 🚀🚀🚀SEAM、MultiSEAM分割物与物相互遮挡、分割小目标性能 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻…

Java Enumeration 接口

Java Enumeration 接口 这段代码展示了如何使用 Enumeration 接口来遍历 Properties 对象的键。在这里,foo.getProp() 返回一个 Properties 对象,而 propertyNames() 方法返回一个 Enumeration 对象,它包含了 Properties 对象中所有键的枚举。…

synchronized锁膨胀过程

轻量级锁: 使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。 轻量级锁原理 1.创建锁记录(Lock Record)对象&#…

光谱图像超分辨率综述

光谱图像超分辨率综述 简介 ​ 论文链接:A Review of Hyperspectral Image Super-Resolution Based on Deep Learning UpSample网络框架 1.Front-end Upsampling ​ 在Front-end上采样中,是首先扩大LR图像,然后通过卷积网络对放大图像进行…

竞赛 题目:基于深度学习的中文汉字识别 - 深度学习 卷积神经网络 机器视觉 OCR

文章目录 0 简介1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 简介 🔥 优质竞赛项目系列,今天要分享的是 基于深度学习的中文汉字识别 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! &a…

什么是媒体见证?媒体宣传有哪些好处?

传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 一,什么是媒体见证? 媒体见证是指企业举办活动,发布会,邀请媒体现场采访的一种宣传方式,媒体到场后,对其进行记录…

lenovo联想笔记本ThinkPad P1 Gen5/X1 Extreme Gen5原装出厂Windows11预装OEM系统

链接:https://pan.baidu.com/s/13E97Nwc-0-N7ffPjEeeeOw?pwdep4l 提取码:ep41 原装出厂系统自带所有驱动、出厂主题壁纸、Office办公软件、联想电脑管家等预装程序 所需要工具:32G或以上的U盘 文件格式:ISO 文件大小&#xff…

Java实现俄罗斯方块游戏

俄罗斯方块游戏本身的逻辑: 俄罗斯方块游戏的逻辑是比较简单的。它就类似于堆砌房子一样,各种各样的方地形状是不同的。但是,俄罗斯方块游戏的界面被等均的分为若干行和若干列,因此方块的本质就是占用了多少个单元。 首先来考虑…