Freemodbus RTU在stm32上的移植分析
最近用到free modbus,需要在stm32上进行移植,以作modbus-RTU之用。现成协议的东西用起来很方便,现成源码很快就可以为设计者所用,也是当初制定标准的初衷吧。首先下载最新的modbus源码,所谓技术更新换代的比较快,用就用最新的东西,协议嘛也要下载最新的,google一下,在http://www.freemodbus.org/index.php?idx=5下载最新的版本freemodbus-v1.5,下载最新的协议不仅可以防止被人改动导致自己做无用功,保持原生态也可以很好的与制定者进行交流。解压freemodbus-v1.5,目录结构很清晰,主要有四个文件件,分别是demo、modbus、tools、doc。其中tools为上位机测试modbus程序,doc为一些说明文件先不讨论。有用的是demo以及modbus。打开demo,没有看到stm32的工程文件,有一个叫BARE的文件夹,是一些不包括任何处理器的部分源代码,我们就用这个建立工程文件。为了给以后移植modbus-TCP带来方便,这里直接打开之前测试好的基于ENC28J68的LwIP的stm32工程,在其中导入各个文件。
导入前的原始以太网测试工程将prot以及modbus文件夹拷贝到工程文件夹下,导入工程,将demo中的main等几个函数拷贝到原先main.c中,注释掉原先的mian函数,就成了这个样子:
先理清所有依赖关系,肯定出现一大堆找不到头文件宏定义什么的错误,这个在keil中将文件夹的路径添加到include path中即可,非常方便。rebuild一下,发现有两个错误:
1
原来Keil4不支持inline这个关键字,直接将其删掉,编译出现了:..\\Output\\STM32-DEMO.axf: Error:L6218E: Undefined symbol __aeabi_assert (referred from mbascii.o).出现的这个问题,各种百度以及谷歌,找了半天也没找到解决方法。这里不得不说百度虽然本土化做得很好,可是以英文作为关键词时,往往搜出一大堆不相干的东西,基本上搜不到国外的网页;谷歌本土化做的不好,服务器响应比较慢。想起360新出了搜索引擎,赶紧去试试,还真的有惊喜,在一篇帖子中写道“MicroLib并不支援assert(),所以才会出现错误讯息”,原来原工程使用了微库,在target中钩掉USE MicroLIB编译就可以通过了。昨天看优库老友记采访周鸿祎说360做搜索引擎,作为360的忠实用户应该支持一下。任何一个公司想做的更好,必须注意用户体验。 下面开始正式的移植以及分析和测试工作:
一、对于时钟的移植:由于modbus RTU模式需要定时器的支持,所以第一步先移植与定时器相关的函数。在porttimer.c中添加BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )的实现,实现50us的基时时钟。添加打开和关闭时钟的函数void vMBPortTimersEnable( )以及voidvMBPortTimersDisable( ),还有超时中断函数voidTIM4_IRQHandler(void) 二、对串口通信的移植
无论是modbus ASCII还是RTU模式,都以串口通讯做为载体,需要添加串口的使能 BOOL xMBPortSerialInit,收发中断的使能voidvMBPortSerialEnable( BOOLxRxEnable, BOOL xTxEnable ),发送以及接收BOOL xMBPortSerialPutByte( CHAR ucByte ),BOOL xMBPortSerialGetByte( CHAR * pucByte ),这几个函数没什么好说的,有两个中断函数我比较好奇,就是static void prvvUARTTxReadyISR( void )以及staticvoid prvvUARTRxISR( void ),就是一个发送中断一个接收中断,为什么是这样的名字呢,stm32串口发生中断怎么去调用它们呢,如果换成其他单片机,为什么是这样的一个名字呢?原来在freemodbus中并没有提供中断函数的具体名称,还需要根据自己使用的处理器自己添加中断处理函数voidUSART1_IRQHandler(void),在其中调用上述两个发送和接收的函数。
三、第一个功能单元——读写寄存的支持
FreeModbus源码包中BARE工程文件的main.c中定义了
eMBErrorCode eMBRegInputCB()、eMBErrorCode eMBRegHoldingCB()、eMBErrorCode eMBRegCoilsCB()以及eMBErrorCode eMBRegDiscreteCB()四个函数与从机寄存器做了接口,并给出了eMBErrorCode eMBRegInputCB()的例子,在工程中建立portreg.c来实现这几个函数。
其中eMBErrorCode eMBRegInputCB()为读寄存器的值,eMBErrorCode eMBRegHoldingCB()为向单
2
片机写入寄存器的值,eMBErrorCodeeMBRegCoilsCB()为读写多个开关量的函数,eMBErrorCodeeMBRegDiscreteCB()为读多个离散开关量的函数。
为了方便测试,我们只先实现第一个eMBErrorCodeeMBRegInputCB()读连续多个寄存器值的函数,比如实现读GPIOA-GPIOG的值。 定义
#define REG_INPUT_START 0 #define REG_INPUT_NREGS 7
在eMBErrorCode eMBRegInputCB()开始部分加入度寄存器值语句,以备查询时使用。
比如参数UCHAR *pucRegBuffer, USHORT usAddress = 0, USHORT usNRegs = 3 就是读取端口GPIOA-GPIOC的值
usRegInputBuf[0] =GPIO_ReadInputData(GPIOA); usRegInputBuf[1] =GPIO_ReadInputData(GPIOB); usRegInputBuf[2] =GPIO_ReadInputData(GPIOC); usRegInputBuf[3] =GPIO_ReadInputData(GPIOD); usRegInputBuf[4] =GPIO_ReadInputData(GPIOE); usRegInputBuf[5] =GPIO_ReadInputData(GPIOF); usRegInputBuf[6] =GPIO_ReadInputData(GPIOG); 这部分定义好,我们看一下freemodbus运行的流程。 四、freemodbus运行的软件仿真分析 在main函数中可以看到 int main( void ){ eMBErrorCode eStatus;
eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN ); /* Enable the Modbus Protocol Stack. */ eStatus = eMBEnable( );
3
for( ;; ){
( void )eMBPoll( );
/* Here we simply count the number ofpoll cycles. */ usRegInputBuf[0]++; } }
(1)其中调用了
eMBErrorCode eMBInit( eMBMode eMode, UCHARucSlaveAddress, UCHAR ucPort, ULONGulBaudRate, eMBParity eParity );
其目的就是选择要使用的模式是RTU、ASCII码还是TCP方式,如果选择的模式是RTU或ASCII模式其他都是串口的一些设置;如果选择的是TCP模式,需要调用到eMBErrorCode eMBTCPInit( USHORT usTCPPort )只需要制定端口号即可。这里我们先用RTU模式做测试。在这同时也对定时器进行了初始化。 (2)之后调用了
eMBErrorCode eMBEnable( void );来使能modbus协议栈,其中调用pvMBFrameStartCur( ),在eMBInit根据模式选择的不同,pvMBFrameStartCur( )会有不同的原型,这里选用的是RTU模式,那么将调用eMBRTUStart,其中调用了 vMBPortSerialEnable vMBPortTimersEnable来时能串口和定时器,使能了超时定时器,故经过T35时间后,发生第一次超时中断,在中断中,向协议栈发送消息EV_READY(Startupfinished),并调用voidvMBPortTimersDisable( )关闭超时定时器,同时将eRcvState设为STATE_RX_IDLE。此时,协议栈可以接收串口数据。 (3)最后调用
eMBErrorCode eMBPoll( void )用来检测协议栈状态用于处理消息。 五,编译后进行软件仿真查看协议栈具体运行流程 打开
keil
软件仿真器,软件仿真进行单步运行,如下图所示。
4
对工程进行软件仿真
(1)执行main()中eStatus = eMBInit( MB_RTU, 0x0A, 0, 9600,MB_PAR_NONE );,我们进去看一下,可以看到,我们初始化的slave address是0x0A,modbus支持1-247个从地址。我们制定的是RTU模式,所以要对RTU一些参数进行赋值,如所调用的初始化函数为 pvMBFrameStartCur = eMBRTUStart; pvMBFrameStopCur = eMBRTUStop; peMBFrameSendCur = eMBRTUSend; peMBFrameReceiveCur =eMBRTUReceive;
pvMBFrameCloseCur =MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; pxMBFrameCBByteReceived =xMBRTUReceiveFSM; pxMBFrameCBTransmitterEmpty =xMBRTUTransmitFSM; pxMBPortCBTimerExpired =xMBRTUTimerT35Expired;
其中包括了modbus的启动、停止、发送以及接收数据的函数具体形式,其中比较关心的是xMBRTUReceiveFSM、xMBRTUTransmitFSM这两个函数,规定了modbus状态转换的状态机,其中xMBRTUReceiveFSM为接收状态机,如下图所示:
5