🐱作者:一只大喵咪1201
🐱专栏:《智能家居项目》
🔥格言:你只管努力,剩下的交给时间!
目录
- 🥞网卡设备接入输入子系统
- 🍔测试
- 🥞业务子系统
- 🍔输入事件转换为统一参数
- 🍔根据参数控制设备
- 🍔定时器按键消抖
- 🥞整体效果展示
- 🥞项目总结
- 🥞源码
🥞网卡设备接入输入子系统
网络子系统实现了,在我们整个项目框架中,网络子系统也输入子系统中输入设备的之一,所以现在要做的就是网络子系统接入到输入子系统中。
如上图所示,在输入子系统中增加网卡输入设备,在头文件net_input.h
中提供了增加网卡输入设备的函数声明,在input_system.c
中的增加输入设备函数中再增加一个网卡输入设备,此时在宏观层面上已经将网卡输入子系统作为输入设备接入到输入子系统中了。
但是网卡设备作为输入子系统的输入设备还需要去实现:
如上图源文件中代码所示,创建了一个全局的网卡输入设备并进行了初始化,还实现了添加网卡输入设备的函数。
在初始化网卡输入设备的时候,用ESP8266NetInputInit
函数作为该设备的初始化方法,在该函数中仅做了注册输入数据处理回调函数的工作,只有在这里注册好了,接收中断发生后才会调用这个函数来处理数据。
输入数据处理回调函数的实现需要单独讲解一下:
如上图代码是输入数据处理的回调函数,同样也是使用状态机模型,有三个状态,前面AT层中详细讲解过,这里类似,不同之处在于对有效数据的处理。
当有效数据读取完毕以后,要构造输入事件,也就是给InputEvent
类型变量event
赋值,包括事件发生事件,事件类型,并且将有效数据拷贝到事件的str
成员数组中去,最后要在数据末尾赋值'\0'
,因为这是一个字符串。
都事件构造好以后,需要将事件上报给业务子系统,也就是将输入事件放入到输入事件的环形缓冲区中,最后将状态机恢复到初始状态以便下一次数据处理。
此时网卡就作为输入设备接入到输入子系统中了,这个过程中的核心工作就是该UART3
中断函数注册回调函数,下面来看测试代码。
🍔测试
如上图代码所示,在上篇文章网络子系统的测试代码中增加了部分内容,如蓝色框所示。
如上图所示,将程序烧带到开发板并且上电以后,准备工作完成,远端发送数据后,ESP8266
模块接收到网络数据,并构造成为输入事件InputEvent
类型,可以看到,事件类型type=2
,并且显示了有效数据。
按键按下时也会产生输入事件,可以看到输入事件类型type=0
。无论什么类型的事件,都会构造一个InputEvent
类型变量放入输入子系统的环形缓冲区中,只需要不断读取环形缓冲区即可。
🥞业务子系统
现在所有的子系统都实现了,接下来就是要用这些子系统共同构成业务子系统,来看下整个业务子系统有什么功能:
- 使用按键控制LED
- K1控制红灯:松开后改变LED状态
- K2控制绿灯:松开后改变LED状态
- 通过网络控制LED、风扇
PC端的sscom
或者用微信小程序给MCU板子发送网络数据,控制板子上的设备:
- 控制灯:LEDDevice设为:lamp1、lamp2、lamp3,一共三个LED等。
控制命令如下:
状态 | 命令 |
---|---|
开 | {“dev”:“lamp1”,“status”:“1”} |
关 | {“dev”:“lamp1”,“status”:“0”} |
反转 | {“dev”:“lamp1”,“status”:“2”} |
- 控制风扇
控制命令如下:
状态 | 命令 |
---|---|
顺时针旋转 | {“dev”:“fan”,“status”:“1”} |
逆时针旋转 | {“dev”:“fan”,“status”:“-1”} |
停止 | {“dev”:“fan”,“status”:“0”} |
- 离家&回家模式:离家时将3个LED灯和1个风扇都关闭,回家时将3个LED灯和1个风扇都打开。
控制命令如下:
状态 | 命令 |
---|---|
回家 | {“dev”:“home”,“status”:“0”} |
离家 | {“dev”:“home”,“status”:“1”} |
上面的控制命令采用Json
格式,以“名称 ":"值”
对的方式存储数据,名称和值之间是以冒号间隔,例如"Name":"A-Big-MiaoMi"
。
Json
数据由花括号括起来,可以包含多个“名称":"值”
对,以逗号隔开,例如{"Name":"A-Big-MiaoMi","Sex":"Male","Age":"24"}
,此时就存在三对数据。
- 底层由按键、网卡发出
InputEvent
输入事件。 - 最上层用来控制设备。
根据底层发来的参数控制设备,要屏蔽底层设备细节及它发来的数据,上层不关心底下的是按键、遥控器还是小程序,只需要告诉上层要做什么。
所以要使用Json
数据来统一的参数格式,比如{"dev":"lamp1","status":"1"}
,此时上层就知道要让设备lamp1
的状态变成1
,它根本不用管这个数据是怎么来的。
无论是按键产生的输入事件函数网络产生的输入事件,都要以Json
数据格式交付给上层。
- 需要一个中间层:将各类方式产生的
InputEvent
输入事件转为为Json
格式的参数。
如上图所示,业务子系统要实现这个中间层message.c
,然后再在smarthome.c
中根据参数控制设备。
🍔输入事件转换为统一参数
如上图代码所示,当输入事件产生以后,就调用该函数ConvertInputEventToJson
来转换为Json
格式。
首先判断是什么类型的输入事件,如果是网络输入事件,则将网络数据直接作为Json
数据,因为发送网络数据就是按照Json
格式发送的,相当于是远端和MCU之间的约定。
- 使用
sscom
发送数据时必须按照这个Json
格式发送。- 使用小程序时,点下按键以后,后台会自动处理为
Json
格式并发送。
如果是按键输入事件,先要判断按键是否松开,如果没有松开则之间返回-1,上层不处理LED灯状态。如果松开,则再判断是哪个键按下,将对应设备名和状态构造成Json
格式供上层去处理LED灯状态。
🍔根据参数控制设备
- 初始化各类设备
- 连接WIFI
- 在OLED上显示IP、端口
- 读取InputEvent
- 转换为Json格式的参数
- 控制设备
按照上诉步骤来编写代码:
如上图所示代码,先初始化所有设备和子系统,再显示启动信息,表明业务系统正在启动,再显示正在连接信息,连接WIFI,连接好之后显示ESP8266的IP地址和端口号,做好所有准备以后,在循环中检测输入事件,并转换为Json
格式。
- 本喵将WIFI名称和账号使用
define
内嵌到了程序中,可以根据具体WIFI情况作修改。
各个步骤都是用一个函数实现的,接下来就讲解一下这些函数的实现:
如上图所示代码,在初始化设备和子系统函数中,初始化了LED设备,风扇,显示设备,字库子系统,所有输入子系统,网络子系统,每一部分初始化都按照前面各个单元测试时的步骤来。
如上图所示代码,前两个函数只是用来表明业务子系统正在启动或者正在连接WIFI,都是通过OLED设备显示的。在连接WIFI的时候,放在一个循环中连接,直到连接成功。
如上图所示代码,WIFI连接成功以后,将ESP8266的IP地址和端口号以及作者信息显示到OLED屏幕上。
如上图所示代码是业务子系统根据Json
数据来控制设备的,首先要解析Json
数据格式,获取设备名称,以及设备状态,都是使用的C库函数strstr
来查找字串。
获取到的设备状态有大于0小于0以及等于0三种情况,如果是小于0则状态部分有两个字节,第一个字节是-
说明是一个负数,否则就是大于等于0的数。
- ASCII码数字字符 -
0
得到的就是整形数字。- 负的数字字符需要单独处理
-
和字符两个字节。
然后根据设备名字,使用C库函数strncmp
来判断是哪个设备,如果是LED设备,则把状态值status
给它的Control
方法来改变LED灯状态,风扇也是同样的道理。
如果设备名是回家,则将所有LED和风扇都打开,如果是离家则都关闭。
- 使用for循环打开3个LED灯这里不太完美,如果三个LED等的编号不是0,1,2则不能这样。
🍔定时器按键消抖
按键输入设备中,需要给按键消抖:
如上图所示,理想情况下,每按一次按键,产生一次按键中断,也就是按键所在引脚的电平由高电平直接变成低电平,记录一次数据。
如上图,但是实际情况是,按下按键以后,由于按键金属片的机械振动,会导致引脚电平发生反复变化,就会发生多次中断,假设发生了五次,难道这五次需要都记录吗?肯定不是,我们只按下一次,所以在程序理也需要只记录一次。
所以就要消除这个机械抖动,通常有三种方式:
- 硬件消抖
就是在机械结构或者电路中设计一些消抖结构或者消抖电路,对于我们写软件的人来说,这种方式不必考虑。
- 延时消抖
在前面的图中可以看到,虽然电平会因为抖动而反复变化,但最终还是会稳定到低电平,可以让程序在机械抖动这个过程中停止不动,也就是延时,待抖动停止后再读取这个数据,这就是延时消抖。
但是在使用HAL库的HAL_Delay()
函数在按键中断中消抖时会出现问题,HAL_Delay
是使用SysTick
计时的,它也会产生中断。
但是默认情况下,SysTick
中断的优先级是最低的,而我们一般设置的中断优先级都是比较高的,所以在我们的中断函数中调用HAL_Delay
时,SysTick
的中断就无法打断我们的中断,就无法执行延时函数。
而我们的中断函数中,它在等SysTick
中断函数执行完毕才会继续向下执行,此时就会导致程序卡死了,不再动了。
- 我们自己的中断函数中不能调用
HAL_Dealy
延时函数,否则会卡死不动。
- 定时器消抖
如上图,同样是延时消抖的思想,假设我们给它设置了10ms延时,也就是一个timer
,每发生一次中断,就延时一个timer
,直到不再抖动稳定下来才记录。
这种方式中,每发生一次中断,延时就推后一个timer
,也就是从头开始计时一个timer
。
- 按键中断这里采用的是上升和下降沿都触发。
如上图所示代码,在按键设备中的头文件gpio_key.h
中增加获取按键的定时时间(延时多久了),设置按键的定时器事件(要延时多久),以及一个对按键定时器处理的函数。
如上图对应gpio_key.c
中的源文件,创建一个timers
数组,大小为2,因为只有两个按键,这个数组中存放的是对应按键要延时的时间值。
获取按键定时器时间就是获得这个数组中的值,表示按键会延时多久,设置定时器事件就是给这个数组设置值,表示按键要要是多久。
对于按键定时器的处理中,对两个定时器都要判断,首先要判断定时器的值不是0,说明该按键在延时,同时再判断当前系统的时间值是否大于这个定时的值,如果小于说明定时没到,大于则说明到了。
定时到了之后,要构建输入事件InputEvent
,并且放入到输入事件的环形缓冲区中,上报给上层,任何将定时器值赋值为0,表示该定时器现在不用了。
- 上报输入事件的工作在这里干了,不用在按键中断函数中上报了!!!
那么是谁来设置这个定时器时间呢?按键中断函数:
如上图所示按键中断函数的回调函数中,每发生一次按键中断,就给对应按键的定时器timers[i]
在当前时间基础上增加20作为新的定时时间。
- 此时中断函数不再负责上报输入事件了,只负责设置定时器事件。
- 当抖动时,中断会产生多次,定时时间也会不断推后,每次推后20。
现在定时器已经设置好了,处理定时器的方法也有了,那么是谁去处理定时器并上报事件呢?SysTick
处理函数:
如上图所示,在SysTick_Handler
函数中调用按键定时器处理函数,相当于在让系统调用这个处理函数,因为这个SysTick_Handler
属于裸机内核的函数。
🥞整体效果展示
将串口调试助手打开,程序烧录到开发板中并且上电:
如上图所示,此时串口助手会打印很多调试信息,之后最后出现OK
等字眼,说明程序启动成功。
如上图,此时在OLED屏幕上会显示ESP8266
模块的IP地址端口号,以及项目作者。
如上图所示,在微信小程序上搜索“百问网嵌入式物联网”,使用百问网已经做好的配套小程序,将OLED屏幕上显示的IP地址和端口号填进去,然后提交。
如上图所示,在小程序中点击3种颜色的灯,通过串口调试助手的调试信息可以看到:
如上图,可以看到受到了3个灯的Json
格式数据,程序中会解析这几个数据并且控制相应的设备:
如上图,此时三个灯就都亮了,同样可以使用两个按键控制白灯和蓝灯。
风扇也可以通过小程序界面去控制,还有离家模式和回家模式,本喵就不一一展示了。
如上图所示,也可以通过远端软件sscom
发送Json
格式的数据,如绿色框中所示,但是切记不要加回车换行,此时远端就会将数据发送给ESP8266
,然后通过串口调试助手打印接收到的信息,MCU会解析数据,进而控制风扇设备反转。
- 使用
sscom
发送的数据必须是Json
格式的,而且设备名也必须是文章开头提到的,因为这是我们和MCU之间的约定。- 在点下小程序的某个控制块后,它也会在后台发送对应的
Json
数据给ESP8266
,只是我们在手机上看不到,但是可以通过串口调试助手看到。
🥞项目总结
至此智能家居的项目裸机版本就实现了,整个项目中最重要的就是在传递:
- 面向对象的编程思想和方法
- 分层的思想和编程架构
无论使用哪种设备,哪种子系统,在业务子系统层面都不用关心它们的底层实现,只需要调用提供的结构就行,都是按照这样的顺序来用:
- 创建设备对象
- 添加所有设备
- 获取需要的设备并进行初始化
- 调用设备的操作方法。
几乎都是按照这样的顺序去使用,如果需要配合其他系统则添加相应的系统并初始化就可以了。
🥞源码
完整裸机版智能家居项目源码。