modbus协议使用小记
下载了libmodbus库,交叉编译后运行,总是接收回复时不正确。原因不明。
由于使用到modbus的需求比较简单,所以选择直接拼出modbus的请求报文,然后用串口直接发送和接收的方式,
拼modbus的请求报文关键在于理解modbus协议,
比如请求报文:
[01][03][00][00][00][02][c4][0b]
第一个字节(0x01)表示设备地址(从机地址),就是设备编号,可以同时接多个设备,设备之间用设备号区分。
第二个字节(0x03)表示功能号,常见的如下:
01 (0x01) 读线圈
02 (0x02) 读离散量输入
03 (0x03) 读保持寄存器
04 (0x04) 读输入寄存器
05 (0x05) 写单个线圈
06 (0x06) 写单个寄存器
15 (0x0F) 写多个线圈
16 (0x10) 写多个寄存器
这里03表示读保持寄存器,具体寄存器的意义需要根据厂家提供的说明。
第三、四字节 (0x00 0x00)表示寄存器起始地址,这里是0,表示从第一个寄存器开始。
第五、六字节 (0x00 0x02) 表示地址偏移量,这里是2,表示从起始地址开始偏移2个字节。
第七、八字节 (0xc4 0x0b) 是CRC校验码。0xc4 是低位,0x0b是高位。就是反序的。
以上一起连起来,就是读取0x01号从机的保持寄存器中从0x00地址开始,到0x00+0x02地址为止的值。
一开始,在485接口上试的时候总是发送什么接收到什么,原因不明,后来再发送和接收之间加上了sleep,
终于能接收到回复报文了,但是前面还是会接收到自己发送的请求报文,尝试过清楚串口缓存,还是不行,
没办法,只能在接收到的报文中手动去掉前面8个字节的请求报文,这样处理后是可以用的,接收后使用查表法
校验CRC。
一开始接收的时候使用了select, 然后在接收时会有20%左右概率出现illegal seek,原因不明。后来去掉了select,
直接使用read读,这样效果好了很多,出现illegal seek的概率大概只有1%。已经够用了,出现illegal seek的时候
检查CRC报错后直接放弃,依然使用上一次的数据。
checkcrc.h
/*
* checkcrc.h
*
* Created on: May 6, 2015
* Author: root
*/ #ifndef CHECKCRC_H_
#define CHECKCRC_H_
#include <stdint.h> uint16_t CRC16(uint8_t *Pushdata, uint8_t length); #endif /* CHECKCRC_H_ */
checkcrc.cpp
/*
* checkcrc.c
*
* Created on: May 6, 2015
* Author: root
*/ #include "checkcrc.h" /* CRC 高位字节值表
CRC high byte
*/
const uint8_t auchCRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
/* CRC低位字节值表
CRC low byte
*/
const uint8_t auchCRCLo[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
} ; uint16_t CRC16(uint8_t *Pushdata, uint8_t length)
{
uint8_t uchCRCHi=0xFF;
uint8_t uchCRCLo=0xFF;
uint8_t uIndex;
while( length-- )
{
uIndex=uchCRCHi^*Pushdata++;
uchCRCHi=uchCRCLo^auchCRCHi[uIndex];
uchCRCLo=auchCRCLo[uIndex];
}
//printf("crchi:%.2x crclo:%.2x \n", uchCRCHi, uchCRCLo);
return (uchCRCHi<< | uchCRCLo);
}
main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> //文件控制定义
#include <termios.h>//终端控制定义
#include <errno.h>
//#include <stdint.h>
#include <string.h>
#include "checkcrc.h" const char *comport = "/dev/ttySAC1";
int bRate = ; int serial_fd = ; //打开串口并初始化设置
int init_serial(void)
{
serial_fd = open(comport, O_RDWR | O_NOCTTY | O_NDELAY);
if (serial_fd < ) {
perror("open");
return -;
}
else
printf("open %s success! \n", comport); //串口主要设置结构体termios <termios.h>
struct termios options; /**1. tcgetattr函数用于获取与终端相关的参数。
*参数fd为终端的文件描述符,返回的结果保存在termios结构体中
*/
tcgetattr(serial_fd, &options);
/**2. 修改所获得的参数*/
options.c_cflag |= (CLOCAL | CREAD);//设置控制模式状态,本地连接,接收使能
options.c_cflag &= ~CSIZE;//字符长度,设置数据位之前一定要屏掉这个位
options.c_cflag &= ~CRTSCTS;//无硬件流控
options.c_cflag |= CS8;//8位数据长度
options.c_cflag &= ~CSTOPB;//1位停止位
options.c_iflag |= IGNPAR;//无奇偶检验位
options.c_oflag = ; //输出模式
options.c_lflag = ; //不激活终端模式 cfsetospeed(&options, B38400);//设置波特率 /**3. 设置新属性,TCSANOW:所有改变立即生效*/
tcflush(serial_fd, TCIFLUSH);//溢出数据可以接收,但不读
tcsetattr(serial_fd, TCSANOW, &options); return ;
} void uart_send(int fd)
{
const unsigned char buf[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xc4, 0x0b};
int len = write(fd, buf, sizeof(buf));
//tcflush(fd, TCIOFLUSH);
printf("send length = %d \n", len);
int i;
for (i = ; i < sizeof(buf); i++)
printf("[%.2x]", buf[i]);
printf("\n");
} void uart_recv(int fd, unsigned char *modbusdata, int *length)
{
unsigned char data[];
int len=, ret = ;
fd_set fs_read;
struct timeval tv_timeout; //FD_ZERO(&fs_read);
//FD_SET(fd, &fs_read);
tv_timeout.tv_sec = ;
tv_timeout.tv_usec = ;
/*
ret = select(fd+1, &fs_read, NULL, NULL, &tv_timeout);
if (ret == -1)
{
printf("select time out\n");
}
if (FD_ISSET(fd, &fs_read)) {
len = read(fd, data, sizeof(data));
printf("receive len: %d (bytes)\n", len);
} else {
perror("select");
}
*/
len = read(fd, data, sizeof(data));
printf("receive len: %d (bytes)\n", len);
unsigned char * p = NULL;
p = data;
p += ;
len -= ;
printf("modbus data length: %d (bytes)\n", len);
memcpy(modbusdata, p, len);
*length = len;
} void modbusCompute(unsigned char * modbusdata, int len)
{
unsigned char * p;
p = modbusdata;
int i;
for (i = ; i < len; i++)
{
printf("<%.2x>",modbusdata[i]);
}
printf("\n");
p += ;
uint16_t hiValue = *p * + *(p+);
uint16_t loValue = *(p+) * + *(p+);
uint16_t sp = hiValue * * + loValue;
printf("[ sp = %d ]\n", sp);
uint16_t sl = ;
uint16_t sh = ;
double x;
x = * (sp - sl)*1.0/(sh - sl);
printf("[x = %.2lf]\n", x); double y;
if (x > && x <= )
y = 0.7 * x;
else if (x > && x <= )
y = 0.6 * x + 0.1;
else if (x > && x <= )
y = 0.25 * x + 1.5;
else if (x > && x <= )
y = 0.36 * x + 0.4;
else if (x > && x <= )
y = 0.29 * x + 1.8;
else if (x > )
y = 0.25 * x + ;
printf("[y = %.2lf]\n", y);
} int main(int argc, char **argv)
{
unsigned char modbusdata[];
int length;
init_serial();
uart_send(serial_fd);
usleep();
uart_recv(serial_fd, modbusdata, &length); uint16_t calCrc = modbusdata[length-] * + modbusdata[length-]; if (calCrc == CRC16(modbusdata, length-))
{
printf("crc ok\n");
modbusCompute(modbusdata, length);
}else{
printf("crc error\n");
} close(serial_fd);
return ;
}
(完)
modbus协议使用小记的更多相关文章
- modbus协议讲义
Modbus 一个工业上常用的通讯协议.一种通讯约定.Modbus协议包括RTU.ASCII.TCP.其中MODBUS-RTU最常用,比较简单,在单片机上很容易实现.虽然RTU比较简单,但是看 ...
- 模拟Modbus协议问题
问题: 在嵌入式系统开发中,Modbus协议是工业控制系统中广泛应用的一种协议.本题用来简单模拟Modbus协议,只需根据条件生成符合该协议的数据帧,并解析所获取的数据.假设设备使用的协议发送数据格式 ...
- 各种非标232,485协议,自定义协议转modbus协议模块定制开发,各种流量计协议转modbus,
工业现场经常会碰到通过485或者232采集各类仪表数据,但是很多早期的仪表和设备不支持标准modbus协议,而是采用自定义的协议,这些协议数据由plc或者dcs系统来实现采集,不仅费时麻烦,而且不方便 ...
- Socket编程之聊天程序 - 模拟Fins/ModBus协议通信过程
设备控制软件编程涉及到的基本通信方式主要有TCP/IP与串口,用到的数据通信协议有Fins与ModBus. 更高级别的通信如.net中的Remoting与WCF在进行C/S架构软件开发时会采用. 本篇 ...
- 基于AVR128单纯Modbus协议实施
Modbus通信协议Modicon公司1979在发展中,适用于工业现场总线协议控制.Modbus通信系统包含芯片的节点,并与组合物可编程控制的公共传输线,它的目的是收集和监视多个节点的数据.Modbu ...
- MODBUS协议详解
MODBUS是一个工业上通信常用的通讯协议,一般在PLC上面用的比较多,主要是定义了一种数据传输的规范,比如数据发给谁,数据是干嘛的,数据错没错,接收到数据的从机告诉我数据有没有接受到等. 传输的方式 ...
- C# MODBUS协议 上位机(转)
源:C# MODBUS协议 上位机 C#写了一款上位机监控软件,基于MODBUS_RTU协议. 软件的基本结构: 采用定时器(Timer控件)为时间片. 串口采用serialPort1_DataRec ...
- 串口屏Modbus协议,串口屏的modbus协议资料,串口屏modbus通讯协议开发,串口屏之modbus协议使用技巧
串口屏Modbus协议,串口屏的modbus协议资料,串口屏modbus通讯协议开发,串口屏之modbus协议使用技巧 本例程中用51单片机作为Modbus从机,从机的设备地址为2,从机有4个寄存器, ...
- 认识Modbus协议
1.什么是Modbus? Modbus协议是应用于电子控制器上的一种通用语言.通过此协议,控制器相互之间,控制器经由网络(例如以太网)和其它设备之间可以通信.Modbus协议定义了一个控制器能认识使用 ...
随机推荐
- docker port is already allocated 的解决方案
ps -aux | grep -v grep | grep docker-proxy 第二列为进程号 停止 doker 进程,删除所有容器,然后删除 local-kv.db 这个文件,再启动 dock ...
- 《微信小程序商城开发实战》唐磊,全网真实评价截图,不吹不黑,全部来自网友的真实评价
偶尔看了下网友的销量和评价,感觉还不错,因为市面上大多关于小程序的书籍可能写的不够全面,要么只是点到为止的大致罗列,要么就是只简单介绍一下小程序的 界面设计这块.这样很难给学习小程序开发的人一个完成的 ...
- Oracle 行转列 动态出转换的列
本文链接:https://blog.csdn.net/Huay_Li/article/details/82924443 10月的第二天,前天写了个Oracle中行转列的pivot的基本使用方法,然后, ...
- yum 安装指定版本Docker
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/weixin_39553910/artic ...
- Cookie的Secure属性和HttpOnly属性
基于安全的考虑,需要给cookie加上Secure和HttpOnly属性,HttpOnly比较好理解,设置HttpOnly=true的cookie不能被js获取到,无法用document.cookie ...
- javascript正则提取字母和数字小数
var item = {name:"PM2.5"}; item.nameFirst = item.name.replace(/[^a-zA-Z]/g, ''); item.name ...
- 理解Spring定时任务@Scheduled的两个属性fixedRate和fixedDelay
fixedRate和fixedDelay都是表示任务执行的间隔时间 fixedRate和fixedDelay的区别:fixedDelay非常好理解,它的间隔时间是根据上次的任务结束的时候开始计时的.比 ...
- SpringMVC request 得到文件路径
1.java中的路径 File directory = new File("abc"); // 对于getCanonicalPath()函数,“."就表示当前的文件夹,而 ...
- osgearth显示中文标签
首先将带有中文的字符串转换成string void unicodeToUTF8(const wstring &src, string& result){int n = WideChar ...
- cheerio 服务器端的jquery
cheerio https://cheerio.js.org/ Fast, flexible, and lean implementation of core jQuery designed spec ...