拓冰建站拓冰建站
首页 / 资讯中心 / 正文

电磁四轮寻迹车调参前必修:信号链路校准与STC16控制框架

简介面向智能车电磁四轮入门者的基础寻迹代码包围绕逐飞STC16核心板编写采用前后台顺序执行框架代码量不大适合学习电磁寻迹基本逻辑后自行扩展元素与算法。资源共77个文件以C语言头文件.h和源文件.c为主另有引脚分配说明、Keil工程文件uvproj/uvopt、静态库lib等压缩包仅152KB内容包含逐飞外设库、官方库文档、MDK工程目录与引脚分配txt结构清晰便于快速定位。已有3438人学习下载配套作者系列博客使用可对照理解尤其适合借鉴其外设初始化与基础寻迹思路作为校赛起步模板。该版本于2022年11月有小更新曾下载者可联系作者重新获取内容适合准备电磁组校赛或刚接触STC16的选手作为起点读懂顺序执行的代码流程后可再逐步加入弯道、十字等元素。1. 先别动PID把电磁四轮的信号链路校准确再说我拆完这套代码的第一反应是它没有惊天动地的算法但几乎所有关键拐点都留对了接口。电磁四轮跑不直、入弯推头多数时候不是PID参数没调好而是电感物理安装和信号链路没对准——这套逐飞STC16F工程正好把这两件事放到最前面。代码库结构很干净外设层用逐飞STC16库封掉了寄存器细节你的工作是补全“处理逻辑”而不是和硬件搏斗。前后台框架适合刚转电磁组的人先跑通闭环也适合老手快速验证自己的传感器排布方案。文档目录里那张引脚分配表值得你先花十分钟读完再接线。2. 从压缩包到工程逐飞STC16库的分层逻辑2.1 解压之后先看懂这六个目录拿到压缩包后我习惯先建立工作目录再解压避免源工程里的相对路径失效unzip CSDN.zip -d emag_car cd emag_car ls -F解压后你能看到CSDN Libraries、seekfree_peripheral、seekfree_libraries、doc、libraries、Project CODE/MDK/USER这几个部分。初次接触逐飞库的人容易在seekfree_*两个目录里迷路我先给出一张划分表目录职责你大概什么时候会去改seekfree_peripheral芯片外设驱动层ADC、PWM、定时器、串口等更换引脚或通道时seekfree_libraries与内核相关的公共封装时钟、中断入口、常用宏基本不动libraries第三方扩展库通常放滤波、控制算法源码加新算法时引用doc芯片手册、数据说明查复用功能和时钟上限Project CODE/MDK/USERKeil 工程入口USER下是main.c和引脚分配 txt每次调试的重点区域这套分层和逐飞官方推荐的结构保持一致外设库把寄存器操作收口成类似adc_init()、pwm_init()的接口你不需要在ADCCFG、PWMA_CCR上反复翻手册。代价是接口参数有学习成本比如同一个ADC_1模块下不同通道的引脚映射不同批次的核心板有可能不一样必须以上手时那份STC16F推荐引脚分配.txt为准。如果你发现初始化时某个通道读值恒为 0先回这份 txt 核对 GPIO 复用关系而不是怀疑代码。2.2 外设初始化的标准套路这个工程把初始化集中在all_init()里属于前后台框架里很典型的写法。下面是一段参考初始化摘掉了具体宏定义只保留调用逻辑void all_init(void) { // 两路水平电感分别接在 ADC_1 模块的 CH8、CH9 通道 adc_init(ADC_1, ADC_1_CH8, ADC_12BIT); adc_init(ADC_1, ADC_1_CH9, ADC_12BIT); // 舵机 50Hz初始占空比 7.5%对应约 1.5ms 中值脉宽 pwm_init(PWM1, PWM1_CH1, 50, 0.075f); // 驱动电机 15kHz双路输出初始占空比为 0 pwm_init(PWM2, PWM2_CH1, 15000, 0.0f); pwm_init(PWM2, PWM2_CH2, 15000, 0.0f); // 定时器11ms 中断回调 timer_init(TIMER_1, 1000, timer1_callback); // 串口11152008N1 uart_init(UART_1, 115200, UART_8BIT, UART_1STOPBIT, UART_NO_PARITY); }参数说明adc_init第一个参数是 ADC 模块编号第二个是通道号第三个是分辨率ADC_12BIT对应结果范围 04095比 8 位分辨率更有利于差比和算法提取细微位置变化。pwm_init第三个参数是频率第四个是初始占空比 0.01.0。舵机 50Hz、电机 10kHz 以上这个组合是电磁四轮最常见的配置后面第 3 章会专门解释为什么。拿到这套代码时我会先把初始化里的模块编号和STC16F推荐引脚分配.txt逐行核对一遍再编译烧录。因为逐飞库在不同型号核心板上的宏定义并不完全一致只要版本对不上大概率会出现引脚冲突或 PWM 通道无输出。2.3 前后台理解“一行一行向下执行”的边界摘要里提到“一行一行向下执行”这正是前后台框架的核心特征。主循环是前台定时器中断是后台代码以线性顺序跑完再回到循环起点void main(void) { all_init(); while (1) { key_scan(); // 按键处理 debug_report(); // 串口日志 } }这段代码的含义是main初始化完成后进入死循环每次从头到尾执行一轮。没有操作系统调度也没有协程切换。所以你在任何函数里写while (flag 0);这种等待语句整台车都会僵在那里。常见做法是把所有耗时操作拆成短任务放进主循环中断回调里只记录时间和原始数据控制算法放到周期中断或者由主循环按节拍触发。void timer1_callback(void) { // 1ms 进入一次只做计时和标志位更新 tick_1ms; }主循环每轮执行时间不固定按键消抖、字符串格式化都会造成抖动所以控制节拍不能依靠主循环的循环速度而是靠定时器中断来“踢一脚”。这个设计的边界在于中断里不能做浮点滤波和串口打印否则中断周期会被拉长车就会在弯道里出现肉眼可见的顿挫。注意如果你的串口发送用了轮询等待把发送放到主循环不要在中断回调里直接 printf。3. 电磁采集与驱动输出引脚、频率和节拍怎么定3.1 电感信号进 ADC 前的检查项电磁组的信号链路是赛道导线流过 20kHz 左右交变电流车头水平电感感应出同频交流信号经过放大、检波后变成直流电平再送入单片机 ADC。逐飞库的adc_get_result()只能保证“读数正确”不能保证“读数代表真实赛道位置”。上电后如果发现某一侧数值恒定不变先从这三个点排查电感排布是否水平且对称检波电容是否漏电或虚焊ADC 通道和引脚分配表是否一一对应。我习惯在读取函数里做一次多次采样平均用来消除充电电容的残留电荷float read_sensor(uint8_t ch) { // 连续采 4 次取平均抵消随机抖动 uint32_t sum 0; for (uint8_t i 0; i 4; i) { sum adc_get_result(ADC_1, ch); delay_us(50); // 确保采样电容充分充电 } return sum / 4.0f; }delay_us(50)这段延时很关键。STC16F 的 ADC 是逐次逼近型信号源内阻偏大时第一次采样往往会略低于真实值。50us 足够内部采样电容稳定4 次累加总开销约 200us相对 10ms 控制周期来说可以忽略。早期我偷懒不写这个延时结果直道误差有 20 左右的波动弯道误差反而不明显查了很久才发现是采样建立时间的问题。3.2 舵机 50Hz 与电机 15kHz频率选错车就抖执行器频率不是越大越好下表列出这套代码里两个关键频率的选型和异常表现执行器典型频率占空比含义异常表现舵机50Hz周期 20ms高电平脉宽决定角度中值约 1.5ms频率过高会啸叫甚至烧舵机电机驱动10kHz20kHz占空比决定平均电压低于 5kHz 能听到明显嗡嗡声MOS 发热对应的初始化代码// 舵机50Hz占空比 0.075 脉宽 1.5ms pwm_init(PWM1, PWM1_CH1, 50, 0.075f); // 驱动15kHz占空比 0.3 30% 输出 pwm_init(PWM2, PWM2_CH1, 15000, 0.30f);舵机内部是直流电机加反馈电位器它的控制信号只认脉宽不认频率50Hz 下 2.5ms 和 20ms 脉宽对应占空比差异很大逐飞库用 float 占空比传参计算时要先换算1.5ms 除以 20ms 等于 0.075。不同品牌舵机的中值脉宽略有差异第一次上电不要猛打大角度先把中值固定到 0.075再手动调整舵机臂让车轮自然居中。3.3 用 1ms 定时器把控制节奏固定下来控制周期不靠编译器优化靠定时器。下面是一段典型的 10ms 控制节拍volatile uint32_t tick_1ms 0; volatile float sensor_L 0.0f, sensor_R 0.0f; void timer1_callback(void) { tick_1ms; // 每 1ms 加一 if (tick_1ms % 10 0) // 每 10ms 执行一次控制 { sensor_L read_sensor(ADC_1_CH8); sensor_R read_sensor(ADC_1_CH9); control_step(sensor_L, sensor_R); } }用累加取模代替在中断里重新初始化定时器是为了避免回调重入造成的时钟漂移。volatile保证主循环读取tick_1ms时拿到的是最新值。主循环需要用到sensor_L时先把它拷贝到局部变量再运算否则中断可能恰好在赋值中间改了它造成数据撕裂。这个工程量小但中断节拍和控制周期是两件事别混在一起。注意中断回调里不适合跑完整的均值滤波和浮点 PID采样函数里那 200us 延时放在中断里会占掉 20% CPU后续最好把滤波挪到control_step()中。4. 从两路电感到转向输出差比和、PD 与差分油门4.1 差比和为什么是电磁寻迹的默认起点电磁车两侧水平电感对称放置时车在赛道正中左右电感值接近相等车身偏向一边内侧电感感应到的磁场更强。直接做减法L - R也能得到误差但电感值受发射电流波动、赛道高度、环境金属物影响同一个位置换一天可能变化 30%。差比和把公共增益约掉error (L - R) / (L R 0.001f)分母加0.001f是为了防止两侧同时为 0 时除零。这个误差天然被限制在 -1 到 1 之间和电感放大倍数无关。对应的代码float eps 0.001f; float sum left right; // 差比和误差归一化到 [-1, 1] float error (left - right) / (sum eps);error 0表示车头偏左还是偏右取决于电感安装方向和差比和的减法顺序。我第一次用这个公式时左右电感接反了调了三小时 PID后来发现把减法顺序换过来车就直了。所以接线后第一件事是拿一块铁片靠近单侧电感确认error的符号方向再写控制逻辑。4.2 舵机 PD 控制先让车头“跟上”误差舵机响应快位置式 PD 就够用。比例项负责把转向角度映射到当前位置偏差微分项负责抑制车头来回摆动#define STEER_CENTER 750 // 中值占空比单位 0.1% #define KP_STEER 180 // 比例系数 #define KD_STEER 220 // 微分系数 static float last_error 0.0f; void steer_control(float error) { float d_error error - last_error; // 当前偏差的变化趋势 last_error error; float steer STEER_CENTER KP_STEER * error KD_STEER * d_error; // 限幅防止舵机打死留出回正余量 if (steer 900) steer 900; if (steer 600) steer 600; pwm_set_duty(PWM1, PWM1_CH1, steer / 10000.0f); }这里的占空比单位是 0.1%750对应 7.5%换算成 50Hz 下的脉宽正好 1.5ms。限幅到 600 和 900即最大脉宽约 1.8ms、最小约 1.2ms这个范围既能过急弯又不会让舵机堵转。调参时参考这张表现象调整方向直道持续蛇行减小KP_STEER或加大KD_STEER入弯推头、切内线加大KP_STEER出弯回正缓慢适当加KD_STEERKD_STEER不是越大越好过大会把噪声放大表现为高速直道上的高频抖动。4.3 电机差分油门没有编码器也能差速这套代码没有速度闭环电机使用开环油门配合差速修正。后轮双路 PWM转向误差大的时候内侧减速、外侧加速让车尾帮着车头“拧”过去#define BASE_DUTY 0.32f // 直道基础油门 #define STEER_DIFF_GAIN 0.30f // 转向差速修正系数 // error 来自差比和范围 [-1, 1] float diff STEER_DIFF_GAIN * error; pwm_set_duty(PWM2, PWM2_CH1, BASE_DUTY diff); pwm_set_duty(PWM2, PWM2_CH2, BASE_DUTY - diff);BASE_DUTY决定直道速度STEER_DIFF_GAIN决定弯道差速强度。先固定BASE_DUTY在 0.3 附近弯道甩尾就降基础油门推头就加差速系数。这个方案和舵机 PD 共享同一个error整个控制链路很干净。学会拆之前的执行链定时器中断触发 → 读取两路 ADC → 计算差比和 → 舵机 PD 输出 → 电机差分输出 → 回到主循环继续做串口日志。前后台代码的调试顺序就沿着这条链往下走每一步的输出都可以用串口打印验证。5. 最后一公里波形验证与两个标定技巧5.1 把 L、R、error 送到串口画波形不接屏幕也能看状态。逐飞库的串口驱动配合重定向fputc可以让printf直接走串口输出char uart_putchar(char ch) { uart_write_char(UART_1, ch); return ch; } void debug_report(void) { // 用 tick 差值判断避免累加归零问题 static uint32_t last 0; if (tick_1ms - last 50) return; last tick_1ms; printf(L:%.2f R:%.2f E:%.2f\n, sensor_L, sensor_R, error_sum); }tick_1ms - last 50表示 50ms 打印一次避免串口阻塞影响主循环。拿到波形后先看静态车放赛道正中L 和 R 是否接近再拿手推车过弯看 error 是否平滑变化。波形只跳不滑优先查采样延时和传感器虚焊不要急着动 PID。5.2 两个容易忽略的标定技巧第一个是舵机中值的校准。代码里的STEER_CENTER只代表“代码认为的中值”不代表机械中值。把车架空直接输出STEER_CENTER然后松舵机臂螺丝手动把车轮转到正前方再锁紧。这个步骤能消除大部分直道偏行问题。第二个是差比和的基线校准。环境变化后即使车放正中间左右电感也可能偏差很大。我一般会在control_step()入口做一次自动基线扣除让动态误差只反映相对变化不受静态偏移影响。运行时用(left - left_baseline) - (right - right_baseline)替换原始差比和分子分母保持不变。换场地时只需要重新采一次基线值而不是重新调三个 PID 参数。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门