本文章基于STM32 UART创建高重用性的ModBus通讯框架,主要分为CPU_UART底层驱动,通信帧断帧校验逻辑,以及RTU协议帧解析,试图构建一个高重用,跨平台易于移植,易于使用和扩展的ModBus通讯框架。
1.CPU外设框架结构
typedef struct {
Uint16 *p_utbuf;
Uint16 *p_urbuf;
Uint16 *p_utbuf_used;
Uint16 *p_urbuf_used;
Uint16 *p_T_break_fram;
Uint16 *is_sending;
void (*SciSendFrame)();
void (*SetSendIO)();
void (*SetRecIO)();
void (*SetBaudrate)(Uint16 a);
void (*SetDateFram)(Uint16 a);
Uint16 (*Send_Over)();
void (*BaseInit)();
}SCI_DateStru;
SCI_DateStru SCI1DateStru;
Uint16 SCI1_RBuff[60];
Uint16 SCI1_RBuffUse = 0;
Uint16 SCI1_TBuff[100];
Uint16 SCI1_TBuffUse = 0;
Uint16 SCI1_Sending = 0;
Uint16 SCI1_BreakFram = 0;
Uint16 SCI1_TLEN = 0;
Uint16 SCI1BufHead = 0;
void SCI1_DateStruDefault()
{
SCI1DateStru.p_utbuf = SCI1_TBuff; //发送缓存
SCI1DateStru.p_urbuf = SCI1_RBuff; //接收缓存
SCI1DateStru.p_utbuf_used = &SCI1_TBuffUse;//发送缓存使用长度
SCI1DateStru.p_urbuf_used = &SCI1_RBuffUse;//接收缓存使用长度
SCI1DateStru.p_T_break_fram = &SCI1_BreakFram;//断帧用于标记系统时间
SCI1DateStru.is_sending = &SCI1_Sending;//判断是否发送中
SCI1DateStru.SciSendFrame = UART4_Trans;//底层发送函数
SCI1DateStru.SetSendIO = UART4_OccuBus;//使用该函数用于占据总线
SCI1DateStru.SetRecIO = UART4_ReleBus;//使用该函数用于释放总线
SCI1DateStru.SetBaudrate = UART4_SetBaud;//设置波特率参数
SCI1DateStru.SetDateFram = UART4_SetDateFram;//设置数据帧形式RTUorTCp
SCI1DateStru.Send_Over = UART4_TOver;//判断底层发送完成
SCI1DateStru.BaseInit = UART4_Init;
}
除此之外还有一个接收函数用于中断接收每一个字节
void UART4_IRQHandler()
{
u8 a = 0;
if(UART4->SR&(0x20))
{
if(SCI1_RBuffUse > 25)SCI1_RBuffUse = 0;
SCI1_RBuff[SCI1_RBuffUse] = UART4->DR;
SCI1_RBuffUse++;
SCI1_BreakFram = P_SysTime->Cnt_ms; //记录接收数据的时间用于断帧处理
}
if(UART4->SR&0x08)
{
a= UART4->DR;
a= UART4->SR;
a= UART4->DR;
a= UART4->CR1;
a= UART4->CR2;
a= UART4->CR3;
}
UART4->SR = 0;
}
接下来就在于实现以上的几个函数注册到结构体上的函数了。
初始化CPU外设函数:
void UART4_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
UartXInit(UART4,(u32 * )SCI1_TBuff,DMA2_Channel5,USART_StopBits_2);
}
void UART4_Trans()
{
uint16_t i=0;
UART4->CR3=1<<7;
if(SCI1_TBuffUse > 80) SCI1_TBuffUse = 0;
for(i = 0;i<SCI1_TBuffUse;i++)
{
buf[i] = SCI1_TBuff[i]&0xff;
}
MYDMA_Enable(DMA2_Channel5,(u32)buf, SCI1_TBuffUse);
SCI1_TBuffUse = 0;
}
void UART4_OccuBus()
{
//在485总线时 置位RTS占据总线
}
void UART4_ReleBus()
{
//在485总线时复位RTS释放总线
}
void UART4_SetBaud(u16 a)
{
//UART4->BRR 修改波特率
}
void UART4_SetDateFram(u16 a)
{
//设置格式 也不用现在
}
u16 UART4_TOver()
{
//判断DMA发完了没有
Uint16 sover = 0;
if(DMA2->ISR & 0x00020000) //finish
{
DMA2->IFCR |= 0x000f0000;
sover = 1;
}
return sover;
}
最终调用以下函数初始化完成底层配置:
SCI1_DateStruDefault();
*(SCI1DateStru.BaseInit)();
2.ModBus帧处理
首先是modbus帧处理的结构体
typedef struct
{
u16 masterorslaver;
u16 modebus_type;
u16 *TBuf_p; //指向底层发送缓存
u16 *RBuf_p; //指向底层接收缓存
u16 RbufSize;//
u16 *R_len; //一级接收缓存使用量
u16 *T_len; //一级发送缓存使用量
u16 *BreakFrmCnt;//指向底层断帧时间
u16 BreakFrmLen;//设置断帧时间
u16 SuccessCnt; //通讯成功计数
u16 FrameCnt; //通讯次数
u16 goodpercent;//通讯成功百分比,便于观测通讯质量
u16 MasterWaitACKCnt; //主机等待应答计数
u16 MasterGetAckFlag;//主机接收到应答标记
void (*BaseTrans)();//底层发送函数
u16 (*isBaseTransFinish)();//判断是否底层发送完成
} FrameStruct;
结构初始化将帧处理结构与底层处理数据和方法连接
void ModbusInit(FrameStruct *modbus_frame, SCI_DateStru *scibase)
{
modbus_frame->BreakFrmCnt = scibase->p_T_break_fram; //
modbus_frame->BreakFrmLen = 3; //3ms断帧时间
modbus_frame->TBuf_p = scibase->p_utbuf; //
modbus_frame->RBuf_p = scibase->p_urbuf; //
modbus_frame->R_len = scibase->p_urbuf_used;//
modbus_frame->T_len = scibase->p_utbuf_used;//
modbus_frame->RbufSize = 35;
modbus_frame->masterorslaver = 1; //slaver
modbus_frame->modebus_type = 1;//RTU
modbus_frame->SuccessCnt = 0;
modbus_frame->FrameCnt = 0;
modbus_frame->goodpercent = 0;
modbus_frame->MasterWaitACKCnt = 0;
modbus_frame->MasterGetAckFlag = 0;
modbus_frame->BaseTrans = scibase->SciSendFrame;
modbus_frame->isBaseTransFinish = scibase->Send_Over;
}
在主流程中调用
ModbusInit(&ma_Frame,&SCI1DateStru);
完成帧处理模块与底层模块的连接
Modbus丛机协议执行主函数
ModbusSlaverHandel(&ma_Frame,&ma_RTU);
void ModbusSlaverHandel(FrameStruct *p_frame,MODBUSRTU_DEV *p_rtu)
{
if( Modbus_Recive(p_frame) ){//帧检测函数
if(ModbusCheck(p_frame)){//帧校验函数
Frame2ModbusRTU(p_frame,p_rtu);//RTU协议处理函数
}
*(p_frame->R_len) = 0;//接收缓存清零
}
}
帧检测函数
u16 Modbus_Recive(FrameStruct *p)
{
u16 t= 0;
//判断是否接收到数据
if(*(p->R_len) == 0) {
*(p->BreakFrmCnt) = P_SysTime->Cnt_ms;
return 0;
}
//判断是否产生断帧
if(P_SysTime->Cnt_ms >= *(p->BreakFrmCnt)){
if( (P_SysTime->Cnt_ms - *(p->BreakFrmCnt)) > p->BreakFrmLen){
*(p->BreakFrmCnt) = P_SysTime->Cnt_ms;
t= 1;
}
}else
{
if( (P_SysTime->Cnt_ms+1000 - *(p->BreakFrmCnt)) > p->BreakFrmLen){
*(p->BreakFrmCnt) = P_SysTime->Cnt_ms;
t= 1;
}
}
return t;
}
校验与通讯质量检测
u16 ModbusCheck(FrameStruct *p)
{
u16 a = 0;
u16 b = 0;
u16 c= 0;
u16 d = 0;
if((*(p->R_len)) < 2) return d;
a = *(p->R_len)-2;
c= ((p->RBuf_p)[a]<<8) + (p->RBuf_p)[a+1];
b = CrcCheck((p->RBuf_p),a); //CRC校验
if(c == b){
p->SuccessCnt ++;
d = 1;
}
else{
}
p->FrameCnt ++;
if(p->FrameCnt > 99){
p->goodpercent = p->SuccessCnt;
p->FrameCnt = 0;
p->SuccessCnt = 0;
}
return d;
}
3.RTU协议
RTU协议处理结构体规划
//命令字定义
typedef enum {
R_DORGE = 1,//读输出线圈
R_DIRGE = 2,//读输入线圈
R_AORGE = 3,//读输出寄存器
R_AIRGE = 4,//读输入寄存器
W_DORGE = 5,//写输出线圈
W_AORGE = 6,//写输出寄存器
W_MDORGE = 15,//写多个线圈
W_MAORGE = 16//写多个输出寄存器
}MODBUS_RTU_CMD;
//帧头两个字节 设备地址 + 命令字
typedef struct {
u16 dev_add;
MODBUS_RTU_CMD cmd;
} Modbush_RTU_Head;
//RTU 主机帧
typedef struct {
Modbush_RTU_Head head;
u8 Startadd_H;
u8 Startadd_L;
u8 Lenth_H;
u8 Lenth_L;
u8 WBytelen;
u16 WWord[10];
u8 WaitAckCnt;
u16 Rbuf[30];
u16 *Tbuf;
} MODBUS_MASTER;
//RYU 丛机帧
typedef struct {
Modbush_RTU_Head head;
u16 Lenth_L;
u16 SlaveBuf[35];
u16 *Tbuf;
} MODBUS_SLAVE;
//RTU协议结构体
typedef struct{
MODBUS_MASTER master;
MODBUS_SLAVE slaver;
u16 Online;
//两个钩子函数 当参数改变时调用,判断参数有效性
u16 (*CheckBitWork)(u16 add,u16 value);//位值参数改变
u16 (*CheckWordWork)(u16 add,u16 value);//字参数改变
} MODBUSRTU_DEV;
结构体初始化
void ModBusRTUInit(MODBUSRTU_DEV *m, u16 DevAdd,void *checkbit,void *checkword)
{
m->slaver.head.dev_add = DevAdd;
m->Online = 0;
m->CheckBitWork = checkbit;
m->CheckWordWork = checkword;
}
这个函数处理RTU协议的丛机应答
Frame2ModbusRTU(p_frame,p_rtu);
```c
void Frame2ModbusRTU(FrameStruct *p,MODBUSRTU_DEV *p_RTU)
{
u16 a = *(p->R_len)-2;
u16 *b = (p->RBuf_p);
u16 i =0;
p_RTU->slaver.Tbuf = p->TBuf_p;
if(p_RTU->slaver.head.dev_add == b[0] ) //判断站号
{
p_RTU->slaver.Lenth_L = 0;
p_RTU->slaver.head.cmd = (u8)b[1]; //读取命令字
for(i=2;i<a;i++)//将帧缓存 载入到RTU丛机的接收缓存中
{
p_RTU->slaver.Lenth_L++;
p_RTU->slaver.SlaveBuf[i-2] = b[i];
}
DecodeMaster_RTU(p_RTU);//RTU协议解析
if( (*(p->isBaseTransFinish))() )//判断是否发送完成
ModBusTrans(p,p_RTU);//发送函数
p_RTU->Online = 1; //标记在线
}
*(p->R_len) = 0;//
}
//RTU协议解码
void DecodeMaster_RTU(MODBUSRTU_DEV *m)
{
u16 add =0;
u16 *p = m->slaver.SlaveBuf;
u16 a,len,i;
switch(m->slaver.head.cmd)
{
case R_DORGE:{//???bits
if(m->slaver.Lenth_L == 4)
{
m->slaver.Lenth_L = 0;
add = p[0]; //????
add = (add<<8) + p[1];
len = p[2]; //????
len = (len<<8)+ p[3];
a = len / 8; //???
if(a*8 < len) a++;
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = a;//???
m->slaver.Lenth_L = 3;
for(i=0;i<a;i++)
{
m->slaver.Tbuf[i+3] = BIT_Write[0];
m->slaver.Lenth_L ++;
}
}
}break;
case R_DIRGE:{//???bit
if(m->slaver.Lenth_L == 4)
{
m->slaver.Lenth_L = 0;
add = p[0]; //????
add = (add<<8) + p[1];
len = p[2]; //????
len = (len<<8)+ p[3];
a = len / 8; //???
if(a*8 < len) a++;
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = a;//???
m->slaver.Lenth_L = 3;
for(i=0;i<a;i++)
{
m->slaver.Tbuf[i+3] = BIT_Read[0];
m->slaver.Lenth_L ++;
}
}
}break;
case R_AORGE:{
if(m->slaver.Lenth_L == 4){
m->slaver.Lenth_L = 0;
add = p[0]; //????
add = (add<<8) + p[1];
len = p[2]; //????
len = (len<<8)+ p[3];
a = len;
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = a*2;
m->slaver.Lenth_L = 3;
for(i=0;i<a;i++)
{
m->slaver.Tbuf[2*i+3] = ( Word_Write[i+add]>>8)&0xff;
m->slaver.Lenth_L ++;
m->slaver.Tbuf[2*i+4] = Word_Write[i+add]&0xff;
m->slaver.Lenth_L ++;
}
}
}break;
case R_AIRGE:{
if(m->slaver.Lenth_L == 4){
m->slaver.Lenth_L = 0;
add = p[0]; //????
add = (add<<8) + p[1];
len = p[2]; //????
len = (len<<8)+ p[3];
a = len;
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = a*2;
m->slaver.Lenth_L = 3;
for(i=0;i<a;i++)
{
m->slaver.Tbuf[2*i+3] = ( Word_Write[i+add]>>8)&0xff;
m->slaver.Lenth_L ++;
m->slaver.Tbuf[2*i+4] = Word_Write[i+add]&0xff;
m->slaver.Lenth_L ++;
}
}
}break;
case W_DORGE:{
if(m->slaver.Lenth_L == 4)
{
m->slaver.Lenth_L = 0;
add = p[0]; //????
add = (add<<8) + p[1];
//HMI??????
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = p[0];
m->slaver.Tbuf[3] = p[1];
m->slaver.Tbuf[4] = p[2];
m->slaver.Tbuf[5] = p[3];
m->slaver.Lenth_L = 6;
if( (*(m->CheckBitWork))(add,p[2]))
{
if(p[2] == 0xFF)
BIT_Write[0] |= 0x01<<add;
else
BIT_Write[0] &= ~((0x01)<<add);
}
}
}break;
case W_AORGE:{
if(m->slaver.Lenth_L == 4)
{
m->slaver.Lenth_L = 0;
add = p[0]; //????
add = (add<<8) + p[1];
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = p[0];
m->slaver.Tbuf[3] = p[1];
m->slaver.Tbuf[4] = 0;
m->slaver.Tbuf[5] = 1;
m->slaver.Lenth_L = 6;
if((*(m->CheckWordWork))(add,(p[2]<<8) + p[3]))
Word_Write[add] = (p[2]<<8) + p[3];
}
}break;
case W_MDORGE:{//???????
m->slaver.Lenth_L = 0;
add = p[0]; //????
len = p[4]/2;
for(i=0;i<len;i++)
{
if((*(m->CheckWordWork))(add+i,(p[2*i+5]<<8) + p[2*i+6]))
Word_Write[add+i] = (p[2*i+5]<<8) + p[2*i+6];
}
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = p[0];
m->slaver.Tbuf[3] = p[1];
m->slaver.Tbuf[4] = p[2];
m->slaver.Tbuf[5] = p[3];
m->slaver.Lenth_L = 6;
}break;
case W_MAORGE:{//???????
add = p[0]; //????
add = (add<<8) + p[1];
m->slaver.Tbuf[0] = m->slaver.head.dev_add;
m->slaver.Tbuf[1] = m->slaver.head.cmd;
m->slaver.Tbuf[2] = p[0];
m->slaver.Tbuf[3] = p[1];
m->slaver.Tbuf[4] = p[2];
m->slaver.Tbuf[5] = p[3];
len = p[4]/2;
for(i=0;i<len;i++)
{
if( (*(m->CheckWordWork))(add+i,(p[2*i+5]<<8) + p[2*i+6]) )
Word_Write[i+add] = (p[2*i+5]<<8) + p[2*i+6];
}
m->slaver.Lenth_L = 6;
}break;
default:break;
}
}