題目名稱:模擬Modbus協議
Description
在嵌入式系統開發中,Modbus協議是工業控制系統中廣泛應用的一種協議。本題用來簡單模擬Modbus協議,只需根據條件生成符合該協議的數據幀,並解析所獲取的數據。
假設設備使用的協議發送數據格式如下:
<SlaveAddress, 1 Byte> <Function, 1 Byte> <Start Address, 2 Bytes> <NumberofBytes, 2 Bytes> <Checksum, 2 Bytes>
其中前四項將在輸入條件中給出,最後一項爲CRC校驗和,需根據前四項的數據,按照CRC算法進行計算。注意數據的長度,多於1byte的高位在前,低位在後。該CRC校驗算法的描述如下:
1)將CRC賦值0xFFFF。
2)取初始信息的第一個字節(8位)與CRC進行異或運算,將結果賦給CRC。
3)將CRC數據右移一位,最前位(左邊)補0。
4)如果右移前,CRC最低位(最右端)爲1,則將右移後的CRC與0xA001進行異或運算,且將結果賦給CRC。否則,跳過此步。
5)重複3,4步8次(即右邊8位)。
6)對初始信息的下一個字節,同樣執行2,3,4,5步,直到信息中所有字節都執行了同樣的步驟。
7)將此時得到的CRC值的高8位和低8位交換,即得到CRC校驗和。
對應的接收格式如下:
<SlaveAddress,1Byte> <Function,1Byte> <NumberofBytes,1Byte> <DataIEEE32,xByte> <Checksum,2Bytes>
其中DataIEEE32爲一個或多個按IEEE754標準定義的32位浮點數,具體的數據長度由NumberofBytes項來決定(比如NumberofBytes爲4,則DataIEEE32項爲4 bytes,正好表示一個浮點數;如爲8,則DataIEEE32項爲8 bytes,可表示兩個浮點數)。本題要求編程實現從IEEE32數據(如“420B999A”)到浮點數(如34.9)的轉換,從而解析出浮點數值。
提示:你可以根據IEEE754標準自行設計轉換算法;或者直接利用C語言float類型的實現特性:x86 linux下,gcc編譯器將C語言代碼“float f = 34.9;”編譯成彙編代碼“movl $0x420b999a, -4(%ebp)” (AT&T x86彙編格式),也就是說,單精度浮點數34.9在內存中就是由整數0x420b999a來表示的,你可以利用這一特性來完成轉換。
Input
輸入包含多組數據,以EOF結束
每組數據共兩行。
第一行共四個十進制整數,分別爲協議格式要求的:<SlaveAddress, 1 Byte>,<Function, 1 Byte>,<Start Address, 2 Bytes>,<NumberofBytes, 2 Bytes>,以逗號“,”分開。
如:1,4,40,2
其中:1爲SlaveAddress;4爲Function;40爲Start Address;2爲NumberofBytes。
第二行爲符合接收格式的數據幀(16進製表示),需從其中解析所接收的數據,其長度小於64個字符,浮點數數據最多爲4個(即DataIEEE32數據項最多爲32bytes)。
如: 010404420B999A7405
其中:01爲SlaveAddress;04爲Function;04爲NumberofBytes; 420B999A爲DataIEEE32;7405爲Checksum。
Output
每組數據輸出共兩行。
第一行:根據輸入結果的第一行,輸出完整的符合該協議發送格式的數據幀,數據用16進制大寫表示,每部分的長度都要求符合協議格式,比如Start Address項如果不到2 bytes,則需要在左邊補零。
如:010400280002F1C3
其中:01爲SlaveAddress;04 爲Function;0028爲Start Address;0002爲NumberofBytes;F1C3爲Checksum。
第二行:根據輸入結果的第二行,依次解析IEEE32數據,將其轉換成浮點數並打印結果(小數點後保留一位)。解析之前需檢查CRC校驗和,如校驗失敗則直接打印CRC_ERROR。如有多個數據,用逗號分隔。
如:34.9
該浮點值爲420B999A所對應的值。
Sample Input
1,4,40,2
010404420B999A7405
1,4,40,2
010404420B999A7404
2,4,383,4
02040841CC0000477F2100DF85
Sample Output
010400280002F1C3
34.9
010400280002F1C3
CRC_ERROR
0204017F0004C1DE
25.5,65313.0
Source (參考代碼)
#include <stdio.h>
#include <string.h>
#define MAX_RECEIVE_LEN 65
int char2int(char c)
{
if(c >= '0' && c <= '9') return c - '0';
return c - 'A' + 10;
}
unsigned short cal_crc(unsigned char* p,int len)
{
unsigned short ret = 0xFFFF;
int i = 0,k = 0;
for(;i < len;++i)
{
ret ^= p[i];
for(k = 0;k < 8;++k)
{
ret = (ret&0x01)?((ret>>1)^0xA001):(ret>>1);
}
}
ret = ((ret&0x00FF)<<8)|((ret&0xFF00)>>8);
return ret;
}
int main()
{
int saveadd = 0,func = 0,startadd = 0,num = 0,i = 0,len = 0,k = 0;
unsigned short val = 0;
unsigned char receive[MAX_RECEIVE_LEN];
unsigned char receivetmp[MAX_RECEIVE_LEN];
unsigned char str[MAX_RECEIVE_LEN];
unsigned char fstr[4];
float * pf = NULL;
while(EOF != scanf("%d,%d,%d,%d",&saveadd,&func,&startadd,&num))
{
str[0] = saveadd;str[1] = func;
str[3] = startadd&0xFF;startadd >>= 8;str[2] = startadd&0xFF;
str[5] = num&0xFF;num >>= 8;str[4] = num&0xFF;
val = cal_crc(str,6);
str[7] = val&0xFF;val >>= 8;str[6] = val&0xFF;
for(i = 0;i < 8;++i) printf("%02X",str[i]);
printf("/n");
scanf("%s",receivetmp);
len = strlen((char*)receivetmp)/2;
for(i = 0;i < len;++i)
{
receive[i] = (unsigned char)(char2int(receivetmp[i*2])*16+char2int(receivetmp[i*2+1]));
}
val = cal_crc(receive,len-2);
if((val&0xFF) != receive[len-1]||((val>>8)&0xFF) != receive[len-2]) { printf("CRC_ERROR/n");continue; }
k = 2;num = receive[k]/4;++k;
for(i = 0;i < num;++i)
{
fstr[0] = receive[k+3];fstr[1] = receive[k+2];
fstr[2] = receive[k+1];fstr[3] = receive[k];
pf = (float*)(fstr);
printf("%.1f",*pf);
if(i != num - 1) printf(",");
k += 4;
}
if(0 != num) printf("/n");
}
return 0;
}
注:該代碼來自參賽選手北京瑞星研發工程師曾劍傑,被“頂嵌杯”專家評審組公認爲符合工程化的高質量代碼。