STM32 FOC调试记录:从串口无数据到电机闭环
2026/9/23 10:12:04 网站建设 项目流程

最近在学习无刷电机的FOC控制,硬件平台是STM32F103C8T6,MT6701磁编码器(SPI接口),三相全桥驱动,双ADC注入组采样电流,上位机用VOFA+看波形。

程序架构参考了一个开源例程:ADC中断里执行FOC核心算法(Clarke、Park、PID、SVPWM),主循环只做printf和LED闪烁。

按照例程移植完之后,遇到了两个问题,在这里记录一下排查过程。

问题一:串口发不出数据

现象

程序烧录后,LED正常闪烁,说明主循环在跑。但VOFA+接收区没有任何数据。试了`printf`和直接调用`HAL_UART_Transmit`,都收不到。

先检查了软件层面:

`fputc`函数已重定向到USART2

Keil里`Use MicroLIB`已勾选

波特率115200,COM口选的COM3

硬件TX/RX没接反,GND也共地了

这些看起来都没问题。

然后我把`while(1)`里改成只发一句最裸的测试:

HAL_UART_Transmit(&huart2, (uint8_t*)"HELLO\r\n", 7, 100);

HAL_Delay(500);

结果还是收不到。

我做了什么测试

把商家提供的能正常运行的例程和自己的工程做了逐文件对比:

`usart.c`里的`MX_USART2_UART_Init`函数——配置完全一样

`main.c`里的`SystemClock_Config`——看起来也一样

但仔细看`SystemClock_Config`的最后一行

PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV2

例程里是`RCC_ADCPCLK2_DIV6`。

打开CubeMX的Clock Configuration看了一下,ADC预分频选的是/2,ADC时钟显示36MHz,而且是红色的

查到的资料

翻STM32F103的数据手册,ADC时钟的最大值是14MHz。36MHz超了2.5倍还多。

根因

ADC时钟超频导致外设时序紊乱,USART2的发送被干扰了。

解决

在CubeMX里把ADC Prescaler从/2改成/6,ADC时钟变成12MHz。重新生成代码,编译烧录,串口立刻正常了。

小结

CubeMX里红色的配置项不能忽略,它是在提示你配置不合法。ADC超频这种问题,表面上看和串口无关,但实际上会影响整个芯片的外设时序。

问题二:程序卡死,电机发

现象

串口通了之后,把电流模式加进`while(1)`,结果程序卡在这行之后

motor_control_context.type = control_type_torque;

后面的printf不打印了。同时电机开始发烫——PWM还在输出,但FOC中断似乎没有更新占空比。

我做了什么测试

在`case control_type_torque:`前后加printf:

case control_type_torque:

printf("T-start\r\n");

lib_torque_control(motor_control_context.torque_norm_d, motor_control_context.torque_norm_q);

printf("T-end\r\n");

break;

结果:`T-start`打印了,`T-end`没打印。说明卡在`lib_torque_control`里。

继续往里加:

void lib_torque_control(float torque_norm_d, float torque_norm_q)

{

printf("L1\r\n");

float d = torque_d_loop(torque_norm_d);

printf("L2\r\n");

float q = torque_q_loop(torque_norm_q);

printf("L3\r\n");

foc_forward(d, q, rotor_logic_angle);

printf("L4\r\n");

}

结果打印了`L1`、`L2`、`L3`,`L4`没打印。说明卡在`foc_forward`。

再往里:

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`函数里。

查到的资料

打开`foc.c`,发现里面有一个weak版本的`set_pwm_duty`:

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`里也有一个同名的`set_pwm_duty`,是真正写寄存器的版本。

我查了一下`__attribute__((weak))`的机制:它表示这是一个“弱符号”,如果链接时发现别处有强符号(普通函数),就用强符号。如果没有,才用这个弱符号。

按理说我的main.c里有强符号,链接器应该用main.c的版本。但实际上,链接器选了foc.c里的weak版本,导致程序卡在while(1)。

关于weak,我的理解

作者在foc.c里放一个weak版本的set_pwm_duty,我理解是为了解耦:

foc.c只负责FOC的数学计算,不应该关心底层硬件是哪个芯片、哪个定时器。

它只需要知道调用set_pwm_duty就能输出PWM就够了。

所以foc.c里放一个weak版本的占位函数,如果你的工程里有实现,就用你的;如果没有实现,就执行这个占位函数。

占位函数写成`while(1);`是故意的:如果忘了实现底层驱动,程序会卡死在这里,让你立刻发现。** 如果占位函数是空的,程序会静默地不输出PWM,电机不转,但你不知道是哪里出了问题,更难查。

这个设计思路是对的,但在我这里翻车了——链接器选了weak版本而不是强符号。我查了一些资料,可能和链接顺序、编译器的weak符号处理有关。

删掉foc.c里的weak函数定义,只保留一行声明:

void set_pwm_duty(float d_u, float d_v, float d_w);

这样链接器只能去`main.c`里找实现,没有选择余地。重新编译烧录,程序不再卡死。

小结

weak符号的机制虽然灵活,但调试时容易让人抓狂——链接器选错版本时不会报错,运行时才暴露问题。如果是我自己写代码,我会在`foc.c`里只放声明不放定义,让链接器在编译期就报未定义符号,第一时间暴露问题。

总结

这两个问题花了我不少时间,记录几点心得

1. CubeMX里红色的配置项必须处理。ADC时钟超频这种问题,表面看和串口无关,实际上会影响整个系统

2. 调试时按信号链逐层隔离。从主循环→中断→算法函数→底层驱动,一层层加printf定位,比瞎猜有效得多。

3. weak符号有它的设计意图,但调试时要小心。链接器的行为依赖于工具链,跨编译器可能不一致。更安全的做法是只声明不定义。

后续会继续记录PID调参和VOFA+在线整定的过程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询