一、移植背景与核心需求
FreeModbus是开源的Modbus协议栈(支持RTU/ASCII模式),广泛用于工业现场设备的串行通信。STM8S系列单片机(如STM8S103F3P6,8KB Flash,1KB RAM)因成本低、外设精简,适合作为Modbus从机(Slave)节点。本移植方案基于STM8S标准库V2.3,实现Modbus RTU从机功能,支持保持寄存器(0x03功能码) 和线圈(0x01/0x05功能码) 读写,可扩展至其他功能码。
二、硬件环境
2.1 核心组件
| 模块 | 型号/参数 | 功能说明 |
|---|---|---|
| 主控 | STM8S103F3P6(8KB Flash,1KB RAM) | Modbus协议处理、寄存器管理、串口通信 |
| 通信接口 | MAX485(RS485物理层) | 实现Modbus RTU差分信号传输 |
| 串口 | UART1(PA4-TX,PA5-RX) | 连接MAX485,波特率9600/19200bps |
| 定时器 | TIM2(16位通用定时器) | 计算Modbus RTU字符间/帧超时(3.5T) |
2.2 硬件连接
| 模块 | 引脚(STM8S103F3P6) | 说明 |
|---|---|---|
| UART1 | PA4(TX)→ MAX485 DI | 发送数据 |
| PA5(RX)← MAX485 RO | 接收数据 | |
| MAX485 | DE/RE → PB0(控制收发) | 高电平发送,低电平接收 |
| TIM2 | 时钟源:内部16MHz | 定时1ms,用于超时计算 |
三、软件移植步骤
3.1 FreeModbus协议栈获取
-
下载FreeModbus源码:FreeModbus官网(选择V1.6稳定版)
-
关键文件:
-
modbus.c/modbus.h:协议核心(从机模式) -
port/portserial.c/portserial.h:串口操作(需移植) -
port/porttimer.c/porttimer.h:定时器操作(需移植) -
port/portother.c/portother.h:其他硬件相关(如临界区保护)
-
3.2 STM8S硬件初始化
3.2.1 时钟配置(16MHz内部RC)
c
#include "stm8s.h"
void CLK_Configuration(void) {
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 内部16MHz RC不分频
CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1); // CPU时钟=16MHz
}
3.2.2 UART1初始化(Modbus RTU,9600bps,8N1)
c
void UART1_Configuration(void) {
UART1_DeInit();
UART1_Init((uint32_t)9600, UART1_WORDLENGTH_8D, UART1_STOPBITS_1,
UART1_PARITY_NO, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TXRX_ENABLE);
UART1_ITConfig(UART1_IT_RXNE_OR, ENABLE); // 使能接收中断
UART1_Cmd(ENABLE); // 使能UART1
}
3.2.3 TIM2初始化(1ms定时,用于Modbus超时)
c
void TIM2_Configuration(void) {
TIM2_DeInit();
TIM2_TimeBaseInit(TIM2_PRESCALER_16000, 16); // 16MHz/16000=1kHz,1ms重装值16(16 * 1ms=16ms?修正:1ms需重装值=16MHz/1000=16000-1=15999)
TIM2_TimeBaseInit(TIM2_PRESCALER_16, 999); // 16MHz/16=1MHz,1ms=1000计数,重装值999
TIM2_ITConfig(TIM2_IT_UPDATE, ENABLE); // 使能更新中断
TIM2_Cmd(ENABLE); // 使能TIM2
}
3.3 FreeModbus移植核心文件
3.3.1 串口操作(portserial.c)
实现FreeModbus要求的串口函数,适配STM8S UART1:
c
#include "portserial.h"
#include "stm8s_uart1.h"
// 串口初始化(已在UART1_Configuration中实现,此处为空)
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity) {
return TRUE;
}
// 使能串口接收/发送
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable) {
if (xRxEnable) {
UART1_ITConfig(UART1_IT_RXNE, ENABLE); // 使能接收中断
GPIO_WriteLow(GPIOB, GPIO_PIN_0); // MAX485接收模式(DE/RE=0)
} else {
UART1_ITConfig(UART1_IT_RXNE, DISABLE);
}
if (xTxEnable) {
GPIO_WriteHigh(GPIOB, GPIO_PIN_0); // MAX485发送模式(DE/RE=1)
} else {
GPIO_WriteLow(GPIOB, GPIO_PIN_0);
}
}
// 发送单个字节
BOOL xMBPortSerialPutByte(CHAR ucByte) {
UART1_SendData8(ucByte);
while (UART1_GetFlagStatus(UART1_FLAG_TXE) == RESET); // 等待发送完成
return TRUE;
}
// 接收单个字节
BOOL xMBPortSerialGetByte(CHAR *pucByte) {
*pucByte = UART1_ReceiveData8();
return TRUE;
}
3.3.2 定时器操作(porttimer.c)
实现Modbus RTU超时控制(3.5字符时间,9600bps时1字符=1.04ms,3.5T≈3.64ms):
c
#include "porttimer.h"
#include "stm8s_tim2.h"
// 定时器初始化(1ms基准)
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us) {
// usTim1Timerout50us=3.5T/50us=3640/50=72.8→73(50us为单位)
// 实际用TIM2 1ms中断,累计计数实现
return TRUE;
}
// 启动定时器(超时时间=usTim1Timerout50us*50us)
void vMBPortTimersEnable(void) {
TIM2_SetCounter(0);
TIM2_Cmd(ENABLE);
}
// 关闭定时器
void vMBPortTimersDisable(void) {
TIM2_Cmd(DISABLE);
TIM2_SetCounter(0);
}
// TIM2中断服务函数(1ms触发一次,累计超时)
INTERRUPT_HANDLER(TIM2_UPD_OVF_BRK_IRQHandler, 13) {
static USHORT usTimeoutCount = 0;
if (TIM2_GetITStatus(TIM2_IT_UPDATE) != RESET) {
TIM2_ClearITPendingBit(TIM2_IT_UPDATE);
usTimeoutCount++;
if (usTimeoutCount >= 4) { // 4ms≈3.5T(9600bps时)
usTimeoutCount = 0;
pxMBPortCBTimerExpired(); // 通知FreeModbus帧超时
}
}
}
3.3.3 其他硬件相关(portother.c)
实现临界区保护(STM8S无OS,用全局变量简单保护):
c
#include "portother.h"
void vMBPortEnterCritical(void) {
disableInterrupts(); // 关总中断
}
void vMBPortExitCritical(void) {
enableInterrupts(); // 开总中断
}
3.4 Modbus寄存器定义
在modbus_slave.c中定义Modbus寄存器映射(示例:4个保持寄存器+2个线圈):
c
// 保持寄存器(0x03功能码,地址0-3)
USHORT usRegHoldingBuf[4] = {0x1234, 0x5678, 0x9ABC, 0xDEF0};
// 线圈(0x01/0x05功能码,地址0-1)
UCHAR ucRegCoilsBuf[2] = {0x01, 0x00}; // 线圈0=ON,线圈1=OFF
// 寄存器回调函数(FreeModbus调用)
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode) {
if (eMode == MB_REG_READ) {
memcpy(pucRegBuffer, &usRegHoldingBuf[usAddress], usNRegs*2);
} else { // 写
memcpy(&usRegHoldingBuf[usAddress], pucRegBuffer, usNRegs*2);
}
return MB_ENOERR;
}
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode) {
if (eMode == MB_REG_READ) {
for (int i=0; i<usNCoils; i++) {
pucRegBuffer[i/8] |= (ucRegCoilsBuf[usAddress+i] << (i%8));
}
} else { // 写
for (int i=0; i<usNCoils; i++) {
ucRegCoilsBuf[usAddress+i] = (pucRegBuffer[i/8] >> (i%8)) & 0x01;
}
}
return MB_ENOERR;
}
参考代码 基于stm8s的freemodbus协议移植 www.youwenfan.com/contentcss/56312.html
四、主函数与测试
4.1 主函数
c
#include "stm8s.h"
#include "modbus.h"
void main(void) {
CLK_Configuration();
UART1_Configuration();
TIM2_Configuration();
GPIO_Configuration(); // 配置PB0(DE/RE)为推挽输出
eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_NONE); // 从机地址0x01,RTU模式
eMBEnable(); // 使能Modbus
while (1) {
eMBPoll(); // 轮询Modbus事件
}
}
4.2 测试工具
-
Modbus Poll(PC端):发送0x03功能码读取保持寄存器,0x01功能码读取线圈。
-
串口助手:监控UART1数据,验证通信格式(如读寄存器请求:01 03 00 00 00 04 44 09)。
五、关键问题与解决方案
5.1 通信乱码
-
原因:波特率计算错误、UART配置不符(如数据位/校验位)。
-
解决 :用示波器测量UART_TX引脚,确认波特率(9600bps时1位=104μs),检查
UART1_Init参数。
5.2 帧超时错误
-
原因:TIM2定时不准确,3.5T计算错误。
-
解决:根据波特率调整TIM2重装值(如19200bps时3.5T≈1.82ms,TIM2 1ms中断需累计2次)。
5.3 寄存器读写异常
-
原因:寄存器地址映射错误,回调函数未正确实现。
-
解决 :用
eMBRegHoldingCB打印调试信息,确认usAddress和usNRegs正确。
六、总结
本方案基于STM8S103F3P6实现了FreeModbus RTU从机,核心步骤包括UART/定时器初始化、FreeModbus接口移植、寄存器映射。通过简单修改寄存器定义,可扩展至更多功能码(如0x06写单寄存器、0x10写多寄存器)。