Free Modbusプロトコルスタックの詳細解析とSTM 32移植:RTU/TCPスレーブ開発コールバック関数とソースコードの詳細な説明

無料無料技术资料

这篇内容可直接阅读,适合用于基础学习和検索引流。

Free Modbusは、組込みシステム用に設計された軽量でポータブルなオープンソースのModbusプロトコルスタックで、Modbus RTU、ASCI I、TCPプロトコルをサポートし、STM 32、AVR、MSP 430、ESP32などのMCUプラットフォームで動作します。この記事では、プロトコルスタックアーキテクチャ、ソース解析、移植ステップ、コールバック関数、関数コードの実装から完全なSTM 32移植インスタンスまで、Free Modbusの動作原理と使用方法を深く分析します。

I. Free Modbusの概要

1.1 什么是FreeModbus

Free ModbusはChristian Walterによって開発されたオープンソースのModbusプロトコルスタックで、BSDライセンスで商用プロジェクトで自由に利用できる。Modbusスレーブ(Slave)プロトコルスタックを実現し、RTU、ASCIIとTCPの3種類の伝送方式をサポートし、コード量が小さく(約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.c8つの異常コイルを読む
08診断はmbfuncother.c返却テストなど。
11イベントカウンタの取得mbfuncother.c通信イベント数
12イベントログの取得mbfuncother.c通信イベント·ログ
15複数のコイルを書く。mbfunccoils.cバッチ書き込みコイル
16複数のレジスタを書く。mbfuncholding.cバルチ书き込みキードレジスタ
17ステーションIDからのレポートmbfuncother.cデバイスID
22マスク書き込みレジスタmbfuncholding.cマスク変更レジスタ
23複数のレジスタの読み書きmbfuncholding.c同時に読む。

II.プロトコルスタックアーキテクチャとソースコード解析

2.1ディレクトリ構造

Free modbus/
通称“Modbus”。                     #プロトコルスタックコア(プラットフォームに依存しない)
│ │ │   あなたは-インクルード
│ │ │   │ │ │   略称はMb。                #メインヘッダファイル、API宣言
│ │ │   │ │ │   mbconfig.hの略。          #設定ファイル(機能トリミング)
│ │ │   │ │ │   mb frame.hとは           #フレームの定義
│ │ │   │ │ │   Mb Proto.h。           #プロトコル定数
│ │ │   │ │ │   略称はMBRTU。             # RTUモジュール
│ │ │   │ │ │   MBASII. 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。           #機能コード0 1/0 2/0 5/15
│ │ │   ��mbfuncholding.c         #コード03/06/16/22/23
│ │ │   略称はmbfuncinput.c。           #関数コード0 4
│ │ │   ·mbfuncother.c           #機能コード0 7/0 8/11/12/17
あなたはポートです。                       #プラットフォーム関連コード(移植が必要)
│ │ │   --ポートH。                  #プラットフォームインタフェース宣言
│ │ │   略称はPORT. C。                  #プラットフォーム共通機能
│ │ │   -portserial.c。            シリアル操作(RTU/ASCII)
│ │ │   -Porttimer.c。             タイマー動作(RTUタイムアウト)
│ │ │   --portevent.c             #イベントキュー
それはデモです。                       #コード例
    略称はAVR。                    # AVRの例
    略称はSTR 71 x。                 # STR 71xの例
    略称はLPC 21 xx。                # LPC 21xxの例
    そして、Win 32。                  # 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;

// メイン·サイクル函数(必须周期性调用)
eMBErrorCode eMBPoll(void) {
    // 1. チェックありますか?事件(收到完全フレーム)
    if (xMBPortEventGet(&eEvent)) {
        switch (eEvent) {
            case EV_READY:
                // プロトコルスタック就绪
                break;
            case EV_FRAME_RECEIVED:
                // 收到完全フレーム,処理
                eMBProcessRxFrame();
                break;
            case EV_EXECUTE:
                // 执行機能コード回调
                eMBExecuteFunction();
                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

3.設定ファイル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.移植の詳細

4.1 需要実装的プラットフォーム函数

Free Modbusの移植は非常に簡単で、portディレクトリに以下のプラットフォーム依存関数を実装するだけです。

関数の機能ドキュメンテーション機能性は
xMBPortSerialInitportserial.cシリアルポート(ボーレート、データ·ビット、検証ビット)の初期化
vMBPortSerialCloseportserial.c关闭シリアルポート
vMBPortSerialEnableportserial.c送受信割込みの有効化/無効化
xMBPortSerialPutByteportserial.c1バイトを送信
xMBPortSerialGetByteportserial.c1バイトを読む
xMBPortTimersInitporttimer.cタイマーの初期化(3.5文字のタイムアウト)
vMBPortTimersCloseporttimer.cタイマーを閉じる。
vMBPortTimersEnableporttimer.cタイマーの起動。
vMBPortTimersDisableporttimer.cタイマーを止める。
xMBPortEventInitportevent.c初期化事件队列
xMBPortEventPostportevent.cイベントの配達
xMBPortEventGetportevent.cイベントの取得
vMBPortCloseport.cすべてのポートリソースをシャットダウン
xMBPortCloseport.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 ucDataBits, eMBParity eParity) {
    // 設定シリアルパラメータ
    huart1.Instance = USART1;
    huart1.Init.BaudRate = ulBaudRate;
    huart1.Init.WordLength = (ucDataBits == 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 STM 32タイマの移植(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_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        pxMBPortCBTimerExpired();  // 通知プロトコルスタックタイムアウト(RTUフレーム结束)
    }
}

4.4イベントキューの移行portevent.c

//portevent.c-イベントキューの移行ベアメタルバージョン
#include "port.h"
#include "mb.h"
#include "mbport.h"

//イベントキュー(Free Modbusは逐次処理されるため、単純な単一イベント)
static eMBEventType eQueuedEvent;
static BOOL xEventInQueue = FALSE;

イベントキューの初期化
BOOL xMBPortInit {
    xIn =;
    真のリターン。
}

//配信イベント(割り込み中に呼び出される)
BOOL xMBPortPost eMBType eEvent {
    eQueuedEvent = eEvent;
    xEventInQueue = TRUE
    真のリターン;
}

//イベントを取得する(メインループ内で呼び出される)
BOOL xMBPortGet eMBType *eEvent {
    BOOL xEventHappened = FALSE;
    if xEventInQueue {
        *eEvent = eQueuedEvent;
        xIn =;
        xEventHappened = TRUE;
    }
    return xEventHappened;
}

//RTOSを使用する場合は、キューに置き換えることができます:
// static QueueHandle_t xEventQueue;
// BOOL xMBPortInit {
//     xEventQueue = xQueueCreate 1 sizeof eMBEventType;
//     真のリターン;
// }
// BOOL xMBPortPost eMBType eEvent {
//     xQueueSendFromISR xEventQueue eEvent NULL;
//     真のリターン;
// }
// BOOL xMBPortGet eMBType *eEvent {
//     return xQueueReceive xEventQueue、eEvent、0;
// }

V.アプリケーション層コールバック関数

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) ======
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 - 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) ======
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 - 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) ======
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress,
                           USHORT usNCoils, eMBRegisterMode eMode) {
    eMBErrorCode eStatus = MB_ENOERR;
    int iBitIndex;

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

        if (eMode == MB_REG_READ) {
            // リードコイルを読む:パッキング成バイト(每81つのコイル一バイト単位。)
            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) ======
eMBErrorCode eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress,
                              USHORT usNDiscrete) {
    // 与リードコイルを読む类似,但只能读書けない。
    eMBErrorCode eStatus = MB_ENOERR;
    int iBitIndex;

    if ((usAddress >= REG_DISCRETE_START) &&
        (usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_NCOILS)) {
        iBitIndex = (int)(usAddress - 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;
}

完全なSTM 32メインプログラム

// 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 UpdateApplicationData(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スレーブ已启动rn");
    printf("駅からの住所: 1, ボーレート: 9600, 格式: 8N1rn");

    // メイン·サイクル
    while (1) {
        // ====== 必须周期性调用eMBPoll() ======
        // 这是FreeModbus的核心,処理所合意がある。事件
        eMBPoll();

        // ====== 应用逻辑:更新センサーはデータ ======
        UpdateApplicationData();

        // 其他应用任务...
        HAL_Delay(10);
    }
}

// 更新应用データ(模拟センサーは采集)
void UpdateApplicationData(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 * pixListenPCB = 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 {
        t_bpxListenPCB、IP_ADDR_ANY、usTCPPort;
        pxListenPCB = tcp_listen pxListenPCB;
        tcp_accept pxListenPCB、prvxMBTCPAccept;
        真のリターン;
    }
    FALSEを返す;
}

新しい接続を許可する
static err_t prvxMBTCPAccept void *arg struct tcp_pcb *pcb err_t err {
    if pxPCB === NULL {
        pxClientPCB = pcb
        t_recv b prMBTCPive;
        t_err b prMBTCPError;
    (エルゼ)
        tcp_close(pcb); //1つの接続のみを許可
    }
    return ERR_OK;
}

//データの受け取り
static err_t prvxMBTCPReceive void *arg struct tcp_pcb *pcb
                              strpbuf *p err_t err {
    if p {
        //データをバッファにコピー
        UCHAR *pucData = p-payload;
        USHORT us = p-;

        //MBAPヘッダの検証(トランザクションID、プロトコルID=0、長さ、セルID)
        if usLength = 7 pucData[2] == 0 pucData[3] == 0 {
            //プロトコルスタック受信フレームを通知する
            pxMBFrameCBByteReceived; //処理を簡略化
        }
        t_recved b p-tot_;
        pbuf_free p;
    }
    return ERR_OK;
}

//応答を送る
BOOL xMBTCPPortSendResponse UCHAR *pucMBTCPFrame USHORT usLength {
    if pxClientPCB {
        tcp_write pxClientPCB、pucMBTCPFrame、usLength、TCP_WRITE_FLAG_COPY;
        t_pxPCB;
        真のリターン;
    }
    FALSEを返す;
}

8.よくある質問とチェック

問題は理由は解決策は
マスター応答なし。eMBPollが呼び出されていないか、時間内に呼び出されていないeMBPollがメインループで頻繁に呼び出され、長時間ブロックされないようにする
RTU通信が不安定3.5文字タイマー不正タイマークロックとプリスケーリングを確認して50us精度を確認
CRCチェックエラーボーレート誤差が大きいか、シリアルポート構成が間違っている外部水晶振動子を使用したボーレート/データビット/検証ビットのチェック
アドレスの不一致スレーブアドレスの設定エラーeMBInitの第2引数はスレーブアドレスである
読み取りレジスタが例外コード0 2を返したアドレスがコールバック関数の範囲外ですコールバック関数でのアドレス範囲判断の確認
対立するよりもバス上のスレーブアドレスの重複确保每个駅からの住所唯一(1-247)
送信は不完全。送信割り込みが正しく有効になっていないvMBPortSerialEnableでTX割込みを有効にするかどうかを確認します
TCP接続後の切断MBAPヘッダ解析エラープロトコルIDが0で、長さフィールドが正しいことを確認します。
メモリ不足バッファ太大MB_PDU_SIZE_MAXを小さくして不要な機能コードをトリミング
ASCIIモードが動作しないMB_ASCII_ENABLEDが有効になっていませんmbconfig.hでMB_ASCII_BLED =1をする

九、性能最適化とリソース占有

  • 機能コードのトリミング:必要な機能コードのみを有効にし、各機能コードで約200-500バイトのFlashを節約します。
  • PDUバッファの縮小:MB_PDU_SIZE_MAXデフォルト253、デバイスレジスタが少ない場合に減少
  • シングルプロトコルモデルRTUまたはTCPのみを有効にし、不要なプロトコルモジュールをコンパイルしない
  • 割り込み優先順位の最適化シリアル割込み優先度はタイマーよりも高く、バイトが失われないようにします。
  • eMBPoll呼び出し頻度:イベントのバックログを回避するため、少なくとも10msごとに呼び出しを推奨
  • レジスタマップ最適化複雑な計算を避けるためのコールバック関数、直接配列インデックス
  • 一般的なリソース使用量:最小RTU設定 Flash~ 8KB、RAM~ 512B;完全機能Flash~ 15KB、RAM~2KB

X.学習資源

  • 官方网站:https://www.freemodbus.org/
  • GitHubのソースコードgithub.com/cwalter-at/freemodbus
  • Modbusプロトコル仕様modbus.org/specs.php
  • STM32 HALドキュメントwww.st.com/
  • FreeMODBUS TCP拡张github.com/htl-weiz/freemodbus-tcp

Free Modbusは、組込み分野で最も古典的なオープンソースのModbusスレーブプロトコルスタックであり、その合理化されたコード、明確なアーキテクチャ、および非常に低いリソースフットプリントを備えており、MCUデバイスがModbusバスにアクセスするための好ましいソリューションとなっています。アーキテクチャ解析からソースコード解釈、STM 32への完全な移植までの詳細なチュートリアルは、開発者がFree Modbusを組み込みプロジェクトに迅速に統合するのに役立ちます。キーは、シリアルポート、タイマー、イベントキューの3つのプラットフォーム層を正しく実装し、コールバック関数を適用するレジスタアドレスマッピングを行うことです。

CLARiX

VIP专属:FreeModbusプロトコル栈STM32コードパッケージの移植

Free Modbusコアアーキテクチャの解析、ポート層の完全な移植、eMBInit/eMBPollコールバック、レジスタの読み書きアプリケーション例。

完全なコードをダウンロードしてロックを解除 30歳以上。 工程実践パッケージの内容:スクリプトのデバッグ、クイックチェック·テーブル、项目模板、トラブルシューティング案例……

VIPデータベースからダウンロード 月額9.9ドル/年間199ドル
この資料を実際のプロジェクトに活用しませんか?

ツールセンターで電文解析、CRCチェック、デバイスデバッグを行うか、要件を送信して選定と接続のアドバイスを受けましょう。

エンジニア会員

この記事を実行可能なデバッグ資料に

登録すると、高度な電文解析、資料パックのダウンロード、コード例、エンジニアリングケース、優先技術サポートが利用可能に。実際のプロジェクト納品に最適です。

高度なツール無制限
資料パックとコードパック
完全なエンジニアリングケースライブラリ
優先技術サポート窓口