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<<8 | 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 = 38400;

int serial_fd = 0;

//打开串口并初始化设置
int init_serial(void)
{
    serial_fd = open(comport, O_RDWR | O_NOCTTY | O_NDELAY);
    if (serial_fd < 0) {
        perror("open");
        return -1;
    }
    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 = 0; //输出模式
    options.c_lflag = 0; //不激活终端模式

    cfsetospeed(&options, B38400);//设置波特率

    /**3. 设置新属性,TCSANOW:所有改变立即生效*/
    tcflush(serial_fd, TCIFLUSH);//溢出数据可以接收,但不读
    tcsetattr(serial_fd, TCSANOW, &options);

    return 0;
}

void uart_send(int fd)
{
    const unsigned char buf[8] = {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 = 0; i < sizeof(buf); i++)
        printf("[%.2x]", buf[i]);
    printf("\n");
}

void uart_recv(int fd, unsigned char *modbusdata, int *length)
{
    unsigned char data[64];
    int len=0, ret = 0;
    fd_set fs_read;
    struct timeval tv_timeout;

    //FD_ZERO(&fs_read);
    //FD_SET(fd, &fs_read);
    tv_timeout.tv_sec  = 2;
    tv_timeout.tv_usec = 0;
    /*
    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 += 8;
    len -= 8;
    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 = 0; i < len; i++)
    {
        printf("<%.2x>",modbusdata[i]);
    }
    printf("\n");
    p += 3;
    uint16_t hiValue = *p * 256 + *(p+1);
    uint16_t loValue = *(p+2) * 256 + *(p+3);
    uint16_t sp = hiValue * 256 * 256 + loValue;
    printf("[ sp = %d ]\n", sp);
    uint16_t sl = 200;
    uint16_t sh = 32200;
    double x;
    x = 80 * (sp - sl)*1.0/(sh - sl);
    printf("[x = %.2lf]\n", x);

    double y;
    if (x > 0 && x <= 1)
        y = 0.7 * x;
    else if (x > 1 && x <= 4)
        y = 0.6 * x + 0.1;
    else if (x > 4 && x <= 10)
        y = 0.25 * x + 1.5;
    else if (x > 10 && x <= 20)
        y = 0.36 * x + 0.4;
    else if (x > 20 && x <= 30)
        y = 0.29 * x + 1.8;
    else if (x > 30)
        y = 0.25 * x + 3;
    printf("[y = %.2lf]\n", y);
}

int main(int argc, char **argv)
{
    unsigned char modbusdata[64];
    int length;
    init_serial();
    uart_send(serial_fd);
    usleep(100000);
    uart_recv(serial_fd, modbusdata, &length);

    uint16_t calCrc = modbusdata[length-2] * 256 + modbusdata[length-1];

    if (calCrc == CRC16(modbusdata, length-2))
    {
        printf("crc ok\n");
        modbusCompute(modbusdata, length);
    }else{
        printf("crc error\n");
    }

    close(serial_fd);
    return 0;
}

(完)

posted @ 2015-05-07 16:07  凤舞十天  阅读(5631)  评论(1编辑  收藏  举报