嵌入式PID控制教学系统:双平台直流电机闭环实践
1. 项目概述
本项目实现了一个面向嵌入式初学者的双平台PID控制教学系统,核心目标是通过可复现、可验证、可移植的硬件-软件协同设计,帮助开发者深入理解闭环控制的基本原理与工程实现方法。系统以直流电机为被控对象,采用增量式编码器作为位置/速度反馈传感器,构建完整的定速(恒转速)与定距(恒位移)两种典型控制场景,并通过1.9英寸SPI TFT显示屏与五键人机界面提供直观的操作交互。
项目原始开发基于TI MSPM0G3507微控制器平台,后续完整移植至ST STM32F103C8T6平台。两次实现均严格遵循嵌入式实时系统设计规范:前台任务负责UI刷新、按键响应与状态切换;后台任务由高精度定时器中断驱动,承担编码器采样、PID计算、PWM更新等时间敏感操作。整个架构采用分层设计思想,将应用逻辑、控制算法、外设驱动解耦,为后续功能扩展与跨平台迁移奠定了坚实基础。
该系统并非仅限于理论演示,其硬件设计已通过实际电机负载验证,软件逻辑覆盖了从上电初始化、参数整定、模式切换到异常退出的全生命周期管理。所有关键模块——包括正交编码器信号解析、抗干扰按键扫描、带限幅的PID运算、双路互补PWM电机驱动——均经过实测验证,具备直接用于课程实验、毕业设计或小型机电控制系统原型开发的能力。
2. 系统架构与工作原理
2.1 整体架构设计
系统采用经典的前后台(Foreground-Background)实时架构,兼顾确定性与时序可控性:
前台任务(Main Loop):运行于
main()函数的无限while(1)循环中,承担非实时性要求高的任务,包括:- 用户界面(UI)周期性刷新(约30Hz)
- 按键事件的轮询检测与去抖处理
- 系统状态机的迁移判断与页面跳转
- 调试信息(如当前转速、目标值、PID参数)的格式化显示
后台任务(Timer Interrupt Service Routine):由20ms周期性定时器中断触发,执行对时序精度要求极高的核心控制任务:
- 编码器AB相脉冲计数与方向判别
- 基于20ms采样窗口的速度计算(RPM)
- PID控制器的误差计算、积分累加、微分估算与输出限幅
- PWM占空比的实时更新与电机功率输出
- 系统运行指示灯(LED)的秒级翻转
该架构确保了控制环路的严格周期性(20ms),避免了主循环中UI刷新等耗时操作对控制精度的影响,同时保持了代码结构的清晰性与可维护性。
2.2 控制对象建模与反馈机制
被控对象为一台带1000线增量式编码器的直流有刷电机。其物理模型可简化为一阶惯性环节,存在明显的机械时间常数与电磁时间常数。系统通过以下方式建立闭环:
速度反馈:编码器每转输出2000个脉冲(A/B相各1000个,四倍频后)。在20ms定时中断内统计脉冲增量ΔN,则实际转速为: [ RPM = \frac{\Delta N \times 50 \times 60}{1000} = 3 \times \Delta N ] 其中50为1/20ms的换算系数(50次中断/秒),60为分钟换算系数。该公式隐含了编码器线数为1000的假设,实际应用中需根据所用编码器规格精确标定。
位置反馈:直接读取编码器累计脉冲数
count,单位为“脉冲”。设定目标距离target(单位:脉冲)后,系统持续比较count与target的差值,直至绝对误差小于预设阈值(如5脉冲),即判定到达目标位置并自动停机。执行机构:BDR6126D双H桥驱动芯片,支持4.5A持续电流。通过BI/FI两路PWM信号控制电机正转(BI高、FI低)、反转(BI低、FI高)与制动(BI/FI同为高或同为低)。硬件设计中未采用主动制动模式,停机时默认将两路PWM置零,依靠电机反电动势与机械摩擦自然减速。
2.3 状态机驱动的人机交互
系统定义了五个核心操作页面,每个页面对应一个独立的状态机,通过五向导航键(上/下/左/右/OK)进行切换与参数修改。状态机设计严格遵循事件驱动原则,所有状态迁移均由按键事件触发,无忙等待或阻塞操作。
首页(Home State):显示嘉立创Logo与常用网址两个静态页面入口,以及“定速”、“定距”两个功能入口。通过
ui_flag标志位记忆用户从Logo/网址页返回后的焦点位置,保证操作连贯性。定速页(Speed Control State):显示当前实测RPM、目标RPM及P/I/D三组参数。用户可通过方向键在“目标值”与“P/I/D参数”间切换焦点,OK键确认进入编辑模式。
定距页(Distance Control State):显示当前累计脉冲数、目标脉冲数及P/I/D参数。操作逻辑与定速页一致,但目标值单位为“脉冲”,反映的是绝对位移量。
设置页(Setting State):提供全局配置选项,如屏幕亮度调节、系统复位等。右键快捷进入当前控制模式(定速/定距)的参数调节子菜单。
调参页(Tuning State):聚焦于PID参数整定。用户可在P、I、D、Target四个字段间循环选择,上下键增减数值,OK键确认修改。
target_flag标志位确保在模式切换(如从定速切至定距)后,参数编辑焦点能准确恢复至上次操作的字段。
所有状态机均采用查表法实现状态转移,避免复杂的嵌套条件判断,提升了代码可读性与执行效率。
3. 硬件设计详解
3.1 电源系统设计
系统采用Type-C接口输入标准5V USB电源,通过简洁而稳健的电源分配网络为各功能模块供电:
电机供电(MOTOR_VCC):5V输入经由单刀开关后,直接供给BDR6126D驱动芯片的VM引脚。此路径不经过任何LDO或滤波,旨在为电机提供最大可能的瞬时电流能力,满足启动与堵转工况需求。设计中特别注意PCB走线宽度,确保能承载4.5A峰值电流。
数字电路供电(+5V):5V输入在开关之后,串联一只防倒灌肖特基二极管(如SS34),再经22μF电解电容与100nF陶瓷电容组成的π型滤波网络,最终接入开发板的5V输入引脚。该设计具有双重作用:
- 防倒灌保护:当开发板自身5V→3.3V LDO出现故障或外部误接3.3V电源时,二极管可阻止电流反向流入USB端口,保护主机设备。
- 储能稳压:22μF电解电容提供低频储能,吸收电机启停瞬间造成的5V母线电压跌落;100nF陶瓷电容则负责高频噪声旁路,抑制BDR6126D开关动作产生的EMI对MCU数字电路的干扰。
开发板内部集成的5V→3.3V LDO(如AMS1117-3.3)为MCU核心、编码器、SPI屏幕逻辑部分提供稳定3.3V电源。该LDO的输入即来自上述+5V滤波输出,因此其输入纹波已得到充分抑制,无需额外增加输入电容。
3.2 电机驱动与编码器接口
3.2.1 BDR6126D驱动电路
BDR6126D是一款集成度高、驱动能力强的双H桥芯片,其关键特性与应用要点如下:
| 特性 | 参数 | 工程意义 |
|---|---|---|
| 供电电压范围 | 4.5V – 18V | 完美兼容5V USB输入,留有充足裕量应对电池供电场景 |
| 持续输出电流 | ±4.5A | 远超一般教学用微型电机需求(通常<1A),具备强过载能力 |
| 逻辑电平兼容 | 3.3V/5V | 可直接由MCU GPIO驱动,无需电平转换电路 |
| 内置保护 | 过流、过热、欠压锁定 | 显著提升系统鲁棒性,避免因编码器卡死或短路导致芯片损坏 |
在原理图中,BDR6126D的BO(Bridge Output A)与FO(Bridge Output B)引脚分别连接至电机的两个电极。BI(Bridge Input A)与FI(Bridge Input B)为逻辑输入端,由MCU的两路独立PWM信号控制。为防止电机感性负载在关断瞬间产生高压尖峰损坏芯片,设计中在BO/FO与VM之间预留了续流二极管焊盘(虽未在描述中明确安装,但PCB已做冗余设计)。
3.2.2 正交编码器接口
系统采用标准A/B相正交编码器,其信号处理分为硬件与软件两层:
硬件层:编码器A/B相信号直接接入MCU的GPIO引脚,并配置为外部中断(EXTI)输入。为提高抗干扰能力,每个信号线上均放置了10kΩ上拉电阻至3.3V,并在PCB布局时尽量缩短走线长度,远离电机驱动等噪声源。
软件层:采用“边沿触发+状态查询”法实现四倍频计数。以MSPM0G3507为例,A相上升沿中断服务程序中,读取B相当前电平:若B为低,则计数器+1(正转);若B为高,则计数器-1(反转)。同理,在B相上升沿中断中,读取A相电平进行相反判断。此方法充分利用了正交编码器的相位关系,将分辨率提升至原始线数的四倍,显著提高了低速下的位置测量精度。
在STM32F103C8T6移植版本中,该逻辑通过HAL_GPIO_EXTI_Callback()回调函数实现,同样基于A/B相的上升沿触发,并在回调中读取另一相信号电平完成方向判别。
3.3 人机交互外设
3.3.1 五向按键阵列
按键采用标准的“GPIO-上拉-按键-GND”设计,所有按键共用一个GND网络。MCU GPIO配置为浮空输入(Floating Input),内部上拉电阻使未按下时引脚为高电平,按下时被拉至GND,产生低电平有效信号。
- MSPM0G3507平台:按键分别接入
GPIOA8、GPIOA9、GPIOA28、GPIOA31、GPIOB4,均为普通GPIO,无特殊复用要求。 - STM32F103C8T6平台:按键统一接入
GPIOB3至GPIOB7,利用其连续编号特性,便于在key_scan()函数中通过数组索引高效读取。
按键消抖采用“定时器中断轮询+软件计数”策略:在20ms定时中断中,对每个按键进行一次电平采样,并与前一次采样值比较。只有当连续N次(如3次)采样结果一致时,才认定为有效按键事件。该方法避免了在中断中使用delay()等阻塞函数,符合实时系统设计规范。
3.3.2 1.9英寸SPI TFT显示屏
显示屏采用160x128分辨率、16位RGB565色彩格式的SPI接口TFT。其控制信号定义如下:
| 信号 | 功能 | MSPM0G3507引脚 | STM32F103C8T6引脚 | 驱动方式 |
|---|---|---|---|---|
SCL(SCK) | 串行时钟 | PB13(SPI2_SCK) | PB13(SPI2_SCK) | 硬件SPI |
SDA(MOSI) | 串行数据 | PB15(SPI2_MOSI) | PB15(SPI2_MOSI) | 硬件SPI |
DC | 数据/命令选择 | PA0 | PA0 | GPIO |
CS | 片选 | PB12 | PB12 | GPIO |
RES | 复位 | PA2 | PA2 | GPIO |
BLK | 背光控制 | PA1 | PA1 | GPIO |
驱动代码中,LCD_DC引脚用于区分发送的是指令(DC=0)还是像素数据(DC=1);LCD_CS引脚在每次SPI传输前拉低,传输结束后拉高,实现片选功能;LCD_RES引脚在初始化时产生一个低脉冲,强制屏幕复位;LCD_BLK引脚通过PWM或直接电平控制背光亮度。
值得注意的是,所有GPIO控制宏(如LCD_DC_Set())均被定义为内联函数,避免了函数调用开销,确保了SPI通信时序的严格性。
4. 软件设计与算法实现
4.1 分层软件架构
软件严格遵循分层架构(Layered Architecture),各层职责清晰,依赖关系单向:
Application Layer (app/) ├── app_sys_mode.c/h # 系统模式管理(状态机、页面跳转) ├── app_speed_pid.c/h # 定速控制业务逻辑 ├── app_distance_pid.c/h # 定距控制业务逻辑 ├── app_key_task.c/h # 按键事件分发与响应 └── app_ui.c/h # UI绘制与刷新 Middle Layer (middle/) ├── mid_pid.c/h # PID算法核心(与硬件无关) ├── mid_timer.c/h # 定时器管理与任务调度 ├── mid_button.c/h # 按键扫描与状态机 ├── mid_debug_led.c/h # 运行指示灯控制 └── mid_debug_uart.c/h # 串口调试输出 Hardware Layer (hardware/) ├── hw_motor.c/h # 电机PWM驱动(抽象为set()/stop()接口) ├── hw_encoder.c/h # 编码器计数与速度计算 ├── hw_lcd.c/h # LCD底层驱动(SPI读写、寄存器配置) ├── hw_key.c/h # 按键硬件读取(返回原始电平状态) ├── lcdfont.h # 字模数据 └── pic.h # 图片资源(Logo等)这种分层设计使得:
- 应用层完全不关心底层硬件细节,可专注于控制逻辑与用户体验。
- 中间层封装了通用算法与服务,
mid_pid.c中的pid_calc()函数可被任意MCU平台复用。 - 硬件层实现了平台相关代码,移植时仅需重写该层,上层逻辑几乎无需修改。
4.2 PID控制算法实现
mid_pid.c实现了标准的位置式PID算法,并针对嵌入式环境进行了关键优化:
float pid_calc(PID *pid, float target, float current) { pid->last_error = pid->error; pid->error = target - current; float pout = pid->error; // 比例项 pid->change_i += pid->error; // 积分项累积(离散求和) float dout = pid->error - pid->last_error; // 微分项(后向差分) // 积分限幅:防止积分饱和(Integral Windup) if(pid->change_i > pid->max_change_i) pid->change_i = pid->max_change_i; else if(pid->change_i < -pid->max_change_i) pid->change_i = -pid->max_change_i; // PID输出计算 pid->output = (pid->kp * pout) + (pid->ki * pid->change_i) + (pid->kd * dout); // 输出限幅:保护执行机构 if(pid->output > pid->max_output) pid->output = pid->max_output; else if(pid->output < -pid->max_output) pid->output = -pid->max_output; return pid->output; }关键工程考量:
- 积分限幅(Anti-Windup):
max_change_i参数限制了积分项的最大累积值。当系统存在大偏差(如电机堵转)时,若不限制积分,change_i会持续增大,一旦偏差消失,积分项仍会维持较大输出,导致严重超调。限幅后,系统响应更平稳。 - 输出限幅:
max_output直接约束了最终PWM占空比的绝对值,防止电机因过大的控制量而失控或过热。 - 数据类型:全部使用
float类型,兼顾精度与ARM Cortex-M系列MCU的FPU硬件加速能力(MSPM0G3507与STM32F103均支持单精度浮点运算)。
4.3 定速与定距控制逻辑
4.3.1 定速控制(app_speed_pid.c)
定速模式的核心是将编码器测得的RPM值稳定在用户设定的目标值。其控制流程如下:
- 在20ms定时中断中,调用
encoder_update_speed()计算当前RPM。 - 主循环中,调用
app_speed_pid_control():- 获取当前RPM作为
current。 - 将用户设定的目标RPM作为
target。 - 调用
mid_pid_calculate()计算PID输出output。 - 将
output(范围[-100, 100])映射为PWM占空比(0-100%),并调用hw_motor_set()驱动电机。
- 获取当前RPM作为
- 当
|current - target| < threshold(如±5 RPM)时,认为系统已进入稳态,可维持当前输出;若偏差持续过大,则需检查PID参数或机械连接。
4.3.2 定距控制(app_distance_pid.c)
定距模式的目标是让电机转动一个精确的脉冲数(即角度)。其实现更为直接:
void app_distance_pid_control(void) { if(g_distance_pid_data.enable == 1) { float current_pos = hw_encoder_get_count(); // 获取累计脉冲数 float output = mid_pid_calculate(&g_distance_pid_data.pid, g_distance_pid_data.target, current_pos); hw_motor_set((int16_t)output); // 输出至电机 // 到达目标位置时停止 if(fabs(g_distance_pid_data.target - current_pos) < 5.0) { hw_motor_stop(); g_distance_pid_data.enable = 0; // 关闭控制环 } } }此处的关键在于hw_encoder_get_count()返回的是自系统上电以来的总脉冲数,是一个绝对位置量。PID控制器将其与目标值比较,输出一个力矩指令,驱动电机向目标靠近。当两者差值小于5个脉冲(约0.018度,假设1000线编码器)时,系统判定已精确定位,立即停机。这种“到位即停”的策略简单有效,适用于对动态性能要求不苛刻的教学场景。
4.4 移植适配关键技术点
从MSPM0G3507到STM32F103C8T6的移植,本质是硬件抽象层(HAL)的替换,而非算法逻辑的修改。关键适配点如下:
| 模块 | MSPM0G3507实现 | STM32F103C8T6实现 | 适配要点 |
|---|---|---|---|
| SPI屏幕 | 自定义Bit-Banging SPI | HAL库HAL_SPI_Transmit() | 屏幕驱动函数内部调用HAL API,上层hw_lcd.c接口保持不变 |
| 按键读取 | DL_GPIO_readPins() | HAL_GPIO_ReadPin() | hw_key.c中key_scan()函数重写,返回结构体KEY_STATUS,内容一致 |
| PWM输出 | DL_TimerG_setCaptureCompareValue() | __HAL_TIM_SetCompare() | hw_motor.c中set_bi()/set_fi()函数重写,调用HAL TIM API |
| 编码器中断 | GROUP1_IRQHandler() | HAL_GPIO_EXTI_Callback() | 中断服务逻辑完全相同,仅API调用方式不同 |
| 定时器中断 | TIMER_LED_INST_IRQHandler() | HAL_TIM_PeriodElapsedCallback() | 后台任务逻辑(编码器更新、按键扫描)完全复用,仅中断注册方式不同 |
移植过程中,中间层(mid_*.c)与应用层(app_*.c)的代码100%复用,证明了分层架构在跨平台开发中的巨大价值。唯一需要关注的是编译器工具链:STM32F103C8T6项目必须使用ARM Compiler 5(AC5),因其对__packed等关键字的支持与AC6存在差异,而AC5是Keil MDK v5.x的默认组件,需单独安装并正确配置路径。
5. BOM清单与器件选型依据
下表列出了项目核心物料及其选型理由,所有器件均为工业级、易采购、成本可控的通用型号:
| 序号 | 器件名称 | 型号/规格 | 数量 | 选型依据 | 备注 |
|---|---|---|---|---|---|
| 1 | 主控MCU | MSPM0G3507R1MT | 1 | TI新一代超低功耗Arm Cortex-M0+,集成高性能ADC与多路PWM,开发板已集成所有必要外围 | 原始平台 |
| 2 | 主控MCU | STM32F103C8T6 | 1 | ST经典主流Cortex-M3 MCU,生态成熟,资料丰富,性价比极高 | 移植平台 |
| 3 | 电机驱动 | BDR6126D | 1 | 国产高集成双H桥,4.5A持续电流,内置保护,价格仅为DRV8871的1/3 | 替代方案:DRV8871 |
| 4 | 编码器 | 1000线增量式 | 1 | 标准教学用编码器,分辨率适中,易于信号调理与软件解析 | 线数可按需更换 |
| 5 | SPI显示屏 | 1.9" TFT, 160x128 | 1 | 小尺寸、低功耗、SPI接口,驱动IC常见(如ST7735S),配套资源丰富 | 分辨率与接口固定 |
| 6 | 电源管理 | AMS1117-3.3 | 1 | 经典LDO,输出3.3V/1A,纹波小,成本低,长期供货稳定 | 必需 |
| 7 | 保护二极管 | SS34 | 1 | 3A肖特基二极管,正向压降低(0.55V),反向耐压40V,满足防倒灌需求 | 必需 |
| 8 | 滤波电容 | 22μF/16V 电解 | 1 | 为+5V数字电源提供低频储能 | 必需 |
| 9 | 滤波电容 | 100nF 陶瓷 | 1 | 为+5V数字电源提供高频旁路 | 必需 |
| 10 | 按键 | 贴片轻触开关 | 5 | 标准6x6mm规格,寿命长,手感好,成本低廉 | 必需 |
所有被动器件(电阻、电容)均选用0805或0603封装,兼顾焊接便利性与PCB空间。BOM总成本控制在百元人民币以内,符合教学项目低成本、易复制的原则。
6. 实际部署与调试经验
6.1 硬件调试要点
电机抖动问题:初次上电时,若电机出现高频抖动,首要检查BDR6126D的
VM与GND引脚是否接触良好。不良的接地会导致驱动芯片工作异常。其次,确认编码器A/B相是否接反,接反将导致方向判别错误,PID控制器持续发出反向修正指令。编码器计数不准:若实测RPM与万用表测得的电机转速偏差较大,应重新校准编码器线数。在
encoder_update_speed()函数中,将1000.0替换为实际编码器规格书标明的线数(如600、1200),并乘以4(四倍频)。SPI屏幕花屏:多数源于
DC引脚电平错误。务必确认在发送指令前DC=0,发送数据前DC=1。可使用逻辑分析仪抓取SCK、MOSI、DC三线波形,验证时序是否符合TFT IC手册要求。
6.2 软件调试技巧
PID参数整定:推荐采用“先比例、后积分、最后微分”的Ziegler-Nichols启发式方法:
- 将
Ki、Kd置零,缓慢增大Kp,直至系统出现等幅振荡,记录此时的Ku与振荡周期Tu。 - 设定
Kp = 0.6*Ku,Ki = 1.2*Ku/Tu,Kd = 0.075*Ku*Tu作为初始值。 - 在此基础上微调,优先保证系统稳定(不振荡),再优化响应速度与超调量。
- 将
串口调试:启用
mid_debug_uart.c,在关键节点(如PID计算前后、编码器计数更新后)打印变量值。例如,在pid_calc()末尾添加:printf("PID: T=%.1f, C=%.1f, Err=%.1f, Out=%.1f\r\n", target, current, pid->error, pid->output);通过串口监视器实时观察控制过程,是定位逻辑错误最高效的手段。
状态机可视化:在
app_sys_mode.c中,为每个状态定义唯一的ASCII字符(如'H'代表首页,'S'代表定速页),并在主循环开头打印该字符。通过串口输出的字符流,可清晰看到用户操作引发的状态迁移路径,极大简化UI逻辑调试。
6.3 系统可靠性增强措施
看门狗(WDT):虽然原始项目未启用,但在商用化部署时,强烈建议在
main()初始化完成后启动独立看门狗(IWDG)。在主循环末尾添加HAL_IWDG_Refresh(&hiwdg),并在所有可能造成死锁的while()循环内定期喂狗。一旦软件跑飞或陷入死循环,WDT将自动复位系统。电源监控:在
+5V输入端增加一个电压检测电路(如TL431+分压电阻),将检测信号接入MCU的ADC通道。当USB电源电压低于4.75V时,系统可主动降低PWM输出上限,避免因供电不足导致电机失步。电机堵转保护:在
app_distance_pid_control()中,增加一个超时计数器。若从启动到enable=0的时间超过预设阈值(如5秒),则强制停机并点亮红色LED报警,提示用户检查机械卡滞。
这些增强措施虽未在基础项目中体现,但它们是将一个教学Demo升级为可靠工程产品的必经之路,体现了嵌入式工程师对系统鲁棒性的深刻理解。
