ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32电磁寻迹与电磁炮二合一控制实战解析

STM32电磁寻迹与电磁炮二合一控制实战解析 简介这份STM32电磁炮与循迹小车工程源码适合电子工程、嵌入式开发及智能车竞赛方向的读者参考。项目以STM32微控制器为核心将寻迹小车的传感器采集、路径识别、电机PID调速与电磁炮的线圈电流精确控制整合在同一套工程中覆盖GPIO、定时器、ADC、PWM、DAC、外部中断、串口与多任务调度等关键点。压缩包共244个文件以C语言源码和头文件为主包含Keil工程配置、编译中间文件、hex烧录文件及调试辅助的map/lst/htm记录整体约8.19MB目录规整便于按模块对照学习。目前已有1573人下载学习。通过源码可重点研习PWM/DAC调流实现电磁线圈发射控制、PID闭环循迹算法、中断服务函数设计、过流与过热保护逻辑以及完整的嵌入式工程组织方式无论备赛智能车还是入门STM32实战都是可直接参考的完整样例。1. STM32电磁寻迹与电磁炮同一个单片机上的两套控制逻辑这个标题把两个经常出现在大学生电子设计竞赛和课程设计里的东西放到了一起电磁循迹小车和电磁炮。很多人第一反应是“这两个项目各做各的拼在一起有什么意义”实际恰恰相反。电磁循迹小车做的是连续控制传感器以几十微秒的周期读电感电压实时调整转向和速度电磁炮做的是离散控制按键触发之后升压、充电、等待电压达标、放电整个过程是状态机。把两套逻辑放在同一个STM32上跑真正要解决的其实是任务调度和资源复用循迹小车的那套差比和算法并不会更长真正决定车跑得稳不稳的是信号链路从电感到ADC再到PWM的整体延迟。这篇写的是这种组合项目的完整落地路径电磁寻迹的传感器怎么选、电感怎么摆、差比和怎么调到电机用什么方式驱动再到电磁炮的升压和触发电路怎么用单片机接最后落到一个快速验证传感器状态的调试技巧。适合手里已经有STM32最小系统板、想自己做一辆电磁循迹车然后把电磁炮作为第二执行负载的开发者。整个过程不依赖某个特定的成品源码头文件直接面向寄存器和标准外设库来说明放到HAL库也一样用。2. 电磁寻迹的传感链路电感布局、谐振频率和检测方案2.1 工字电感与并联电容为什么电磁循迹要用LC谐振电磁循迹车跑的路面下埋的是一条通有20kHz左右交变电流的导线导线周围会产生交变磁场。车上检测这个磁场的方式不是直接用霍尔元件而是用工字电感。工字电感在交变磁场中会产生感应电动势电动势的大小和电感与导线的相对位置有关这就是最原始的循迹信号来源。单个电感直连ADC的话感应的信号只有毫伏级别而且环境噪声会直接叠加进去非常难处理。常见做法是给工字电感并联一个谐振电容让电感和电容组成LC并联谐振回路谐振频率对准发射导线的载波频率。在谐振点上回路阻抗最大感应电压会被抬升到ADC可以稳定采样的水平。这个选型是整个电磁寻迹项目里最容易被忽略但影响最大的环节。电感量和电容量的搭配决定了谐振频率。公式是 f 1 / (2π√(LC))。如果发射频率是20kHz电感选10mH算出谐振电容的经验范围是6.8nF到10nF。实际装车之后要微调因为工字电感的实际电感量会受磁芯、绕线分布电容和周围金属件影响比标称值偏大或偏小都正常。我的习惯是先用LCR表实测每个电感的实际电感量再反推电容值这样谐振点更准。没有LCR表就用示波器看信号幅度把电容从小到大换信号幅度最大的那组就是接近谐振的组合。3D打印的外壳里如果嵌了金属螺母位置不要挨着工字电感涡流会让感应信号畸变。2.2 比较器阈值方案与ADC幅值方案的选择传感器的输出有两种送进单片机的方式。第一种是电感信号经过运放整流滤波后接到比较器上比较器输出高低电平单片机读数字输入。这种方案的优点是主控完全不参与信号解算响应极快程序里只有一个GPIO读操作。缺点是丢失了信号强度信息只能判断“有没有越过导线”离导线的距离、导线在哪一侧都判断不出来。窄赛道直道可以跑但S弯和十字交叉基本没法处理。第二种是电感信号经过精密整流和高频滤波后变成直流电压直接接STM32的ADC引脚程序里把几路电压数字化。这个方案能保留信号的强度信息用差比和算法算出导线相对于车体中线的偏差值。缺点是对ADC采样稳定性和参考电压有要求如果直接用VDD做参考电池电压波动会直接反映在采样值上。做电磁循迹车推荐用ADC方案哪怕是入门版本也要用ADC。比较器方案只适合那种“只要车不冲出去就行”的最低要求演示。ADC方案里的三个信号在stm32上对应三路ADC通道采样周期要和DMA循环配合起来不要用阻塞式逐通道读取否则电机PWM更新会被传感器采样卡住。2.3 电感排布与差比和的物理意义三电感是电磁循迹的最低配置。左右两个电感隔着车体中心线对称安装中间电感装在正前方。驱动导线在当前时刻的正下方时中间电感感应电压最高左右两个电压不对称这种不对称性就是转向的依据。差比和公式写为int16_t error (100 * (left_adc - right_adc)) / (left_adc right_adc middle_adc);注意这里用的是左减右不是右减左。方向取决于你的电机安装朝向和转向控制环路的正负反馈极性。代码里这个error是后续PID控制器的输入正值表示车身偏左还是偏右由你的电感安装方向和转向定义决定第一次跑之前需要实际验证一下极性。这个公式分母加上中间电感的值好处是两个场景一是左右电感同时进入强磁场分子变大但分母也变大输出不会爆炸二是某个电感接近饱和拐点、数值变化剧烈时差比和依然保持相对平滑。分母里的middle_adc不建议去掉没有它的话车正对导线时左右电压都很高差比和反而会不稳定。电感的横向间距三轮车常见的是10cm到14cm。间距太小差比和输出范围窄直线段的灵敏度不够间距太大过弯时外侧电感很容易丢信号进入饱和区输出就不再线性。参数表格如下参数推荐值说明发射频率20kHz也可做28kHz和LC谐振频率保持一致工字电感10mH直径7mm兼顾灵敏度和抗饱和能力谐振电容6.8nF~10nF实测调整以信号幅度最大为准电感间距10cm~14cm三轮车架常用范围电感离地高度10mm~25mm越低信号越强但越容易被路面电磁噪声干扰3. 在STM32上把电磁寻迹跑通从ADC采集到电机PWM3.1 用ADCDMA循环采集多路电感信号先把传感器路径整清楚才能写程序。三路电感电压进入单片机之前经过整流变成直流直接连到STM32的ADC引脚比如PC0、PC1、PC2。程序里用ADC的扫描模式配合DMA循环传输refills三个通道的采样值到数组里。下面是基于标准外设库的初始化片段ADC_InitTypeDef adc_init; DMA_InitTypeDef dma_init; uint16_t adc_buf[3]; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel1); dma_init.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; dma_init.DMA_MemoryBaseAddr (uint32_t)adc_buf; dma_init.DMA_DIR DMA_DIR_PeripheralSRC; dma_init.DMA_BufferSize 3; dma_init.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma_init.DMA_MemoryInc DMA_MemoryInc_Enable; dma_init.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; dma_init.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; dma_init.DMA_Mode DMA_Mode_Circular; dma_init.DMA_Priority DMA_Priority_High; DMA_Init(DMA1_Channel1, dma_init); DMA_Cmd(DMA1_Channel1, ENABLE); adc_init.ADC_Mode ADC_Mode_Independent; adc_init.ADC_ScanConvMode ENABLE; adc_init.ADC_ContinuousConvMode ENABLE; adc_init.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; adc_init.ADC_DataAlign ADC_DataAlign_Right; adc_init.ADC_NbrOfChannel 3; ADC_Init(ADC1, adc_init); ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 2, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_12, 3, ADC_SampleTime_239Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE);这段代码做的事情是让ADC1自动循环扫描三个通道每次转换完结果由DMA搬运到adc_buf数组环形传输所以主循环拿到的始终是最新数据。采样时间用较长的239.5周期在20kHz的电磁信号环境下能起到硬件低通滤波的效果。ADC_RegularChannelConfig里的三个通道要和你实际接线的引脚对应别照抄通道编号这个项目最常见的接线错误就是代码和电路图通道号错位。主循环里直接读adc_buf不用等待转换完成位。读取速度远快于20kHz的载波频率对差比和计算来说完全够用。如果要进一步提升稳定性可以对adc_buf做滑动平均取最近4次采样的均值再做差比和开销只有三次加法和一次移位。3.2 电机驱动与转向控制的最小实现循迹小车电机驱动常用TB6612或者DRV8833控制方式都是PWM调速加方向引脚。STM32上用一个定时器的两个通道输出PWM分别控制左右电机。转向不需要额外舵机差速就能转弯。转向控制用位置式PD即可积分项在电磁循迹里不是必须的因为差比和本身是一个稳定的偏差信号不会像编码器测速那样有静差问题。加积分容易造成弯道处转向过量。PD参数表按这个范围作为起点调试参数起步值调整方向Kp3.0~5.0直道抖动大就减小Kd8.0~15.0弯道甩尾就增大基础PWM25%~35%必须能让车克服静止摩擦起步核心控制代码放在定时器中断里中断频率设1kHz每毫秒执行一次保证控制周期稳定不受主循环里电磁炮状态机的影响void TIM2_IRQHandler(void) { static int16_t last_error 0; int16_t error, p, d, turn, motor_l, motor_r; if (TIM_GetITStatus(TIM2, TIM_IT_Update) RESET) return; error get_track_error(); p KP * error; d KD * (error - last_error); turn p d; last_error error; motor_l BASE_SPEED - turn; motor_r BASE_SPEED turn; if (motor_l PWM_MAX) motor_l PWM_MAX; if (motor_r PWM_MAX) motor_r PWM_MAX; if (motor_l 0) motor_l 0; if (motor_r 0) motor_r 0; set_motor_duty(motor_l, motor_r); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); }get_track_error()内部执行差比和公式motor_l和motor_r是左右轮的PWM占空比。BASE_SPEED减去turn是内侧轮减速加上turn是外侧轮加速实现差速转向。这套逻辑如果发现转向反了把motor_l和motor_r的表达式交换或者在差比和公式里把left减right换成right减left。控制中断的优先级要有讲究。让此时基中断优先级高于串口和ADC事件低于任何一个不可屏蔽的系统异常。同时电磁炮的触发相关操作如果涉及GPIO翻转不要放在比这个更高的中断优先级里做长延时只需要翻转引脚具体时序交给外部硬件去执行。3.3 串口打点与标定跑起来之前先做一次标定。把车悬空手机横着缓慢扫过电感下方模拟从左侧到右侧的移动同时用串口把left_adc、right_adc、middle_adc和差比和error打出来。正常现象是电感对称时error接近0扫到右边时error平稳变化到正数或负数。这一步能发现三个问题某一路ADC读数恒为4095接线断路或通道配置错、差比和突变跳变谐振电容偏了导致信号进入非线性区、数值在某个位置停留不变化运放输出饱和需要调低放大倍数或增大电感离地高度。标定通过再上赛道不然上赛道之后拿激光笔和示波器去查信号链路非常浪费时间。void send_debug(void) { printf(L:%d R:%d M:%d Err:%d\r\n, adc_buf[0], adc_buf[1], adc_buf[2], get_track_error()); }dcb上跑起来之后串口打印会占用不少时间调试完毕之后把send_debug关掉只保留get_track_error。不关也行但波特率115200持续打印会有约3%的周期占用对1kHz控制中断影响不大可读性和稳定性之间做个取舍。4. 电磁炮单片机控制升压、电容充放电与触发时序4.1 电磁炮的功率链路和单片机接口电磁炮在课程设计语境下通常是电容储能放电型。用升压电路把电池电压升到数十伏甚至上百伏给电容充电储到设定电压后通过可控硅或者IGBT瞬间放电电流流过线圈产生脉冲磁场把铁磁性弹丸加速推出去。STM32在这一环里做的不是功率变换本身而是状态管理。它控制三点升压电路的开关PWM或者使能脚、电容电压的采样电阻分压接ADC、放电管的触发GPIO输出脉冲给驱动电路。电路基本结构是电池 → 升压模块 → 限流电阻 → 储能电容电容两端并接分压电阻网络 → ADC通道电容正极 → 放电线圈 → 可控硅阳极可控硅阴极接地控制极接单片机的触发驱动。RCC配置里要额外把DMA1或DMA2的时钟打开ADC采样通道和循迹传感器通道分开配置。安全设计这里必须注意放电管关断之后储能电容可能还留存电荷。需要并联一个泄放电阻典型取100kΩ到220kΩ功率选1W以上。这个电阻让电容在上电后10秒内放到安全电压以下防止误触线圈造成伤害。4.2 电容电压采集与充放电状态机电容电压不能用单片机直接测高压会把ADC引脚击穿。用电阻分压比如两个电阻按1:100分压电压降到原来的百分之一。选择电阻时要注意分压电阻本身的耐压和功耗至少两个电阻串联再分压不能只靠一个分压。电压ADC采样和循迹电感的ADC采样要分开看待因为它们的时间尺度完全不同。循迹用DMA循环搬运几乎每个控制周期都有新数据。电容电压只需每20ms~50ms采样一次用定时器触发单次转换即可。状态机代码typedef enum { GUN_IDLE, GUN_CHARGING, GUN_READY, GUN_FIRE, GUN_DUMP } gun_state_t; gun_state_t gun_state GUN_IDLE; void gun_update(void) { uint16_t v_adc gun_get_voltage_adc(); uint32_t v_mv (uint32_t)v_adc * 3300 / 4096 * 100; /* 乘分压比 */ switch (gun_state) { case GUN_IDLE: if (fire_button_pressed()) { gun_state GUN_CHARGING; boost_enable(1); } break; case GUN_CHARGING: if (v_mv TARGET_VOLTAGE_MV) { boost_enable(0); gun_state GUN_READY; } if (charge_timeout()) { gun_state GUN_DUMP; } break; case GUN_READY: if (fire_button_released()) { gun_state GUN_FIRE; trigger_fire(); gun_state GUN_IDLE; } break; case GUN_FIRE: /* 触发后立即回到IDLE */ break; default: boost_enable(0); gun_state GUN_IDLE; break; } }注意这个状态机里每个状态转移都有明确条件没有用延时阻塞。充电过程中boost_enable是拉高一个GPIO还是打开PWM输出取决于升压模块的使能接口类型。带软启动的升压模块通常给一个高电平使能即可。触发后电容电压会瞬间跌到很低所以触发完立刻回到IDLE不要停在READY判断电压。TARGET_VOLTAGE_MV根据电容耐压和线圈参数来确定不要超过电容标称耐压的80%。同时要在充电路径上加限流否则电容短路或者误接时升压模块会被瞬间拉到极限甚至损坏。4.3 与循迹任务共享STM32的实时性协调两个任务在同一颗主控上跑最容易出问题的不是计算量而是两个不同的实时性要求互相挤占。电磁循迹的控制中断固定1kHz不能因为主循环在做充电电压判断就被拖延。解决办法是分层把循迹控制放定时器中断里把电磁炮状态机放主循环里。因为电磁炮状态机本身不要求微秒级响应几十毫秒的延迟无关紧要。而电磁炮的触发输出是整个项目里唯一要求硬实时的地方。触发信号由状态机置位但实际引脚翻转放在定时器中断里执行一次确保可控硅的触发时刻相对于电机PWM周期是确定的。以下代码放在和循迹控制相同的中断里每毫秒检查一次标志位uint8_t fire_request 0; void TIM2_IRQHandler(void) { if (fire_request) { fire_request 0; GPIO_SetBits(GUN_TRIGGER_GPIO, GUN_TRIGGER_PIN); delay_us_pin(20); /* 触发脉冲宽度 */ GPIO_ResetBits(GUN_TRIGGER_GPIO, GUN_TRIGGER_PIN); } /* 循迹PD控制的代码也在这里 */ }触发脉冲宽度通常10到20微秒就足够太宽会使可控硅控制极功耗增大。这段里面用delay_us_pin而不是普通Delay因为标准库自带的微秒延时函数是靠死循环延时的问题在于编译器开O2优化之后延时时间会缩短需要校准。实际工程中更稳妥的做法是把这个触发操作外移到一个专用的单次定时器里设定定时器延时到20微秒后产生更新事件在中断里复位引脚。忌用毫秒级的软件延时那会把循迹控制周期彻底打乱。电容充电期间也并没有占用任何单片机中断资源升压模块完全依靠硬件闭环工作MCU只在需要判断“是否达到目标电压”时读一次ADC。所以整车的转向性能不会因为带了电磁炮模块而明显下降这是这种主控方案的最大优势。5. 调试技巧用串口看差比和的分段特征快速定位丢线问题5.1 差比和输出幅值的三个区间把车放在赛道上上电之后串口打印差比和数值观察它在不同位置的输出。正常信号应该呈现三个分段车在导线正右方时输出是较大的饱和值正上方时输出稳定在0附近正左方时输出是反向饱和值。这三个分段的中间过渡区越陡峭说明传感器对位置的灵敏度越高但过渡区一般不希望过窄否则PID一碰就发散。过渡区的数据特征如果出现断崖式跳跃比如从50直接跳到-50中间没有连续值说明两侧电感的感应范围重叠不足两个电感之间的间距过大或者谐振频率没对准左右电感的灵敏度不一致。把采样到的左路和右路分别打印出来两侧同时移动时数值是否同步升降立刻能看出差异。这两行串口数据比任何理论分析都有用。5.2 丢线判定和信号饱和的边界差比和的分母降到很低说明车已经离线远三路信号都弱了。此时PID会把转向输出打到极限常见行为是车身猛打方向冲出去。解决办法不是调Kp而是给丢线状态加一个独立逻辑。int16_t get_track_error(void) { uint32_t sum adc_buf[0] adc_buf[1] adc_buf[2]; if (sum LOST_THRESHOLD) { return (last_error 0) ? LOST_TURN_RIGHT : LOST_TURN_LEFT; } return update_last_error(calculate_error()); }丢线时让转向保持上一次的有效方向而不是归零这是电磁寻迹车上最实用的一招。常见错误是丢线时直接返回0车会沿直线冲出赛道。LOST_THRESHOLD需要和运放的静态输出电压配合调推车横扫赛道时观察串口数据确认最小值留出20%的裕量。最后一层的验证方法是把示波器的两个探头分别接左右电感的整流输出手动推车过弯观察波形峰值变化是否平滑。如果某一侧波形到弯道顶点出现削顶说明运放饱和需要减小放大倍数。这个检查和调PID参数同样关键削顶会让ADC读丢信息再好的控制参数也救不回来。本文还有配套的精品资源点击获取
返回列表