[嵌入式][铁头山羊平衡车][Bug记录]PID控制失败:未引入math.h与SysTick中断隐患
·
问题背景
- 最开始使用的是铁头山羊的标准库的template模板直接开始跟着课程,学到PID课程部分做实验却存在一个问题:实际转速根本跟不上阶段性增加的目标转速
- 经过与课程源码对比后发现是有两个地方出了问题,留帖记录
问题点
-
app_pwm.c没有引入math.h,然后用了Duty = fabsf(Duty);,编译通过但是存在问题!!fabsf()是一个标准库函数,用于计算单精度浮点数 (float) 的绝对值。- 当编译器遇到
Duty = fabsf(Duty);却没有 fabsf 的原型时:- 它可能假设 fabsf 返回 int。
- 当你传递一个 float 类型的 Duty 给它,并期望返回一个 float 类型的绝对值时,实际发生的情况是未定义的。
- 最可能的情况是,fabsf 函数(在数学库中正确实现)确实计算了绝对值并准备返回一个 float,但调用它的代码(app_pwm.c 中的部分)却期望接收一个 int。这种类型不匹配会导致返回的浮点数值在被当成整数解释时变成一个完全不相关的、混乱的整数值。
- 或者,更糟糕的是,如果编译器完全不认识 fabsf,它可能将其优化掉,或者链接到一个完全不相关的同名函数(如果存在的话),或者 Duty 的值根本没有被正确地取绝对值,仍然保持为负。
- 结果可想而知这对 ccr 计算的灾难性影响:
uint16_t ccr = Duty / 100.0f * 999;- 假设PID控制器输出一个负的 Duty 值,例如 -50.0f,表示电机需要反向转动或减速。
- 如果 fabsf(Duty) 因为上述原因没有正确工作,Duty 的值可能仍然是 -50.0f(或者一个因类型错误解释而产生的混乱值)。
- 那么 ccr 的计算就会变成:
(-50.0f / 100.0f) * 999 = -0.5f * 999 = -499.5f。 - 当这个浮点数 -499.5f 被转换为 uint16_t (无符号16位整型) 时,它首先会被截断为整数 -499。
- 将负数 -499 赋给无符号类型 uint16_t 会导致数值“回绕”(wrap-around)。uint16_t 的范围是 0 到 65535。-499 会变成 65536 - 499 = 65037。
- 所以,ccr 变量现在的值是一个非常大的正数 65037。
- 原先使用的模板的
stm32f10x_it.c存在一个问题,直接递增ulTicks,没有检查COUNTFLAG
- 原先的模板工程
/**
* @brief This function handles PPP interrupt request.
* @param None
* @retval None
*/
/*void PPP_IRQHandler(void)
{
}*/
extern __IO uint64_t ulTicks;
void SysTick_Handler(void)
{
ulTicks++;
}
- 正确的模板工程
/**
* @brief This function handles PPP interrupt request.
* @param None
* @retval None
*/
/*void PPP_IRQHandler(void)
{
}*/
extern __IO uint64_t ulTicks;
void SysTick_Handler(void)
{
if(SysTick->CTRL & SysTick_CTRL_COUNTFLAG) // 检查COUNTFLAG
{
ulTicks++; // 注意这里 ulTicks 的类型应已根据上一步修正
}
}
- 这又是为什么?
SysTick是一个24位的倒计数定时器。当它从加载值(LOAD寄存器)递减到0时,硬件会将 SysTick->CTRL 寄存器中的 COUNTFLAG(第16位)置1,并(如果中断使能)触发SysTick中断,调用 SysTick_Handler。
读取 SysTick->CTRL 寄存器会清除 COUNTFLAG 位。这是硬件设计的一部分,用于确认中断已被处理。
- 中断标志位未清除: 在原来的写法中,SysTick_Handler 只是简单地递增 ulTicks,它并没有读取 SysTick->CTRL 寄存器。这意味着 COUNTFLAG 在中断处理后可能没有被清除(除非有其他代码意外地读取了这个寄存器)。
- 中断可能重复触发或行为异常(理论上):
- 如果
COUNTFLAG没有被清除,当中断控制器(NVIC)在中断服务程序返回后重新评估挂起的中断时,它可能会认为SysTick中断仍然是活动的(因为标志位还在),这理论上可能导致中断比预期更频繁地触发,或者在中断优先级处理上出现一些难以预测的行为。 - 如果
SysTick_Handler被异常地、过于频繁地调用,ulTicks 就会比预期的1毫秒(或其他设定的SysTick周期)递增得更快。
- 如果
- 对 dt (时间间隔) 计算的影响:
- PID控制器严重依赖于准确的时间间隔
dt来计算积分项error * dt和微分项delta_error / dt。 - 如果 ulTicks 递增的速度比实际流逝的时间快(例如,SysTick周期是1ms,但由于中断处理问题,ulTicks 每0.5ms就增加一次),那么:
dt的值(如果通过GetTick()或类似方式获得,并计算差值)相对于真实时间会显得“缩水”或不稳定。- 对于积分项:如果
dt看起来变小了(相对于真实时间),积分累积的速度会减慢,导致系统响应迟缓,难以消除静差。 - 对于微分项:如果
dt看起来变小了,delta_error / dt的结果会被放大,导致D项对噪声更敏感,输出更剧烈,可能引起系统震荡。
- PID控制器严重依赖于准确的时间间隔
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)