资讯动态

STM32电磁循迹小车代码详解:从ADC采样到舵机打角

发布时间:2026/10/26 19:27:00 来源:尧图企业网站定制
简介面向嵌入式初学者与参加智能车竞赛的学生这份资源提供基于STM32的电磁循迹小车完整工程代码并附有详细中文注释帮助读者理解从传感器数据采集到电机控制的完整实现链路。压缩包共249个文件、约9MB以c与h源文件为核心配合Keil工程文件uvprojx、uvoptx、编译产物o、d、crf、axf、hex及少量bat脚本与配置文档构成可直接编译下载的完整工程。代码在基础循迹之外集成蓝牙遥控、线路长度测量与定圈停止等扩展功能蓝牙部分通过串口接收并解析手机指令测距部分借助编码器脉冲计数估算行驶距离定圈逻辑则依靠标志点检测累计圈数后自动停车。已有6729人学习下载适合希望对照注释逐模块吃透STM32外设配置、PWM调速与转向控制的读者参考借鉴。1. 从一堆注释里读懂一辆电磁循迹小车拿到这份 STM32 电磁循迹小车代码时我先翻的不是主函数而是那几个被注释塞满的驱动文件。原因很简单电磁循迹的坑几乎全在底层——电感采回来的信号怎么滤波、差比和怎么算、舵机打角怎么映射这些细节决定了车是稳稳压线还是原地画龙。这份代码把蓝牙遥控、测线路长度、定圈停止这些小功能也一并做了进去注释密度高到接近手把手教学适合刚接触 STM32 循迹、想直接拿一套能跑的完整工程改参数的人。它不是一个精简到只剩核心算法的 Demo而是一辆真正下过赛道、被反复调过的车。下面我按「这车怎么想 → 怎么跑起来 → 怎么改」的顺序把这套工程拆开讲。2. 工程结构与技术选型一辆车由哪些文件撑起来2.1 目录分层与模块职责这套工程是典型的 STM32 标准外设库或 HAL 库风格按功能切成若干目录每个目录对应一类硬件或一类逻辑。我把它整理成下面这张表方便你拿到源码后先对号入座目录/文件职责改动频率Core/或USER/主函数、系统时钟、中断入口低HARDWARE/ADC/电感信号采集与滤波中HARDWARE/MOTOR/电机 PWM 输出与方向控制低HARDWARE/SERVO/舵机 PWM 打角中HARDWARE/BLUETOOTH/串口收发、遥控指令解析中HARDWARE/ENCODER/编码器测速、里程累计低APP/或主循环内循迹决策、定圈判断、状态机高分层的好处是改参数不用翻主函数。比如你想调舵机中值只动SERVO里的宏想换滤波系数只动ADC里的数组长度。我一般会先确认每个模块对外暴露的函数名再决定从哪一层下手避免改一处牵动全身。2.2 电磁循迹的信号链路电磁循迹的核心链路是电感 → 选频放大 → ADC 采样 → 归一化 → 差比和 → 打角。这份代码在 ADC 之后做了软件滤波常见做法是滑动平均或一阶低通目的是压掉电机换向带来的尖峰干扰。差比和公式一般写成(L - R) / (L R)的形式输出落在 -1 到 1 之间再乘以一个打角系数映射到舵机 PWM 脉宽。这个系数是最需要现场调的参数赛道宽度、电感高度、车速都会影响它。2.3 蓝牙遥控与定圈停止的挂载方式蓝牙遥控走的是串口中断收到指令后置一个标志位主循环里根据标志位切换「遥控模式 / 自主循迹模式」。定圈停止则依赖编码器累计脉冲每转一圈清零一次并计数达到设定圈数后强制停车。这两个功能都不复杂但挂载位置很关键——如果放在主循环里做阻塞判断会拖慢循迹响应这份代码用标志位加非阻塞判断算是比较稳妥的写法。3. 把车跑起来编译、烧录与首次上电3.1 环境准备与工程导入先确认你手上的工程是用哪个库建的。标准库工程一般带.uvprojxHAL 库工程可能带.ioc。用 Keil 打开后先检查三件事器件型号是否和你手上的板子一致、下载器配置是否为 ST-Link 或 DAP、晶振频率是否匹配。这三项任何一项不对编译能过但跑不起来是最常见的翻车点。# 以命令行方式确认工程文件结构Windows 下可用 dir # 重点看是否有 .uvprojx 和启动文件 startup_stm32f10x_hd.s ls -R | grep -E uvprojx|startup|\.s$上面这条命令只是帮你快速定位工程入口和启动文件。启动文件的后缀ld/md/hd必须和芯片 Flash 容量对应选错了会在链接阶段报错或运行时跑飞。3.2 关键宏配置逐项说明上电前必须改的宏通常集中在sys.h或各模块头文件里。我按重要性列一下// 电机 PWM 频率与舵机频率常见做法是电机 10kHz、舵机 50Hz #define MOTOR_PWM_FREQ 10000 #define SERVO_PWM_FREQ 50 // 舵机中值与左右极限脉宽单位 us #define SERVO_MID 1500 #define SERVO_LEFT 1100 #define SERVO_RIGHT 1900 // 差比和打角系数现场调 #define STEER_KP 0.8f // 定圈停止圈数 #define STOP_LAP_COUNT 3SERVO_MID一定要先用示波器或舵机测试程序确认不同舵机的中值可能差几十微秒中值偏了车会一直往一边偏。STEER_KP从 0.5 左右起步赛道急弯多就往上加直道抖就往下减。STOP_LAP_COUNT改圈数即可逻辑本身不用动。3.3 首次上电的验证顺序别一上来就放赛道。我的习惯是分三步验证第一步只上电不转电机用串口打印各电感 ADC 原始值确认每个电感都有明显变化第二步架空车轮看舵机打角是否跟随电感差值左右摆动第三步低速放赛道观察是否压线。任何一步异常就停下来查不要带着问题往下走。// 调试打印示例主循环里周期性输出电感值与打角 printf(L:%d R:%d steer:%.2f\r\n, adc_left, adc_right, steer_out);这段打印放在主循环里配合串口助手看波形。adc_left/adc_right是滤波后的值steer_out是最终打角量。如果左右值在直道上差异很大说明电感安装不对称或放大电路增益不一致先解决硬件再调软件。4. 核心代码拆解从 ADC 采样到舵机打角4.1 电感采样与滑动平均滤波#define FILTER_N 8 static uint16_t buf[FILTER_N]; static uint8_t idx 0; uint16_t adc_filter(uint16_t raw) { uint32_t sum 0; buf[idx] raw; idx (idx 1) % FILTER_N; for (uint8_t i 0; i FILTER_N; i) { sum buf[i]; } return (uint16_t)(sum / FILTER_N); }这是最朴素的滑动平均。FILTER_N越大越平滑但滞后越明显8 是一个折中值。电机启动瞬间的电流冲击会耦合到 ADC如果发现打角抖动可以先把FILTER_N加到 16 试试代价是过弯响应变慢。更讲究的做法是一阶低通y y k*(x - y)k取 0.1 到 0.3滞后比滑动平均小但需要调k。4.2 差比和计算与打角映射float steer_calc(uint16_t l, uint16_t r) { float diff (float)l - (float)r; float sum (float)l (float)r; if (sum 1.0f) return 0.0f; // 防止除零 float ratio diff / sum; // 落在 -1 ~ 1 return ratio * STEER_KP; // 映射为打角量 }ratio的符号决定左右绝对值决定打角大小。STEER_KP是唯一需要现场反复试的系数。注意sum过小时直接返回 0否则车在电感信号很弱的位置比如赛道边缘会算出离谱的打角。这个保护在实车上救过我好几次。4.3 舵机 PWM 输出void servo_set(float steer) { // steer 范围约 -1 ~ 1映射到脉宽 int32_t pulse SERVO_MID (int32_t)(steer * (SERVO_RIGHT - SERVO_MID)); if (pulse SERVO_LEFT) pulse SERVO_LEFT; if (pulse SERVO_RIGHT) pulse SERVO_RIGHT; __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, pulse); }这里用定时器比较值直接写脉宽前提是定时器计数频率已经配成 1MHz这样比较值就等于微秒数。如果你的定时器分频不同需要先换算。限幅那两行不能省否则急弯时舵机会打到机械限位长期下来齿轮容易扫齿。4.4 编码器里程与定圈判断volatile uint32_t pulse_total 0; volatile uint8_t lap_count 0; void encoder_isr(void) { pulse_total; if (pulse_total PULSE_PER_LAP) { pulse_total 0; lap_count; if (lap_count STOP_LAP_COUNT) { motor_stop(); } } }PULSE_PER_LAP需要实测让车直线跑一圈读pulse_total的累计值。这个值和轮径、编码器线数、减速比都有关换轮子就要重测。中断里只做计数和停车判断不要放打印或复杂运算否则会丢脉冲。5. 避坑与排查实车上最容易翻车的几处5.1 现象车一上电就往一边猛打原因多半是舵机中值没校准或者电感安装左右不对称导致静态差比和不为零。解决方法是先断开电机只上电看steer_calc在直道上的输出如果静态值偏离 0 超过 0.1就在代码里加一个零点补偿ratio - offsetoffset用实测静态值。5.2 现象直道能跑一进弯就冲出赛道这是STEER_KP偏小或滤波滞后太大。先把FILTER_N降到 4再把STEER_KP提高 0.1 到 0.2 试。如果还是冲出去检查电感高度是否太高——电感离地越远弯道处左右差值越小打角自然不够。5.3 现象定圈停止提前或延后触发PULSE_PER_LAP不准。这个值受轮胎打滑影响很大急弯多的一圈实际脉冲数会比直线多。稳妥做法是取多圈平均值或者干脆用赛道上的标志物配合电感特征做圈数判断编码器只做辅助。5.4 现象蓝牙遥控指令偶尔丢串口中断里做了耗时操作或者波特率和上位机不一致。检查中断服务函数里是否只做了收字节和置标志解析逻辑放到主循环。另外共地问题也会导致丢包蓝牙模块和主控必须共地。提示每次改完参数先用低速跑三圈再提速。参数是速度的函数低速调好的系数到高速未必适用。6. 二次开发把这份代码改成你自己的车这套工程最值钱的地方不是算法多先进而是结构清晰、注释到位改起来有抓手。我一般按「先换硬件参数、再调控制系数、最后加功能」的顺序改。换硬件时把SERVO_MID、PULSE_PER_LAP、MOTOR_PWM_FREQ这几个宏重新测一遍其他逻辑基本不用动。调系数时固定车速只动STEER_KP和FILTER_N一次只改一个改完记录在注释里不然调着调着就忘了哪组参数好用。加功能的话蓝牙遥控和定圈停止已经给了很好的挂载范例——都是「中断置标志、主循环处理」的模式。想加超声波避障照这个模式挂一个标志位就行想加 OLED 显示把打印换成屏幕刷新注意别放在中断里。验证方法我习惯用「分段验证」新功能先单独跑通再和循迹逻辑合并合并后先低速跑确认不干扰再提速。有个细节值得单独说这份代码的注释密度很高但注释和代码可能不同步。改参数时顺手把注释里的数值也改掉否则过两周你自己都分不清哪个是真实值。我曾经因为注释写着KP0.8实际代码是1.2排查了半天才发现是注释骗了我。从那以后我每次改完参数都强制走一遍「代码、注释、实测值」三方对齐希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑