STM32F407和ucosIII移植FreeMODBUS RTU

  • Post author:
  • Post category:其他




1 FreeMODBUS是什么


FreeMODBUS 是针对通用的Modbus协议栈在嵌入式系统中应用的一个实现

。Modbus协议是一个在工业制造领域中得到广泛应用的一个网络协议。

一个Modbus通信协议栈包括两层:定义了数据结构和功能Modbus应用协议和网络层。


在FreeMODBUS的当前版本中,提供了Modbus Application Protocol v1.1a 的实现并且支持在Modbus over serial line specification 1.0中定义的RTU/ASCII传输模式。从0.7版本开始,FreeModbus也支持在TCP defined in Modbus Messaging on TCP/IP Implementation Guide v1.0a中定义的TCP传输。Freemodbus遵循BSD[1] ,这意味着本协议栈的实现代码可以应用于商业用途。目前版本的FreeModbus支持如下的功能码:

  • 读输入寄存器 (0x04)
  • 读保持寄存器 (0x03)
  • 写单个寄存器 (0x06)
  • 写多个寄存器 (0x10)
  • 读/写多个寄存器 (0x17)
  • 读取线圈状态 (0x01)
  • 写单个线圈 (0x05)
  • 写多个线圈 (0x0F)
  • 读输入状态 (0x02)
  • 报告从机标识 (0x11)

本实现基于最新的标准并且与标准完全兼容。

接收和传输Modbus RTU/ASCII数据帧是通过一个由硬件提取层的调用来驱动状态机来实现的

。这就使得该协议非常容易移植到其他的平台之上。当收到一个完整的数据帧后,该数据帧被传入Modbus应用层,数据帧的内容在该层得到解析。为例方便增加新的Modbus功能,Freemodbus在应用层通提供了Hooks。

如果用到了Modbus TCP协议,那么当准备处理一个新数据帧的时候,移植层就必须首先向协议栈发送一个事件标志。然后,协议栈调用一个返回值为接收到的Modbus TCP数据帧的函数,并且开始处理这个数据帧。如果数据有效,则相应的Modbus反馈帧将提供给移植层生成反馈帧。最后,该反馈被发送到客户端。

(原文来自

freemodbus

)



1.1 移植准备

FreeModbus协议对硬件的需求非常少——基本上任何具有串行接口,并且有一些能够容纳modbus数据帧的RAM的微控制器都足够了。

  • 一个异步串行接口,能够支持接收缓冲区满和发送缓存区空中断。
  • 一个能够产生RTU传输所需要的t3.5字符超时定时器的时钟

对于软件部分,仅仅需要一个简单的事件队列。

在使用操作系统的处理器上,可通过单独定义一个任务完成Modbus时间的查询

。小点的微控制器往往不允许使用操作系统,在那种情况下,可以使用一个全局变量来实现该事件队列。



2 下载demo

在freemodbus的GitHub上下载最新的代码

https://github.com/cwalter-at/freemodbus



2.1 拷贝基础代码

文件夹中demo是一些不同嵌入式平台的例子,将modbus 文件夹拷贝到自己的工程目录,并添加到工程。

freemodbus文件夹

进入demo 文件夹,将BARE文件夹中的port文件夹拷贝到自己的工程目录,并添加到工程。



3 增加硬件驱动层代码



3.1 修改串口驱动



3.1.1 修改portserial.c

打开port目录下的portserial.c,增加串口使能接口、串口初始化设置、发送数据,接收数据等接口。

/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
	if(TRUE==xRxEnable)
	{
		USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	}
	else
	{
		USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); 
	}

	if(TRUE==xTxEnable)
	{
		USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
	}
	else
	{
	   USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
	}
}


BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	
	USART_InitStructure.USART_BaudRate = ulBaudRate; // 设置了USART传输的波特率 ;
	if(ucDataBits == 9)
		USART_InitStructure.USART_WordLength = USART_WordLength_9b; // 9位数据 ;
	else
		USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据 ;
	
	if(eParity == MB_PAR_ODD)
		USART_InitStructure.USART_Parity = USART_Parity_Odd;  // 无校验位;
	else if(eParity == MB_PAR_EVEN)
		USART_InitStructure.USART_Parity = USART_Parity_Even;  // 无校验位;
	else
		USART_InitStructure.USART_Parity = USART_Parity_No;  // 无校验位;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;	// 在帧结尾传输1个停止位 ;	
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制失能 ;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  // 接收发送使能 ;

	//USART1端口配置
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10

	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9,GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10,GPIO_AF_USART1);

	USART_Init(USART1, &USART_InitStructure); 
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); 
	USART_Cmd(USART1, ENABLE); // 

    return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
    /* Put a byte in the UARTs transmit buffer. This function is called
     * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
     * called. */
	USART_SendData(USART1, ucByte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);/*等待发送完成*/		
    return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
    /* Return the byte in the UARTs receive buffer. This function is called
     * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
     */
    *pucByte = USART_ReceiveData(USART1);
    return TRUE;
}

/* Create an interrupt handler for the transmit buffer empty interrupt
 * (or an equivalent) for your target processor. This function should then
 * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
 * a new character can be sent. The protocol stack will then call 
 * xMBPortSerialPutByte( ) to send the character.
 */
void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

/* Create an interrupt handler for the receive interrupt for your target
 * processor. This function should then call pxMBFrameCBByteReceived( ). The
 * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
 * character.
 */
void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}




3.1.2 修改中断响应函数

void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u8 Res;
	OSIntEnter();    
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 
	{
		//Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
		prvvUARTRxISR();

		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		
	} 
	if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
	{
		prvvUARTTxReadyISR();
	}
	OSIntExit();    	//退出中断

} 



3.2 修改时钟驱动



3.2.1 修改porttimer.c

设置一个能够产生RTU传输所需要的t3.5字符超时定时器的时钟。

根据官网的描述,


  • 当波特率 > 19200bps时,采用定时器时钟固定为1750us ,

  • 当波特率 < 19200bps时,定时器时钟为3.5个字符传输时间长

在文件中的 xMBPortTimersInit函数的参数usTim1Timerout50us为50us的倍数

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	
	TIM_TimeBaseInitStructure.TIM_Period = usTim1Timerout50us; 	
	TIM_TimeBaseInitStructure.TIM_Prescaler=16800-1;  //定时器分频 50us
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 

	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化TIM

	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许定时器更新中断
	TIM_Cmd(TIM2,ENABLE); //使能定时器

    return TRUE;
}

porttimer.c完整文件

/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "config.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	
	TIM_TimeBaseInitStructure.TIM_Period = usTim1Timerout50us; 	
	TIM_TimeBaseInitStructure.TIM_Prescaler=2100-1;  //定时器分频 50us
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 

	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化TIM

	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //允许定时器更新中断
	TIM_Cmd(TIM2,ENABLE); //使能定时器

    return TRUE;
}


inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	TIM_SetCounter(TIM2, 0);
	//TIM_Cmd(TIM2, ENABLE);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);    
}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
	TIM_SetCounter(TIM2, 0);
	//TIM_Cmd(TIM2, DISABLE);
	TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);    
}

/* Create an ISR which is called whenever the timer has expired. This function
 * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
 * the timer has expired.
 */
void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}




3.2.2 修改中断响应函数

void TIM2_IRQHandler(void)
{
	OSIntEnter(); //进入中断 
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //溢出中断
	{	
		 prvvTIMERExpiredISR();
	}
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    
	OSIntExit();  
}



3.3 中断配置

在系统初始化中配置中断映射

void InterruptConfig(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

	//NVIC_DeInit();
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    //USART1中断 
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;	 
  	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
  	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);		
	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn; //定时器3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=9; //子优先级8
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);	
}



3.4修改mbconfig.h

在修改确认 使能打开MB_RTU_ENABLED

/*! \brief If Modbus ASCII support is enabled. */
#define MB_ASCII_ENABLED                        (  0 )

/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED                          (  1 )

/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED                          (  0 )



4 数据处理

int main(void)
{	 	
	delay_init();	    	
	InterruptConfig(); 

	eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE);//初始化freemodbus 设置RTU模式和ID等
	eMBEnable();	
	
		OSInit(&err);		//初始化UCOSIII
	//创建开始任务
	OSTaskCreate((OS_TCB 	* )&StartTaskTCB,		//任务控制块
				 (CPU_CHAR	* )"start task", 		//任务名字
                 (OS_TASK_PTR )start_task, 			//任 务函数
                 (void		* )0,					//传递给任务函数的参数
                 (OS_PRIO	  )START_TASK_PRIO,     //任务优先级
                 (CPU_STK   * )&START_TASK_STK[0],	//任务堆栈基地址
                 (CPU_STK_SIZE)START_STK_SIZE/10,	//任务堆栈深度限位
				 (CPU_STK_SIZE)START_STK_SIZE,		//任务堆栈大小
                 (OS_MSG_QTY  )0,					//任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
                 (OS_TICK	  )0,					//当使能时间片轮转时的时间片长度,为0时为默认长度,
                 (void   	* )0,					//用户补充的存储区
                 (OS_OPT      )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
                 (OS_ERR 	* )&err);				//存放该函数错误时的返回值	 
	OSStart(&err);  //开启UCOSIII   
}
//创建串口任务
void start_task(void *pdata)
{
  OS_CPU_SR cpu_sr=0;
	pdata = pdata; 	
	OSStatInit();					//初始化统计任务.这里会延时1秒钟左右	
 	OS_ENTER_CRITICAL();		
	// other task 
	//--------------------------------
	//创建串口任务
			//控制任务
		OSTaskCreate((OS_TCB*	  )&DataTaskTCB,		
					 (CPU_CHAR*   )"Data Task", 		
					 (OS_TASK_PTR )DataTask,			
					 (void* 	  )0,					
					 (OS_PRIO	  )DATA_TASK_PRIO,	   
					 (CPU_STK*	  )&DATA_TASK_STK[0],	
					 (CPU_STK_SIZE)DATA_STK_SIZE/10,	
					 (CPU_STK_SIZE)DATA_STK_SIZE,		
					 (OS_MSG_QTY  )0,					
					 (OS_TICK	  )0,					
					 (void* 	  )0,					
					 (OS_OPT	  )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
					 (OS_ERR*	  )&err);	
 	OSTaskSuspend(START_TASK_PRIO);	
	OS_EXIT_CRITICAL();				
}	

单独的任务处理数据收发

void DataTask (void *pdata)
{
	OS_ERR err;
	while(1)
	{
		eMBPoll();
		OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err);//延时10ms
		
	}	
}



4.1 拷贝寄存器处理

拷贝

eMBRegHoldingCB//保持寄存器

eMBRegInputCB//输入寄存器

eMBRegCoilsCB//线圈状态寄存器

eMBRegDiscreteCB//离散输入状态寄存器

到main文件中

MBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_HOLDING_START ) &&
        ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegHoldingStart );
        switch ( eMode )
        {
                /* Pass current register values to the protocol stack. */
            case MB_REG_READ:
                while( usNRegs > 0 )
                {
                    *pucRegBuffer++ =
                        ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
                    *pucRegBuffer++ =
                        ( unsigned char )( usRegHoldingBuf[iRegIndex] &
                                           0xFF );
                    iRegIndex++;
                    usNRegs--;
                }
                break;

                /* Update current register values with new values from the
                 * protocol stack. */
            case MB_REG_WRITE:
                while( usNRegs > 0 )
                {
                    usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                    usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                    iRegIndex++;
                    usNRegs--;
                }
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

/**
  * @功能
  * @参数
  * @返回值
  */
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}


/**
  * @功能
  * @参数
  * @返回值
  */


eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iNCoils = ( int )usNCoils;
    unsigned short  usBitOffset;

    /* Check if we have registers mapped at this block. */
    if( ( usAddress >= REG_COILS_START ) &&
        ( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )
    {
        usBitOffset = ( unsigned short )( usAddress - REG_COILS_START );
        switch ( eMode )
        {
                /* Read current values and pass to protocol stack. */
            case MB_REG_READ:
                while( iNCoils > 0 )
                {
                    *pucRegBuffer++ =
                        xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,
                                        ( unsigned char )( iNCoils >
                                                           8 ? 8 :
                                                           iNCoils ) );
                    iNCoils -= 8;
                    usBitOffset += 8;
                }
                break;

                /* Update current register values. */
            case MB_REG_WRITE:
                while( iNCoils > 0 )
                {
                    xMBUtilSetBits( ucRegCoilsBuf, usBitOffset, 
                                    ( unsigned char )( iNCoils > 8 ? 8 : iNCoils ),
                                    *pucRegBuffer++ );
                    iNCoils -= 8;
                    usBitOffset += 8;
                }
								Led_Update();
                break;
        }

    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    short           iNDiscrete = ( short )usNDiscrete;
    unsigned short  usBitOffset;

    /* Check if we have registers mapped at this block. */
    if( ( usAddress >= REG_DISC_START ) &&
        ( usAddress + usNDiscrete <= REG_DISC_START + REG_DISC_SIZE ) )
    {
        usBitOffset = ( unsigned short )( usAddress - REG_DISC_START );
        while( iNDiscrete > 0 )
        {
            *pucRegBuffer++ =
                xMBUtilGetBits( ucRegDiscBuf, usBitOffset,
                                ( unsigned char )( iNDiscrete >
                                                   8 ? 8 : iNDiscrete ) );
            iNDiscrete -= 8;
            usBitOffset += 8;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}



5 验证

代码验证通过,移植成功



版权声明:本文为sxlworld原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。