STM32 FOC调试记录:程序卡死在set_pwm_duty,weak符号踩坑
现象
ADC中断正常后,把电流模式代码加进 while(1):
c
motor_control_context.torque_norm_d = 0;
motor_control_context.torque_norm_q = 0.4;
motor_control_context.type = control_type_torque;
HAL_Delay(1000);
程序卡在这段代码后面,后面的printf不打印了。更严重的是,电机开始发烫——PWM还在输出,但FOC中断不再更新占空比,某一相持续通电。
排查过程
第一步:用printf逐段定位
在关键位置加printf,看程序执行到哪一步:
c
printf("A\r\n");
motor_control_context.type = control_type_torque;
printf("B\r\n");
HAL_Delay(1000);
printf("C\r\n");
打印到B就停了,说明卡在 HAL_Delay(1000) 里或者它后面。
第二步:在lib_torque_control里加printf
c
void lib_torque_control(float torque_norm_d, float torque_norm_q)
{
printf("T1\r\n");
float d = torque_d_loop(torque_norm_d);
printf("T2\r\n");
float q = torque_q_loop(torque_norm_q);
printf("T3\r\n");
foc_forward(d, q, rotor_logic_angle);
printf("T4\r\n");
}
打印到T3就停了,卡在 foc_forward。
第三步:在foc_forward里加printf
c
void foc_forward(float d, float q, float rotor_rad)
{
printf("F1\r\n");
float d_u = 0, d_v = 0, d_w = 0;
svpwm(rotor_rad, d, q, &d_u, &d_v, &d_w);
printf("F2\r\n");
set_pwm_duty(d_u, d_v, d_w);
printf("F3\r\n");
}
打印了4个F1(进入函数、三个变量初始化、svpwm完成),第5个F1(set_pwm_duty之后)没打印。
卡在set_pwm_duty函数里。
根因:weak符号被链接器选中
打开 foc.c,发现里面有一个weak版本的 set_pwm_duty:
c
void set_pwm_duty(float d_u, float d_v, float d_w) __attribute__((weak));
void set_pwm_duty(float d_u, float d_v, float d_w)
{
while (1)
;
}
我的 main.c 里明明有强符号版本:
c
void set_pwm_duty(float d_u, float d_v, float d_w)
{
d_u = min(d_u, 0.9);
d_v = min(d_v, 0.9);
d_w = min(d_w, 0.9);
__disable_irq();
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, d_u * htim1.Instance->ARR);
// ...
__enable_irq();
}
但链接器选了 foc.c 里的weak版本,运行时卡在 while(1)。
关于weak符号
__attribute__((weak)) 是GCC/ARM编译器的弱符号机制。
规则:如果别处有同名的强符号,链接器就用强符号;如果没有,才用弱符号。
原作者的意图:
foc.c 是算法层,不应该关心底层硬件
所以放一个weak版本的 set_pwm_duty 作为占位
如果你的工程里实现了硬件驱动,就用你的
如果忘了实现,就执行 while(1) 死循环,让你一运行就发现
这个设计思路是对的,但在我这翻车了。
为什么链接器选错了
可能的原因:
链接顺序问题:foc.o 在 main.o 之前被链接,链接器先看到weak版本就定了
编译器对weak的处理有差异:不同工具链行为可能不一致
main.c里的强符号因为某种原因没被识别
结果就是本该用“主胎”的,用了“备胎”,卡死在while(1)。
修复方法
删掉 foc.c 里的weak函数定义,只保留一行声明:
c
void set_pwm_duty(float d_u, float d_v, float d_w);
这样链接器只能去 main.c 里找实现,没有选择余地。
改完后编译烧录,程序不再卡死,电机正常响应。
声明和定义的区别
写法 类型 作用
void set_pwm_duty(float d_u, float d_v, float d_w); 声明 告诉编译器有这个函数,具体实现去别处找
void set_pwm_duty(float d_u, float d_v, float d_w) { ... } 定义 给出函数的完整实现
唯一区别在末尾:有分号 = 声明,有花括号 = 定义。
总结
项目 内容
现象 程序卡死,电机发烫
根因 链接器选了foc.c里的weak版本,卡在while(1)
解决 删掉weak定义,只保留声明
教训 weak符号调试时要小心,编译期报错比运行期死循环好