原创

基于STM32移植FreeMODBUS RTU

温馨提示:
本文最后更新于 2025年12月11日,已超过 74 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

一、FreeMODBUS

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

   包含以下文件

   

二、FreeMODBUS移植

只需要保留modbus以下内容就可以

还有demo中的port

主要修改port接口中的内容

串口的相关配置内容

/*
 * 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 "usart.h"

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

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) 
{   //控制中断的使能
    /* If xRXEnable enable serial receive interrupts. If xTxENable enable
     * transmitter empty interrupts.
     */
    if( xRxEnable )
    {   
       //  printf("xRxEnable \n");
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);
    }
    else
    {   
        __HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);
    }
    if( xTxEnable )
    {   
      //  printf("xTxEnable \n");
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_TXE);
    }
    else
    {   
        __HAL_UART_DISABLE_IT(&huart2, UART_IT_TXE);
    }

}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    // MX_USART2_UART_Init(); 在main.c中已经调用 
    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. */

    USART2->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=USART2->DR&0xFF;
    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 USART2_IRQHandler(void)
{
    // 获得接收中断标志位的时候 调用prvvUARTRxISR
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
    {   
        __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE);
         //printf("rxne \n");
         prvvUARTRxISR();
    }

    // 获得发送中断标志位的时候 调用prvvUARTTxReadyISR
    if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE))
    {
        __HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TXE);
         prvvUARTTxReadyISR();
    }
}

定时器的相关配置

/*
 * 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 "tim.h"

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

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    //  MX_TIM3_Init(); main.c已经调用
    return TRUE;
}


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

}

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

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

/**
  * @brief This function handles TIM3 global interrupt.
  */
void TIM3_IRQHandler(void)
{
    if(__HAL_TIM_GET_FLAG(&htim3,  TIM_FLAG_UPDATE))
    {
      __HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
        prvvTIMERExpiredISR();
    }
}


port.c中是关于线圈,离散寄存器,保持寄存器,输入寄存器相关操作方法的实现内容,且声明了相关寄存器的缓存区。


关闭ASCII

初始化


三、记录一下

   使用Modbus Poll通过modbus读取设备上的信息,Modbus Poll会设置slaveId所以在它日志文件中没有地址ID,设备接收到的数据是没问题的


关于收发命令的含义

接收命令 (Rx): 01 03 00 00 00 0A C5 CD

Modbus RTU 请求帧,用于读取保持寄存器:

  • 从站 ID: 01 - 目标设备地址为 1
  • 功能码: 03 - 表示读取保持寄存器(Read Holding Registers)
  • 起始地址: 00 00 - 从地址 0 开始读取(对应 Modbus 地址 40001)
  • 寄存器数量: 00 0A - 读取 10 个寄存器(0x0A = 10)
  • CRC校验: C5 CD - 循环冗余校验码

发送命令 (Tx): 01 03 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A3 67

Modbus RTU 响应帧,服务器对请求的回复:

  • 从站 ID: 01 - 响应来自设备地址 1
  • 功能码: 03 - 与请求的功能码相同,表示正常响应
  • 数据长度: 14 - 后面有 20 字节的数据(10 个寄存器 × 2 字节/寄存器)
  • 寄存器数据: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 10 个寄存器的值都是 0
  • CRC校验: A3 67 - 响应帧的校验码

数据解析

根据项目配置,这些寄存器对应以下变量:

  • 地址 40001: REG_HOLD_BUF[0]
  • 地址 40002: REG_HOLD_BUF[1]
  • ...
  • 地址 40010: REG_HOLD_BUF[9]

发送的请求是读取地址 40001 开始的 10 个保持寄存器。服务器返回了这 10 个寄存器的值,全部为 0。

通信流程

  1. 主机发送请求:01 03 00 00 00 0A C5 CD
  2. 从机接收并处理请求
  3. 从机返回响应:01 03 14 00 00 ... A3 67

如果需要修改这些寄存器的值,可以使用功能码 6(写单个寄存器)或 16(写多个寄存器)。

Modbus Poll也有其他的命令,可以自行尝试一下

正文到此结束