大家好,我是『芯知識學堂』的SingleYork,前一篇文章給大家介紹了“SYK-0806-A2S1 工業自動化控制之【15-串口收發十六進制數】”,這一篇中,筆者繼續給大家介紹跟串口通信有關的“自定義協議通信”,
在一些工業應用的場合,我們經常需要用到串口通信,既然是要通信肯定是需要相關協議的支持,業內比較標準的協議當然要數MODBUS協議了,
然而MODBUS協議要完全弄懂,也并非易事,很多時候,可能我們只需要簡單控制一些輸出同時讀取輸入輸出狀態,以及設定一些引數等,如果用標準的MODBUS協議肯定是沒有問題的,但是并不是所有人都能在短時間內摸透MODBUS協議,
那么,或許有人會說,自己隨便寫個簡單的協議不就好了!沒錯,這樣也是可以,只要通信設備雙方都按照約定好的協議去執行相關動作即可,這一講中,筆者就要著重介紹這種自定義協議的通信了,
說到自定義協議,筆者第一次接觸的時候,還是在用迪文DGUS屏的時候,在接觸了迪文DGUS屏的指令后,筆者才學會的使用自定義協議來做一些通信,那么,筆者就以迪文DGUS屏的指令為例,跟大家詳細一下自定義協議的相關知識吧,
迪文DGUS串口資料幀的架構是由以下幾個部分組成:
幀頭(2位元組)+ 資料長度(1位元組)+ 指令(1位元組)+ 資料(N位元組)+ CRC校驗(2位元組,可選)
所有指令都是以十六進制數發送,以寫控制暫存器指令(80)為例:
假如我們需要講觸摸屏的畫面“從當前頁面切換到第五幅圖片”,那么我們只需要通過串口向屏發送如下指令即可:
5A A5 04 80 03 00 05(此處不帶CRC校驗)
那么這些十六進制數都代表什么意思呢?其含義如下:
5A A5:幀頭由兩個位元組組成,可以自定義
04:發送資料的長度(指從指令開始到最后的資料長度,此處從80指令開始共發送4個位元組)
80:寫控制暫存器指令
03:控制暫存器地址
00 05:圖片地址
在這條命令中,省去了CRC校驗,其實在很多場合也可以用和校驗的方式替代CRC校驗,比如,我們可以將上述指令改成如下方式:
5A A5 04 80 03 00 05 92
最后一個數92即是一個校驗和,從資料長度位開始到最后一個資料累加求余,即得到了校驗和,
當然咯,這個校驗和我們可以用1個位元組,也可以用2個位元組,看大家使用習慣了,筆者經常都是用1個位元組來做和校驗位,既然是自定義,那么肯定是你想怎么用就怎么用了,前提是,通信兩邊都用一樣的協議,這樣大家都能通信上了,哈哈,看到這里,相信大家對于自定義協議應該有一定了解了,
其實,自定義協議確實很簡單,可以自己任意定義一串數字,只要雙方都按照定義好的格式收發資料即可,
接下來,筆者就一個實際的案例,在這款工控板上演示一下自定義協議通信,該工控板上有6個輸出,分別是Y0-Y5;8個輸入,分別是X00-X07,用串口助手模擬上位機來發送指令,分別控制每個輸出口的輸出狀態,在工控板接收到串口助手發來的指令后,根據不同指令執行相關動作,并回傳此時輸入輸出口的狀態,
首先,筆者定義串口助手發送的通信幀格式如下:
幀頭(2位元組)+ 長度(1位元組)+ 命令(1位元組)+ 控制指令(2位元組)
控制邏輯如下:
串口助手發送(十六進制):5A A5 03 06 00 00,Y00輸出ON
串口助手發送(十六進制):5A A5 03 06 00 01,Y00輸出OFF
串口助手發送(十六進制):5A A5 03 06 00 01,Y01輸出ON
串口助手發送(十六進制):5A A5 03 06 00 11,Y01輸出OFF
串口助手發送(十六進制):5A A5 03 06 00 02,Y02輸出ON
串口助手發送(十六進制):5A A5 03 06 00 12,Y02輸出OFF
串口助手發送(十六進制):5A A5 03 06 00 03,Y03輸出ON
串口助手發送(十六進制):5A A5 03 06 00 13,Y03輸出OFF
串口助手發送(十六進制):5A A5 03 06 00 04,Y04輸出ON
串口助手發送(十六進制):5A A5 03 06 00 14,Y04輸出OFF
串口助手發送(十六進制):5A A5 03 06 00 05,Y05輸出ON
串口助手發送(十六進制):5A A5 03 06 00 15,Y05輸出OFF
其中:
5A A5 – 即為幀頭
03 ------ 為資料長度(從該為后面一位起資料總位元組數)
06 ------ 為命令字
00 00 – 為控制指令
本例中筆者偷懶了,也省去了和校驗/CRC校驗,不過筆者相信,看到這里了,大家對應該是有能力自己增加上和校驗的,要是實在不知道,可以私聊筆者,
那么,發送問題是解決了,但是,要怎么接收這一串指令呢?還是參考迪文DGUS接收資料幀的方式,首先校驗幀頭:5A A5,然后再判斷長度位,根據長度位來確定需要接收資料的長度,比如,此處長度位為03,那么,我們只需要在接收到長度位之后,繼續再接收完3個資料即可認為資料接收完成,具體代碼實作如下:
/********************* UART1中斷函式************************/
void UART1_int (void) interrupt UART1_VECTOR
{
static bit RX_5A_OK = 0;
static bit RX_A5_OK = 0;
static u8 UART1_DataTemp = 0;
if(RI)
{
RI = 0;
UART1_DataTemp = SBUF;
if(RX_5A_OK)
{
if(RX_A5_OK)
{
RX1_Buffer[COM1.RX_Cnt++] = UART1_DataTemp; //將接收到的陣列暫存到RX1_Buffer陣列
if(COM1.RX_Cnt == RX1_Buffer[0] + 1) //接收完成
{
Uart1_RX_Finish = 1; //資料接收完成,將標志位置1
RX_5A_OK = 0;
RX_A5_OK = 0;
}
}
else
{
if(UART1_DataTemp == 0xA5)
{
RX_A5_OK = 1;
COM1.RX_Cnt = 0;
}
}
}
else
{
if(UART1_DataTemp == 0x5A)
{
RX_5A_OK = 1;
}
}
if(COM1.RX_Cnt >= COM_RX1_Lenth) COM1.RX_Cnt = 0;
}
if(TI)
{
TI = 0;
COM1.B_TX_busy = 0;
}
}
當然,這里其實我們也可以使用結束符來實作,比如回車換行符,也很簡單,只要找到回車換行對應的ASCII碼的十六進制數就好了,要是筆者沒記錯的話,應該是:0x0D、0x0A,那么我們可以在串口接收到0x0D、0x0A兩個資料之后認為是接收完成,當然,代碼部分筆者就不再貼出來了,留給讀者去完成了,
串口中斷接收完資料后,會產生一個Uart1_RX_Finish接收完成標志,然后就可以開始決議資料了:
/********************* APP運行 ***********************/
void app_run(void)
{
static u8 cnt = 0;
if(Uart1_RX_Finish)
{
if(!Uart1_TX_EN)
{
//根據收到的指令執行相應的動作
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x00))
{
//接收到指令(十六進制):5A A5 03 06 00 00,Y00輸出ON
Y00 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x10))
{
//接收到指令(十六進制):5A A5 03 06 00 01,Y00輸出OFF
Y00 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x01))
{
//接收到指令(十六進制):5A A5 03 06 00 01,Y01輸出ON
Y01 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x11))
{
//接收到指令(十六進制):5A A5 03 06 00 11,Y01輸出OFF
Y01 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x02))
{
//接收到指令(十六進制):5A A5 03 06 00 02,Y02輸出ON
Y02 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x12))
{
//接收到指令(十六進制):5A A5 03 06 00 12,Y02輸出OFF
Y02 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x03))
{
//接收到指令(十六進制):5A A5 03 06 00 03,Y03輸出ON
Y03 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x13))
{
//接收到指令(十六進制):5A A5 03 06 00 13,Y03輸出OFF
Y03 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x04))
{
//接收到指令(十六進制):5A A5 03 06 00 04,Y04輸出ON
Y04 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x14))
{
//接收到指令(十六進制):5A A5 03 06 00 14,Y04輸出OFF
Y04 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x05))
{
//接收到指令(十六進制):5A A5 03 06 00 05,Y05輸出ON
Y05 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x15))
{
//接收到指令(十六進制):5A A5 03 06 00 15,Y05輸出OFF
Y05 = OutputT_OFF;
}
TX1_Buffer[0] = 0x5A; //幀頭
TX1_Buffer[1] = 0xA5; //幀頭
TX1_Buffer[2] = 0x10; //長度:從長度后一位開始到最后一個資料總位元組數
TX1_Buffer[3] = 0x06; //命令0x06
TX1_Buffer[4] = !X00; //X00輸入狀態
TX1_Buffer[5] = !X01; //X01輸入狀態
TX1_Buffer[6] = !X02; //X02輸入狀態
TX1_Buffer[7] = !X03; //X03輸入狀態
TX1_Buffer[8] = !X04; //X04輸入狀態
TX1_Buffer[9] = !X05; //X05輸入狀態
TX1_Buffer[10] = !X06; //X06輸入狀態
TX1_Buffer[11] = !X07; //X07輸入狀態
TX1_Buffer[12] = Y00; //Y00輸出狀態
TX1_Buffer[13] = Y01; //Y01輸出狀態
TX1_Buffer[14] = Y02; //Y02輸出狀態
TX1_Buffer[15] = Y03; //Y03輸出狀態
TX1_Buffer[16] = Y04; //Y04輸出狀態
TX1_Buffer[17] = Y05; //Y05輸出狀態
TX1_Buffer[18] = 0x00; //和校驗:從長度位開始(包括長度位)到校驗前一位數累加和取低8位
for(cnt=2;cnt<18;cnt++)
{
TX1_Buffer[18] += TX1_Buffer[cnt];
}
COM1.TX_write = 0;
Uart1_TX_EN = 1;//uart1發送資料使能置“1”,準備開始發送資料
}
else
{
if(COM1.TX_write<19)
{
if(COM1.B_TX_busy == 0)
{
COM1.B_TX_busy = 1;
SBUF = TX1_Buffer[COM1.TX_write++]; //發送接收到的字符
}
}
else
{
COM1.RX_Cnt = 0;
Uart1_RX_Finish = 0;
Uart1_TX_EN = 0;//資料發送完成,uart1發送資料使能清“0”
memset(RX1_Buffer,NULL,sizeof(RX1_Buffer));//清空RX1_Buffer陣列
}
}
}
}
這部分代碼主要有兩個功能,第一部分主要就是對串口發來的資料的決議,根據不同的指令,執行相關動作:
//根據收到的指令執行相應的動作
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x00))
{
//接收到指令(十六進制):5A A5 03 06 00 00,Y00輸出ON
Y00 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x10))
{
//接收到指令(十六進制):5A A5 03 06 00 01,Y00輸出OFF
Y00 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x01))
{
//接收到指令(十六進制):5A A5 03 06 00 01,Y01輸出ON
Y01 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x11))
{
//接收到指令(十六進制):5A A5 03 06 00 11,Y01輸出OFF
Y01 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x02))
{
//接收到指令(十六進制):5A A5 03 06 00 02,Y02輸出ON
Y02 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x12))
{
//接收到指令(十六進制):5A A5 03 06 00 12,Y02輸出OFF
Y02 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x03))
{
//接收到指令(十六進制):5A A5 03 06 00 03,Y03輸出ON
Y03 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x13))
{
//接收到指令(十六進制):5A A5 03 06 00 13,Y03輸出OFF
Y03 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x04))
{
//接收到指令(十六進制):5A A5 03 06 00 04,Y04輸出ON
Y04 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x14))
{
//接收到指令(十六進制):5A A5 03 06 00 14,Y04輸出OFF
Y04 = OutputT_OFF;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x05))
{
//接收到指令(十六進制):5A A5 03 06 00 05,Y05輸出ON
Y05 = OutputT_ON;
}
if((RX1_Buffer[0] == 0x03)&&(RX1_Buffer[1] == 0x06)&&(RX1_Buffer[2] == 0x00)&&(RX1_Buffer[3] == 0x15))
{
//接收到指令(十六進制):5A A5 03 06 00 15,Y05輸出OFF
Y05 = OutputT_OFF;
}
處理完相關指令后,變重新裝載要發送的資料(控制板回傳給串口助手的資料),回傳的資料幀格式為:幀頭(2位元組)+ 長度(1位元組)+ 命令(1位元組)+ X00狀態(1位元組)+ X01狀態(1位元組)+ X02狀態(1位元組)+ X03狀態(1位元組)+ X04狀態(1位元組)+ X05狀態(1位元組)+ X06狀態(1位元組)+ X07狀態(1位元組)+ Y0狀態(1位元組)+ Y1狀態(1位元組)+ Y2狀態(1位元組)+ Y3狀態(1位元組)+ Y4狀態(1位元組)+ Y5狀態(1位元組),代碼如下圖:
TX1_Buffer[0] = 0x5A; //幀頭
TX1_Buffer[1] = 0xA5; //幀頭
TX1_Buffer[2] = 0x10; //長度:從長度后一位開始到最后一個資料總位元組數
TX1_Buffer[3] = 0x06; //命令0x06
TX1_Buffer[4] = !X00; //X00輸入狀態
TX1_Buffer[5] = !X01; //X01輸入狀態
TX1_Buffer[6] = !X02; //X02輸入狀態
TX1_Buffer[7] = !X03; //X03輸入狀態
TX1_Buffer[8] = !X04; //X04輸入狀態
TX1_Buffer[9] = !X05; //X05輸入狀態
TX1_Buffer[10] = !X06; //X06輸入狀態
TX1_Buffer[11] = !X07; //X07輸入狀態
TX1_Buffer[12] = Y00; //Y00輸出狀態
TX1_Buffer[13] = Y01; //Y01輸出狀態
TX1_Buffer[14] = Y02; //Y02輸出狀態
TX1_Buffer[15] = Y03; //Y03輸出狀態
TX1_Buffer[16] = Y04; //Y04輸出狀態
TX1_Buffer[17] = Y05; //Y05輸出狀態
TX1_Buffer[18] = 0x00; //和校驗:從長度位開始(包括長度位)到校驗前一位數累加和取低8位
for(cnt=2;cnt<18;cnt++)
{
TX1_Buffer[18] += TX1_Buffer[cnt];
}
當然,這里筆者寫的有點繁瑣,其實8個輸入狀態完全可以用一個位元組來實作,6個輸出狀態也可以完全用一個位元組來實作,這里筆者只是為了讓大家更好的理解這個協議,所以寫的繁瑣了一點,資料裝載完成,就可以將資料一個個發送出去了,這里便是第二部分的功能:
if(COM1.TX_write<19)
{
if(COM1.B_TX_busy == 0)
{
COM1.B_TX_busy = 1;
SBUF = TX1_Buffer[COM1.TX_write++]; //發送接收到的字符
}
}
else
{
COM1.RX_Cnt = 0;
Uart1_RX_Finish = 0;
Uart1_TX_EN = 0;//資料發送完成,uart1發送資料使能清“0”
memset(RX1_Buffer,NULL,sizeof(RX1_Buffer));//清空RX1_Buffer陣列
}
發送完成之后,將接收陣列中的資料清零:
memset(RX1_Buffer,NULL,sizeof(RX1_Buffer));//清空RX1_Buffer陣列
接下來,我們只需要將程式下載到控制板中,再用串口助手來發送對應的指令,就能看到效果了,好了,有關自定義協議的知識就簡單介紹到這里了,有疑問的小伙伴們可以給筆者留言或者直接參與評論,下一節筆者將給大家介紹“單片機EEPROM的應用”,詳見“SYK-0806-A2S1 工業自動化控制之【17-EEPROM實作資料掉電保存】”,感謝大家的支持!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/399656.html
標籤:其他
上一篇:RFID射頻標簽
下一篇:ESP32之 ESP-IDF + Clion 開發環境搭建(三)—— 使用 Clion 對 ESP32 進行 JTAG 除錯
