基于STM32F103的PLC开发板设计:从硬件到步进电机控制
在工业控制领域PLC可编程逻辑控制器作为核心控制设备广泛应用于各种自动化场景。传统PLC价格昂贵且功能固定而基于STM32F103自主设计的PLC开发板则提供了高度定制化和成本优化的解决方案。这种开发板不仅具备开关量输入输出、模拟量采集等基本PLC功能还能直接驱动两路步进电机特别适合小型自动化设备、实验装置和教学演示场景。对于嵌入式开发者和工业自动化工程师来说理解如何基于STM32F103构建PLC系统具有重要实践价值。本文将完整介绍从硬件设计到软件实现的整个开发流程重点解析开关量输入的光耦隔离、模拟量输入的ADC配置、开关量输出的继电器驱动以及步进电机的脉冲控制等关键技术要点。1. STM32F103作为PLC主控芯片的优势与选型考量STM32F103系列是意法半导体推出的基于ARM Cortex-M3内核的32位微控制器在工业控制领域应用广泛。其作为PLC主控芯片的核心优势在于丰富的外设资源和实时处理能力。1.1 核心性能参数与工业适用性STM32F103主频可达72MHz具备单周期乘法和硬件除法器能够满足多数工业控制场景的实时性要求。芯片内置的Flash存储空间从64KB到512KB不等RAM从20KB到64KB为PLC梯形图解释器和用户程序提供了充足的存储空间。在外设方面STM32F103通常包含多个USART、SPI、I2C接口便于扩展通信模块具备2-3个ADC模块支持12位精度模拟量采集定时器资源丰富特别适合产生步进电机控制所需的精确脉冲信号。1.2 与传统PLC芯片的对比优势与传统专用PLC芯片相比STM32F103具有明显的灵活性优势。开发者可以完全掌控底层硬件根据实际需求定制功能而不受限于厂商提供的封闭平台。成本方面STM32F103芯片价格通常在10-30元人民币区间整体BOM成本远低于商业PLC。但需要注意的是基于STM32F103自研PLC需要自行实现PLC运行时环境、通信协议栈和安全机制这对开发团队的技术能力提出了一定要求。2. PLC开发板硬件架构设计与关键电路一个完整的PLC开发板需要包含电源管理、输入隔离、输出驱动和通信接口等基本模块。合理的硬件设计是系统稳定运行的基础。2.1 电源电路设计工业现场电源环境复杂PLC开发板需要具备宽电压输入和抗干扰能力。典型设计采用24VDC工业标准电源输入通过DC-DC降压芯片转换为5V和3.3V为系统供电。关键设计要点包括输入电源反接保护在电源入口处串联二极管或使用MOS管实现防反接过压过流保护采用TVS管和自恢复保险丝防护浪涌电流电源隔离数字部分与模拟部分使用磁珠或隔离DC-DC分开供电退耦电容每个芯片电源引脚附近放置100瓦拉电容和10uF电解电容// 电源监控代码示例 void Power_Check(void) { if(ADC_GetValue(POWER_VOLTAGE_CH) POWER_MIN_VOLTAGE) { System_Shutdown(); // 电压过低时安全关机 } }2.2 开关量输入电路设计工业现场的开关量输入通常为24V电平需要通过光耦隔离转换为3.3V CMOS电平。每路开关量输入都应包含以下元件限流电阻根据输入电压计算合适阻值通常为2-10kΩ光耦如TLP521-4提供2500Vrms隔离电压上拉/下拉电阻确保未连接时的确定状态滤波电路RC滤波消除触点抖动典型8路开关量输入电路原理图设计24V输入 → 限流电阻 → 光耦发光二极管 → 地 光耦输出集电极 → 上拉电阻 → 3.3V 光耦输出发射极 → GPIO输入引脚2.3 模拟量输入电路设计模拟量输入用于采集传感器信号如0-10V电压或4-20mA电流。STM32F103内置的12位ADC能够满足多数应用精度要求。关键设计考虑信号调理运放电路进行电平转换和阻抗匹配抗混叠滤波RC低通滤波器抑制高频噪声参考电压使用精密基准源如REF3130提高ADC精度多路切换采用模拟开关如CD4051扩展采集通道对于4-20mA电流输入需要通过精密采样电阻转换为电压信号。250Ω电阻可将4-20mA转换为1-5V电压适合STM32F103的ADC输入范围。2.4 开关量输出电路设计开关量输出需要驱动继电器、接触器等负载每路输出应包含驱动芯片如ULN2003达林顿晶体管阵列续流二极管保护驱动芯片免受感性负载反电动势冲击状态指示LED直观显示输出状态过流保护自恢复保险丝或熔断器继电器输出适合驱动交流负载晶体管输出适合高频开关场合。根据实际负载特性选择合适的输出类型。2.5 步进电机驱动电路两路步进电机驱动可采用专用驱动芯片如A4986或TMC2209这些芯片将简单的脉冲方向信号转换为电机绕组电流。接线要点脉冲信号连接STM32F103定时器的PWM输出方向信号普通GPIO控制电机旋转方向使能信号控制电机使能状态电流设置通过参考电压或电阻设置电机相电流3. 软件开发环境搭建与基础配置PLC开发板的软件部分包括底层驱动、PLC运行时和用户程序解释器。使用STM32CubeIDE作为开发环境能够简化初始化流程。3.1 开发环境准备首先安装STM32CubeIDE和STM32CubeMXR32F1固件库。创建新项目时选择STM32F103系列对应型号配置系统时钟为72MHz。关键初始化步骤配置系统时钟树确保各总线时钟符合要求初始化GPIO设置输入输出模式和上下拉配置ADC设置采样时间和转换序列初始化定时器产生步进电机控制脉冲配置串口用于调试和通信3.2 硬件抽象层实现建立硬件抽象层HAL将底层操作封装为统一接口便于PLC运行时调用。// hal_digital_in.h typedef enum { DI_CHANNEL_1 0, DI_CHANNEL_2, // ... 其他通道 DI_CHANNEL_MAX } DI_Channel_t; uint8_t HAL_DI_GetValue(DI_Channel_t channel); // hal_stepper.h typedef enum { STEPPER_MOTOR_1 0, STEPPER_MOTOR_2 } StepperMotor_t; void HAL_Stepper_MoveSteps(StepperMotor_t motor, int32_t steps, uint32_t speed);3.3 PLC运行时环境设计PLC运行时负责循环扫描用户程序处理输入输出刷新。基本架构包括输入映像区存储开关量和模拟量输入状态输出映像区存储开关量输出状态用户程序区存储编译后的PLC指令系统定时器处理定时器和计数器功能typedef struct { uint8_t di_status[DI_CHANNEL_MAX]; // 开关量输入状态 uint16_t ai_value[AI_CHANNEL_MAX]; // 模拟量输入值 uint8_t do_status[DO_CHANNEL_MAX]; // 开关量输出状态 int32_t stepper_position[STEPPER_MAX]; // 步进电机位置 } PLC_IO_Image_t; void PLC_Runtime_Cycle(void) { // 1. 读取物理输入到输入映像区 PLC_ReadInputs(); // 2. 执行用户程序 PLC_ExecuteProgram(); // 3. 将输出映像区写入物理输出 PLC_WriteOutputs(); // 4. 处理通信和系统任务 PLC_SystemTasks(); }4. 关键功能模块实现与代码详解各功能模块的实现需要综合考虑实时性、可靠性和易用性。下面详细分析核心模块的实现方法。4.1 开关量输入处理与防抖动工业现场开关量输入往往存在触点抖动需要通过软件滤波确保信号稳定。#define DEBOUNCE_TIME_MS 10 // 防抖动时间10ms typedef struct { uint8_t current_state; // 当前状态 uint8_t stable_state; // 稳定状态 uint32_t last_change_time; // 上次状态变化时间 } DI_Debounce_t; DI_Debounce_t di_debounce[DI_CHANNEL_MAX]; void DI_Debounce_Process(void) { for(int i 0; i DI_CHANNEL_MAX; i) { uint8_t raw_state HAL_GPIO_ReadDI(i); if(raw_state ! di_debounce[i].current_state) { di_debounce[i].current_state raw_state; di_debounce[i].last_change_time HAL_GetTick(); } // 状态稳定时间超过防抖动时间 if(HAL_GetTick() - di_debounce[i].last_change_time DEBOUNCE_TIME_MS) { if(di_debounce[i].stable_state ! di_debounce[i].current_state) { di_debounce[i].stable_state di_debounce[i].current_state; // 触发状态变化事件 PLC_OnDIChange(i, di_debounce[i].stable_state); } } } }4.2 模拟量输入采集与滤波模拟量输入需要多次采样并采用数字滤波提高信号质量。#define ADC_OVERSAMPLE_TIMES 16 // 过采样倍数 uint16_t AI_GetFilteredValue(AI_Channel_t channel) { uint32_t sum 0; // 过采样采集 for(int i 0; i ADC_OVERSAMPLE_TIMES; i) { sum HAL_ADC_GetValue(channel); // 短暂延时避免ADC转换冲突 HAL_Delay(1); } // 滑动平均滤波 static uint16_t filter_buffer[AI_CHANNEL_MAX][FILTER_DEPTH] {0}; static uint8_t filter_index[AI_CHANNEL_MAX] {0}; uint16_t average sum / ADC_OVERSAMPLE_TIMES; filter_buffer[channel][filter_index[channel]] average; filter_index[channel] (filter_index[channel] 1) % FILTER_DEPTH; // 计算移动平均值 sum 0; for(int i 0; i FILTER_DEPTH; i) { sum filter_buffer[channel][i]; } return sum / FILTER_DEPTH; }4.3 步进电机控制算法步进电机控制需要精确的脉冲时序和加减速算法避免失步。typedef struct { int32_t target_position; // 目标位置 int32_t current_position; // 当前位置 uint32_t current_speed; // 当前速度 uint32_t acceleration; // 加速度 uint8_t is_moving; // 运动状态标志 } Stepper_Control_t; void Stepper_MoveToPosition(StepperMotor_t motor, int32_t position, uint32_t max_speed) { Stepper_Control_t* ctrl stepper_ctrl[motor]; ctrl-target_position position; ctrl-current_speed 0; ctrl-acceleration DEFAULT_ACCELERATION; ctrl-is_moving 1; // 设置定时器比较值生成脉冲 uint32_t pulse_period SystemCoreClock / max_speed / 2; HAL_TIM_SetCompare(htim2, TIM_CHANNEL_1, pulse_period); } // 定时器中断中处理速度曲线 void TIM2_IRQHandler(void) { if(__HAL_TIM_GET_FLAG(htim2, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(htim2, TIM_FLAG_UPDATE); for(int i 0; i STEPPER_MAX; i) { Stepper_Control_t* ctrl stepper_ctrl[i]; if(ctrl-is_moving) { // 梯形速度曲线计算 Stepper_UpdateSpeedProfile(ctrl); // 生成脉冲 HAL_GPIO_TogglePin(step_port[i], step_pin[i]); // 更新位置 if(ctrl-current_position ! ctrl-target_position) { ctrl-current_position (ctrl-target_position ctrl-current_position) ? 1 : -1; } else { ctrl-is_moving 0; // 到达目标位置 } } } } }4.4 PLC指令解释器实现简单的PLC指令解释器能够执行基本逻辑运算实现梯形图功能。typedef enum { PLC_CMD_LD 0, // 装载常开触点 PLC_CMD_LDI, // 装载常闭触点 PLC_CMD_AND, // 与操作 PLC_CMD_ANI, // 与非操作 PLC_CMD_OUT, // 输出线圈 PLC_CMD_END // 程序结束 } PLC_Command_t; typedef struct { PLC_Command_t cmd; // 指令类型 uint16_t operand; // 操作数 } PLC_Instruction_t; uint8_t PLC_ExecuteProgram(void) { uint8_t accumulator 0; // 逻辑累加器 uint16_t pc 0; // 程序计数器 while(1) { PLC_Instruction_t inst program_memory[pc]; switch(inst.cmd) { case PLC_CMD_LD: accumulator io_image.di_status[inst.operand]; break; case PLC_CMD_LDI: accumulator !io_image.di_status[inst.operand]; break; case PLC_CMD_AND: accumulator accumulator io_image.di_status[inst.operand]; break; case PLC_CMD_OUT: io_image.do_status[inst.operand] accumulator; break; case PLC_CMD_END: return 0; // 程序正常结束 default: return 1; // 无效指令错误 } } }5. 系统集成测试与功能验证完成各模块开发后需要进行系统集成测试确保硬件软件协同工作正常。5.1 硬件功能测试流程按照以下顺序验证各硬件模块功能电源测试测量各电压点是否正常检查纹波大小开关量输入测试逐路输入24V信号验证光耦隔离和状态读取模拟量输入测试输入标准电压电流信号验证ADC精度和线性度开关量输出测试控制输出点通断验证驱动能力和隔离效果步进电机测试发送脉冲信号验证电机转动和定位精度测试过程中使用示波器监测关键信号质量特别是步进电机脉冲波形和ADC参考电压稳定性。5.2 软件功能测试用例设计自动化测试用例验证软件功能void Test_PlcBasicLogic(void) { // 测试基本逻辑功能 printf(Testing basic PLC logic...\n); // 模拟输入信号 io_image.di_status[0] 1; // DI0接通 io_image.di_status[1] 0; // DI1断开 // 执行测试程序DI0 AND DI1 - DO0 PLC_ExecuteProgram(); // 验证输出结果 assert(io_image.do_status[0] 0); // 与运算结果应为0 printf(Basic logic test passed.\n); } void Test_StepperPositioning(void) { printf(Testing stepper motor positioning...\n); // 命令电机移动1000步 Stepper_MoveToPosition(STEPPER_MOTOR_1, 1000, 1000); // 等待定位完成 while(stepper_ctrl[STEPPER_MOTOR_1].is_moving) { HAL_Delay(1); } // 验证位置 assert(stepper_ctrl[STEPPER_MOTOR_1].current_position 1000); printf(Stepper positioning test passed.\n); }5.3 性能指标验证系统性能验证应包括以下关键指标扫描周期典型PLC应用要求扫描周期小于10ms模拟量采集精度12位ADC应达到±0.1%FS精度步进电机定位精度整步模式下无丢失脉冲温度稳定性-40℃到85℃范围内功能正常EMC抗干扰通过静电放电和电快速瞬变脉冲群测试使用以下代码测量系统扫描周期uint32_t scan_time_start, scan_time_end, max_scan_time 0; void Measure_Performance(void) { scan_time_start HAL_GetTick(); // 执行一个完整的PLC扫描周期 PLC_Runtime_Cycle(); scan_time_end HAL_GetTick(); uint32_t scan_time scan_time_end - scan_time_start; if(scan_time max_scan_time) { max_scan_time scan_time; printf(New max scan time: %lu ms\n, max_scan_time); } }6. 常见问题排查与解决方案在实际开发和应用过程中会遇到各种问题系统化的排查方法能够快速定位故障点。6.1 硬件常见问题及处理问题现象可能原因检查方法解决方案电源模块发热严重负载过重或短路测量各路线路电流检查负载连接增加散热片开关量输入无响应光耦损坏或接线错误测量光耦输入输出端电压更换光耦检查接线极性模拟量采集波动大参考电压不稳或接地不良测量ADC参考电压纹波增加参考源滤波电容改善接地步进电机失步脉冲频率过高或电流不足观察脉冲波形测量驱动电流降低速度调整驱动电流通信干扰严重未使用屏蔽线或接地不当检查电缆布线和接地使用屏蔽双绞线单点接地6.2 软件常见问题及处理问题1PLC程序执行异常现象输出逻辑不符合预期程序跑飞排查步骤检查程序存储器是否溢出验证指令解释器边界条件处理监控堆栈使用情况防止溢出检查看门狗复位情况// 增加程序执行监控 void PLC_Safety_Check(void) { if(program_counter PROGRAM_MEMORY_SIZE) { // 程序计数器越界系统复位 NVIC_SystemReset(); } // 看门狗喂狗 IWDG_ReloadCounter(); }问题2步进电机定位不准现象电机实际位置与指令位置存在偏差排查步骤检查脉冲波形是否完整验证加减速曲线是否合理检查电机电流设置是否合适测量机械传动间隙问题3模拟量采集值跳变现象稳定输入信号下ADC读数不稳定解决方案增加软件滤波算法优化ADC采样时序检查模拟地数字地隔离使用外部精密参考电压源6.3 电磁兼容性EMC问题处理工业环境电磁干扰严重需要采取额外措施保证系统稳定性。电源输入端增加共模电感、X/Y电容滤波信号线路使用屏蔽电缆信号线远离电源线PCB布局模拟数字部分分开大面积铺地软件容错增加CRC校验、超时重试机制7. 工程应用建议与扩展方向基于STM32F103的PLC开发板在实际工程应用中需要根据具体场景进行优化和扩展。7.1 生产环境部署注意事项将开发板转换为产品需要考虑以下因素环境适应性选择工业级元器件工作温度范围-40℃~85℃防护等级根据安装环境选择合适的外壳防护等级IP20~IP65认证要求满足CE、UL等相关安全认证标准维护性设计诊断接口和状态指示灯便于现场维护7.2 功能扩展建议根据应用需求可以考虑以下扩展功能通信扩展增加Ethernet、CAN总线、无线通信模块存储扩展添加SD卡存储数据记录和程序备份安全功能实现安全继电器、急停按钮双重化电路HMI接口提供触摸屏通信接口实现人机交互7.3 软件架构优化方向长期维护和功能扩展需要良好的软件架构模块化设计各功能模块独立便于测试和替换配置系统参数外置化支持在线修改和保存远程监控实现设备状态远程查询和故障诊断OTA升级支持远程固件升级降低维护成本// 模块化设计示例通信模块接口 typedef struct { int (*init)(void); int (*send)(uint8_t* data, uint32_t len); int (*receive)(uint8_t* buffer, uint32_t max_len); int (*deinit)(void); } Communication_Driver_t; // 支持多种通信方式 extern Communication_Driver_t uart_driver; extern Communication_Driver_t ethernet_driver; extern Communication_Driver_t can_driver;基于STM32F103的PLC开发板为中小型自动化项目提供了高性价比的解决方案。通过合理的硬件设计和软件架构可以实现与传统PLC相当的功能性能。开发过程中需要特别注意工业环境的特殊要求确保系统的可靠性和稳定性。这种自主设计的PLC系统特别适合需要定制化功能的场合为自动化工程师提供了更大的设计灵活性。