📄 modbus.c
字号:
/********************************************************************
*函数的编写力求独立性!函数用到的参数只通过形参出入口传输!
*对于 Modbus, 每个功能代码都对应着对一个特定的Modbus 参考集的访问。
*因此,在Modbus 消息的地址域中不包括最高位。
*0xxxx 参考集– Modbus 线圈(支持的功能码01,05,15),参考ACS510用户手册P154:
前32 个线圈专门用于控制字的逐位映射(00001~00020位,00021~00032保留);
继电器输出从线圈00033开始顺序编号到00038;
*1xxxx 映射 – Modbus 离散输入(支持的功能码02),参考ACS510用户手册P156:
前32 个输入专门用于状态字映射(10001~10032)。
硬件离散输入从输入33开始按位顺序编号(10033~10038)
*3xxxx 映射– Modbus 输入(支持的功能码04),参考ACS510用户手册P157:
任何由用户定义的模拟输入:30001(AI1),30002(AI2)
*4xxxx 寄存器映射.传动将它的参数和其它数据映射到4xxxx 保持寄存器(支持的功能码03/06/16/23).
40001 ~ 40099 映射到传动控制和实际值
40101 ~ 49999 映射到传动参数 0101 ~ 9999。
如果寄存器的地址不对应传动参数,那么该寄存器地址无效。
如果试图对参数地址以外的寄存器进行读写,那么Modbus 接口会向控制器返回一个异常码
**********************************************************************/
#include "GLOBAL.H"
//线圈(0xxxx):控制位、继电器输出
#define READ_COIL 01 //读取线圈状态(ABB ACS510)
#define SET_COIL 05 //对单个线圈进行强制(ABB ACS510)
#define SET_COILs 15 //对多个线圈进行强制(ABB ACS510)
//离散输入(1xxxx):状态位、离散输入
#define READ_DI 02 //读取输入状态(ABB ACS510)
//输入寄存器(3xxxx):模拟输入
#define READ_AI 04 //读取输入寄存器(ABB ACS510)
//保持寄存器(4xxxx):参数、控制字/ 状态字、给定
#define READ_HLD_REGs 03 //读单个4X 寄存器(ABB ACS510)
#define SET_HLD_REG 06 //写单个4X 寄存器(ABB ACS510)
#define SET_HLD_REGs 16 //写多个4X 寄存器(ABB ACS510)
#define READ_SET_HLD_REGs 23 //读/写 4X 寄存器(ABB ACS510)
//异常
#define PROTOCOL_ERR (-1)
#define FRM_ERR (-2)
#define CRC_ERR (-3)
#define CMD_ERR (-4) //
uchar Rx_Ptr; // 接收数据指针
uchar Tx_Ptr; // 发送数据指针
uchar Rx_Flag; // 接收标志 1: 准备接收; 0: 接收结束
uchar Tx_Flag; // 发送标志 1: 准备发送; 0: 发送结束
uchar Tx_Buffer[256],Rx_Buffer[256];
uint TxBytes; // 每帧信息中发送字节总数
uint RxBytes; // 每帧信息中接收字节总数
uchar Slave_addr = 1; //地址范围1~31,主设备默认31,从设备默认设置1
uchar Modbus_mode = 0; //0:RTU模式;1:ASCII模式
uchar ModbusBPS = 3; //默认9600bps,可选波特率:1200,2400,4800,9600,19200,38400,76800
uchar Commformat = 0;
/*Commformat范围0~5
数据位 校验位 停止位
0: 8 N 1
1: 8 N 2
2: 8 E 1
3: 8 E 2
4: 8 O 1
5: 8 O 1
*/
const uchar 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
} ;
const uchar 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
};
const uchar char_tab[128]=
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //31
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //47
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, //63
0, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
const uchar tab_char[16]=
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :getCRC16()
* 功能 :取字的高字节
* 返回 :getCRC16校验和(字)
* 备注 :Tested;
****************************************************************************************/
uint getCRC16(uchar *puchMsg , uint usDataLen)//*puchMsg:message to calculate CRC upon;usDataLen:quantity of bytes in message
{
uchar CRCH = 0xFF ; /* high byte of CRC initialized */
uchar CRCL = 0xFF ; /* low byte of CRC initialized */
uchar uIndex ; /* will index into CRC lookup table */
while (usDataLen--) /* pass through message buffer*/
{
uIndex = CRCL ^ *puchMsg++;/* calculate the CRC*/
CRCL = CRCH ^ auchCRCHi [uIndex] ;
CRCH = auchCRCLo [uIndex] ;
}
return (CRCH<< 8 | CRCL);//注意:Modbus传输getCRC16时要求低字节在前高字节在后
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :CheckCRC()
* 功能 :对接收的数据进行CRC校验,与发送来的CRC校验值进行比较
* 返回 :如果CRC校验值与接收到的CRC校验值相等,则返回1,否则返回0
* 备注 :
****************************************************************************************/
uchar CheckCRC(uchar *crctmp,uint length)
{
uint crc_result, crc_tmp;
crc_tmp = *(crctmp + length-2); // crc 低字节
crc_tmp = crc_tmp + (*( crctmp+length-1))* 256; // CRC 值
crc_result = getCRC16(crctmp, length-2); // 计算CRC 值
if ( crc_tmp != crc_result ) // 比较CRC值
return(0);
return(1);
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :send()
* 功能 :发送命令请求
* 返回 :无
* 备注 :该函数要借助串口中断,全局变量有Tx_Flag、Tx_Ptr、Tx_Buffer[]
****************************************************************************************/
void comSend(void)
{
//开始发送,后续数据在中断中继续发送
Tx_Flag = 1;//准备发送,发送完成后清零
Tx_Ptr = 0; //发送指针清零(也相当于发送计数器)
SODR1 = Tx_Buffer[0]; //发送第一个数据
SSR1_TIE = 1; //发送中断使能,通过串口中断发送剩余数据
while (Tx_Flag); //等待发送完成
SSR1_TIE = 0; //发送完成后,关闭发送中断
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :comReceive()
* 功能 :中断接收从设备发送来的应答数据,每100ms判断是否接收完毕。
* 返回 :如果接收成功,则返回接收到的字节数RxBytes,否则返回0,认为接收到的数据无效
* 备注 :该函数利用了2.5ms定时中断以及串口接收中断;
该函数修改了接收计数器Rx_Ptr,接收允许标志Rx_Flag,产生了接收数据总字节数;
接收到的数据,存放在Rx_Buffer[256]缓冲区中;
Rx_Ptr、Rx_Flag是全局变量,Rx_Buffer[256]是全局数组,都在串口中断中进行改变
****************************************************************************************/
uchar comReceive(void)
{
uint j,ptr_old;
//中断接收数据,100ms延时查询接收计数器变化,如果无变化,则认为接收完毕(只有被查询设备才处于应答状态)
Rx_Flag = 1;//准备接收,接收完成后清零
Rx_Ptr = 0; //接收指针清零,保证数据从缓冲区的首地址处开始存放
ptr_old = 0;//记录上次指针位置,以判断数据接收是否结束
j = soft_timer;
SSR1_RIE = 1; //接收中断使能,通过串口中断接收数据
while (Rx_Flag) //等待接收完成,以及数据溢出判断Rx_Ptr<256
{
if (soft_timer - j>40) //每50ms延时判断接收是否完毕
{
j = soft_timer;
if(Rx_Ptr > ptr_old)
ptr_old = Rx_Ptr; //正在接收
else
{
Rx_Flag = 0; //接收完成标志置位
SSR1_RIE = 0; //关接收中断
Rx_Ptr = 0; //初始化接收计数器
}
}
}
if (!Rx_Flag)//接收完毕,记录总字节数
{
RxBytes = ptr_old;//记录接收到的字节总数,以便命令处理用。
return (RxBytes);
}
RxBytes = 0;//超过256个字节,认为错误,清零;
return (0); //return(0)置于该处而不是置于while循环中的if语句后,是鉴于该函数具有超时重试的功能
}
/*******************************************************
* Name :init_uart1
* Func :Initiallize uart1
* Input :uint bps,uchar DataBits,uchar ParityBits, uchar StopBits
* Output:None
* Mark :φ=16M,div decided by CDCR1
CS2 CS1 CS0 BAUD Calculation
0 0 0 76,923 bps (φ/div) / (8 × 13 × 2)
0 0 1 38,461 bps (φ/div) / (8 × 13 × 4)
0 1 0 19,230 bps (φ/div) / (8 × 13 × 8)
0 1 1 9,615 bps (φ/div) / (8 × 13 × 16)
1 0 0 500 kbps (φ/div) / (8 × 2 × 2)
1 0 1 250 kbps (φ/div) / (8 × 2 × 4)
********************************************************/
void init_uart1(uchar bps,uchar Dataform)
{
switch(bps)
{
case 0://1200
{
//SMR0/1:MD1 MD0 CS2 CS1 CS0 BCH SCKE SOE
SMR1 = 0x19;
//CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0
// 使能预分频器,设置预分频器系数为4分频
CDCR1 = 0x87;
break; //实际产生1201bps的波特率
}
case 1://2400
{
//SMR0/1:MD1 MD0 CS2 CS1 CS0 BCH SCKE SOE
SMR1 = 0x19;
//CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0
// 使能预分频器,设置预分频器系数为2分频
CDCR1 = 0x83;
break;//实际产生2403bps的波特率
}
case 2://4800
{
//SMR0/1:MD1 MD0 CS2 CS1 CS0 BCH SCKE SOE
SMR1 = 0x19;
//CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0
// 使能预分频器,设置预分频器系数为2分频
CDCR1 = 0x81; //实际产生4807bps的波特率
break;
}
case 3://9600
{
//SMR0/1:MD1 MD0 CS2 CS1 CS0 BCH SCKE SOE
SMR1 = 0x19;
//CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0
// 使能预分频器,设置预分频器系数为1分频
CDCR1 = 0x80;
break; //实际产生9615bps的波特率
}
case 4://19200
{
//SMR0/1:MD1 MD0 CS2 CS1 CS0 BCH SCKE SOE
SMR1 = 0x11;
//CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0
// 使能预分频器,设置预分频器系数为1分频
CDCR1 = 0x80;
break;//实际产生19230bps的波特率
}
case 5://38400
{
//SMR0/1:MD1 MD0 CS2 CS1 CS0 BCH SCKE SOE
SMR1 = 0x09;
//CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0
// 使能预分频器,设置预分频器系数为1分频
CDCR1 = 0x80;
break;//实际产生38461bps的波特率
}
case 6://76800
{
//SMR0/1:MD1 MD0 CS2 CS1 CS0 BCH SCKE SOE
SMR1 = 0x01;
//CDCR0/1:MD - - - 0 DIV2 DIV1 DIV0
// 使能预分频器,设置预分频器系数为1分频
CDCR1 = 0x80;
break;//实际产生76,923bps的波特率
}
default:
break;
}
switch(Dataform)
{
//SCR0/1: PEN P SBL CL A/D REC RXE TXE 数据格式设置
case 0: //8 N 1
{
SCR1 = 0x13;//1位停止位 0001 0011
break;
}
case 1: //8 N 2
{
SCR1 = 0x33;//2位停止位 0011 0011
break;
}
case 2: //8 E 1
{
SCR1 = 0x93;//1位停止位 1001 0011
break;
}
case 3: //8 E 2
{
SCR1 = 0xB3;//2位停止位 1011 0011
break;
}
case 4://8 O 1
{
SCR1 = 0x93;//1位停止位 1101 0011
break;
}
case 5://8 O 2
{
SCR1 = 0xB3;//2位停止位 1111 0011
break;
}
default:
{
break;
}
}
//SSR0/1:PE ORE PRE PDRF TDRE BDS RIE TIE (SIDR/SODR)
SSR1_TIE = 0; // 关闭发送中断,发送时要开中断
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -