基于STM32F103的PLC开发板设计:低成本工业控制解决方案
这次我们来看一个基于STM32F103设计的PLC开发板项目这个板子集成了开关量输入、模拟量输入、开关量输出还能驱动两路步进电机对于想学习工业控制或者做小型自动化项目的开发者来说是个很实用的选择。这个开发板的核心价值在于用成本更低的STM32F103实现了传统PLC的核心功能特别适合教学实验、小型自动化设备控制和物联网边缘节点应用。板载资源包括多路数字IO、模拟量采集ADC、步进电机驱动电路基本上覆盖了工业现场最常见的控制需求。1. 核心能力速览能力项规格说明主控芯片STM32F103C8T672MHz Cortex-M3数字IO开关量输入x8开关量输出x8模拟量输入支持0-3.3V电压输入12位ADC精度电机驱动可同时驱动2路步进电机通信接口UART、I2C、SPI可扩展RS485编程方式支持Keil、STM32CubeIDE、PlatformIO供电电压DC 12-24V工业标准电源适合场景教学实验、小型PLC系统、自动化设备控制2. 适用场景与使用边界这个开发板最适合以下几类用户自动化相关专业的学生和教师用于PLC原理教学和实验小型自动化设备开发者需要低成本的控制解决方案物联网边缘计算项目需要采集现场传感器数据并执行控制传统PLC编程学习者想了解底层硬件实现原理使用边界需要特别注意不适合直接用于高可靠性工业现场需要额外的隔离和保护电路模拟量输入范围有限0-3.3V需要外接信号调理电路处理工业标准信号输出驱动能力有限大功率负载需要外加继电器或固态继电器工作温度范围为民品级高温工业环境需要特殊考虑3. 环境准备与前置条件要开始使用这个PLC开发板需要准备以下软硬件环境硬件准备STM32F103 PLC开发板本体ST-Link V2调试器或J-LinkDC 12-24V工业电源步进电机28BYJ-48或42步进电机杜邦线、按钮、传感器等外设软件环境Keil MDK-ARM或STM32CubeIDESTM32CubeMX配置工具串口调试助手如SecureCRT、Putty对应的设备驱动ST-Link驱动等基础技能要求基本的C语言编程能力STM32开发基础GPIO、定时器、中断电路基础知识能看懂简单原理图4. 硬件设计与接口说明4.1 核心电路设计开发板以STM32F103C8T6为核心这款芯片具有64KB Flash、20KB RAM72MHz主频完全能满足一般PLC应用需求。电源部分采用LM2596开关稳压芯片支持宽电压输入12-24V提供稳定的3.3V和5V输出。数字IO接口都做了光耦隔离或电平转换提高抗干扰能力。模拟量输入通道通过RC滤波电路后接入STM32的ADC引脚确保采样稳定性。4.2 步进电机驱动电路两路步进电机驱动采用ULN2003或DRV8825驱动芯片。ULN2003适合驱动28BYJ-48等小型步进电机而DRV8825支持更高电流的42步进电机。驱动电路包含电流调节、细分设置接口可以通过跳线配置不同的工作模式。// 步进电机驱动基础代码示例 typedef enum { STEPPER_CW 0, // 顺时针 STEPPER_CCW 1 // 逆时针 } Stepper_Direction; void Stepper_Move(uint8_t channel, int steps, Stepper_Direction dir, uint16_t speed) { // 设置方向 HAL_GPIO_WritePin(DIR_GPIO_Port, DIR_Pin, dir); // 生成脉冲 for(int i 0; i steps; i) { HAL_GPIO_WritePin(STEP_GPIO_Port, STEP_Pin, GPIO_PIN_SET); HAL_Delay(speed); HAL_GPIO_WritePin(STEP_GPIO_Port, STEP_Pin, GPIO_PIN_RESET); HAL_Delay(speed); } }4.3 接口定义与引脚分配开发板引脚分配经过优化确保功能模块互不干扰开关量输入PA0-PA78路支持中断检测 开关量输出PB0-PB78路推挽输出 模拟量输入PC0-PC34路12位ADC 步进电机1PE2方向PE3脉冲 步进电机2PE4方向PE5脉冲 通信接口USART1调试USART2RS4855. 软件开发环境搭建5.1 工程创建与配置使用STM32CubeMX快速创建工程打开STM32CubeMX选择STM32F103C8Tx芯片配置时钟树HSE 8MHzPLL到72MHz使能GPIO配置输入输出引脚设置合适的上拉/下拉配置ADC设置采样时间、触发方式配置定时器用于步进电机脉冲生成、PWM输出生成代码选择MDK-ARM或TrueSTUDIO// 系统时钟配置示例 void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; // 配置HSE和PLL RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; HAL_RCC_OscConfig(RCC_OscInitStruct); }5.2 基础驱动库实现创建模块化的驱动库便于代码复用// plc_io.h - IO操作接口定义 #ifndef __PLC_IO_H #define __PLC_IO_H #include stm32f1xx_hal.h // 开关量输入状态读取 uint8_t PLC_Read_DigitalInput(uint8_t channel); // 开关量输出控制 void PLC_Write_DigitalOutput(uint8_t channel, uint8_t state); // 模拟量读取 uint16_t PLC_Read_AnalogInput(uint8_t channel); // 步进电机控制 void PLC_Stepper_Control(uint8_t stepper_id, int32_t steps, uint16_t rpm); #endif6. 功能测试与效果验证6.1 开关量输入输出测试测试目的验证数字IO功能的正确性和响应速度测试步骤连接按钮到开关量输入通道0连接LED到开关量输出通道0编写测试程序当输入0检测到高电平时输出0点亮LED编译下载程序按下按钮观察LED响应// 开关量测试代码 void Digital_IOTest(void) { while(1) { // 读取输入状态 uint8_t input_state HAL_GPIO_ReadPin(INPUT0_GPIO_Port, INPUT0_Pin); // 控制输出 HAL_GPIO_WritePin(OUTPUT0_GPIO_Port, OUTPUT0_Pin, input_state); HAL_Delay(10); // 10ms扫描周期 } }预期结果按钮按下时LED立即点亮松开时熄灭响应延迟小于20ms6.2 模拟量采集测试测试目的验证ADC采集精度和稳定性测试步骤使用电位器提供可调电压0-3.3V到模拟量输入通道0编写ADC采集程序连续采样并转换为实际电压值通过串口输出采集结果与万用表测量值对比// 模拟量采集测试 void Analog_Test(void) { ADC_ChannelConfTypeDef sConfig {0}; uint16_t adc_value; float voltage; // 配置ADC通道 sConfig.Channel ADC_CHANNEL_0; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES5; HAL_ADC_ConfigChannel(hadc1, sConfig); while(1) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); adc_value HAL_ADC_GetValue(hadc1); voltage (adc_value * 3.3) / 4095.0; // 转换为电压值 printf(ADC值: %d, 电压: %.2fV\r\n, adc_value, voltage); HAL_Delay(1000); } }判断标准采集误差小于0.1V采样值稳定无跳变6.3 步进电机驱动测试测试目的验证步进电机控制精度和运行平稳性测试步骤连接28BYJ-48步进电机到驱动接口1编写控制程序让电机正转180度暂停1秒再反转180度观察电机运行是否平稳测量实际旋转角度// 步进电机角度控制 void Stepper_AngleTest(void) { // 28BYJ-48步进电机参数64步/圈减速比1:64 const int STEPS_PER_REVOLUTION 64 * 64; // 4096步/圈 // 正转180度2048步 PLC_Stepper_Control(1, 2048, 60); // 60RPM HAL_Delay(1000); // 反转180度 PLC_Stepper_Control(1, -2048, 60); HAL_Delay(1000); }成功标准电机旋转角度误差小于5度运行无失步现象7. PLC功能实现与编程模式7.1 梯形图逻辑解释器为了实现传统PLC的编程体验可以开发一个简单的梯形图解释器// 梯形图元件结构定义 typedef struct { uint8_t type; // 元件类型常开、常闭、线圈等 uint8_t operand; // 操作数地址 uint8_t value; // 当前值 } Ladder_Element; // 梯形图网络结构 typedef struct { Ladder_Element elements[16]; // 网络中的元件 uint8_t element_count; // 元件数量 } Ladder_Network; // 梯形图解释执行 void Ladder_Execute(Ladder_Network* network) { uint8_t power_rail 1; // 电源轨状态 for(int i 0; i network-element_count; i) { Ladder_Element* elem network-elements[i]; switch(elem-type) { case CONTACT_NORMAL: // 常开触点 elem-value power_rail PLC_Read_DigitalInput(elem-operand); break; case CONTACT_INVERSE: // 常闭触点 elem-value power_rail !PLC_Read_DigitalInput(elem-operand); break; case COIL: // 输出线圈 if(power_rail) { PLC_Write_DigitalOutput(elem-operand, 1); } break; } power_rail elem-value; // 传递到下一个元件 } }7.2 典型PLC程序示例实现一个简单的电机启停控制程序// 电机启停控制逻辑 void Motor_Control_Logic(void) { static uint8_t start_btn 0; // 启动按钮 static uint8_t stop_btn 0; // 停止按钮 static uint8_t motor_state 0; // 电机状态 static uint8_t overload 0; // 过载信号 // 读取输入状态 start_btn PLC_Read_DigitalInput(0); // IO0:启动按钮 stop_btn PLC_Read_DigitalInput(1); // IO1:停止按钮 overload PLC_Read_DigitalInput(2); // IO2:过载信号 // 启停逻辑启动优先于停止过载时立即停止 if(start_btn !overload) { motor_state 1; } if(stop_btn || overload) { motor_state 0; } // 控制输出 PLC_Write_DigitalOutput(0, motor_state); // Q0:电机运行 PLC_Write_DigitalOutput(1, overload); // Q1:故障指示 }8. 通信功能扩展8.1 Modbus RTU从站实现工业设备通常需要支持Modbus通信协议// Modbus RTU从站实现 typedef struct { uint8_t slave_id; // 从站地址 uint16_t holding_regs[64]; // 保持寄存器 uint16_t input_regs[64]; // 输入寄存器 uint8_t coils[32]; // 线圈状态 uint8_t discrete_inputs[32];// 离散输入 } Modbus_Slave; // Modbus功能码处理 uint8_t Modbus_Process_Request(Modbus_Slave* slave, uint8_t* request, uint8_t* response) { uint8_t func_code request[1]; switch(func_code) { case 0x03: // 读保持寄存器 return Read_Holding_Registers(slave, request, response); case 0x06: // 写单个寄存器 return Write_Single_Register(slave, request, response); case 0x10: // 写多个寄存器 return Write_Multiple_Registers(slave, request, response); default: return Exception_Response(response, 0x01); // 非法功能码 } }8.2 自定义通信协议对于特定应用可以设计更简洁的通信协议// 自定义通信协议帧结构 #pragma pack(1) typedef struct { uint8_t header; // 帧头 0xAA uint8_t cmd; // 命令字 uint8_t len; // 数据长度 uint8_t data[16]; // 数据域 uint8_t checksum; // 校验和 } Custom_Protocol_Frame; // 协议命令处理 void Protocol_Command_Handler(Custom_Protocol_Frame* frame) { switch(frame-cmd) { case CMD_READ_IO: // 读取IO状态 Protocol_Read_IO(frame); break; case CMD_WRITE_IO: // 控制IO输出 Protocol_Write_IO(frame); break; case CMD_SET_MOTOR: // 设置电机参数 Protocol_Set_Motor(frame); break; } }9. 实际应用案例9.1 小型传送带控制系统应用场景工厂物料传送带的启停控制和速度调节系统配置开关量输入启动按钮、停止按钮、急停开关、物料检测传感器开关量输出电机接触器、运行指示灯、故障报警模拟量输入调速电位器0-10V对应0-最大速度步进电机控制传送带精确定位控制逻辑void Conveyor_Control(void) { // 读取控制信号 uint8_t start PLC_Read_DigitalInput(0); uint8_t stop PLC_Read_DigitalInput(1); uint8_t emergency PLC_Read_DigitalInput(2); uint8_t material_detect PLC_Read_DigitalInput(3); // 读取速度设定 uint16_t speed_pot PLC_Read_AnalogInput(0); uint16_t motor_speed map(speed_pot, 0, 4095, 0, 1000); // 映射到速度值 // 安全逻辑急停最高优先级 if(emergency) { Motor_Stop(); Alarm_On(); return; } // 正常启停控制 if(start !stop) { Motor_Run(motor_speed); Run_Light_On(); } else { Motor_Stop(); Run_Light_Off(); } // 物料计数 if(material_detect) { Material_Count; } }9.2 温度控制系统应用场景工业烘箱温度控制系统配置模拟量输入热电偶温度传感器通过变送器转换为0-10V开关量输出加热器控制、风扇控制、超温报警通信接口连接上位机监控系统PID控制实现// 简易PID温度控制器 typedef struct { float setpoint; // 设定温度 float kp, ki, kd; // PID参数 float integral; // 积分项 float prev_error; // 上次误差 } PID_Controller; float PID_Compute(PID_Controller* pid, float current_temp) { float error pid-setpoint - current_temp; // 比例项 float proportional pid-kp * error; // 积分项抗积分饱和 pid-integral error; if(pid-integral 1000) pid-integral 1000; if(pid-integral -1000) pid-integral -1000; float integral_term pid-ki * pid-integral; // 微分项 float derivative pid-kd * (error - pid-prev_error); pid-prev_error error; return proportional integral_term derivative; } void Temperature_Control(void) { static PID_Controller heater_pid {100.0, 2.0, 0.1, 1.0, 0, 0}; // 设定100°C // 读取当前温度 float current_temp Read_Temperature_Sensor(); // PID计算 float output PID_Compute(heater_pid, current_temp); // 输出控制PWM调节加热器功率 if(output 0) { Set_Heater_Power(output); Fan_Off(); // 加热时关闭风扇 } else { Set_Heater_Power(0); Fan_On(); // 停止加热时开启风扇降温 } // 超温保护 if(current_temp 120.0) { Emergency_Shutdown(); Alarm_On(); } }10. 性能优化与稳定性提升10.1 实时性优化技巧中断优先级配置// 配置NVIC优先级确保关键任务及时响应 void NVIC_Configuration(void) { HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); // 系统滴答定时器最高优先级 // 外部中断用于急停等安全功能 HAL_NVIC_SetPriority(EXTI0_IRQn, 1, 0); // 高优先级 HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 通信中断优先级较低 HAL_NVIC_SetPriority(USART1_IRQn, 3, 0); // 低优先级 HAL_NVIC_EnableIRQ(USART1_IRQn); }任务调度优化// 基于时间片的任务调度 typedef struct { void (*task_func)(void); // 任务函数 uint32_t interval; // 执行间隔(ms) uint32_t last_run; // 上次执行时间 } Task_Control_Block; Task_Control_Block task_table[] { {Digital_IO_Scan, 10, 0}, // 10ms扫描一次IO {Analog_Sampling, 100, 0}, // 100ms采样一次模拟量 {Communication_Process, 50, 0}, // 50ms处理通信 {NULL, 0, 0} // 结束标记 }; void Task_Scheduler(void) { uint32_t current_time HAL_GetTick(); for(int i 0; task_table[i].task_func ! NULL; i) { if(current_time - task_table[i].last_run task_table[i].interval) { task_table[i].task_func(); task_table[i].last_run current_time; } } }10.2 抗干扰措施硬件抗干扰所有IO口增加TVS管防护模拟量输入使用π型滤波电路电源输入端加入共模电感信号线使用双绞线或屏蔽线软件抗干扰// 数字输入软件去抖 uint8_t Digital_Input_Debounce(uint8_t channel) { static uint8_t history[8] {0}; // 每个通道的历史状态 static uint8_t count[8] {0}; // 稳定计数器 uint8_t current_state HAL_GPIO_ReadPin(INPUT_GPIO[channel], INPUT_PIN[channel]); if(current_state (history[channel] 0x01)) { count[channel]; if(count[channel] 5) { // 连续5次相同认为稳定 history[channel] (history[channel] 1) | current_state; return current_state; } } else { count[channel] 0; } return history[channel] 0x01; // 返回上次稳定状态 } // 模拟量软件滤波 #define FILTER_WINDOW 8 uint16_t Analog_Input_Filter(uint8_t channel) { static uint16_t buffer[8][FILTER_WINDOW] {0}; static uint8_t index[8] {0}; uint32_t sum 0; // 新采样值存入缓冲区 buffer[channel][index[channel]] PLC_Read_AnalogInput(channel); index[channel] (index[channel] 1) % FILTER_WINDOW; // 计算移动平均 for(int i 0; i FILTER_WINDOW; i) { sum buffer[channel][i]; } return sum / FILTER_WINDOW; }11. 常见问题与排查方法问题现象可能原因排查方式解决方案开发板无法供电电源极性接反、电压不符检查电源适配器输出电压和极性使用12-24V直流电源注意正负极程序下载失败ST-Link连接问题、芯片锁死检查ST-Link驱动、连接线重新插拔调试器尝试芯片解锁数字输入无响应接线错误、上拉电阻未启用万用表测量输入电压检查接线配置内部上拉电阻模拟量采样不准参考电压不稳、采样时间不足测量参考电压波形增加ADC采样时间添加硬件滤波步进电机抖动驱动电流不足、加速曲线过陡观察电机运行波形调整驱动电流优化加速算法通信中断波特率不匹配、线路干扰用示波器检查信号质量确认波特率设置添加终端电阻下载调试问题排查确认BOOT0和BOOT1引脚状态通常BOOT00BOOT10检查ST-Link驱动是否正常安装尝试先擦除整个芯片再下载程序如果芯片被锁使用STM32 ST-LINK Utility解锁电机控制问题排查测量驱动芯片供电电压是否正常检查脉冲信号是否正常产生示波器观察确认电机绕组接线正确相序无误调整细分设置匹配电机参数12. 项目扩展与进阶应用12.1 物联网功能扩展通过添加ESP8266/ESP32模块实现远程监控// WiFi通信接口 void IoT_Send_Data(void) { // 采集设备状态 float temperature Read_Temperature(); uint8_t motor_status Read_Motor_Status(); uint16_t production_count Read_Production_Count(); // 组建成JSON格式 char json_data[256]; snprintf(json_data, sizeof(json_data), {\temp\:%.1f,\motor\:%d,\count\:%d}, temperature, motor_status, production_count); // 通过WiFi发送到云平台 WiFi_Send_Data(api.iot-platform.com, json_data); }12.2 多轴协调控制扩展实现2-4轴步进电机的协调运动// 多轴直线插补 void Linear_Interpolation(float target_x, float target_y, float speed) { float dx target_x - current_x; float dy target_y - current_y; float distance sqrt(dx*dx dy*dy); int steps (int)(distance / step_resolution); if(steps 0) return; float step_x dx / steps; float step_y dy / steps; for(int i 0; i steps; i) { current_x step_x; current_y step_y; Stepper1_MoveTo(current_x); Stepper2_MoveTo(current_y); Delay_Step_Interval(speed); } }这个STM32F103 PLC开发板项目最大的优势是成本低、功能全面既适合学习PLC原理也能用于实际的小型自动化项目。建议先从数字IO和步进电机基础功能开始验证再逐步扩展到模拟量采集和通信功能最后实现完整的控制系统。