FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)

FreeModbus的移植,基于STM32F4+HAL库平台的MODBUS RTU从机

FreeModbus

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 ,这意味着本协议栈的实现代码可以应用于商业用途。

目前版本的FreeModbus支持如下的功能码:

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

freemobus的API说明
freemobus github
源码下载页面

移植过程

  1. 下载freemodbus 源码
    在上面的链接下,可以下载1.6版本的源代码,此版本包括Modbus RTU / ASCII,Modbus TCP,输入/保持寄存器访问,离散寄存器等。此外,它还被移植到以下平台:FreeRTOS / Cortex M3 SAM3S,FreeRTOS / ARM STR71X,FreeRTOS / ARM AT91SAM7X,lwIP / PPP / STR71X,飞思卡尔MCF5235,lwIP / MCF5235,Atmel AVR ATMega168,TI-MSP430,Win32和Linux操作系统。(居然没有STM32平台?)
    解压缩后
    FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第1张图片
    移植需要的文件再demomodbus文件夹中
    demo文件夹中的内容☟
    FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第2张图片
    我们需要BARE文件夹里的文件
    BARE文件夹
    之后是modbus文件夹,里面有modbus功能的源代码
    FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第3张图片

  2. 新建stm32F4的工程
    因为我使用的是正点原子阿波罗的开发板,我用cubeMX软件进行初始化配置,芯片选择stm32f429IGT6
    时钟,GPIO,调试端口这些按需要进行配置,这里freemodbus需要的外设有1个TIMER和1个UART。(可以看出来,freeModbus需要的外设资源就是这么少)串口是用来承载modbus协议的物理层端口,定时器是用来产生T3.5的中断的,freemodbus是根据这个超时中断来判断一帧数据接收完成的。
    FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第4张图片
    TIMER6的配置,这里预分频器和自动装载寄存器的值可以先随便填一个,因为后续我们在freemodbus的移植文件里还要重新对TIMER进行初始化。
    uart1的配置
    FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第5张图片
    uart1的配置。这里也是可以先随便填(停止位要配置好),后续在移植文件里还要重新对UART1进行初始化。
    FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第6张图片
    之后记得使能两个外设的中断,这里优先级的设置要提一下,查阅了很多文档,都没有说明这两个外设(串口和定时器)优先级的配置,我在开始配置的时候认为定时器的优先权要高一些,也许freemodbus要使用这个定时器定时查询什么状态。但是在之后移植完成后,我仔细阅读了源代码,认为应该把串口的优先级调高,优先保证将数据从串口中取出,定时器中断的作用就是post一个信号量,使得eMBPoll() 可以 get这个信号量,这个post的动作适当推迟一些应该也没什么问题,所以定时器的中断优先级就可以比中断优先级低。(图中是我一开始配置的优先级,之后我在程序里将这两个外设的优先级对调了一下?)
    时钟配置按需配置就行,记得TIMER6用的PCLK频率是多少,这个需要我们来计算分频系数的。
    配置完成就可以generate code了,我用的最新的F4的HAL库(V1.24.1)

  3. 添加*.c *.h文件
    FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第7张图片
    上面是keil的工程目录结构
    sys.cusart.c是我从正点原子代码工程里拷贝来的,里面有些比较好用的宏定义和函数API
    注意sys.h中需要修改1个宏

#define SYSTEM_SUPPORT_OS		0		//系统文件夹是否支持OS,1:支持  0:不支持

然后就是把usart.c中和其他文件中冲突的代码都注释掉,因为这个文件里我们只用如下功能

//重定义fput()
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送
	USART1->DR = (u8) ch;      
	return ch;
}

紧接着添加头文件
FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第8张图片

  1. 正式移植过程
    freemodbus中需要移植的都是BARE文件夹里port的文件
    port文件
  • port.h
#define ENTER_CRITICAL_SECTION( )   __disable_irq()
#define EXIT_CRITICAL_SECTION( )    __enable_irq()

补充完整这两个宏定义

  • portserial.c
    串口相关的API
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "sys.h"
extern UART_HandleTypeDef huart1;
/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );

添加sys.h头文件,去掉*prvvUARTTxReadyISR( void )*和 prvvUARTRxISR( void )static属性,这是方便在ISR中调用这两个函数。
添加huart1声明,目的是在这个文件中使用这个句柄。

void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
	if(xRxEnable == TRUE)
		 __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
	else
		 __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
	
	if(xTxEnable == TRUE)
		 __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
	else
		 __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
}

完成串口使能函数体,接收时使用RXNE中断,发送时使用TXE中断
这两个中断是在RM0090中USART状态寄存器 (USART_SR)定义的
FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第9张图片
FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第10张图片

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
	HAL_UART_DeInit(&huart1);//DEINIT cubeMX中的初始化配置
	(void)ucPORT;
	huart1.Instance = USART1;
    huart1.Init.BaudRate = ulBaudRate;

  
    huart1.Init.StopBits = UART_STOPBITS_1;
	switch (eParity)//使用校验位,就需要将uart的数据位配置为9位
  {
  	case MB_PAR_ODD:
			huart1.Init.WordLength = UART_WORDLENGTH_9B;
			huart1.Init.Parity = UART_PARITY_ODD;
  		break;
  	case MB_PAR_EVEN:
			huart1.Init.WordLength = UART_WORDLENGTH_9B;
			huart1.Init.Parity = UART_PARITY_EVEN;
  		break;
  	default:
			huart1.Init.WordLength = UART_WORDLENGTH_8B;
			huart1.Init.Parity = UART_PARITY_NONE;
  		break;
  }
  
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    return FALSE;
  }

		return TRUE;
}

重新对串口1进行初始化,重新配置波特率和校验位,至于数据位,freemodbus中固定为8位,停止位按需配置,端口号暂时我没有用到。

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. */
	
		if(HAL_UART_Transmit(&huart1,(uint8_t*)&ucByte,1,1) == HAL_OK)
			return TRUE;
		else
			return FALSE;
}

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.
     */

	if(HAL_UART_Receive(&huart1,(uint8_t*)pucByte,1,1) == HAL_OK)
    	return TRUE;
	else
		return FALSE;
}

发送和接收1个字节,调用的HAL库函数。

/* 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(  );
}

这两个函数在串口1ISR中会调用,在这里只需要删掉函数前的static即可

  • porttimer.c
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

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

/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );
extern TIM_HandleTypeDef htim6;

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
	HAL_TIM_Base_DeInit(&htim6);
	
	TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM6_Init 1 */

  /* USER CODE END TIM6_Init 1 */
  htim6.Instance = TIM6;
  htim6.Init.Prescaler = 4499;//50us分频,这里使用的timer PCLK频率是90Mhz
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;


  htim6.Init.Period = usTim1Timerout50us-1;//modbus 规定的TIMEOUT时间

		
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  {
     return FALSE;
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
  {
     return FALSE;
  }
	 return TRUE;
	
}

定时器的初始化。这里是被eMBInit进行调用,调用时usTim1Timerout50us参数是用来计算50us的倍数的,这个值在eMBRtuInit()中根据波特率进行计算。

inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
	__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
	__HAL_TIM_SetCounter(&htim6,0);//这里一定要清零计数器
	HAL_TIM_Base_Start_IT(&htim6);

}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
	HAL_TIM_Base_Stop_IT(&htim6);
	
	__HAL_TIM_SetCounter(&htim6,0);
	__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
	
}

定时器的启停

/* 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(  );
}

会在TIMER6的ISR中调用,去掉前面static即可

  • portevent.c
    这是实现事件标志的代码,完全不用动,至少不动不会影响代码运行。

看到这里freemodbus中的工作已经完成,现在需要修改原工程下的一些文件

  • stm32f4xx_it.c
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint8_t tmp;
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_PE))//奇偶校验位判断
	{
		HAL_UART_Receive(&huart1,&tmp,1,1);
	}
	
	else if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_RXNE))
	{
		prvvUARTRxISR();
		
	}
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_TXE))
	{
		prvvUARTTxReadyISR();
		
	}
	
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

/**
  * @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
  */
void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */

  /* USER CODE END TIM6_DAC_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */
	prvvTIMERExpiredISR();
  /* USER CODE END TIM6_DAC_IRQn 1 */
}

修改两个外设的ISR,在UART的ISR中加入了奇偶校验的判断

主函数main.c应用程序

#include "mb.h"
#include "sys.h"
//添加头文件
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START     0x0001U //寻址地址是从1开始的
#define REG_INPUT_NREGS 4

#define REG_HOLDING_START               ( 1 )
#define REG_HOLDING_NREGS               ( 32 )
/* ----------------------- Static variables ---------------------------------*/
static uint16_t   usRegInputStart = REG_INPUT_START;
static uint16_t   usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};//为了验证使用的初始化值

static USHORT   usRegHoldingStart = REG_HOLDING_START;
static USHORT   usRegHoldingBuf[REG_HOLDING_NREGS];

添加输入寄存器和保持寄存器的起始地址,定义寄存器大小,定义数组

int main(void)
{
    /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

   /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_TIM6_Init();

	__HAL_TIM_CLEAR_FLAG(&htim6,TIM_FLAG_UPDATE);//是HAL库的一个BUG,初始化完成后会置位UIF
	eMBInit(MB_RTU, 0x01, 1, 9600, MB_PAR_ODD);//Modbus初始化
	eMBEnable();//使能协议栈


  /* Infinite loop */

  while (1)
  {

		eMBPoll();

		PBout(0) = !PBout(0);//这是我点亮LED的语句
		PBout(1) = !PBout(1);

		HAL_Delay(100);//轮询时间为100ms
  }

}

void __aeabi_assert(const char * x1, const char * x2, int x3)
{
	
}

freemodbus中使用了断言函数,这里添加函数体解决断言函数找不到的问题

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
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 )
        {
        case MB_REG_READ:
            while( usNRegs > 0 )
            {
                *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
                *pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
                iRegIndex++;
                usNRegs--;
            }
            break;

        case MB_REG_WRITE:
            while( usNRegs > 0 )
            {
                usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

在这里实现了输入寄存器和保持寄存器的相关功能函数。

试验0x04功能

FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第11张图片

试验0x06功能

FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第12张图片

试验0x03功能

FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第13张图片

试验0x10功能

FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)_第14张图片
0x17功能也可以实现,这里就偷懒不放图了

到这里就全部移植完成了

你可能感兴趣的:(嵌入式,freemodbus,stm32)