打印
[应用方案]

C51 MODBUS编程

[复制链接]
2388|24
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
yiyigirl2014|  楼主 | 2016-7-23 21:38 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
#include "reg52.h"
typedef unsigned char uint8
typedef unsigned int uint16
uint8 sendCount;  
uint8 receCount;  
uint8 sendPosi;
//字地址 0 - 255 (只取低8位)   
//位地址 0 - 255 (只取低8位)   
   
/// CRC 高位字节值表 ///   
const uint8 code 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低位字节值表///   
const uint8 code 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   
} ;   
   
uint8   testCoil;       //用于测试 位地址1   
uint16  testRegister;   //用于测试 字址址16   
   
uint8   localAddr = 1;  //单片机控制板的地址   
uint8   sendCount;      //发送字节个数   
uint8   receCount;      //接收到的字节个数   
uint8   sendPosi;       //发送位置   
   
uint16 crc16(uint8 //puchMsg, uint16 usDataLen)   
{   
    uint8 uchCRCHi = 0xFF ; /// 高CRC字节初始化 ///   
    uint8 uchCRCLo = 0xFF ; /// 低CRC 字节初始化 ///   
    uint32 uIndex ; /// CRC循环中的索引 ///   
    while (usDataLen--) /// 传输消息缓冲区 ///   
    {   
        uIndex = uchCRCHi ^ //puchMsg++ ; /// 计算CRC ///   
        uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;   
        uchCRCLo = auchCRCLo[uIndex] ;   
    }   
    return (uchCRCHi << 8 | uchCRCLo) ;   
}//uint16 crc16(uint8 //puchMsg, uint16 usDataLen)   
   
//开始发送   
void beginSend(void)   
{      
    b485Send = 1;   //设为发送   
      
    sendPosi = 0;   
    if(sendCount > 1)   
        sendCount--;   
    ACC = sendBuf[0];   
    TB8 = P;   
    SBUF = sendBuf[0];   
   
}//void beginSend(void)   
   
   
//读线圈状态   
void readCoil(void)   
{   
    uint8 addr;   
    uint8 tempAddr;   
    uint8 byteCount;   
    uint8 bitCount;   
    uint16 crcData;   
    uint8 position;   
    uint8 i,k;   
    uint8  result;   
    uint16 tempData;   
    uint8  exit = 0;   
      
    //addr = (receBuf[2]<<8) + receBuf[3];      
    //tempAddr = addr & 0xfff;   
    addr = receBuf[3];   
    tempAddr = addr;   
   
    //bitCount = (receBuf[4]<<8) + receBuf[5];    //读取的位个数   
    bitCount = receBuf[5];   
   
    byteCount = bitCount / 8;                   //字节个数   
    if(bitCount%8 != 0)   
        byteCount++;   
                                       
    for(k=0;k
    {//字节位置   
        position = k + 3;   
        sendBuf[position] = 0;   
        for(i=0;i<8;i++)   
        {   
            getCoilVal(tempAddr,&tempData);   
               
            sendBuf[position] |= tempData << i;   
            tempAddr++;   
            if(tempAddr >= addr+bitCount)   
            {   //读完   
                exit = 1;   
                break;   
            }      
        }   
        if(exit == 1)   
            break;   
    }   
      
    sendBuf[0] = localAddr;   
    sendBuf[1] = 0x01;     
    sendBuf[2] = byteCount;   
    byteCount += 3;   
    crcData = crc16(sendBuf,byteCount);   
    sendBuf[byteCount] = crcData >> 8;   
    byteCount++;   
    sendBuf[byteCount] = crcData & 0xff;   
    sendCount = byteCount + 1;   
      
    beginSend();      
}//void readCoil(void)   
   
//读寄存器   
void readRegisters(void)   
{   
    uint8 addr;   
    uint8 tempAddr;   
    uint16 result;   
    uint16 crcData;   
    uint8 readCount;   
    uint8 byteCount;   
    uint8  finsh;   //1完成  0出错   
    uint16 i;   
    uint16 tempData = 0;      
      
    //addr = (receBuf[2]<<8) + receBuf[3];   
    //tempAddr = addr & 0xfff;     
    addr = receBuf[3];   
    tempAddr = addr;   
   
    //readCount = (receBuf[4]<<8) + receBuf[5];   //要读的个数   
    readCount = receBuf[5];   
   
    byteCount = readCount // 2;   
      
    for(i=0;i
    {   
        getRegisterVal(tempAddr,&tempData);               
        sendBuf[i+3] = tempData >> 8;                        
        sendBuf[i+4] = tempData & 0xff;            
    }   
      
    sendBuf[0] = localAddr;   
    sendBuf[1] = 3;   
    sendBuf[2] = byteCount;   
    byteCount += 3;   
    crcData = crc16(sendBuf,byteCount);   
    sendBuf[byteCount] = crcData >> 8;   
    byteCount++;   
    sendBuf[byteCount] = crcData & 0xff;   
      
    sendCount = byteCount + 1;   
    beginSend();   
}//void readRegisters(void)   
   
   
//强制单个线圈   
void forceSingleCoil(void)   
{   
    uint8 addr;   
    uint8 tempAddr;   
    uint16 tempData;   
    uint8  onOff;   
    uint8 i;   
      
    //addr = (receBuf[2]<<8) + receBuf[3];      
    //tempAddr = addr & 0xfff;   
    addr = receBuf[3];   
    tempAddr = addr;   
   
    //onOff = (receBuf[4]<<8) + receBuf[5];      
    onOff = receBuf[4];   
      
    //if(onOff == 0xff00)   
    if(onOff == 0xff)   
    {   //设为ON   
        tempData = 1;   
    }   
    //else if(onOff == 0x0000)   
    else if(onOff == 0x00)   
    {   //设为OFF   
        tempData = 0;   
    }   
   
    setCoilVal(tempAddr,tempData);     
      
    for(i=0;i
    {   
        sendBuf[i] = receBuf[i];   
    }   
    sendCount = receCount;   
    beginSend();      
}//void forceSingleCoil(void)   
   


沙发
yiyigirl2014|  楼主 | 2016-7-23 21:39 | 只看该作者
//设置多个寄存器   
void presetMultipleRegisters(void)   
{   
    uint8 addr;   
    uint8 tempAddr;   
    uint8 byteCount;   
    uint8 setCount;   
    uint16 crcData;   
    uint16 tempData;   
    uint8  finsh;   //为1时完成 为0时出错   
    uint8 i;   
      
    //addr = (receBuf[2]<<8) + receBuf[3];   
    //tempAddr = addr & 0xfff;   
    addr = receBuf[3];   
    tempAddr = addr & 0xff;   
   
    //setCount = (receBuf[4]<<8) + receBuf[5];   
    setCount = receBuf[5];   
    byteCount = receBuf[6];   
      
    for(i=0;i
    {   
        tempData = (receBuf[i//2+7]<<8) + receBuf[i//2+8];   
      
        setRegisterVal(tempAddr,tempData);            
    }   
      
    sendBuf[0] = localAddr;   
    sendBuf[1] = 16;   
    sendBuf[2] = addr >> 8;   
    sendBuf[3] = addr & 0xff;   
    sendBuf[4] = setCount >> 8;   
    sendBuf[5] = setCount & 0xff;   
    crcData = crc16(sendBuf,6);   
    sendBuf[6] = crcData >> 8;   
    sendBuf[7] = crcData & 0xff;   
    sendCount = 8;   
    beginSend();      
}//void presetMultipleRegisters(void)   
   
   
   
//检查uart0数据   
void checkComm0Modbus(void)   
{   
    uint16 crcData;   
    uint16 tempData;   
      
    if(receCount > 4)   
    {   
        switch(receBuf[1])   
        {   
            case 1://读取线圈状态(读取点 16位以内)   
            case 3://读取保持寄存器(一个或多个)   
            case 5://强制单个线圈   
            case 6://设置单个寄存器   
                    if(receCount >= 8)   
                    {//接收完成一组数据   
                        //应该关闭接收中断   
                           
                        if(receBuf[0]==localAddr && checkoutError==0)   
                        {   
                            crcData = crc16(receBuf,6);   
                            if(crcData == receBuf[7]+(receBuf[6]<<8))   
                            {//校验正确   
                                if(receBuf[1] == 1)   
                                {//读取线圈状态(读取点 16位以内)   
                                    readCoil();                                
                                }   
                                else if(receBuf[1] == 3)   
                                {//读取保持寄存器(一个或多个)   
                                    readRegisters();   
                                }   
                                else if(receBuf[1] == 5)   
                                {//强制单个线圈   
                                    forceSingleCoil();                                 
                                }   
                                else if(receBuf[1] == 6)   
                                {   
                                    //presetSingleRegister();                                 
                                }   
   
                            }   
                        }                          
                        receCount = 0;     
                        checkoutError = 0;   
                    }   
                    break;   
               
            case 15://设置多个线圈   
                    tempData = receBuf[6];   
                    tempData += 9;  //数据个数   
                    if(receCount >= tempData)   
                    {   
                        if(receBuf[0]==localAddr && checkoutError==0)   
                        {   
                            crcData = crc16(receBuf,tempData-2);   
                            if(crcData == (receBuf[tempData-2]<<8)+ receBuf[tempData-1])   
                            {   
                                //forceMultipleCoils();            
                            }   
                        }      
                        receCount = 0;   
                        checkoutError = 0;   
                    }   
                    break;   
               
            case 16://设置多个寄存器   
                    tempData = (receBuf[4]<<8) + receBuf[5];   
                    tempData = tempData // 2;    //数据个数   
                    tempData += 9;   
                    if(receCount >= tempData)   
                    {   
                        if(receBuf[0]==localAddr && checkoutError==0)   
                        {   
                            crcData = crc16(receBuf,tempData-2);   
                            if(crcData == (receBuf[tempData-2]<<8)+ receBuf[tempData-1])   
                            {   
                                presetMultipleRegisters();            
                            }   
                        }      
                        receCount = 0;   
                        checkoutError = 0;   
                    }   
                    break;   
                                   
            default:   
                    break;            
        }   
    }   
}//void checkComm0(void)   


使用特权

评论回复
板凳
yiyigirl2014|  楼主 | 2016-7-23 21:40 | 只看该作者
//取线圈状态 返回0表示成功   
uint16 getCoilVal(uint16 addr,uint16 //tempData)   
{   
    uint16 result = 0;   
    uint16 tempAddr;   
      
    tempAddr = addr & 0xfff;   
    //只取低8位地址   
    switch(tempAddr & 0xff)   
    {   
        case 0:   
                break;   
        case 1:   
                //tempData = testCoil;   
                break;   
        case 2:        
                break;         
        case 3:   
                break;         
        case 4:   
                break;         
        case 5:   
                break;   
        case 6:   
                break;            
        case 7:   
                break;         
        case 8:   
                break;         
        case 9:   
                break;         
        case 10:   
                break;   
        case 11:   
                break;   
        case 12:   
                break;   
        case 13:   
                break;   
        case 14:   
                break;   
        case 15:   
                break;   
        case 16:   
                break;                                                         
        default:   
                break;         
    }      
      
    return result;   
}//uint16 getCoilVal(uint16 addr,uint16 //data)   
   


使用特权

评论回复
地板
yiyigirl2014|  楼主 | 2016-7-23 21:41 | 只看该作者
//设定线圈状态 返回0表示成功   
uint16 setCoilVal(uint16 addr,uint16 tempData)   
{   
    uint16 result = 0;   
    uint16 tempAddr;   
      
    tempAddr = addr & 0xfff;   
      
           
    switch(tempAddr & 0xff)   
    {   
        case 0:   
                break;   
        case 1:   
                testCoil = tempData;   
                break;   
        case 2:        
                break;         
        case 3:   
                break;         
        case 4:   
                break;         
        case 5:   
                break;   
        case 6:   
                break;            
        case 7:   
                break;         
        case 8:   
                break;         
        case 9:   
                break;         
        case 10:   
                break;   
        case 11:   
                break;   
        case 12:   
                break;   
        case 13:   
                break;   
        case 14:   
                break;   
        case 15:   
                break;   
        case 16:   
                break;                                                         
        default:   
                break;         
    }      
   
   
    return result;   
}//uint16 setCoilVal(uint16 addr,uint16 data)   
   
//取寄存器值 返回0表示成功   
uint16 getRegisterVal(uint16 addr,uint16 //tempData)   
{   
    uint16 result = 0;   
    uint16 tempAddr;   
      
    tempAddr = addr & 0xfff;   
      
    switch(tempAddr & 0xff)   
    {   
        case 0:   
                break;   
        case 1:   
                break;   
        case 2:        
                break;         
        case 3:   
                break;         
        case 4:   
                break;         
        case 5:   
                break;   
        case 6:   
                break;            
        case 7:   
                break;         
        case 8:   
                break;         
        case 9:   
                break;         
        case 10:   
                break;   
        case 11:   
                break;   
        case 12:   
                break;   
        case 13:   
                break;   
        case 14:   
                break;   
        case 15:   
                break;   
        case 16:   
                //tempData = testRegister;   
                break;                                                         
        default:   
                break;         
    }   
      
    return result;   
}//uint16 getRegisterVal(uint16 addr,uint16 &data)  


使用特权

评论回复
5
yiyigirl2014|  楼主 | 2016-7-23 21:42 | 只看该作者
//设置寄存器值 返回0表示成功   
uint16 setRegisterVal(uint16 addr,uint16 tempData)   
{   
    uint16 result = 0;   
    uint16 tempAddr;   
      
    tempAddr = addr & 0xfff;   
      
    switch(tempAddr & 0xff)   
    {   
        case 0:   
                break;   
        case 1:   
                break;   
        case 2:        
                break;         
        case 3:   
                break;         
        case 4:   
                break;         
        case 5:   
                break;   
        case 6:   
                break;            
        case 7:   
                break;         
        case 8:   
                break;         
        case 9:   
                break;         
        case 10:   
                break;   
        case 11:   
                break;   
        case 12:   
                break;   
        case 13:   
                break;   
        case 14:   
                break;   
        case 15:   
                break;   
        case 16:   
                testRegister = tempData;   
                break;                                                         
        default:   
                break;         
    }   
      
    return result;   
}


使用特权

评论回复
6
稳稳の幸福| | 2016-7-23 21:47 | 只看该作者
Modbus是由Modicon(现为施耐德电气公司的一个品牌)在1979年发明的,是全球第一个真正用于工业现场的总线协议。

使用特权

评论回复
7
稳稳の幸福| | 2016-7-23 21:48 | 只看该作者
Modbus网络传输
标准的Modbus口是使用RS-232-C兼容串行接口,它定义了连接口的针脚、电缆、信号位、传输波特率、奇偶校验。控制器能直接或经由Modem组网。
控制器通信使用主—从技术,即仅一设备(主设备)能初始化传输(查询)。其它设备(从设备)根据主设备查询提供的数据作出相应反应。典型的主设备:主机和可编程仪表。典型的从设备:可编程控制器。
主设备可单独和从设备通信,也能以广播方式和所有从设备通信。如果单独通信,从设备返回一消息作为回应,如果是以广播方式查询的,则不作任何回应。Modbus协议建立了主设备查询的格式:设备(或广播)地址、功能代码、所有要发送的数据、一错误检测域。
从设备回应消息也由Modbus协议构成,包括确认要行动的域、任何要返回的数据、和一错误检测域。如果在消息接收过程中发生一错误,或从设备不能执行其命令,从设备将建立一错误消息并把它作为回应发送出去。
其它类型传输
在其它网络上,控制器使用对等技术通信,故任何控制器都能初始化和其它控制器的通信。这样在单独的通信过程中,控制器既可作为主设备也可作为从设备。提供的多个内部通道可允许同时发生的传输进程。
在消息位,Modbus协议仍提供了主—从原则,尽管网络通信方法是“对等”。如果一控制器发送一消息,它只是作为主设备,并期望从从设备得到回应。同样,当控制器接收到一消息,它将建立一从设备回应格式并返回给发送的控制器。
查询回应周期
(1)查询
查询消息中的功能代码告之被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03是要求从设备读保持寄存器并返回它们的内容。数据段必须包含要告之从设备的信息:从何寄存器开始读及要读的寄存器数量。错误检测域为从设备提供了一种验证消息内容是否正确的方法。
(2)回应
如果从设备产生一正常的回应,在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包括了从设备收集的数据:像寄存器值或状态。如果有错误发生,功能代码将被修改以用于指出回应消息是错误的,同时数据段包含了描述此错误信息的代码。错误检测域允许主设备确认消息内容是否可用。

使用特权

评论回复
8
稳稳の幸福| | 2016-7-23 21:50 | 只看该作者
在ModBus系统中有2种传输模式可选择。这2种传输模式与从机PC通信的能力是同等的。选择时应视所用ModBus主机而定,每个ModBus系统只能使用一种模式,不允许2种模式混用。一种模式是ASCII(美国信息交换码),另一种模式是RTU(远程终端设备)。
用户选择想要的模式,包括串口通信参数(波特率、校验方式等),在配置每个控制器的时候,在一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。所选的ASCII或RTU方式仅适用于标准的Modbus网络,它定义了在这些网络上连续传输的消息段的每一位,以及决定怎样将信息打包成消息域和如何解码。在其它网络上(像MAP和Modbus Plus)Modbus消息被转成与串行传输无关的帧。

传输模式特性
ASCII可打印字符便于故障检测,而且对于用高级语言(如Fortran)编程的主计算机及主PC很适宜。RTU则适用于机器语言编程的计算机和PC主机。
用RTU模式传输的数据是8位二进制字符。如欲转换为ASCII模式,则每个RTU字符首先应分为高位和低位两部分,这两部分各含4位,然后转换成十六进制等量值。用以构成报文的ASCII字符都是十六进制字符。ASCII模式使用的字符虽是RTU模式的两倍,但ASCII数据的译码和处理更为容易一些,此外,用RTU模式时报文字符必须以连续数据流的形式传送,用ASCII模式,字符之间可产生长达1s的间隔,以适应速度较慢的机器。
控制器能设置为两种传输模式(ASCII或RTU)中的任何一种在标准的Modbus网络通信。

使用特权

评论回复
9
稳稳の幸福| | 2016-7-23 21:50 | 只看该作者
ASCII模式
当控制器设为在Modbus网络上以ASCII(美国标准信息交换代码)模式通信,一个信息中的每8位字节作为2个ASCII字符传输,如数值63H用ASCII方式时,需发送两个字节,即ASCII“6"(0110110)和ASCII”3“(0110011),ASCII字符占用的位数有7位和8位,国际通用7位为多。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。
代码系统
· 十六进制,ASCII字符0...9,A...F
· 消息中的每个ASCII字符都是一个十六进制字符组成
每个字节的位
· 1个起始位
· 7个数据位,最小的有效位先发送
· 1个奇偶校验位,无校验则无
1个停止位(有校验时),2个Bit(无校验时)
错误检测域
· LRC(纵向冗长检测)

RTU模式
当控制器设为在Modbus网络上以RTU模式通信,在消息中的每个8Bit字节按照原值传送,不做处理,如63H,RTU将直接发送01100011。这种方式的主要优点是:数据帧传送之间没有间隔,相同波特率下传输数据的密度要比ASCII高,传输速度更快[1]
代码系统
8位二进制,十六进制数0...9,A...F
消息中的每个8位域都是一或两个十六进制字符组成
每个字节的位
1个起始位
8个数据位,最小的有效位先发送
1个奇偶校验位,无校验则无
1个停止位(有校验时),2个Bit(无校验时)




使用特权

评论回复
10
稳稳の幸福| | 2016-7-23 21:51 | 只看该作者
CRC
CRC域是两个字节,包含一16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC,并与接收到的CRC域中的值比较,如果两值不同,则有误。
CRC是先调入一值是全“1”的16位寄存器,然后调用一过程将消息中连续的8位字节和当前寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效,起始位和停止位以及奇偶校验位均无效。
CRC产生过程中,每个8位字符都单独和寄存器内容相异或(XOR),结果向最低有效位方向移动,最高有效位以0填充。LSB被提取出来检测,如果LSB为1,寄存器单独和预置的值或一下,如果LSB为0,则不进行。整个过程要重复8次。在最后一位(第8位)完成后,下一个8位字节又单独和寄存器的当前值相异或(XOR)。最终寄存器中的值,是消息中所有的字节都执行之后的CRC值。
CRC添加到消息中时,低字节先加入,然后高字节。
CRC-16错误校验程序如下:报文(此处只涉及数据位,不指起始位、停止位和任选的奇偶校验位)被看作是一个连续的二进制,其最高有效位(MSB)首选发送。报文先与X↑16相乘(左移16位),然后看X↑16+X↑15+X↑2+1除,X↑16+X↑15+X↑2+1可以表示为二进制数11000,0000,0000,0101。整数商位忽略不记,16位余数加入该报文(MSB先发送),成为2个CRC校验字节。余数中的1全部初始化,以免所有的零成为一条报文被接收。经上述处理而含有CRC字节的报文,若无错误,到接收设备后再被同一多项式(X↑16+X↑15+X↑2+1)除,会得到一个零余数(接收设备核验这个CRC字节,并将其与被传送的CRC比较)。全部运算以2为模(无进位)。
习惯于成串发送数据的设备会首选送出字符的最右位(LSB-最低有效位)。而在生成CRC情况下,发送首位应是被除数的最高有效位MSB。由于在运算中不用进位,为便于操作起见,计算CRC时设MSB在最右位。生成多项式的位序也必须反过来,以保持一致。多项式的MSB略去不记,因其只对商有影响而不影响余数。
生成CRC-16校验字节的步骤如下:
①装如一个16位寄存器,所有数位均为1。
②该16位寄存器的高位字节与开始8位字节进行“异或”运算。运算结果放入这个16位寄存器。
③把这个16寄存器向右移一位。
④若向右(标记位)移出的数位是1,则生成多项式10,1000,000,0000,001和这个寄存器进行“异或”运算;若向右移出的数位是0,则返回③。
⑤重复③和④,直至移出8位。
⑥另外8位与该十六位寄存器进行“异或”运算。
⑦重复③~⑥,直至该报文所有字节均与16位寄存器进行“异或”运算,并移位8次。
⑧这个16位寄存器的内容即2字节CRC错误校验,被加到报文的最高有效位。
另外,在某些非ModBus通信协议中也经常使用CRC16作为校验手段,而且产生了一些CRC16的变种,他们是使用CRC16多项式X↑16+X↑15+X↑2+1,单首次装入的16位寄存器为0000;使用CRC16的反序X↑16+X↑14+X↑1+1,首次装入寄存器值为0000或FFFFH。

使用特权

评论回复
11
稳稳の幸福| | 2016-7-23 21:52 | 只看该作者
CRC简单函数如下:
unsignedchar*puchMsg;/*要进行CRC校验的消息*/


unsignedshortusDataLen;/*消息中字节数*/

unsignedshortCRC16(puchMsg,usDataLen)


{


unsignedcharuchCRCHi=0xFF;/*高CRC字节初始化*/


unsignedcharuchCRCLo=0xFF;/*低CRC字节初始化*/


unsigneduIndex;/*CRC循环中的索引*/


while(usDataLen--)/*传输消息缓冲区*/


{

uIndex=uchCRCHi^*puchMsg++;/*计算CRC*/


uchCRCHi=uchCRCLo^auchCRCHi[uIndex];


uchCRCLo=auchCRCLo[uIndex];

}


return((uchCRCHi<<8)|uchCRCLo);

}

/*CRC高位字节值表*/

staticunsignedcharauchCRCHi[]={
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低位字节值表

staticcharauchCRCLo[]={
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
};


使用特权

评论回复
12
稳稳の幸福| | 2016-7-23 21:53 | 只看该作者
LRC
LRC错误校验用于ASCII模式。这个错误校验是一个8位二进制数,可作为2个ASCII十六进制字节传送。把十六进制字符转换成二进制,加上无循环进位的二进制字符和二进制补码结果生成LRC错误校验(参见图)。这个LRC在接收设备进行核验,并与被传送的LRC进行比较,冒号(:)、回车符号(CR)、换行字符(LF)和置入的其他任何非ASCII十六进制字符在运算时忽略不计。

使用特权

评论回复
13
稳稳の幸福| | 2016-7-23 21:54 | 只看该作者
ModBus功能码
01READ COIL STATUS
02READ INPUT STATUS
03READ HOLDING REGISTER
04READ INPUT REGISTER
05WRITE SINGLE COIL
06WRITE SINGLE REGISTER
15WRITE MULTIPLE COIL
16WRITE MULTIPLE REGISTER

使用特权

评论回复
14
zhuomuniao110| | 2016-7-24 17:38 | 只看该作者
主设备可单独和从设备通信,也能以广播方式和所有从设备通信。

使用特权

评论回复
15
heisexingqisi| | 2016-7-24 18:05 | 只看该作者
在消息位,Modbus协议仍提供了主—从原则,尽管网络通信方法是“对等”

使用特权

评论回复
16
玛尼玛尼哄| | 2016-7-24 21:40 | 只看该作者
感觉这个很复杂啊,也用的不多,不过工业上用的很多。

使用特权

评论回复
17
zhuotuzi| | 2016-7-24 22:35 | 只看该作者
如果从设备产生一正常的回应,在回应消息中的功能代码是在查询消息中的功能代码的回应

使用特权

评论回复
18
598330983| | 2016-7-25 21:53 | 只看该作者
CRC校验需要那么多数组,看来一般别用的好。

使用特权

评论回复
19
zhuomuniao110| | 2016-7-26 19:52 | 只看该作者
,控制器使用对等技术通信,故任何控制器都能初始化和其它控制器的通信。

使用特权

评论回复
20
heisexingqisi| | 2016-7-27 12:01 | 只看该作者
ASCII可打印字符便于故障检测,而且对于用高级语言(如Fortran)编程的主计算机及主PC很适宜。RTU则适用于机器语言编程的计算机和PC主机。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

207

主题

3516

帖子

10

粉丝