OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 6803|回复: 8

分享标准 ModbusRTU 协议

[复制链接]

36

主题

1263

帖子

1

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1612
金钱
1612
注册时间
2012-6-15
在线时间
39 小时
发表于 2013-6-14 15:34:33 | 显示全部楼层 |阅读模式

1. 支持主机、从机;[主机尚待完善]

2. 通用协议,不仅仅支持RS485 与物理层无关,可通过继承扩展, 如我此例中兼支持433M无线通信
3. 支持EEPROM(24C02) 及 内部FLASH存储<通过Configure Wizard设定>




继承关系:



详解:


1 .  MODBUS_BASE
---基类,实现基本通用功能。
class MODBUS_BASE 
{
public:
inline u16 DATA_CHG_INT16(u16 n) { return ((n<<8) + (n>>8)) ; }
WORD CRC16(WORD crc,    const u8 *sdBuf , int len);
WORD CRC16( const u8 *sdBuf , int len); //crc
};


2. MODBUS_SLAVE_BASE 
--继承于
MODBUS_BASE ,为 RTU从机协议实现
class MODBUS_SLAVE_BASE public MODBUS_BASE
{
protected:
u8 Address;  //通信地址
public:
#pragma pack(1)        //单字节对齐
typedef struct {                 //从机接收帧
union {
u8 buf[248]; //单帧数据最大值
struct{
u8 addr;
u8 cmd;
}msg;
struct {  // 1/2/3/4/5/6号指令格式
u8 addr; //???·
u8 cmd; //?ü??
u16 shift; //???????·
u16 number; //?????÷????
u16 crc;   //???é
}msg_3_4; //???ü???ù??????????
struct{    //16号指令格式
u8 addr; //???·
u8 cmd; //?ü??
u16 shift; //???????·
u16 number; //?????÷????
u8  bytecnt; //×???????
u16 data[1]; //????
}msg_16;
}Buf;
u8 rcS; //超时计数
u16 rcIndex; //数据索引
bool rcOK; //接收完成标志
}UARTMsgType;                        
#pragma pack()

MODBUS_SLAVE_BASE( ) :Address(1) {}
MODBUS_SLAVE_BASE(u8 addr) :Address(addr) {}

virtual void SetAddress(u8 addr) { Address = addr ; }
virtual u8 GetAddress(void) { return Address ; }
virtual u8 ReadAddress(void);
//发送消息由具体继承类实现[纯虚函数]
virtual void SendMessage(UARTMsgType *msg, int len) = 0;
//ModbusRTU解析功能
virtual void Transfer(UARTMsgType *msg); 
/*---------数据信息读取---------*/
virtual void InitDatas();
};

后续...<估计下周一了>


PS: 建议原子哥 论坛 应该实现 代码插入功能, 像CSDN那样的,要不太费劲了!!




正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2013-6-14 16:22:42 | 显示全部楼层
谢谢分享,这个代码插入功能,得看看了,暂时没时间搞啊.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

头像被屏蔽

88

主题

231

帖子

2

精华

高级会员

Rank: 4

积分
844
金钱
844
注册时间
2013-4-11
在线时间
40 小时
发表于 2013-6-14 20:26:09 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 反对

使用道具 举报

0

主题

7

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
376
金钱
376
注册时间
2013-5-23
在线时间
101 小时
发表于 2013-6-14 20:34:57 | 显示全部楼层
先收藏再说,主要关心主机部分 
原子论坛要是也搞成discuz,像amobbs,维护装插件就方便多了
回复 支持 反对

使用道具 举报

36

主题

1263

帖子

1

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1612
金钱
1612
注册时间
2012-6-15
在线时间
39 小时
 楼主| 发表于 2013-6-17 15:23:50 | 显示全部楼层
::我的编码跟这个编码不一样,注释部分贴上来就是乱码了 ,注释是重新写的,就写写主要部分

协议实现1 : <modbus.cpp>

#if USE_FLASG_EE

static FLASH_EE fe;
#endif
/*
读取标定数据部分
*/
void MODBUS_SLAVE_BASE::InitDatas()
{
#if USE_EEPROM    //用EE存储
for(int i=0 ; i<NumOfHR * 2 ; i++)
{
DatasOfBD.buf = EE.ReadByte( EE_BD_START + i );
}
#elif USE_FLASG_EE    //用内部FLASH存储
fe.MemRead(FLASH_ID_PAGE+1,(u16*)DatasOfBD.buf,NumOfHR);
#endif
}
/*
读取设备地址,此地址存储在EE或FLASH下,  可以改成其他方式如拨码开关等
*/
u8 MODBUS_SLAVE_BASE::ReadAddress(void)
{
#if USE_EEPROM
Address = EE.ReadByte(EE_ID_ADDR);
#elif USE_FLASG_EE
u16 adr[5];
fe.MemRead(FLASH_ID_PAGE, adr,5);
Address = adr[2] ;
#else 
Address = 1;
#endif

if(Address <1 || Address >127) //地址范围
Address = 1;

return Address;

}


/*
从机协议解析(RTU)
*/
void MODBUS_SLAVE_BASE::Transfer(UARTMsgType *msg)
{
if(!msg->rcOK) return; //未接收完一帧数据
// if(msg->rcIndex<8)             //帧长度<8 ,如果不支持日志等几个外,则长度一定>=8
// {
// msg->rcOK = false;
// msg->rcIndex = 0;
// return;
// }

if((Address==msg->Buf.msg.addr) || (msg->Buf.msg.addr==0))    // 地址正确或广播地址 则计算CRC
{
WORD crc = CRC16(msg->Buf.buf ,
(msg->Buf.msg.cmd!=16)? 6 : (msg->Buf.msg_16.bytecnt + 7));
if(crc.Value != ((msg->Buf.msg.cmd!=16)? \
msg->Buf.msg_3_4.crc : \
(*(u16*)((u8*)msg->Buf.buf + msg->Buf.msg_16.bytecnt + 7 ))) )
{//CRC错误
msg->rcOK = false;
msg->rcIndex = 0;
return;
}
#if USE_FLASG_EE
u16 adr[5];
int ij;
#endif
if(msg->Buf.msg.addr==0)             //广播地址
{
switch(msg->Buf.msg.cmd )
{
case 0xcc: //自定义命令 -更改设备地址,可去掉
Address = msg->Buf.buf[5];
#if USE_EEPROM
EE.WriteByte(EE_ID_ADDR,Address );
#elif USE_FLASG_EE
for(ij=0;ij<5;ij++)
adr[ij] = Address ;
fe.MemWrite(FLASH_ID_PAGE, adr,5);
#endif
break;
default:
break;
}
for(int i=0;i<8;i++)
msg->Buf.buf = msg->Buf.buf;
SendMessage(msg,8);

msg->rcOK = false;
msg->rcIndex = 0;
return;
}
                
                //正常操作

u16 shift,number;  //计算偏移、数量/数值 供后面使用
shift = DATA_CHG_INT16(msg->Buf.msg_3_4.shift);    
number= DATA_CHG_INT16(msg->Buf.msg_3_4.number);
msg->Buf.buf[0] = msg->Buf.msg.addr;
msg->Buf.buf[1] = msg->Buf.msg.cmd;
switch(msg->Buf.msg.cmd ) //modbus?ü??
{
case 0: //
break;
case 1:   //????????×???  
break;
case 2: //????????×???
break;  
case 3: //????±????????÷(±ê?¨????)
case 4:   //?????????????÷(???ù????)

msg->Buf.buf[2] = number*2;

if((shift + number) <= ((msg->Buf.msg_3_4.cmd==3)?NumOfHR:NumOfIR))    //不能越界
{  
u8 *p = (u8*)(((msg->Buf.msg_3_4.cmd==3)?DatasOfBD.bufatasOfDT.buf) + shift*2); 指针位置

for(int i=0; i<number; i++) //????????
{
msg->Buf.buf[3+ 2*i ] = p[2*i+1];
msg->Buf.buf[3+ 2*i+1] = p[2*i ];
}

crc.Value = 0xffff;                //CRC计算
crc = CRC16(crc, msg->Buf.buf, 3 + number*2) ;
msg->Buf.buf[3 + number*2]=crc.cV[0] ;
msg->Buf.buf[4 + number*2]=crc.cV[1] ;

SendMessage(msg,(5 + number*2)) ;  //返回数据
}   
break;

case 5: //??????????
break;
case 6:   //设置单个寄存器
DatasOfBD.Datas[shift] = number;
#if USE_EEPROM
                            EE.WriteByte(EE_BD_START + shift*2 + 0, DatasOfBD.buf[2*shift+0]);
                            EE.WriteByte(EE_BD_START + shift*2 + 1, DatasOfBD.buf[2*shift+1]);
#endif
#if USE_FLASG_EE
fe.MemWrite(FLASH_ID_PAGE+1,(u16*)DatasOfBD.buf,NumOfHR);
#endif
for(int i=2;i<8;i++)
msg->Buf.buf = msg->Buf.buf;
SendMessage(msg,8);
break;
case 16:   //配置多组寄存器
if(shift + number < NumOfHR)
{
u16 *p =  (u16*)((u8*)msg->Buf.buf + 7); //???????????·
for(int i=0; i<number; i++)
{
DatasOfBD.Datas[shift+i] = DATA_CHG_INT16(p);
#if USE_EEPROM
            EE.WriteByte(EE_BD_START + (shift+i)*2 + 0, DatasOfBD.buf[2*(shift+i)+0]);
            EE.WriteByte(EE_BD_START + (shift+i)*2 + 1, DatasOfBD.buf[2*(shift+i)+1]);
#endif
#if USE_FLASG_EE
fe.MemWrite(FLASH_ID_PAGE+1,(u16*)DatasOfBD.buf,NumOfHR);
#endif
}

for(int i=2; i<6;i++)
msg->Buf.buf = msg->Buf.buf;
crc = CRC16(msg->Buf.buf, 6) ;
msg->Buf.buf[6]=crc.cV[0] ;
msg->Buf.buf[7]=crc.cV[1] ;
SendMessage(msg,8);

}
break;
default:
break;
}
}

msg->rcOK = false;
msg->rcIndex = 0;
}






回复 支持 反对

使用道具 举报

36

主题

1263

帖子

1

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1612
金钱
1612
注册时间
2012-6-15
在线时间
39 小时
 楼主| 发表于 2013-6-17 15:42:37 | 显示全部楼层
用于485总线[USART]从机 -- 


/******************************************************************************/
/****************************** 485总线-Modbus ***************************/
/******************************************************************************/

/*此部分用于Configuration wizard ,方便改动*/
//<e> RS485接口通信 ( ModbusRTU从机 )
#define USE_485_MODBUS_S 1

#if USE_485_MODBUS_S
//<o0>????????
// <1=> USART1
// <2=> USART2
// <3=> USART3
// <4=> UART4
// <5=> UART5 ***??????DMA

#define USARTPORT 5

#if USARTPORT == 1
#define mUsart USART1
#define mIRQChannel USART1_IRQChannel
#define mDMASendChanel DMA1_Channel4
#define mDMAIRQChannel DMA1_Channel4_IRQChannel
#elif USARTPORT == 2
#define mUsart USART2
#define mIRQChannel USART2_IRQChannel
#define mDMASendChanel DMA1_Channel7
#define mDMAIRQChannel DMA1_Channel7_IRQChannel
#elif USARTPORT == 3
#define mUsart USART3
#define mIRQChannel USART3_IRQChannel
#define mDMASendChanel DMA1_Channel2
#define mDMAIRQChannel DMA1_Channel2_IRQChannel
#elif USARTPORT == 4
#define mUsart UART4
#define mIRQChannel UART4_IRQChannel
#define mDMASendChanel DMA2_Channel5
#define mDMAIRQChannel DMA2_Channel4_5_IRQChannel
#elif USARTPORT == 5
#define mUsart UART5
#define mIRQChannel UART5_IRQChannel
#endif

//<q>是否使用DMA发送
#define MODBUS_USE_DMA 0

//</e>

class MODBUS_485_S : public MODBUS_SLAVE_BASE    //继承关系
{
public:

UARTMsgType uartRMsg;             //收发缓冲区

#if MODBUS_USE_DMA
volatile bool TransOk;
volatile bool DmaEn;
volatile u8   TimeCount;

void Chk_DMAOK(void); //DMA接收完成
#endif

void Init(u32 band=9600);           //485初始化,参数只一个BAND,其他默认,可自行设计

void Chk_TimeOut(u8 t); //超时检测{供定时器调用,超时间 =  1ms * t}
void Recieve_MSG(char ch);         //接收一个字节,供串口中断调用{没使用DMA}

virtual void SendMessage(UARTMsgType *msg, int len);    //发送消息
void RTUTransfer()                                                      //解析消息
{
MODBUS_SLAVE_BASE::Transfer(  &uartRMsg  );
}

};

#endif
回复 支持 反对

使用道具 举报

42

主题

568

帖子

0

精华

高级会员

Rank: 4

积分
784
金钱
784
注册时间
2010-12-19
在线时间
5 小时
发表于 2013-7-16 13:04:59 | 显示全部楼层
LZ就不能上传project上来吗?
一个上蹿下跳的猴子~~~
回复 支持 反对

使用道具 举报

18

主题

128

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
421
金钱
421
注册时间
2013-2-4
在线时间
214 小时
发表于 2014-9-18 22:00:00 | 显示全部楼层
谢谢 分享。。
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手上路

积分
22
金钱
22
注册时间
2015-4-4
在线时间
0 小时
发表于 2015-4-4 18:14:28 | 显示全部楼层
回复【5楼】aleda303:
---------------------------------
if(msg->Buf.msg.addr==0)             //广播地址
{
switch(msg->Buf.msg.cmd )
{
case 0xcc: //自定义命令 -更改设备地址,可去掉
Address = msg->Buf.buf[5];

#if USE_EEPROM
EE.WriteByte(EE_ID_ADDR,Address );
#elif USE_FLASG_EE
for(ij=0;ij<5;ij++)
adr[ij] = Address ;
fe.MemWrite(FLASH_ID_PAGE, adr,5);
#endif

break;
default:
break;
}
for(int i=0;i<8;i++)
msg->Buf.buf = msg->Buf.buf;  //楼主 这个没看懂啊 能不能解释下?
SendMessage(msg,8);

msg->rcOK = false;
msg->rcIndex = 0;
return;

}
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-24 03:17

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表