嵌入式系統(tǒng)通常都會與外部設(shè)備進行通訊,這就涉及到通訊協(xié)議的問題。這些通訊協(xié)議有的是標準協(xié)議有的廠家自定義的協(xié)議,如宇電的AI-BUS。在本篇中,我們將討論AI-BUS的驅(qū)動,以便于與宇電設(shè)備的通訊。
1 、功能概述
宇電的設(shè)備使用基于RS-485的自定義協(xié)議,該協(xié)議稱為AI-BUS。AI-BUS協(xié)議采用16進制數(shù)據(jù)格式來表示各種指令代碼。數(shù)據(jù)協(xié)議本身比較簡單,標準的通訊指令只有兩條,一條為讀指令,一條為寫指令:
讀:地址代號 +52H ( 82 ) + 要讀的參數(shù)代號 +0+0+ 校驗碼
寫:地址代號 +43H ( 67 ) + 要寫的參數(shù)代號 + 寫入數(shù)低字節(jié) + 寫入數(shù)高字節(jié) + 校驗碼
具體結(jié)構(gòu)如下圖所示:
地址代號:為了在一個通訊接口上連接多臺 AI 儀表,需要給每臺 AI 儀表編一個互不相同的通訊地址。有效的地址為 080(部分型號為 0100),所以一條通訊線路上最多可連接 81 臺 AI 儀表,儀表的通訊地址由參數(shù) Addr 決定。儀表內(nèi)部采用兩個重復的 128208(16 進制為 80HD0H)之間數(shù)值來表示地址代號,由于大于 128 的數(shù)較少用到(如 ASC 方式的協(xié)議通常只用 0-127 之間的數(shù)),因此可降低因數(shù)據(jù)與地址重復造成沖突的可能性。 AI 儀表通訊協(xié)議規(guī)定,地址代號為兩個相同的字節(jié),數(shù)值為(儀表地址+80H)。例如:儀表參數(shù) Addr=10
(16 進制數(shù)為 0AH,0A+80H=8AH),則該儀表的地址代號為:8AH 8AH
參數(shù)代號:儀表的參數(shù)用 1 個 8 位二進制數(shù)(一個字節(jié),寫為 16 進制數(shù))的參數(shù)代號來表示。它在指令中表示要讀/寫的參數(shù)名。
校驗碼:校驗碼采用16 位求和校驗方式,其中讀指令的校驗碼計算方法為:要讀參數(shù)的代號 ×256+82+ADDR 。寫指令的校驗碼計算方法為以下公式做16位二進制加法計算得出的余數(shù)(溢出部分不處理):要寫的參數(shù)代號 ×256+67+ 要寫的參數(shù)值 +ADDR 。
公式中的數(shù)字都為十進制;公式中 ADDR 為儀表地址參數(shù)值,范圍是 0~80(注意不要加上 80H)。校驗碼為以上公式做二進制 16 位整數(shù)加法后得到的余數(shù),余數(shù)為 2 個字節(jié),其低字節(jié)在前,高字節(jié)在后。要寫的參數(shù)值用 16 位二進制整數(shù)表示。
返回的數(shù)據(jù)格式更是固定的,無論是讀還是寫,儀表都返回以下10個字節(jié)數(shù)據(jù):
測量值 ** PV+** 給定值 ** SV+** 輸出值 ** MV ** 及報警狀態(tài) + 所讀 / 寫參數(shù)值 + 校驗碼。
其中 PV、 SV 及所讀參數(shù)值均各占 2 個字節(jié),代表一個 16 位二進制有符號補碼整數(shù),低位字節(jié)在前,高位字節(jié)在后,整數(shù)無法表示小數(shù)點,要求用戶在上位機處理; MV 占一個字節(jié),按 8 位有符號二進制數(shù)格式,數(shù)值范圍-110~+110,狀態(tài)位占一個字節(jié),校驗碼占 2 個字節(jié),共 10 個字節(jié)。
返回校驗碼的計算公式: PV+SV+ (報警狀態(tài) *256+MV ) + 參數(shù)值 +ADDR
2 、驅(qū)動設(shè)計與實現(xiàn)
我們已經(jīng)清楚了AI-BUS協(xié)議的的基本規(guī)則,對于通訊協(xié)議的驅(qū)動開發(fā),只需要按照通訊協(xié)議來實現(xiàn)代碼就可以了。
2.1 、對象定義
同樣的我們在操作AI-BUS設(shè)備之前,我們先需要定義AI-BUS設(shè)備對象。然后針對AI-BUS設(shè)備的操作就是針對該對象的操作。
2.1.1 、對象的抽象
根據(jù)AI-BUS設(shè)備對象的特點,我們抽象對象類型。該對象各類型包括設(shè)備地址和狀態(tài)2個屬性和發(fā)送命令一個操作。而對于消息的接收我們一般采用串口中斷方式。對象類型定義如下:
/* 定義AI-BUS設(shè)備對象 */
typedef struct AIbusObject {
uint8_t deviceAddr;
uint8_t status;
void(*SendBytes)(uint8_t *cmd,uint16_t size);
}AIbusObjectType;
2.1.2 、對象初始化
定義了AI-BUS對象類型后,我們就可以使用該類型聲明不同的對象,但是聲明的對象僅為一個對象變量,在使用之前必須對其進行初始化,初始化函數(shù)如下:
/* AI-BUS對象初始化 */
void AIbusInitialization(AIbusObjectType*aibus,uint8_t addr,AiBusSendBytessend)
{
if((aibus==NULL)||(send==NULL))
{
return;
}
aibus->deviceAddr=addr;
aibus->SendBytes=send;
}
2.2 、對象操作
完成了對象的初始化后就可以實現(xiàn)對對象的操作了。根據(jù)前面對AI-BUS協(xié)議的了解,我們所需要完成的操作實際上就是3個方面。一是對目標設(shè)備參數(shù)的讀操作;二是對目標設(shè)備參數(shù)的寫操作;三是對接收到的消息進行解析。
2.2.1 、讀對象操作
對AI-BUS對象的讀就是將讀命令按一定格式下發(fā)就好了。讀命令的格式為**:地址代號** +52H ( 82 ) + 要讀的參數(shù)代號 +0+0+ 校驗碼 ??梢該?jù)此編寫讀操作如下:
/*讀取目標設(shè)備的參數(shù)值*/
void ReadAiBusDeviceParameter(AIbusObjectType *aibus,uint8_tparaAddr)
{
uint8_t readCommand[INSTRUCTION_LENGTH];
uint16_t index=0;
readCommand[index++]=0x80+aibus->deviceAddr;
readCommand[index++]=0x80+aibus->deviceAddr;
readCommand[index++]=READ_INSTRUCTION;
readCommand[index++]=paraAddr;
readCommand[index++]=0x0;
readCommand[index++]=0x0;
uint16_tcheckSum=(uint16_t)paraAddr*256+READ_INSTRUCTION+(uint16_t)aibus->deviceAddr;
readCommand[index++]=checkSum;
readCommand[index++]=(checkSum>>8);
aibus->SendBytes(readCommand,INSTRUCTION_LENGTH);
}
2.2.2 、寫對象操作
同樣,對AI-BUS對象的寫操作也是按照寫命令的格式下發(fā)命令就可以了。寫對象的命令格式為**:地址代號** +43H ( 67 ) + 要寫的參數(shù)代號 + 寫入數(shù)低字節(jié) + 寫入數(shù)高字節(jié) + 校驗碼 。我們據(jù)此可以編寫寫操作函數(shù):
/*設(shè)置目標設(shè)備的參數(shù)值*/
void WriteAiBusDeviceParameter(AIbusObjectType *aibus,uint8_t paraAddr,uint16_t data)
{
uint8_t writeCommand[INSTRUCTION_LENGTH];
uint16_t index=0;
writeCommand[index++]=0x80+aibus->deviceAddr;
writeCommand[index++]=0x80+aibus->deviceAddr;
writeCommand[index++]=WRITE_INSTRUCTION;
writeCommand[index++]=paraAddr;
writeCommand[index++]=data;
writeCommand[index++]=(data>>8);
uint16_t checkSum=(uint16_t)paraAddr*256+WRITE_INSTRUCTION+(uint16_t)aibus->deviceAddr+data;
writeCommand[index++]=checkSum;
writeCommand[index++]=(checkSum>>8);
aibus->SendBytes(writeCommand,INSTRUCTION_LENGTH);
}
2.2.3 、消息解析
我們已經(jīng)知道AI-BUS對象的返回消息是一個固定的的格式。即:測量值 ** PV+** 給定值 ** SV+** 輸出值 ** MV ** 及報警狀態(tài) + 所讀 / 寫參數(shù)值 + 校驗碼 。每一個字都是低字節(jié)在前,而校驗碼則是返回的數(shù)據(jù)和加上設(shè)備地址,我們據(jù)此編寫解析函數(shù):
/*解析返回數(shù)據(jù),返回值為讀或者寫的參數(shù)值*/
int ParsingReturnData(uint8_t*receiveData,uint16_t *returnData,AIbusObjectType *aibus,uint16_t deviceNum)
{
int status=-1;
uint16_t pValue=0;
uint16_t sValue=0;
uint16_t mValue=0;
uint16_t alarmStatus=0;
uint16_t paraValue=0;
uint16_t checkSum=0;
pValue=receiveData[0]+receiveData[1]*256;
sValue=receiveData[2]+receiveData[3]*256;
mValue=(uint16_t)receiveData[4];
alarmStatus=(uint16_t)receiveData[5];
paraValue=receiveData[6]+receiveData[7]*256;
checkSum=receiveData[8]+receiveData[9]*256;
uint16_t chk=pValue+sValue+alarmStatus*256+mValue+paraValue;
for(int i=0;iif(checkSum==chk+aibus[i].deviceAddr)
{
status=i;
returnData[0]=pValue;
returnData[1]=sValue;
returnData[2]=mValue;
returnData[3]=alarmStatus;
returnData[4]=paraValue;
break;
}
}
aibus[status].status=alarmStatus;
return status;
}
3 、驅(qū)動的使用
AI-BUS協(xié)議設(shè)備驅(qū)動的使用主要按照三個步驟來操作:聲明并初始化對象;發(fā)送操作命令;接收并解析消息。接下來我們將據(jù)此完成驅(qū)動的使用。
3.1 、聲明并初始化對象
我們需要使用AIbusObjectType類型聲明對象變量。同時我們要實現(xiàn)一個typedef void (*AiBusSendBytes)(uint8_t *cmd,uint16_t size)類型的操作函數(shù)。我們假設(shè)使用的USART1端口,則具體實現(xiàn)如下:
/*發(fā)送數(shù)據(jù)*/
void AiBusSendByte(uint8_t *instruction,uint16_t length)
{
/*RS485設(shè)置為發(fā)送模式,準備發(fā)送*/
TEMPCTL_TRANSMIT_ALLOW();
aiBusRxLength=0;
uint16_t i;
for(i=0;i/*傳送寄存器不為空,等待傳送結(jié)束*/
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET)
{
}
/*寫一個字節(jié)到對應的串口傳送數(shù)據(jù)寄存器*/
USART_SendData(USART1, instruction[i]);
}
Delayms(3);
/*發(fā)送完畢,將RS485改為接收模式準備接收*/
TEMPCTL_RECIEVE_ALLOW();
Delayms(20);
}
對于單個的對象我們直接使用其聲明就可:AIbusObjectType aiDev;假設(shè)其設(shè)備地址為uint8_t addr=0x01。然后調(diào)用初始化函數(shù)初始化。對于單臺設(shè)備則可直接調(diào)用:
AIbusInitialization(&aiDev,addr,AiBusSendByte);
而如果是在同一總線上有多個設(shè)備,我們也可以將其定義為數(shù)組形式。假設(shè)有4臺設(shè)備,地址我分別為:0x01,0x02,0x03,0x04,則可定義為:
AIbusObjectTypeaiDev[4];
uint8_t addr[4]={0x01,0x02,0x03,0x04};
然后同樣調(diào)用初始化函數(shù)初始化:
for(inti=0;i<4;i++)
{
AIbusInitialization(aiDev+i,addr[i],AiBusSendByte);
}
3.2 、發(fā)送操作命令
初始化完成后就可以對其進行真正的操作:讀取或者寫某個參數(shù)的值。對于讀參數(shù)的值操作則只需要調(diào)用讀操作函數(shù)來完成:
ReadAiBusDeviceParameter(&aiDev,paraAddr);
而對于寫參數(shù)值的操作也只是簡單的調(diào)用寫操作函數(shù)來完成:
WriteAiBusDeviceParameter(&aiDev,paraAddr,data);
如果是多個對象,與前面一樣操作數(shù)組的方式來操作就可以了,再次就不贅述。
3.3 、接收并解析消息
接收消息我們采用串口中斷接收。具體實現(xiàn)如下:
void USART1_ReceiveDataHandle(void)
{
if(aiBusRxLength>=RETURNING_DATA_LENGTH)
{
aiBusRxLength=0;
}
/*接收寄存器為空,等待字節(jié)被對應的串口完全接收*/
if(USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET)
{
/*獲取接收到的字節(jié)數(shù)*/
aiBusRxBuffer[aiBusRxLength++] =USART_ReceiveData(USART1);
}
}
我們知道接受的消息格式是固定的,我們調(diào)用消息解析函數(shù)來完成解析:
ParsingReturnData(receiveData,returnData,&aiDev,deviceNum);
其中receiveData是長度為10的uint8_t類型數(shù)組。returnData是長度為5的uint16_t類型數(shù)組。
4 、應用總結(jié)
我們完成了AI-BUS驅(qū)動的編寫及應用。我們使用其同時操作4臺溫度控制器。我們操作數(shù)組的方式簡化函數(shù)的調(diào)用過程。當然結(jié)果與我們的預期是相符的。
使用本驅(qū)動程序操作AI-BUS設(shè)備,有一點需要注意:對于不懂的設(shè)備類型,參數(shù)的具體地址是用所不同的,需要查看廠家的參數(shù)定義來操作。
-
AI
+關(guān)注
關(guān)注
88文章
35190瀏覽量
280229 -
bus
+關(guān)注
關(guān)注
0文章
121瀏覽量
48637 -
通訊協(xié)議
+關(guān)注
關(guān)注
10文章
289瀏覽量
20857 -
驅(qū)動設(shè)計
+關(guān)注
關(guān)注
1文章
111瀏覽量
15556
發(fā)布評論請先 登錄

STM32應用實例七:與宇電設(shè)備實現(xiàn)AI-BUS通訊
基于LabVIEW的Modbus串口通訊協(xié)議的實現(xiàn)
S.BUS協(xié)議原理
采用socket形式進行通訊的ec20驅(qū)動是需要自己實現(xiàn)mqtt協(xié)議嗎?
Modbus通訊協(xié)議的幾種實現(xiàn)方式
基于DSP的CANopen通訊協(xié)議的實現(xiàn)
基于C++的modbus通訊協(xié)議模型實現(xiàn)
宇電AIBUS及MODBUS通訊協(xié)議說明V80
ModbusRTU通訊協(xié)議(主站)通訊聯(lián)機便利指令

評論