找回密码
 注册

QQ登录

只需一步,快速开始

搜索

C51 modbus 通信协议 可以完成01 03 05 06 功能码

[复制链接]
山海致远 发表于 2013-2-3 14:09:58 | 显示全部楼层 |阅读模式
#include "main.h"
sbit SPEAK=P3^4;


/**********************************************************************
欢迎指正,多多交流,只有不断交流才会不断进步,
本协议内容是根据网络资源修改,网上的MODbus协议大多不能使用,本协议修改后可以完成01 03 05 06
功能码。10功能码大家完成吧,
本程序已经调试好,直接下载就可以使用,

-----------------------------------------------------------------------
modbus RTU 的C51程序
-----------------------------------------------------------------------
单片机AT89C51 11.0592MHZ
-----------------------------------------------------------------------
通信波特率 9600 8位数据 1位停止位 偶校验 485通位接口
-----------------------------------------------------------------------
单片机控制板地址 localAddr(变量)
通信可设置数据的地址:
字地址 0 - 255 (只取16位的低8位)
**********************************************************************/
uint16 idata D[64] _at_ 0x40; //10进制地址是64,
/**********************************************************************
D0 的10进制地址是 64
D1 的10进制地址是 65
该ModBUS支持的功能
01 读线圈 可以读取单个或多个线圈,
上位机发送数据格式 地址,功能码,线圈地址高位,线圈地址低位,读取线圈个数高位,读取线圈个数低位,CRC低位,CRC 高位,
下位机回应数据格式,地址 功能码,数据个数, 数据,CRC低位,CRC 高位,
03 读寄存器,可以读取单个或者多个寄存器
上位机发送数据格式 地址,功能码,寄存器地址高位,寄存器地址低位,数据个数高位,数据个数低位,CRC低位,CRC 高位,
下位机回应数据格式,地址 功能码,数据个数,数据1高位 数据1低位 数据2高位 数据2低位 。。。。,CRC低位,CRC 高位,
05 写单个线圈
上位机发送数据格式 地址,功能码,线圈地址高位,线圈地址低位,数据高位,数据低位,CRC低位,CRC 高位,
下位机回应数据格式,地址,功能码,线圈地址高位,线圈地址低位,数据高位,数据低位,CRC低位,CRC 高位,
06 写单个寄存器
上位机发送数据格式 地址,功能码,数据地址高位,数据地址低位,数据高位,数据低位,CRC低位,CRC 高位,
下位机回应数据格式 地址,功能码,数据地址高位,数据地址低位,数据高位,数据低位,CRC低位,CRC 高位,
0x10 写多个寄存器
上位机发送数据格式 地址,功能码,线圈地址高位,线圈地址低位,读取线圈个数高位,读取线圈个数低位,CRC低位,CRC 高位,
下位机回应数据格式,地址 功能码,数据个数, 数据,CRC低位,CRC 高位,
**********************************************************************/

uint8 xdata sendBuf[24],receBuf[24]; //发送接收缓冲区
uint8 idata checkoutError; // ==2 偶校验错
uint8 idata receTimeOut; //接收超时
bit idata bt1ms;//100ms,bt100ms; //定时标志位
uint8 xdata M[100] _at_ 0x00;
//-------------------------------定时器0 1ms 中断 -------------------------
void timer0_IntProc() interrupt 1
{
TL0 = TIMER_LOW;
TH0 = TIMER_HIGHT;
bt1ms = 1;

}
//--------------------------------串行中断程序---------------------------
void comm_IntProc() interrupt 4
{
if(TI)
{
TI = 0;
if(sendPosi < sendCount)
{
sendPosi++;
ACC = sendBuf[sendPosi];
TB8 = P; //加上校验位
SBUF = sendBuf[sendPosi];
}
else
{
//发送完后将485置于接收状态
b485Send = 1;
receCount = 0; //清接收地址偏移寄存器
checkoutError = 0;
}
}
else if(RI)
{
RI = 0;
receTimeOut = 10; //通讯超时值 这个地方很重要
//receTimeOut = 50; //通讯超时值
receBuf[receCount] = SBUF;
ACC = receBuf[receCount];
if(P != RB8)
checkoutError = 2; //偶校验出错
receCount++; //接收地址偏移寄存器加1
receCount &= 0x0f; //最多一次只能接收16个字节
}

}
//------------------------------------定时处理--------------------------------
void timeProc(void)
{
if(bt1ms)
{
bt1ms = 0;
if(receTimeOut>0)
{
receTimeOut--;
if(receTimeOut==0 && receCount>0) //判断通讯接收是否超时
{
//将485置为接收状态
b485Send = 1;
receCount = 0;// //将接收地址偏移寄存器清零
checkoutError = 0;
}
}
}
}
//------------------------------串口初始化------------------------------------
void initUart(void)
{
//偶校验
SCON=0xD0;
PCON=0X80;
ES = 1;
}
//-----------------------------初始化中断------------------------------------
void initInt(void)
{
TMOD= 0x21; //T0用于定时,T1用于波特
TH0 = TIMER_HIGHT;
TL0 = TIMER_LOW;
TR0 = 1;
ET0 = 1; //开中断T0
TH1 = 0xfa; //晶振为11.0592波特率为9600,SMOD=1,12T,TH1=F6
TR1 = 1;
TI = 1; //发送允许中断,
EA=1;
}
//--------------------------初始化-----------------------------
void initProg(void)
{
initInt(); initUart(); b485Send = 1;
}
//----------------------MAIN_PROG---------------------------------------------
void main(void)
{
initProg(); //初始化

M[0]=0x11;
M[1]=0x22;
M[2]=0x33;
M[3]=0x44;
M[4]=0x55;
M[5]=0x66;
M[6]=0x77;
M[7]=0x88;
M[8]=0x99;

D[0]=0X1122;
D[1]=0x3344;
D[2]=0x5566;
D[3]=0x7788;
D[4]=0X99aa;
while(1)
{
timeProc();///////////////////////////通讯
checkComm0Modbus();///////////////////通讯
/**********************************控制程序*********************************************/
/*测试程序说明 上位机发送 01 06 00 40 56 78 或者01 05 00 00 00 00 蜂鸣器都应该响*/
if(M[0]==0X00|D[0]==0X5678)
{
SPEAK=0;
}
else
SPEAK=1;

/*上位机发送数据说明
读取一个线圈
主机器发送 地址,功能码,寄存器高位,寄存器低位,数据个数高位, 数据个数低位,CRC低位,CRC高位
数组位置 0 1 2 3 4 5 6 7
例如读取MO 发送, 01 01 00 00 00 01
主机器返回 地址,功能码, , 数据个数, 数据1低位 CRC低位,CRC高位
数组位置 0 1 2 3
例如读取MO 返回, 01 01 01 11

读取一个寄存D0 D0 的地址为3F ,D1地址为41 D2地址为43 D3地址为45 D4 地址为47
主机器发送 地址,功能码,寄存器高位,寄存器低位,数据个数高位, 数据个数低位,CRC低位,CRC高位
数组位置 0 1 2 3 4 5 6 7
例如读取D0 发送, 01 03 00 3F 00 01
主机器返回 地址,功能码, , 数据个数, 数据1低位 CRC低位,CRC高位
数组位置 0 1 2 3
例如读取D0 返回, 01 01 01 11
/********************************** END *************************************************/

}
}




以下为MODBUS.C 文件

#include "main.h"

//字地址 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 xdata localAddr = 01; //单片机控制板的地址
uint8 xdata sendCount; //发送字节个数
uint8 xdata receCount; //接收到的字节个数
uint8 xdata sendPosi,xxx=0; //发送位置

uint8 xdata *MADDR; //M区地址指针
//uint8 xdata *DADDR; // D区地址指针

uint16 crc16(uint8 *puchMsg, uint16 usDataLen)
{
uint8 xdata uchCRCHi = 0xFF ; /* 高CRC字节初始化 */
uint8 xdata uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */
uint32 xdata 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];
b485Send =0;

}

/*读线圈状态能够正确读取一个或者多个线圈状态 OK*/
void readCoil(void)
{
uint8 *xuwu;
uint8 i;
uint8 byteCount;
uint16 crcData;
// 读取一个线圈
//主机器发送 地址,功能码,寄存器高位,寄存器低位,数据个数高位, 数据个数低位,CRC低位,CRC高位
//数组位置 0 1 2 3 4 5 6 7
//例如读取MO 发送, 01 01 00 00 00 01
//主机器返回 地址,功能码, , 数据个数, 数据 CRC低位,CRC高位
//数组位置 0 1 2 3
//例如读取MO 返回, 01 01 01 11 */
MADDR= (uint8*)receBuf[3]; //读取寄存器开始位置 //强制类型转换 值得思考,
byteCount = receBuf[5]; //字节个数
for(i=0;i<byteCount;i++,MADDR++)
{
xuwu=(uint8*)MADDR;
sendBuf[i+3] =*xuwu;
}
//组织发送的数据
sendBuf[0] = localAddr; //地址
sendBuf[1] = 0x01; //功能码
sendBuf[2] = byteCount; //发送的数据个数
// sendBuf[3] = *MADDR; //发送的数据
byteCount += 3; //参加CRC校验计算的数据个数,
crcData = crc16(sendBuf,byteCount); //CRC校验
sendBuf[byteCount] = crcData >> 8; //应该是低位,怎么成了高位??????
byteCount++;
sendBuf[byteCount] = crcData & 0xff; //应该是高位,怎么成了低位??????
sendCount = byteCount + 1;
beginSend();
}

/*读寄存器能够正确读取一个或者多个寄存器的数据 */
void readRegisters(void)
{
uint8 tempAddr,*xuwu;
uint16 crcData;
uint8 readCount;
uint8 byteCount;
uint8 i;
/* 读取一个寄存D0 D0 的地址为3F ,D1地址为41 D2地址为43 D3地址为45 D4 地址为47
主机器发送 地址,功能码,寄存器高位,寄存器低位,数据个数高位, 数据个数低位,CRC低位,CRC高位
例如读取D0 发送, 01 03 00 3F 00 01
主机器返回 地址,功能码, , 数据个数, 数据1低位 CRC低位,CRC高位
数组位置 0 1 2 3
例如读取D0 返回, 01 01 01 11 */
tempAddr = receBuf[3];//寄存器 地址的低位
readCount = receBuf[5]; //要读取的数据个数,
byteCount = readCount * 2; //字节数目=数据个数*2
for(i=0;i<byteCount;i+=2,tempAddr+=2)
{
xuwu=(uint8*)tempAddr;
sendBuf[i+3] =*xuwu;
sendBuf[i+4] =*(xuwu+1);
}
/*组织发送数据*/
sendBuf[0] = localAddr; //地址
sendBuf[1] = 3; //功能码
sendBuf[2] = byteCount; //发送字节个数
byteCount += 3;
crcData = crc16(sendBuf,byteCount);//CRC校验
sendBuf[byteCount] = crcData >> 8;
byteCount++;
sendBuf[byteCount] = crcData & 0xff;
sendCount = byteCount + 1;
beginSend();
}
//强制单个线圈
void forceSingleCoil(void)
{

uint8 i;
/* 强制一个线圈。
主机器发送 地址,功能码,寄存器高位,寄存器低位,数据高位, 数据低位,CRC低位,CRC高位
例如强制MO发送, 01 03 00 00 00 fa 01
返回数据相同,执行完成后MO=FA */
MADDR=(uint8*)receBuf[3]; //强制的地址 //强制类型转换 值得思考,
*MADDR=receBuf[5]; //强制地址的数据赋值
for(i=0;i<receCount;i++)
{
sendBuf = receBuf;
}
sendCount = receCount;
beginSend();

}
/*设置单个寄存器ok*/
void presetSingleRegister(void)
{
uint8 i;
uint8 *Daddr;

/* 设置寄存器一个寄存D0 D0 的地址为40 ,D1地址为42 D2地址为44 D3地址为46 D4 地址为48
主机器发送 地址,功能码,寄存器高位,寄存器低位,数据数高位, 数数低位,CRC低位,CRC高位
例如设置D0 发送, 01 06 00 3F 00 01 */
//设置成功原样返回
Daddr = (uint8*)receBuf[3]; //地址码
*Daddr=receBuf[4]; //地址的赋值数据
*(Daddr+1)=receBuf[5]; //地址的赋值数据
for(i=0;i<receCount;i++)
{
sendBuf = receBuf;
}
sendCount = receCount;
beginSend();
}

//设置多个寄存器 还未调试,
void presetMultipleRegisters(void)
{
uint8 addr;
uint8 tempAddr,*xxiang;
uint8 byteCount;
uint8 setCount;
uint16 crcData;
uint16 tempData;
uint8 i;

/* 设置寄存器一个寄存D0 D0 的地址为3F ,D1地址为41 D2地址为43 D3地址为45 D4 地址为47
主机器发送 地址,功能码,寄存器高位,寄存器低位,数据数高位, 数数低位,CRC低位,CRC高位
例如设置D0 发送, 01 10 00 3F 00 01 */
//设置成功原样返回

addr = receBuf[3]; //地址码
tempAddr = addr & 0xff; // 屏蔽高位,
xxiang=(uint8*)tempAddr+2;
xxx=tempAddr+2;

setCount = receBuf[5];
byteCount = receBuf[6];

for(i=0;i<setCount;i++,tempAddr++)
{
tempData = (receBuf[i*2+7]<<8) + receBuf[i*2+8];
*xxiang=receBuf[8];
*(xxiang-1)=receBuf[7];
}
//组织返回数据,
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();
}
//检查uart0数据
checkComm0Modbus()
{
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;
}
}
return xxx;
}
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|手机版|小黑屋|ELEOK |网站地图

GMT+8, 2024-12-27 17:51

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表