FreeModbus移植与使用
·
目录
1.摘要
FreeModbus简介:
FreeModbus是一款开源的Modbus协议栈,采用C语言编写,轻量级且跨平台,广泛用于嵌入式系统中实现Modbus通信。它支持RTU、ASCII和TCP模式,具有高可移植性,可运行于裸机或RTOS环境,适用于STM32、ARM、AVR等多种微控制器。
使用场景:
工业自动化中连接PLC与传感器;能源管理中用于智能电表数据采集;楼宇自控系统中实现设备通信;物联网终端中作为标准通信协议;以及教学科研中用于协议学习与开发实践。
2.代码移植与使用
2.1 下载官方的FreeModbus代码

2.2 代码移植
我们移植的只需要其中的modbus文件里的所有所有文件(这些文件不同修改,可以直接使用)
3.第一步:移植层 (Port) 代码实现
FreeModbus 采用分层设计,我们需要实现 port 文件夹下的三个核心文件。
新建一个port文件夹,这里存放的都是需要手写的代码
3.1事件管理 (portevent.c)
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
// enent type in queue
static eMBEventType eQueuedEvent; // 队列中的事件类型
//enent queue flag, TRUE: event in queue, FALSE: no event in queue
static BOOL xEventInQueue; // 事件队列标志
/* ----------------------- static variables ----------------------------------*/
// 事件模块初始化
BOOL xMBPortEventInit( void )
{
//enent queue init
xEventInQueue = FALSE;
return TRUE;
}
/**
// 发送事件到队列
* @param eEvent: (EV_READY, EV_FRAME_RECEIVED, EV_EXECUTE, EV_FRAME_SENT)
*/
BOOL xMBPortEventPost( eMBEventType eEvent )
{
eQueuedEvent = eEvent;
xEventInQueue = TRUE;
return TRUE;
}
/**
// 从队列获取事件
*/
BOOL xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;
//check if event in queue
if( xEventInQueue )
{
// 1. get event from queue
*eEvent = eQueuedEvent;
// 2. clear event in queue
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
3.2 串口驱动 (portserial.c)
这里包含了串口的初始化、使能以及中断回调函数的接口。
#include "mb.h"
#include "mbport.h"
#include "port.h"
#include "uart.h"
uart_handle_t UartHandle;
typedef enum
{
PORT_RS232, //0
PORT_RS485, //1
} MBPort;
/* uart enable or disable */
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if( xTxEnable ) //tx enable
{
/* Enable the USART Transmit interrupt. */
}
else //tx disable
{
/* Disable the USART Transmit interrupt. */
}
if( xRxEnable ) //rx enable
{
/* Enable the USART Receive interrupt. */
}
else //rx disable
{
/* Disable the USART Receive interrupt. */
}
}
/* uart init * /*/
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
//仅作演示,需替换为实际硬件初始化
u8 u8Ret = FALSE;
u32 u32baudrate;
u32 u32parity;
(void)ucDataBits;
u32baudrate=ulBaudRate;
u32parity=eParity;
switch(ulBaudRate)
{
case 1200:
u32baudrate = HAL_UART_BAUDRATE_1200;
break;
case 1800:
u32baudrate = HAL_UART_BAUDRATE_1800;
break;
case 2400:
u32baudrate = HAL_UART_BAUDRATE_2400;
break;
case 4800:
u32baudrate = HAL_UART_BAUDRATE_4800;
break;
case 7200:
u32baudrate = HAL_UART_BAUDRATE_7200;
break;
case 9600:
u32baudrate = HAL_UART_BAUDRATE_9600;
break;
case 14400:
u32baudrate = HAL_UART_BAUDRATE_14400;
break;
case 19200:
u32baudrate = HAL_UART_BAUDRATE_19200;
break;
case 38400:
u32baudrate = HAL_UART_BAUDRATE_38400;
break;
case 57600:
u32baudrate = HAL_UART_BAUDRATE_57600;
break;
case 115200:
u32baudrate = HAL_UART_BAUDRATE_115200;
break;
case 125000:
u32baudrate = HAL_UART_BAUDRATE_125000;
break;
case 172800:
u32baudrate = HAL_UART_BAUDRATE_172800;
break;
default:
break;
}
switch (eParity)
{
case MB_PAR_NONE:
u32parity = HAL_UART_PARITY_NONE;
break;
case MB_PAR_ODD:
u32parity = HAL_UART_PARITY_ODD;
break;
case MB_PAR_EVEN:
u32parity = HAL_UART_PARITY_EVEN;
break;
}
switch(ucPORT)
{
case PORT_RS232:
if(HalInitRS232(u32baudrate,u32parity,HAL_UART_STOPBITS_1)==HAL_ERR_NONE) u8Ret=TRUE;
break;
case PORT_RS485:
if(HalInitRS485(u32baudrate,u32parity,HAL_UART_STOPBITS_1)==HAL_ERR_NONE) u8Ret=TRUE;
break;
default:
break;
}
return u8Ret;
}
/* uart put byte 发送一个字节 */
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
UartHandle.TxBuf[UartHandle.TxIndex++] = ucByte;
huart2.Instance->DR = (uint8_t)ucByte;
return TRUE;
}
/* uart get byte 接收一个字节 */
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = UartHandle.RxBuf[UartHandle.RxIndex++];
return TRUE;
}
/* uart tx ready interrupt 串口发送中断回调 (函数名固定,由协议栈内部调用) */
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
/* uart rx interrupt 串口接收中断回调 (函数名固定)*/
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
3.3 定时器驱动 (porttimer.c)
Modbus RTU 模式依赖定时器来检测帧间隔(3.5T)。定时时间需根据波特率计算(如 9600bps 约需 3.65ms,建议设为 4ms)。
#include "api.h"
#include "port.h"
//modbus timerout = 50us * usTim1Timerout50us
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );
/* ----------------------- Start implementation -----------------------------*/
/*
timer init 定时器初始化
*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
//usTim1Timerout50us set timer period, 50us * usTim1Timerout50us
//1.timer init
//2.timer clear
//3.timer clear interrupt
return TRUE;
}
/*
timer enable 定时器使能 (开始计时)
*/
void
vMBPortTimersEnable( )
{
//1.timer clear 计数器请0
//2.timer clear interrupt 中断清除
//3.timer start //开始计时
}
/*
timer disable (停止计时)
*/
void
vMBPortTimersDisable( )
{
/* timer stop */
}
/*
* timer expired interrupt service routine 定时器中断服务函数 (函数名固定)
*/
void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
3.4 端口通用定义 (port.h)
定义基础数据类型和临界区保护(关中断)。
#ifndef _PORT_H
#define _PORT_H
#include <assert.h>
#include <inttypes.h>
#define INLINE
#define PR_BEGIN_EXTERN_C extern "C" {
#define PR_END_EXTERN_C }
#define ENTER_CRITICAL_SECTION( ) EnterCriticalSection( )
#define EXIT_CRITICAL_SECTION( ) ExitCriticalSection( )
/*回调函数,方便其他文件使用*/
void prvvUARTRxISR( void ); //UART Receive Interrupt Service Routine
void prvvUARTTxReadyISR( void ) ; //UART Send Ready Interrupt Service Routine
void prvvTIMERExpiredISR( void ); //TIMER Expired Interrupt Service Routine
typedef uint8_t BOOL;
typedef unsigned char UCHAR;
typedef char CHAR;
typedef uint16_t USHORT;
typedef int16_t SHORT;
typedef uint32_t ULONG;
typedef int32_t LONG;
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#endif
以上代码都是freemobus的基础代码,接下来需要些使用freemodbus的代码apifreemodbus
4. 第二步:应用层接口与数据映射
我们需要实现寄存器的读写回调函数,并将其封装供主程序调用。
4.1 回调函数实现 (apimodbus.c)
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2026-05-09 20:10:24
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2026-05-10 20:56:52
* @FilePath: \modbus\apifreemobus.c
* @Description: 杩欐槸榛樿璁剧疆,璇疯缃甡customMade`, 鎵撳紑koroFileHeader鏌ョ湅閰嶇疆 杩涜璁剧疆: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#include "mbport.h"
#include "mb.h"
#include <stdint.h>
#include "apifreemodbus.h"
#define USE_FREE_MODBUS // FREEMODBUS ENABLE
#ifdef USE_FREE_MODBUS
#define REG_INPUT_START 1
#define REG_HOLDING_START 1
#define REG_COILS_START 1
#define REG_DISCRETE_START 1
#define REG_INPUT_NREGS 500 //INPUT Register Number
#define REG_HOLDING_NREGS 500 // Holding Register Number
#define REG_COILS_NCOILS 500 // Coils Number
#define REG_DISCRETE_NREGS 500 // Discrete Input Number
/* REGISTER */
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegCoilsStart = REG_COILS_START;
static USHORT usRegDiscreteStart= REG_DISCRETE_START;
/* REGISTER BUFFER */
static USHORT REG_INPUT_BUFF[REG_INPUT_NREGS] = {0}; // INPUT Register Buffer (16-bit)
static USHORT REG_HOLDING_BUFF[REG_HOLDING_NREGS] = {0}; // HOLDING Register Buffer (16-bit)
static UCHAR REG_COILS_BUFF[REG_COILS_NCOILS] = {0}; // COILS Buffer (bit-level)
static UCHAR REG_DISCRETE_BUFF[REG_DISCRETE_NREGS] = {0}; // DISCRETE Input Buffer (bit-level)
/* -------------------------------------------------------------------------- */
/* INPUT Register Callback 输入寄存器回调函数(函数名固定) */
/* -------------------------------------------------------------------------- */
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 )( REG_INPUT_BUFF[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUFF[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/* -------------------------------------------------------------------------- */
/* REGHOLDING Register Callback(保持寄存器回调函数(函数名固定) */
/* -------------------------------------------------------------------------- */
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 )
{
/* Pass current register values to the protocol stack. */
case MB_REG_READ: /*master read register values from the protocol stack. */
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( UCHAR ) ( REG_HOLDING_BUFF[iRegIndex] >> 8 ); //high byte
*pucRegBuffer++ = ( UCHAR ) ( REG_HOLDING_BUFF[iRegIndex] & 0xFF ); //low byte
iRegIndex++;
usNRegs--;
}
break;
/* Update current register values with new values from the
* protocol stack. */
case MB_REG_WRITE: /*master write register values to the protocol stack. */
while( usNRegs > 0 )
{
REG_HOLDING_BUFF[iRegIndex] = *pucRegBuffer++ << 8; //high byte
REG_HOLDING_BUFF[iRegIndex] |= *pucRegBuffer++; //low byte
iRegIndex++;
usNRegs--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/* -------------------------------------------------------------------------- */
/* COILS Register Callback 线圈回调函数(函数名固定)*/
/* -------------------------------------------------------------------------- */
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if( (usAddress >= REG_COILS_START) &&
(usAddress + usNCoils <= REG_COILS_START + REG_COILS_NCOILS) )
{
iRegIndex = (int)(usAddress - usRegCoilsStart);
switch( eMode )
{
case MB_REG_READ:
while( usNCoils > 0 )
{
UCHAR ucResult = 0;
for( UCHAR i = 0; i < 8 && usNCoils > 0; i++, usNCoils-- )
{
if( REG_COILS_BUFF[iRegIndex] )
ucResult |= (1 << i);
iRegIndex++;
}
*pucRegBuffer++ = ucResult;
}
break;
case MB_REG_WRITE:
while( usNCoils > 0 )
{
UCHAR ucByte = *pucRegBuffer++;
for( UCHAR i = 0; i < 8 && usNCoils > 0; i++, usNCoils-- )
{
REG_COILS_BUFF[iRegIndex] = (ucByte & (1 << i)) ? 1 : 0;
iRegIndex++;
}
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/* -------------------------------------------------------------------------- */
/* DISCRETE Input Register Callback离散线圈回调函数(函数名固定) */
/* -------------------------------------------------------------------------- */
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if( (usAddress >= REG_DISCRETE_START) &&
(usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_NREGS) )
{
iRegIndex = (int)(usAddress - usRegDiscreteStart);
while( usNDiscrete > 0 )
{
UCHAR ucResult = 0;
for( UCHAR i = 0; i < 8 && usNDiscrete > 0; i++, usNDiscrete-- )
{
if( REG_DISCRETE_BUFF[iRegIndex] )
ucResult |= (1 << i);
iRegIndex++;
}
*pucRegBuffer++ = ucResult;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
#endif
/* ==================== Modbus Free Modbus Library - FreeModbus.org ==================== */
typedef uint8_t u8;
typedef uint16_t u16;
/* Swap Endian 16 (2 bytes) ,this not is a standard function 大小端转换,不需要*/
void SwapEndian16(u8* pData, u16 u16ByteLen)
{
if (!pData || (u16ByteLen & 1)) return; /* Length must be even */
for (u16 i = 0; i < u16ByteLen; i += 2)
{
u8 tmp = pData[i];
pData[i] = pData[i+1];
pData[i+1] = tmp;
}
}
/*=============================封装函数============================*/
/**
* @brief register hold register encapsulation function
*
* @param pu8RegBuffer: data buffer for holding register, the data is in big-endian format, which means the high byte is stored first followed by the low byte.
* @param u16Address: Modbus holding register address, 1-based address.
* @param u16NRegs: Number of holding registers, each register is 16 bits (2 bytes).
* @param u8Mode: operation mode
* - 0: MB_REG_READ read holding register values and pass to protocol stack.
* - 1: MB_REG_WRITE update holding register values with new values from protocol stack.
* @return error code, MB_ENOERR=0 means success.
*/
u8 Modbus_RegHold(u8* pu8RegBuffer, u16 u16Address, u16 u16NRegs, u8 u8Mode)
{
#ifdef USE_FREE_MODBUS
return (u8)eMBRegHoldingCB((UCHAR *)pu8RegBuffer, (USHORT)u16Address, (USHORT)u16NRegs, (eMBRegisterMode)u8Mode);
#else
return 0;
#endif
}
/**
* @brief register input register encapsulation function
*
* @param pu8RegBuffer: data buffer for input register, the data is in big-endian format, which means the high byte is stored first followed by the low byte.
* @param u16Address: Modbus input register address, 1-based address.
* @param u16NRegs: Number of input registers, each register is 16 bits (2 bytes).
* @return error code, MB_ENOERR=0 means success.
*/
u8 Modbus_RegInput(u8* pu8RegBuffer, u16 u16Address, u16 u16NRegs)
{
#ifdef USE_FREE_MODBUS
return (u8)eMBRegInputCB((UCHAR *)pu8RegBuffer, (USHORT)u16Address, (USHORT)u16NRegs);
#else
return 0;
#endif
}
/**
* @brief register coil encapsulation function
*
* @param pu8RegBuffer: data buffer for coil register, the data is in big-endian format, which means the high byte is stored first followed by the low byte.
* @param u16Address: Modbus coil register address, 1-based address.
* @param u16NCoils: Number of coil registers, each register is 1 bit.
* @param u8Mode: operation mode
* - 0: MB_REG_READ read coil register values and pass to protocol stack.
* - 1: MB_REG_WRITE update coil register values with new values from protocol stack.
* @return error code, MB_ENOERR=0 means success.
*
* 使用场景:
* - 控制开关量输出(继电器、指示灯等)
* - MCGS(主站)通过 01/05/15 功能码读写线圈
*
*/
u8 Modbus_RegCoil(u8* pu8RegBuffer, u16 u16Address, u16 u16NCoils, u8 u8Mode)
{
#ifdef USE_FREE_MODBUS
return (u8)eMBRegCoilsCB((UCHAR *)pu8RegBuffer, (USHORT)u16Address, (USHORT)u16NCoils, (eMBRegisterMode)u8Mode);
#else
return 0;
#endif
}
/**
* @brief register discrete input encapsulation function
*
* @param pu8RegBuffer: data buffer for discrete input register, the data is in big-endian format, which means the high byte is stored first followed by the low byte.
* @param u16Address: Modbus discrete input register address, 1-based address.
* @param u16NDiscrete: Number of discrete input registers, each register is 1 bit.
* @return error code, MB_ENOERR=0 means success.
*
*/
u8 Modbus_RegDiscrete(u8* pu8RegBuffer, u16 u16Address, u16 u16NDiscrete)
{
#ifdef USE_FREE_MODBUS
return (u8)eMBRegDiscreteCB((UCHAR *)pu8RegBuffer, (USHORT)u16Address, (USHORT)u16NDiscrete);
#else
return 0;
#endif
}
4.2 apifreemodbus.h
/*
* @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @Date: 2026-05-09 22:14:46
* @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git
* @LastEditTime: 2026-05-10 19:21:46
* @FilePath: \modbus\apifreemodbus.h
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
#ifndef __APIFREEMODBUS_H__
#define __APIFREEMODBUS_H__
#include <stdint.h>
/* 类型定义(若全局已有可删除) */
typedef uint8_t u8;
typedef uint16_t u16;
/* 封装接口声明 */
u8 Modbus_RegHold(u8* pu8RegBuffer, u16 u16Address, u16 u16NRegs, u8 u8Mode);
u8 Modbus_RegInput(u8* pu8RegBuffer, u16 u16Address, u16 u16NRegs);
u8 Modbus_RegCoil(u8* pu8RegBuffer, u16 u16Address, u16 u16NCoils, u8 u8Mode);
u8 Modbus_RegDiscrete(u8* pu8RegBuffer, u16 u16Address, u16 u16NDiscrete);
void SwapEndian16(u8* pData, u16 u16ByteLen);//big-endian <-> little-endian,this is not a standard function, just for modbus register data format conversion.
#endif /* __APIFREEMODBUS_H__ */
5. 第三步:主程序调用 (main.c)
#include "mb.h"
#include "mbport.h"
#include "apifreemodbus.h"
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
//数据需要通过网络发送、写入文件、或与外部硬件/协议交互(如 Modbus、USB、SPI Flash、TCP/IP 包头)。
// 定义一个测试结构体 (例如时间信息)
#pragma pack(push, 1) // 设置结构体字节对齐
typedef struct {
u16 year; // 2 bytes
u8 month; // 1 byte
u8 day; // 1 byte
u8 hour; // 1 byte
u8 minute; // 1 byte
u8 second; // 1 byte
u8 reserved; // 1 byte (Padding for alignment to 16-bit Modbus register)
} time_t;
#pragma pack(pop)
time_t time;
#define MB_TIME_ADDRESS 0x0000
int main(void)
{
// HAL 库初始化 (时钟、串口、LED等)
HAL_Init();
SystemClock_Config();
// Modbus协议栈初始化
// 参数: MB_RTU, 从机地址,串口端口(类型), 波特率,校验位
//如果已经硬件初始化,关于串口的参数可以随意写(最好一样)
eMBErrorCode eStatus = eMBInit( MB_RTU, 0x01, PORT_RS232, 115200, MB_PAR_EVEN );//已包含硬件初始化
// 设置从机 ID 信息 (你在 mbconfig.h 开启了 REP_SLAVEID)
//不调用没有太大影响(主机发送17功能码,从机依然会回复主机,不会死机或返回异常码(从机会回复一段默认的空数据(或者长度为 0 的数据段))
UCHAR ucSlaveID[] = { 'S', 'T', 'M', '3', '2', '-', 'F', 'R', 'E', 'E', 'M', 'O', 'D', 'B', 'U', 'S' };
eMBSetSlaveID( 0x01, TRUE, ucSlaveID, sizeof(ucSlaveID) );//单片机作为主机时不调用
// 启动 Modbus
eMBEnable();
while (1)
{
// 这个函数内部会处理所有收发逻辑
eMBPoll();
Modbus_RegHold(&time,MB_TIME_ADDRESS,(sizeof(time)/2),MB_REG_WRITE);//主机写时间信息
// 可以在这里放其他的用户代码
}
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)