这次我们来详细分析一个基于STM32F103设计的PLC开发板项目。这个开发板集成了开关量输入、模拟量输入、开关量输出功能并且能够驱动两路步进电机对于想要学习工业控制系统或者进行自动化项目开发的工程师来说是个很实用的平台。从项目描述看这个开发板的核心价值在于将传统的PLC功能与STM32微控制器相结合提供了完整的工业控制解决方案。开关量输入可以接按钮、限位开关等数字信号模拟量输入支持电压电流信号的采集开关量输出控制继电器等执行机构而两路步进电机驱动能力使其在位置控制应用中具有优势。1. 核心能力速览能力项技术规格说明主控芯片STM32F103系列微控制器输入接口开关量输入、模拟量输入输出接口开关量输出、两路步进电机驱动通信接口预计支持USART、SPI、I2C等开发环境Keil MDK、STM32CubeIDE等编程语言C语言支持PLC梯形图转换适用场景工业自动化教学、小型控制系统开发、步进电机控制实验2. PLC与STM32结合的技术优势传统的PLC采用专用硬件和梯形图编程而基于STM32的PLC开发板结合了微控制器的灵活性和PLC的工业可靠性。STM32F103作为经典的Cortex-M3内核微控制器具有丰富的外设资源和较强的处理能力能够满足大多数工业控制场景的需求。这种设计方案的优势在于既保留了PLC在工业环境中的稳定性和易用性又发挥了STM32在程序复杂度和定制化方面的优势。开发者可以使用C语言进行底层开发也可以使用转换工具将梯形图程序转换为C代码兼顾了灵活性和开发效率。对于开关量处理STM32的GPIO端口可以直接读取数字信号状态通过光耦隔离后接入控制系统。模拟量输入则需要ADC模块将连续的电压或电流信号转换为数字值STM32F103内置的12位ADC能够满足大多数精度要求。3. 硬件架构设计分析从项目描述推断该开发板的硬件架构应该包含以下几个关键部分电源管理模块工业现场通常需要宽电压输入如12-24VDC开发板需要设计相应的电源转换电路为STM32核心板和外围接口提供稳定的工作电压。信号隔离模块工业环境存在各种干扰开关量输入输出通常需要光耦隔离模拟量输入可能需要线性光耦或隔离运放来提高抗干扰能力。STM32核心板作为控制中枢负责信号采集、逻辑运算、运动控制算法的执行。STM32F103的定时器资源特别适合步进电机的脉冲生成和精确控制。步进电机驱动电路两路步进电机驱动可能采用专用的驱动芯片如DRV8825、TMC2208等或者使用分立元件搭建的H桥电路。驱动电路需要提供足够的电流输出能力和保护功能。4. 开发环境搭建与工具链配置要开始这个项目的开发需要准备相应的软件工具链。推荐使用STM32CubeIDE作为主要的开发环境它集成了STM32CubeMX配置工具和Eclipse开发环境能够大幅提高开发效率。首先安装STM32CubeIDE然后通过STM32CubeMX对STM32F103进行引脚配置。根据开发板的具体设计需要配置GPIO引脚用于开关量输入输出ADC通道用于模拟量采集定时器用于步进电机脉冲生成串口用于程序调试和通信配置完成后生成工程代码框架在此基础上添加业务逻辑。对于PLC功能开发可以考虑使用开源PLC运行时库如OpenPLC或者自行实现梯形图到C代码的转换逻辑。5. 开关量输入输出编程实战开关量处理是PLC的基本功能在STM32中实现相对简单。以下是一个基本的开关量输入检测示例// 开关量输入检测函数 uint8_t Read_Digital_Input(uint8_t channel) { // 读取指定通道的输入状态 switch(channel) { case 0: return HAL_GPIO_ReadPin(DI0_GPIO_Port, DI0_Pin); case 1: return HAL_GPIO_ReadPin(DI1_GPIO_Port, DI1_Pin); // 更多通道... default: return 0; } } // 开关量输出控制函数 void Write_Digital_Output(uint8_t channel, uint8_t state) { switch(channel) { case 0: HAL_GPIO_WritePin(DO0_GPIO_Port, DO0_Pin, state); break; case 1: HAL_GPIO_WritePin(DO1_GPIO_Port, DO1_Pin, state); break; // 更多通道... } }在实际应用中需要添加去抖动处理来提高开关量输入的可靠性// 带去抖动的输入检测 uint8_t Read_Digital_Input_Debounce(uint8_t channel) { static uint32_t last_time[8] {0}; static uint8_t last_state[8] {0}; uint8_t current_state Read_Digital_Input(channel); if(current_state ! last_state[channel]) { if(HAL_GetTick() - last_time[channel] 10) // 10ms去抖动 { last_state[channel] current_state; last_time[channel] HAL_GetTick(); return current_state; } } return last_state[channel]; }6. 模拟量输入采集与处理模拟量输入需要配置STM32的ADC模块以下是一个完整的模拟量采集实现// ADC初始化配置 void ADC_Init(void) { ADC_ChannelConfTypeDef sConfig {0}; hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode ENABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 4; // 4个模拟量通道 HAL_ADC_Init(hadc1); // 配置通道0 sConfig.Channel ADC_CHANNEL_0; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); // 更多通道配置... } // 读取模拟量值 uint16_t Read_Analog_Input(uint8_t channel) { ADC_ChannelConfTypeDef sConfig {0}; // 设置要读取的通道 sConfig.Channel channel; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); return HAL_ADC_GetValue(hadc1); } // 将ADC值转换为实际工程值 float ADC_to_Engineering(uint16_t adc_value, float min_range, float max_range) { // 假设12位ADC0-3.3V输入 float voltage (adc_value * 3.3f) / 4095.0f; // 转换为工程值如4-20mA对应0-100度 return min_range (voltage * (max_range - min_range)) / 3.3f; }7. 步进电机驱动与控制算法两路步进电机的驱动是该项目的重要特性。步进电机控制需要精确的脉冲时序STM32的定时器非常适合这种应用// 步进电机控制结构体 typedef struct { GPIO_TypeDef* dir_port; uint16_t dir_pin; GPIO_TypeDef* step_port; uint16_t step_pin; uint32_t current_position; uint32_t target_position; uint16_t speed; // 脉冲频率 uint8_t direction; } Stepper_Motor; // 步进电机初始化 void Stepper_Init(Stepper_Motor* motor, GPIO_TypeDef* dir_port, uint16_t dir_pin, GPIO_TypeDef* step_port, uint16_t step_pin) { motor-dir_port dir_port; motor-dir_pin dir_pin; motor-step_port step_port; motor-step_pin step_pin; motor-current_position 0; motor-target_position 0; motor-speed 1000; // 默认1kHz motor-direction 0; } // 设置目标位置 void Stepper_Set_Target(Stepper_Motor* motor, uint32_t position) { motor-target_position position; if(position motor-current_position) { motor-direction 1; HAL_GPIO_WritePin(motor-dir_port, motor-dir_pin, GPIO_PIN_SET); } else { motor-direction 0; HAL_GPIO_WritePin(motor-dir_port, motor-dir_pin, GPIO_PIN_RESET); } } // 步进电机脉冲生成在定时器中断中调用 void Stepper_Generate_Pulse(Stepper_Motor* motor) { if(motor-current_position ! motor-target_position) { // 生成一个脉冲 HAL_GPIO_WritePin(motor-step_port, motor-step_pin, GPIO_PIN_SET); // 短暂延时后拉低实际使用中需要精确的时序控制 HAL_GPIO_WritePin(motor-step_port, motor-step_pin, GPIO_PIN_RESET); if(motor-direction) motor-current_position; else motor-current_position--; } }对于更复杂的运动控制可以实现加速度曲线控制// 梯形速度曲线控制 typedef struct { uint32_t acceleration; // 加速度步/秒² uint32_t deceleration; // 减速度 uint32_t max_speed; // 最大速度 uint32_t current_speed; // 当前速度 uint32_t distance_to_go; // 剩余距离 } Motion_Profile; void Calculate_Speed_Profile(Motion_Profile* profile, uint32_t total_steps) { // 计算加速段、匀速段、减速段步数 uint32_t accel_steps (profile-max_speed * profile-max_speed) / (2 * profile-acceleration); uint32_t decel_steps (profile-max_speed * profile-max_speed) / (2 * profile-deceleration); if(total_steps (accel_steps decel_steps)) { // 完整的梯形曲线 profile-distance_to_go total_steps; } else { // 三角曲线距离较短 profile-max_speed sqrt((total_steps * profile-acceleration * profile-deceleration) / (profile-acceleration profile-deceleration)); } }8. PLC功能逻辑实现在STM32上实现PLC功能核心是创建一个周期性的扫描循环模拟传统PLC的工作方式// PLC运行状态结构体 typedef struct { uint8_t digital_inputs[16]; // 开关量输入状态 uint8_t digital_outputs[16]; // 开关量输出状态 uint16_t analog_inputs[8]; // 模拟量输入值 uint16_t analog_outputs[8]; // 模拟量输出值 uint32_t scan_time; // 扫描周期时间 uint8_t run_mode; // 运行模式 } PLC_System; // PLC主循环 void PLC_Main_Loop(PLC_System* plc) { uint32_t start_time HAL_GetTick(); while(plc-run_mode) { // 1. 读取输入状态 Read_All_Inputs(plc); // 2. 执行用户逻辑程序 Execute_User_Program(plc); // 3. 更新输出状态 Update_All_Outputs(plc); // 4. 等待下一个扫描周期 while(HAL_GetTick() - start_time plc-scan_time) { // 空闲时处理通信等其他任务 Handle_Communication(); } start_time HAL_GetTick(); } } // 简单的梯形图逻辑执行示例 void Execute_Ladder_Logic(PLC_System* plc) { // 实现基本的与或非逻辑 // 示例Y0 X0 AND X1 if(plc-digital_inputs[0] plc-digital_inputs[1]) { plc-digital_outputs[0] 1; } else { plc-digital_outputs[0] 0; } // 定时器功能实现 static uint32_t timer_start 0; static uint8_t timer_running 0; if(plc-digital_inputs[2] !timer_running) { timer_start HAL_GetTick(); timer_running 1; } if(timer_running) { if(HAL_GetTick() - timer_start 5000) // 5秒定时 { plc-digital_outputs[1] 1; timer_running 0; } } }9. 通信接口与上位机集成工业控制系统通常需要与上位机进行通信STM32F103支持多种通信协议// Modbus RTU从站实现 void Modbus_Slave_Handler(UART_HandleTypeDef* huart) { uint8_t rx_buffer[256]; uint8_t tx_buffer[256]; if(HAL_UART_Receive(huart, rx_buffer, 8, 100) HAL_OK) { // 解析Modbus帧 uint8_t slave_addr rx_buffer[0]; uint8_t function_code rx_buffer[1]; uint16_t start_addr (rx_buffer[2] 8) | rx_buffer[3]; uint16_t quantity (rx_buffer[4] 8) | rx_buffer[5]; if(slave_addr MODBUS_SLAVE_ADDR) { switch(function_code) { case 0x03: // 读保持寄存器 Modbus_Read_Holding_Registers(start_addr, quantity, tx_buffer); break; case 0x06: // 写单个寄存器 Modbus_Write_Single_Register(start_addr, quantity, tx_buffer); break; case 0x10: // 写多个寄存器 Modbus_Write_Multiple_Registers(start_addr, quantity, tx_buffer); break; } HAL_UART_Transmit(huart, tx_buffer, Calculate_Modbus_Frame_Length(tx_buffer), 100); } } } // 自定义通信协议实现 typedef struct { uint8_t header[2]; // 帧头 uint8_t command; // 命令字 uint8_t length; // 数据长度 uint8_t data[32]; // 数据域 uint8_t checksum; // 校验和 } Custom_Protocol_Frame; void Custom_Protocol_Handler(uint8_t* data, uint8_t len) { Custom_Protocol_Frame* frame (Custom_Protocol_Frame*)data; // 校验帧完整性 if(Verify_Checksum(frame)) { switch(frame-command) { case CMD_READ_DIGITAL_INPUTS: Handle_Read_Digital_Inputs(frame); break; case CMD_WRITE_DIGITAL_OUTPUTS: Handle_Write_Digital_Outputs(frame); break; case CMD_CONTROL_STEPPER: Handle_Stepper_Control(frame); break; } } }10. 系统调试与故障排查在实际开发过程中调试是至关重要的环节。以下是一些实用的调试技巧使用串口调试输出// 重定向printf到串口 int _write(int file, char *ptr, int len) { HAL_UART_Transmit(huart1, (uint8_t*)ptr, len, 1000); return len; } // 调试信息输出 void Debug_Print_System_Status(PLC_System* plc) { printf(PLC System Status:\r\n); printf(Digital Inputs: ); for(int i 0; i 8; i) { printf(%d , plc-digital_inputs[i]); } printf(\r\n); printf(Scan Time: %lums\r\n, plc-scan_time); }常见问题排查清单问题现象可能原因排查方法开关量输入无响应光耦损坏、接线错误、GPIO配置错误检查硬件连接、测量信号电压、验证GPIO配置模拟量读数不稳定电源噪声、接地问题、ADC配置错误检查电源质量、添加滤波电容、验证ADC采样时间步进电机不转动驱动芯片故障、脉冲时序错误、电流设置不当检查驱动芯片供电、测量脉冲信号、调整电流设置通信连接失败波特率不匹配、线路干扰、协议错误验证通信参数、检查线路质量、使用逻辑分析仪抓包系统监控与看门狗// 独立看门狗配置 void IWDG_Init(void) { hiwdg.Instance IWDG; hiwdg.Init.Prescaler IWDG_PRESCALER_32; hiwdg.Init.Reload 0x0FFF; hiwdg.Init.Window 0x0FFF; HAL_IWDG_Init(hiwdg); } // 定期喂狗 void Task_Monitor(void) { // 主循环中定期调用 HAL_IWDG_Refresh(hiwdg); // 检查堆栈使用情况 Check_Stack_Usage(); // 记录系统运行时间 Log_System_Uptime(); }11. 项目扩展与进阶应用在基础功能实现后可以考虑以下扩展方向多轴协调运动控制利用两路步进电机实现简单的XY平台控制或双轴协调运动。PID控制算法集成在模拟量控制中引入PID算法提高控制精度。工业以太网通信通过扩展模块增加Ethernet或PROFINET通信能力。HMI人机界面连接触摸屏实现参数设置和状态监控。安全功能实现添加急停按钮、安全继电器等工业安全元件。这个基于STM32F103的PLC开发板项目为工业控制学习提供了完整的实践平台从基础的开关量处理到复杂的运动控制涵盖了工业自动化的核心技术和应用场景。通过实际动手开发可以深入理解PLC工作原理和嵌入式系统在工业控制中的应用价值。