MODBUS MASTER RTU在STM32上的實現
1.概述
-
最近需要將幾個信號采集模塊通過總線串聯起來,這樣便於系統模塊化。故將目光關注到了工業上經常使用的modbus協議。
-
modbus協議是一種一主多從的拓撲結構,主要是應用層軟件協議,有關modbus的相關信息,可以自行google、百度。
-
STM32實現的Master工程代碼在github上,點擊獲取。
2.開發環境
- STM32F042單片機
- MDK-KEIL5
- STM32-CUBE庫
- Modbus slaver測試軟件
3.移植來源
-
信號采集模塊作為slaver,采用的是開源的freemodbus協議。關於其的實現大家百度一下都能發現,相關的移植過程介紹也很多,不再一一贅述。值得注意的是:這個freemodbus的源碼值得一看,其判斷對一幀數據包的接受采用的是定時器判斷超時。大體思路是中斷接收函數在接收每一個字節數據時會重置定時器,如果定時器發生定時溢出中斷,則說明沒有新數據到來,代表一個數據包接收完整。
-
然后就是master的設計實現。freemodbus並沒有開源的master實現代碼,故這部分需要我們自己開發完成。在github上發現有人發布了ardunio版本的master,但是ardunio的代碼采C++語言編寫,需要我們做一些C語言的移植和一些硬件底層接口的移植。
ardunio master的github源工程鏈接點擊,感謝他的分享。
4.移植過程
-
了解ardunio modbus庫的實現思路——很簡單明了。打開源工程,里面有源代碼和例程代碼,不過例程代碼需要用ardunio的IDE打開。其大體思路就是每個Modbus Function都用一個函數實現,如
uint8_t ModbusMaster::readDiscreteInputs(uint16_t u16ReadAddress,uint16_t u16BitQty) { _u16ReadAddress = u16ReadAddress; _u16ReadQty = u16BitQty; return ModbusMasterTransaction(ku8MBReadDiscreteInputs); }
這個ModbusMasterTransaction函數就是根據用戶選擇的功能模塊填充數組並且發送,然后等待從機回應的數據(帶超時檢測),接着解析接收到的數據包,如果成功則將數據放在_u16ResponseBuffer數組中
-
將ardunio的C++代碼移植為C語言
-
將ardunio相關的serial等函數使用自己的代碼實現,serial函數其實就是硬件層接口的函數封裝,這也是移植到其他平台必須要根據自身平台做相應的改變。
(1). 在ModbusMasterTransaction函數中涉及到_serial->read()、_serial->write()、_serial->flush()、_serial->available()、millis()、bitWrite()、bitRead()、word()幾種函數,從名字中我們就可以知道什么意思,故我們需要在我們的系統中重新實現這幾個函數。
(2). 我們底層串口的設計思路如下,數據發送采用數據的發送直接采用循環發送,而STM32cube庫已經將這個功能封裝好API接口,我們再封裝一層即可,如下所示/** * @brief 將數據包發送出去 * @param * @note * @retval void * @author xiaodaqi */ uint8_t Modbus_Master_Write(uint8_t *buf,uint8_t length) { if(HAL_UART_Transmit(&huart2 ,(uint8_t *)buf,length,0xff)) { return HAL_ERROR; } else { return HAL_OK; } }
這句函數就相當於_serial->write()的實現。
(3). 串口數據的接收:我們采用中斷接收的方式,並且在中斷處理函數中將接收到的字節采用循環隊壓人緩沖區,這樣子就能實現ardunio的功能代碼。
> 跟底層相關的移植代碼可到我的工程 Modbus_Master--trans_recieve_buff_control.c .h文件查看。
_serial->read()的變體為:
/**
* @brief 讀出緩沖區的數據
* @param
* @note
* @retval void
* @author xiaodaqi
*/
uint8_t Modbus_Master_Read(void)
{
uint8_t cur =0xff;
if( !rbIsEmpty(&m_Modbus_Master_RX_RingBuff))
{
cur = rbPop(&m_Modbus_Master_RX_RingBuff);
}
return cur;
}
_serial->flush()的變體為:
/**
* @brief 清除環形隊列
* @param
* @note
* @retval void
* @author xiaodaqi
*/
uint8_t Modbus_Master_Rece_Flush(void)
{
rbClear(&m_Modbus_Master_RX_RingBuff);
}
_serial->available()的變體為:
/**
* @brief 判斷ringbuffer里面是否有尚未處理的字節
* @param
* @note
* @retval void
* @author xiaodaqi
*/
uint8_t Modbus_Master_Rece_Available(void)
{
/*如果數據包buffer里面溢出了,則清零,重新計數*/
if(m_Modbus_Master_RX_RingBuff.flagOverflow==1)
{
rbClear(&m_Modbus_Master_RX_RingBuff);
}
return !rbIsEmpty(&m_Modbus_Master_RX_RingBuff);
}
millis()的變體為:
/**
* @brief 1ms周期的定時器
* @param
* @note
* @retval void
* @author xiaodaqi
*/
uint32_t Modbus_Master_Millis(void)
{
return HAL_GetTick();
}
其余幾個函數的變體如下:
/*模擬ardunio函數*************************************************/
static inline uint8_t lowByte(uint16_t ww)
{
return (uint8_t) ((ww) & 0x00FF);
}
static inline uint8_t highByte(uint16_t ww)
{
return (uint8_t) ((ww) >> 8);
}
static inline uint16_t word(uint8_t H_Byte,uint8_t L_Byte)
{
uint16_t word;
word = (uint16_t)(H_Byte<<8);
word = word + L_Byte;
return word;
}
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
(4).剩下的就是串口的一些硬件初始化,如波特率等,這些配置由於我是用CUBEMX配置直接生成的代碼,這里不做過多闡述。
5.代碼調試
完成上述關鍵代碼的移植后就可以進行調試了。
-
在main函數中增加如下的測試代碼
while (1) { uint8_t result; //測試read input registers功能 //從機地址0x01 ,連續都2個地址為0x2的寄存器 result = ModbusMaster_readInputRegisters(0x01,0x2, 2); if (result == 0x00) { Input_Result[0] = ModbusMaster_getResponseBuffer(0x00); Input_Result[1] = ModbusMaster_getResponseBuffer(0x01); } HAL_Delay(1000); }
-
我們使用Modbus Slave軟件來模擬從機。並且設置相應的地址里面的數值為1和2。軟件使用方法可自行百度、google。下圖為軟件設置及運行結果。
-
然后我們用keil仿真查看結果Input_Result數組的結果。如下圖所示,結果顯示modbus通信正確。
6.總結
以上即為本次移植的過程,有需要的朋友可以直接使用我的代碼,亦可使用ardunio代碼自行移植。