下位机就是圈圈玩usb的那个板子,一下是modbus下位机的关键代码
①偶校验
偶校验是针对串口数据帧而言的。
本例使用了偶校验,串口工作在方式3对实现偶校验比较方便。让第9位作为偶校验位即可。定时器1工作在模式2(自动重装),为串口提供波特率。如9600,,even,8,1.
用串口方式3也可以实现无校验,即在接收发送中断函数中不进行奇或偶校验,那么奇偶位就没用了,相当于停止位是两位。pc上需设置如9600,n,8,2.
但一般通过串口的方式1来实现无校验传输,较方便,且比方式3每次省发一位。pc上需设置如9600 ,n ,8 ,1.
串口的方式3,即11位异步收发,其每一个串口数据帧的格式是
起始位(0) D0 D1 D2 D3 D4 D5 D6 D7 TB8 停止位(1),其中TB8称第9位
先说一下51的发送,串口线路上闲时都是高电平1,执行指令move SBUF, A;则一个低电平0立刻塞进P3.1脚,然后SBUF中8位数据按照波特率移位到P3.1,再将TB8移位到P3.1,再将停止位1塞进P3.1。(硬件动将起始位1,SBUF,TB8,停止位0塞进P3.1)。所以每传输一个有效字节,需要在线路上额外传输3位。发送完成置中断TI为1。所以在程序里执行move SBUF,A之前要先把偶检验值装进TB8.。
再说一下51的接收:串口线路上闲时都是高电平1,当硬件检测到一个1->0的跳变即起始位时,便按照波特率从P3.1上采集8位数据到移位寄存器中,再传给SBUF中(此SBUF与发送时的SBUF不是同一个寄存器,虽然名字相同)并把第9位塞进RB8中。硬件接完一个字节到SBUF,置中断标志RI为1.
在程序的中断函数里,判断如果是RI中断,则从SBUF取出数据到ACC,再RB8中取出偶校验位和P比较,可得出有没错误。
如下,
如果出错则置checkoutError = 2;而在checkComm0Modbus()函数中检测checkoutError ,若非0,则不继续接受也不回复。主机侧在未接到设备回复的时候,需要自动重发或其他处理。
②CRC检验
CRC校验是针对modbus rtu数据帧而言的。采用CRC16生成多项式。
MODBUS rtu数据帧格式如下
起始位 设备地址 功能代码 数据 CRC校验 结束符
T1-T2-T3-T4 8Bit 8Bit n个8Bit 16Bit T1-T2-T3-T4
仅对设备地址域,功能代码域,数据域进行CRC校验,产生16位的校验码。每个帧之间利用时间间隔来区分。一下是区分代码
③在强制线圈1时需要发送FF00,强制线圈0时需要发送0000,。这样设计符合modbus标准,通用。
The reguested ON/OFF state is specified by a constant in the query data field. A value of FF 00 hex requests the coil to be ON. A value of 00 00 requests it to be OFF. All other values are illegal and will not affect the coil. p32
④
在出错时,设备需要发送给主机异常信息。
l 如果服务器设备接收到无通信错误的请求,并且可以正常地处理询问,那么服务器设备将
返回一个正常响应。
2 如果由于通信错误,服务器没有接收到请求,那么不能返回响应。客户机程序将最终处理
请求的超时状态。
3 如果服务器接收到请求,但是检测到一个通信错误(奇偶校验、LRC、CRC、...) ,那么不
能返回响应。客户机程序将最终处理请求的超时状态。
4 如果服务器接收到无通信错误的请求,但不能处理这个请求(例如,如果请求读一个不存
在的输出或寄存器) ,服务器将返回一个异常响应,通知用户错误的本质特性。
异常响应报文有两个与正常响应不同的域:
功能码域:在正常响应中,服务器利用响应功能码域来应答最初请求的功能码。所有功能码的
最高有效位(MSB)都为 0(它们的值都低于十六进制 80) 。在异常响应中,服务器设置功能码的
SB 为1。这使得异常响应中的功能码值比正常响应中的功能码值高十六进制80。
通过设置功能码的MSB, 客户机的应用程序能够识别异常响应, 并且能够检测异常码的数据域。
数据域:在正常响应中,服务器可以返回数据域中数据或统计表(请求中要求的任何报文) 。在
异常响应中,服务器返回数据域中的异常码。这就定义了产生异常的服务器状态。
modbus 协议中文规范 http://download.csdn.net/detail/songqqnew/3857785
//modbus.c
#include "main.h"
sbit P10=P1^0;
sbit P11=P1^1;
sbit P12=P1^2;
sbit P13=P1^3;
sbit P14=P1^4;
sbit P15=P1^5;
sbit P16=P1^6;
sbit P17=P1^7;
sbit P34=P3^4;
sbit P35=P3^5;
sbit P36=P3^6;
sbit P37=P3^7;
/* 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; //单片机控制板的地址
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)
{
sendPosi = 0;
if(sendCount > 1)
sendCount--;
ACC = sendBuf[0];
TB8 = P;
SBUF = sendBuf[0];
}//void beginSend(void)
//读线圈状态 01 ,02
void readCoil(void)
{
/*比如
01 01 00 00 00 07 7D C8 ----主机发,modbus地址01;功能号01;起始地址00 00;数量00 07即读7位,不足1字节,就发回1字节的数据(不读的那位默认0,看程序)。
(32 ms)
01 01 01 6B 10 67 ---单片机回,modbus地址01;功能号01;返回的数据数量01即1字节;返回的数据6B即0110 1011 (msb--lsb)
*/
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 & 0xff;//按照需要屏蔽地址
bitCount = (receBuf[4]<<8) + receBuf[5];
bitCount &= 0xff;//按照需要屏蔽数据个数
byteCount = bitCount / 8; //字节个数
if(bitCount%8 != 0)
byteCount++; //字节数向上取整
for(k=0;k<byteCount;k++)
{//字节位置
position = k + 3;
sendBuf[position] = 0;//初值清零
for(i=0;i<8;i++)
{
getCoilVal(tempAddr,&tempData);//返回的tempData要么是0要么是1,表示该位的状态
//tempAddr是如 1 2 3等形式的表示位的地址,具体在getCoilVal函数中体现。
//调用此函数之前应先屏蔽掉非法地址和数据
sendBuf[position] |= tempData << i;
tempAddr++;
if(tempAddr >= addr+bitCount)
{ //读完
exit = 1;
break;
}
}
if(exit == 1)
break;
}
sendBuf[0] = localAddr;
sendBuf[1] = receBuf[1];
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)
//读寄存器 03,04
void readRegisters(void)
{
/*比如
01 03 00 02 00 03 A4 0B ----主机发,modbus地址01;功能号03;起始地址00 02;数量00 03即读3个数据,每个数据2字节即6字节。
(31 ms)
01 03 06 00 02 00 03 00 04 A9 76 ---单片机回,modbus地址01;功能号03;返回的数据数量06即6字节;返回的数据是00 02 00 03 00 04
*/
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 & 0xff;//按照需要屏蔽地址
readCount = (receBuf[4]<<8) + receBuf[5];
// readCount &=0xf; //按照需要屏蔽数据个数,并考虑sendBuf[]的容量设定
if (readCount>60) readCount=60;
byteCount = readCount * 2; //字节个数
for(i=0;i<byteCount;i+=2,tempAddr++)//主机要读的byteCount不要超过数组sendBuf[]的大小-2,否则sendBuf里就没空存放CRC码了,而导致主机判断CRC校验出错
//但也没关系,因为上面屏蔽掉了,使最多每次只能读取60个数,即120字节。
//但在一次读取大于60个数时,在modscan32测试显示time-out,在自己的软件上测试可以通过即总是返回60个数
//说明modscan32软件设计的是请求多少设备就要回复多少,否则modscan判断time-out
{
getRegisterVal(tempAddr,&tempData);//调用此函数之前应先屏蔽掉非法地址和数据
sendBuf[i+3] = tempData >> 8;
sendBuf[i+4] = tempData & 0xff;
}
sendBuf[0] = localAddr;
sendBuf[1] = receBuf[1];
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)
//强制单个线圈 05
void forceSingleCoil(void)
{
/*比如,置on
01 05 00 03 FF 00 7C 3A ----主机发,modbus地址01;功能号05;起始地址00 03;要写入的数据是FF 00,即on
(47 ms)
01 05 00 03 FF 00 7C 3A ----单片机回,和主机发的一样
比如,置off
01 05 00 03 00 00 3D CA----主机发,modbus地址01;功能号05;起始地址00 03;要写入的数据是00 00,即off
(47 ms)
01 05 00 03 00 00 3D CA ----单片机回,和主机发的一样
*/
uint8 addr;
uint8 tempAddr;
uint16 tempData=0xabcd;
uint8 onOff;
uint8 i;
addr = (receBuf[2]<<8) + receBuf[3];
tempAddr = addr & 0xff;//按照需要屏蔽地址
onOff = (receBuf[4]<<8) + receBuf[5];
if(onOff == 0xff00)
{ //设为ON
tempData = 1;
}
else if(onOff == 0x0000)
{ //设为OFF
tempData = 0;
}
if (tempData==1 || tempData==0){
setCoilVal(tempAddr,tempData); //调用此函数之前应先屏蔽掉非法地址和数据
for(i=0;i<receCount;i++)
{
sendBuf[i] = receBuf[i];
}
sendCount = receCount;
beginSend();
}
}//void forceSingleCoil(void)
//设置单个寄存器 06
void presetSingleRegister(void)
{
/*比如
01 06 00 01 00 66 58 20 ----主机发,modbus地址01;功能号06;起始地址00 01;要写入的数据是00 66
(31 ms)
01 06 00 01 00 66 58 20 ----单片机回,和主机发的一样
*/
uint8 addr;
uint8 tempAddr;
uint16 crcData;
uint16 tempData;
// uint8 finsh; //为1时完成 为0时出错
addr = (receBuf[2]<<8) + receBuf[3];
tempAddr = addr & 0xff;//按照需要屏蔽地址
sendBuf[4] = receBuf[4];
sendBuf[5] = receBuf[5];
tempData = (receBuf[4]<<8) + receBuf[5];
setRegisterVal(tempAddr,tempData); //调用此函数之前应先屏蔽掉非法地址和数据
sendBuf[0] = localAddr;
sendBuf[1] = receBuf[1];
sendBuf[2] = addr >> 8;
sendBuf[3] = addr & 0xff;
crcData = crc16(sendBuf,6);
sendBuf[6] = crcData >> 8;
sendBuf[7] = crcData & 0xff;
sendCount = 8;
beginSend();
}//void presetMultipleRegisters(void)
//设置多个寄存器
/*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<setCount;i++,tempAddr++)
{
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=0;
if(receCount >= 8)
{//接收完成一组数据
//应该关闭接收中断
if(receBuf[0]==localAddr && checkoutError==0)
{
crcData = crc16(receBuf,6); //功能码 01-06 发送字节均是8个字节,第7、8是校验码,功能码16不是
if(crcData == receBuf[7]+(receBuf[6]<<8))
{//校验正确
if(receBuf[1] == 1)
{//读取逻辑线圈状态.支持的地址见readCoil()--->getCoilVal(tempAddr,&tempData);
readCoil();
}
else if(receBuf[1] == 2)
//输入线圈状态,支持的地址见readCoil()--->getCoilVal(tempAddr,&tempData);
{
readCoil();
}
else if(receBuf[1] == 3)
{//读取保持寄存器(一个或多个),支持的地址见readRegisters()--->getRegisterVal(tempAddr,&tempData);
readRegisters();
}
else if(receBuf[1] == 4)
{//读取输入寄存器(一个或多个),支持的地址见readRegisters()--->getRegisterVal(tempAddr,&tempData);
readRegisters();
}
else if(receBuf[1] == 5)
{//强制单个线圈 ,支持的地址见forceSingleCoil()--->setCoilVal
forceSingleCoil();
}
else if(receBuf[1] == 6)
{ //可以修改寄存器的值,支持的地址见presetSingleRegister()--->setRegisterVal
presetSingleRegister();
}
}
//输入线圈 P34-P37 可以通过02来读取状态 IN
//输出线圈 P10-P17 可以通过05来读设定 OUT
//读模拟量 moni[] 可以通过03来读取状态 模拟量
//设定寄存器 moni[] 可以通过06来设定
//
}
else{;}//错误代码
receCount = 0; //初始化
checkoutError = 0;
}
}//void checkComm0(void)
//取线圈状态 返回0表示成功
uint16 getCoilVal(uint16 addr,uint16 *tempData)
{
uint16 result = 0;
uint16 tempAddr=addr;
switch(tempAddr)
{
case 0:
*tempData = 1;
break;
case 1:
*tempData = 1;
break;
case 2:
*tempData = 0;
break;
case 3:
*tempData = 1;
break;
case 4:
*tempData = 0;
break;
case 5:
*tempData = P34;
break;
case 6:
*tempData = P35;
break;
case 7:
*tempData = P36;
break;
case 8:
*tempData = P37;
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)
//设定线圈状态 返回0表示成功
uint16 setCoilVal(uint16 addr,uint16 tempData)
{
uint16 result = 0;
uint16 tempAddr=addr;
switch(tempAddr)
{
case 0:
P10 = tempData;
break;
case 1:
P11 = tempData;
break;
case 2:
P12 = tempData;
break;
case 3:
P13 = tempData;
break;
case 4:
P14 = tempData;
break;
case 5:
P15 = tempData;
break;
case 6:
P16 = tempData;
break;
case 7:
P17 = tempData;
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=addr;
switch(tempAddr)//
{
case 0:
*tempData=moni[0];
break;
case 1:
*tempData=moni[1];
break;
case 2:
*tempData=moni[2];
break;
case 3:
*tempData=moni[3];
break;
case 4:
*tempData=moni[4];
break;
case 5:
*tempData=moni[5];
break;
case 6:
*tempData=moni[6];
break;
case 7:
*tempData=moni[7];
break;
case 8:
*tempData=P3;
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:
*tempData=0x66;
break;
}
return result;
}//uint16 getRegisterVal(uint16 addr,uint16 &data)
//设置寄存器值 返回0表示成功
uint16 setRegisterVal(uint16 addr,uint16 tempData)
{
uint16 result = 0;
uint16 tempAddr=addr;
switch(tempAddr)
{
case 0:
moni[0] = tempData;
break;
case 1:
moni[1] = tempData;
break;
case 2:
moni[2] = tempData;
break;
case 3:
moni[3] = tempData;
break;
case 4:
moni[4] = tempData;
break;
case 5:
moni[5] = tempData;
break;
case 6:
moni[6] = tempData;
break;
case 7:
moni[7] = tempData;
break;
case 8:
P1 = tempData;
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;
}//uint8 setRegisterVal(uint16 addr,uint16 data)
说明几点,
①偶校验
偶校验是针对串口数据帧而言的。
本例使用了偶校验,串口工作在方式3对实现偶校验比较方便。让第9位作为偶校验位即可。定时器1工作在模式2(自动重装),为串口提供波特率。如9600,,even,8,1.
用串口方式3也可以实现无校验,即在接收发送中断函数中不进行奇或偶校验,那么奇偶位就没用了,相当于停止位是两位。pc上需设置如9600,n,8,2.
但一般通过串口的方式1来实现无校验传输,较方便,且比方式3每次省发一位。pc上需设置如9600 ,n ,8 ,1.
//初始化串口
void initUart(void)
{
SCON = 0xd0; //工作方式3
PCON = 0x00;
// TH1 = 0xfD; //11.0592MHz PCON=0X00,TH1=0XFD 9600波特率
// TL1 = 0xfD; //12 PCON=0X80,TH1=0XF3 4800
TH1 = 0xFA; //22118400Hz PCON=0X00,TH1=0XFA 9600波特率
TL1 = 0xFA;
// TI = 1;
TR1 = 1; //启动定时器
ES = 1;
}//void initUart(void)
怎么偶校验?
串口的方式3,即11位异步收发,其每一个串口数据帧的格式是
起始位(0) D0 D1 D2 D3 D4 D5 D6 D7 TB8 停止位(1),其中TB8称第9位
先说一下51的发送,串口线路上闲时都是高电平1,执行指令move SBUF, A;则一个低电平0立刻塞进P3.1脚,然后SBUF中8位数据按照波特率移位到P3.1,再将TB8移位到P3.1,再将停止位1塞进P3.1。(硬件动将起始位1,SBUF,TB8,停止位0塞进P3.1)。所以每传输一个有效字节,需要在线路上额外传输3位。发送完成置中断TI为1。所以在程序里执行move SBUF,A之前要先把偶检验值装进TB8.。
再说一下51的接收:串口线路上闲时都是高电平1,当硬件检测到一个1->0的跳变即起始位时,便按照波特率从P3.1上采集8位数据到移位寄存器中,再传给SBUF中(此SBUF与发送时的SBUF不是同一个寄存器,虽然名字相同)并把第9位塞进RB8中。硬件接完一个字节到SBUF,置中断标志RI为1.
在程序的中断函数里,判断如果是RI中断,则从SBUF取出数据到ACC,再RB8中取出偶校验位和P比较,可得出有没错误。
如下,
如果出错则置checkoutError = 2;而在checkComm0Modbus()函数中检测checkoutError ,若非0,则不继续接受也不回复。主机侧在未接到设备回复的时候,需要自动重发或其他处理。
void commIntProc() interrupt 4
{
if(TI)//数据发送完毕
{
TI = 0;
if(sendPosi < sendCount)//小于发送的数量。
{
sendPosi++;
ACC = sendBuf[sendPosi];//放到acc中以便偶校验
TB8 = P; //加上校验位
SBUF = sendBuf[sendPosi];
}
else
{
receCount = 0; //清接收地址偏移寄存器
checkoutError = 0;
}
}
else if(RI)//有数据到来
{
RI = 0; //modbus响应小于10毫秒
receTimeOut = 10;
receBuf[receCount] = SBUF;//接收数据
/*
51de串口接收缓冲器是双缓冲结构,简单的说就是有两个8为寄存器来接客。一个是输入以为寄存器,一个是地址为0x99的接收SBUF,
硬件会按照波特率去采集引脚rxd即p3.0的数据,一位一位的塞进移位寄存器中〉。当移位寄存器满了8位数据,则硬件将移位寄存器中的8位数据塞进接收SBUF寄存器。
此时会引起串口接收中断,需要你立刻从SBUF中取走数据。如果此时不取,可能会被硬件塞进来的移位寄存器的下一个数据覆盖掉。
*/
ACC = receBuf[receCount];//放到acc中以便偶校验
if(P != RB8)
checkoutError = 2; //偶校验出错
receCount++; //接收地址偏移寄存器加1
receCount &= 0x0f; //最多一次只能接收16个字节
}
} // void CommIntProc()
②CRC检验
CRC校验是针对modbus rtu数据帧而言的。采用CRC16生成多项式。
MODBUS rtu数据帧格式如下
起始位 设备地址 功能代码 数据 CRC校验 结束符
T1-T2-T3-T4 8Bit 8Bit n个8Bit 16Bit T1-T2-T3-T4
仅对设备地址域,功能代码域,数据域进行CRC校验,产生16位的校验码。每个帧之间利用时间间隔来区分。一下是区分代码
void timeProc(void)
{
static uint8 c200ms;
b1ms = 0;
b10ms = 0;
b100ms = 0;
ET0 = 0;
dwTickCount = dwIntTick;
ET0 = 1;
if(bt1ms)
{
bt1ms = 0;
b1ms = 1;
if(receTimeOut>0)//达到1ms,receTimeOut--,初值10
{
receTimeOut--;
if(receTimeOut==0 && receCount>0) //receTimeOut==0,还没接收完,超时
{
/*
主机发的命令即modbus消息帧,一般是n个字节,并且连续发出。
如果在帧完成之前有超过1.5 个字符时间的停顿时间,接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地,如果一个新消息在小于3.5个字
符时间内接着前个消息开始,接收的设备将认为它是前一消息的延续。这将导致一个错误,因为在最后的CRC域的值不可能是正确的
在设备侧,即此处代码即是检测一帧是否完成。本例帧间隔时间应大于10ms。即主机发送每个命令的时间间隔至少要10ms,否则本单片机不买账。
每当sbuf里面来了1字节新数据,会引起接收中断,在中断处理中receTimeOut就会被置10,接收字节数也代表地址的变量receCount也会+1,然后receTimeOut每隔1ms减1。
在receTimeOut减到0之前,如果sbuf中又来了新数据而引起中断,此时receTimeOut又被置10,receCount再+1,然后receTimeOut继续每隔1ms减1
在receTimeOut已经减到0,但没有新的数据到达sbuf,则receCount被置0.表示新的一帧开始。
*/
receCount = 0; //将接收地址偏移寄存器清零,表示新的一帧开始。
checkoutError = 0;
}
}
}
if(bt100ms)
{
bt100ms = 0;
b100ms = 1;
}
if(bt10ms) //判断中断10ms标志位是否1
{
bt10ms = 0; //清中断10ms标志位
b10ms = 1;//到了10ms,就一直置1
c200ms++; //200ms计时器加1
if(c200ms >= 20) //判断是否计时到200ms
{
c200ms = 0; //清200ms计时器
}
}
} // void TimerProc(void)
③在强制线圈1时需要发送FF00,强制线圈0时需要发送0000,。这样设计符合modbus标准,通用。
The reguested ON/OFF state is specified by a constant in the query data field. A value of FF 00 hex requests the coil to be ON. A value of 00 00 requests it to be OFF. All other values are illegal and will not affect the coil. p32
④
在出错时,设备需要发送给主机异常信息。
l 如果服务器设备接收到无通信错误的请求,并且可以正常地处理询问,那么服务器设备将
返回一个正常响应。
2 如果由于通信错误,服务器没有接收到请求,那么不能返回响应。客户机程序将最终处理
请求的超时状态。
3 如果服务器接收到请求,但是检测到一个通信错误(奇偶校验、LRC、CRC、...) ,那么不
能返回响应。客户机程序将最终处理请求的超时状态。
4 如果服务器接收到无通信错误的请求,但不能处理这个请求(例如,如果请求读一个不存
在的输出或寄存器) ,服务器将返回一个异常响应,通知用户错误的本质特性。
异常响应报文有两个与正常响应不同的域:
功能码域:在正常响应中,服务器利用响应功能码域来应答最初请求的功能码。所有功能码的
最高有效位(MSB)都为 0(它们的值都低于十六进制 80) 。在异常响应中,服务器设置功能码的
SB 为1。这使得异常响应中的功能码值比正常响应中的功能码值高十六进制80。
通过设置功能码的MSB, 客户机的应用程序能够识别异常响应, 并且能够检测异常码的数据域。
数据域:在正常响应中,服务器可以返回数据域中数据或统计表(请求中要求的任何报文) 。在
异常响应中,服务器返回数据域中的异常码。这就定义了产生异常的服务器状态。
modbus 协议中文规范 http://download.csdn.net/detail/songqqnew/3857785