陳文禮+饒毅+高翔
摘 ?要: 為了解決工業(yè)測控系統(tǒng)儀表數(shù)量多,通信協(xié)議不兼容,組網(wǎng)困難的問題,在此給出一種Modbus從站設計方法。從站系統(tǒng)平臺基于STM32F103處理器和μC/OS?Ⅱ嵌入式實時操作系統(tǒng),通過移植μC/Modbus協(xié)議棧實現(xiàn)Modbus RTU從站通信功能。在此詳細分析了μC/Modbus的架構和工作流程,給出了移植及使用方法,能夠幫助讀者快速開發(fā)出符合標準的Modbus從站設備。
關鍵詞: μC/Modbus; μC/OS?Ⅱ; STM32F103; 從站
中圖分類號: TN919?34; TP368.1 ? ? ? ? ? ? ? 文獻標識碼: A ? ? ? ? ? ? ? ? ? ?文章編號: 1004?373X(2014)24?0090?04
Transplantation of μC/Modbus on STM32 and slave device design
CHEN Wen?li, RAO Yi, GAO Xiang
(Tangshan Kaicheng Electric Control Equipment Group Co., Ltd., Tangshan 063020, China)
Abstract: In order to reduce instrumentation quantity, realize communications protocol compatibility and solve networking difficulty of industrial measurement and control system, a design method of Modbus slave is presented in this paper. The slave device platform is based on STM32F103 processor and μC/OS?II real?time embedded operating system. Modbus RTU slave communication function is realized by porting μC/Modbus protocol stack. The μC/Modbus architecture and workflow are analyzed in this paper. The transplantation and use methods are given to help readers to develop a qualified Modbus slave device quickly.
Keywords: μC/Modbus; μC/OS?II; STM32F103; slave device
隨著計算機技術的飛速發(fā)展,現(xiàn)代工業(yè)測控系統(tǒng)變得越來越復雜,儀表種類繁多且來自不同廠家,因此通信協(xié)議的標準化、統(tǒng)一化對整個系統(tǒng)的聯(lián)網(wǎng)顯得尤為重要。Modbus 協(xié)議是電子儀器儀表的一種通用語言,協(xié)議定義了電子設備能識別和使用的信息結構,廣泛用于工業(yè)通信領域,其優(yōu)點是實時性好,可靠性高,適用于小到中等規(guī)模的數(shù)據(jù)傳輸。
自1979年由莫迪康公司發(fā)明以來,逐漸成為工業(yè)領域最流行的協(xié)議。盡管目前各種工業(yè)通信協(xié)議不斷涌現(xiàn),但Modbus憑借其簡單而精致的結構以及開放性和無會員限制的特點仍得到越來越多的支持。國內企業(yè)對Modbus 則更為青睞。幾乎所有工業(yè)設備、智能儀表等都在使用它作為通信標準,HMI和組態(tài)軟件也都把它作為所支持的通信協(xié)議之一。
μC/Modbus是一個針對嵌入式應用的通用Modbus 協(xié)議棧,具有結構清晰簡單,響應速度快,占用資源少,單任務多通道等特點。將μC/Modbus移植到 ARM Cortex?M3 處理器上就可以構架出高性價比的嵌入式產品,對于減少開發(fā)周期和提高產品的質量等方面有著重要的意義。
1 ?μC/Modbus的架構及工作流程
μC/Modbus源代碼包括有4個文件夾:Source、OS、Ports、Product。其中Source文件夾下的程序是相對獨立的、與硬件平臺和操作系統(tǒng)無關的部分,也就是說這部分在使用時不需要做任何改動。包含文件:MB.C、MB.H,MB_DEF.H,MB_UTIL.C,MBS_CORE.C 。
以下3個文件文件的程序是需要用戶根據(jù)使用的軟硬件平臺和應用需求做相應移植和修改的:
OS???μC/Modbus與操作系統(tǒng)的接口程序。包含文件:MB_OS.C,MB_OS.H。
Ports???μC/Modbus與CPU的接口程序。包含文件:CPU.H,MB_BSP.C。
Product???μC/Modbus的配置文件和用戶應用程序。包含文件:MB_CFG.H,MB_DATA.C。各模塊之間的關系如圖1所示。
μC/Modbus協(xié)議棧要求處理器具有一個或多個串行接口、能夠容納Modbus數(shù)據(jù)幀的RAM空間和一個定時器。對操作系統(tǒng)的要求是能夠建立一個接收任務和一個消息事件隊列。μC/Modbus采用一個任務處理所有通道接收的數(shù)據(jù)幀,空閑時任務只是等待消息隊列上的事件。當任何通道接收到數(shù)據(jù)幀時,一個指向收到數(shù)據(jù)幀通道的指針被發(fā)布到消息隊列上,任務收到消息后解析該數(shù)據(jù)包,并做相應的回復響應。
μC/Modbus為每個通道分配一個MODBUS_CH類型的數(shù)據(jù)結構,MODBUS_CH數(shù)據(jù)結構里包括通道號,工作模式,節(jié)點地址,通信參數(shù),超時計數(shù),發(fā)送緩存,接收緩存等。串口接收中斷服務程序會把收到的字節(jié)放置在接收緩存中,直到收到結束標志。如果該通道被配置用于Modbus ASCII模式,包的末端為一個換行字符(即0x0A)。如果該通道被配置為Modbus RTU模式,一個數(shù)據(jù)幀發(fā)送結束后至少要有3.5個字節(jié)的空閑時間(參見Modbus RTU通訊規(guī)范)。本設計采用Modbus RTU模式,需要CPU提供一個定時器,以保證數(shù)據(jù)幀接收結束超時機制。如圖2所示,μC/Modbus為每個通道分配的MODBUS_CH數(shù)據(jù)結構里都包含一個超時計數(shù)器TimeoutCtr。串口初始化時,μC/Modbus會根據(jù)所設置的波特率計算出一個超時計數(shù)值。每次收到數(shù)據(jù),該通道的RTU計數(shù)器都會重置為超時計數(shù)值。例如一個通道配置為9 600 b/s,那么3.5個字符的傳輸時間為3 646 μs,假如CPU定時周期為1 ms,那么超時計數(shù)值為4,為了提高容錯性μC/Modbus會在理論值上多一個定時周期,也就是此例最終的超時計數(shù)值等于5。串口每次接收數(shù)據(jù)都會對計數(shù)器重置,在定時中斷里,計數(shù)值遞減。當計數(shù)值遞減到0時,就會發(fā)送一個指向該超時通道的消息指針。μC/Modbus RTU模式下代碼工作流程如下:
MB_CommRxTxISR_Handler() ??? MB_BSP.C
串口收到數(shù)據(jù),并觸發(fā)接收中斷,中斷服務函數(shù)MB_CommRxTxISR_Handler調用MB_RxByte()函數(shù)進行處理:
MB_RxByte() ??? MB.C
根據(jù)通道的工作模式,將接收的數(shù)據(jù)傳遞給ASCII或RTU 處理程序。如果是RTU調用 MB_RTU_RxByte()。
MB_RTU_RxByte() ??? MB.C
串口接收到的數(shù)據(jù)存放到接收緩存RXBUF[]里。由于RTU幀結束是按時間定界的,MB_RTU_RxByte()重置通道RTU定時計數(shù)器??,表示接收幀還沒有結束。一個完整幀的接收完成信號是通過前面所述的RTU計時超時產生的(參見 MB_RTU_TmrUpdate()???MB.C)。
MB_OS_RxTask() ??? MB_OS.C
μC/Modbus所有通信都是由MB_OS_RxTask()接收任務來處理。任務等待來自RTU定時器的超時消息,表明一個數(shù)據(jù)幀已接收結束。該消息實際上是一個指向Modbus通道的指針。然后調用MB_RxTask()(MB.C)判斷是Master還是Slave工作模式,如果是Slave模式則調用 MBS_RxTask()(MBS_CORE.C)。 MBS_RxTask()根據(jù)ASCII或RTU模式調用MBS_ASCII_Task()(MBS_CORE.C)或MBS_RTU_Task()(MBS_CORE.C)做該消息的實際處理:
MBS_RTU_Task() – MBS_CORE.C
MBS_RTU_Task()首先判斷接收的幀是否完整,然后調用MB_RTU_RxCalcCRC驗證數(shù)據(jù)是否正確。如果是一個有效的幀,則調用MBS_FCxx_Handler()來解析:
MBS_FCxx_Handler() ??? MBS_CORE.C
該函數(shù)根據(jù)收到的功能碼,調用相應的功能碼處理函數(shù)。并生產回復數(shù)據(jù)幀存放在TxFrameData[]數(shù)組里。
MB_RTU_Tx() ??? MB.C
如果從站需要回復一個數(shù)據(jù)幀給Modbus主站,這個函數(shù)就會被調用。 MB_RTU_Tx()將TxFrameData[]里的數(shù)據(jù)復制到發(fā)送緩存TXBUF[]中。 并是通過調用MB_RTU_TxCalcCRC()計算CRC校驗。然后調用MB_Tx()來啟動傳輸。
MB_Tx()?MB.C
這個函數(shù)用來將回復的數(shù)據(jù)幀發(fā)送到Modbus主站。將指針TxBufPtr指向TXBUF[]并使能發(fā)送完成中斷。通過調用MB_TxByte()來發(fā)送第一個字節(jié),在發(fā)送完成中斷里TxBufPtr遞增以發(fā)送下一個數(shù)據(jù),直到全部發(fā)送完成:
MB_TxByte()?MB.C
首先檢查剩余待發(fā)送的字節(jié)數(shù)TxBufByteCtr,如果TxBufByteCtr等于0則表明消息幀全部發(fā)送完成,則關閉發(fā)送中斷。如果TxBufByteCtr大于0,則調用MB_BSP.C文件里的串口發(fā)送函數(shù)MB_CommTx1(),將TxBufPtr指向的字節(jié)數(shù)據(jù)發(fā)送到串口。TxBufPtr加1以指向下一個數(shù)據(jù),TxBufByteCtr減1以記憶剩余待發(fā)送的字節(jié)數(shù)。
2 ?μC/OS?Ⅱ操作系統(tǒng)接口部分移植
為了建立Modbus接收任務及實現(xiàn)消息機制, μC/Modbus需要在MB_OS.C文件里實現(xiàn)3個函數(shù)和一個接收任務。
MB_OS_Init()
MB_OS_RxSignal()
MB_OS_Exit()
MB_OS_RxTask()
首先用戶需要定義消息事件變量、任務優(yōu)先級及任務堆棧大小,其中消息單元數(shù)等于Modbus通道數(shù)[1]。
static ?OS_EVENT ? ? ? ? ? ?*MB_OS_RxQ;
static ?void ? ? *MB_OS_RxQTbl[MODBUS_CFG_MAX_CH];
#define ?MB_OS_CFG_RX_TASK_PRIO ? ? ? ? ? ? ? ?3
#define MB_OS_CFG_RX_TASK_ID ? MB_OS_CFG_RX_
TASK_PRIO
#define ?MB_OS_CFG_RX_TASK_STK_SIZE ? ? ? ? ? ?256
MB_OS_Init ()
用于初始化μC/OS?II操作系統(tǒng)接口,創(chuàng)建一個消息隊列和一個等待要接收的數(shù)據(jù)包的任務。主要代碼如下[2]:
MB_OS_RxQ=OSQCreate(&MB_OS_RxQTbl[0], ?MODBUS_CFG_MAX_CH);
(void)OSTaskCreateExt((void (*)(void *)) MB_OS_RxTask,
(void ? ? ? ? ? *) 0,
(OS_STK *)&MB_OS_RxTaskStk[MB_OS_CFG_RX_TASK_STK_SIZE ? 1],
(INT8U ? ? ? ? ? ) MB_OS_CFG_RX_TASK_PRIO,
(INT16U ? ? ? ? ?) MB_OS_CFG_RX_TASK_ID,
(OS_STK ? ? ? ? *)&MB_OS_RxTaskStk[0],
(INT32U) MB_OS_CFG_RX_TASK_STK_SIZE,
(void ? ? ? ? ? *) 0,
(INT16U)(OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR));
MB_OS_RxSignal()
一個數(shù)據(jù)包接收結束時MB_OS_RxSignal()就會被調用。其主要代碼就是向消息隊列發(fā)布消息OSQPost(MB_OS_RxQ, (void *)pch);其中參數(shù)pch為指向接收到數(shù)據(jù)包的Modbus通道的數(shù)據(jù)結構。
MB_OS_Exit()
此函數(shù)在Modbus任務終止時被調用,用于刪除任務和 消息隊列。
OSTaskDel(MB_OS_CFG_RX_TASK_PRIO);
OSQDel(MB_OS_RxQ, OS_DEL_ALWAYS, ?&err);
MB_OS_RxTask()
這個任務是由MB_OS_Init()創(chuàng)建, 如前面所述該任務只是簡單等待消息,并調用MB_RxTask()主要代碼如下:
while (DEF_TRUE) {
pch=(MODBUS_CH*)OSQPend(MB_OS_RxQ,0,&err); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?MB_RxTask(pch); }
μC/Modbus已經提供了基于μC/OS?Ⅱ操作系統(tǒng)的接口程序,如果用戶系統(tǒng)使用的是μC/OS?Ⅱ則不需要做任何改動。但μC/Modbus可以在任何實時操作系統(tǒng)下運行,只是需要用戶來完成接口程序。
3 ?STM32F103處理器接口部分移植
μC/Modbus源碼Ports文件夾下的程序是與硬件平臺關聯(lián)的。CPU.H用于定義數(shù)據(jù)類型、處理器堆棧數(shù)據(jù)字長、堆棧增長方向、任務切換宏和臨界區(qū)訪問處理方式。MB_BSP.C用于操作串口和RTU定時器[3]。
OS_CPU.H 頭文件移植到Cortex?M3平臺時,需要重新定義包含與編譯器無關的數(shù)據(jù)類型。代碼如下:
typedef ? ? ? ? ? ?void ? ? ?CPU_VOID;
typedef ?unsigned ?char ? ? ?CPU_CHAR;
typedef ?unsigned ?char ? ? ?CPU_BOOLEAN;
typedef ?unsigned ?char ? ? ?CPU_INT08U;
typedef ? ?signed ?char ? ? ?CPU_INT08S;
typedef ?unsigned ?short ? ? CPU_INT16U;
typedef ? ?signed ?short ? ? CPU_INT16S;
typedef ?unsigned ?int ? ? ? CPU_INT32U;
typedef ? ?signed ?int ? ? ? CPU_INT32S;
typedef ? ? ? ? ? ?float ? ? CPU_FP32;
typedef ? ? ? ? ? ?double ? ?CPU_FP64;
定義存儲模式,Cortex?M3 使用的是小端存儲模式。
#define ?CPU_CFG_ENDIAN_TYPE ? CPU_ENDIAN_TYPE_LITTLE
定義臨界區(qū)代碼訪問涉及到的全局中斷開關指令有OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。 在OS_CPU.H 頭文件中定義如下[4]:
typedef ?CPU_INT32U ?CPU_SR;
#define ?CPU_CRITICAL_ENTER() ? ?{cpu_sr = CPU_SR_Save();}
#define ?CPU_CRITICAL_EXIT() ? ? {CPU_SR_Restore(cpu_sr);}
其中CPU_SR_Save ()和CPU_SR_Restore () 分別對應關中斷和開中斷處理,需要使用匯編代碼實現(xiàn),代碼如下:
CPU_SR_Save
MRS ? ? R0, PRIMASK ? ? ? ? ? ? ? ? ?;保存PRIMASK
CPSID ? I ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ;關中斷
BX ? ? ?LR
CPU_SR_Restore
MSR ? ? PRIMASK, R0 ? ? ? ? ? ? ? ? ?;恢復PRIMASK
BX ? ? ?LR
MB_BSP.C中實現(xiàn)串口操作、串口初始化、串口配置、串口發(fā)送接收中斷服務程序和定時中斷服務程序,具體實現(xiàn)參考STM32固件庫。
MB_CommExit()???關閉使用的所有串行接口
MB_CommPortCfg()???配置串口通信參數(shù)
MB_CommRxTxISR_Handler()???串口接收、發(fā)送中斷服務程序,需要加入如下代碼。
if (Rx Interrupt) {
pch?>RxCtr++;
MB_RxByte(pch, c);
}
if (Tx Interrupt) {
pch?>TxCtr++;
MB_TxByte(pch);
}
MB_CommRxIntDis()???使能串口接收中斷。
MB_CommRxIntEn()???禁止串口接收中斷。
MB_CommTx1()???發(fā)送一個字節(jié)數(shù)據(jù)。
MB_CommTxIntDis()???禁止串口發(fā)送中斷。
MB_CommTxIntEn()???使能串口發(fā)送中斷。
MB_RTU_TmrInit()???初始化RTU定時器。
MB_RTU_TmrExit()???停用RTU定時器。
MB_RTU_TmrISR_Handler()???RTU 定時中斷服務程序。需要加入如下代碼。
MB_RTU_TmrCtr++;
MB_RTU_TmrUpdate() ;
4 ?μC/ Modbus的使用及應用程序設計
移植部分的代碼編寫完成后,就可以根據(jù)自己的應用需求配置μC/Modbus和設計應用程序。
在MB_CFG.H文件中配置參數(shù)。
定義設備為Modbus從站設備
#define ?MODBUS_CFG_SLAVE_EN ? ? ?DEF_ENABLED ? ? ? ?#define ?MODBUS_CFG_MASTER_EN ? ? DEF_DISABLED ? ? ? ? 設置從站支持Modbus RTU模式
#define ?MODBUS_CFG_ASCII_EN ? DEF_DISABLED ? ? ? ? ? ? ? ? ?#define ?MODBUS_CFG_RTU_EN ? ? DEF_ENABLED
設置通道數(shù)
#define ?MODBUS_CFG_MAX_CH ? ? 1
設置串口發(fā)送接收緩存大小
#define ?MODBUS_CFG_BUF_SIZE ? ? ? 255
設置禁用浮點數(shù)
#define ?MODBUS_CFG_FP_EN ? ? ? ? ? DEF_DISABLED
設置設備支持的命令碼
#define ?MODBUS_CFG_FC01_EN ? ? ? ? DEF_ENABLED
#define ?MODBUS_CFG_FC02_EN ? ? ? ? DEF_ENABLED
#define ?MODBUS_CFG_FC03_EN ? ? ? ? DEF_ENABLED
#define ?MODBUS_CFG_FC04_EN ? ? ? ? DEF_ENABLED
#define ?MODBUS_CFG_FC05_EN ? ? ? ? DEF_ENABLED
#define ?MODBUS_CFG_FC06_EN ? ? ? ? DEF_ENABLED
#define ?MODBUS_CFG_FC15_EN ? ? ? ? DEF_ENABLED
#define ?MODBUS_CFG_FC16_EN ? ? ? ? DEF_ENABLED
根據(jù)所支持的命令碼在MB_DATA.C文件中實現(xiàn)以下函數(shù):
MB_CoilRd()用于響應01功能碼, 讀一個線圈的值。
MB_CoilWr()用于響應05、15功能碼,寫一個線圈的值。
MB_DIRd()用于響應02功能碼,讀一個離散輸入的值。
MB_InRegRd()用于響應04功能碼。讀一個輸入寄存器的值。
MB_HoldingRegRd()用于響應03功能碼,讀取一個保持寄存器的值。
MB_HoldingRegWr()用于響應06、16功能碼,寫一個保持寄存器的值。
最后用戶就可以在main函數(shù)里或在用戶任務里啟動μC/ Modbus。首先調用MB_Init()初始化μC/Modbus,然后通過MB_CfgCh()配置Modbus通道。一旦通道配置完成,就可以與Modbus主站通信而不需要用戶參與。例如設從站設備具有一個Modbus RTU端口、9 600 b/s、從站地址為1。代碼如下[5]:
MB_Init(1000); ? //初始化 μC/Modbus,時鐘頻率 1 000 Hz
MB_CfgCh ( ? 1, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Modbus通道號
MODBUS_SLAVE, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 從設備
0,
MODBUS_MODE_RTU, ? ? ? ? ? ? ?//Modbus RTU模式
MODBUS_USARTPort, ? ? ? ? ? ? ? ? ? // UART串口號
9600, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//波特率
8, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //8個數(shù)據(jù)位
MODBUS_PARITY_NONE , ? ? ? ? ? ? ? ? ? ? //無校驗
1, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //1個停止位
MODBUS_WR_EN); ? ? ? ? ? ? ? ? ? ? ? ?//允許寫操作
5 ?結 ?語
本文主要論述了μC/Modbus在Cortex?M3內核處理器上的移植過程并給出關鍵代碼,移植后的μC/Modbus能夠穩(wěn)定運行于STM32F103處理器上。本移植能通用于所有Cortex?M3處理器和μC/OS?Ⅱ操作系統(tǒng)平臺上,對于將μC/Modbus移植到其他體系平臺上同樣具有參考作用。實驗表明μC/Modbus 是一款簡單高效移植方便的標準Modbus協(xié)議棧。讀者在了解μC/Modbus工作原理及移植過程之后,也可進一步做優(yōu)化操作,比如利用STM32的串口DMA功能取代中斷方式,以進一步降低CPU的開銷。
參考文獻
[1] LABROSSE J J. μC/OS?Ⅱ:源碼公開的實時嵌入式操作系統(tǒng)[M].紹貝貝,譯.北京:中國電力出版社,2001.
[2] YIU J. Cortex?M3權威指南[M].宋巖,譯.北京:清華大學出版社,2008.
[3] 張桂,金國強,李輝.基于ARM平臺Modbus RTU協(xié)議的研究與實現(xiàn)[J].電力科學與工程,2011(1):23?27.
[4] 王曉鳴.實時操作系統(tǒng)μC/OS?Ⅱ在ARM上的移植[J].機電一體化,2007(1):56?58.
[5] 周軍,陳偉峰.基于ARM9和μC/OS?Ⅱ的Modbus通信協(xié)議的實現(xiàn)[J].自動化儀表,2009(2):24?26.
[6] 潘迪夫,習可.以PLC為通信主站的Modbus控制網(wǎng)絡的設計與實現(xiàn)[J].現(xiàn)代電子技術,2010,33(5):142?144.