STM32 MODBUS-RTU主从站库移植

代码地址

STM32MODBUSRTU: stm32上的modbus工程

从站

FreeModbus是一个开源的Modbus通信协议栈实现。它允许开发者在各种平台上轻松地实现Modbus通信功能,包括串口和以太网。FreeMODBUS提供了用于从设备和主站通信的功能,支持Modbus RTU和Modbus TCP协议。在工业控制和自动化领域广泛应用。

FreeModBus可通过官方网站下载:FreeMODBUS

参考文章:FreeModbus RTU 从机Hal库裸机移植避坑指南 - Atul-8 - 博客园

1. STM32CubeMX 配置流程

我假设你已经学会使用stm32cubeMX点灯了;

1.1下载模式配置

1.2 定时器配置

为了实现一个1750us的超时计时定时器,其中计算过程如下,使用内部时钟8M,预分配399+1,及400/8m=50us,计数周期为34+1,也就是35*50us=1750us

1.3 串口配置

1.4 中断配置

关闭自动生成中断函数,因为需要在freemodbus源码里添加这两个函数。

2.库文件导入

2.1 .c文件汇总

2.2 .h文件汇总

2.3 demo文件选择

3. 移植流程

ok 完成上述步骤后, 你就可以开始正式的移植工作了:

主要需要移植的地方为: portserial.c && porttimer.c && demo.c

E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\modbus\include\mbconfig.h

文件中将ASCII关闭

E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\modbus\rtu\mbrtu.c

文件中有两处断言需要调整,我不清楚为什么,但是我的不修改会进入断言

E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\modbus\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"
#include "main.h"
#include "usart.h"
/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );

extern UART_HandleTypeDef huart1;

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

    if (xTxEnable) {
        __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
    } else {
        __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
    }
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    MX_USART1_UART_Init();
	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. */
    USART1->DR = ucByte;
    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 = (USART1->DR & (uint16_t)0x00FF);
    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.
 */
static 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.
 */
static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

void USART1_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);	
        prvvUARTRxISR();
        // __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
    }

    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))
    {
        __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);			
        prvvUARTTxReadyISR();
    }
//    HAL_UART_IRQHandler(&huart1);
}

E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\modbus\port\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"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "main.h"
/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );

extern TIM_HandleTypeDef htim4;
/* ----------------------- Start implementation -----------------------------*/

static void MX_TIM4_Init(void)
{
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    htim4.Instance = TIM4;
    htim4.Init.Prescaler = 399;
    htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim4.Init.Period = 34;
    htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim4) != HAL_OK) {
        Error_Handler();
    }
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK) {
        Error_Handler();
    }
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK) {
        Error_Handler();
    }
}

BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    MX_TIM4_Init();
	__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);      
    __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);					
    return TRUE;
}


inline void
vMBPortTimersEnable(  )
{
    /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
    __HAL_TIM_SET_COUNTER(&htim4, 0);
    __HAL_TIM_ENABLE(&htim4);
}

inline void
vMBPortTimersDisable(  )
{
    /* Disable any pending timers. */
    __HAL_TIM_DISABLE(&htim4);
}

/* 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.
 */
static void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

void TIM4_IRQHandler(void)
{
    if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE))
    {
        __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);
        prvvTIMERExpiredISR();
    }
}

E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\modbus\demo.c

代码如下:这里是主要modbus栈需要维护的数据

复制代码
/*
 * FreeModbus Libary: BARE Demo Application
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

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

/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4

/* ----------------------- Static variables ---------------------------------*/
static USHORT   usRegInputStart = REG_INPUT_START;
static USHORT   usRegInputBuf[REG_INPUT_NREGS];

// 十路输入寄存器
#define REG_INPUT_SIZE  10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];


// 十路保持寄存器
#define REG_HOLD_SIZE   10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];


// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};


// 十路离散量
#define REG_DISC_SIZE  10
uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};

/* ----------------------- Start implementation -----------------------------*/

/// CMD4命令处理回调函数
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    USHORT usRegIndex = usAddress - 1;

    // 非法检测
    if ((usRegIndex + usNRegs) > REG_INPUT_SIZE) {
        return MB_ENOREG;
    }

    // 循环读取
    while (usNRegs > 0) {
        *pucRegBuffer++ = (unsigned char)(REG_INPUT_BUF[usRegIndex] >> 8);
        *pucRegBuffer++ = (unsigned char)(REG_INPUT_BUF[usRegIndex] & 0xFF);
        usRegIndex++;
        usNRegs--;
    }

    // 模拟输入寄存器被改变
    for (usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++) {
        REG_INPUT_BUF[usRegIndex]++;
    }

    return MB_ENOERR;
}

/// CMD6、3、16命令处理回调函数
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
    USHORT usRegIndex = usAddress - 1;

    // 非法检测
    if ((usRegIndex + usNRegs) > REG_HOLD_SIZE) {
        return MB_ENOREG;
    }

    // 写寄存器
    if (eMode == MB_REG_WRITE) {
        while (usNRegs > 0) {
            REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1];
            pucRegBuffer += 2;
            usRegIndex++;
            usNRegs--;
        }
    }

    // 读寄存器
    else {
        while (usNRegs > 0) {
            *pucRegBuffer++ = (unsigned char)(REG_HOLD_BUF[usRegIndex] >> 8);
            *pucRegBuffer++ = (unsigned char)(REG_HOLD_BUF[usRegIndex] & 0xFF);
            usRegIndex++;
            usNRegs--;
        }
    }

    return MB_ENOERR;
}

/// CMD1、5、15命令处理回调函数
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    USHORT usRegIndex = usAddress - 1;
    UCHAR ucBits = 0;
    UCHAR ucState = 0;
    UCHAR ucLoops = 0;

    // 非法检测
    if ((usRegIndex + usNCoils) > REG_COILS_SIZE) {
        return MB_ENOREG;
    }

    if (eMode == MB_REG_WRITE) {
        ucLoops = (usNCoils - 1) / 8 + 1;
        while (ucLoops != 0) {
            ucState = *pucRegBuffer++;
            ucBits = 0;
            while (usNCoils != 0 && ucBits < 8) {
                REG_COILS_BUF[usRegIndex++] = (ucState >> ucBits) & 0X01;
                usNCoils--;
                ucBits++;
            }
            ucLoops--;
        }
    } else {
        ucLoops = (usNCoils - 1) / 8 + 1;
        while (ucLoops != 0) {
            ucState = 0;
            ucBits = 0;
            while (usNCoils != 0 && ucBits < 8) {
                if (REG_COILS_BUF[usRegIndex]) {
                    ucState |= (1 << ucBits);
                }
                usNCoils--;
                usRegIndex++;
                ucBits++;
            }
            *pucRegBuffer++ = ucState;
            ucLoops--;
        }
    }

    return MB_ENOERR;
}

/// CMD2命令处理回调函数
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    USHORT usRegIndex = usAddress - 1;
    UCHAR ucBits = 0;
    UCHAR ucState = 0;
    UCHAR ucLoops = 0;

    // 非法检测
    if ((usRegIndex + usNDiscrete) > REG_DISC_SIZE) {
        return MB_ENOREG;
    }

    ucLoops = (usNDiscrete - 1) / 8 + 1;
    while (ucLoops != 0) {
        ucState = 0;
        ucBits = 0;
        while (usNDiscrete != 0 && ucBits < 8) {
            if (REG_DISC_BUF[usRegIndex]) {
                ucState |= (1 << ucBits);
            }
            usNDiscrete--;
            usRegIndex++;
            ucBits++;
        }
        *pucRegBuffer++ = ucState;
        ucLoops--;
    }

    // 模拟离散量输入被改变
    for (usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++) {
        REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex];
    }

    return MB_ENOERR;
}

E:\STM32CubeIDE\Workspace\FreeMODBUSRTUSlave\Core\Src\freertos.c

如下修改:

复制代码
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
/* USER CODE END Includes */

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
  eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE); 
	eMBEnable();
  for(;;)
  {
    osDelay(1);
    eMBPoll();
  }
  /* USER CODE END StartDefaultTask */
}

4. 运行

好像带连续读取保护,这个我还没看,连续读取会失败

答:由于主频过低,导致使用中断的方式接收数据时,让处理时间稍微耗时,导致下一次数据露采,所以会有通讯失败的情况。

解决办法是提高主频,或者降低波特率。

主站

开源一套MODBUS主机代码(带讲解分析) -- 电子创客营

源码地址:https://github.com/Derrick45/modbus-host

1. STM32CubeMX 配置流程

1.1配置系统模式

1.2配置定时器3

这个需要和port中代码保持一致

因为系统主频配置为64MHz

1.3配置串口1

1.4打开串口1和tim3中断

1.5 取消默认生成中断函数

因为后面代码需要自己实现

1.6 freeRTOS创建两个任务

一个默认任务,处理poll函数,一个任务做发送

1.7 配置系统时钟为64M

1.8 生成独立文件

2. 库文件导入

把源码文件放到modbus文件夹中

环境配置

3.移植流程

MODBUSRTUMaster\Core\Src\freertos.c

添加头文件

任务逻辑实现,默认任务处理poll

发送任务每一秒钟发送一次测试数据

4. 运行

相关推荐
电鱼智能的电小鱼1 小时前
基于电鱼 AI 工控机的智慧工地视频智能分析方案——边缘端AI检测,实现无人值守下的实时安全预警
网络·人工智能·嵌入式硬件·算法·安全·音视频
电院工程师6 小时前
SIMON64/128算法Verilog流水线实现(附Python实现)
python·嵌入式硬件·算法·密码学
Shang180989357267 小时前
T41LQ 一款高性能、低功耗的系统级芯片(SoC) 适用于各种AIoT应用智能安防、智能家居方案优选T41L
人工智能·驱动开发·嵌入式硬件·fpga开发·信息与通信·信号处理·t41lq
BreezeJuvenile7 小时前
外设模块学习(8)——HC-SR04超声波模块(STM32)
stm32·单片机·嵌入式硬件·学习·超声波测距模块·hc-sr04
安庆平.Я10 小时前
STM32——定时器
stm32·单片机·嵌入式硬件·定时器
ACP广源盛1392462567310 小时前
(ACP广源盛)GSV2231---DisplayPort 1.4 MST 到 HDMI 2.0/DP/Type-C 转换器(带嵌入式 MCU)
c语言·开发语言·单片机·嵌入式硬件·音视频·mst
电鱼智能的电小鱼11 小时前
基于电鱼 ARM 边缘网关的智慧工地数据可靠传输方案——断点续传 + 4G/5G冗余通信,保障数据完整上传
arm开发·人工智能·嵌入式硬件·深度学习·5g·机器学习
范纹杉想快点毕业11 小时前
12个月嵌入式进阶计划ZYNQ 系列芯片嵌入式与硬件系统知识学习全计划(基于国内视频资源)
c语言·arm开发·单片机·嵌入式硬件·学习·fpga开发·音视频
NEU-UUN12 小时前
1.2.STM32简介——全程手敲板书
stm32·单片机·嵌入式硬件
三佛科技-1341638421212 小时前
全自动削皮机方案,果蔬去皮机/削皮机MCU控制方案开发设计
单片机·嵌入式硬件