任务篇是建立在协议篇之上的。以我的项目为例,我有两个从机设备,一个是EPC(控制器控制电磁阀开度使电磁阀出口处的气压保持恒定)需要主机设定出口处气压和定时读取出口处实际气压;另一个是温湿压传感器变送器需要定时读取温湿度压数据。
我的理念是不管主机挂载几个从机设备,而是主机有几个任务。以上文为例,主机一共有三个任务,一是设定EPC出口处气压(写保持寄存器);二是读取EPC出口处气压(读输入寄存器);三是读取温湿压数据(读输入寄存器)。我们首先要建立这三个任务。我们根据从机名称来创建4个文件分别是"EPC.c"、"EPC.h"、"THP.c"和"THP.h"。按照惯例我们先来看"EPC.h"和"THP.h"。
- #ifndef __EPC_H
- #define __EPC_H
- #include "Header.h"
- #include "UartDebug.h"
- #include "Modbus_Master.h"
- #include "Delay.h"
- struct EpcPre
- {
- struct ModbusMasterDevice *MMD;
- uint8_t DeviceAddr;
- int16_t P_Fb,P_Sta;
- uint16_t Pset;
- uint8_t S_P_En,R_En;
- uint8_t DataUpdate;
- uint8_t SetSuccess;
- };
- extern struct EpcPre EPC1;
- void EPCInit(void);
- uint8_t ReadEPC(void *Para);
- void ReadEPFEnable(struct EpcPre *EPF);
- uint8_t SetPreVal(void *Para);
- void SetPreValEnable(struct EpcPre *EPC,uint16_t Pre);
- #endif
复制代码- #ifndef __THP_H
- #define __THP_H
- #include "Header.h"
- #include "UartDebug.h"
- #include "Modbus_Master.h"
- #include "Delay.h"
- struct TemHumPre
- {
- struct ModbusMasterDevice *MMD;
- uint8_t DeviceAddr;
- float AirTemp;
- float AirHun;
- float AirPre;
- uint8_t Enable;
- uint8_t DataUpdate;
- };
- extern struct TemHumPre THP1;
- void THPInit(void);
- uint8_t ReadTHP(void *Para);
- void ReadTHPEnable(struct TemHumPre *THP);
- #endif
复制代码 这两个文件非常相似,下面我们来详细介绍一下。
略过头文件我们来看从机结构体。
“struct ModbusMasterDevice *MMD;” 从机挂载到的ModBus主机;
“uint8_t DeviceAddr;” 从机的设备地址;
“int16_t P_Fb,P_Sta;” “float AirTemp;”“float AirHun;”“float AirPre;”数据区,根据从机的需求而定;
“uint8_t S_P_En,R_En;”“uint8_t Enable; ”任务的使能标志,可以看到因为EPC有两个任务所以它有S_P_En和R_En两个使能标志,而THP只有一个任务所以它只有一个Enable;
“uint8_t DataUpdate;”读保持/输入寄存器成功标志;
“uint8_t SetSuccess;”写保持寄存器成功标志,THP没有相对应得任务所以也就没有该标志。
接下来我们来看具体得任务实现。
- #include "EPC.h"
- struct EpcPre EPC1;
- void EPCInit(void)
- {
- EPC1.MMD = &MMDPort1;
- EPC1.DeviceAddr = 0x01;
- }
- uint8_t ReadEPC(void *Para)
- {
- struct EpcPre *EPC;
-
- EPC = (struct EpcPre *)Para;
- if(ReadHoldInputReg(EPC->MMD,EPC->DeviceAddr,0x04,0,4) != 0)
- {
- if(EPC->MMD->RIR_Update)
- {
- EPC->MMD->RIR_Update = 0;
- EPC->DataUpdate = 1;
- EPC->P_Fb = EPC->MMD->Input_Reg[0];
- EPC->P_Sta = EPC->MMD->Input_Reg[1];
- }
- EPC->R_En = 0;
- return 1;
- }
- return 0;
- }
- void ReadEPFEnable(struct EpcPre *EPC)
- {
- EPC->R_En = 1;
- }
- uint8_t SetPreVal(void *Para)
- {
- struct EpcPre *EPC;
-
- EPC = (struct EpcPre *)Para;
- if(WriteHoldReg(EPC->MMD,EPC->DeviceAddr,0,1,&EPC->Pset) != 0)
- {
- if(EPC->MMD->WHR_Success)
- {
- EPC->MMD->WHR_Success = 0;
- EPC->SetSuccess = 1;
- }
- EPC->S_P_En = 0;
- return 1;
- }
- return 0;
- }
- void SetPreValEnable(struct EpcPre *EPC,uint16_t Pre)
- {
- EPC->S_P_En = 1;
- EPC->Pset = Pre;
- }
复制代码- #include "THP.h"
- struct TemHumPre THP1;
- void THPInit(void)
- {
- THP1.MMD = &MMDPort1;
- THP1.DeviceAddr = 0x03;
- }
- uint8_t ReadTHP(void *Para)
- {
- struct TemHumPre *THP;
-
- THP = (struct TemHumPre*)Para;
- if(ReadHoldInputReg(THP->MMD,THP->DeviceAddr,0x04,0,3) != 0)
- {
- if(THP->MMD->RIR_Update)
- {
- THP->MMD->RIR_Update = 0;
- if(THP->MMD->Input_Reg[0] != 0xffff)
- {
- THP->AirTemp = (float)(THP->MMD->Input_Reg[0]-10000)/10;
- }
- if(THP->MMD->Input_Reg[1] != 0xffff)
- {
- THP->AirHun = (float)THP->MMD->Input_Reg[1]/10;
- }
- if(THP->MMD->Input_Reg[2] != 0xffff)
- {
- THP->AirPre = (float)THP->MMD->Input_Reg[2]/10;
- }
- }
- THP->Enable = 0;
- return 1;
- }
- return 0;
- }
- void ReadTHPEnable(struct TemHumPre *THP)
- {
- THP->Enable = 1;
- }
复制代码 整体来看,每个任务都分为任务主体和任务使能两个部分,任务主体由下一章要介绍的调度篇来调用,使能由用户来调用,比如THP需要每隔1S读取一次那么就使函数“void ReadTHPEnable(struct TemHumPre *THP)”每隔1S执行一次。
- static void Task_1000ms(void)
- {
- ReadTHPEnable(&THP1);
- }
复制代码 而EPC需要500ms读取一次气压,则使函数“void ReadEPFEnable(struct EpcPre *EPC)”每隔500ms执行一次。
- static void Task_500ms(void)
- {
- ReadEPFEnable(&EPC1);
- }
复制代码 对于写保持寄存器相关的任务我们可以顺道把要写的数据放在使能函数里,这样可以使写保持寄存器的任务主体函数更加简洁。
下面我们需要着重讲解一下任务主体的实现。
以读取EPC气压为例,任务会首先执行函数“ReadHoldInputReg()”并判断返回值,根据协议篇可以知道在等待从机应答的那段时间“ReadHoldInputReg()”会返回0,于是“ReadEPC()”也返回0告知上层函数目前正在等待从机应答;若“ReadHoldInputReg()”返回1则说明从机成功应答或者应答超时了,我们怎么知道到底是应答了还是超时了呢?这就需要读取变量RIR_Update,若为1则说明应答成功我们就需要把读到的数据进行保存和处理,RIR_Update也完成了它的使命所以将其置0,并将DataUpdate置1告知上层函数数据读取成功。从机无论是应答还是超时都需要将R_En置0并返回1这点至关重要。
所有的任务必须严格按照该格式来实现,否则会影响上层的调度工作。
至此任务篇就介绍完毕了。
|