【智能家居项目】FreeRTOS版本——多任务系统中使用DHT11 | 获取SNTP服务器时间 | 重新设计功能框架
🐱作者:一只大喵咪1201
🐱专栏:《智能家居项目》
🔥格言:你只管努力,剩下的交给时间!
目录
- 🍓多任务系统中使用DHT11
- 🍅关闭调度器
- 🍅使用中断
- 🍓获取SNTP服务器时间
- 🍓重新设计功能框架
- 🍓总结
🍓多任务系统中使用DHT11
在上篇文章中,本喵仅进行了单任务的DHT11温湿度传感器使用,相当于裸机使用。

根据上面时序图计算接收一次数据(5个字节)的耗时,不考虑主机发送起始信号的耗时:
- 最小时间:40 + 80 + 80 + (50+28)*40 = 3320us = 3.32ms。
- 最大时间:40 + 80 + 80 + (50+70)*40 = 5000us = 5ms。

在配置文件FreeRTOSConfig.h中,configTICK_RATE_HZ的值是1000,这表示FreeRTOS中有多个同优先级的就绪任务时,它们依次执行1ms。
- 而读取DHT11时最少耗时3.32ms,如果读取DHT11的过程被其他任务打断的话,必定失败。

如上图所示,在smarthouse.c文件中定义一个DHT11_UpdateValue函数来更新DHT11传感器的值,并且通过串口打印出来。

如上图,创建三个相同优先级的任务,分别执行SmartHomeTask任务,DHT11_UpdataValue以及other_task任务。
此时三个任务按照时间片轮转的方式在执行:

如上图,此时虽然ESP8266在某种巧合下连接成功了,但是DHT11读取温湿度数据失败了,始终都是0。
有三种解决方法:
- 读取时关中断或者关闭调度。
- 把DHT11的任务优先级设置为最高。
- 修改程序,在中断里记录时间、解析数据。
将DHT11任务优先级设置为最高必然能成功,因为只有这一个任务在运行,所以只介绍其他两种解决方法。
🍅关闭调度器
目前项目中存在SmartHouseTask任务和DHT11_UpdateValue任务,在连接WIFI和读取温湿度数据时,都不能被打断,必须具有原子性,否则就会出现错误。

如上图所示,在连接WIFI和建立连接之前,先调用vTaskSuspendAll()关闭调度器,此时其他任务无法调度,操作完毕后再调用xTaskResumeAll()打开调度器,恢复任务调度。

如上图所示,在DHT11读取温湿度数据前将调度器关闭,读取完毕后再打开,防止其他任务来干扰导致读取出错。

如上图,此时WIFI连接和温湿度数据读取都正常。
🍅使用中断
将主机和DHT11传送数据的总线设置成外部中断模式:

如上图,在主机发出起始信号并将总线拉高时使能中断,并且启动定时器。
DHT11每改变一次总线状态就会触发一次中断,在中断函数中记录中断发生的时间Tx,如上图中的T3-T2就是总线保持高电平时间,根据时间判断出该bit是0还是1。
- 在发出起始信号后使能中断并启动定时器的原因:
- 最理想的使能位置是在T3位置。
- 但是这里使能完中断并启动定时器以后,可能就过了50us了。
- 会导致中断丢失,从而造成数据读取错误。
所以提前使能中断,这样的话一次读取数据的过程中会产生83次中断,前3次DHT11做出应答时触发的中断,所以在程序中抛弃这三次中断,仅处理后面的80次中断即可。
- 40个bit,一个bit会触发两次中断。
编程思路:
- 初始化
- 设置定时器,精度要达到1us量级
- 设置DHT11数据引脚:用作开漏输出模式
- 设置DHT11数据引脚:用于中断、双边沿触发
定时器前面本喵在上篇文中已经初始化过了,这里直接使用DHT11_TIM_Init来初始化微秒定时器。

如上图,定义一个函数DHT11_GPIO_Init_As_Output将数据引脚设置成开漏输出模式。

如上图,定义一个函数DHT11_GPIO_Init_As_IRQ将数据引脚设置成中断模式。

如上图,在DHT11初始化的时候,先将引脚初始化为输出引脚,并创建一个二进制信号量。
- 在
DHT11_Read函数中:- 发出Start信号
- 等待ACK
- 使能数据引脚的中断
- 阻塞

如上图,先提供使能和失能中断的方式以及启动和停止定时器的方式,在每次失能定时器的时候都要将记录中断发生次数的变量dht11_edge_cnt清零。
在启动定时器的时候,设置最大超时时间为10ms,因为读取DHT11一次数据(40bit)最多耗时5ms,如果10ms还没有读成功就说明出问题了。

如上图所示,发出起始信号,将总线拉高后立即打开中断并启动定时器,然后去获取信号量,这是为了让该任务阻塞。
- 在DHT11数据引脚的中断函数中
- 记录中断发生的时间,放在数组里
- 累加中断次数,发现40位数据都接收完毕后,唤醒任务

如上图,先提供一个能读取微秒定时器计数值的函数DHT11_ReadTimer_us。

如上图,在中断函数中再增加用于处理DHT11产生的中断。当中断发生后,获取一下发生的时间并放入到存放时间的数组中。
当中断发生80次以后就唤醒读取数据的任务,调用DHT11_Notify_Task,因为40个bit会产生80次中断。

如上图代码,归还初始化时创建的信号量,唤醒读取温湿度数据的任务。
如果中断发生了83次就直接返回,不记录时间,因为这时必然接收到了40个bit,多发生的3次中断是DHT11应答产生的。
- 任务被唤醒后:根据中断时间解析出得到的数据
在读取温湿度数据DHT11_Read时,任务被唤醒后没有立刻处理数据,而是又调用vTaskDelay(1)延时了1ms,这是因为该任务被唤醒时产生了80次中断,这80次中断中包含DHT11应答产生的中断,此时数据产生的中断就没有到达80,所以要再等一会。
然后再失能中断并停止定时器,将数据引脚设置成输出模式,再去解析数据。

如上图,整体处理裸机和之前的方式差不多,数据左移以后,如果该bit是1则或等,如果是0则不用处理。
但是这里需要处理多出来的3次中断,83次中断中只有80次中断发生时记录的时间数据是有用的,这里通过校验和来舍弃多发生的3次中断:
- 从记录时间的数组首部开始,取80个数据解析出温湿度数据。
- 然后使用校验和来判断,如果校验通过则直接采用,如果不通过就抛弃数组第一个时间数据,从下一个数据开始再取80个解析。
- 最多解析3次就能校验得到正确的温湿度数据。
在判断每个bit是高电平还是低电平的时候,让数组中后一个时间值减去前一个时间中,得到的就是两次中断之间电平保持的时间,如果大于40us则是高电平,否则就是低电平。
如上图代码中所示,需要的差值仅是高电平保持的时间,低电平的不需要,也就是只需要算1时刻减0时刻和3时刻减2时刻的时间差值。
所以每计算完一次以后,数组下标直接加2而不是加1。

如上图,将更新温湿度任务中关闭调度器的部分屏蔽掉,因为现在使用的是中断方式。
- 中断的优先级高于所有任务,所以读取温湿度过程中的是实时性是可以保证的。
- 在中断没有将80个时间值处理完毕前,该任务由于无法申请到信号量而处于阻塞状态。

如上图,此时温湿度数据可以正常读取,温度是T=16℃,湿度是H = 38%,并且WIFI也成功连接。打印的路径信息是校验错误时打印的,这个数据就被抛弃了。
- 由于温湿度数据并不会剧烈变化,所以不需要很高的实时性。
🍓获取SNTP服务器时间
ESP8266带有从SNTP服务器上获取时间的功能,查看手册中相关用法:
设置:

如上图所示是设置时域和SNTP服务器的AT指令,设置指令格式:
AT+CIPSNTPCFG=1,8,"cn.ntp.org.cn","ntp.sjtu.edu.cn","us.pool.ntp.org"- 1表示使能,相应的填0就表示失能。
- 8表示时域,北京时间位于东八区,所以是8。
- 引号中的内容是SNTP服务器的IP地址。
对于SNTP服务器,可以将3个都填进去,也可以不填使用默认的,网上有很多SNTP服务器的IP地址,这里本喵使用上海市的SNTP服务器IP地址202.120.2.101。发送的指令就是AT+CIPSNTPCFG=1,8,"202.120.2.101"。

如上图,将指令使用串口发送给ESP8266后,回显OK表明设置成功。
查询:

如上图所示是查询SNTP时间的指令AT+CIPSNTPTIME?,回显的是查询到的时间:

如上图所示,查询时间后返回+CIPSNTPTIME:Fri Nov 17 13:44:30 2023,当前时间是星期五11月17日13点44分30秒2023年。
接下来就是将获取SNTP服务器时间加入到智能家居项目中了,获取SNTP服务器时间是网卡的功能,所以这部分代码放在网卡设备及esp8266驱动层。

如上图是描述获取到时间的结构体,获取到的时间Fri Nov 17 13:44:30 2023中,年是4个字节,月是3个字节,日是2个字节,时是2个字节,分是2个字节,秒是2个字节,使用数组存放。由于是字符串,所以每个数组多出一个字节来放\0。

如上图,在网卡设备结构体中,增加一个unsigned char isConnected来表示网卡状态,0表示WIFI没有连接,1表示WIFI连接成功,只有WIFI连接成功才能获取时间。再增加一个获取时间的函数指针GetTime。

如上图,在esp8266.c中定义获取时间的函数ESP8266_GetNetTime,在该函数中,由于设置时区和服务器比较耗时,还需要等待,而且也无需重复设置,所以只设置一次即可。
从回显的字符串中解析出时间后,要判断一下是否是是1970年,如果是则说明设置服务器和时区没有成功,需要重新设置一下。

如上图,初始化ESP8266时增加网络状态和获取时间函数指针的初始化。

如上图,在smarthouse.c中连接WIFI的函数中,WIFI连接成功就将isConnected标志置一,表示WIFI连接成功了。

如上图,在smarthouse.c中定义一个GetNetTime函数来获取网络时间,获取到以后分别在屏幕和串口上显示。

如上图所示,创建一个任务用来获取网络时间。

如上图串口所示,此时既能获取温湿度数据,又能获取网络时间,但是此时还没有充分利用起来FreeRTOS特性,所以其他功能会收到影响,这里只是验证获取时间的功能是正常的,至于OLED屏幕上的显示本喵就不拍照了。
🍓重新设计功能框架

如上图所示是增加了两个功能模块后的框架结构图。
输入事件队列是信息中轴:
- 按键中断触发定时器中断,消除抖动后往队列写入"按键类事件"。
- 使用微信小程序、sscom发出控制命令,UART3接收到数据,解析出数据后往队列写入"网络类事件"。
- SNTP任务:每隔1分钟通过网络获取时间,修正本地时间。
- 1秒钟周期定时器:往队列写入"时间类事件",以便更新OLED上的时间。
- DHT11任务:每隔几秒钟读取温湿度,往队列写入"温湿度类事件"。
- SmartHome任务:
- 连接WIFI
- 读取输入事件,控制设备:LED、风扇、OLED
任务的优先级:
- SmartHome任务:优先级最高,它要及时响应用户的操作,平时大部分时间都是阻塞状态。
- 定时器后台任务:优先级第2高,用来更新时间,如果时间的更新不及时会影响产品体验。
- SNTP任务:优先级第3高,它每隔1分钟执行一次,用来修正时间。
- DHT11任务:优先级最低,温湿度不会剧烈变化。
使用一个软件定时器作为1秒钟周期定时器,将该定时器也看作是一个设备:

如上图代码,创建一个结构体来描述软件定时器,包括设备编号,定时周期,回调函数,软件定时器句柄,以及初始化等方法。

如上图,定义具体的函数,然后来初始化周期定时器全局变量,回调函数使用的是外部的vOneSecTimerFunc。其他函数调用内核层相应的函数。

如上图,在内核抽象层使用FreeRTOS提供的操作软件定时器的接口即可,在初始化的时候,将创建软件定时器后生成的句柄填入到上一层的全局变量ptTimer->xPeriodTimer中。
软件定时器超时后会调用回调函数:

如上图代码所示,在软件定时器的回调函数中,会先获取一下当前网卡的时间,然后再给这个时间增加1秒,因为软件定时器1秒钟超时一次,在时间增加的时候要注意进位。
时间增加完毕后,再将这个时间设置到网卡时间中,并且构造输入事件写入到输入队列中,让最上层的智能家居任务来显示这个时间到OLED上。
每超时一次后,超时次数OverTimeCounter加1,当前其等于60的时候到了一分钟,此时要唤醒更新时间任务,从SNTP服务器获取新的网络时间来校正本地时间。
在回调函数中获取网卡时间的时候,前提是网卡中已经存在时间了:

如上图,在获取网卡时间时,先xSemaphoreTake申请信号量,如果成功则返回网卡时间的地址,否则就阻塞。
- 使用一个信号量,让更新网络时间的任务和软件定时器回调函数互斥访问网卡时间。

如上图,在设置一次网卡时间后,就会增加一下信号量,好让软件定时器的回调函数能访问,而不会阻塞。
那么是谁会设置网卡时间呢?

如上图代码,在获取网络时间的任务运行后,会先获取一次网络时间,然后设置到网卡时间中,此时定时器的回调还是才能获取到网卡时间,在这之前处于阻塞状态。
除此之外,还创建了一个xUpDateNetTimeSema信号量,用来控制SNTP服务器上获取网络时间。在创建成功后先增加一下,方便第一次获取网络时间,Take以后,再次到了这里后就会阻塞。
当软件定时器超时60次,也就是一分钟时:

如上图,调用UpdateTimeNotify时就会Give一下这个信号量,此时就会唤醒任务从网络上获取一下时间来校正本地时间。
- 更新网络时间的任务大部分时间处于阻塞状态,每一分钟会被软件定时器的回调函数唤醒一次来从SNTP服务器上获取一次时间。

如上图,在初始化所有子系统和设备的时候,需要将软件定时器也初始化。

如上图,在智能家居任务将输入事件转换成Json格式的时候,再增加一个定时器输入的转换,如红色框中所示。

如上图,在根据输入事件控制设备时,增加定时器输入部分,从网卡中获取时间后将时间显示到OLED屏幕的最下面。
🍓总结
至此本喵就带着大家实现了FreeRTOS版本的智能家居项目,它的优点主要在于有非常高的实时性。
温湿度部分使用了中断方式,这也是一种保证实时性的手段,有兴趣的小伙伴可以自行了解一下中断编程。
多任务系统中,每个任务在不需要运行的时候,要让其处于阻塞状态,让出CPU资源,可以通过信号量,队列等等方式来实现。
相关文章:
【智能家居项目】FreeRTOS版本——多任务系统中使用DHT11 | 获取SNTP服务器时间 | 重新设计功能框架
🐱作者:一只大喵咪1201 🐱专栏:《智能家居项目》 🔥格言:你只管努力,剩下的交给时间! 目录 🍓多任务系统中使用DHT11🍅关闭调度器🍅使用中断 &am…...
鸿蒙APP外包开发需要注意的问题
在进行鸿蒙(HarmonyOS)应用开发时,开发者需要注意一些重要的问题,以确保应用的质量、性能和用户体验。以下是一些鸿蒙APP开发中需要特别关注的问题,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软…...
Redis 19 事务
Redis通过MULTI、EXEC、WATCH等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求…...
Fabric多机部署启动节点与合约部署
这是我搭建的fabric的网络拓扑 3 个 orderer 节点;组织 org1 , org1 下有两个 peer 节点, peer0 和 peer1; 组织 org2 , org2 下有两个 peer 节点, peer0 和 peer1; 以上是我的多机环境的网络拓扑,使用的是docker搭建的。我的网络…...
WordPress主题WoodMart v7.3.2 WooCommerce主题和谐汉化版下载
WordPress主题WoodMart v7.3.2 WooCommerce主题和谐汉化版下载 WoodMart是一款出色的WooCommerce商店主题,它不仅提供强大的电子商务功能,还与流行的Elementor页面编辑器插件完美兼容。 主题文件在WoodMart Theme/woodmart.7.3.2.zip,核心在P…...
Java 高等院校分析与推荐系统
1)项目简介 随着我国高等教育的大众化,高校毕业生就业碰到了前所未有的压力,高校学生就业问题开始进入相关研究者们的视野。在高校学生供给忽然急剧增加的同时,我国高校大学生的就业机制也在发生着深刻的变化,作为就业…...
【JVM】Java虚拟机
本文主要介绍了JVM的内存区域划分,类加载机制以及垃圾回收机制. 其实JVM的初心,就是让java程序员不需要去了解JVM的细节,它把很多工作内部封装好了.但是学习JVM的内部原理有利于我们深入理解学习Java. 1.JVM的内存区域划分 JVM其实是一个java进程 ; 每个java进程,就是一个jvm…...
业务架构、技术架构、项目管理的有机结合
新入职的创业公司一年不行了。 这一年来没有上班,也因为大龄的问题找不到合适的工作。然后考了几个项目管理证书,又思考了一个技术兑现的问题。 技术本身是架构的执行层面,如果上面的公司战略、业务架构变小,缩水,或者…...
拜耳阵列(Bayer Pattern)以及常见彩色滤波矩阵(CFA)
一、拜耳阵列的来源 图像传感器将光线转化成电流,光线越亮,电流的数值就越大;光线越暗,电流的数值就越小。图像传感器只能感受光的强弱,无法感受光的波长。由于光的颜色由波长决定,所以图像传播器无法记录…...
【信息安全】浅谈IDOR越权漏洞的原理、危害和防范:直接对象引用导致的越权行为
前言 ┌──────────────────────────────────┐ │ 正在播放《越权访问》 - Hanser │ ●━━━━━━─────── 00:00 / 03:05 │ ↻ ◁ ❚❚ ▷ ⇆ └───────────────────────────────…...
uni-app 蓝牙打印, CPCL指令集使用
先上代码: GitHub - byc233518/uniapp-bluetooth-printer-demo: 使用uniApp 连接蓝牙打印机 Demo, CPCL 指令简单实用示例 (内含 芝珂,佳博,精臣 多个厂家指令集使用文档) 文件结构: ├── App.vue ├── CPCL 指令手册.pdf // 指令集参考手册 ├── LICENSE ├── R…...
vue-组件通信(二)
🌈个人主页:前端青山 🔥系列专栏:Vue篇 🔖人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-组件通信(二) 目录 组件通信(二) (1) props / $emit 1. 父组件向子组…...
2023年【危险化学品经营单位安全管理人员】考试题及危险化学品经营单位安全管理人员模拟试题
题库来源:安全生产模拟考试一点通公众号小程序 危险化学品经营单位安全管理人员考试题是安全生产模拟考试一点通总题库中生成的一套危险化学品经营单位安全管理人员模拟试题,安全生产模拟考试一点通上危险化学品经营单位安全管理人员作业手机同步练习。…...
Uni-App常用事件
Uni-App是一个跨平台的前端开发框架,支持多个平台的应用开发,包括H5、小程序、App等。在Uni-App中,有许多常用的事件可以用来处理用户交互、页面生命周期等方面的逻辑。以下是一些Uni-App中常用的事件: 点击事件(click…...
【笔记 Pytorch】稀疏矩阵、scipy.sparse模块的使用
安装:pip install scipy 描述:就是专门为了解决稀疏矩阵而生。导入模块:from scipy import sparse 优缺点总结 七种矩阵类型描述coo_matrix ★【名称】coordinate format 【优点】 ① 不同稀疏格式间转换效率高(特别是CSR和CSC) …...
C#学习相关系列之Linq常用方法---排序(一)
一、构建数据 public class Student_1{public int ID { get; set; }public string Name { get; set; }public int Chinese { get; set; }public int Math { get; set; }public int English { get; set; }public override string ToString(){return string.Format("ID:{0},…...
Android Proguard混淆
关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。 目录 一、导读二、概览三、语法规则3.1 输入/输出选项3.2 保留选项3.3 缩…...
MySQL 1、初识数据库
一、什么是数据库? 以特定的格式保存好的文件,我们就叫做数据库。 提供较为便捷的数据的存取服务的软件集合、解决方案,我们就叫它数据库。 存储数据用文件就可以了,为什么还要弄个数据库。 文件或数据库都可以存储数据&#…...
H5ke11--3介绍本地,会话存储
代码顺序: 1.设置input,捕获input如果有多个用属性选择符例如 input[typefile]点击事件.向我们的本地存储设置键值对 2.在点击事件外面设置本地存储表示初始化的值.点击上面的事件才能修改我们想修改的值 会话(session)浏览a数据可以写到本地硬盘,关闭页面数据就没了 本地(…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
从实验室到产业:IndexTTS 在六大核心场景的落地实践
一、内容创作:重构数字内容生产范式 在短视频创作领域,IndexTTS 的语音克隆技术彻底改变了配音流程。B 站 UP 主通过 5 秒参考音频即可克隆出郭老师音色,生成的 “各位吴彦祖们大家好” 语音相似度达 97%,单条视频播放量突破百万…...

