最近一個項目用到了MODBUS協議,就學習了一下,這里做一下記錄以免后續忘記。
要用到MODBUS肯定要先知道是MOBUS協議,這里呢我們就又要先理解協議的含義了。
所謂的協議是什么?就是互相之間的約定嘛,如果不讓別人知道那就是暗號。現在就來定義一個新的最簡單協議。
例如: 協議: “A”--“LED滅” “B”--“報警” “C”--“LED亮” 。
單片機接收到“A”控制一個LED滅,單片機接收到“B”控制報警,單片機接收到“A”控制一個LED亮。那么當收到對應的信息就執行相應的動作,這就是協議。
MODBUS 一包數據主要組成有 : 設備地址 功能碼 數據長度 數據 CRC
先來簡單分析一條MODBUS-RTU報文,例如:
這個包數據的意思是往設備地址為 0x01 的設備中,執行 0x1f 代號的功能 ,這里假設 0x1f 代號功能碼代表的是保存數據再Flash,那這包數據的意思就是,控制 0x01 設備,執行 0x1f 代號功能保存后面4位數據到 MCU 的 Flash 中,其中數據長度是指后面數據字節數的大小,最后兩個 CRC 校驗是除了最后兩位 CRC 前面所有數據經過CRC運算出來的校驗碼,用來保證數據的准確性。
協議大概就是這么些內容,因為只是簡單的應用,就沒有深入去研究了,知道這些運用到STM32上已經夠了,下面的是CRC校驗的代碼(重點):
/*** 函數名:uint16_t Crc(uint8_t Rxbuff[],uint8_t Rx_len) 說 明:Modbus協議CRC校驗 傳入值:Rxbuff[] 串口接收的數據,len串口接收的數據長度 傳出值:返回兩個字節的CRC校驗碼,高位在前,低位在后 **/ uint16_t Crc(uint8_t Rxbuff[],uint8_t Rx_len) { uint8_t len = Rx_len - 2; uint16_t crc_result = 0xffff; int crc_num =0; int xor_flag=0; for(int i=0;i<len;i++) { crc_result ^= Rxbuff[i]; crc_num = (crc_result&0x0001); for(int m=0;m<8;m++) { if(crc_num==1) xor_flag = 1; else xor_flag = 0; crc_result >>= 1; if(xor_flag) crc_result ^= 0xa001; crc_num = (crc_result & 0x0001); } } return crc_result; }
下面的是MODBUS運用的舉例代碼,這里我對MODBUS協議做了些修改,在 “功能碼 ” 跟 “數據長度 ” 間多加了個 “讀寫標志位”,實際項目時可以根據自己項目需求做一下修改也無可厚非:
/*** 函數名:void modbus(uint8_t Rxbuff[],uint8_t len) 說 明:Modbus協議處理 傳入值:Rxbuff[] 串口接收的數據,len串口接收的數據長度 傳出值:無 **/ void modbus(uint8_t Rxbuff[],uint8_t len) { uint8_t Read_Robot [1]={0}; uint8_t Robot_add=0x01; uint8_t Robot = Get_Robot_Num(); //獲取預先保存好的設備地址 uint16_t crc=Crc(Rxbuff,len); uint16_t Rx_crc = Rxbuff[len-2]<<8 | Rxbuff[len-1]; if((Robot==Rxbuff[0] ||Rxbuff[0] ==0xff) && Rx_crc==crc) //判斷設備地址是否正確,預留在不知道地址的時候,使用虛擬地址0xff,防止忘記設備地址后,用虛擬地址可以修改 { switch(Rxbuff[1]) { case 0x01: //操作ID if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,ID_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,ID_Flash_Add); break; case 0x02: //操作名稱 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Name_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Name_Flash_Add); break; case 0x03: //操作環節 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Link_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Link_Flash_Add); break; case 0x04: //操作型號 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Model_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Model_Flash_Add); break; case 0x05: //操作品牌 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Brand_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Brand_Flash_Add); break; case 0x06: //操作行數 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Linage_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Linage_Flash_Add); break; case 0x07: //操作幅寬 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Wite_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Wite_Flash_Add); break; case 0x08: //操作高度 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Hight_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Hight_Flash_Add); break; case 0x09: //操作馬力 if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Soup_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Soup_Flash_Add); break; case 0x0a: //操作合作社ID if(Rxbuff[2]==0x00) //讀 Read(MCU_Rx_buff,Artel_Flash_Add); else if(Rxbuff[2]==0x01) //寫 Write(Rxbuff,len,Artel_Flash_Add); break; case 0x21: //修改波特率 if(Rxbuff[2]==0x00) //讀 { Get_Boud_num(); } else if(Rxbuff[2]==0x01) //寫 { Boud_Set(Rxbuff); HAL_UART_Transmit(&huart1,Rxbuff,len,0x1000); } break; case 0x22: //操作設備地址 if(Rxbuff[2]==0x00) //讀 { Read_Robot[0] = Get_Robot_Num(); HAL_UART_Transmit(&huart1,Read_Robot,1,0x1000); } else if(Rxbuff[2]==0x01) //寫 { Robot_add=Rxbuff[5]; Robot_Num_Init(Robot_add); HAL_UART_Transmit(&huart1,Rxbuff,len,0x1000); } break; } } }
最后直接在串口接收處理函數里調用MODBUS函數即可,代碼如下:
/*** 函數名:void Usart_RX(void) 說 明:接收數據處理 傳入值:無 傳出值:無 **/ void Usart_RX(void) { if(Rx_End_flag==1) { modbus(Rx_buffer,Rx_len); //調用MODBUS協議處理函數 memset(Rx_buffer,0,sizeof(Rx_buffer)); //清空數組 Rx_len=0; Rx_End_flag=0;//清除接收結束標志位 } HAL_UART_Receive_DMA(&huart1,Rx_buffer,BUFFER_SIZE); }
只要經過這些步驟,就可以在STM32上簡單運用 MODBUS 協議了,其實 MODBUS 協議說難不難,它就跟我們平時自己定義的串口通信協議類似,有數據頭、數據內容、數據尾,只不過更加科學規范罷了。
參考鏈接:https://wenku.baidu.com/view/9cdb001533687e21af45a9e2.html