STM32F030R8移植freeModbus协议栈

环境

硬件

1、NUCLEO-F030R8,芯片为 STM32F030R8。该板子 RAM 为 8KB,FLASH 为 64KB,主频最高为48MHz。
2、一台 Win10 的机器,运行 Modbus Poll。
3、一个 USB 转 232 TTL 电平的小板。请特别注意,我是使用 RS232 进行通信的。如果是 RS485 或者 RS422 需要在对收发进行分别处理。

软件

IAR EWARM 8.22.1 + Stm32CubeMX 6.3 + HAL 1.11.3 + freeModbus 最新版

前言

本来是想用 libModbus 3.1.6,因为我的主站用的是这个。但是尝试移植到裸奔系统的时候才发现,libModbus 3.1.6 是在 Linux 下使用的。所以只好放弃,重新回到 freeModbus。

freeModbus 移植需求

freeModbus 是一个很小开源的 Modbus 协议栈,支持 ascii,rtu 和 tcp 模式。移植 freeModbus 需要硬件支持包括如下:
1、一个串口。
2、一个定时器。用于产生 3.5T 时间中断。
3、Modbus 相关的回调函数。

使用 CubeMX 生产基本代码

时钟

NUCLEO-F030R8 板子没有晶振,最高的时钟频率为 48 48 48MHz,我这里配置为 40 40 40MHz。其实这个频率可以任意配置,没有什么特别的要求。
STM32F030R8移植freeModbus协议栈_第1张图片

串口

我是用了 usart1,波特率什么都可以随便设置,因为在一直 freeModbus 的时候都会重新配置。只需要硬件使能串口,并打开中断即可。下面是 CubeMX 的配置截图。
STM32F030R8移植freeModbus协议栈_第2张图片
下图为串口中断配置。
STM32F030R8移植freeModbus协议栈_第3张图片
主要目的是让 CubeMX 自动生成对应的代码。注意串口中断的优先级设置为最高。
你也可以不配置自动生成中断代码,如果这样配置,所有串口中断代码需要自己完成。因此我就选择配置自动生成中断代码。

定时器

任意选择一个 Timer 都可以。这里我选择了 TIM6,没有什么特别原因,就是它简单。配置如图。
STM32F030R8移植freeModbus协议栈_第4张图片
也是随便配置一下。同样,在移植 freeModbus 的时候,会将 TIM6 重新配置的。
下图是 TIM6 中断配置。
STM32F030R8移植freeModbus协议栈_第5张图片
同样注意 TIM6 的优先级设置为 2 2 2,比 USART1 的优先级低。
你也可以不配置自动生成中断代码,如果这样配置,所有串口中断代码需要自己完成。因此我就选择配置自动生成中断代码。

生成代码

这样配置完成后,生成代码即可。

freeModbus 移植

拷贝代码

我遵守 CubeMX 的方式,也就是增加了 Middlewares 目录。然后将 freeModbus-master 压缩包中的 modbus 目录拷贝过来即可。对应的目录结构和文件如下图。
STM32F030R8移植freeModbus协议栈_第6张图片

port 目录

根据 freeModbus 官方文档定义,移植相关代码建议放在 port 目录下。建议参考 freeModbus-master/demo/bare 目录。
在该目录下有:文件 demo.c,该文件告诉你 main() 函数应该写什么,modbus 对应的回调函数应该如何实现。子目录 port 下有 4 4 4 个文件:
port.h
portevent.c 事件相关的移植文件
portserial.c 串口相关的移植文件
porttimer.c 定时器相关的移植文件
我就是将 bare 目录的 port 直接拷贝过来。具体的目录结构参考上图。

port.h

该文件不需要什么改动,只需要增加对应的板子头文件即可。我增加了如下代码:

#include "stm32f0xx_hal.h"

portevent.c

这个文件我没有做任何修改。

portserial.c

vMBPortSerialEnable

该函数为串口使能函数。根据自己的板子对串口中断进行使能即可。
我的代码如下:

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);
}

xMBPortSerialInit

该函数为串口初始化函数。根据自己的板子对串口初始化即可。
我的代码如下:

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、我是用的是 USART1,所以对应为 huart1,参考 usart.c。
2、由于 CubeMX 会对串口进行初始化,所以要先 DeInit。

xMBPortSerialPutByte

发送一个字节。

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;
}

xMBPortSerialGetByte

接收一个字节。

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;
}

prvvUARTTxReadyISR

Tx Ready ISR。

void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

注意:原来这个函数是一个静态函数,需要将 static 删除。我在 USART1 中断中调用了本函数。

prvvUARTRxISR

Rx ISR。

void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

注意:原来这个函数是一个静态函数,需要将 static 删除。我在 USART1 中断中调用了本函数。

porttimmer.c

xMBPortTimersInit

定时器初始化函数。

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频率是40Mhz
  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;
}

感觉这个定时器不需要特别的准确。随便给一个时间就可以了。

vMBPortTimersEnable

使能定时器中断。

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);
}

vMBPortTimersDisable

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);
}

prvvTIMERExpiredISR

定时器结束中断。

void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

注意:原来这个函数是一个静态函数,需要将 static 删除。我在 TIM 中断中调用了本函数。

中断函数

在stm32f0_xx_it.c 中。增加对应的代码。

串口中断

CubeMX 生成的函数为 USART1_IRQHandler。增加的代码如下:

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

以上的代码是处理串口接收数据。

定时器中断

CubeMX 生成的函数为 TIM6_IRQHandler。增加的代码如下:

  /* USER CODE BEGIN TIM6_IRQn 1 */
  prvvTIMERExpiredISR();
  /* USER CODE END TIM6_IRQn 1 */

回调函数

由于 freeModbus 采用了回调函数的方法来实现具体的功能。所以我们必须实现对应的协议回调函数。
这部分代码的实现,可以参考 freeModbus-master/demo 目录中任意一个实现。

头文件

必须包含 mb.h。

#include "mb.h"

初始化

主要是定义开始地址,数据缓存区之类。

#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];

eMBRegInputCB

Read Input Register。响应功能码 0x04。

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;
}

eMBRegHoldingCB

Write Holding Register。响应功能码 0x06。

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;
}

eMBRegCoilsCB

Read Coils。响应功能码 0x01。

eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
    return MB_ENOREG;
}

也就是说我暂时没有实现。

eMBRegDiscreteCB

Read Discrete Inputs。响应功能码 0x02。

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    return MB_ENOREG;
}

其他相关功能码

其参考 mb.c。代码如下:

static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0
    {MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0
    {MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
    {MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
    {MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
    {MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
    {MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0
    {MB_FUNC_READ_COILS, eMBFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0
    {MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
    {MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0
    {MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs},
#endif
};

freeModbus 支持的功能码

参考 mbproto.h。支持的功能码如下:

#define MB_FUNC_READ_COILS                    (  1 )
#define MB_FUNC_READ_DISCRETE_INPUTS          (  2 )
#define MB_FUNC_WRITE_SINGLE_COIL             (  5 )
#define MB_FUNC_WRITE_MULTIPLE_COILS          ( 15 )
#define MB_FUNC_READ_HOLDING_REGISTER         (  3 )
#define MB_FUNC_READ_INPUT_REGISTER           (  4 )
#define MB_FUNC_WRITE_REGISTER                (  6 )
#define MB_FUNC_WRITE_MULTIPLE_REGISTERS      ( 16 )
#define MB_FUNC_READWRITE_MULTIPLE_REGISTERS  ( 23 )
#define MB_FUNC_DIAG_READ_EXCEPTION           (  7 )
#define MB_FUNC_DIAG_DIAGNOSTIC               (  8 )
#define MB_FUNC_DIAG_GET_COM_EVENT_CNT        ( 11 )
#define MB_FUNC_DIAG_GET_COM_EVENT_LOG        ( 12 )
#define MB_FUNC_OTHER_REPORT_SLAVEID          ( 17 )

代码运行效果

04 命令测试

STM32F030R8移植freeModbus协议栈_第7张图片
注意上面代码中

static uint16_t   usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};//为了验证使用的初始化值

06 命令测试

STM32F030R8移植freeModbus协议栈_第8张图片

03 命令测试

其实代码收到 03 命令就直接返回了。但是从协议的角度。报文时正常的。
STM32F030R8移植freeModbus协议栈_第9张图片

你可能感兴趣的:(笔记,stm32,物联网,freeModbus,STM32F0)