modbus应用
简介
这个示例为了方便,使用qt+x86平台,但是对于modbus的应用是一样的,无论是在linux,还是在mcu上使用都是一样的。
实现功能:
·串口通信
·modbus功能码03,04,06,10
·16位crc校验算法
·IEEE 754浮点数在程序中的应用
串口通信
qt5以后的中串口通信使用的是qt自带的类QSerialPort,老版本中一般使用开源的类qextserialport,使用方式基本相同,我在这里使用qt5.6.1,所以使用QSerialPort就行了。
串口放到一个单独的线程里,来保证数据接收的实时性。多线程通信一般用全局变量,用互斥锁mutex来保证数据不会同时读写(qt中有QMutex),这里为了省事没有用。
1.定义串口发送和接收的buf
uchar modbus_Rxbuf[20];
uchar modbus_Txbuf[20];
uint16_t rxlen;//接收数据的时候用来计数
2.初始化串口
void comthread::cominit()
{
//cc是用来中转数据的缓存
cc = (unsigned char*)malloc(10*sizeof (unsigned char));
serial = new QSerialPort;
if(serial->isOpen())
{
serial->clear();
serial->close();
}
serial->setPortName("com1");
if(!serial->open(QIODevice::ReadWrite))
qDebug("serial opend failed!");
serial->setBaudRate(QSerialPort::Baud1200,QSerialPort::AllDirections);//设置波特率为1200 modbus常用的波特率
serial->setDataBits(QSerialPort::Data8);//设置数据位8
serial->setParity(QSerialPort::NoParity); //校验位设置为0
serial->setStopBits(QSerialPort::OneStop);//停止位设置为1
serial->setFlowControl(QSerialPort::NoFlowControl);//设置为无流控制
}
3.串口接收数据
void comthread::run()
{
cominit();
int num=0;
int len = 0;
QByteArray comdata;
//waitForReadyRead参数是接收数据的等待时间,这里设为-1是为了实现同步接收方式,一直等待数据的到来
while(serial->waitForReadyRead(-1))
{
if(bquit)//如果进程退出了
{
free(cc);//释放为cc开辟的空间
return;
}
comdata = serial->readAll();
cc=(unsigned char*)comdata.data();
len = comdata.length();
for(int i=0;i<len;i++)
{
modbus_Rxbuf[rxlen]=cc[i];
rxlen++;
}
//下面对计时器操作跟modbus协议有关,在modbus部分会说明
emit mmmstop(); //清空计时器
emit mmmstart(); //重启计时器
}
}
4.发送数据的函数
void comthread::modbuswrite( char *buf, qint64 len)
{
serial->write(buf,len);
}
16位crc计算
crc是个啥?请百度,下面上代码,代码有注释
#define POLYNOMIAL 0xA001 //crc默认多项式 这个是计算crc的一个基数,基数不同,计算出来的结果不同
//计算16Crc校验码
//buf是要计算crc的数据
//len是取buf中的几个字节来计算crc
uint16_t Calculate_CRC(uchar *buf,uchar len)
{
uint16_t Rcrc=0xFFFF; //crc寄存器默认值
uint8_t i,j;
for(i = 0; i < len; i++){// 要校验的字节为len个
Rcrc = buf[i] ^ Rcrc;
for(j = 0;j < 8;j++){ //此处的8 -- 指每一个uint8类型有8bit,每bit都要处理
if(Rcrc & 0x01){
Rcrc = Rcrc >> 1;
Rcrc = Rcrc ^ POLYNOMIAL;
}
else{
Rcrc = Rcrc >> 1;
}
}
}
return Rcrc;
}
modbus协议
modbus是个通信协议,一个标准化的协议。串口,网络都可以使用这个协议。
参考链接: lModbus 通信协议详解.
消息帧:两种传输模式中(ASCII或RTU),传输设备以将Modbus消息转为有起点和终点的帧,这就允许接收的设备在消息起始处开始工作,读地址分配信息,判断哪一个设备被选中(广播方式则传给所有设备),判知何时信息已完成。部分的消息也能侦测到并且错误能设置为返回结果。
我只用到rtu帧格式,如下图:
T1-T4的含义应该是:每个T表示传输一个Byte的时间(我是这样理解,也是这样用,不对请指正,感谢!)。
所以在使用modbus协议的时候,没有我们常用的帧头和帧尾,通过判断时间来确定一帧的开始和结束。
帧开始:接收第一个字节就认为是开始
帧结束:当超过4个T(也可以是一段时间,我程序里设置的5ms)没有接收到数据就是一帧的结束
功能码:功能码可以百度搜索modbus功能码,资料挺多。这里设计03,04,06,10(都是16进制)。
03:读取一个寄存器的值
04:和03一样
06:写一个寄存器的值(2个字节的值-整数)
10:写一个寄存器的值(4个字节的值-浮点数)
IEEE754浮点数
ieee754可以百度出很多文章,写的也很详细,然并卵。
原理我们能理解,但是实际使用的时候是要能把浮点数转化到我们要发送的数据buf中,和从结束buf中转为浮点数。
示例-接收数据:从485数据中读取一个浮点数放到内存里
float data_32;//接收的数据放到这里
unsigned char modbus_Rxbuf[10];//接收buf
unsigned char tmpbuf[4];//中转buf
float *p;//一个浮点型指针
p=(float*)(&tmpbuf);//让指针指向中转buf
//把接收的数据放到中转buf 注意字节顺序
tmpbuf[0] = modbus_Rxbuf[5];
tmpbuf[1] = modbus_Rxbuf[4];
tmpbuf[2] = modbus_Rxbuf[7];
tmpbuf[3] = modbus_Rxbuf[6];
data_32 = *p;
示例-发送数据:把内存的浮点数放到发送buf
float data_32;//要发送的数据放到这里
unsigned char modbus_Txbuf[10];//发送buf
unsigned char tmpbuf[4];//中转buf
float *p;//一个浮点型指针
p=(float*)(&tmpbuf);//让指针指向中转buf
*p=data_32;
//把要发送的数据放到发送buf 注意字节顺序
modbus_Txbuf[7]=tmpbuf[1];
modbus_Txbuf[8]=tmpbuf[0];
modbus_Txbuf[9]=tmpbuf[3];
modbus_Txbuf[10]=tmpbuf[2];
下一篇: Verilog practice