基于STM32F103的PLC开发板设计方案与实现

基于STM32F103的PLC开发板设计方案与实现
在工业自动化项目中很多开发者会遇到传统PLC成本高、扩展性差的问题而基于STM32F103自主设计PLC开发板既能满足控制需求又能大幅降低成本。本文将完整分享一套基于STM32F103的PLC开发板设计方案包含开关量输入输出、模拟量采集、双路步进电机驱动等实用功能提供可复用的硬件设计和嵌入式代码。1. 项目背景与核心需求1.1 为什么选择STM32F103做PLC开发板STM32F103作为意法半导体经典的Cortex-M3内核微控制器具有72MHz主频、丰富的外设资源和良好的生态支持非常适合工业控制场景。相比传统PLC基于STM32F103的开发板具有以下优势成本可控核心芯片价格仅为高端PLC的1/5甚至更低灵活可扩展可根据具体需求定制功能模块开发门槛低有完善的STM32生态和丰富的学习资料性能足够满足大多数中小型工业控制场景的需求1.2 PLC开发板核心功能规划本方案设计的PLC开发板主要包含以下功能模块开关量输入8路光耦隔离数字输入支持24V工业标准开关量输出8路继电器输出支持220VAC/10A负载模拟量输入4路16位ADC采集支持0-10V电压/4-20mA电流步进电机驱动两路步进电机驱动支持脉冲方向控制通信接口RS485、CAN总线工业通信接口人机交互LCD显示屏和按键输入2. 硬件电路设计详解2.1 核心控制器电路设计STM32F103C8T6作为主控芯片需要设计最小系统电路// 电源部分设计要点 // 1. 3.3V稳压电路AMS1117-3.3V // 2. 退耦电容10uF电解电容 100nF陶瓷电容 // 3. 复位电路10K上拉电阻 100nF电容 // 4. 晶振电路8MHz主晶振 32.768KHz RTC晶振 // PCB布局注意事项 // - 模拟和数字电源分开走线 // - 晶振尽量靠近芯片引脚 // - 退耦电容靠近电源引脚2.2 开关量输入电路设计工业环境的开关量输入需要电气隔离和抗干扰设计// 光耦隔离输入电路 // 使用TLP521-4光耦每路包含 // - 输入限流电阻2.2KΩ/1W // - 滤波电容100nF // - 上拉电阻10KΩ到3.3V // 典型接线方式 // IN1 → 24V正极 → 现场开关 → 24V负极 // 当现场开关闭合时光耦导通STM32检测到低电平2.3 模拟量输入电路设计模拟量采集需要信号调理和ADC转换// 电压输入通道0-10V // 分压电阻10KΩ 2KΩ将0-10V分压到0-2V // 运放跟随LM358做电压跟随器 // RC滤波100Ω 100nF低通滤波 // 电流输入通道4-20mA // 采样电阻250Ω将4-20mA转换为1-5V // 后续处理同电压通道 // ADC配置代码片段 void ADC_Config(void) { ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 4; ADC_Init(ADC1, ADC_InitStructure); }2.4 步进电机驱动电路采用专用驱动芯片TB6600实现步进电机控制// 步进电机驱动接口 // - PUL PUL-脉冲信号 // - DIR DIR-方向信号 // - ENA ENA-使能信号 // STM32控制代码 void Stepper_Motor_Control(uint8_t motor_id, uint32_t steps, uint8_t direction) { // 设置方向 if(motor_id 0) { GPIO_WriteBit(GPIOA, GPIO_Pin_0, direction ? Bit_SET : Bit_RESET); } else { GPIO_WriteBit(GPIOA, GPIO_Pin_1, direction ? Bit_SET : Bit_RESET); } // 发送脉冲 for(uint32_t i 0; i steps; i) { GPIO_SetBits(GPIOA, motor_id ? GPIO_Pin_3 : GPIO_Pin_2); delay_us(10); GPIO_ResetBits(GPIOA, motor_id ? GPIO_Pin_3 : GPIO_Pin_2); delay_us(1000); // 脉冲间隔控制速度 } }3. 软件架构设计3.1 主程序框架设计采用前后台系统架构确保实时性要求// main.c 主程序框架 int main(void) { // 硬件初始化 System_Init(); GPIO_Config(); ADC_Config(); Timer_Config(); UART_Config(); // 外设初始化 LCD_Init(); Key_Init(); Relay_Init(); Stepper_Init(); while(1) { // 后台任务调度 Task_ADC_Scan(); // ADC扫描任务 Task_DI_Scan(); // 开关量输入扫描 Task_Communication(); // 通信处理 Task_Display(); // 显示更新 // 实时性要求高的任务在中断中处理 // 步进电机控制、通信接收等在中断服务函数中处理 } }3.2 开关量处理模块开关量输入需要去抖动处理输出需要状态保持// 开关量输入去抖动处理 typedef struct { uint8_t current_state; uint8_t stable_state; uint8_t debounce_counter; uint8_t debounce_threshold; } DI_Channel_t; void DI_Debounce_Handler(DI_Channel_t *channel, uint8_t raw_input) { if(raw_input ! channel-current_state) { channel-current_state raw_input; channel-debounce_counter 0; } else { if(channel-debounce_counter channel-debounce_threshold) { channel-debounce_counter; } else { channel-stable_state raw_input; } } } // 开关量输出状态管理 void DO_Write_Channel(uint8_t channel, uint8_t state) { if(channel MAX_DO_CHANNELS) { g_do_state[channel] state; Relay_Set(channel, state); // 记录操作日志便于调试 Log_Write(DO_CH%d set to %d, channel, state); } }3.3 模拟量采集与处理模拟量采集需要滤波和标度变换// 模拟量通道数据结构 typedef struct { uint16_t raw_value; float filtered_value; float physical_value; uint8_t channel_type; // 0:电压, 1:电流 } AI_Channel_t; // 滑动平均滤波算法 float Moving_Average_Filter(float new_value, float *buffer, uint8_t size) { static uint8_t index 0; static float sum 0; sum - buffer[index]; buffer[index] new_value; sum new_value; index (index 1) % size; return sum / size; } // 工程值转换 float AI_Convert_to_Engineering(uint16_t adc_value, uint8_t type) { float voltage (adc_value * 3.3f) / 4095.0f; if(type 0) { // 电压输入0-10V return voltage * 5.0f; // 考虑分压比 } else { // 电流输入4-20mA return (voltage - 1.0f) * 16.0f / 4.0f 4.0f; } }4. 步进电机控制算法4.1 步进电机基础控制实现加减速曲线控制避免失步// 步进电机控制参数结构 typedef struct { uint32_t current_speed; // 当前速度脉冲频率 uint32_t target_speed; // 目标速度 uint32_t acceleration; // 加速度 uint32_t deceleration; // 减速度 uint32_t total_steps; // 总步数 uint32_t moved_steps; // 已走步数 uint8_t direction; // 方向 uint8_t running; // 运行状态 } Stepper_Motor_t; // S曲线加减速算法 void S_Curve_Acceleration(Stepper_Motor_t *motor) { if(!motor-running) return; // 加速阶段 if(motor-moved_steps motor-total_steps / 3) { motor-current_speed motor-acceleration; if(motor-current_speed motor-target_speed) { motor-current_speed motor-target_speed; } } // 匀速阶段 else if(motor-moved_steps motor-total_steps * 2 / 3) { motor-current_speed motor-target_speed; } // 减速阶段 else { motor-current_speed - motor-deceleration; if(motor-current_speed 100) { // 最小速度 motor-current_speed 100; } } }4.2 多轴协调运动控制实现两路步进电机的同步控制// 多轴协调控制结构 typedef struct { Stepper_Motor_t motor[2]; uint8_t sync_mode; // 同步模式 float ratio; // 速度比例 } Multi_Axis_Controller_t; void Multi_Axis_Move(Multi_Axis_Controller_t *controller, int32_t steps1, int32_t steps2, uint32_t speed) { // 计算速度比例 if(steps1 ! 0 steps2 ! 0) { controller-ratio (float)steps2 / (float)steps1; } // 设置电机参数 controller-motor[0].total_steps abs(steps1); controller-motor[0].direction steps1 0 ? 1 : 0; controller-motor[0].target_speed speed; controller-motor[1].total_steps abs(steps2); controller-motor[1].direction steps2 0 ? 1 : 0; controller-motor[1].target_speed speed * controller-ratio; // 启动电机 controller-motor[0].running 1; controller-motor[1].running 1; }5. 通信协议设计5.1 Modbus RTU通信协议实现标准的Modbus RTU从站协议// Modbus协议处理函数 uint8_t Modbus_RTU_Process(uint8_t *request, uint8_t *response) { uint8_t slave_addr request[0]; uint8_t function_code request[1]; uint16_t start_addr (request[2] 8) | request[3]; uint16_t quantity (request[4] 8) | request[5]; // 检查本机地址 if(slave_addr ! MODBUS_SLAVE_ADDR slave_addr ! 0xFF) { return 0; } switch(function_code) { case 0x01: // 读线圈 return Read_Coils(start_addr, quantity, response); case 0x03: // 读保持寄存器 return Read_Holding_Registers(start_addr, quantity, response); case 0x05: // 写单个线圈 return Write_Single_Coil(start_addr, quantity, response); case 0x06: // 写单个寄存器 return Write_Single_Register(start_addr, quantity, response); default: return Exception_Response(response, 0x01); // 非法功能码 } } // 读保持寄存器实现 uint8_t Read_Holding_Registers(uint16_t start_addr, uint16_t quantity, uint8_t *response) { if(quantity 125 || (start_addr quantity) MODBUS_MAX_REGISTERS) { return Exception_Response(response, 0x03); // 非法数据值 } response[0] MODBUS_SLAVE_ADDR; response[1] 0x03; response[2] quantity * 2; for(int i 0; i quantity; i) { uint16_t reg_value Get_Register_Value(start_addr i); response[3 i * 2] reg_value 8; response[4 i * 2] reg_value 0xFF; } return 3 quantity * 2; // 返回响应长度 }5.2 自定义通信协议针对特定应用设计轻量级协议// 自定义协议帧结构 #pragma pack(1) typedef struct { uint8_t header; // 帧头 0xAA uint8_t cmd; // 命令字 uint8_t len; // 数据长度 uint8_t data[32]; // 数据域 uint8_t checksum; // 校验和 } Custom_Protocol_Frame_t; #pragma pack() // 协议命令处理 void Custom_Protocol_Handler(uint8_t *data, uint8_t len) { Custom_Protocol_Frame_t *frame (Custom_Protocol_Frame_t *)data; // 校验帧头和数据完整性 if(frame-header ! 0xAA || !Check_Checksum(data, len)) { return; } switch(frame-cmd) { case CMD_READ_AI: // 读取模拟量 Handle_Read_AI(frame); break; case CMD_WRITE_DO: // 写数字输出 Handle_Write_DO(frame); break; case CMD_MOTOR_CTRL: // 电机控制 Handle_Motor_Control(frame); break; } }6. PCB设计与布局要点6.1 电源分区设计工业控制板的电源设计至关重要电源架构 24V输入 → DC-DC降压 → 12V继电器驱动 ↓ LDO稳压 → 5V光耦、运放 ↓ LDO稳压 → 3.3VSTM32、逻辑芯片 布局要点 - 功率器件DC-DC靠近板边远离模拟部分 - 每级电源增加π型滤波 - 模拟部分使用独立的LDO供电 - 大面积铺地数字地和模拟地单点连接6.2 信号完整性考虑工业环境电磁干扰严重需要特别注意// PCB布局规则 // 1. 高频信号晶振、时钟尽量短包地处理 // 2. 模拟信号远离数字信号特别是PWM信号 // 3. 继电器线圈增加续流二极管 // 4. RS485接口增加TVS管防护 // 5. 电源入口增加共模电感 // 典型层叠结构4层板 // Top Layer信号线、元器件 // Inner13.3V电源平面 // Inner2GND地平面 // Bottom Layer模拟信号、电源走线7. 系统调试与测试方法7.1 硬件调试步骤按照模块化思路逐步调试// 调试顺序 // 1. 电源电路检查各电压点是否正常 // 2. 最小系统测试晶振、复位、下载功能 // 3. 数字输入用开关测试光耦隔离电路 // 4. 数字输出测试继电器动作和指示灯 // 5. 模拟输入用可调电源测试ADC精度 // 6. 电机驱动单独测试每路步进电机 // 7. 通信接口测试RS485收发功能 // 常用调试工具 // - 万用表测量电压、电阻 // - 示波器观察信号波形 // - 逻辑分析仪分析数字信号时序 // - 串口调试助手测试通信协议7.2 软件调试技巧嵌入式系统调试需要系统的方法// 调试日志系统 void Debug_Log(const char *format, ...) { va_list args; va_start(args, format); char buffer[128]; vsprintf(buffer, format, args); // 通过串口输出日志 UART_Send_String(buffer); va_end(args); } // 断言检查 #define ASSERT(condition) \ do { \ if(!(condition)) { \ Debug_Log(Assert failed: %s at %s:%d, \ #condition, __FILE__, __LINE__); \ while(1); \ } \ } while(0) // 内存使用监控 void Check_Stack_Usage(void) { uint32_t stack_used Stack_Memory_Used(); if(stack_used STACK_WARNING_THRESHOLD) { Debug_Log(Stack usage warning: %lu bytes, stack_used); } }8. 常见问题与解决方案8.1 硬件常见问题排查问题现象可能原因解决方案上电不工作电源反接、短路检查电源极性测量对地电阻ADC采集不准参考电压不稳、信号干扰增加参考电压滤波优化PCB布局通信异常终端电阻未接、波特率错误检查120Ω终端电阻确认波特率设置电机振动大驱动电流不足、加速度过大调整驱动电流优化加减速曲线8.2 软件调试问题// 典型软件问题及解决方法 // 问题1程序跑飞或死机 // 原因堆栈溢出、中断冲突、硬件错误 // 解决增加看门狗检查中断优先级使用HardFault调试 // 问题2ADC采样值跳动大 // 原因电源噪声、信号调理电路问题 // 解决优化滤波算法检查PCB布局增加软件滤波 // 问题3步进电机失步 // 原因脉冲频率过高、加速度太大 // 解决降低最高速度优化加减速曲线检查电机电流 // 问题4通信数据错误 // 原因波特率偏差、电磁干扰 // 解决校准时钟源增加通信超时重发机制9. 项目优化与扩展方向9.1 性能优化建议进一步提升系统可靠性// 软件优化措施 // 1. 使用DMA传输减少CPU占用 void ADC_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_buffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize ADC_CHANNELS; DMA_Init(DMA1_Channel1, DMA_InitStructure); } // 2. 优化中断服务函数 void TIM2_IRQHandler(void) // 步进电机脉冲中断 { if(TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 精简的中断处理代码 Stepper_Pulse_Handler(); } } // 3. 电源管理优化 void Enter_Low_Power_Mode(void) { // 无任务时进入睡眠模式 if(No_Active_Task()) { PWR_EnterSleepMode(PWR_Regulator_LowPower, PWR_SLEEPEntry_WFI); } }9.2 功能扩展思路根据实际需求可扩展以下功能// 扩展功能示例 // 1. 以太网通信 // 增加W5500等以太网模块实现TCP/IP通信 void Ethernet_Communication_Init(void) { // 初始化以太网模块 // 实现Modbus TCP协议 } // 2. SD卡数据存储 // 增加SDIO接口实现数据记录功能 void SD_Card_Data_Logging(void) { // 创建文件系统 // 定时保存运行数据 } // 3. 触摸屏人机界面 // 替换LCD为触摸屏实现更丰富的交互 void Touch_Screen_Interface(void) { // 实现图形界面 // 增加参数设置功能 } // 4. 无线通信模块 // 增加4G/NB-IoT模块实现远程监控 void Wireless_Communication_Init(void) { // 初始化无线模块 // 实现MQTT协议上传数据 }10. 实际应用案例10.1 小型自动化设备控制适用于包装机、灌装机等设备// 包装机控制逻辑示例 void Packaging_Machine_Control(void) { // 检测物料到位DI输入 if(DI_Read(MATERIAL_SENSOR)) { // 启动步进电机送料 Stepper_Move(0, 1000, 1); // 1000步正方向 // 延时后执行封装 delay_ms(500); DO_Write(HEATER_RELAY, 1); delay_ms(1000); DO_Write(HEATER_RELAY, 0); // 完成计数 g_package_count; Update_Display(); } }10.2 环境监测系统结合模拟量传感器实现环境监控// 温湿度监测应用 void Environment_Monitoring_Task(void) { // 读取温度传感器PT100 float temperature AI_Read_Channel(TEMP_CHANNEL); // 读取湿度传感器 float humidity AI_Read_Channel(HUMIDITY_CHANNEL); // 超限报警 if(temperature TEMP_MAX_LIMIT) { DO_Write(ALARM_BUZZER, 1); DO_Write(COOLING_FAN, 1); } else { DO_Write(ALARM_BUZZER, 0); } // 通过RS485上传数据 Modbus_Write_Register(TEMP_REG_ADDR, (uint16_t)(temperature * 10)); Modbus_Write_Register(HUMIDITY_REG_ADDR, (uint16_t)(humidity * 10)); }本设计方案提供了完整的STM32F103 PLC开发板实现方案从硬件设计到软件编程都给出了详细的可执行代码。在实际项目中可以根据具体需求调整功能模块和参数设置。这种自主设计的PLC方案不仅成本低廉而且具有很好的灵活性和可扩展性特别适合中小型自动化项目和教学实验使用。开发过程中要特别注意工业环境的可靠性和抗干扰设计包括电源隔离、信号滤波、看门狗保护等措施。建议先在实验室环境下完成基本功能测试再逐步应用到工业现场。