【智能家居项目】裸机版本——项目介绍 | 输入子系统(按键) | 单元测试
🐱作者:一只大喵咪1201
🐱专栏:《智能家居项目》
🔥格言:你只管努力,剩下的交给时间!
目录
- 🏀项目简介
- 🏀输入子系统(按键)
- ⚽应用层
- ⚽设备层
- ⚽ 内核层抽象层
- ⚽芯片抽象层
- ⚽硬件操作
- 🏀按键单元测试
- ⚽串口
- ⚽测试
- 🏀源码
- 🏀总结
在这个专栏中,本喵要实现一个智能家居的小项目,先基于HAL库实现裸机版本,之后再实现一个RTOS版本,为了无缝实现从裸机到RTOS的移植以及维护,本喵会使用面向对象的思想,将整个项目分层来实现,构建一种编程架构。
本项目重点:
- 设计出优秀的程序框架:容易扩展、容易维护。
- 具体:
- 把项目拆分为各个子系统。
- 使用面向对象的思想,把子系统抽象为结构体。
- 编写函数时,有一定的封装细节,看函数名就知道怎么用,不需要深入函数内部看它的实现。
🏀项目简介
如上图,使用百问网的STM32F103ZET6
开发板,实现:
- 开发板启动后,自动连接家里的路由器,在OLED上显示出IP。
- 手机上启动微信小程序,输入开发板OLED上显示的IP,连接开发板。
- 在微信小程序里,点击图标控制开发板的LED、风扇。
如上图所示,在程序设计过程中,分为几个层次:
- 第1层:软件系统,就是整个系统、整个程序。
- 第2层:分解为子系统,比如我们可以拆分为:输入子系统、显示子系统、业务系统。
- 第3层:分解为类,在C语言里没有类,可以使用结构体来描述子系统。
- 第4层:分解成子程序,实现那些结构体中的属性和方法(结构体中有函数指针)。
如上图所示,在本项目中,可以分为6个子系统:
- 设备子系统:比如实现LED控制、风扇控制。
- 显示子系统:在OLED上显示信息。
- 输入子系统:可以接收按键数据、网络数据。
- 网络子系统:负责网络连接、数据收发。
- 字体子系统:获得字符的字库。
- 业务子系统:起综合作用,根据输入值(网络数据),控制设备。
其中业务子系统包含其余5个子系统,可以看作是上层,并且同样也可以看作一个子系统。
🏀输入子系统(按键)
首先来实现输入子系统,它可以接收来自按键,网络,标准输入等设备的数据,然后供上层业务子系统去使用。整个输入子系统划分为五个层次实现,这里本喵仅实现按键一个输入设备。
⚽应用层
- 对于传递的"数据数据",我们把它称为"输入事件"。
如上图,在input_system.h
输入子系统头文件中定义输入事件结构体,用来描述发生的输入事件,无论是按键输入还是网络以及标准输入,都会创建一个这样的结构体对象,但是INPUT_EVENT_TYPE
不同,只有根据该成员变量的值才可以确定发生了哪种输入,通过其他成员变量可以获取到需要的事件属性,比如发生事件,按键编号,以及字符串数据等等。
输入事件类型有多种,在这个项目中并不会用到触摸屏输入,本喵这样写是为了表明拓展维护的方便性,在输入子系统层面,需要增加输入事件类型,以及描述输入事件的结构体InputEvent
中增加触摸屏触摸的位置。
接下来就是输入事件的来源了,从框图中看到有按键输入,网络输入,标准输入,以后甚至可以扩展更多的输入来源,这些输入来源产生输入事件。
- "输入事件"由"输入设备"产生。
如上图,在input_system.h
输入子系统头文件中定义输入设备结构体,用来描述输入设备,每一个设备都会创建一个这样的结构体对象,其中包含设备的名称,获取输入事件,初始化设备,去初始化设备等方法,以及下一个设备节点的指针。
每一个设备都自带获取输入事件的方法,也就是获取InputEvent
对象的函数,站在输入子系统的层面,它并不关心该方法是如何实现的,只在需要获取输入数据的时候直接调取该方法即可。
包括初始化和去初始化也是设备自带的方法,上层只需要直接调用即可,至于去初始化是在不需要某个设备的时候,将其配置恢复到初始化状态,从系统中抹除该设备。
为了管理多个设备,本喵将其放在一个链表中,所以还有一个pNext
指针指向下一个设备节点。
- 在输入子系统层面,并不关心获取输入事件函数是如何实现的,而且该函数的实现涉及到了硬件底层,所以并不在子系统层面实现。
如上图,在input_system.c
源文件中,创建一个全局链表,用来让输入子系统管理输入设备,并且实现注册输入设备,增加输入设备,初始化所有输入设备等函数。
注册输入设备的本质就是将新增加的输入设备节点插入到链表中,让输入子系统能够通过操作链表来维护使用输入设备。
增加输入设备也是输入子系统要处理的事情,在增加输入设备函数中再调用增加具体输入设备的函数,需要增加多少输入设备,就将对应设备的增加函数放进去。
初始化所有输入设备的时候,只需要变量链表中的设备节点,调用每个设备节点自带的初始化化函数即可。
- 这三个函数要在
input_system.h
中声明。
无论是一个输入设备还是多个输入设备,所产生的数据并不只一个,但是使用者只有输入子系统一个,为了防止数据丢失,所以这些数据也需要维护起来,这里使用环缓冲区列来维护,主要有输入事件产生,就将相应的InputEvent
对象放入环形缓冲区中,子系统只需要从环形缓冲区读取数据就可以,不用关心数据是怎么来的。
如上图所示,环形缓冲区本质上也是一个数组,就拿写来说,当这个数组被写满后ring_buffer[7] = data
,就通过取模运算pW = (7 + 1) % 8 = 0
重新从数组的起始位置开始写数据,读也是类似的道理。
- pR是向环形缓冲区读数据时的下标。
- pW是向环形缓冲区写数据时的下标。
通过pR
是否等于pW
来判断环形缓冲区中是否有数据,没有数据就相等,有数据就不相等,同样通过pW
是否等于pR
来判断环形缓冲区是否写满数据,相等就写满了,不相等就没写满。
如上图所示,定义环形缓冲区结构体,通过维护pW
和pR
来维护环状,以及从存放输入事件的buffer
中读写事件。
输入子系统还需要提供读写数据的方法:
如上图所示,创建一个全局的环形缓冲区对象,由于是静态全局变量,且没有初始化,所以编译器会用0去初始化,并放在未初始化数据段,读写事件都是在操作这个全局的环形缓冲区。
此时,输入子系统已经具有了上图所示结构以及对应的操作方法,输入子系统的层就完成了,到目前位置丝毫没有提及到和STM32F103ZE
开发板有关的内容,连一句相关的代码也没有,实现了应用层和硬件的解耦。
⚽设备层
此时输入子系统中的上层部分已经完成了,还需要处理输入子系统设备层,这里本喵仅实现按键输入设备:
如上图所示,在gpio_key.h
中定义了两个按键的编号,之后直接使用即可。
如上图所示,在gpio_key.c
中实例化出一个按键对象,并进行初始化,赋值设备名,初始化函数等,还要提供一个增加按键设备的函数AddInputDeviceGPIOKey
供应用层在初始化所有设备时候调用。
- 对于裸机程序,事件获取方法不用注册到设备队列中,而是在后面中断函数中调用。
此时,已经实现了按键的设备层,包括按键设备的实例化,按键设备的初始化方法,以及增加按键设备的方法。
⚽ 内核层抽象层
本喵想让这个系统支持多个系统,包括裸机,FreeRTOS,RT-Thread,甚至是Linux,这里将裸机也看作是一种内核。
不同内核下的数据来源:
- 裸机:数据来自中断,在中断中解析数据并放入环形缓冲区。
- RTOS:创建任务,在任务中解析数据并放入环形缓冲区。
内核抽象层中,根据不同的内核对按键进行初始化,本喵这里仅实现裸机的按键初始化:
如上图,初始化按键的时候,调用KAL_GPIOKeyInit
,在函数内部再调用不同内核对按键的初始化函数,对于裸机则调用芯片层的CAL_GPIOKinit
函数进行初始化,如果是RTOS,则仅需要将该函数改成对应的初始化函数即可。
- 设备抽象层调用的是该层的
KAL_GPIOKeyInit
,根本不关心具体的实现逻辑。
在描述输入事件的结构体InputEvent
中有一个time
成员变量用来记录事件发生的事件,而这个时间在不同的内核中表现方式不同,所以在内核抽象层需要实现获取时间的函数。
如上图,在使用的时候,直接调用内核抽象层的KAL_GetTime
获取时间即可,在该函数内部,根据具体的获取方式调用对应的函数。
如本喵使用的STM32F103ZET6
是通过滴答定时器来获取时间的,需要获取芯片中寄存器的值,所以要调用CAL_GetTime
从芯片获取时间。
对于Linux,它在系统内部会记录着时间,此时就可以直接返回时间,不用再向下调用。
此时,内核抽象层也实现了,设备层会调用内核抽象层的初始化函数。
⚽芯片抽象层
项目的最终实现需要依托具体的芯片,本喵用的STM32F103ZET6
是支持HAL
库的,但是也有一些芯片并没有HAL
库,需要用它自己的库来操作,所以在这一层要实现对不同类型芯片的支持。
如上图,在芯片抽象层会调用CAL_GPIOKeyInit
来初始化按键,在函数内部根据不同的芯片再调用它对应的初始化函数,如ST芯片就调用KEY_GPIO_ReInit
。
同样,不同芯片获取时间的方式也不同,这里也要实现针对不同芯片获取时间的方式:
如上图所示,从芯片寄存器中获取时间的时候,对于ST芯片,调用HAL_GetTick
获取即可,对于其他芯片,放入对应的获取方式即可。
此时芯片抽象层也实现了,内核抽象层会调用该层的CAL_GPIOKeyInit
初始化按键。
⚽硬件操作
本喵使用的是STM32F103ZET6
芯片,使用CubeMX
和HAL
库进行按键初始化,在初始化的时候,要在中断函数中进行输入数据的读取,并放入环形缓冲区中。
如上图,在driver_key.h
中进行一些芯片的资源定义,方便后面使用。
如上图,使用HAL
库对按键进行初始化,在按键中断函数中处理输入事件InputEvent
并且放入到环形队列中
此时,具体芯片的硬件配置也设置好了,输入子系统中按键设备就完全写好了。
如上图,现在整个代码结构是这样,其中智能家居项目部分全部放在了smartdevice
文件夹中,包含输入子系统的应用层,设备抽象层,内核抽象层,芯片抽象层。
其余部分是通过CubeMX
进行的基本外设配置,整个输入子系统中,只有在硬件操作的时候会用到这里的配置,其余四层都是独立的,不存在耦合。
🏀按键单元测试
⚽串口
为了观察按键按下后的现象,使用串口将发生的输入事件InputEvent
打印出来,此时串口配置并不属于我们实现的输入子系统,只是一个调试工具,直接使用HAL
库配置就可以。
如上图所示是串口的头文件,只包含串口的使能和失能函数声明。
如上图所示是串口的具体配置函数,这里同样需要一个环形缓冲区,这里本喵就不展示它的实现了,后面本喵会放源码。
在调用EnableDebugIRQ
打开串口后,在向串口发送数据的时候,直接调用printf
即可,因为printf
底层会调用fputc
函数,所以需要在这里将fput
重定向,使得printf
符合我们的要求。
在fputc
中,先将发送完成标志清0,然后调用HAL
库的中断发送函数发送一个字节,当发送完成标志位为0时就一直等待,说明没有发送完成。这个字节发送完成以后,会进入串口的发送中断回调函数,在中断函数中将发送标志位置1,让fputc
退出循环等待。printf
发送多个字节就调用多次fputc
。
在获取串口发送来的数据时,直接调用scanf
即可,因为scanf
底层会调用fgetc
函数,所以也需要重定向fgetc
函数,使得scanf
符合我们的要求。
当串口上有数据到来时,会发生串口中断,通过判断SR
寄存器的第五位确定是接收到了数据,并且将接收到的数据放入到环形缓冲区中。fgetc
直接从环形缓冲区中读取数据。
- 为了像在PC端一样使用标准库中的
printf
和scanf
,必须重新实现fputc
和fgetc
函数,让终端变成串口,符合我们的要求。
⚽测试
为了看我们设计的输入子系统是否正确,需要专门写一个单元测试函数来测试一下:
如上图所示,将按键设备添加到输入子系统中,然后进行初始化,在while(1)
循环中读取输入事件,并通过串口打印输入事件的信息。
在main
函数中调用该测试函数,通过串口调试助手查看打印信息:
如上图,将板子的串口和电脑连在一起后,通过串口调试助手可以看到,当按键1或者按键2按下后,会打印出发生的事件信息,包括事件类型,发生事件,按键编号,以及按键值,说明设计的输入子系统是成功的。
🏀源码
这部分代码是在OLED代码的基础上写的,包含源码以及串口调试工具,需要的小伙伴自取传送门。
🏀总结
这篇文章实现了智能家居项目中输入子系统中的按键设备,最重要的是介绍的代码框架和编程思想,之后的项目部分都会按照这个思路来扩展维护。
相关文章:
【智能家居项目】裸机版本——项目介绍 | 输入子系统(按键) | 单元测试
🐱作者:一只大喵咪1201 🐱专栏:《智能家居项目》 🔥格言:你只管努力,剩下的交给时间! 目录 🏀项目简介🏀输入子系统(按键)⚽应用层⚽设备层⚽ 内核层抽象层⚽…...
算法练习8——有序三元组中的最大值
LeetCode 100088 有序三元组中的最大值 I LeetCode 100086 有序三元组中的最大值 II 给你一个下标从 0 开始的整数数组 nums 。 请你从所有满足 i < j < k 的下标三元组 (i, j, k) 中,找出并返回下标三元组的最大值。如果所有满足条件的三元组的值都是负数&am…...
git创建
问: git remote add origin https://github.com//blog.git fatal: not a git repository (or any of the parent directories): .git 回答: 这个错误提示指出当前目录或其父目录中不存在.git文件夹,因此无法执行git相关操作。请确保你是在一个已经初始化为git仓库…...
yolov8 opencv模型部署(python版)
yolov8 opencv模型部署(python版) 使用opencv推理yolov8模型,以yolov8n为例子,一共几十行代码,没有废话,给出了注释,从今天起,少写一行代码,少掉一根头发。测试数据有需…...
Simulink仿真封装中的参数个对话框设置
目录 参数和对话框窗格 初始化窗格 文档窗格 为了更加直观和清晰的分析仿真,会将多个元件实现的一个功能封装在一起,通过参数对话框窗格,可以使用参数、显示和动作选项板中的对话框控制设计封装对话框。如图所示: 参数和对话框…...
【C++】class的设计与使用(十)重载iostream运算符
希望对某个类对象进行读写操作,直接cout<<类对象<<endl;或cin>>类对象;编译器会报错,所以我们必须提供一份重载的input/output运算符: 重载ostream运算符 ostream& operator<<(ostream &os, const Triangu…...
Java使用Scanner类实现用户输入与交互
概述: Scanner类是Java中的一个重要工具类,用于读取用户的输入。它提供了一系列的方法,可以方便地读取不同类型的数据,如整数、浮点数、字符串等。在本文中,我们将详细介绍Scanner类的使用方法,并通过两个…...
FFmpeg 命令:从入门到精通 | ffppeg 命令参数说明
FFmpeg 命令:从入门到精通 | ffmpeg 命令参数说明 FFmpeg 命令:从入门到精通 | ffmpeg 命令参数说明主要参数音频参数视频参数更多参考 FFmpeg 命令:从入门到精通 | ffmpeg 命令参数说明 本节主要介绍了 ffmpeg 命令的常用参数。 主要参数 …...
Chrome(谷歌浏览器)如何关闭搜索栏历史记录
目录 问题描述解决方法插件解决(亲测有效)自带设置解决步骤首先打开 地址 输入:chrome://flags关闭浏览器,重新打开Chrome 发现 已经正常 问题描述 Chrome是大家熟知的浏览器,但是搜索栏的历史记录如何自己一条条的删…...
基于Java的宠物医院管理系统设计与实现(源码+lw+部署文档+讲解等)
文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding)有保障的售后福利 代码参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...
使用WPS自动化转换办公文档: 将Word, PowerPoint和Excel文件转换为PDF
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
对pyside6中的textedit进行自定义,实现按回车可以触发事件。
我的实现方法是,先用qt designer写好界面,如下图: 接着将其生成的ui文件编译成为py文件。 找到里面这几行代码: self.textEdit QTextEdit(self.centralwidget)self.textEdit.setObjectName(u"textEdit")self.textEdit…...
Spark SQL
Spark SQL 一、Spark SQL概述二、准备Spark SQL的编程环境三、Spark SQL程序编程的入口四、DataFrame的创建五、DataFrame的编程风格六、DataSet的创建和使用七、Spark SQL的函数操作 一、Spark SQL概述 Spark SQL属于Spark计算框架的一部分,是专门负责结构化数据的…...
初识多线程
一、多任务 现实中太多这样同时做多件事的例子了,例如一边吃饭一遍刷视频,看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件事情。 二、普通方法调用和多线程 普通方法调用只有主线程一条执行路径 多线程多条执行路径…...
Linux用户、用户组和文件权限的管理与实践
目录 一、Linux用户、用户组和文件权限的基础概念与作用1.1 Linux用户的概念与作用1.2 Linux用户组的概念与作用1.3 Linux文件权限的概念与作用 二、Linux用户、用户组和文件权限的具体操作实践2.1 创建新用户:从零开始构建用户体系2.2 修改用户和用户组属性&#x…...
【CMU15-445 Part-14】Query Planning Optimization I
Part14-Query Planning & Optimization I SQL is Declarative,只告诉想要什么而不需要说怎么做。 IBM System R是第一个实现query optimizer查询优化器的系统 Heuristics / Rules 条件触发 静态规则,重写query来remove 低效或者愚蠢的东西…...
七、垃圾收集中级
JVM由浅入深系列 JVM由浅入深系列一、关于Java性能的误解二、Java性能概述三、了解JVM概述四、探索JVM架构五、垃圾收集基础六、HotSpot中的垃圾收集七、垃圾收集中级八、垃圾收集高级👋垃圾收集中级 ⚽️1. 权衡收集器插件 就 Java 平台而言,有一点可能初学者未必能马上意…...
el-menu 导航栏学习(1)
最简单的导航栏学习跳转实例效果: (1)index.js路由配置: import Vue from vue import Router from vue-router import NavMenuDemo from /components/NavMenuDemo import test1 from /components/test1 import test2 from /c…...
Axios请求封装
安装axios,在net文件下新建index.js,封装InternalPsot请求: function internalPost(url,data,header,success,failure,errordefaultError()){axios.post(url,data,{headers:header}).then(({data})>{if (data.code200){success(data.dat…...
Pikachu靶场——XXE 漏洞
文章目录 1. XXE1.1 查看系统文件内容1.2 查看PHP源代码1.3 查看开放端口1.4 探测内网主机 1. XXE 漏洞描述 XXE(XML External Entity)攻击是一种利用XML解析器漏洞的攻击。在这种攻击中,攻击者通过在XML文件中插入恶意实体来触发解析器加载…...
vscode登录租的新服务器
1.connect to…… 选择 connect current window to host 2.configure SSH Host 选择本地配置文件 打开配置文件,把主机名端口号写进去 再返回vscode远程登录页面,左侧栏就会出现这个主机名了。...
Verilog参数定义与仿真模块中的参数修改
文章目录 参数方式定义参数的优势rtl模块中的参数定义模块名后定义参数parameter定义参数 仿真模块中的参数修改例化时修改defparam修改 总结与说明附录:测试代码 参数方式定义参数的优势 当一个模块被另一个模块引用例化时,高层模块可以对低层模块的参…...
Android studio升级Giraffe | 2022.3.1 Patch 1踩坑
这里写自定义目录标题 not "opens java.io" to unnamed module错误报错信息解决 superclass access check failed: class butterknife.compiler.ButterKnifeProcessor$RScanner报错报错信息解决 Android studio升级Giraffe | 2022.3.1 Patch 1后,出现项目…...
使用U3D、pico开发VR(二)——添加手柄摇杆控制移动
一、将unity 与visual studio 相关联 1.Edit->Preference->External tool 选择相应的版本 二、手柄遥控人物转向和人物移动 1.添加Locomotion System组件 选择XR Origin; 2.添加Continuous Move Provider(Action-based)组件 1>…...
【FPGA项目】图像采集及显示(2)详细设计方案
目录 前言 一、视频流采集设计 二、DDR3缓存控制 三、FIFO 设计 四、VGA显示器驱动设计...
查找排序部分习题 242. 有效的字母异位词 74. 搜索二维矩阵 1. 两数之和 167.两数之和 II
242. 有效的字母异位词 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。 class Solution(object):def isAnagram(self, s, t):""…...
MATLAB算法实战应用案例精讲-【优化算法】冠状病毒优化算法(COVIDOA)(附MATLAB代码实现)
目录 前言 知识储备 1 冠状病毒群体免疫优化算法...
React查询、搜索类功能的实现
React查询、搜索类功能的实现 查询之类的如果是通过向列表接口中发送对应参数来查询的,那么在默认输出时,在useEffect钩子中的请求中可以先为需要查询的请求参数设初始的state,也就是null或者未定义,这样的话初始请求的还是整个列…...
k8s搭建EFK日志系统
搭建 EFK 日志系统 前面大家介绍了 Kubernetes 集群中的几种日志收集方案,Kubernetes 中比较流行的日志收集解决方案是 Elasticsearch、Fluentd 和 Kibana(EFK)技术栈,也是官方现在比较推荐的一种方案。 Elasticsearch 是一个实…...
LuatOS-SOC接口文档(air780E)-- fonts - 字体库
fonts.list(tp) 返回固件支持的字体列表 参数 传入值类型 解释 string 类型, 默认 u8g2, 还可以是lvgl 返回值 返回值类型 解释 table 字体列表 例子 -- API新增于2022-07-12 if fonts.list thenlog.info("fonts", "u8g2", json.encode(fonts…...
网站权重优化/做网站企业
原文链接:SLAM论文写作经验 | 小白、跨专业、无人指导、一年多从零到发顶会,他如何做到? 昨晚知识星球定期(每月6/16/26日)组织的内部私密直播:《SLAM方向顶会论文写作及发表经验分享》,引起SLA…...
阿里云网站怎么备案域名解析/预防电信网络诈骗
在Shell脚本中要经常做各种测试,测试语句的格式:(1)test (2) [](3) [[]]三种的区别,在第三种中可以进行通配符的匹配,而且&&,||,,操作符也可以正常的存在[[]]中,但是不能存在[]中。文件测试操作…...
做名片最好的网站是哪个/百度网站入口链接
惠普电脑如何设置光驱启动呢惠普hp pavilion g4 购买之后一直使用很好,最近重新安装系统想设置光盘启动。发现按照常规的F2 F12 del 等都不能进入Bios。最后才发现原来HP进入BIOS的.键是F10,下面是小编为大家收集的资料,一起来看看吧。惠普…...
做二手网站赚钱不/5151app是交友软件么
前言 本文我们来对Lucene具体如何进行数据的搜索,进行详细的介绍。 环境准备 我们直接使用在上一篇文章中的应用代码案例。 因为索引和存储两者是分开的,对于某一个字段我们可以建立索引,但是不存储,我们依然可以对此字段进行…...
富阳做网站公司/网站优化塔山双喜
一、Bootstrap 卡片(面板) 1.1 简单的卡片 我们可以通过 Bootstrap4 的 .card 与 .card-body 类来创建一个简单的卡片,实例如下: <div class"container"><div class"card"><div class"card-body">简单的卡片</…...
新一站保险网/放心网站推广优化咨询
接上文:http://blog.csdn.net/l241002209/article/details/72763774[mysqllocalhost MySQL-5.6.36-1.linux_glibc2.5.x86_64.rpm-bundle]$ mysql -uroot -p123456#设置远程访问#root是允许远程访问的账户名(可以是其他的),"192.168.7.200"表示…...