1.蜂鸣原理
在消费级无人机解锁、手持电动工具自检或工业协作机器人启动时,操作者往往需要一种直观的听觉反馈来确认设备状态。许多设计选择利用电机线圈本身来发出蜂鸣声,节省了额外的蜂鸣器硬件空间,还能直接利用现有的驱动控制算法。
电机发声的机理与动圈式扬声器很相似,在BLDC电机中,定子绕组就相当于“音圈”,转子永磁体提供了磁场。当向绕组注入高频的交变电流时,定子产生的交变磁场会与转子的永磁场相互作用,产生高频波动的电磁转矩。这种高频波会引起定子齿、绕组线圈以及电机外壳的高频振动,发出声音。
在FOC框架下,我们将三相坐标系下的电流通过Clarke变换映射到α-β平面。为了让电机只发声而不旋转,最理想的方法是在α轴上注入信号:在α轴上施加一个高频的正弦波或方波电压信号,保持β轴电压为0。这种单轴向的高频脉冲产生的平均力矩为零,转子只会在原位发生小幅度的往复微振,不会产生连续旋转。
通过SVPWM调制,系统可以利用功率管的快速开关模拟出这一高频电压向量。FOC矢量注入可以将参考电压矢量精确地映射为三相PWM,占空比受控,使得声学效果更稳定。
改变电压的频率可以改变声音音调;改变幅值则可以改变响度。
2.技术实现
本工程依然在MCSDK生成的代码框架下进行修改和开发,关于MCSDK的使用、开发板硬件说明等等,可以参考前面几篇文章:为了验证可行性,尝试编写代码,在2208电机启动前加入两声蜂鸣声。IDLE 状态下按下按钮 → 蜂鸣1开始 → 蜂鸣1(250ms) → 间隔(90ms) → 蜂鸣2开始 → 蜂鸣2(250ms) → 启动电机
关键参数设置
在 mc_tasks.c 中,定义了几个可调节的宏定义:
| 参数 |
值 |
含义 |
| PRESTART_BEEP_ON_MS |
250ms |
每段蜂鸣持续时间 |
| PRESTART_BEEP_GAP1_MS |
90ms |
两段蜂鸣间隔 |
| PRESTART_BEEP1_HALF_MS |
1ms |
半周期 |
| PRESTART_BEEP1_AMP |
28000 |
第一段蜂鸣振幅 |
| PRESTART_BEEP2_AMP |
22000 |
第二段蜂鸣振幅 |
半周期用于调节PWM的频率;最后两个幅值参数的范围是 [-32767, 32767],用于调节响度。28000和22000两个不同的振幅可以验证电压幅值对响度的影响。
声明了用于状态机的几个全局变量:
static volatile bool g_prestart_beep_pending = false;
static PrestartBeepState_t g_prestart_beep_state = PRESTART_BEEP_IDLE; //状态机
static uint32_t g_prestart_beep_tick = 0U;
static uint32_t g_prestart_beep_mod_tick = 0U;
static bool g_prestart_beep_wave_positive = true; //标志位,用于反转波形
用于状态机的结构体,标志蜂鸣执行的状态:
typedef enum
{
PRESTART_BEEP_IDLE = 0,
PRESTART_BEEP_1_ON,
PRESTART_BEEP_1_GAP,
PRESTART_BEEP_2_ON,
PRESTART_BEEP_2_GAP
} PrestartBeepState_t;
波形生成方式
每个蜂鸣段内部以1ms的半周期快速翻转α轴的电压极性,等效为产生500Hz的双极性方波信号,比如幅值为28000的方波,就是在28000和-28000之间翻转。在电机的机械特性和绕圈电感作用下,这个方波会转化为相对平滑的周期性激励,驱动结构振动发声。
我们的函数都在mc_tasks.c中定义和调用: UI_ApplyPrestartBeepVector():每个半周期翻转一次 g_prestart_beep_wave_positive 标志位,产生双极性的方波信号。PWMC_SetPhaseVoltage() 函数的调用可以自动计算对应的PWM波形并驱动MOS管。
关于PWMC_SetPhaseVoltage()接口的介绍可以在用户文档里的Component/PWM & Current Feedback里面找到:
文档文本大意:作用是将α和β的电压转换成PWM占空比并将其馈送到逆变器。该函数计算每个相位的晶体管在PWM周期内被接通的时间,以达到由Valfa_beta设定的参考相位电压。然后,该函数对相关定时器通道中的占空比进行编程。它还相应地设置下一个PWM周期的相电流采样点。
static inline void UI_ApplyPrestartBeepVector(PrestartBeepState_t state)
{
alphabeta_t v;
int16_t amp = UI_GetPrestartBeepAmp(state);
v.alpha = g_prestart_beep_wave_positive ? amp : (-amp); // +-交替
v.beta = 0;
(void)PWMC_SetPhaseVoltage(pwmcHandle[M1], v); // 施加到电机
}
状态机
蜂鸣过程由状态机统一调度,我们利用 HAL_GetTick() 获取系统运行时间,并依据预设参数控制两段蜂鸣的时序。在蜂鸣持续期间,通过翻转g_prestart_beep_wave_positive标志位,生成方波信号,并调用前面实现的UI_ApplyPrestartBeepVector()函数将电压施加到电机上,使其振动发声。
在蜂鸣完毕后,自动调用MC_StartMotor1()启动电机。
//这两个可以在文件头用户自定义区加入,用于判断两声蜂鸣的频率和振幅
static inline uint32_t UI_GetPrestartBeepHalfMs(PrestartBeepState_t state)
{
return (state == PRESTART_BEEP_1_GAP) ? PRESTART_BEEP1_HALF_MS : PRESTART_BEEP2_HALF_MS;
}
static inline int16_t UI_GetPrestartBeepAmp(PrestartBeepState_t state)
{
return (state == PRESTART_BEEP_1_GAP) ? PRESTART_BEEP1_AMP : PRESTART_BEEP2_AMP;
}
在用户区实现状态机:
/* USER CODE BEGIN Private Functions */
static void UI_ProcessPrestartBeep(void)
{
uint32_t now;
if (!g_prestart_beep_pending)
{
return;
}
if (IDLE != MC_GetSTMStateMotor1())
{
g_prestart_beep_pending = false;
g_prestart_beep_state = PRESTART_BEEP_IDLE;
PWMC_SwitchOffPWM(pwmcHandle[M1]);
return;
}
now = HAL_GetTick();
switch (g_prestart_beep_state)
{
case PRESTART_BEEP_1_ON:
g_prestart_beep_tick = now;
g_prestart_beep_mod_tick = now;
g_prestart_beep_wave_positive = true;
PWMC_SwitchOnPWM(pwmcHandle[M1]);
UI_ApplyPrestartBeepVector(PRESTART_BEEP_1_GAP);
g_prestart_beep_state = PRESTART_BEEP_1_GAP;
break;
case PRESTART_BEEP_1_GAP:
if ((now - g_prestart_beep_tick) >= PRESTART_BEEP_ON_MS)
{
PWMC_SwitchOffPWM(pwmcHandle[M1]);
g_prestart_beep_tick = now;
g_prestart_beep_state = PRESTART_BEEP_2_ON;
}
else if ((now - g_prestart_beep_mod_tick) >= UI_GetPrestartBeepHalfMs(g_prestart_beep_state))
{
g_prestart_beep_mod_tick = now;
g_prestart_beep_wave_positive = !g_prestart_beep_wave_positive;
UI_ApplyPrestartBeepVector(g_prestart_beep_state);
}
break;
case PRESTART_BEEP_2_ON:
if ((now - g_prestart_beep_tick) >= PRESTART_BEEP_GAP1_MS)
{
g_prestart_beep_mod_tick = now;
g_prestart_beep_wave_positive = true;
PWMC_SwitchOnPWM(pwmcHandle[M1]);
UI_ApplyPrestartBeepVector(PRESTART_BEEP_2_GAP);
g_prestart_beep_tick = now;
g_prestart_beep_state = PRESTART_BEEP_2_GAP;
}
break;
case PRESTART_BEEP_2_GAP:
if ((now - g_prestart_beep_tick) >= PRESTART_BEEP_ON_MS)
{
PWMC_SwitchOffPWM(pwmcHandle[M1]);
g_prestart_beep_tick = now;
g_prestart_beep_state = PRESTART_BEEP_IDLE;
g_prestart_beep_pending = false;
g_as5600_gimbal_hold_active = false;
(void)MC_StartMotor1();
}
else if ((now - g_prestart_beep_mod_tick) >= UI_GetPrestartBeepHalfMs(g_prestart_beep_state))
{
g_prestart_beep_mod_tick = now;
g_prestart_beep_wave_positive = !g_prestart_beep_wave_positive;
UI_ApplyPrestartBeepVector(g_prestart_beep_state);
}
break;
case PRESTART_BEEP_IDLE:
default:
g_prestart_beep_pending = false;
break;
}
if ((!g_prestart_beep_pending) && ((HAL_GetTick() - g_prestart_beep_tick) > PRESTART_BEEP_GAP2_MS))
{
/* Keep timing constants intentionally visible for future tuning. */
}
}
在MC_RunMotorControlTasks()中调用上面我们编写的状态机函数:
__weak void MC_RunMotorControlTasks(void)
{
if (0U == bMCBootCompleted)
{
/* Nothing to do */
}
else
{
/* ** Medium Frequency Tasks ** */
/* USER CODE BEGIN MC_Scheduler 0 */
UI_SyncUserControlMode();
UI_ProcessPrestartBeep();
/* USER CODE END MC_Scheduler 0 */
//……后面省略
整个过程在中速FOC任务中处理,这个任务是在SysTick_Handler中运行的,靠中断进入,不阻塞主循环,确保系统实时性不受影响。调度链如下:
SysTick → MC_RunMotorControlTasks → UI_ProcessPrestartBeep
实验结果可以清晰发出两声“哔哔”提示音。
4.小结
本文利用FOC控制框架,通过在α轴注入高频双极性电压信号,实现了无需额外硬件的电机蜂鸣功能。该方法利用电机结构的机械振动特性,将电磁激励转化为声波输出。