📄 modbus.c
字号:
SSR1_RIE = 0; // 关闭接收中断,查询时要开中断
ICR13 = 5; // 设定中断优先级
}
#ifdef USE_UART1RX_INT
__interrupt void OnUART1RX(void)
{
if(Rx_Flag)
{
Rx_Buffer[Rx_Ptr] = SIDR1;// 接收数据存缓存
Rx_Ptr++; //接收计数器,又名接收指针,可以延时判断其变化情况,判断是否已经接收完毕
}
SCR1_REC = 0; //清除错误标志
}
#endif
#ifdef USE_UART1TX_INT
__interrupt void OnUART1TX(void)
{
++Tx_Ptr;//在send_rec()中,已将Tx_Buffer[0]发出
if (Tx_Ptr < TxBytes)
{
SODR1 = Tx_Buffer[Tx_Ptr]; // 发送数据
}
else
{
Tx_Flag = 0; // 发送结束标志
SSR1_TIE = 0; // 关闭发送中断
}
}
#endif
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :wordHByte()
* 功能 :取字的高字节
* 返回 :字的高字节
* 备注 :Tested.
正数的原码、反码、补码相同;负数的补码为负数绝对值的反码加1
移位:一端的位被"挤掉" 而另一端空出的位以0 填补;如果是对有符号类型数据进行右移操作,
则在其左端补入原来数据的符号位,即保持原来的符号不变其右端的移出位被丢弃
****************************************************************************************/
uchar wordHByte(uint uintdata)
{
return((uchar) (uintdata>>8));
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :wordLByte()
* 功能 :取字的低字节
* 返回 :字的低字节
* 备注 :Tested
****************************************************************************************/
uchar wordLByte(uint uintdata)
{
return((uchar)uintdata);
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :ASCII2RTU()
* 功能 :将Modbus中ASCII模式串,去除起始和结束符后,转换为二进制串
* 返回 :RTU串的长度(字节数)
* 备注 :
****************************************************************************************/
uint ASCII2RTU( uchar *dest, uchar *source)
{
uchar i;
if( *source !=':') return 0;//非ASCII串
source ++;//忽略起始符“:”
i = 0; //初始化RTU串的长度计数器
while ( *source != 0x0d)
{
*dest = char_tab[*source];//asctohex函数每次合并两个ASCII字符成一个二进制的字节
source++;
*dest = *dest*16 + char_tab[*source];
dest ++;
source ++;
i++;
}
source ++;
if ( *source != 0x0a) return 0;//判断0x0D/0x0A结束符
else return i;//返回RTU串的长度(字节数)
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :getLRC()
* 功能 :ASCII模式下的LRC校验计算
* 返回 :LRC校验和(十六进制值),在构成ASCII串时,需要拆分转换hextoasc()以添加在串尾
* 备注 :Tested
*source 是初始的二进制串,所以可以直接LRC计算;
LRC计算方法:把每一个需要传输的数据(按照十六进制表示与计算)按字节叠加后取反加1即可;
计算中,不包括起始位、停止位、奇偶位(如果有的话);
此外,无符数相加,类似循环计数的形式,自动丢弃进位;
****************************************************************************************/
uchar getLRC(uchar *source,uint lenth)
{
uchar tmp;
tmp = 0;
while (lenth-- )
{
tmp += *source++;
}
return ((uchar)(-((char)tmp))); //LRC结果,比较新颖;也可以这样计算:0xFF-tmp+1
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :RTU2ASCII()
* 功能 :将RTU模式串去除两字节的getCRC16校验值后,转换为ASCII模式串
* 返回 :无
* 备注 :注意,该算法中*source不包含getCRC16校验域(两个字节);
转换后,增加起始位、停止位、LRC校验后才构成ASCII串
****************************************************************************************/
void RTU2ASCII( uchar *dest, uchar *source, uint lenth)
{
uchar tmp;
dest++; //预留起始符空间
//下面向字符0~9以及A~F转换,不包括两个字节的getCRC16校验值
for ( ; lenth>0; lenth--)
{
tmp = *source;
tmp = *source & 0xf0; //取高字节
tmp >>= 4;
*dest = tab_char[tmp];//高字节转换成ASCII码
dest++;
*dest = tab_char[*source & 0x0f];//低字节转换成ASCII码
dest++;
source++;
}
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :construct_ascii_frm()
* 功能 :构建ASCII模式帧
* 返回 :无
* 备注 :
****************************************************************************************/
void construct_ascii_frm ( uchar *dst_buf, uchar *src_buf, uchar lenth)
{
uchar lrc_tmp;
lrc_tmp = getLRC( src_buf, lenth);//对二进制串,计算LRC校验和
*(src_buf+lenth) = lrc_tmp;//添加LRC校验到二进制串
lenth++;//二进制串长度增1
*dst_buf = ':'; //添加起始符
RTU2ASCII( dst_buf,src_buf, lenth);//由二进制串转换为可视字符串
*(dst_buf + 2 * lenth+1) = 0x0d; //可视字符串尾添加结束符
*(dst_buf + 2 * lenth+2) = 0x0a; //可视字符串尾添加结束符,最终构成了ASCII码串
}
/***************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :construct_rtu_frm()
* 功能 :构建RTU模式帧
* 返回 :无
* 备注 :
****************************************************************************************/
void construct_rtu_frm ( uchar *dst_buf,uchar *src_buf,uchar lenth)
{
uint crc_tmp;
crc_tmp = getCRC16(src_buf, lenth); //对二进制串,计算getCRC16校验
*(src_buf+lenth) = crc_tmp & 0xff; //CRC 低字节在前
*(src_buf+lenth+1) = crc_tmp >> 8; //CRC 高字节在后
lenth++;
lenth++;
while ( lenth--) //形成Modbus通信的RTU串
{
*dst_buf = *src_buf;
dst_buf++;
src_buf++;
}
}
void FaultProcess(void)
{
switch(Rx_Buffer[2])
{
case 0x01://从设备接收到的功能码是不允许的
{
//在显示终端上报警该故障(测试用)
display_str(0,0," ");
display_str(0,2," ");
display_str(0,4," Cmd Invalid ");
display_str(0,6," ");
wait_ms(1000);
break;
}
case 0x02://对从设备来说,数据地址无效
{
//在显示终端上报警该故障(测试用)
display_str(0,0," ");
display_str(0,2," ");
display_str(0,4,"Address Invalid ");
display_str(0,6," ");
wait_ms(1000);
break;
}
case 0x03://对从设备来说,数据值无效
{
//在显示终端上报警该故障(测试用)
display_str(0,0," ");
display_str(0,2," ");
display_str(0,4," Data Invalid ");
display_str(0,6," ");
wait_ms(1000);
break;
}
case 0x04://
{
}
case 0x05://
{
}
case 0x06://
{
}
case 0x07://
{
}
case 0x08://
{
}
case 0x09://CRC校验错误
{
//在显示终端上报警该故障(测试用)
display_str(0,0," ");
display_str(0,2," ");
display_str(0,4," CRC Error ");
display_str(0,6," ");
wait_ms(1000);
break;
}
default:
{
//在显示终端上报警该故障(测试用)
display_str(0,0," ");
display_str(0,2," ");
display_str(0,4," COMM ERROR ");
display_str(0,6," ");
wait_ms(1000); //1s used for test
break;
}
}
}
/********************************************************************************************************
* 作者 :冯子龙
* 日期 :200709
* 名称 :Data_anlysis_RTU()
* 功能 :RTU模式 接收分析
* 返回 :1,CMD_ERR
* 备注 :
*********************************************************************************************************/
char Data_anlysis_RTU( int *dest, uchar *src,uint start_address, uint fr_lenth)
{
uint crc_result, crc_tmp;
uchar i, j, shift;
crc_tmp = *(src + fr_lenth-2); // crc 第一字节
crc_tmp = crc_tmp * 256 + *( src+fr_lenth-1); // CRC 值
crc_result = getCRC16(src, fr_lenth-2); // 计算CRC 值
if ( crc_tmp != crc_result )
{
#ifdef operatorDEBUG
Rx_Buffer[2] = 0x09;//显示 crc 错误
FaultProcess();
#endif
return CRC_ERR;// CRC 校验错误
}
switch ( *(src+1) ) // 功能码
{
case READ_COIL://读取继电器状态 CMD01
{
for ( i=0; i<*( src+2); i++)
{
shift = 1;
for ( j=0; j<8; j++)
{
*(dest+start_address+i*8+j) = shift & *( src+3);
*(src+3) >>= 1;
}
}
break;
}
case READ_DI://读取开关量输入 CMD02
{
for ( i=0; i<*( src+2); i++)
{
shift = 1;
for (j=0; j<8; j ++)
{
*(dest+start_address+i*8+j) = shift & *( src+3);
*( src+3)>>=1;
}
}
break;
}
case READ_HLD_REGs://读取多个保持寄存器 CMD03
{
for ( i=0; i<*( src+2); i+=2)
{
*(dest + start_address+ i/2)= *(src+i+3)*256 + *(src+i+4) ;
}
#ifdef operatorDEBUG
if(*( src+2)==2)//手持操作器显示用,因为手持仅进行每次读一个字的操作。连接ABB测试用
{
para_val = *(src+3)*256 + *(src+4);
}
#endif
break ;
}
case READ_AI://读取模拟量输入 CMD04
{
for ( i=0; i<*( src+2); i+=2)
{
*(dest + start_address+ i/2) = *( src+i+3)*256 + *( src+i+4) ;
}
break;
}
case SET_COIL://强制单个线圈进行 CMD05
{
break;
}
case SET_HLD_REG://写单个寄存器 CMD06
{
#ifdef operatorDEBUG
display_str(0,4, " Changed! ");//液晶提示更改完成
#endif
break;
}
case SET_COILs://强制多个线圈进行 CMD15
{
break;
}
case SET_HLD_REGs: //写多个寄存器 CMD16
{
break;
}
case READ_SET_HLD_REGs: //读写多个寄存器 CMD23
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -