FreeModbus프로토콜栈深度분석与STM32移植完整튜토리얼:RTU/TCP슬레이브개발 回调函数与源码详解

무료무료 기술 자료

이 글은 바로 읽을 수 있으며 기초 학습과 검색 유입에 적합합니다.

FreeModbus是一个轻量级、可移植的오픈소스Modbus프로토콜栈,专为嵌入式系统设计,支持Modbus RTU、ASCII和TCP三种프로토콜,可运行在STM32、AVR、MSP430、ESP32等多种MCU平台。本文从프로토콜栈架构、源码분석、移植步骤、回调函数、기능 코드实现到完整的STM32移植实例,深度분석FreeModbus的工作원리和使用方法。

一、FreeModbus개요

1.1 什么是FreeModbus

FreeModbus是由Christian Walter개발的오픈소스Modbus프로토콜栈,采用BSD许可证,可무료用于商业项目。它实现了Modbus슬레이브(Slave)프로토콜栈,支持RTU、ASCII和TCP三种传输방식,代码量小(约5000行),자료占用低(RAM约2KB,Flash约10KB),非常适合자료受限的嵌入式设备。

1.2 核心特性

  • 多프로토콜支持:Modbus RTU、Modbus ASCII、Modbus TCP可单独或组合使用
  • 完整기능 코드:支持01/02/03/04/05/06/07/08/11/12/15/16/17/22/23/24/43等기능 코드
  • 高度可移植:平台相关代码集中在port目录,移植只需实现几个函数
  • 자료占用低:最小설정RAM约512바이트,Flash约8KB
  • 非阻塞设计:基于状态机,不占用CPU대기,可在RTOS或裸机运行
  • 可설정:通过mbconfig.h宏开关裁剪功能,按需编译
  • BSD오픈소스:商业무료,无GPL传染性
  • 成熟稳定:2006年首发,经过多年工业验证

1.3 支持的기능 코드

기능 코드名称源文件설명
01读코일mbfunccoils.c读多个코일 상태
02读이산 입력mbfunccoils.c读多个이산 입력状态
03读유지 레지스터mbfuncholding.c读多个유지 레지스터
04读입력 레지스터mbfuncinput.c读多个입력 레지스터
05写单个코일mbfunccoils.c写一个코일
06写单个레지스터mbfuncholding.c写一个유지 레지스터
07读异常状态mbfuncother.c读8个异常코일
08诊断mbfuncother.c回送测试等
11获取事件计数器mbfuncother.c통신事件计数
12获取事件로그mbfuncother.c통신事件로그
15写多个코일mbfunccoils.c批量写코일
16写多个레지스터mbfuncholding.c批量写유지 레지스터
17보고서슬레이브IDmbfuncother.c设备标识
22屏蔽写레지스터mbfuncholding.c掩码修改레지스터
23读写多个레지스터mbfuncholding.c同时读写

二、프로토콜栈架构与源码분석

2.1 目录结构

freemodbus/
├── modbus/                     # 프로토콜栈核心(平台无关)
│   ├── include/
│   │   ├── mb.h                # 主头文件,API声明
│   │   ├── mbconfig.h          # 설정文件(功能裁剪)
│   │   ├── mbframe.h           # 帧定义
│   │   ├── mbproto.h           # 프로토콜常量
│   │   ├── mbrtu.h             # RTU模块
│   │   ├── mbascii.h           # ASCII模块
│   │   ├── mbtcp.h             # TCP模块
│   │   ├── mbfunccoils.h       # 코일기능 코드
│   │   ├── mbfuncholding.h     # 유지 레지스터기능 코드
│   │   ├── mbfuncinput.h       # 입력 레지스터기능 코드
│   │   └── mbfuncother.h       # 其他기능 코드
│   ├── mb.c                    # 프로토콜栈主逻辑(状态机)
│   ├── mbrtu.c                 # RTU收发处理
│   ├── mbascii.c               # ASCII收发处理
│   ├── mbtcp.c                 # TCP收发处理
│   ├── mbfunccoils.c           # 기능 코드01/02/05/15
│   ├── mbfuncholding.c         # 기능 코드03/06/16/22/23
│   ├── mbfuncinput.c           # 기능 코드04
│   └── mbfuncother.c           # 기능 코드07/08/11/12/17
├── port/                       # 平台相关代码(需要移植)
│   ├── port.h                  # 平台인터페이스声明
│   ├── port.c                  # 平台通用函数
│   ├── portserial.c            # 시리얼 포트操作(RTU/ASCII)
│   ├── porttimer.c             # 定时器操作(RTU超时)
│   └── portevent.c             # 事件队列
└── demo/                       # 예제代码
    ├── AVR/                    # AVR예제
    ├── STR71x/                 # STR71x예제
    ├── LPC21xx/                # LPC21xx예제
    └── WIN32/                  # Windows模拟예제

2.2 核心状态机

// mb.c - 프로토콜栈主状态机
typedef enum {
    STATE_ENABLED,      // 프로토콜栈已활성화,대기수신
    STATE_DISABLED,     // 프로토콜栈已禁用
    STATE_NOT_INITIALIZED  // 未初始化
} eMBState;

// 수신状态
typedef enum {
    STATE_RX_IDLE,      // 空闲,대기新帧
    STATE_RX_RCV,       // 正在수신
    STATE_RX_WAIT,      // RTU대기超时判断
    STATE_RX_ERROR      // 수신错误
} eMBRcvState;

// 보내기状态
typedef enum {
    STATE_TX_IDLE,      // 空闲
    STATE_TX_XMIT       // 正在보내기
} eMBSndState;

// 主循环函数(必须周期性调用)
eMB오답Code eMBPoll(void) {
    // 1. 检查是否有事件(收到전체 프레임)
    if (xMBPortEventGet(&eEvent)) {
        switch (eEvent) {
            case EV_READY:
                // 프로토콜栈就绪
                break;
            case EV_FRAME_RECEIVED:
                // 收到전체 프레임,处理
                eMBProcessRxFrame();
                break;
            case EV_EXECUTE:
                // 执行기능 코드回调
                eMBExecute기능();
                break;
            case EV_FRAME_SENT:
                // 帧보내기完成
                break;
        }
    }
    return MB_ENOERR;
}

2.3 RTU帧处理流程

// mbrtu.c - RTU수신流程
// 1. 시리얼 포트中断:每收到一个바이트,调用prvvMBRTUReceive()
//    - 重置3.5문자超时定时器
//    - 将바이트存入수신缓冲区
//    - 状态:STATE_RX_RCV
//
// 2. 定时器中断:3.5문자时间到(RTU帧종료标志)
//    - 调用prvvMBRTUTimerT35Expired()
//    - 如果收到了데이터,触发EV_FRAME_RECEIVED事件
//    - 状态:STATE_RX_WAIT → 通知主循环
//
// 3. 主循环eMBPoll()收到EV_FRAME_RECEIVED
//    - 验证CRC16체크섬
//    - 检查슬레이브 주소是否일치
//    - 查找기능 코드处理函数
//    - 调用기능 코드回调(읽기/쓰기적용데이터)
//    - 构建响应帧
//    - 触发EV_EXECUTE → 보내기响应
//
// 4. 보내기响应
//    - 계산CRC16
//    - 시리얼 포트逐바이트보내기
//    - 보내기完成触发EV_FRAME_SENT

三、설정文件mbconfig.h详解

// mbconfig.h - FreeModbus설정文件
// 通过宏开关裁剪功能,最小化자료占用

// ====== 프로토콜选择 ======
#define MB_RTU_ENABLED                  1   // 활성화RTU
#define MB_ASCII_ENABLED                0   // 禁用ASCII
#define MB_TCP_ENABLED                  0   // 禁用TCP

// ====== 기능 코드选择 ======
#define MB_FUNC_READ_COILS_ENABLED      1   // 기능 코드01
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED  1  // 기능 코드02
#define MB_FUNC_READ_HOLDING_ENABLED    1   // 기능 코드03
#define MB_FUNC_READ_INPUT_ENABLED      1   // 기능 코드04
#define MB_FUNC_WRITE_COIL_ENABLED      1   // 기능 코드05
#define MB_FUNC_WRITE_SINGLE_ENABLED    1   // 기능 코드06
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED  1  // 기능 코드15
#define MB_FUNC_WRITE_MULTIPLE_ENABLED  1   // 기능 코드16
#define MB_FUNC_READWRITE_MULTIPLE_ENABLED  0  // 기능 코드23(禁用)
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED  1  // 기능 코드17
#define MB_FUNC_OTHER_REPORT_SLAVEID    1   // 보고서슬레이브ID

// ====== 高级功能 ======
#define MB_FUNC_HANDLERS_MAX            16  // 最大기능 코드处理函数数
#define MB_ASCII_TIMEOUT_SEC            1   // ASCII超时(秒)
#define MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS  0  // ASCII보내기前대기
#define MB_TCP_PORT_USE_DEFAULT         502 // TCP默认포트

// ====== 디버깅 ======
#define MB_DEBUG_LEVEL                  0   // 디버깅级别 0=닫기

// ====== 缓冲区大小 ======
#define MB_PDU_SIZE_MAX                 253 // PDU最大길이
#define MB_SER_PDU_SIZE_MAX             256 // 시리얼 포트PDU最大길이

四、移植详解

4.1 需要实现的平台函数

FreeModbus的移植非常简单,只需在port目录中实现以下平台相关函数:

函数文件功能
xMBPortSerialInitportserial.c初始化시리얼 포트(통신 속도、데이터 비트、패리티)
vMBPortSerial닫기portserial.c닫기시리얼 포트
vMBPortSerialEnableportserial.c使能/禁用收发中断
xMBPortSerialPutByteportserial.c보내기一个바이트
xMBPortSerialGetByteportserial.c읽기一个바이트
xMBPortTimersInitporttimer.c初始化定时器(3.5문자超时)
vMBPortTimers닫기porttimer.c닫기定时器
vMBPortTimersEnableporttimer.c启动定时器
vMBPortTimersDisableporttimer.c정지定时器
xMBPortEventInitportevent.c初始化事件队列
xMBPortEventPostportevent.c投递事件
xMBPortEventGetportevent.c获取事件
vMBPort닫기port.c닫기所有포트자료
xMBPort닫기port.c닫기포트

4.2 STM32시리얼 포트移植(portserial.c)

// portserial.c - STM32시리얼 포트移植实现
#include "port.h"
#include "mb.h"
#include "mbport.h"
#include "stm32f1xx_hal.h"

extern UART_HandleTypeDef huart1;  // 使用USART1
static uint8_t rx_byte;            // 수신바이트缓冲

// 初始化시리얼 포트
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate,
                       UCHAR uc데이터Bits, eMBParity eParity) {
    // 설정시리얼 포트 매개변수
    huart1.Instance = USART1;
    huart1.Init.BaudRate = ulBaudRate;
    huart1.Init.WordLength = (uc데이터Bits == 8) ? UART_WORDLENGTH_8B : UART_WORDLENGTH_9B;

    // 패리티(Modbus RTU: 8N1/8E1/8O1)
    switch (eParity) {
        case MB_PAR_NONE:
            huart1.Init.Parity = UART_PARITY_NONE;
            break;
        case MB_PAR_ODD:
            huart1.Init.Parity = UART_PARITY_ODD;
            break;
        case MB_PAR_EVEN:
            huart1.Init.Parity = UART_PARITY_EVEN;
            break;
    }
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;

    HAL_UART_Init(&huart1);

    // 使能수신中断(空闲+수신)
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);

    return TRUE;
}

// 使能收发(xRxEnable=TRUE使能수신,xTxEnable=TRUE使能보내기)
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) {
    if (xRxEnable) {
        // 使能수신中断,开始수신
        HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
    } 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 xMBPortSerialPutByte(CHAR ucByte) {
    huart1.Instance->DR = (uint8_t)ucByte;  // 直接写데이터레지스터
    return TRUE;
}

// 읽기一个바이트(由수신中断调用)
BOOL xMBPortSerialGetByte(CHAR *pucByte) {
    *pucByte = rx_byte;
    return TRUE;
}

// USART1中断服务函数
void USART1_IRQHandler(void) {
    // 수신中断
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) {
        HAL_UART_Receive_IT(&huart1, &rx_byte, 1);
        pxMBFrameCBByteReceived();  // 通知프로토콜栈收到바이트
    }
    // 보내기空中断
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE)) {
        pxMBFrameCBTransmitterEmpty();  // 通知프로토콜栈可以보내기下一个바이트
    }
}

4.3 STM32定时器移植(porttimer.c)

// porttimer.c - STM32定时器移植(RTU 3.5문자超时)
#include "port.h"
#include "mb.h"
#include "mbport.h"
#include "stm32f1xx_hal.h"

extern TIM_HandleTypeDef htim2;  // 使用TIM2

// 初始化定时器
// usTim1Timerout50us = 超时时间,单位50微秒
// RTU 3.5문자时间 = 3.5 * (1시작+8데이터+체크섬+1정지) / 통신 속도
// 例如9600통신 속도8N1:3.5*11/9600 = 4.01ms = 80 * 50us
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us) {
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 3600 - 1;     // 72MHz/3600 = 20kHz = 50us
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = usTim1Timerout50us - 1;  // 超时周期
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);

    return TRUE;
}

// 启动定时器
void vMBPortTimersEnable(void) {
    __HAL_TIM_SET_COUNTER(&htim2, 0);    // 清零计数器
    HAL_TIM_Base_Start_IT(&htim2);       // 启动定时器中断
}

// 정지定时器
void vMBPortTimersDisable(void) {
    HAL_TIM_Base_Stop_IT(&htim2);
}

// 定时器中断回调
void HAL_TIM_PeriodElapsed호명back(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        pxMBPortCBTimerExpired();  // 通知프로토콜栈超时(RTU帧종료)
    }
}

4.4 事件队列移植(portevent.c)

// portevent.c - 事件队列移植(裸机버전)
#include "port.h"
#include "mb.h"
#include "mbport.h"

// 事件队列(简单的单事件,因为FreeModbus是顺序处理的)
static eMBEventType eQueuedEvent;
static BOOL xEventInQueue = FALSE;

// 初始化事件队列
BOOL xMBPortEventInit(void) {
    xEventInQueue = FALSE;
    return TRUE;
}

// 投递事件(在中断中调用)
BOOL xMBPortEventPost(eMBEventType eEvent) {
    eQueuedEvent = eEvent;
    xEventInQueue = TRUE;
    return TRUE;
}

// 获取事件(在主循环中调用)
BOOL xMBPortEventGet(eMBEventType *eEvent) {
    BOOL xEventHappened = FALSE;
    if (xEventInQueue) {
        *eEvent = eQueuedEvent;
        xEventInQueue = FALSE;
        xEventHappened = TRUE;
    }
    return xEventHappened;
}

// 如果使用RTOS,可以用队列替代:
// static QueueHandle_t xEventQueue;
// BOOL xMBPortEventInit(void) {
//     xEventQueue = xQueueCreate(1, sizeof(eMBEventType));
//     return TRUE;
// }
// BOOL xMBPortEventPost(eMBEventType eEvent) {
//     xQueueSendFromISR(xEventQueue, &eEvent, NULL);
//     return TRUE;
// }
// BOOL xMBPortEventGet(eMBEventType *eEvent) {
//     return xQueueReceive(xEventQueue, eEvent, 0);
// }

五、적용层回调函数

FreeModbus通过回调函数与적용层交互,적용程序需要实现四个回调函数来提供데이터存储。

// app_mb.c - 적용层回调函数实现
#include "mb.h"
#include "mbport.h"

// ====== 적용데이터存储 ======
#define REG_INPUT_START   1000    // 입력 레지스터시작주소
#define REG_INPUT_NREGS   4       // 입력레지스터 수량
#define REG_HOLDING_START 2000    // 유지 레지스터시작주소
#define REG_HOLDING_NREGS 10      // 保持레지스터 수량
#define REG_COILS_START   1       // 코일시작주소
#define REG_COILS_NCOILS  16      // 코일 수량
#define REG_DISCRETE_START 1      // 이산 입력시작주소
#define REG_DISCRETE_NCOILS 8     // 이산 입력数量

static USHORT usRegInputBuf[REG_INPUT_NREGS];      // 입력 레지스터(只读)
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];  // 유지 레지스터(读写)
static UCHAR  ucRegCoilsBuf[REG_COILS_NCOILS / 8 + 1];  // 코일(读写)
static UCHAR  ucRegDiscreteBuf[REG_DISCRETE_NCOILS / 8 + 1];  // 이산 입력(只读)

// ====== 回调1:读입력 레지스터(기능 코드04) ======
eMB오답Code eMBRegInputCB(UCHAR *pucRegBuffer, USHORT us주소,
                           USHORT usNRegs) {
    eMB오답Code eStatus = MB_ENOERR;
    int iRegIndex;

    // 检查주소范围
    if ((us주소 >= REG_INPUT_START) &&
        (us주소 + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS)) {
        iRegIndex = (int)(us주소 - REG_INPUT_START);
        // 복사데이터到响应缓冲区(大端모드,高바이트在前)
        while (usNRegs > 0) {
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] >> 8);
            *pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] & 0xFF);
            iRegIndex++;
            usNRegs--;
        }
    } else {
        eStatus = MB_ENOREG;  // 주소不存在,返回异常码02
    }
    return eStatus;
}

// ====== 回调2:读/写유지 레지스터(기능 코드03/06/16/22/23) ======
eMB오답Code eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT us주소,
                             USHORT usNRegs, eMB회원가입Mode eMode) {
    eMB오답Code eStatus = MB_ENOERR;
    int iRegIndex;

    if ((us주소 >= REG_HOLDING_START) &&
        (us주소 + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS)) {
        iRegIndex = (int)(us주소 - REG_HOLDING_START);

        switch (eMode) {
            case MB_REG_READ:  // 读操作(기능 코드03)
                while (usNRegs > 0) {
                    *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);
                    *pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);
                    iRegIndex++;
                    usNRegs--;
                }
                break;

            case MB_REG_WRITE:  // 写操作(기능 코드06/16)
                while (usNRegs > 0) {
                    usRegHoldingBuf[iRegIndex] =
                        ((USHORT)*pucRegBuffer++ << 8) | *pucRegBuffer++;
                    iRegIndex++;
                    usNRegs--;
                }
                // 쓰기后可以触发적용动作
                // 例如:레지스터2000是设定值,쓰기后更新PWM출력
                break;
        }
    } else {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

// ====== 回调3:读/写코일(기능 코드01/05/15) ======
eMB오답Code eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT us주소,
                           USHORT usNCoils, eMB회원가입Mode eMode) {
    eMB오답Code eStatus = MB_ENOERR;
    int iBitIndex;

    if ((us주소 >= REG_COILS_START) &&
        (us주소 + usNCoils <= REG_COILS_START + REG_COILS_NCOILS)) {
        iBitIndex = (int)(us주소 - REG_COILS_START);

        if (eMode == MB_REG_READ) {
            // 读코일:打包成바이트(每8个코일一个바이트)
            while (usNCoils > 0) {
                UCHAR ucResult = 0;
                for (int i = 0; i < 8 && usNCoils > 0; i++) {
                    if (ucRegCoilsBuf[iBitIndex / 8] & (1 << (iBitIndex % 8))) {
                        ucResult |= (1 << i);
                    }
                    iBitIndex++;
                    usNCoils--;
                }
                *pucRegBuffer++ = ucResult;
            }
        } else {
            // 写코일
            while (usNCoils > 0) {
                UCHAR ucBits = *pucRegBuffer++;
                for (int i = 0; i < 8 && usNCoils > 0; i++) {
                    if (ucBits & (1 << i)) {
                        ucRegCoilsBuf[iBitIndex / 8] |= (1 << (iBitIndex % 8));
                    } else {
                        ucRegCoilsBuf[iBitIndex / 8] &= ~(1 << (iBitIndex % 8));
                    }
                    iBitIndex++;
                    usNCoils--;
                }
            }
        }
    } else {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

// ====== 回调4:读이산 입력(기능 코드02) ======
eMB오답Code eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT us주소,
                              USHORT usNDiscrete) {
    // 与读코일类似,但只能读不能写
    eMB오답Code eStatus = MB_ENOERR;
    int iBitIndex;

    if ((us주소 >= REG_DISCRETE_START) &&
        (us주소 + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_NCOILS)) {
        iBitIndex = (int)(us주소 - REG_DISCRETE_START);
        while (usNDiscrete > 0) {
            UCHAR ucResult = 0;
            for (int i = 0; i < 8 && usNDiscrete > 0; i++) {
                if (ucRegDiscreteBuf[iBitIndex / 8] & (1 << (iBitIndex % 8))) {
                    ucResult |= (1 << i);
                }
                iBitIndex++;
                usNDiscrete--;
            }
            *pucRegBuffer++ = ucResult;
        }
    } else {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

六、完整的STM32主程序

// main.c - FreeModbus STM32完整主程序
#include "stm32f1xx_hal.h"
#include "mb.h"
#include "mbport.h"

// 外部변수(적용데이터)
extern USHORT usRegInputBuf[];
extern USHORT usRegHoldingBuf[];
extern UCHAR ucRegCoilsBuf[];
extern UCHAR ucRegDiscreteBuf[];

// 函数声明
void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_USART1_UART_Init(void);
void MX_TIM2_Init(void);
void UpdateApplication데이터(void);

int main(void) {
    // HAL库初始化
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    // ====== 初始化FreeModbus RTU슬레이브 ======
    // 매개변수:슬레이브 주소=1, 시리얼 포트=0, 통신 속도=9600, 체크섬=패리티 없음
    eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE);

    // 활성화프로토콜栈
    eMBEnable();

    printf("FreeModbus RTU슬레이브已启动\r\n");
    printf("슬레이브 주소: 1, 통신 속도: 9600, 형식: 8N1\r\n");

    // 主循环
    while (1) {
        // ====== 必须周期性调用eMBPoll() ======
        // 这是FreeModbus的核心,处理所有프로토콜事件
        eMBPoll();

        // ====== 적용逻辑:更新센서데이터 ======
        UpdateApplication데이터();

        // 其他적용任务...
        HAL_Delay(10);
    }
}

// 更新적용데이터(模拟센서采集)
void UpdateApplication데이터(void) {
    static uint32_t last_update = 0;
    uint32_t now = HAL_GetTick();

    // 每100ms更新一次
    if (now - last_update >= 100) {
        last_update = now;

        // 입력 레지스터(只读):模拟센서
        usRegInputBuf[0] = 250 + (now / 100) % 10;  // 온도 25.0-25.9°C
        usRegInputBuf[1] = 650 + (now / 150) % 20;  // 湿度 65.0-66.9%
        usRegInputBuf[2] = 2200;                     // 电压 220.0V
        usRegInputBuf[3] = 135;                      // 电流 1.35A

        // 이산 입력:模拟开关状态
        ucRegDiscreteBuf[0] = (now / 500) & 0x01;  // 每秒翻转
    }

    // 유지 레지스터的쓰기由Modbus마스터触发
    // 적용可以根据유지 레지스터的值执行动作
    // 例如:usRegHoldingBuf[0]是PWM设定值
    // __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, usRegHoldingBuf[0]);
}

// 시리얼 포트보내기printf重定向(디버깅用)
int fputc(int ch, FILE *f) {
    HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 100);
    return ch;
}

七、Modbus TCP移植要点

如果需要支持Modbus TCP,除了시리얼 포트和定时器移植外,还需要实现TCP포트层。

// porttcp.c - Modbus TCP포트层(使用lwIP)
#include "port.h"
#include "mb.h"
#include "mbport.h"
#include "lwip/tcp.h"

static struct tcp_pcb *pxListenPCB = NULL;
static struct tcp_pcb *pxClientPCB = NULL;
static UCHAR pucTCPBuffer[MB_TCP_PDU_SIZE_MAX];

// 初始化TCP监听
BOOL xMBTCPPortInit(USHORT usTCPPort) {
    pxListenPCB = tcp_new();
    if (pxListenPCB) {
        tcp_bind(pxListenPCB, IP_ADDR_ANY, usTCPPort);
        pxListenPCB = tcp_listen(pxListenPCB);
        tcp_accept(pxListenPCB, prvxMBTCPAccept);
        return TRUE;
    }
    return FALSE;
}

// 接受新연결
static err_t prvxMBTCPAccept(void *arg, struct tcp_pcb *pcb, err_t err) {
    if (pxClientPCB == NULL) {
        pxClientPCB = pcb;
        tcp_recv(pcb, prvxMBTCPReceive);
        tcp_err(pcb, prvxMBTCP오답);
    } else {
        tcp_close(pcb);  // 只允许一个연결
    }
    return ERR_OK;
}

// 수신데이터
static err_t prvxMBTCPReceive(void *arg, struct tcp_pcb *pcb,
                              struct pbuf *p, err_t err) {
    if (p) {
        // 복사데이터到缓冲区
        UCHAR *puc데이터 = p->payload;
        USHORT usLength = p->len;

        // 验证MBAP头(事务ID、프로토콜ID=0、길이、单元ID)
        if (usLength >= 7 && puc데이터[2] == 0 && puc데이터[3] == 0) {
            // 通知프로토콜栈收到帧
            pxMBFrameCBByteReceived();  // 简化处理
        }
        tcp_recved(pcb, p->tot_len);
        pbuf_free(p);
    }
    return ERR_OK;
}

// 보내기响应
BOOL xMBTCPPortSendResponse(UCHAR *pucMBTCPFrame, USHORT usLength) {
    if (pxClientPCB) {
        tcp_write(pxClientPCB, pucMBTCPFrame, usLength, TCP_WRITE_FLAG_COPY);
        tcp_output(pxClientPCB);
        return TRUE;
    }
    return FALSE;
}

八、常见문제与排查

문제原因解决方法
마스터无响应eMBPoll()未调用或调用不及时确保主循环中频繁调用eMBPoll(),不要长时间阻塞
RTU통신不稳定3.5문자超时定时器不准检查定时器时钟和预分频,确保50us精度
CRC 검증错误통신 속도误差大或시리얼 포트 설정错核对통신 속도/데이터 비트/패리티,使用外部晶振
주소不일치슬레이브 주소设置错误eMBInit()的第二个매개변수是슬레이브 주소
读레지스터返回异常码02주소超出回调函数范围检查回调函数中的주소范围判断
多슬레이브冲突总线上슬레이브 주소重复确保每个슬레이브 주소唯一(1-247)
보내기不完整보내기中断未正确使能检查vMBPortSerialEnable()中TX中断使能
TCP연결后断开MBAP头분석错误检查프로토콜ID是否为0,길이필드是否正确
内存不足缓冲区太大减小MB_PDU_SIZE_MAX,裁剪不需要的기능 코드
ASCII모드不工作未활성화MB_ASCII_ENABLEDmbconfig.h中设置MB_ASCII_ENABLED=1

九、性能优化与자료占用

  • 裁剪기능 코드:只활성화需要的기능 코드,每个기능 코드约节省200-500바이트Flash
  • 减小PDU缓冲区:MB_PDU_SIZE_MAX默认253,如果设备레지스터少可减小
  • 单프로토콜모드:只활성화RTU或TCP,不编译不需要的프로토콜模块
  • 优化中断优先级:시리얼 포트中断优先级高于定时器,确保바이트不丢失
  • eMBPoll调用频率:建议至少每10ms调用一次,避免事件积压
  • 레지스터 매핑优化:回调函数中避免复杂계산,直接数组索引
  • 典型자료占用:最小RTU설정 Flash~8KB, RAM~512B;完整功能 Flash~15KB, RAM~2KB

十、학습자료

  • 官方웹사이트:https://www.freemodbus.org/
  • GitHub源码:https://github.com/cwalter-at/freemodbus
  • Modbus프로토콜사양:https://modbus.org/specs.php
  • STM32 HAL문서:https://www.st.com/
  • FreeMODBUS TCP扩展:https://github.com/htl-weiz/freemodbus-tcp

FreeModbus作为嵌入式领域最经典的오픈소스Modbus슬레이브프로토콜栈,以其精简的代码、清晰的架构和极低的자료占用,成为MCU设备接入Modbus总线的首选方案。本文提供的从架构분석、源码解读到STM32完整移植的详细튜토리얼,可帮助개발者快速将FreeModbus통합到自己的嵌入式项目中。关键是正确实现시리얼 포트、定时器和事件队列三个平台层,并在적용回调函数中做好레지스터주소映射。

📦

VIP专属:FreeModbus프로토콜栈STM32移植代码包

FreeModbus核心架构분석、port层完整移植、eMBInit/eMBPoll回调、레지스터读写적용예제。

开通VIP即可下载完整代码,同时解锁 30+ 엔지니어링实战자료包:디버깅스크립트、速查表、项目模板、排查案例……

前往VIP자료库下载 → 月费仅9.9元 / 年费199元
실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널