1. 电磁寻迹小车系统概述
电磁寻迹小车是一种通过感应赛道预设电磁信号实现自主导航的智能移动平台,在大学生智能车竞赛和工业AGV领域有着广泛应用。基于STM32C8T6主控的方案因其性价比高、开发资源丰富而成为入门首选。
我去年带队参加智能车竞赛时,就采用了这套方案。相比摄像头方案,电磁寻迹对环境光线不敏感,稳定性更好。但要想让小车在赛道上流畅运行,需要处理好三个关键环节:传感器信号采集精度、电机控制响应速度以及两者之间的控制算法。
2. 硬件系统设计详解
2.1 核心器件选型分析
主控芯片选择STM32C8T6的考量:
- 72MHz主频的Cortex-M3内核,足够处理传感器数据并输出控制信号
- 内置3个ADC模块,支持多通道模拟信号采集
- 多达4个定时器可用于PWM生成
- TSSOP20封装体积小巧,适合小车紧凑布局
- 市场货源充足,价格约8-12元/片
实际使用中发现,C8T6的ADC采样速率最高1MHz,对于50Hz的电磁信号完全够用。但要注意其ADC参考电压需稳定,建议单独用LDO供电。
电磁传感器方案对比:
- 成品电感模块(约15元/个)
- 优点:即插即用,自带放大电路
- 缺点:灵敏度固定,难以调参
- 自制LC谐振电路
- 优点:成本低(约2元/个),可自由设计谐振频率
- 缺点:需要手工绕制电感,一致性较差
我们最终选择了自制方案,使用10mH工字电感和104瓷片电容组成LC回路,谐振频率约20kHz,正好匹配赛道信号。
2.2 原理图设计要点
传感器信号调理电路:
circuit复制[电磁信号] → LC谐振 → 电压跟随器 → 精密整流 → 低通滤波 → [ADC输入]
- 电压跟随器采用LM358,输入阻抗>1MΩ
- 整流电路使用1N4148二极管配合运放构成精密整流
- 二阶低通滤波器截止频率设为100Hz
电机驱动设计:
- 选用L298N双H桥驱动芯片
- 自举电容选用100nF/50V陶瓷电容
- 续流二极管用1N5819肖特基管
- 电机电源与逻辑电源完全隔离
2.3 PCB布局实战经验
层叠结构:
- Top Layer:信号走线
- GND Plane:完整地平面
- Power Plane:电源分割
- Bottom Layer:大电流走线
关键布局技巧:
- 将STM32放置在板子几何中心
- 传感器接口集中在板子前部
- 电机驱动芯片靠近板子后侧
- 每个IC的去耦电容尽量靠近电源引脚
布线注意事项:
- 模拟信号线宽6mil,与数字线间距3W原则
- 电机电源线宽≥40mil,过孔数量≤2个
- ADC参考电压走线包地处理
- 晶振下方禁止走线
3. 软件系统实现
3.1 底层驱动开发
ADC配置优化:
c复制void ADCx_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
// 时钟使能省略...
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 启用扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 3;
ADC_Init(ADC1, &ADC_InitStructure);
// 设置采样时间
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5);
// 其他通道配置...
ADC_DMACmd(ADC1, ENABLE); // 启用DMA
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 启动连续转换
}
PWM生成配置:
c复制void TIMx_PWM_Init(u16 arr, u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 时钟使能省略...
TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
3.2 控制算法实现
基础差值算法:
c复制#define BASE_SPEED 500
#define MAX_DIFF 200
void Control_Motor(int16_t left_val, int16_t right_val)
{
int16_t diff = left_val - right_val;
diff = constrain(diff, -MAX_DIFF, MAX_DIFF);
int16_t left_speed = BASE_SPEED - diff;
int16_t right_speed = BASE_SPEED + diff;
TIM_SetCompare1(TIM3, left_speed);
TIM_SetCompare2(TIM3, right_speed);
}
改进的PID算法:
c复制typedef struct {
float Kp, Ki, Kd;
float integral;
float last_error;
} PID_Controller;
void PID_Init(PID_Controller* pid, float Kp, float Ki, float Kd)
{
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->integral = 0;
pid->last_error = 0;
}
float PID_Update(PID_Controller* pid, float error, float dt)
{
pid->integral += error * dt;
float derivative = (error - pid->last_error) / dt;
pid->last_error = error;
return pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
}
3.3 系统主程序框架
c复制int main(void)
{
SystemInit();
ADCx_Init();
TIMx_PWM_Init(999, 71);
PID_Controller pid;
PID_Init(&pid, 0.8f, 0.02f, 0.5f);
while(1) {
uint16_t sensor_values[3];
Get_Sensor_Values(sensor_values); // 通过DMA获取
float position = Calculate_Position(sensor_values);
float error = position - CENTER_POSITION;
float adjust = PID_Update(&pid, error, 0.01f);
Set_Motor_Speeds(BASE_SPEED, adjust);
Delay_ms(10);
}
}
4. 调试与优化实战
4.1 传感器校准方法
-
静态校准:
- 将小车置于赛道中心位置
- 记录各传感器原始ADC值
- 计算平均值作为基准偏移量
-
动态补偿:
c复制// 在读取传感器值时进行实时补偿
float compensated_value = (raw_value - offset) * gain;
4.2 常见问题排查
问题1:小车左右摆动过大
- 检查PID参数,适当减小Kp
- 增加机械阻尼,如调整轮胎摩擦力
- 降低基速BASE_SPEED
问题2:直线行驶时偏向一侧
- 检查电机一致性,用示波器对比PWM波形
- 校准传感器基准值
- 检查电池电压是否平衡
问题3:响应延迟明显
- 提高控制频率到100Hz以上
- 检查ADC采样时间设置
- 优化算法计算量
4.3 性能优化技巧
-
ADC采样优化:
- 使用DMA传输减少CPU开销
- 开启ADC过采样提高分辨率
- 定期自动校准基准
-
控制算法优化:
- 加入死区控制防止电机抖动
- 实现速度前馈补偿
- 添加转向平滑滤波
-
电源管理:
- 单独给模拟部分供电
- 添加大容量储能电容
- 监测电池电压实现低压保护
5. 进阶开发方向
5.1 赛道记忆功能
c复制#define MAX_PATH_POINTS 100
typedef struct {
float position;
float speed;
uint32_t timestamp;
} PathPoint;
PathPoint path[MAX_PATH_POINTS];
uint8_t path_index = 0;
void Record_Path(float pos, float spd)
{
if(path_index < MAX_PATH_POINTS) {
path[path_index].position = pos;
path[path_index].speed = spd;
path[path_index].timestamp = Get_System_Tick();
path_index++;
}
}
5.2 无线调试接口
c复制void USARTx_Init(uint32_t baudrate)
{
// USART初始化代码...
// 启用接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_EnableIRQ(USART1_IRQn);
}
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE)) {
uint8_t data = USART_ReceiveData(USART1);
Process_Command(data); // 处理无线指令
}
}
5.3 多传感器融合
c复制typedef enum {
SENSOR_EM = 0,
SENSOR_IMU,
SENSOR_ENCODER
} SensorType;
float Get_Fused_Position(void)
{
float em_pos = Get_EM_Position();
float imu_angle = Get_IMU_Angle();
float encoder_dist = Get_Encoder_Distance();
// 卡尔曼滤波或加权融合
return 0.6f*em_pos + 0.3f*imu_angle + 0.1f*encoder_dist;
}
在完成基础功能后,建议先用示波器观察关键信号波形,特别是PWM输出和ADC采样值。我们团队当时发现电机启动时会对ADC产生干扰,后来通过在电源端增加LC滤波解决了这个问题。另外,PID参数整定需要耐心,可以先从纯P控制开始,逐步加入I和D参数。