C51应用 Modbs Rtu协议实现与KEPServerEx 通信

时间:2022-10-06 03:44:37

最近一客户要求使用STC12C5A60S2实现Modbus Rtu协议与KEPServerEx V4.0软件通信,采集单片机P2口每位的状态,设置P0口每位的状态,实现三路AD转换其中一路采集的是C02的浓度,以及使用SHT10获取温度和湿度。KEPServerEx V4.0使用TCP通信,而单片机使用的是串口RS232通信,所以增加了TCP转RS232的模块。

本程序相对比较简单,STH10有现成的代码,AD转换直接官方提供的程序,主要需要实现的就是Modbus RTU通信协议的实现。根据对方的要求,P0,P2口的设置需要一位一位的读取或设置,如果按Modbus的内容,可以使用设置线圈状态和读取线圈状态,而AD和温度湿度获取需要读取寄存器,这样一来就需要实现3个功能即对线圈设置线圈就读取寄存器。为了减少功能的实现数量,读取线圈和读取AD等数据都使用读取寄存器的03功能,为了方便能够位下位机传数据所以设置线圈使用06写单个寄存器。这样就只需要实现2个功能即可。在我的前一篇博客中已经介绍了使用单片机与PC进行通信,当时实现了03功能,本次只需要实现06功能即可。

06功能码的通信格式:
 上位机发送数据格式  地址,功能码,数据地址高位,数据地址低位,数据高位,数据低位,CRC低位,CRC 高位,
 下位机回应数据格式  地址,功能码,数据地址高位,数据地址低位,数据高位,数据低位,CRC低位,CRC 高位,

所以接收到上位机的命令后,只需要判断地址是否为本机地址,功能码是否为相应的06即可,如果是则对后面的数据进行解析,将相应的数据写入对应的寄存器即可。由于上位机每次发来的是对应P0口某一位的状态,所以需要有8个字节来对应8个位。而返回的信息就比较简单了,如果没有错误就直接将接收到的命令返回即可。

unsigned char createRespond_M_6(unsigned char *respondMessage,unsigned char* _messageReceived ,unsigned int* _registerData)
{
unsigned char i;
unsigned char numberOfPoints = ;
unsigned char _slaveAddress = ;
unsigned char bytesToSend = ;
unsigned char startAddress=;
unsigned char crcCalculation[]={,,}; unsigned int Address = _messageReceived[]; //将其转换为int型,由于使用的寄存器地址最多1,20个,所以只取低位即可,高位永远为0,丢弃 _registerData[Address ] = _messageReceived[]<<;
_registerData[Address ] |= _messageReceived[]; for(i = ; i < ; i ++)
{
respondMessage[i] = _messageReceived[i];
} return ;
}

由于在通信中要么是读取寄存器的值,要么是读取寄存器的做(其实就是开辟的一个整型数组),那么我们要实现设置P0口的状态和获取P2口的状态就需要在程序中根据需要,不停地更新寄存器的值或者从寄存器中读取数据写入到相应的端口即可,当然AD和温度湿度也是一样的道理。

在主函数的整个while大循环中加入以下代码:

        P2 = 0XFF;
//八路输入由P2口决定,随时更新其寄存器的值,若主机要返回,则直接返回相应寄存器的值 registerData[] = P2&0x01; registerData[] = (P2&0x02)>>;
registerData[] = (P2&0x04)>>;
registerData[] = (P2&0x08)>>;
registerData[] = (P2&0x10)>>;
registerData[] = (P2&0x20)>>;
registerData[] = (P2&0x40)>>;
registerData[] = (P2&0x80)>>; //八路输出由P0输出,只需要由主机设置寄存器值即可更新寄存器的值 。
P0_0 = registerData[]&0x01 ; P0_1 = registerData[]&0x01 ;
P0_2 = registerData[]&0x01 ;
P0_3 = registerData[]&0x01 ;
P0_4 = registerData[]&0x01 ;
P0_5 = registerData[]&0x01 ;
P0_6 = registerData[]&0x01 ;
P0_7 = registerData[]&0x01 ; GetTempAndHumi(); //检查串口数据并使用ModBus协议进行检验,正确则返回数据,返回的数据在registerData,可以在其他的函数中改变相应的值。
CheckModBusAndRespondToHost();

对于AD的数据,由于AD的结果是使用的中断方式而不是查询所以在主循环中就看不到了,在中断函数中直接设置寄存器的值,在需要时直接返回。这部分使用了10的AD转换, 所以需要做相应的转换:代码如下:

void adc_isr() interrupt  using
{
ADC_CONTR &= !ADC_FLAG; //Clear ADC interrupt flag registerData[ch+] = ADC_RES<<;
registerData[ch+] |= ADC_LOW2<<;
registerData[ch+] = registerData[ch+] >>; //if you want show 10-bit result, uncomment next line
// SendData(ADC_LOW2); //Show ADC low 2-bit result if (++ch > ) ch = ; //switch to next channel
ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | ch;
}

在这里只使用了3路AD转换。
在使用Modbus RTU数据进行寄存器的读写时需要注意的是在上位机比如组态王、KEPServerEx等,在设置寄存器地址时是从1开始,而发送的命令是从0开始,

读写寄存器都位于4区,所以我们 设置的地址一般都为40001.....40002等,对应寄存器0    1。

以下是在KEPServerEx设置的每个端口即AD等所对应的寄存器地址,

C51应用 Modbs Rtu协议实现与KEPServerEx 通信

测试结果:

C51应用 Modbs Rtu协议实现与KEPServerEx 通信

在这里AD全为1023是因为开发板全部接了上拉所以都为1023。

在这次开发完成后,客户反应AD测量结果不准确,由于我并没有在客户的现场,无法得知道具体如何,所以我的第一反应是STC12C5A60S2的AD基准电源使用的是VCC,可能是VCC电源与5V差距比较远而引起的,建议客户增加电池做为基准或者提高电源的精度,但对方说比较困难。后来我问他如何发现不准确的,他说是和别的系统测量的结果进行比较的,并且给我发来了他们的传感器资料,我看了一下资料,突然发现传感器输出是0-2v,而STC的AD是0-5V,一下就明白了这中间的问题,让客户将结果X2.5后就正常了。