不用代码可以做网站设计吗,网站建设计划建议,手机图片制作软件免费,wordpress外贸主题用哪个好#x1f431;作者#xff1a;一只大喵咪1201 #x1f431;专栏#xff1a;《智能家居项目》 #x1f525;格言#xff1a;你只管努力#xff0c;剩下的交给时间#xff01; 目录 #x1f3c0;项目简介#x1f3c0;输入子系统(按键)⚽应用层⚽设备层⚽ 内核层抽象层⚽… 作者一只大喵咪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供应用层在初始化所有设备时候调用。 对于裸机程序事件获取方法不用注册到设备队列中而是在后面中断函数中调用。 此时已经实现了按键的设备层包括按键设备的实例化按键设备的初始化方法以及增加按键设备的方法。
⚽ 内核层抽象层
本喵想让这个系统支持多个系统包括裸机FreeRTOSRT-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代码的基础上写的包含源码以及串口调试工具需要的小伙伴自取传送门。
总结
这篇文章实现了智能家居项目中输入子系统中的按键设备最重要的是介绍的代码框架和编程思想之后的项目部分都会按照这个思路来扩展维护。