目录
一、项目思路简单阐述

并且这里使用了很多的通信协议,含金量还是很高的,并且具有很大的综合性,并且对modbus协议中的RTU方式进行了移植开发!
首先我们需要分配好中控所需寄存器的种类和数量:这个项目我已经提前总结出来了,关于寄存器的种类忘记了的可以看看:(详细的可以看我前面的博文,写的很详细)
项目寄存器所需; 所以经过计算,我们可以知道我们需要3个离散输入,15个线圈,4个输入寄存器
但是这个项目我还想通过上位机控制中控H5上的Led,所以我自己单独添加了一个线圈:
主要软件思路:低成本传感器中都是使用到了同样的STMF030芯片,并且板子上设计的GPIO都相同,所以我们只需要编写一个程序,经过定义不同的变量,从而达到不同功能的程序进行烧写,主要思路就是中控芯片H5中创建5个任务:
- 任务1:通过移植modbus和USB连接上位机,并且根据低成本传感器的所需寄存器的总和进行设计mapping和它们对应,如果上位机需要控制中控本身,就需添加所需要类别寄存器,上位机如果修改了mapping(可读可写),任务1就会检测到,根据上位机修改的mapping里面的寄存器类别和地址识别出对应的是哪个传感器的,然后唤醒和它“联系”的任务,然后和它“联系”的任务机会将中控直接连接的mapping修改后的值发送到传感器,以此达到上位机控制传感器的效果。
- 任务2:对应传感器1开关量模块,就是上面所说的和传感器“联系”的任务,被唤醒后我会将任务1的mapping里面我所使用的空间发送给我“联系”的传感器,修改它的mapping。并且我会读取传感器上的只可读传感器,如果他们的值改变了(比如,按键被按下),我就会将读取到的值memcpy到任务1的mapping里面对应的空间。
- 任务3:对应传感器2环境检测模块,就是上面所说的和传感器“联系”的任务,被唤醒后我会将任务1的mapping里面我所使用的空间发送给我“联系”的传感器,修改它的mapping。并且我会读取传感器上的只可读传感器,如果他们的值改变了(比如,光敏电阻ADC、可调节电阻器ADC改变),我就会将读取到的值memcpy到任务1的mapping里面对应的空间。
- 任务4:对应传感器3温湿度检测模块,和前面的任务3的功能几乎相同,只不过我就将温湿度的值就会将读取到的值memcpy到任务1的mapping里面对应的空间。
- 还有一个默认任务就是使用到USB必须使用的函数
- 任务2~4还有的功能就将值显示在屏幕上面
主机中控思路可以使用一个图来总结:
任务间的通知使用的是信号量,开关量传感器和环境检测传感器都是通过中控的UART2,温湿度则是通过UART4,所以开关量传感器和环境检测传感器对应的两个任务之间需要有资源保护,不然的话就会有bug,资源保护使用了互斥量,保证同一时间内只能有一个任务使用串口2进行发送。同时涉及临界区的还有设置modbus从机的ID,因为经过多次的轮转可能有一定的概率出现在任务2设置了ID后,然后到任务3运行了,并且任务3设置了ID,然后又轮到任务2运行了,这就会导致任务2后面的操作就是对其他从机通信!
传感器作为从机软件思路: 传感器的话就相对简单了,通过不同的变量定义综合成一个程序:
然后后面就是将外设中的数据传入到传感器中的mapping中,还有就是根据mapping中的对应的值(根据自己设计)开关灯,开关蜂鸣器,开关继电器等,这里以温湿度传感器为例子,这个有点特殊,因为我是用的是AHT20温湿度传感器,根据它的时序,启动温度转换需要delay80ms,如果主机想要读取温度度的话,传感器作为从机收到信号后才开始启动温湿度转换,花费的时间太长(里面有delay),所以主机迟迟得不到回复,就会出错(超时错误),所以我就再创建了一个任务单独用来温湿度的转换,将温湿度转换得到的结果存在两个全局变量上,这样从机就可以直接从全局变量中读取温湿度回复给主机!
二、硬件清单
1、STM32H563RIV开发板作为中控
2、低成本传感器(传感器开发套件共有三,三个板子的使用的主控方案是 STM32F030 芯 片)
2.1、开关量模块 2.2、温湿度变送器模块
2.3、环境检测模块
三、串口USB编程最终修改(综合)
前面因为H5才使用了2个串口和USB而已,所以我就将串口2和串口4都分别写了一套函数,后面发现这样代码灵活性不是很高,因为假如我们项目使用到了很多串口,那么我们不是就要整理出很多套函数吗,所以后面我就将代码改进了,所有串口共用一套函数,经过观察 :
两套代码中函数不一样的地方还是很多的,比如串口、队列、信号量以及开始接收函数的buf都不一样,所以我就将这些统归私有数据,并且每一个串口都有自己的私有数据,因为涉及变量很多,将其定义成 一个结构体:
我们给每一个串口设备都单独定义一个私有数据,使用的时候从里面拿就行了! 初始化:
所以经过改造,我就将函数改造成这个样:
四、modbus和我们的驱动联系移植
其实modbus我们也是使用串口的思方法,不同串口和USB之间使用的后端都是一样的,就像所有的串口使用的函数都是一套的一样,不一样的是私有函数,modbus也是如此,里面结构体也有一个backend_data类型成员,我们可以在里面添加一个成员——UART_Device* 类型!后端函数呢,里面就是根据不同的backend_data使用相同的一套串口驱动函数,但是由于backend_data的新加的成员不一样,里面执行的也不一样。
具体阐述:
那么我们modbus怎么使用到我们上面自己编写的驱动呢?因为modbus中里面的函数参数是已经定死的,比如:
那么我们只能在ctx里面操作一下,让modbus可以使用到我们的驱动,所以我是单独给modbus后端私有数据添加一个成员,而且成员正是UART_Device,所以我们可以在modbus_new_st_rtu时候添加一些操作:
然后就可以根据不同的ctx取出里面的这个成员,然后使用里面的函数即可: 总体思路就是这样:
比如,我们使用串口2:
然后 modbus_new_st_rtu就会找到结构体
并且将他填入到ctx里面去,我们使用modbus的时候,参数中有ctx类型,我们可以从里面取出这关于串口2的设备结构体,我们就直接使用里面的函数即可!
五、增加容错率和修改bug
5.1、串口增加容错率
串口增加容错率其实相对简单和容易发现的,每次突发断开后,串口重定义和重新使能串口接收就行了就可以了(从机也是如此修改):
5.2、modbus增加容错率(*)
因为在项目设计中,开关量传感器和环境检测传感器公用一个主机(H5)的的串口2,这两个传感器接收的报文都是主机H5通过串口2发送的,并且主机中有两个线程任务对接这两个传感器,虽然已经使用互斥量互斥使用串口资源了,但是我忽略了一个场景:——任务2(对接开关量的线程)发送完一个报文后,刚好轮转到任务3运行了(对接环境监测的线程),并且任务3也发送了一个报文,所以就会导致:
modbus默认有两个等待时间,一个是
-
_RESPONSE_TIMEOUT 请求发出后等待回复时,最多等待多久
-
_BYTE_TIMEOUT 开始得到数据后,后续没有数据了,等待多久退出
-
两个默认都是500ms

- _RESPONSE_TIMEOUT和_BYTE_TIMEOUT都修改小一点(我修改为10ms)
- 每次发送报文前都delay让传感器方进入超时状态,自动丢弃数据,重新接收报文。
修改例子:
5.3、串口驱动bug修复
第一,就是发现有时候数据会丢失(程序偶尔会出错),是因为我们前面编写串口驱动的时候,忽略了一个细节,写的不够严谨:
我们没有判断是因为什么中断事件进中断的,每次都重新启动DMA串口中断,就有可能数据丢失,所以为了两全其美的办法就是,只有空闲中断进来才会重新启动DMA串口中断(数据传输到一半贸然重新启动DMA串口中断的过程内可能会丢失数据),空闲中断和数据传输到一半中断都会将数据储存在队列里面(可以尽可能及时通知,不然只有空闲中断或者全部接收完成中断才储存进队列的话,应用程序就相对慢一点GET到数据了)。
还有就是我们使用串口DMA传输数据,只有当数据传输到一半或者空闲中断或者全部接收完成中断才会将数据储存到队列中,程序才能Get到数据,这是需要时间的,如果传输比较大的数据的时候就会发生超时错误
比如我们一次性接收200个数据,最早就是数据传输到一半的时候或者数据没有储存到一半没有数据来了发生空闲中断才会将数据储存到队列中,程序才能Get到数据,但是我们modbus之间通信是会超时的,所以我们需要严格写好这两个数据的值:
-
_RESPONSE_TIMEOUT 请求发出后等待回复时,最多等待多久
-
_BYTE_TIMEOUT 开始得到数据后,后续没有数据了,等待多久退出

就可以完美的预防了上面的情况。