任务二只解决了"能测"。要训练模型,还得有一个每次都一样的振动源:同一状态采十组,波形包络得基本重合,特征才稳定。拿手晃板子做不到,所以任务三用一个 28BYJ-48 步进电机加 ULN2003 驱动板做机械激励。
选步进是因为可重复。每给一拍转子转固定角度,转速只由换相频率决定,不随负载漂移;换成直流电机,同电压下空载和带载转速能差一倍,两次采集的频率对不上,样本就没法比。28BYJ-48 带 1:64 减速箱,半步角 0.088°,一圈 4096 个半步,节拍准了转速就是确定的。

引脚带不动线圈。每相约 90 毫安,两相通电接近 200 毫安,远超 STM32 单个 IO 的二十毫安,中间必须加一级。ULN2003 是达林顿阵列,这里用四路:输入侧只要基极电流,集电极侧承担线圈电流,芯片内部还有续流二极管,线圈断电瞬间的反向尖峰直接泄放掉。它同时是反相驱动,输入高电平对应的管子导通、把线圈一端拉到地,线圈另一端固定接 5 伏,所以 GPIO 输出 1 就是这相通电。这点先弄清楚,序列里 1 和 0 的含义才不会写反。
/* 四相控制线:PC7、PC6、PC9、PA7 */ GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_WritePin(MOTOR_IN1_PORT, MOTOR_IN1_PIN, GPIO_PIN_RESET); /* IN2、IN3、IN4 同样先拉低 */ GPIO_InitStruct.Pin = MOTOR_IN1_PIN; HAL_GPIO_Init(MOTOR_IN1_PORT, &GPIO_InitStruct); GPIO_InitStruct.Pin = MOTOR_IN4_PIN; HAL_GPIO_Init(MOTOR_IN4_PORT, &GPIO_InitStruct);
驱动序列用八拍半步:A、AB、B、BC、C、CD、D、DA,位模式是 0x1、0x3、0x2、0x6、0x4、0xC、0x8、0x9,bit0 到 bit3 依次对应 IN1 到 IN4。四拍整步每拍要跨 0.176°,转子得越过两个平衡位置,换相冲击和扭矩起伏都大;半步在整步之间插进一个两相通电的中间态,每拍只走 0.088°,冲击减半。做振动源这点很关键:整步波形上每拍顶一个尖峰,会跟齿轮啮合的高频冲击叠在一起,特征随电机停在哪一相而变;半步波形规整,同一转速下两次采集的包络几乎重合。
static const uint8_t s_seq[8] = { 0x1u, 0x3u, 0x2u, 0x6u, 0x4u, 0xCu, 0x8u, 0x9u };
static void coil_apply(uint8_t pattern)
{
uint8_t i;
for (i = 0u; i < 4u; i++)
{
HAL_GPIO_WritePin(s_coil[i].port, s_coil[i].pin,
((pattern >> i) & 0x1u) ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
}换相不能放主循环。采一窗要阻塞 1.28 秒,还要打印 512 行,一段几十毫秒,靠主循环轮询换相,转速就跟着打印节奏抖。所以换相挂在定时器中断上:TIM6 从 250 兆二分频到 1 兆,再计 1000 得到 1 毫秒时基,中断里用累加器数够设定的拍间隔再走一拍。主循环被采集、打印、命令行占多久,转速都不受影响。
htim6.Init.Prescaler = 250u - 1u; /* 250MHz / 250 = 1MHz */
htim6.Init.Period = 1000u - 1u; /* 1MHz / 1000 = 1kHz */
HAL_TIM_Base_Start_IT(&htim6);
void Motor_Service1ms(void) /* 在 TIM6 回调里调用 */
{
if (s_running == 0u) { return; }
if (++s_acc < s_step_ms) { return; }
s_acc = 0u;
s_phase = (uint8_t)((s_phase + (s_dir == 0u ? 1u : 7u)) & 0x7u);
coil_apply(s_seq[s_phase]);
}拍间隔做了三档:2、4、8 毫秒,正好按两倍递增。按 4096 个半步一圈算,分别约 7.3、3.7、1.8 转每分,三档的转速差一倍,不用仪器也分得出来。28BYJ-48 的牵入频率标称 600 赫兹以上,但那是 12 伏空载的条件,5 伏供电实际到 300 赫兹附近就开始丢步,表现为咯咯响却不转。最快那档 500 赫兹贴着这条线,再快就得先试。
停机时四相全部拉低。线圈一直通电会持续发热,两相通电接近 200 毫安,不管它十几分钟外壳就烫手;做振动源也不需要保持扭矩,停下就断电,转子回到自由状态。串口给了三个命令:m 起停,d 换向,s 在三档间循环并打印当前拍间隔,采集和实时流进行中都能随时敲。
void Motor_ToggleRun(void)
{
s_running = (uint8_t)(s_running ? 0u : 1u);
if (s_running == 0u)
{
coil_apply(0u); /* 停机断电,避免线圈长期通电发热 */
}
}烧任务三的固件,开机自检会打印带 stepper motor 的一行。敲 m 电机开始转,再敲一次停;敲 d 转向立刻反过来;敲 s 换速度,拍间隔在 2、4、8 毫秒之间轮换。电机转起来后敲 v 开实时流,正常情况下 Z 轴会随着转动出现周期性起伏,把电机压在桌面上比悬空拿着明显;如果几乎没变化,先确认四相线序有没有插错、驱动板电源灯亮不亮。再敲 c 采一窗,1280 毫秒里电机会一直转:换相在中断里,采集只是在等 DWT 计数,两者互不干扰。
不接串口也能操作。板上的蓝色按键 B1 短按一下电机开始转,转起来之后每按一下往上换一档,到第三档再按回到最快那档;按住超过 0.8 秒是停。按键判定在边沿中断里做:按下记时刻,松开时算按住多久,不足 0.8 秒算短按,所以 PC13 配成双边沿触发,而不是任务一里的单边沿。绿灯跟着档位走:切档时先闪 N 下,N 就是第几档,每下亮 200 毫秒、中间停 400 毫秒,隔得开才数得清;闪完转成按转速闪,周期是拍间隔的 40 倍、亮的时间占一半,档位越高闪得越快,停机就灭。

任务三到这里完成。下一步把静止、正常运转、堵转三种状态各采几十组,做成训练集交给 NanoEdge AI Studio。
我要赚赏金
