返回博客
基于软硬件结合的分布式 IO 拓展系统设计

基于软硬件结合的分布式 IO 拓展系统设计

这是我的本科毕业设计项目,基于 STM32 与 Modbus RTU 协议设计了一套分布式 IO 拓展系统。系统采用模块化硬件架构和封装式软件设计,可根据具体应用场景灵活组合 IO 功能。

系统模块实物

项目背景

在工业 4.0 与中国制造 2025 的背景下,工业控制系统对可扩展性和可维护性的要求越来越高。传统集中式 IO 系统在面对复杂生产设备时,布线成本高、扩展困难。分布式 IO 系统因其灵活部署和高可靠性的特点,被广泛应用于智能制造、能源管理、楼宇自动化等领域。

然而,市场上的分布式 IO 产品(如西门子 ET200SP、施耐德 Modicon Edge IO 等)价格较高,且不同厂商的产品之间兼容性有限。本项目旨在设计一套低成本、可灵活配置的分布式 IO 系统框架。

系统架构

整个系统由三类模块组成,通过统一的 PH2.0 2×20P 排针排母接口和内部 RS-485 总线连接:

系统拓扑架构

核心板

系统的计算与控制核心,基于 STM32F103C8T6 最小系统设计。

  • 128K Flash + 20K SRAM
  • 引出 UART、SPI、I2C、CAN 等外设接口
  • 外部 5V 供电,板载 AMS1117 LDO 稳压至 3.3V
  • 双 LED 指示(运行状态 + 总线状态)

核心板 MCU 引脚分配原理图

核心板通过标准接口与不同功能板配合,实现不同的 IO 功能。

Modbus RTU 接口板

作为系统网关,连接外部 PLC 与内部扩展总线。

  • 外部 24V DC 供电(支持 18-36V 宽压输入)
  • 板载防雷保护、ESD 防护、防反接整流、自恢复保险丝
  • DCDC 降压(LGS5145,4.5-55V 输入)为核心板供 5V
  • 双路 UART 转 RS-485(MAX485),分别用于外部通讯和内部总线
  • 外部接口兼容西门子 S7-1200/S7-1500 PLC

接口板 PCB Layout

数字量扩展板

实现具体的 IO 功能,挂载在内部总线上。

数字量输入板:

  • 8 路晶体管数字量输入,带 LED 状态指示
  • 1 路高速脉冲输入(≥1KHz,计数误差 ≤0.3%)
  • 输入信号光耦隔离(TLP281-4,隔离度 ≥1000V DC)

数字量输出板:

  • 8 路晶体管数字量输出,带 LED 状态指示
  • 1 路高速脉冲输出(≥5KHz,误差 ≤0.3%),支持频率和占空比可调
  • 输出信号光耦隔离

数字量输出板 PCB Layout

软件设计

软件架构采用封装式设计,便于移植到不同硬件平台。

Modbus RTU 协议栈

自行实现了完整的 Modbus RTU 从站协议栈,支持以下功能码:

功能码 功能
01 读取线圈寄存器
02 读取离散输入寄存器
03 读取保持寄存器
05 写单个线圈寄存器
06 写单个保持寄存器
0F 写多个线圈寄存器
10 写多个保持寄存器

协议栈采用分层设计:底层串口收发 → 帧解析与 CRC 校验 → 功能码分发 → 寄存器读写回调。各模块只需实现自己的寄存器回调函数即可。

Modbus 帧解析与功能码分发核心逻辑:

void Modbus_Process(uint8_t *frame, uint16_t len) {
    uint8_t addr = frame[0];
    uint8_t func = frame[1];

    // CRC 校验
    uint16_t crc_recv = (frame[len-1] << 8) | frame[len-2];
    uint16_t crc_calc = Modbus_CRC16(frame, len - 2);
    if (crc_recv != crc_calc) return;

    // 地址过滤
    if (addr != local_addr) return;

    // 功能码分发
    switch (func) {
        case 0x01: Modbus_ReadCoils(frame);           break;
        case 0x02: Modbus_ReadDiscreteInputs(frame);  break;
        case 0x03: Modbus_ReadHoldingRegs(frame);     break;
        case 0x05: Modbus_WriteSingleCoil(frame);     break;
        case 0x06: Modbus_WriteSingleReg(frame);      break;
        case 0x0F: Modbus_WriteMultiCoils(frame);     break;
        case 0x10: Modbus_WriteMultiRegs(frame);      break;
        default:   Modbus_ErrorResponse(addr, func, 0x01); break;
    }
}

系统初始化与寄存器构建时序

接口板网关逻辑

接口板核心板运行网关转发程序,负责:

  • 接收外部 PLC 的 Modbus RTU 请求
  • 根据从站地址转发到内部总线上对应的扩展板
  • 将扩展板的响应回传给 PLC
void Gateway_Forward(uint8_t *plc_frame, uint16_t len) {
    uint8_t target_addr = plc_frame[0];

    // 查询寄存器偏移表,定位目标扩展板
    uint8_t bus_addr = RegOffsetTable_Lookup(target_addr);
    if (bus_addr == 0xFF) {
        // 无设备响应
        return;
    }

    // 转发到内部 RS-485 总线
    IBUS_Send(plc_frame, len);

    // 等待扩展板响应(超时机制)
    uint16_t resp_len = IBUS_WaitResponse(resp_buf, TIMEOUT_MS);
    if (resp_len > 0) {
        // 回传给外部 PLC
        EBUS_Send(resp_buf, resp_len);
    }
}

扩展板 IO 控制

  • 数字量输入:GPIO 配置为输入模式,通过 IDR 寄存器读取引脚电平,映射到离散输入寄存器
  • 高速脉冲输入:TIM 配置为外部时钟模式,上升沿触发 CNT 计数,映射到保持寄存器
  • 数字量输出:GPIO 配置为推挽输出,通过 ODR 寄存器控制,映射到线圈寄存器
  • 高速脉冲输出:TIM 配置为 PWM 比较输出模式,72MHz 主频预分频至 1MHz,通过 ARR/CCR 寄存器控制频率和占空比

数字量输入——读取 8 路 GPIO 状态并映射到离散输入寄存器:

// 02 功能码回调:读取离散输入寄存器
uint8_t Modbus_ReadDiscreteInputs_CB(uint16_t addr, uint16_t qty) {
    uint8_t state = Get_GPIO_Ports();  // 读取全部 IO 状态
    // 按寄存器地址偏移返回对应位
    return (state >> addr) & ((1 << qty) - 1);
}

uint8_t Get_GPIO_Ports(void) {
    uint8_t state = 0;
    state |= ((GPIOB->IDR >> 10) & 0x01) << 0;  // PB10 → Port1
    state |= ((GPIOB->IDR >> 11) & 0x01) << 1;  // PB11 → Port2
    state |= ((GPIOB->IDR >> 12) & 0x01) << 2;  // PB12 → Port3
    // ... 其余端口类似
    return state;
}

高速脉冲输出——TIM PWM 配置与保持寄存器控制:

void TIM_PWM_Init(void) {
    // 72MHz / (71+1) = 1MHz 计数频率
    htim.Init.Prescaler = 71;
    htim.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim.Init.Period = 0xFFFF;        // ARR: 控制输出频率
    htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_PWM_Init(&htim);

    // PWM 通道配置
    sConfig.OCMode = TIM_OCMODE_PWM1;
    sConfig.Pulse = 0;                // CCR: 控制占空比
    HAL_TIM_PWM_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_3);
}

// 03/06/10 功能码回调:读写保持寄存器
void Modbus_HoldingReg_CB(uint16_t addr, uint16_t value, uint8_t rw) {
    if (rw == WRITE) {
        switch (addr) {
            case 0x00: TIM3->ARR  = value; break;  // 频率控制
            case 0x01: TIM3->CCR3 = value; break;  // 占空比控制
            case 0x02:                              // 脉冲数量
                if (value > 0) HAL_TIM_PWM_Start_IT(&htim, TIM_CHANNEL_3);
                break;
        }
    } else {
        // 读取对应寄存器值返回
    }
}

总线寻址

每块扩展板通过硬件拨码开关设置从站地址,ADC 读取分压电阻值识别地址,支持总线上多设备挂载。

uint8_t Get_Bus_Address(void) {
    uint16_t adc_val = HAL_ADC_GetValue(&hadc1);  // 读取 AIN 引脚电压
    // 根据分压电阻阶梯映射到地址 1~8
    if (adc_val < 512)  return 1;
    if (adc_val < 1024) return 2;
    if (adc_val < 1536) return 3;
    if (adc_val < 2048) return 4;
    // ...
    return 0xFF;  // 无效地址
}

硬件设计

所有 PCB 使用嘉立创 EDA 设计,核心板与功能板通过统一的排针排母接口对接。

主要设计要点:

  • 电源保护:防雷空气放电管 + ESD 保护二极管 + 防反接整流 + 自恢复保险丝
  • 信号隔离:输入输出均采用光耦隔离(TLP281-4),隔离度 ≥1000V DC
  • 电平转换:UART 转 RS-485 采用 MAX485 芯片
  • 模块化接口:统一 PH2.0 2×20P 连接器,核心板可即插即用

测试结果

与西门子 S7-1200 PLC 联调测试

  • 数字量输入输出功能正常,LED 指示准确
  • 高速脉冲输入计数频率 ≥1KHz,误差在 0.3% 以内
  • 高速脉冲输出频率 ≥5KHz,误差在 0.3% 以内
  • 与西门子 S7-1200 PLC 通过 Modbus RTU 通讯正常
  • 系统在 18-36V 供电范围内稳定运行

PLC 数据监控与模块运行状态

总结

这个项目让我完整经历了从需求分析、硬件选型、原理图设计、PCB Layout、焊接调试到固件开发的全流程。模块化的设计思路使系统具备良好的可扩展性——后续只需设计新的功能板(如模拟量输入输出板),挂载到内部总线上即可。软件的封装式设计也使协议栈和驱动代码可以方便地移植到其他 MCU 平台。