开始本篇之前我想先谈一下为什么要把一个ModBus主机分成4篇来写,一是代码的分层理念,随着工作时间的增长以及工作的深入你会发现分层是那么的至关重要不仅仅是代码的可读性更重要的是更方便的维护。初期写代码一个文件中甚至一个函数中既有功能代码又有底层代码,当增删功能或者平台移植的时候都不知道去哪里哭,分层也许在初期会增加代码量显得很麻烦但是当你的架构建立起来之后会变得十分方便,解决问题得心应手。操作系统帮你做好了底层的分层和软件层面的任务调度,但是应用层面依然需要个人来做好。分层理念需要时时有处处有。二是ModBus的一对多特性,当一个主机任务读取某个从机的数据并等待从机应答时,必须保证别的主机任务不要来动ModBus总线,如果有个不长眼的主机任务过来咔咔操作了总线那么之前等待从机应答的任务就崩溃了。可能有人说了等待应答的时候我直接while死等不就行了,直接断了别的主机任务的念想,当然这样是可以解决问题的不过要是某个从机不在线就会导致整个系统卡死一段时间,要是你用了操作系统还好如果是裸机那就非常影响用户体验并且会使其他任务比如刷屏按键出现一些莫名其妙的问题,我们都知道人的新陈代谢越快身体越强壮,同样任务轮询越快系统也越强壮。无论你使用操作系统还是裸机都要避免使用阻塞式的写法。所以我们才大费周章分4层来解决一个ModBus主机问题。本篇调度篇内容很少但是整个ModBus主机系统的重中之重。
我们先来看代码。
- #ifndef __RS485_H
- #define __RS485_H
- #include "Header.h"
- #include "THP.h"
- #include "EPC.h"
- #include "Delay.h"
- extern uint16_t RS485BusSilentTime1;
- void RS485Device1Init(void);
- void RS485Device1Handle(void);
- #endif
复制代码- #include "RS485Device.h"
- uint16_t RS485BusSilentTime1;
- void RS485Device1Init(void)
- {
- THPInit();
- EPCInit();
- }
- static uint8_t TaskHandle(uint8_t En, uint8_t (*Task)(void*), void *Dev, uint8_t *BusTake, uint8_t TaskID, uint16_t *DelayTime)
- {
- if((En!=0) && ((*BusTake==0)||(*BusTake==TaskID)))
- {
- if(Task(Dev) != 0)
- {
- /*释放总线*/
- *BusTake = 0;
- Set_Delay_Time(10,DelayTime);
- }
- else
- {
- /*占用总线*/
- *BusTake = TaskID;
- return 1;
- }
- }
- return 0;
- }
- void RS485Device1Handle(void)
- {
- static uint8_t BusTake=0;
-
- if(CheckDelay(&RS485BusSilentTime1) == 0)
- {
- if(TaskHandle(EPC1.S_P_En,SetPreVal,&EPC1,&BusTake,1,&RS485BusSilentTime1) != 0)
- {
- return;
- }
- if(TaskHandle(THP1.Enable,ReadTHP,&THP1,&BusTake,2,&RS485BusSilentTime1) != 0)
- {
- return;
- }
- if(TaskHandle(EPC1.R_En,ReadEPC,&EPC1,&BusTake,3,&RS485BusSilentTime1) != 0)
- {
- return;
- }
- }
- }
复制代码 通过代码可以看到该层引用了任务篇创建的"THP.h"和"EPC.h",在这里我们要对三个主机任务进行调度。
首先来看函数“static uint8_t TaskHandle(uint8_t En, uint8_t (Task)(void), void *Dev, uint8_t *BusTake, uint8_t TaskID, uint16_t *DelayTime)”;
该函数参数比较多,分别为任务使能信号(uint8_t En)、任务主体(uint8_t (Task)(void))、设备名称(void *Dev)、是谁在使用总线(uint8_t *BusTake)、任务编号(uint8_t TaskID)、总线静默时间控制(uint16_t *DelayTime);
该函数逻辑很简单,就是判断任务使能信号是否置位了并查看总线被哪个任务占用,若总线空闲或者被自己占用就去占用总线去执行任务主体函数,若任务主体函数返回0说明正在等待从机应答接着占用总线并返回1告知调度器我还没用完,否则说明任务完成了释放总线并给总线插入10ms的静默时间然后返回0告诉调度器我完事了让别人来吧。
函数“void RS485Device1Handle(void)”就是最终的调度器它在main函数的while循环中被执行,它首先实现10ms的总线静默然后挨个询问主机任务的当前状态并满足他们的需求。
至此,我的ModBus主机就完结了。
|