本文目录
- 1. 一、XTinyModbus概述
- 2. 二、文件结构
- 3. 三、配置选项
- 4. 四、平台移植接口
- 5. 五、从站模式完整示例
- 6. 六、主站模式完整示例
- 7. 七、51单片机移植要点
- 8. 八、常见问题
- 9. 九、与其他Modbus协议栈对比
- 10. (XTinyModbus)C语言嵌入式Modbus协议栈,主站、从站示例源码¥6.99
XTinyModbus是一款轻量级的C语言嵌入式Modbus协议栈,专为资源受限的嵌入式系统设计。它支持Modbus RTU和Modbus ASCII两种传输模式,同时提供主站(Master)和从站(Slave)功能。本文详细介绍XTinyModbus的架构特点、API接口、移植方法以及完整的主从站示例源码。
一、XTinyModbus概述
XTinyModbus的设计目标是在极小的资源占用下提供完整的Modbus协议功能。它采用纯C语言编写,不依赖任何操作系统,可轻松移植到51单片机、AVR、STM32、MSP430等各类嵌入式平台。
1.1 核心特性
- 超轻量级:代码体积约5KB,RAM占用不到200字节
- 主从站一体:同一套代码可配置为主站或从站模式
- 双协议支持:Modbus RTU和Modbus ASCII可切换
- 无操作系统依赖:裸机运行,适合各类单片机
- 功能码完整:支持01/02/03/04/05/06/15/16等常用功能码
- 可裁剪配置:通过宏定义裁剪不需要的功能,进一步减小体积
- 中断驱动:采用串口中断接收,不占用CPU轮询时间
- CRC校验:内置CRC16校验表,快速计算
1.2 资源占用
| 资源 | 从站模式 | 主站模式 | 主从站模式 |
|---|---|---|---|
| Flash(代码) | ~3KB | ~3.5KB | ~5KB |
| RAM(全局变量) | ~64字节 | ~80字节 | ~120字节 |
| 栈空间 | ~32字节 | ~48字节 | ~64字节 |
| 接收缓冲区 | 32字节(可配置) | 32字节 | 32字节 |
| 发送缓冲区 | 32字节(可配置) | 32字节 | 32字节 |
二、文件结构
XTinyModbus/
├── xtiny_modbus.h // 主头文件,API声明和配置宏
├── xtiny_modbus.c // 核心协议实现
├── xtiny_modbus_port.h // 平台移植接口(用户实现)
├── xtiny_modbus_config.h // 配置文件(功能裁剪)
└── examples/
├── slave_demo.c // 从站示例
├── master_demo.c // 主站示例
└── stm32_port.c // STM32移植示例
三、配置选项
在xtiny_modbus_config.h中通过宏定义配置功能:
// 使能主站功能(注释掉则只编译从站)
#define XMB_ENABLE_MASTER 1
// 使能从站功能
#define XMB_ENABLE_SLAVE 1
// 协议模式:1=RTU,0=ASCII
#define XMB_RTU_MODE 1
// 缓冲区大小
#define XMB_BUF_SIZE 64
// 从站地址(1-247)
#define XMB_SLAVE_ADDR 1
// 支持的功能码(不需要的注释掉可减小代码体积)
#define XMB_FC_READ_COILS 1 // 0x01
#define XMB_FC_READ_INPUTS 1 // 0x02
#define XMB_FC_READ_HOLDINGS 1 // 0x03
#define XMB_FC_READ_INPUT_REGS 1 // 0x04
#define XMB_FC_WRITE_COIL 1 // 0x05
#define XMB_FC_WRITE_REGISTER 1 // 0x06
#define XMB_FC_WRITE_COILS 1 // 0x0F
#define XMB_FC_WRITE_REGISTERS 1 // 0x10
// 数据区大小
#define XMB_COILS_NUM 16 // 线圈数量
#define XMB_INPUTS_NUM 16 // 离散输入数量
#define XMB_HOLDINGS_NUM 32 // 保持寄存器数量
#define XMB_INPUT_REGS_NUM 16 // 输入寄存器数量
// 帧超时时间(字符间隔,单位ms)
#define XMB_FRAME_TIMEOUT 5
四、平台移植接口
XTinyModbus通过回调函数与硬件平台解耦,用户只需实现以下接口:
// xtiny_modbus_port.h
// 串口初始化(波特率、数据位、停止位、校验位)
void xmb_port_uart_init(uint32_t baudrate);
// 发送一个字节
void xmb_port_uart_send_byte(uint8_t byte);
// 使能/禁用接收中断
void xmb_port_uart_rx_enable(uint8_t enable);
// 使能/禁用发送完成中断
void xmb_port_uart_tx_enable(uint8_t enable);
// 获取系统毫秒计数(用于超时检测)
uint32_t xmb_port_get_tick(void);
// 进入临界区(关中断)
void xmb_port_enter_critical(void);
// 退出临界区(开中断)
void xmb_port_exit_critical(void);
// RS485收发控制(1=发送,0=接收)
void xmb_port_rs485_dir(uint8_t tx);
4.1 STM32移植示例
// stm32_port.c
#include "stm32f10x.h"
#include "xtiny_modbus.h"
static volatile uint32_t sys_tick = 0;
// SysTick中断,1ms一次
void SysTick_Handler(void) {
sys_tick++;
}
void xmb_port_uart_init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// TX: PA9 复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// RX: PA10 浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// RS485方向控制: PA8
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
USART_InitStruct.USART_BaudRate = baudrate;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStruct);
// 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
USART_Cmd(USART1, ENABLE);
}
void xmb_port_uart_send_byte(uint8_t byte) {
USART_SendData(USART1, byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void xmb_port_uart_rx_enable(uint8_t enable) {
if (enable) USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
else USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
}
void xmb_port_uart_tx_enable(uint8_t enable) {
if (enable) USART_ITConfig(USART1, USART_IT_TC, ENABLE);
else USART_ITConfig(USART1, USART_IT_TC, DISABLE);
}
uint32_t xmb_port_get_tick(void) {
return sys_tick;
}
void xmb_port_enter_critical(void) {
__disable_irq();
}
void xmb_port_exit_critical(void) {
__enable_irq();
}
void xmb_port_rs485_dir(uint8_t tx) {
if (tx) GPIO_SetBits(GPIOA, GPIO_Pin_8);
else GPIO_ResetBits(GPIOA, GPIO_Pin_8);
}
// 串口中断服务程序
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t byte = USART_ReceiveData(USART1);
xmb_uart_rx_isr(byte); // 交给协议栈处理
}
if (USART_GetITStatus(USART1, USART_IT_TC) != RESET) {
USART_ClearITPendingBit(USART1, USART_IT_TC);
xmb_uart_tx_isr(); // 通知协议栈发送完成
}
}
五、从站模式完整示例
// slave_demo.c
#include "xtiny_modbus.h"
#include <stdint.h>
// 应用数据区(用户可直接读写)
uint8_t coils[XMB_COILS_NUM]; // 线圈状态
uint8_t inputs[XMB_INPUTS_NUM]; // 离散输入
uint16_t holdings[XMB_HOLDINGS_NUM]; // 保持寄存器
uint16_t input_regs[XMB_INPUT_REGS_NUM]; // 输入寄存器
int main(void) {
// 初始化系统时钟、GPIO等
SystemInit();
SysTick_Config(SystemCoreClock / 1000); // 1ms SysTick
// 初始化Modbus从站
xmb_slave_init(9600, XMB_SLAVE_ADDR);
// 注册数据区回调(或直接使用全局数组)
xmb_slave_set_coils(coils);
xmb_slave_set_inputs(inputs);
xmb_slave_set_holdings(holdings);
xmb_slave_set_input_regs(input_regs);
// 初始化一些数据
holdings[0] = 100; // 温度
holdings[1] = 50; // 湿度
while (1) {
// 主循环中调用协议栈处理
xmb_slave_poll();
// 用户应用代码
// 例如:读取传感器更新到input_regs
// input_regs[0] = read_adc();
// 更新线圈状态到实际输出
// for (i=0; i<8; i++) set_relay(i, coils[i]);
}
}
// 写线圈回调(可选,用于执行实际动作)
void xmb_callback_write_coil(uint16_t addr, uint8_t value) {
// 当主站写线圈时调用
// 例如:控制继电器
// set_relay(addr, value);
}
// 写寄存器回调(可选)
void xmb_callback_write_register(uint16_t addr, uint16_t value) {
// 当主站写保持寄存器时调用
// 例如:更新设定值
// if (addr == 0) set_temperature(value);
}
六、主站模式完整示例
// master_demo.c
#include "xtiny_modbus.h"
#include <stdio.h>
int main(void) {
uint8_t coils[8];
uint16_t regs[10];
int16_t rc;
SystemInit();
SysTick_Config(SystemCoreClock / 1000);
// 初始化Modbus主站
xmb_master_init(9600);
while (1) {
// 读取从站1的8个线圈状态
rc = xmb_master_read_coils(1, 0, 8, coils);
if (rc == XMB_OK) {
printf("读取成功:");
for (int i = 0; i < 8; i++) printf("%d ", coils[i]);
printf("\n");
} else {
printf("读取失败,错误码:%d\n", rc);
}
// 读取从站1的10个保持寄存器
rc = xmb_master_read_holdings(1, 0, 10, regs);
if (rc == XMB_OK) {
printf("寄存器:");
for (int i = 0; i < 10; i++) printf("%d ", regs[i]);
printf("\n");
}
// 写单个线圈:控制第1路继电器吸合
rc = xmb_master_write_coil(1, 0, 1);
if (rc == XMB_OK) printf("写线圈成功\n");
// 写单个寄存器
rc = xmb_master_write_register(1, 0, 1234);
if (rc == XMB_OK) printf("写寄存器成功\n");
// 写多个线圈
uint8_t coil_vals[4] = {1, 0, 1, 0};
xmb_master_write_coils(1, 0, 4, coil_vals);
// 写多个寄存器
uint16_t reg_vals[3] = {100, 200, 300};
xmb_master_write_registers(1, 0, 3, reg_vals);
// 延时1秒
delay_ms(1000);
}
}
// 错误码说明
// XMB_OK = 0 成功
// XMB_ERR_TIMEOUT = -1 超时
// XMB_ERR_CRC = -2 CRC校验错误
// XMB_ERR_EXCEPT = -3 从站异常响应
// XMB_ERR_ADDR = -4 地址不匹配
// XMB_ERR_BUSY = -5 总线忙
七、51单片机移植要点
- 缓冲区大小:51单片机RAM有限,建议将XMB_BUF_SIZE设为32
- 寄存器数量:线圈和寄存器数量不宜过多,建议各16个
- 晶振选择:使用11.0592MHz晶振保证9600bps准确
- 中断优先级:串口中断优先级设为最高,避免丢字节
- 帧超时:51单片机无SysTick,可用定时器0实现1ms计时
- 变量存储:大数组定义为xdata或idata,避免占用直接寻址区
- CRC表:CRC16查表法占用256字节ROM,51单片机可接受
7.1 51单片机定时器实现ms计时
// 51单片机1ms定时器(11.0592MHz)
static uint16_t xmb_tick = 0;
void timer0_init(void) {
TMOD |= 0x01; // 定时器0模式1
TH0 = 0xFC; // 1ms初值
TL0 = 0x67;
ET0 = 1; // 使能定时器0中断
TR0 = 1; // 启动定时器0
EA = 1;
}
void timer0_isr(void) interrupt 1 {
TH0 = 0xFC;
TL0 = 0x67;
xmb_tick++;
}
uint32_t xmb_port_get_tick(void) {
return xmb_tick;
}
八、常见问题
- 通讯无响应:检查从站地址、波特率、校验位是否一致,A/B线是否接反
- CRC错误:确认晶振频率准确,串口配置正确,检查是否有干扰
- 只能收不能发:检查RS485方向控制引脚,确认DE/RE接在一起
- 数据丢包:增大缓冲区,提高串口中断优先级,减少关中断时间
- 主站超时:增加超时时间,确认从站响应速度,检查总线负载
- 多从站冲突:确保每个从站地址唯一,总线两端接120Ω终端电阻
- 编译报错:确认配置宏定义正确,移植接口全部实现
九、与其他Modbus协议栈对比
| 协议栈 | 代码体积 | RAM占用 | 主站 | 从站 | 适用平台 |
|---|---|---|---|---|---|
| XTinyModbus | ~5KB | ~120B | 支持 | 支持 | 51/AVR/STM32/MSP430 |
| FreeModbus | ~10KB | ~300B | 不支持 | 支持 | 各类MCU |
| uModbus | ~8KB | ~200B | 支持 | 支持 | ARM Cortex-M |
| libmodbus | ~50KB | ~1KB | 支持 | 支持 | Linux/Windows |
| mnModbus | ~15KB | ~500B | 支持 | 支持 | STM32/嵌入式Linux |
XTinyModbus以其极小的资源占用和简洁的API设计,特别适合51单片机等资源极度受限的嵌入式系统。对于需要在极小硬件上实现Modbus通讯的项目,XTinyModbus是理想的选择。本文提供的代码可直接用于项目开发,建议先在测试板上验证通讯,再集成到实际产品中。
xtiny_modbus.h xtiny_modbus.c这两个文件找不到