基于STM8S的FreeModbus协议移植指南

一、移植背景与核心需求

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打印调试信息,确认usAddressusNRegs正确。

六、总结

本方案基于STM8S103F3P6实现了FreeModbus RTU从机,核心步骤包括UART/定时器初始化、FreeModbus接口移植、寄存器映射。通过简单修改寄存器定义,可扩展至更多功能码(如0x06写单寄存器、0x10写多寄存器)。

相关推荐
kanhao1003 小时前
从 Vectorless 到 SAIF 再到板级实测:HLS Kernel 功耗估计全流程实战
嵌入式硬件·fpga开发
yongui478344 小时前
基于FPGA的频率计与串口通信系统设计与实现
fpga开发
FPGA_ADDA5 小时前
国产复旦微FPGA+DSP 6U VPX处理板
fpga开发·全国产·复旦微690t·ft6678dsp·6u vpx板卡·jfm7vx690t36
fengfuyao9857 小时前
基于FPGA的简易电子密码锁设计(Verilog实现)
fpga开发
hoiii1878 小时前
104键PS2接口标准键盘C语言驱动程序
c语言·fpga开发·计算机外设
jllllyuz18 小时前
基于FPGA的通信信号源设计
fpga开发
Saniffer_SH1 天前
【每日一题】一台可编程的PCIe 6.0主机 + 一套自动化CTS验证平台 + 一个轻量级链路分析系统
运维·服务器·测试工具·fpga开发·自动化·计算机外设·硬件架构
952361 天前
计算机组成原理 - 主存储器
单片机·嵌入式硬件·学习·fpga开发
简简单单做算法1 天前
【第2章>第1节】基于FPGA的图像放大和插值处理概述
计算机视觉·fpga开发·双线性插值·线性插值·图像放大·均值插值·最邻近插值