μC/Modbus在STM32上的移植及从站设计

2014-12-18 11:14陈文礼饶毅高翔
现代电子技术 2014年24期

陈文礼+饶毅+高翔

摘  要: 为了解决工业测控系统仪表数量多,通信协议不兼容,组网困难的问题,在此给出一种Modbus从站设计方法。从站系统平台基于STM32F103处理器和μC/OS?Ⅱ嵌入式实时操作系统,通过移植μC/Modbus协议栈实现Modbus RTU从站通信功能。在此详细分析了μC/Modbus的架构和工作流程,给出了移植及使用方法,能够帮助读者快速开发出符合标准的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

随着计算机技术的飞速发展,现代工业测控系统变得越来越复杂,仪表种类繁多且来自不同厂家,因此通信协议的标准化、统一化对整个系统的联网显得尤为重要。Modbus 协议是电子仪器仪表的一种通用语言,协议定义了电子设备能识别和使用的信息结构,广泛用于工业通信领域,其优点是实时性好,可靠性高,适用于小到中等规模的数据传输。

自1979年由莫迪康公司发明以来,逐渐成为工业领域最流行的协议。尽管目前各种工业通信协议不断涌现,但Modbus凭借其简单而精致的结构以及开放性和无会员限制的特点仍得到越来越多的支持。国内企业对Modbus 则更为青睐。几乎所有工业设备、智能仪表等都在使用它作为通信标准,HMI和组态软件也都把它作为所支持的通信协议之一。

μC/Modbus是一个针对嵌入式应用的通用Modbus 协议栈,具有结构清晰简单,响应速度快,占用资源少,单任务多通道等特点。将μC/Modbus移植到 ARM Cortex?M3 处理器上就可以构架出高性价比的嵌入式产品,对于减少开发周期和提高产品的质量等方面有着重要的意义。

1  μC/Modbus的架构及工作流程

μC/Modbus源代码包括有4个文件夹:Source、OS、Ports、Product。其中Source文件夹下的程序是相对独立的、与硬件平台和操作系统无关的部分,也就是说这部分在使用时不需要做任何改动。包含文件:MB.C、MB.H,MB_DEF.H,MB_UTIL.C,MBS_CORE.C 。

以下3个文件文件的程序是需要用户根据使用的软硬件平台和应用需求做相应移植和修改的:

OS???μC/Modbus与操作系统的接口程序。包含文件: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协议栈要求处理器具有一个或多个串行接口、能够容纳Modbus数据帧的RAM空间和一个定时器。对操作系统的要求是能够建立一个接收任务和一个消息事件队列。μC/Modbus采用一个任务处理所有通道接收的数据帧,空闲时任务只是等待消息队列上的事件。当任何通道接收到数据帧时,一个指向收到数据帧通道的指针被发布到消息队列上,任务收到消息后解析该数据包,并做相应的回复响应。

μC/Modbus为每个通道分配一个MODBUS_CH类型的数据结构,MODBUS_CH数据结构里包括通道号,工作模式,节点地址,通信参数,超时计数,发送缓存,接收缓存等。串口接收中断服务程序会把收到的字节放置在接收缓存中,直到收到结束标志。如果该通道被配置用于Modbus ASCII模式,包的末端为一个换行字符(即0x0A)。如果该通道被配置为Modbus RTU模式,一个数据帧发送结束后至少要有3.5个字节的空闲时间(参见Modbus RTU通讯规范)。本设计采用Modbus RTU模式,需要CPU提供一个定时器,以保证数据帧接收结束超时机制。如图2所示,μC/Modbus为每个通道分配的MODBUS_CH数据结构里都包含一个超时计数器TimeoutCtr。串口初始化时,μC/Modbus会根据所设置的波特率计算出一个超时计数值。每次收到数据,该通道的RTU计数器都会重置为超时计数值。例如一个通道配置为9 600 b/s,那么3.5个字符的传输时间为3 646 μs,假如CPU定时周期为1 ms,那么超时计数值为4,为了提高容错性μC/Modbus会在理论值上多一个定时周期,也就是此例最终的超时计数值等于5。串口每次接收数据都会对计数器重置,在定时中断里,计数值递减。当计数值递减到0时,就会发送一个指向该超时通道的消息指针。μC/Modbus RTU模式下代码工作流程如下:

MB_CommRxTxISR_Handler() ??? MB_BSP.C

串口收到数据,并触发接收中断,中断服务函数MB_CommRxTxISR_Handler调用MB_RxByte()函数进行处理:

MB_RxByte() ??? MB.C

根据通道的工作模式,将接收的数据传递给ASCII或RTU 处理程序。如果是RTU调用 MB_RTU_RxByte()。

MB_RTU_RxByte() ??? MB.C

串口接收到的数据存放到接收缓存RXBUF[]里。由于RTU帧结束是按时间定界的,MB_RTU_RxByte()重置通道RTU定时计数器??,表示接收帧还没有结束。一个完整帧的接收完成信号是通过前面所述的RTU计时超时产生的(参见 MB_RTU_TmrUpdate()???MB.C)。

MB_OS_RxTask() ??? MB_OS.C

μC/Modbus所有通信都是由MB_OS_RxTask()接收任务来处理。任务等待来自RTU定时器的超时消息,表明一个数据帧已接收结束。该消息实际上是一个指向Modbus通道的指针。然后调用MB_RxTask()(MB.C)判断是Master还是Slave工作模式,如果是Slave模式则调用 MBS_RxTask()(MBS_CORE.C)。 MBS_RxTask()根据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验证数据是否正确。如果是一个有效的帧,则调用MBS_FCxx_Handler()来解析:

MBS_FCxx_Handler() ??? MBS_CORE.C

该函数根据收到的功能码,调用相应的功能码处理函数。并生产回复数据帧存放在TxFrameData[]数组里。

MB_RTU_Tx() ??? MB.C

如果从站需要回复一个数据帧给Modbus主站,这个函数就会被调用。 MB_RTU_Tx()将TxFrameData[]里的数据复制到发送缓存TXBUF[]中。 并是通过调用MB_RTU_TxCalcCRC()计算CRC校验。然后调用MB_Tx()来启动传输。

MB_Tx()?MB.C

这个函数用来将回复的数据帧发送到Modbus主站。将指针TxBufPtr指向TXBUF[]并使能发送完成中断。通过调用MB_TxByte()来发送第一个字节,在发送完成中断里TxBufPtr递增以发送下一个数据,直到全部发送完成:

MB_TxByte()?MB.C

首先检查剩余待发送的字节数TxBufByteCtr,如果TxBufByteCtr等于0则表明消息帧全部发送完成,则关闭发送中断。如果TxBufByteCtr大于0,则调用MB_BSP.C文件里的串口发送函数MB_CommTx1(),将TxBufPtr指向的字节数据发送到串口。TxBufPtr加1以指向下一个数据,TxBufByteCtr减1以记忆剩余待发送的字节数。

2  μC/OS?Ⅱ操作系统接口部分移植

为了建立Modbus接收任务及实现消息机制, μC/Modbus需要在MB_OS.C文件里实现3个函数和一个接收任务。

MB_OS_Init()

MB_OS_RxSignal()

MB_OS_Exit()

MB_OS_RxTask()

首先用户需要定义消息事件变量、任务优先级及任务堆栈大小,其中消息单元数等于Modbus通道数[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操作系统接口,创建一个消息队列和一个等待要接收的数据包的任务。主要代码如下[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()

一个数据包接收结束时MB_OS_RxSignal()就会被调用。其主要代码就是向消息队列发布消息OSQPost(MB_OS_RxQ, (void *)pch);其中参数pch为指向接收到数据包的Modbus通道的数据结构。

MB_OS_Exit()

此函数在Modbus任务终止时被调用,用于删除任务和 消息队列。

OSTaskDel(MB_OS_CFG_RX_TASK_PRIO);

OSQDel(MB_OS_RxQ, OS_DEL_ALWAYS,  &err);

MB_OS_RxTask()

这个任务是由MB_OS_Init()创建, 如前面所述该任务只是简单等待消息,并调用MB_RxTask()主要代码如下:

while (DEF_TRUE) {

pch=(MODBUS_CH*)OSQPend(MB_OS_RxQ,0,&err);                                      MB_RxTask(pch); }

μC/Modbus已经提供了基于μC/OS?Ⅱ操作系统的接口程序,如果用户系统使用的是μC/OS?Ⅱ则不需要做任何改动。但μC/Modbus可以在任何实时操作系统下运行,只是需要用户来完成接口程序。

3  STM32F103处理器接口部分移植

μC/Modbus源码Ports文件夹下的程序是与硬件平台关联的。CPU.H用于定义数据类型、处理器堆栈数据字长、堆栈增长方向、任务切换宏和临界区访问处理方式。MB_BSP.C用于操作串口和RTU定时器[3]。

OS_CPU.H 头文件移植到Cortex?M3平台时,需要重新定义包含与编译器无关的数据类型。代码如下:

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

定义临界区代码访问涉及到的全局中断开关指令有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 () 分别对应关中断和开中断处理,需要使用汇编代码实现,代码如下:

CPU_SR_Save

MRS     R0, PRIMASK                  ;保存PRIMASK

CPSID   I                                             ;关中断

BX      LR

CPU_SR_Restore

MSR     PRIMASK, R0                  ;恢复PRIMASK

BX      LR

MB_BSP.C中实现串口操作、串口初始化、串口配置、串口发送接收中断服务程序和定时中断服务程序,具体实现参考STM32固件库。

MB_CommExit()???关闭使用的所有串行接口

MB_CommPortCfg()???配置串口通信参数

MB_CommRxTxISR_Handler()???串口接收、发送中断服务程序,需要加入如下代码。

if (Rx Interrupt) {

pch?>RxCtr++;

MB_RxByte(pch, c);

}

if (Tx Interrupt) {

pch?>TxCtr++;

MB_TxByte(pch);

}

MB_CommRxIntDis()???使能串口接收中断。

MB_CommRxIntEn()???禁止串口接收中断。

MB_CommTx1()???发送一个字节数据。

MB_CommTxIntDis()???禁止串口发送中断。

MB_CommTxIntEn()???使能串口发送中断。

MB_RTU_TmrInit()???初始化RTU定时器。

MB_RTU_TmrExit()???停用RTU定时器。

MB_RTU_TmrISR_Handler()???RTU 定时中断服务程序。需要加入如下代码。

MB_RTU_TmrCtr++;

MB_RTU_TmrUpdate() ;

4  μC/ Modbus的使用及应用程序设计

移植部分的代码编写完成后,就可以根据自己的应用需求配置μC/Modbus和设计应用程序。

在MB_CFG.H文件中配置参数。

定义设备为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

设置通道数

#define  MODBUS_CFG_MAX_CH     1

设置串口发送接收缓存大小

#define  MODBUS_CFG_BUF_SIZE       255

设置禁用浮点数

#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

根据所支持的命令码在MB_DATA.C文件中实现以下函数:

MB_CoilRd()用于响应01功能码, 读一个线圈的值。

MB_CoilWr()用于响应05、15功能码,写一个线圈的值。

MB_DIRd()用于响应02功能码,读一个离散输入的值。

MB_InRegRd()用于响应04功能码。读一个输入寄存器的值。

MB_HoldingRegRd()用于响应03功能码,读取一个保持寄存器的值。

MB_HoldingRegWr()用于响应06、16功能码,写一个保持寄存器的值。

最后用户就可以在main函数里或在用户任务里启动μ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个数据位

MODBUS_PARITY_NONE ,                     //无校验

1,                                             //1个停止位

MODBUS_WR_EN);                        //允许写操作

5  结  语

本文主要论述了μC/Modbus在Cortex?M3内核处理器上的移植过程并给出关键代码,移植后的μC/Modbus能够稳定运行于STM32F103处理器上。本移植能通用于所有Cortex?M3处理器和μC/OS?Ⅱ操作系统平台上,对于将μC/Modbus移植到其他体系平台上同样具有参考作用。实验表明μC/Modbus 是一款简单高效移植方便的标准Modbus协议栈。读者在了解μC/Modbus工作原理及移植过程之后,也可进一步做优化操作,比如利用STM32的串口DMA功能取代中断方式,以进一步降低CPU的开销。

参考文献

[1] LABROSSE J J. μC/OS?Ⅱ:源码公开的实时嵌入式操作系统[M].绍贝贝,译.北京:中国电力出版社,2001.

[2] YIU J. Cortex?M3权威指南[M].宋岩,译.北京:清华大学出版社,2008.

[3] 张桂,金国强,李辉.基于ARM平台Modbus RTU协议的研究与实现[J].电力科学与工程,2011(1):23?27.

[4] 王晓鸣.实时操作系统μC/OS?Ⅱ在ARM上的移植[J].机电一体化,2007(1):56?58.

[5] 周军,陈伟峰.基于ARM9和μC/OS?Ⅱ的Modbus通信协议的实现[J].自动化仪表,2009(2):24?26.

[6] 潘迪夫,习可.以PLC为通信主站的Modbus控制网络的设计与实现[J].现代电子技术,2010,33(5):142?144.