大一新生手搓STM32寻迹小车:从零到跑通,我的HAL库踩坑与填坑实录

张开发
2026/4/21 17:33:05 15 分钟阅读

分享文章

大一新生手搓STM32寻迹小车:从零到跑通,我的HAL库踩坑与填坑实录
大一新生手搓STM32寻迹小车从零到跑通我的HAL库踩坑与填坑实录第一次点亮STM32开发板时LED微弱的光芒让我兴奋得像个发现新大陆的孩子。作为刚接触嵌入式开发的菜鸟我从未想过三个月后自己能独立完成一辆寻迹小车的制作。这段从零开始的旅程充满了HAL库的迷惑、硬件接线的混乱和深夜调试的崩溃但最终小车稳稳跑起来的那一刻所有挫折都化作了成长的养分。1. 硬件准备从散件到系统搭建在淘宝下单的那一刻我根本分不清L298N和TB6612的区别。店家推荐的STM32小车全家桶包含核心控制STM32F103C8T6最小系统板后来发现正点原子的教程更友好动力系统L298N驱动模块 12V减速电机 ×2 万向轮感知部件TCRT5000红外循迹模块 ×4能源方案18650电池盒 AMS1117降压模块第一个坑出现在电源系统。当我直接把12V电池接入L298N的电源口时电机疯狂转动但STM32不断重启。后来用万用表测量才发现测量点理论电压实际电压电池输出端12V11.7VL298N 5V输出5V4.3VAMS1117输出3.3V2.9V提示L298N的5V输出只能给模块自身供电不能作为MCU电源。正确做法是电池正极同时接L298N和独立降压模块分别给电机和STM32供电。接线时还闹过笑话把红外模块的AO输出当数字信号接GPIO结果小车像醉汉一样左右摇摆。直到示波器显示AO输出的是模拟波形才明白需要接ADC引脚或者改用DO模式。2. HAL库初体验从迷茫到顿悟正点原子的标准库教程看了三遍却发现自己的开发板只能用HAL库。CubeMX生成的代码像天书一样尤其是这个神秘结构体TIM_HandleTypeDef htim4; htim4.Instance TIM4; htim4.Init.Prescaler 72-1; htim4.Init.CounterMode TIM_COUNTERMODE_UP; htim4.Init.Period 1000-1;三个关键认知突破HAL库通过句柄(Handle)管理外设比直接操作寄存器友好CubeMX生成的代码只是骨架需要自己填充肌肉HAL_Delay()会阻塞整个系统后来改用HAL_TIM_PeriodElapsedCallback实现非阻塞延时最痛苦的PWM配置过程让我理解了时钟树系统时钟72MHz通过APB1预分频(默认2分频)得到36MHzTIM4挂在APB1上因此需要设置Prescaler36-1得到1MHz计数频率设置Period100-1实现10kHz PWM波// 电机速度控制实战代码 void Set_Motor_Speed(uint8_t speed) { __HAL_TIM_SET_COMPARE(htim4, TIM_CHANNEL_1, speed); __HAL_TIM_SET_COMPARE(htim4, TIM_CHANNEL_2, speed); }3. 循迹算法从理论到实践四路红外模块的安装位置决定了算法效果。最初将模块间距设为2cm结果小车在弯道频繁出轨。通过实验数据对比模块间距直道稳定性弯道通过率反应速度1.5cm★★★★☆★★☆☆☆快速2.0cm★★★☆☆★★★☆☆中等3.0cm★★☆☆☆★★★★☆较慢最终采用非对称安装中间两模块间隔1.8cm外侧模块间隔3cm。控制逻辑优化为void Track_Handler(void) { uint8_t sensor (HAL_GPIO_ReadPin(GPIOA, L1_Pin) 3) | (HAL_GPIO_ReadPin(GPIOA, L2_Pin) 2) | (HAL_GPIO_ReadPin(GPIOA, R2_Pin) 1) | HAL_GPIO_ReadPin(GPIOA, R1_Pin); switch(sensor) { case 0b1000: Hard_Left(); break; case 0b1100: Soft_Left(); break; case 0b0110: Forward(); break; case 0b0011: Soft_Right(); break; case 0b0001: Hard_Right(); break; default: Stop(); } }4. 调试血泪史那些教科书不会教的事最诡异的bug小车偶尔会突然加速撞墙。用逻辑分析仪抓取PWM波形后发现当电池电压低于10V时L298N的输出会出现异常脉冲。解决方案是增加电压检测// 在main.c的while循环中添加 if(HAL_ADC_GetValue(hadc1) 2400) { // 对应10V Stop(); HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET); }GPIO配置的坑忘记启用GPIO时钟(__HAL_RCC_GPIOA_CLK_ENABLE())输出模式误设为开漏(GPIO_MODE_OUTPUT_OD)输入模式未启用上拉电阻(GPIO_PULLUP)最耗时的调试竟然是电机线序问题。当发现左右轮转向相反时我花了两个小时检查代码最后发现是L298N的输出线接反了。现在养成了新习惯// 电机测试函数 void Motor_Test(void) { Forward(); HAL_Delay(1000); Turn_Left(); HAL_Delay(1000); Turn_Right(); HAL_Delay(1000); Stop(); }5. 进阶优化从能跑到跑得好加入PID控制后小车的循迹效果明显提升。简易位置式PID实现typedef struct { float Kp, Ki, Kd; float error, last_error, integral; } PID_TypeDef; void PID_Update(PID_TypeDef *pid, float setpoint, float actual) { pid-error setpoint - actual; pid-integral pid-error; float derivative pid-error - pid-last_error; float output pid-Kp * pid-error pid-Ki * pid-integral pid-Kd * derivative; pid-last_error pid-error; return output; }参数调试经验值Kp从0.5开始观察小车摆动幅度Kd通常取Kp的1/10抑制超调Ki最后微调避免积分饱和电池续航问题通过引入休眠模式解决当所有传感器10秒未检测到黑线时自动进入STOP模式电流从120mA降至2mA。唤醒方式很简单// 在中断回调函数中 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin SENSOR_Pin) { HAL_NVIC_SystemWakeUp(); } }这段开发经历让我明白嵌入式开发就像在黑暗森林中探险——每解决一个问题就会点亮一块区域而更多未知的领域仍在等待探索。当看到自己亲手打造的小车稳稳跑完全程时那种成就感比通关任何游戏都来得真实。

更多文章