2024年电赛h题扩展
)

简介:电赛微缩组小车,TB6612电机驱动,软件I2C读MPU6050,SysTick毫秒时基,蓝牙串口下发任务指令。全文按上电执行顺序逐模块拆解,分层架构,代码可折叠,附带参数调优实战经验。

整体三层架构总览

┌─────────────────────────────────────────────┐
│ 决策层  路线表T1~T8 + RunTask                │  "走哪条路、先转几度"
│         只做编排, 不碰硬件                    │
├─────────────────────────────────────────────┤
│ 动作层  DoTurn      陀螺角度闭环(原地转)      │  "一种运动怎么做"
│         DoGap       陀螺航向保持(走直+找线)   │   各有各的传感器
│         DoLine      循迹PD闭环(巡弧+找线)     │   和退出条件
├─────────────────────────────────────────────┤
│ 驱动层  Motor(出力) Tracking(看) MPU(感觉)    │  "直接摸硬件"
│         Beep(说)    Millis(钟)               │
└─────────────────────────────────────────────┘

层间通信:全局标志(中断→主循环) / 返回值(动作→调度) / p·yaw(传感→动作)


第1块:main() 上电初始化【开机自检清单】

点击展开代码

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    Delay_Init();
    Motor_Init();
    Tracking_Init();
    Beep_Init();
    UART1_Init();
    printf("\r\n=== Smart Car Ready ===\r\n");
    MPU_Init();
    Motor_Stop();
    printf("GZ raw:%d (should jitter within ±80)\r\n", MPU_GZ());

代码功能讲解
一行一件事,上电按顺序执行,属于开机自检清单:

  1. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2):配置中断分组,2位抢占优先级+2位响应优先级。全局只配置一次,后续所有中断都遵循这套排队规则。
  2. Delay_Init():初始化SysTick,搭建1ms系统时基,必须最先初始化,后续所有延时函数都依赖它。
  3. Motor_Init():电机PWM外设初始化,初始占空比置0,上电电机不动作。
  4. Tracking_Init():8路红外循迹引脚配置为上拉输入,准备读取黑线信号。
  5. Beep_Init():蜂鸣器IO拉高,低电平触发蜂鸣器,上电先静音,防止上电瞬间误响。
  6. UART1_Init():蓝牙串口USART1,波特率9600,开启接收中断。
  7. printf打印就绪提示。
  8. MPU_Init():MPU6050初始化+0.3s零偏校准,校准阶段小车必须保持静止。
  9. Motor_Stop():电机停止。
  10. 打印陀螺原始角速度自检值,读数在±80内抖动代表传感器工作正常。

初始化完成后进入while(1)主循环。


第2块:Delay.c —— 全系统时间地基

点击展开代码

void Delay_Init(void)
{
    SysTick->LOAD = 72000 - 1;     /* 72MHz/72000 = 1kHz → 每1ms中断一次 */
    SysTick->VAL  = 0;
    SysTick->CTRL = 0x00000007;    /* 时钟源HCLK + 开中断 + 使能 */
}
void SysTick_Handler(void)   /* 每1ms自动进入中断 */
{
    g_ms++;                  /* 全局毫秒计数器自增 */
}
uint32_t Millis(void)  { return g_ms; }
void Delay_ms(uint32_t t)
{
    uint32_t s = g_ms;
    while (g_ms - s < t);    /* 阻塞延时,计数器回绕也安全 */
}

功能讲解
SysTick是STM32内置系统定时器,本项目配置为1ms触发一次中断,中断内部对全局变量g_ms自增,相当于给智能车装上一块毫秒级电子表。

  • Millis():读取当前系统毫秒时间,看电子表;
  • Delay_ms():阻塞延时,盯着时间差值等待,死等指定毫秒。

全系统依赖场景

if (Millis() - f0 > FOUND_MS)      /* DoGap: 见线持续50ms才算到达 */
if (Millis() - lost0 > LOST_MS)    /* DoLine: 丢线135ms判定到达弧线终点 */
if ((int32_t)(Millis() - t0) > GAP_TIMEOUT_MS)  /* 空白区超时兜底 */
if (Millis() - prt > 150)          /* 打印限流,防止串口刷屏拖慢控制 */

第3块:usart.c —— 蓝牙命令入口

点击展开代码

int fputc(int ch, FILE *f)
{
    USART_SendData(USART1, (uint8_t)ch);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    return ch;
}

void USART1_IRQHandler(void)   /* 蓝牙每收到1字节,硬件自动进入中断 */
{
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        uint8_t c = (uint8_t)USART_ReceiveData(USART1);
        if (c >= '1' && c <= '8') { g_task = c - '0'; g_newtask = 1; }
        else if (c == 'G' || c == 'g') g_start = 1;
        else if (c == 'S' || c == 's') g_stop  = 1;
        else if (c == 'T' || c == 't') g_test ^= 1;
        else if (c == 'C' || c == 'c') g_calib = 1;
    }
}

功能讲解

  1. fputc:重定向printf。C标准printf默认输出到电脑屏幕,这里重定向到USART1串口,所有printf输出会通过蓝牙发送到手机APP,作为小车的“输出嘴”。
  2. 串口接收中断:手机蓝牙下发字符,硬件触发中断。>

核心原则:中断收信,主循环办事。中断内只置全局标志位,不执行复杂动作,保证中断快进快出(几十微秒),实际业务逻辑交给主循环处理。

  • '1'~'8':选择对应任务,g_newtask=1通知主循环;
  • G/g:g_start=1,发车指令;
  • S/s:g_stop=1,急停标志,任务函数循环内持续检测;
  • T/t:调试打印开关翻转;
  • C/c:触发MPU重新校准。

第4块:主循环 while(1) —— 事件分拣器

点击展开代码

while (1)
{
    Yaw_Update();                    /* ★每圈必做: 陀螺角度积分 */
    if (g_newtask) {                 /* 门铃1: 选中新任务 */
        uint8_t k;  g_newtask = 0;
        for (k = 0; k < g_task; k++) { Beep(90); Delay_ms(120); }
    }                                /* 任务3就滴3声,确认任务选择 */
    if (g_calib) {                   /* 门铃2: 请求重校准陀螺 */
        g_calib = 0;
        printf("Recalib:%s\r\n", MPU_Calib(300) ? "OK" : "FAIL");
    }
    if (g_test) {                    /* 门铃3: 调试打印开关 */
        static uint32_t p = 0;
        if (Millis() - p > 100) {
            p = Millis();
            printf("Y:%d GZ:%d W:%d\r\n", (int)Yaw(), MPU_GZ(), (int)MPU_Rate());
        }
    }
    if (g_start && g_task) {         /* 门铃4: 正式发车执行任务 */
        g_start = 0;
        RunTask(g_task);
        g_stop = 0;                  /* 任务跑完清除急停标志,等待下一次发车 */
    }
}

功能讲解
无限循环事件分拣台,不断轮询各类事件标志,有事件就处理。
重点:Yaw_Update()放在循环第一行,每一轮循环都执行陀螺角度积分,即使在任务间隙,航向角度也持续更新,保证陀螺角度数据准确。


第5块:路线表 —— 任务的“菜谱”

点击展开代码

#define SEG_GAP  0        /* 穿空白区段 */
#define SEG_LINE 1        /* 巡黑线弧段 */
typedef struct { uint8_t type; float turn; } Seg;
/*                      段类型      进段前先转的度数(+右-左) */
/* 任务3: A→B→右弧→C→D→左弧→A, 车头朝正东摆放 */
static const Seg T3[] = { {SEG_GAP,0},{SEG_LINE,0},{SEG_GAP,0},{SEG_LINE,0} };
/* 任务4: B→斜穿D→左弧→A→直行B */
static const Seg T4[] = { {SEG_GAP,0},{SEG_LINE,+DIAG},{SEG_GAP,0} };

功能讲解
把不同赛道任务,抽象成动作序列表。每一段包含两个信息:区段类型(空白穿区/弧线巡线)、进入本段之前需要原地旋转的角度。
角度规则:正数向右旋转,负数向左旋转。

例:任务4斜线与弧线切线夹角≈38.7°,巡弧前原地右转+DIAG。
优势:修改赛道、新增任务,只修改路线表,底层动作函数无需改动。


第6块:RunTask —— 按菜谱炒菜,任务总指挥

点击展开代码

void RunTask(uint8_t t)
{
    const Seg *seg = 0;  int n = 0, lap, laps = 1, i;
    switch (t) {                       /* 查表选择任务路线 */
        case 3: seg = T3; n = 4;          break;
        case 5: seg = T5; n = 4;          break;
        case 6: seg = T6; n = 4; laps = 4; break;   /* 多圈任务 */
        ...
    }
    Beep(300);                          /* 出发提示音 */
    for (lap = 0; lap < laps; lap++)    /* 外层循环:跑几圈 */
        for (i = 0; i < n; i++) {       /* 内层循环:依次执行每一段路线 */
            if (g_stop) goto EXIT;      /* 急停标志触发,直接跳出任务 */
            DoTurn(seg[i].turn);        /* ①原地转向 */
            if (seg[i].type == SEG_GAP) {
                if (!DoGap()) goto EXIT;   /* ②空白区直行,失败直接退出 */
            } else {
                if (!DoLine()) goto EXIT;  /* ②弧线PD巡线,失败直接退出 */
            }
            Beep(120);                  /* 到达该段终点,短滴提示 */
        }
EXIT:
    Motor_Brake();  Beep(300); ...      /* 任务收尾:刹车+蜂鸣提示+打印完成信息 */
}

功能讲解
任务调度总函数,双层for循环遍历路线表。
动作原语返回值:1=本段执行成功,0=失败(超时/急停)。任意一段失败,使用goto跳转到EXIT统一收尾,保证小车任何情况都会刹车,不会失控乱跑。


第7块:DoTurn —— 陀螺闭环原地旋转

点击展开代码

static void DoTurn(float deg)
{
    float rem; uint8_t spd;
    if (deg > -0.5f && deg < 0.5f) return;   /* 旋转角度极小,直接跳过 */
    if (!MPU_OK()) {                          /* 陀螺异常,采用固定时间兜底 */
        int8_t u = (int8_t)(deg / 45.0f);     /* 45°约190ms,按比例估算时间 */
        ...Delay_ms(ms); Motor_Brake(); return;
    }
    Yaw_Zero();                               /* 当前车头角度置0,作为本次旋转基准 */
    if (deg > 0) Motor( TURN_PWM, -TURN_PWM); /* +: 左轮前进、右轮后退 → 原地右旋 */
    else         Motor(-TURN_PWM,  TURN_PWM); /* -: 反向原地左旋 */
    while (1) {
        Yaw_Update();                         /* 实时刷新当前转过角度 */
        rem = fabs(deg) - fabs(Yaw());        /* 剩余需要旋转角度 */
        if (rem <= 2.0f) break;               /* 误差小于2°,判定到位 */
        spd = (rem > 15.0f) ? TURN_PWM : 30;  /* 大角度全速,接近目标降速,防止过冲 */
        if (deg > 0) Motor( spd, -spd);
        else         Motor(-spd,  spd);
        if (g_stop) return;
    }
    Motor_Brake(); Delay_ms(60);              /* 刹车,等待60ms稳定车身 */
}

功能讲解
原地角度闭环控制。设定目标旋转角度,电机旋转,陀螺实时反馈当前角度,不断修正电机输出。

  • 每次调用Yaw_Zero()重置角度基准,上一段的角度误差不会传递到下一段;
  • 接近目标自动降速,抑制超调;
  • 误差阈值2°,到位刹车稳定60ms。

第8块:DoGap —— 航向保持,穿越空白区

点击展开代码

static uint8_t DoGap(void)
{
    Yaw_Zero();  hdg = 0;        /* 将当前车头方向锁定为基准航向 */
    /* ---- 阶段1: 盲冲350ms,冲出顶点黑线区域 ---- */
    while ((int32_t)(Millis() - t0) < ESCAPE_MS) {
        if (g_stop) return 0;
        Yaw_Update();
        err = Yaw() - hdg + GAP_BIAS;      /* 车头偏移角度 */
        fix = (int)(2.0f * err);           /* 角度误差放大得到修正量 */
        if (fix >  20) fix =  20;          /* 修正量限幅,防止大幅度猛打方向 */
        if (fix < -20) fix = -20;
        Motor(GAP_SPEED + GAP_TRIM - fix,  GAP_SPEED + fix);
    }   /*      左轮(带右偏补偿)          右轮 */

    /* ---- 阶段2: 继续保持航向 + 寻找黑线 ---- */
    while ((int32_t)(Millis() - t0) < GAP_TIMEOUT_MS) {
        /* 同上航向保持逻辑 */
        if (Tracking_Bits()) {              /* 任意探头检测到黑线 */
            if (f0 == 0) f0 = Millis();     /* 记录第一次检测到黑线的时刻 */
            if (Millis() - f0 > FOUND_MS) { /* 黑线持续存在满50ms */
                Motor_Brake();  return 1;   /* 判定到达顶点,本段成功 */
            }
        } else f0 = 0;                      /* 黑线消失,计时清零,重新检测 */
    }
    Motor_Brake(); Beep(600); return 0;     /* 超时,本段执行失败 */
}

阶段1讲解
无黑线,依靠陀螺锁定航向走直线。GAP_TRIM用来补偿左右电机出厂转速不一致,消除小车自然跑偏。
车头右偏,误差err>0,fix>0,左轮减速、右轮加速,车头修正回基准方向。

阶段2讲解
FOUND_MS连续见线50ms防抖,过滤地面污渍、噪声造成的探头误触发,只有持续检测黑线才判定到达顶点。超时则任务失败。


第9块:DoLine —— PD巡弧线(核心循迹控制)

点击展开代码

p = Tracking_Pos();        /* 获取黑线位置:中心0,左负右正,丢线返回100 */
if (p > 90.0f) {           /* ---- 岔路A: 丢失黑线 ---- */
    if (lost0 == 0) lost0 = Millis();          /* 记录丢线起始时间 */
    if (Millis() - lost0 > LOST_MS) {          /* 持续丢线超过135ms */
        Motor_Brake();  return 1;              /* 判定冲出弧线末端,本段完成 */
    }
    s = (seen >= 0) ? 1 : -1;                  /* 记住黑线最后出现的方向 */
    Motor(BASE_SPEED + 30*s, BASE_SPEED - 30*s); /* 朝丢失方向画弧继续前进搜线 */
}
else {                     /* ---- 岔路B: 正常检测到黑线,PD闭环控制 ---- */
    lost0 = 0;
    seen = p;                                  /* 保存当前黑线位置,丢线时使用 */
    st = KP * p + KD * (p - last);             /* ★PD核心公式 */
    last = p;
    if (st >  STEER_LIMIT) st =  STEER_LIMIT;  /* 舵量限幅 ±40 */
    if (st < -STEER_LIMIT) st = -STEER_LIMIT;
    Motor(BASE_SPEED + (int)st, BASE_SPEED - (int)st);
}

丢线逻辑讲解
丢线有两种可能性:临时脱离黑线,或者已经跑完弧线。利用持续丢线135ms作为弧线终点判定,不需要额外终点传感器。
丢线时,小车沿着黑线消失的方向画弧前进,而不是原地打转,提高找回黑线概率。

PD讲解

  • 比例项 KP*p:黑线偏离越远,打舵力度越大,负责把车拉回黑线;
  • 微分项 KD*(p-last):黑线位置变化速度,抑制惯性超调,防止来回S形振荡;
  • 黑线在右侧 p>0 → st>0,左轮加速、右轮减速,车头右转追线。

第10块:tracking.c —— 巡迹的眼睛,8路红外探头

点击展开代码

static const uint16_t SEN_PIN[8] = { GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_10,
    GPIO_Pin_11, GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_4, GPIO_Pin_5 };
static GPIO_TypeDef* const SEN_PORT[8] = { GPIOB,...,GPIOA,GPIOA };

uint8_t Tracking_Bits(void)
{
    uint8_t b = 0, i;
    for (i = 0; i < 8; i++) {
        if (GPIO_ReadInputDataBit(SEN_PORT[i], SEN_PIN[i]) == LINE_LEVEL)
            b |= (1 << i);          /* 压线探头对应的bit置1 */
    }
    return b;   /* 返回1字节位图,例:0x18=00011000,中间两个探头压线,车居中 */
}

float Tracking_Pos(void)
{
    uint8_t b = Tracking_Bits();
    float sum = 0;
    uint8_t cnt = 0;
    for (i = 0; i < 8; i++) {
        if (b & (1 << i)) {
            sum += i;  cnt++;
        }
    }
    if (cnt == 0) return 100.0f;       /* 全白,丢线标记 */
    if (cnt == 8) return 0.0f;         /* 全黑,横线压顶,判定居中 */
    return sum / cnt - 3.5f + CENTER_OFFSET;
}

功能讲解

  1. 两个数组建立探头编号到GPIO端口、引脚的映射。8个探头引脚分散在GPIOA、GPIOB,查表循环读取,不用重复写8次IO读取代码。
  2. Tracking_Bits():读取8路探头状态,压缩为1字节位图,调试串口打印可以直观观察探头压线状态。
  3. Tracking_Pos()加权平均算法,把8路探头位图,换算成黑线偏移量,作为PD控制器输入。
  • 探头3、4同时压线:(3+4)/2=3.5,减3.5,偏移=0,黑线居中;
  • 仅探头6压线:6-3.5=+2.5,黑线在右侧,需要右转;
  • CENTER_OFFSET:零点偏移,用来调整平衡点,偏移值过大,小车会一直贴赛道外侧行驶。

第11块:mpu6050.c —— 航向感知器官 MPU6050

点击展开代码

static void I2C_Start(void){ SDA_OUT(); SDA_H(); SCL_H(); dly(); SDA_L(); dly(); SCL_L(); }
static uint8_t I2C_WB(uint8_t b){          /* 发送1字节,高位先行 */
    uint8_t i;
    for(i=0;i<8;i++){
        if(b&0x80) SDA_H(); else SDA_L();  /* 准备电平 */
        SCL_H(); dly(); SCL_L();           /* 时钟脉冲,从机读取bit */
        b<<=1;
    }
    // 应答读取省略
}

void MPU_Init(void){
    //GPIO配置开漏模式
    WReg(0x6B,0x01);   /* 退出睡眠模式,选择PLL时钟,稳定性优于内部RC */
    WReg(0x1A,0x03);   /* 低通滤波42Hz,滤除电机振动高频噪声 */
    WReg(0x1B,0x08);   /* 陀螺仪量程±500°/s,灵敏度65.5 LSB/(°/s) */
    MPU_Calib(300);    /* 静止采样300次,计算陀螺零偏 */
}
uint8_t MPU_Calib(uint16_t n){
    int32_t s = 0; uint16_t i;
    for(i=0;i<n;i++){ s += GZ_raw(); Delay_ms(1); }
    gz_bias = s/(float)n;     /* 静止状态下的固有偏移,保存零偏 */
    yaw = 0;
    return 1;
}

void Yaw_Update(void){
    static uint32_t last_t = 0;
    float dt = (Millis() - last_t) / 1000.0f;        /* 距离上次积分的时间,单位秒 */
    float w  = YAW_SIGN * (GZ_raw() - gz_bias) / GYRO_LSB;   /* 换算为°/s角速度 */
    if (w > DEADBAND || w < -DEADBAND)          /* 死区,小于阈值不积分 */
        yaw += w * dt;                          /* ★角速度积分,得到总旋转角度 */
    last_t = Millis();
}

功能讲解
软件模拟I2C时序,手动操作GPIO高低电平实现I2C通信。不占用硬件I2C外设,通信速度低,适合低频读取陀螺数据。

  • 陀螺静止时读数不为0,存在固定零偏,上电静止采样求平均得到gz_bias,后续每次读取角速度减去零偏。支持蓝牙C指令随时重校准。
  • 陀螺核心积分逻辑:陀螺仪只能输出瞬时角速度,角度=角速度 × 时间积分。
  • 死区:过滤静止时微小噪声,避免长时间静止角度漂移;
  • YAW_SIGN:符号翻转,适配芯片贴装方向,修正角度正负。

第12块:motor.c / beep.c —— 底层执行机构 TB6612+蜂鸣器

点击展开代码

void Motor(int l, int r)
{
    if (l >= 0) { AIN1_H(); AIN2_L(); }   /* 正转:方向脚10 */
    else        { AIN1_L(); AIN2_H(); l = -l; }  /* 反转:方向脚01 */
    // 右轮方向逻辑
    if (r >= 0) { BIN1_H(); BIN2_L(); }
    else        { BIN1_L(); BIN2_H(); r = -r; }

    if (l > 80) l = 80;
    if (r > 80) r = 80;
    TIM3->CCR2 = l;    /* CCR寄存器值=PWM占空比,控制速度 */
    TIM3->CCR1 = r;
}

void Motor_Brake(void)   /* 电磁刹车 vs 断电滑行 */
{
    AIN1_H(); AIN2_H(); BIN1_H(); BIN2_H();  /* TB6612两端同电平,电机短接制动 */
    TIM3->CCR1 = 0;  TIM3->CCR2 = 0;
}

void Beep(uint16_t ms)
{
    uint32_t end = Millis() + ms;
    LED_ON();  BUZZ_ON();                    /* 低电平触发蜂鸣器 */
    while (Millis() < end);   /* 阻塞延时 */
    BUZZ_OFF();  LED_OFF();
}

功能讲解
Motor函数:将左右轮速度指令翻译成TB6612驱动信号。

  • 数值符号控制AIN1/AIN2方向引脚,决定电机正反转;
  • 绝对值赋值PWM捕获比较寄存器,控制输出电压,调节转速;
  • 限幅保护,最大占空比80。

区分:

  • Motor_Stop():断电滑行,依靠惯性前进;
  • Motor_Brake():电机绕组短接,电磁制动,快速锁死车轮,所有动作终点使用。

蜂鸣器声光提示:任务选择滴对应次数、每段到达短滴、失败长鸣、任务结束双响。


完整运行流程示例:执行任务5

手机发'5' ──→ 串口中断置g_task=5,g_newtask=1
              ↓ 主循环: 滴5声确认任务选中
手机发'G' ──→ 串口中断置g_start=1
              ↓ 主循环触发RunTask(5)
查表T5: {GAP,0}{LINE,-DIAG}{GAP,-DIAG+5}{LINE,+DIAG-5}
  │
  ├─ DoTurn(0)     → 直接跳过
  ├─ DoGap()       → Yaw_Zero锁航向,盲冲350ms,航向保持直行找线
  │                  → 连续见线50ms,判定到达C顶点,滴一声
  ├─ DoTurn(-40)   → Yaw_Zero,左旋40°,误差≤2°刹车稳定
  ├─ DoLine()      → Tracking_Pos读取黑线位置,PD闭环巡右弧
  │                  → 持续丢线满135ms,判定到达B顶点,滴一声
  ├─ DoTurn(-35)   → 原地左旋35°
  ├─ DoGap()       → 陀螺锁航向斜穿D区段
  └─ DoTurn(+35), DoLine() → 巡弧线回到A点
EXIT: 电磁刹车+双响蜂鸣+打印任务完成

参数调优实战章节(电赛调试经验)

1. PD巡线参数 DoLine

  • KP(比例系数):基础纠偏力度。
    • KP太小:反应迟钝,车容易偏离赛道;
    • KP太大:轻微偏移就大幅度打舵,车身剧烈抖动、振荡S形。>

    推荐起步:KP=6~10,从小到大试。

  • KD(微分系数):抑制惯性超调,防甩头。
    • KD过小:过弯冲线、来回震荡;
    • KD过大:控制僵硬,对弯道变化反应迟钝。>

    推荐起步:KD=1~3。

  • STEER_LIMIT舵量上限:限制最大打舵,防止瞬间猛打翻车,一般±35~±40。

2. 空白区航向保持 DoGap

  • GAP_TRIM:电机转速差静态补偿,解决小车天然跑偏。>

车一直往左跑 → GAP_TRIM加大;一直往右跑 → 减小。

  • FOUND_MS=50ms:见线防抖时间。地面污渍多就加大到60~80;赛道干净可以降到30。
  • GAP_TIMEOUT_MS空白区最大超时,防止卡死,一般5000~6000ms。

3. 弧线终点判定 DoLine

LOST_MS=135ms:持续丢线判定弧线结束。

  • 数值偏大:出弧后跑太远才判定到达;
  • 数值偏小:弯道中间短暂丢线直接误判终点。

4. 原地旋转 DoTurn

  • TURN_PWM:原地旋转PWM。PWM高转得快,但振动大,陀螺噪声增加;
  • 到位阈值2°:要求高精度可以降到1.5°,但容易震荡反复修正;放宽到3°更稳。

5. 探头零点偏移 Tracking_Pos

CENTER_OFFSET:探头阵列平衡点偏移。

现象:车总是贴赛道外侧跑 → CENTER_OFFSET绝对值太大,减小。
想让车骑在线内侧,增大偏移;居中行驶设置为0。

6. MPU陀螺相关

  • 低通滤波:42Hz,电机震动大可以切到20Hz,代价是相位滞后;
  • 死区DEADBAND:过滤静止噪声,0.2~0.3°/s最合适;
  • 校准:上电必须静置;赛道震动大,比赛前蓝牙发送C指令重新校准。

文末小结

本智能车采用分层架构,决策层只负责路线编排,动作层封装独立运动原语(原地旋转、空白区直行、弧线PD巡线),驱动层负责硬件读写。

  • 8路红外加权获取黑线偏移量,PD闭环巡弧线;
  • MPU6050陀螺积分获取航向,实现无黑线时航向保持与原地精确转角;
  • SysTick提供统一毫秒时基,串口蓝牙接收指令,中断置标志+主循环处理,保证系统稳定;
  • 丢线超时、见线防抖、急停全局检测,多重安全兜底,防止小车失控。

适用场景:电赛智能车微缩组,赛道包含黑线弧线、空白穿越段。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐