资讯动态

STM32F407直流有刷电机电流环PID控制实战详解

发布时间:2026/9/16 18:34:19 来源:尧图企业网站定制
简介面向STM32F4系列嵌入式开发者提供直流有刷电机电流环PID控制的完整工程源码、可执行固件与配套工程配置。资源共242个文件以C源码与头文件为主涵盖HAL库定时器、ADC、I2C、RCC等驱动模块并包含uvprojx工程文件、hex与bin固件压缩包约1.66MB便于直接导入编译或烧录验证。项目针对F407的浮点运算及多通道ADC/PWM资源实现从电流采样、PID运算到PWM输出的完整闭环兼顾积分饱和限制与参数整定。已有405人学习下载适合正在学习电机控制或需要将驱动快速移植到其他F4系列芯片的工程师参考。通过阅读和调试该工程可掌握电流环PID的工程化实现方法理解底层外设配置思路节省大量驱动开发与调试时间。1. 电流环PID让直流有刷电机从“能转”到“转得准”直接给PWM占空比直流有刷电机也能转但在负载变化、电源电压波动、堵转恢复这些场景下开环控制会暴露出明显问题同一占空比下负载加大时转速跌落电源电压下降时电流上不去堵转时电流飙升轻则烧驱动重则烧电机。电流环PID解决的就是这件事实时采样电枢电流与目标电流做差用PI调节器输出修正PWM占空比让实际电流始终贴住设定值。这个资源把ATK F407的HAL库工程从驱动芯片初始化到电流采样、PID运算、PWM更新串成了一整条可编译的链路适合正在做STM32F4直流有刷电机驱动的开发者和做运动控制课设的学生。下面从硬件接线、定时器触发采样、PID实现到串口验证按实际调试顺序逐层拆开。2. 电流环的硬件基础采样电阻、运放与驱动芯片接线2.1 为什么直流有刷电机要单独做电流环先看一个常见误区有人把电流环等同于“限制最大电流”在PWM输出前加一个硬限幅就认为电流环做完了。这其实是把电流保护与电流闭环搞混了。电流保护是“超了就切断”本质是逻辑判断电流环是“实际电流跟随目标电流”本质是负反馈调节。两者的区别在动态过程里非常明显——电机从静止猛然启动时电枢反电动势为零如果只做限幅而不做闭环电流会在限制值附近来回反弹产生明显的电流纹波和转矩脉动而电流环通过PI调节器提前减小占空比让电流平滑上升并稳定在设定值附近。电流环在级联PID结构里处于最内层。典型的三环PID是位置环→速度环→电流环外环的输出就是内环的给定。电流环带宽最高一般做到几千赫兹到几十千赫兹这决定了它对负载扰动的抑制速度。如果电流环响应慢外环速度环做得再好遇到突加负载时也会出现转速先跌后补的“迟钝感”。所以电流环不仅是保护机制更是整个电机控制性能的地基。2.2 电流采样电路从采样电阻到ADC输入STM32F407内部ADC只能采集电压电流必须先转换成电压。最常用的方案是低端采样电阻法在驱动桥的下管源极和电源地之间串入一个精密电阻流过电机的电流在这个电阻上产生压降再用运算放大器放大到ADC的量程范围内。这个资源对应的硬件设计我建议采样电阻取0.1Ω采样电阻两端电压等于0.1×I_motor。假设电机最大工作电流按3A估算采样电阻上最大压降为0.3V这个信号太小直接送STM32F407的ADC12位分辨率下每LSB对应的电流约为0.8mA看似够用但实际板上的地噪声和开关噪声很容易淹掉几十毫伏的差分信号。所以中间要加一个差分运放放大增益按40到50倍设计。以INA240这类电流检测放大器为例增益50倍时3A电流对应输出电压1.5V距离3.3V ADC满量程还有余量这个余量用于容纳堵转瞬间的电流尖峰。在驱动芯片选型上摘要里提到了L298N或TB6612FNG。需要补充的是这两颗芯片本身都不带电流反馈输出引脚。L298N内部有两个H桥可以直接驱动一路有刷电机TB6612FNG是MOSFET H桥压降低、频率响应好。它们都有使能PWM引脚单个PWM加两个方向电平即可控制电机换向逻辑与电流采样共地接线相对清晰。如果手头有自带电流镜像输出的驱动芯片例如DRV8873的IPROPI引脚这个引脚输出的电流经过一个电阻接地也能得到与电机电流成正比的电压采样电路可以简化但原理和后续ADC处理一致。2.3 驱动级接线与死区时间设置以TB6612FNG为例标准接法是VM接电机电源如12VVCC接3.3V逻辑电源AIN1/AIN2接STM32F407的两个GPIO用于方向控制PWMA接定时器PWM输出A01/A02接电机两端GND与STM32F407共地。注意电机电源的地与控制逻辑的地必须在驱动板附近单点汇合不要分开接地否则电流采样电阻上的压降会被地线噪声污染。STM32F407控制PWM的定时器选择上推荐使用高级定时器TIM1或TIM8。高级定时器支持带死区插入的互补PWM输出这在驱动H桥的上下管时需要用到。虽然TB6612FNG内部集成了死区防止逻辑外部PWM若不设置死区也不会立刻炸管但如果后续更换为半桥驱动芯片加外部MOSFET的方案互补PWM死区就是必须项。所以工程里把TIM1的CH1与CH1N都配置好CH1连接驱动芯片的PWMACH1N可以悬空或作为备用。死区时间计算公式为死区时间 (DTG[7:0] 1) × T_ck其中T_ck是定时器时钟周期的整数倍。系统时钟168MHzAPB2定时器时钟84MHzT_ck约为11.9ns。若设置DTG84则死区约1μs这个值对10kHz到20kHz的PWM频率是合理的。死区也不是越大越好死区过大会引入电流波形畸变和额外功率损耗判断标准是上下管导通波形没有交叠即可。下面是引脚分配表这个接线关系与代码里的宏定义一致动手接线前先对着这个表逐项确认功能STM32F407引脚外设通道连接目标PWM输出PE9TIM1_CH1TB6612FNG的PWMA互补PWM备用PE8TIM1_CH1N外部半桥驱动器的下管方向控制1PE7GPIO输出TB6612FNG的AIN1方向控制2PE10GPIO输出TB6612FNG的AIN2电流采样输入PC1ADC123_IN11采样运放输出定时器触发内部信号TIM1_TRGO内部连线ADC注入组触发源PC1这个引脚对应ADC123_IN11在三个ADC上都可用代码里选择ADC1的注入组来采集。用注入组而不是规则组的原因是注入组可以被TIM1_TRGO硬件触发从而让电流采样与PWM周期严格同步避免在PWM开关切换瞬间采到尖峰噪声。3. STM32F407的定时器触发ADC与PWM输出代码实现3.1 TIM1输出带死区的PWMPWM频率选择要兼顾开关损耗与电流纹波。有刷电机的电枢电感通常只有几毫亨到几十毫亨PWM频率太低比如1kHz电流纹波会非常大电机会发出明显啸叫频率太高驱动芯片的开关损耗又上去了。工程里取20kHz这个频率超出人耳可听范围电流纹波也控制在合理区间。TIM1的时钟来自APB2定时器时钟84MHz要产生20kHz PWM预分频器设为0自动重装载值为4199占空比初始设为0等电流环算完再更新。void MX_TIM1_Init(void) { TIM_OC_InitTypeDef sConfigOC {0}; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig {0}; htim1.Instance TIM1; htim1.Init.Prescaler 0; // 84MHz/1 84MHz htim1.Init.CounterMode TIM_COUNTERMODE_UP; htim1.Init.Period 4200 - 1; // 84MHz / 4200 20kHz htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter 0; HAL_TIM_PWM_Init(htim1); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 0; sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; sConfigOC.OCIdleState TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState TIM_OCNIDLESTATE_RESET; HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1); sBreakDeadTimeConfig.OffStateRunMode TIM_OSSR_DISABLE; sBreakDeadTimeConfig.OffStateIDLEMode TIM_OSSI_DISABLE; sBreakDeadTimeConfig.LockLevel TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime 84; // (841)*11.9ns ≈ 1us sBreakDeadTimeConfig.BreakState TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.BreakFilter 0; sBreakDeadTimeConfig.AutomaticOutput TIM_AUTOMATICOUTPUT_DISABLE; HAL_TIMEx_ConfigBreakDeadTime(htim1, sBreakDeadTimeConfig); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1); }注意几个参数的含义。Prescaler设置为0意味着对84MHz定时器时钟不分频当要输出更低的PWM频率时优先增大Period而不是Prescaler这样能保持PWM占空比分辨率。Period取4200减1是因为STM32定时器计数上限是自动重装载值需要减1。死区时间设置为84对应约1μs这个值主要针对外部半桥驱动场景如果只用TB6612这类集成驱动芯片死区参数对功能没有直接影响但留着可以让代码直接平移到更大功率的驱动板。3.2 用TRGO触发ADC注入组让电流采样和PWM同步电流采样最怕采到PWM开关瞬间的毛刺。当PWM高电平开通瞬间驱动桥上下管换流电流波形上会出现纳秒级的振铃这个振铃如果被ADC采到PID控制器会把它当成真实电流导致占空比被错误修正。解决办法是把ADC采样时刻对准PWM周期的“平稳区”。工程中通过TIM1主输出触发信号TRGO来启动ADC注入组采样。定时器配置里需要设置主从模式void MX_ADC1_Init(void) { ADC_InjectionConfTypeDef sConfigInjected {0}; hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_RISING; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T1_TRGO; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 0; HAL_ADC_Init(hadc1); sConfigInjected.InjectedChannel ADC_CHANNEL_11; sConfigInjected.InjectedRank 1; sConfigInjected.InjectedNbrOfConversion 1; sConfigInjected.InjectedSamplingTime ADC_SAMPLETIME_480CYCLES; sConfigInjected.ExternalTrigInjecConv ADC_EXTERNALTRIGINJECCONV_T1_TRGO; sConfigInjected.AutoInjectedConv DISABLE; sConfigInjected.InjectedDiscontinuousConvMode DISABLE; sConfigInjected.InjectedOffset 0; HAL_ADCEx_InjectedConfigChannel(hadc1, sConfigInjected); }这里的关键是ExternalTrigConv和ExternalTrigInjecConv都指向T1_TRGO表示定时器TIM1的TRGO信号上升沿到来时自动启动一次ADC注入组转换。TRGO的产生需要配置TIM1的主模式void MX_TIM1_ConfigTrigger(void) { TIM_MasterConfigTypeDef sMasterConfig {0}; sMasterConfig.MasterOutputTrigger TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(htim1, sMasterConfig); }MasterOutputTrigger选用TIM_TRGO_UPDATE意思是定时器计数器溢出更新时产生TRGO信号。由于ADC采样一般放在PWM周期的非开关时刻常见的做法是把PWM计数模式改为中心对齐模式让TRGO在计数器上溢或下溢时触发这样采样点会落在PWM周期的中间位置避开通断边沿的噪声。中心对齐模式下Period设为8400减1PWM频率仍然是20kHz但计数先增后减波形周期是左对齐模式的两倍长度TRGO在每个周期触发两次实际可以选择只用其中一次或者接受频率翻倍不影响使用。3.3 在注入组转换完成中断里取电流值注入组转换完成后会进入中断回调这个回调函数运行在定时器周期中断上下文要求尽量短小。电流环计算放在这里计算完直接更新TIM1的CCR寄存器。下面代码展示了电流读取与占空比更新的典型写法float g_current_adc_raw; // ADC原始值 float g_current_amp; // 换算后的电流值 float g_current_target 1.0f; // 目标电流单位为A float g_pwm_duty 0.0f; // 当前占空比输出 void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc-Instance ADC1) { g_current_adc_raw HAL_ADCEx_InjectedGetValue(hadc1, ADC_INJECTED_RANK_1); g_current_amp (g_current_adc_raw / 4095.0f) * 3.3f / 50.0f / 0.1f; g_pwm_duty CurrentPI_Calc(g_current_target, g_current_amp); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, (uint32_t)g_pwm_duty); } }电流换算公式里ADC原始值除以4095得到归一化电压比例乘以3.3V得到运放输出电压除以运放增益50再除以采样电阻0.1Ω得到电流值。这段代码里的采样增益和电阻值如果不匹配实际硬件换算出来的电流就是错的PID整定无从谈起。所以第一步先用电桥或万用表量采样电阻的实际阻值再看运放增益电阻网络最终以实测电流为准修正这两个系数。TIM1的更新中断或ADC注入组完成中断频率就是电流环的运行频率。20kHz是这款芯片处理起来比较舒适的一个频率浮点PID运算加上中断上下文切换占用的CPU时间可以忽略。如果进一步提高PWM频率到50kHz以上电流环仍然能运行但ADC采样时间需要相应缩短采样保持时间过长会引入更大的延迟可能导致电流环相位裕度下降。4. 电流环PI控制器离散化公式、抗积分饱和与参数整定4.1 位置式PI还是增量式PI电流环里怎么选PID算法分为位置式和增量式两种实现方式。位置式的输出直接是PWM占空比增量式的输出是占空比的增量。很多人从速度环入门时习惯了增量式因为增量式天然适合输出给步进电机驱动器或舵机这类设备。但电流环应用里位置式PI更合适理由有三点第一电流环的干扰主要来自采样噪声和PWM开关噪声位置式PI的I项会不断累加误差配合输出限幅就能形成稳定的控制量。增量式PI对噪声更敏感因为其积分作用隐含在增量累加过程中噪声会被直接积分进输出造成占空比抖动。第二电流环需要明确的输出限幅。位置式PI可以直观地把输出限制在0到PWM周期值之间再配合积分限幅实现逻辑很简单。增量式PI虽然也能限幅但限幅的是变化率不是绝对值堵转保护时反而要多一层判断。第三从级联PID结构看电流环输入是速度环输出的电流指令输出是PWM。位置式PI的“位置”直接对应物理量——占空比——物理意义清晰哪里出了问题看数值就能判断。电流环也基本不用微分项D。采样电阻上的电流信号本身包含大量开关纹波D项对高频噪声的放大作用会把占空比扰动放大数倍严重时电机运行声音都会变刺耳。如果系统确实需要阻尼效果优先考虑在采样值上多做几次软件滤波而不是加D项。4.2 离散化PI控制器代码实现电流环PI的离散化公式输出 Kp × 误差 Ki × 误差积分 输出限幅修正。控制周期为50μs20kHz定义如下结构体typedef struct { float Kp; // 比例增益 float Ki; // 积分增益 float integral; // 积分累加值 float integral_max; // 积分限幅防止积分饱和 float out_max; // 输出限幅 float out; // PID输出值 } CurrentPID_t; CurrentPID_t g_current_pid { .Kp 0.15f, .Ki 0.02f, .integral 0.0f, .integral_max 200.0f, .out_max 4100.0f, // 对应4200-100的最小占空比余量 .out 0.0f }; float CurrentPI_Calc(float target_current, float actual_current) { float error target_current - actual_current; float p_out g_current_pid.Kp * error; g_current_pid.integral g_current_pid.Ki * error; if (g_current_pid.integral g_current_pid.integral_max) g_current_pid.integral g_current_pid.integral_max; else if (g_current_pid.integral -g_current_pid.integral_max) g_current_pid.integral -g_current_pid.integral_max; g_current_pid.out p_out g_current_pid.integral; if (g_current_pid.out g_current_pid.out_max) g_current_pid.out g_current_pid.out_max; else if (g_current_pid.out 0.0f) g_current_pid.out 0.0f; return g_current_pid.out; }代码里两个限幅分别处理不同问题。积分限幅把integral限制在正负200防止长时间堵转时积分项无限制累积堵转解除后出现大幅超调输出限幅把最终占空比限制在0到4100其中4100比PWM周期4200留出了100个计数值的余量这是防止PID输出顶到满占空比后电机电枢电流失去调节方向。如果要反转控制输出范围改为-4100到4100PWM方向由AIN1/AIN2决定PI输出正值正转负值反转。4.3 参数整定步骤从临界比例到微调实际调试中我给出一套常用的整定方法。第一步将Ki设为0只保留Kp从较小的值如0.05开始逐步增大Kp观察电机电流阶跃响应。当电流响应开始出现等幅振荡时记录当前Kp值这个值就是临界比例增益Ku振荡周期记为Tu。第二步根据齐格勒-尼古拉斯整定规则计算初值。对于PI控制器Kp取0.45×KuKi取Kp除以(Tu/1.2)。这个规则给出来的参数能保证系统稳定但一般偏“野”需要接续微调。第三步在初值基础上微调。增大Kp加快响应但如果电流波形在阶跃启动时出现明显过冲说明Kp偏大增大Ki消除稳态误差但如果电流在目标值附近低频振荡周期在数百毫秒级别说明Ki过大。电流环出现高频抖动几kHz的振荡采样噪声或死区设置问题是主因先降低Kp如果还抖检查PWM死区时间是否太小导致电流波形畸变。下面是不同参数特征对应的调整方向实际调试时对照这个表操作效率更高现象调整方向说明阶跃启动过冲大回稳慢减小Kp比例增益过大导致输出饱和稳态电流与目标偏差大增大Ki积分增益不足稳态误差未被消除电流在目标值附近低频振荡减小Ki积分项反复越界形成周期性振荡电流高频抖动声音尖锐降低Kp检查采样滤波开关噪声被PI放大堵转后恢复出现长时间超调减小integral_max积分累积过多需要等积分泄放一个容易被忽略的细节是电机空载时调好的参数带负载后常需要回炉。空载时电枢电流小PI调节器工作在低占空比区间带载后电流增大驱动桥的非线性和电机电刷换向噪声都会改变系统增益。所以整定参数时至少要在空载、半载、满载三种工况下各跑一次阶跃取折中值。5. 用串口示波器验证电流环动态响应量化调参效果5.1 构造轻量级串口数据帧直读目标电流与实际电流PID参数调得好不好不能只靠耳朵听电机声音要把数据拿出来看。STM32F407带有多个USART项目里使用USART1连接板载USB转串口芯片把目标电流和实际电流通过串口实时发出来在PC端用VOFA这类串口示波器观察波形。串口输出使用的代码简短放在主循环或200Hz的定时器回调里void Debug_SendCurrentFrame(void) { char buf[64]; int len snprintf(buf, sizeof(buf), %.2f,%.2f\n, g_current_target, g_current_amp); HAL_UART_Transmit(huart1, (uint8_t*)buf, len, 100); }使用VOFA的协议就是JustFloat格式也可以用更简单的CSV格式每行两个浮点数第一个是目标电流第二个是采样电流。串口波特率设置为115200或460800均可460800下每秒能传约4600帧数据足够覆盖20kHz电流环的包络信息。帧格式里特意把目标电流放在第一列这样示波器上两条曲线叠加后可以直接观察实际电流对目标电流的跟踪速度和超调量。阶跃响应测试前先串口发送指令把目标电流从0A突变到1A观察电流上升时间。好的电流环上升时间应该在1ms到5ms之间对应控制周期50μs的20到100个周期。上升太慢说明Kp太小或输出限幅限制过紧上升过快并伴随振荡说明Kp偏大。5.2 三个可用参数与调试中的典型陷阱VOFA上观察到的数据是经过采样电阻、运放、ADC三层处理的模拟量换算结果响应速度远慢于电机内部的真实电流变化。所以示波器上看到的延迟可能是正常的要判断是否异常计算整个链路延迟运放带宽通常在百kHz级别ADC采样保持时间这里设置为480个周期约5.7μs与20kHz的PWM周期相比占约11%这个延迟在可接受范围内。如果采样时间设置过短反而会因为欠采样混叠引入波形失真。调试时有三个参数值得反复确认。第一个是运放增益与ADC参考电压的匹配度。F407的VREF引脚如果没有单独接基准源默认接到VDDAVDDA与3.3V存在约几十毫伏的波动这会被直接折算到电流测量值里。如果对精度要求高把VREF接到外部2.5V或3.0V基准源同时把上面代码里的基准电压常量从3.3改为实际值。第二个是电流环与外部级联PID的配合。如果后续要把这个电流环嵌入速度环或位置环注意外环输出要经过限幅再作为电流环给定。直接让速度环输出的PWM值进入电流环目标一旦速度环饱和电流环会一直工作在最大电流状态非常危险。常见做法是把速度环输出限制在电机额定电流的1.2倍以内。第三个是堵转检测与保护。电流环本身不会避免堵转——堵转电流被控制在设定值上但持续的大电流仍然会加热电机和驱动芯片。可以在主循环里检测电流环输出持续超过阈值的时间比如实际电流大于2A连续超过500ms就关闭PWM输出并让AIN1和AIN2同时输出低电平使H桥进入制动状态而不是继续通电。这个逻辑我通常放在一个10ms周期的定时器回调里判断次数累计逻辑简单不会干扰20kHz电流环的实时性。把这套串口输出接到示波器后建议先跑一组目标电流从0.5A跳到1.5A再跳回0.5A的三角波测试观察电流跟踪是否存在相位滞后再跑一组长方波阶跃读取超调量最后用手捏住电机轴模拟堵转确认电流被限制在设定值附近且持续一段时间后保护停机。这三个波形过了电流环基本就可以交给上层的速度环和位置环使用了。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价