当前位置:首页 » 《我的小黑屋》 » 正文

STM32F030R8移植freeModbus协议栈_努力中的老周的专栏

4 人参与  2021年12月23日 12:51  分类 : 《我的小黑屋》  评论

点击全文阅读


环境

硬件

NUCLEO-F030R8,芯片为 STM32F030R8。该板子 RAM 为 8KB,FLASH 为 64KB,主频最高为48MHz。

软件

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。其实这个频率可以任意配置,没有什么特别的要求。
在这里插入图片描述

串口

我是用了 usart1,波特率什么都可以随便设置,因为在一直 freeModbus 的时候都会重新配置。只需要硬件使能串口,并打开中断即可。下面是 CubeMX 的配置截图。
在这里插入图片描述
下图为串口中断配置。
在这里插入图片描述
主要目的是让 CubeMX 自动生成对应的代码。注意串口中断的优先级设置为最高。

定时器

任意选择一个 Timer 都可以。这里我选择了 TIM6,没有什么特别原因,就是它简单。配置如图。
在这里插入图片描述
也是随便配置一下。同样,在移植 freeModbus 的时候,会将 TIM6 重新配置的。
下图是 TIM6 中断配置。
在这里插入图片描述
同样注意 TIM6 的优先级设置为 2 2 2,比 USART1 的优先级低。

生成代码

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

freeModbus 移植

拷贝代码

我遵守 CubeMX 的方式,也就是增加了 Middlewares 目录。然后将 freeModbus-master 压缩包中的 modbus 目录拷贝过来即可。对应的目录结构和文件如下图。
在这里插入图片描述

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 中断中调用了本函数。

中断函数

道歉这个部分忘记加了。节后补上。

回调函数

由于 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 命令测试

在这里插入图片描述
注意上面代码中

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

06 命令测试

在这里插入图片描述

03 命令测试

其实代码收到 03 命令就直接返回了。但是从协议的角度。报文时正常的。
在这里插入图片描述


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/32012.html

函数  串口  中断  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1