STM32项目开发:基于STM32F103的智能循迹避障小车

文章目录

    • 一、前言
      • [1.1 项目背景](#1.1 项目背景)
      • [1.2 应用场景](#1.2 应用场景)
      • [1.3 技术栈](#1.3 技术栈)
      • [1.4 学习目标](#1.4 学习目标)
    • 二、系统架构设计
      • [2.1 整体架构](#2.1 整体架构)
      • [2.2 数据流程](#2.2 数据流程)
      • [2.3 状态机设计](#2.3 状态机设计)
    • 三、硬件准备
      • [3.1 硬件清单](#3.1 硬件清单)
      • [3.2 引脚分配表](#3.2 引脚分配表)
      • [3.3 硬件连接图](#3.3 硬件连接图)
    • 四、开发环境搭建
      • [4.1 安装Keil MDK-ARM](#4.1 安装Keil MDK-ARM)
      • [4.2 安装ST-Link驱动](#4.2 安装ST-Link驱动)
      • [4.3 创建工程模板](#4.3 创建工程模板)
    • 五、核心代码实现
      • [5.1 项目文件结构](#5.1 项目文件结构)
      • [5.2 系统配置文件](#5.2 系统配置文件)
      • [5.3 电机驱动模块](#5.3 电机驱动模块)
      • [5.4 循迹传感器模块](#5.4 循迹传感器模块)
      • [5.5 超声波测距模块](#5.5 超声波测距模块)
      • [5.6 主程序](#5.6 主程序)
    • 六、测试与调试
      • [6.1 单元测试](#6.1 单元测试)
        • [6.1.1 电机测试](#6.1.1 电机测试)
        • [6.1.2 循迹传感器测试](#6.1.2 循迹传感器测试)
        • [6.1.3 超声波测试](#6.1.3 超声波测试)
      • [6.2 集成测试](#6.2 集成测试)
        • [6.2.1 循迹测试](#6.2.1 循迹测试)
        • [6.2.2 避障测试](#6.2.2 避障测试)
        • [6.2.3 综合测试](#6.2.3 综合测试)
    • 七、故障排查
      • [7.1 电机不转](#7.1 电机不转)
      • [7.2 循迹失效](#7.2 循迹失效)
      • [7.3 超声波测距异常](#7.3 超声波测距异常)
      • [7.4 程序跑飞](#7.4 程序跑飞)
    • 八、优化与扩展
      • [8.1 算法优化](#8.1 算法优化)
        • [8.1.1 PID循迹算法](#8.1.1 PID循迹算法)
        • [8.1.2 速度闭环控制](#8.1.2 速度闭环控制)
      • [8.2 功能扩展](#8.2 功能扩展)
        • [8.2.1 蓝牙遥控](#8.2.1 蓝牙遥控)
        • [8.2.2 红外遥控](#8.2.2 红外遥控)
    • 九、总结
      • [9.1 知识点回顾](#9.1 知识点回顾)
      • [9.2 项目扩展方向](#9.2 项目扩展方向)
      • [9.3 学习资源](#9.3 学习资源)

一、前言

1.1 项目背景

智能小车是嵌入式学习中最经典的项目之一,它综合了电机控制、传感器应用、算法实现等多个技术领域。循迹避障小车通过红外传感器检测地面黑线实现自动循迹,通过超声波传感器检测障碍物实现自动避障,是理解嵌入式系统工作原理的绝佳实践项目。

1.2 应用场景

  • 教育领域:高校电子、自动化专业教学实验
  • 竞赛项目:电子设计大赛、智能车竞赛
  • 工业应用:AGV自动导引车、仓库搬运机器人
  • 智能家居:扫地机器人、智能巡逻车

1.3 技术栈

类别 技术/器件 说明
主控芯片 STM32F103C8T6 ARM Cortex-M3,72MHz主频
电机驱动 L298N 双H桥直流电机驱动模块
循迹模块 TCRT5000×4 红外反射式光电传感器
避障模块 HC-SR04 超声波测距模块
显示模块 OLED 0.96寸 I2C接口显示屏
通信方式 USART 串口调试输出
开发环境 Keil MDK-ARM 集成开发环境

1.4 学习目标

通过本教程,你将学会:

  • ✅ STM32 GPIO、TIM、USART、ADC、I2C等外设配置
  • ✅ L298N电机驱动原理和PWM调速控制
  • ✅ 红外循迹传感器的工作原理和算法实现
  • ✅ 超声波测距模块的时序控制
  • ✅ 多传感器数据融合和决策算法
  • ✅ 状态机设计模式在小车控制中的应用

二、系统架构设计

2.1 整体架构

人机交互
执行层
控制层
传感器层
GPIO
GPIO
GPIO
GPIO
GPIO/TIM
PWM
PWM
GPIO
GPIO
I2C
USART
左循迹传感器
中左循迹传感器
中右循迹传感器
右循迹传感器
超声波模块
STM32F103主控
左电机驱动
右电机驱动
蜂鸣器
LED指示灯
OLED显示屏
串口调试

2.2 数据流程

OLED显示 电机驱动 STM32主控 超声波模块 循迹传感器 OLED显示 电机驱动 STM32主控 超声波模块 循迹传感器 loop [主循环10ms] 读取循迹状态 返回4路传感器值 触发测距 返回距离值 状态机决策 输出PWM控制 更新显示数据

2.3 状态机设计

启动按钮按下
检测到左偏
检测到右偏
前方有障碍物
停止按钮按下
回到中线
左偏严重
回到中线
右偏严重
左侧可行
右侧可行
两侧都有障碍
障碍清除
障碍清除
后退完成
停止
前进
左转
右转
避障
左转调头
右转调头
左转避障
右转避障
后退

三、硬件准备

3.1 硬件清单

序号 名称 数量 参考价格 说明
1 STM32F103C8T6最小系统板 1 ¥12 主控芯片
2 L298N电机驱动模块 1 ¥8 双路电机驱动
3 TT减速电机+车轮 2 ¥15 带编码器可选
4 亚克力小车底盘 1 ¥10 2轮/4轮可选
5 TCRT5000循迹模块 4 ¥8 红外循迹传感器
6 HC-SR04超声波模块 1 ¥6 超声波测距
7 OLED 0.96寸显示屏 1 ¥10 I2C接口
8 18650锂电池+电池盒 1 ¥20 7.4V供电
9 ST-Link调试器 1 ¥15 程序下载调试
10 杜邦线 若干 ¥5 公对母、母对母

3.2 引脚分配表

功能 STM32引脚 连接对象 说明
左电机PWM PA0 L298N ENA TIM2_CH1
右电机PWM PA1 L298N ENB TIM2_CH2
左电机方向1 PB12 L298N IN1 GPIO输出
左电机方向2 PB13 L298N IN2 GPIO输出
右电机方向1 PB14 L298N IN3 GPIO输出
右电机方向2 PB15 L298N IN4 GPIO输出
超声波Trig PB10 HC-SR04 Trig GPIO输出
超声波Echo PB11 HC-SR04 Echo TIM2_CH3输入捕获
循迹传感器1 PA4 左外侧 GPIO输入
循迹传感器2 PA5 左内侧 GPIO输入
循迹传感器3 PA6 右内侧 GPIO输入
循迹传感器4 PA7 右外侧 GPIO输入
OLED SCL PB6 OLED SCL I2C1_SCL
OLED SDA PB7 OLED SDA I2C1_SDA
串口TX PA9 USB转TTL RX USART1_TX
串口RX PA10 USB转TTL TX USART1_RX
蜂鸣器 PA8 有源蜂鸣器 GPIO输出
LED指示灯 PC13 板载LED GPIO输出

3.3 硬件连接图

复制代码
STM32F103C8T6
┌─────────────────────────────────────┐
│                                     │
│  PA0 ────────┐                      │
│  PA1 ────────┼──> L298N (PWM)       │
│              │                      │
│  PB12 ───────┼──> L298N IN1         │
│  PB13 ───────┼──> L298N IN2         │
│  PB14 ───────┼──> L298N IN3         │
│  PB15 ───────┘──> L298N IN4         │
│                                     │
│  PB10 ──────────> HC-SR04 Trig      │
│  PB11 ──────────> HC-SR04 Echo      │
│                                     │
│  PA4 ───────────> 循迹传感器1(左外)  │
│  PA5 ───────────> 循迹传感器2(左内)  │
│  PA6 ───────────> 循迹传感器3(右内)  │
│  PA7 ───────────> 循迹传感器4(右外)  │
│                                     │
│  PB6 ───────────> OLED SCL          │
│  PB7 ───────────> OLED SDA          │
│                                     │
│  PA9 ───────────> USB-TTL RX        │
│  PA10 ──────────> USB-TTL TX        │
│                                     │
│  PA8 ───────────> 蜂鸣器            │
│  PC13 ──────────> LED指示灯         │
│                                     │
└─────────────────────────────────────┘

四、开发环境搭建

4.1 安装Keil MDK-ARM

步骤1:下载安装

  1. 访问Keil官网:https://www.keil.com/download
  2. 下载MDK-ARM版本(建议5.38或更高)
  3. 运行安装程序,按默认选项安装

步骤2:安装STM32支持包

  1. 打开Keil,点击菜单 Project -> Manage -> Pack Installer
  2. 在左侧找到 STMicroelectronics -> STM32F1 Series
  3. 安装 Keil::STM32F1xx_DFP

步骤3:破解(仅供学习使用)

  1. 以管理员身份运行Keil
  2. 点击 File -> License Management
  3. 复制CID,使用注册机生成License ID
  4. 添加License完成注册

4.2 安装ST-Link驱动

Windows系统:

  1. 访问ST官网下载ST-Link驱动
  2. 运行 dpinst_amd64.exe(64位系统)或 dpinst_x86.exe(32位系统)
  3. 连接ST-Link调试器,系统自动识别

验证安装:

打开设备管理器,应看到 STMicroelectronics STLink dongle

4.3 创建工程模板

步骤1:新建工程

  1. 打开Keil,点击 Project -> New μVision Project
  2. 选择保存路径,输入工程名 SmartCar
  3. 选择芯片型号 STM32F103C8

步骤2:添加启动文件

  1. 在工程目录创建 Startup 文件夹
  2. 复制 startup_stm32f10x_md.s 到该文件夹
  3. 在Keil中添加该文件到工程

步骤3:配置工程选项

复制代码
Target选项卡:
- IROM1: 0x8000000, Size: 0x20000 (128KB)
- IRAM1: 0x20000000, Size: 0x5000 (20KB)

Output选项卡:
- 勾选 Create HEX File

C/C++选项卡:
- Define: STM32F10X_MD, USE_STDPERIPH_DRIVER
- Include Paths: 添加头文件路径

Debug选项卡:
- Use: ST-Link Debugger
- Settings -> Flash Download -> 勾选 Reset and Run

五、核心代码实现

5.1 项目文件结构

复制代码
SmartCar/
├── User/
│   ├── main.c              # 主程序入口
│   ├── stm32f10x_it.c      # 中断服务程序
│   └── stm32f10x_conf.h    # 外设配置文件
├── Hardware/
│   ├── motor.c/h           # 电机驱动模块
│   ├── track.c/h           # 循迹传感器模块
│   ├── ultrasonic.c/h      # 超声波测距模块
│   ├── oled.c/h            # OLED显示模块
│   ├── beep.c/h            # 蜂鸣器模块
│   └── sys.c/h             # 系统延时函数
├── STM32F10x_StdPeriph_Driver/
│   ├── src/                # 标准库源文件
│   └── inc/                # 标准库头文件
├── Startup/
│   └── startup_stm32f10x_md.s  # 启动文件
└── Project/
    └── SmartCar.uvprojx    # Keil工程文件

5.2 系统配置文件

📄 创建文件:Hardware/sys.h

c 复制代码
#ifndef __SYS_H
#define __SYS_H

#include "stm32f10x.h"

// 系统时钟配置
void SystemClock_Config(void);

// 微秒延时(使用SysTick)
void delay_us(uint32_t us);

// 毫秒延时
void delay_ms(uint32_t ms);

// 获取系统运行时间(毫秒)
uint32_t get_tick(void);

#endif

📄 创建文件:Hardware/sys.c

c 复制代码
#include "sys.h"

static volatile uint32_t sys_tick = 0;

/**
 * 系统时钟配置
 * 配置为72MHz,使用外部8MHz晶振
 */
void SystemClock_Config(void)
{
    ErrorStatus HSEStartUpStatus;
    
    // 使能HSE
    RCC_HSEConfig(RCC_HSE_ON);
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
    
    if (HSEStartUpStatus == SUCCESS)
    {
        // 使能预取指缓存
        FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
        FLASH_SetLatency(FLASH_Latency_2);
        
        // 配置PLL:8MHz * 9 = 72MHz
        RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
        RCC_PLLCmd(ENABLE);
        
        // 等待PLL就绪
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
        
        // 切换系统时钟到PLL
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
        while (RCC_GetSYSCLKSource() != 0x08);
        
        // 配置AHB、APB1、APB2分频
        RCC_HCLKConfig(RCC_SYSCLK_Div1);    // HCLK = 72MHz
        RCC_PCLK1Config(RCC_HCLK_Div2);     // APB1 = 36MHz
        RCC_PCLK2Config(RCC_HCLK_Div1);     // APB2 = 72MHz
        
        // 使能AFIO时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    }
}

/**
 * SysTick中断处理
 * 每1ms触发一次
 */
void SysTick_Handler(void)
{
    sys_tick++;
}

/**
 * 初始化SysTick定时器
 * 配置为1ms中断
 */
void SysTick_Init(void)
{
    // 配置SysTick为1ms中断
    // SystemCoreClock = 72MHz
    // 72MHz / 1000 = 72000
    if (SysTick_Config(SystemCoreClock / 1000))
    {
        while (1); // 配置失败
    }
}

/**
 * 获取系统运行时间(毫秒)
 */
uint32_t get_tick(void)
{
    return sys_tick;
}

/**
 * 毫秒延时
 */
void delay_ms(uint32_t ms)
{
    uint32_t start = get_tick();
    while ((get_tick() - start) < ms);
}

/**
 * 微秒延时
 * 使用空循环实现,72MHz下约72个周期1us
 */
void delay_us(uint32_t us)
{
    uint32_t count;
    while (us--)
    {
        count = 8; // 根据实际时钟调整
        while (count--);
    }
}

5.3 电机驱动模块

📄 创建文件:Hardware/motor.h

c 复制代码
#ifndef __MOTOR_H
#define __MOTOR_H

#include "sys.h"

// 电机方向定义
typedef enum {
    MOTOR_STOP = 0,     // 停止
    MOTOR_FORWARD,      // 正转
    MOTOR_BACKWARD      // 反转
} Motor_Direction;

// 电机初始化
void Motor_Init(void);

// 左电机控制
// speed: 0-1000 (对应0-100%占空比)
void Motor_Left_Set(Motor_Direction dir, uint16_t speed);

// 右电机控制
void Motor_Right_Set(Motor_Direction dir, uint16_t speed);

// 小车前进
void Car_Forward(uint16_t speed);

// 小车后退
void Car_Backward(uint16_t speed);

// 小车左转(差速转向)
void Car_TurnLeft(uint16_t speed);

// 小车右转(差速转向)
void Car_TurnRight(uint16_t speed);

// 小车停止
void Car_Stop(void);

// 小车原地左转
void Car_SpinLeft(uint16_t speed);

// 小车原地右转
void Car_SpinRight(uint16_t speed);

#endif

📄 创建文件:Hardware/motor.c

c 复制代码
#include "motor.h"

// 电机控制引脚定义
#define LEFT_MOTOR_ENA_PIN    GPIO_Pin_0   // PA0 - TIM2_CH1
#define RIGHT_MOTOR_ENB_PIN   GPIO_Pin_1   // PA1 - TIM2_CH2
#define LEFT_MOTOR_IN1_PIN    GPIO_Pin_12  // PB12
#define LEFT_MOTOR_IN2_PIN    GPIO_Pin_13  // PB13
#define RIGHT_MOTOR_IN3_PIN   GPIO_Pin_14  // PB14
#define RIGHT_MOTOR_IN4_PIN   GPIO_Pin_15  // PB15

#define LEFT_MOTOR_ENA_PORT   GPIOA
#define RIGHT_MOTOR_ENB_PORT  GPIOA
#define LEFT_MOTOR_IN_PORT    GPIOB
#define RIGHT_MOTOR_IN_PORT   GPIOB

#define PWM_MAX  1000  // PWM最大值

/**
 * 电机GPIO和PWM初始化
 * 使用TIM2的CH1和CH2输出PWM
 * 频率:10kHz
 */
void Motor_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 配置PA0、PA1为复用推挽输出(PWM输出)
    GPIO_InitStructure.GPIO_Pin = LEFT_MOTOR_ENA_PIN | RIGHT_MOTOR_ENB_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置PB12-PB15为推挽输出(方向控制)
    GPIO_InitStructure.GPIO_Pin = LEFT_MOTOR_IN1_PIN | LEFT_MOTOR_IN2_PIN |
                                   RIGHT_MOTOR_IN3_PIN | RIGHT_MOTOR_IN4_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 初始化方向引脚为低电平(停止状态)
    GPIO_ResetBits(LEFT_MOTOR_IN_PORT, LEFT_MOTOR_IN1_PIN | LEFT_MOTOR_IN2_PIN);
    GPIO_ResetBits(RIGHT_MOTOR_IN_PORT, RIGHT_MOTOR_IN3_PIN | RIGHT_MOTOR_IN4_PIN);
    
    // TIM2时基配置
    // PWM频率 = 72MHz / (719+1) / (99+1) = 10kHz
    TIM_TimeBaseStructure.TIM_Period = PWM_MAX - 1;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;  // 72MHz / 72 = 1MHz
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // TIM2 CH1 PWM模式配置(左电机)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;  // 初始占空比0
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
    
    // TIM2 CH2 PWM模式配置(右电机)
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
    
    // 使能TIM2
    TIM_Cmd(TIM2, ENABLE);
    TIM_ARRPreloadConfig(TIM2, ENABLE);
}

/**
 * 设置左电机速度和方向
 * @param dir: 方向(停止/正转/反转)
 * @param speed: 速度值 0-1000
 */
void Motor_Left_Set(Motor_Direction dir, uint16_t speed)
{
    // 限制速度范围
    if (speed > PWM_MAX) speed = PWM_MAX;
    
    switch (dir)
    {
        case MOTOR_STOP:
            GPIO_ResetBits(LEFT_MOTOR_IN_PORT, LEFT_MOTOR_IN1_PIN | LEFT_MOTOR_IN2_PIN);
            TIM_SetCompare1(TIM2, 0);
            break;
            
        case MOTOR_FORWARD:
            GPIO_SetBits(LEFT_MOTOR_IN_PORT, LEFT_MOTOR_IN1_PIN);
            GPIO_ResetBits(LEFT_MOTOR_IN_PORT, LEFT_MOTOR_IN2_PIN);
            TIM_SetCompare1(TIM2, speed);
            break;
            
        case MOTOR_BACKWARD:
            GPIO_ResetBits(LEFT_MOTOR_IN_PORT, LEFT_MOTOR_IN1_PIN);
            GPIO_SetBits(LEFT_MOTOR_IN_PORT, LEFT_MOTOR_IN2_PIN);
            TIM_SetCompare1(TIM2, speed);
            break;
    }
}

/**
 * 设置右电机速度和方向
 */
void Motor_Right_Set(Motor_Direction dir, uint16_t speed)
{
    if (speed > PWM_MAX) speed = PWM_MAX;
    
    switch (dir)
    {
        case MOTOR_STOP:
            GPIO_ResetBits(RIGHT_MOTOR_IN_PORT, RIGHT_MOTOR_IN3_PIN | RIGHT_MOTOR_IN4_PIN);
            TIM_SetCompare2(TIM2, 0);
            break;
            
        case MOTOR_FORWARD:
            GPIO_SetBits(RIGHT_MOTOR_IN_PORT, RIGHT_MOTOR_IN3_PIN);
            GPIO_ResetBits(RIGHT_MOTOR_IN_PORT, RIGHT_MOTOR_IN4_PIN);
            TIM_SetCompare2(TIM2, speed);
            break;
            
        case MOTOR_BACKWARD:
            GPIO_ResetBits(RIGHT_MOTOR_IN_PORT, RIGHT_MOTOR_IN3_PIN);
            GPIO_SetBits(RIGHT_MOTOR_IN_PORT, RIGHT_MOTOR_IN4_PIN);
            TIM_SetCompare2(TIM2, speed);
            break;
    }
}

/**
 * 小车前进
 */
void Car_Forward(uint16_t speed)
{
    Motor_Left_Set(MOTOR_FORWARD, speed);
    Motor_Right_Set(MOTOR_FORWARD, speed);
}

/**
 * 小车后退
 */
void Car_Backward(uint16_t speed)
{
    Motor_Left_Set(MOTOR_BACKWARD, speed);
    Motor_Right_Set(MOTOR_BACKWARD, speed);
}

/**
 * 小车左转(差速转向,左慢右快)
 */
void Car_TurnLeft(uint16_t speed)
{
    Motor_Left_Set(MOTOR_FORWARD, speed / 2);
    Motor_Right_Set(MOTOR_FORWARD, speed);
}

/**
 * 小车右转(差速转向,右慢左快)
 */
void Car_TurnRight(uint16_t speed)
{
    Motor_Left_Set(MOTOR_FORWARD, speed);
    Motor_Right_Set(MOTOR_FORWARD, speed / 2);
}

/**
 * 小车停止
 */
void Car_Stop(void)
{
    Motor_Left_Set(MOTOR_STOP, 0);
    Motor_Right_Set(MOTOR_STOP, 0);
}

/**
 * 小车原地左转(左退右进)
 */
void Car_SpinLeft(uint16_t speed)
{
    Motor_Left_Set(MOTOR_BACKWARD, speed);
    Motor_Right_Set(MOTOR_FORWARD, speed);
}

/**
 * 小车原地右转(右退左进)
 */
void Car_SpinRight(uint16_t speed)
{
    Motor_Left_Set(MOTOR_FORWARD, speed);
    Motor_Right_Set(MOTOR_BACKWARD, speed);
}

5.4 循迹传感器模块

📄 创建文件:Hardware/track.h

c 复制代码
#ifndef __TRACK_H
#define __TRACK_H

#include "sys.h"

// 循迹传感器数量
#define TRACK_SENSOR_NUM  4

// 传感器位置定义
typedef enum {
    TRACK_LEFT_OUTER = 0,   // 左外侧
    TRACK_LEFT_INNER,       // 左内侧
    TRACK_RIGHT_INNER,      // 右内侧
    TRACK_RIGHT_OUTER       // 右外侧
} Track_Sensor_Position;

// 循迹状态定义
typedef enum {
    TRACK_STATE_ON_LINE = 0,    // 在线上(检测到黑线)
    TRACK_STATE_OFF_LINE        // 离线(检测到白色)
} Track_State;

// 小车位置状态
typedef enum {
    CAR_ON_CENTER = 0,      // 在中心
    CAR_LEFT_LIGHT,         // 轻微左偏
    CAR_LEFT_HEAVY,         // 严重左偏
    CAR_RIGHT_LIGHT,        // 轻微右偏
    CAR_RIGHT_HEAVY,        // 严重右偏
    CAR_OFF_TRACK           // 完全偏离
} Car_Position_State;

// 循迹传感器初始化
void Track_Init(void);

// 读取单个传感器状态
Track_State Track_ReadSensor(Track_Sensor_Position pos);

// 读取所有传感器状态
// 返回值:4位二进制,bit0-3对应传感器0-3,1表示检测到黑线
uint8_t Track_ReadAll(void);

// 获取小车位置状态
Car_Position_State Track_GetCarPosition(void);

// 循迹控制(根据位置状态返回建议动作)
void Track_FollowLine(uint16_t base_speed);

#endif

📄 创建文件:Hardware/track.c

c 复制代码
#include "track.h"

// 循迹传感器引脚定义
const uint16_t TRACK_PINS[TRACK_SENSOR_NUM] = {
    GPIO_Pin_4,   // PA4 - 左外侧
    GPIO_Pin_5,   // PA5 - 左内侧
    GPIO_Pin_6,   // PA6 - 右内侧
    GPIO_Pin_7    // PA7 - 右外侧
};

/**
 * 循迹传感器初始化
 * 配置为输入模式,使能上拉电阻
 */
void Track_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置PA4-PA7为带上拉的输入模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

/**
 * 读取单个传感器状态
 * @param pos: 传感器位置
 * @return: 状态(在线上/离线)
 * 
 * 注意:TCRT5000输出低电平表示检测到黑线(反射弱)
 *       输出高电平表示检测到白色(反射强)
 */
Track_State Track_ReadSensor(Track_Sensor_Position pos)
{
    if (pos >= TRACK_SENSOR_NUM) return TRACK_STATE_OFF_LINE;
    
    // 读取引脚电平,低电平表示检测到黑线
    if (GPIO_ReadInputDataBit(GPIOA, TRACK_PINS[pos]) == Bit_RESET)
    {
        return TRACK_STATE_ON_LINE;
    }
    else
    {
        return TRACK_STATE_OFF_LINE;
    }
}

/**
 * 读取所有传感器状态
 * @return: 4位值,从低位到高位对应左外、左内、右内、右外
 *          位值为1表示检测到黑线
 */
uint8_t Track_ReadAll(void)
{
    uint8_t state = 0;
    
    for (int i = 0; i < TRACK_SENSOR_NUM; i++)
    {
        if (Track_ReadSensor((Track_Sensor_Position)i) == TRACK_STATE_ON_LINE)
        {
            state |= (1 << i);
        }
    }
    
    return state;
}

/**
 * 根据传感器状态判断小车位置
 * @return: 小车位置状态
 */
Car_Position_State Track_GetCarPosition(void)
{
    uint8_t sensor_state = Track_ReadAll();
    
    switch (sensor_state)
    {
        case 0b0000:  // 0000 - 全部离线
            return CAR_OFF_TRACK;
            
        case 0b0110:  // 0110 - 中间两个在线(正常)
            return CAR_ON_CENTER;
            
        case 0b0100:  // 0100 - 左内在线(轻微左偏)
        case 0b0010:  // 0010 - 右内在线(轻微右偏)
            return CAR_ON_CENTER;
            
        case 0b1100:  // 1100 - 左侧两个在线(轻微左偏)
        case 0b1000:  // 1000 - 左外在线(轻微左偏)
            return CAR_LEFT_LIGHT;
            
        case 0b0011:  // 0011 - 右侧两个在线(轻微右偏)
        case 0b0001:  // 0001 - 右外在线(轻微右偏)
            return CAR_RIGHT_LIGHT;
            
        case 0b1110:  // 1110 - 左侧三个在线(严重左偏)
        case 0b1111:  // 1111 - 全部在线(严重左偏)
            return CAR_LEFT_HEAVY;
            
        case 0b0111:  // 0111 - 右侧三个在线(严重右偏)
            return CAR_RIGHT_HEAVY;
            
        default:
            return CAR_ON_CENTER;
    }
}

/**
 * 循迹控制函数
 * 根据小车位置自动调整方向
 * @param base_speed: 基础速度 0-1000
 */
void Track_FollowLine(uint16_t base_speed)
{
    Car_Position_State pos = Track_GetCarPosition();
    
    switch (pos)
    {
        case CAR_ON_CENTER:
            // 在中心,直行
            Car_Forward(base_speed);
            break;
            
        case CAR_LEFT_LIGHT:
            // 轻微左偏,小幅度右转
            Motor_Left_Set(MOTOR_FORWARD, base_speed);
            Motor_Right_Set(MOTOR_FORWARD, base_speed * 0.6);
            break;
            
        case CAR_LEFT_HEAVY:
            // 严重左偏,大幅度右转
            Motor_Left_Set(MOTOR_FORWARD, base_speed);
            Motor_Right_Set(MOTOR_FORWARD, base_speed * 0.3);
            break;
            
        case CAR_RIGHT_LIGHT:
            // 轻微右偏,小幅度左转
            Motor_Left_Set(MOTOR_FORWARD, base_speed * 0.6);
            Motor_Right_Set(MOTOR_FORWARD, base_speed);
            break;
            
        case CAR_RIGHT_HEAVY:
            // 严重右偏,大幅度左转
            Motor_Left_Set(MOTOR_FORWARD, base_speed * 0.3);
            Motor_Right_Set(MOTOR_FORWARD, base_speed);
            break;
            
        case CAR_OFF_TRACK:
            // 完全偏离,停止或后退
            Car_Stop();
            delay_ms(100);
            Car_Backward(base_speed * 0.5);
            delay_ms(300);
            break;
    }
}

5.5 超声波测距模块

📄 创建文件:Hardware/ultrasonic.h

c 复制代码
#ifndef __ULTRASONIC_H
#define __ULTRASONIC_H

#include "sys.h"

// 超声波引脚定义
#define ULTRASONIC_TRIG_PIN     GPIO_Pin_10  // PB10
#define ULTRASONIC_ECHO_PIN     GPIO_Pin_11  // PB11
#define ULTRASONIC_TRIG_PORT    GPIOB
#define ULTRASONIC_ECHO_PORT    GPIOB

// 距离阈值定义(单位:厘米)
#define OBSTACLE_DISTANCE_NEAR   15   // 近距离
#define OBSTACLE_DISTANCE_MID    30   // 中距离
#define OBSTACLE_DISTANCE_FAR    50   // 远距离

// 超声波模块初始化
void Ultrasonic_Init(void);

// 测量距离
// 返回值:距离(厘米),最大400cm
uint16_t Ultrasonic_GetDistance(void);

// 检测是否有障碍物
// 返回值:1-有障碍,0-无障碍
uint8_t Ultrasonic_IsObstacle(uint16_t threshold);

// 连续测量多次取平均值
uint16_t Ultrasonic_GetDistanceAvg(uint8_t times);

#endif

📄 创建文件:Hardware/ultrasonic.c

c 复制代码
#include "ultrasonic.h"

// 记录Echo引脚高电平时间
static volatile uint32_t ultrasonic_capture_start = 0;
static volatile uint32_t ultrasonic_capture_end = 0;
static volatile uint8_t ultrasonic_capture_flag = 0;

/**
 * 超声波模块初始化
 * Trig:普通GPIO输出
 * Echo:TIM2_CH3输入捕获
 */
void Ultrasonic_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 配置PB10为推挽输出(Trig)
    GPIO_InitStructure.GPIO_Pin = ULTRASONIC_TRIG_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(ULTRASONIC_TRIG_PORT, &GPIO_InitStructure);
    GPIO_ResetBits(ULTRASONIC_TRIG_PORT, ULTRASONIC_TRIG_PIN);
    
    // 配置PB11为浮空输入(Echo)
    GPIO_InitStructure.GPIO_Pin = ULTRASONIC_ECHO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(ULTRASONIC_ECHO_PORT, &GPIO_InitStructure);
    
    // TIM2时基配置(1MHz计数频率)
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
    TIM_TimeBaseStructure.TIM_Prescaler = 71;  // 72MHz / 72 = 1MHz
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // TIM2 CH3输入捕获配置
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 上升沿捕获
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x0;
    TIM_ICInit(TIM2, &TIM_ICInitStructure);
    
    // 配置中断
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 使能输入捕获中断
    TIM_ITConfig(TIM2, TIM_IT_CC3, ENABLE);
    
    // 使能TIM2
    TIM_Cmd(TIM2, ENABLE);
}

/**
 * TIM2中断服务程序
 * 处理输入捕获中断
 */
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_CC3) != RESET)
    {
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
        
        if (ultrasonic_capture_flag == 0)  // 第一次捕获(上升沿)
        {
            ultrasonic_capture_start = TIM_GetCapture3(TIM2);
            ultrasonic_capture_flag = 1;
            
            // 切换为下降沿捕获
            TIM_OC3PolarityConfig(TIM2, TIM_ICPolarity_Falling);
        }
        else  // 第二次捕获(下降沿)
        {
            ultrasonic_capture_end = TIM_GetCapture3(TIM2);
            ultrasonic_capture_flag = 2;
            
            // 切换回上升沿捕获
            TIM_OC3PolarityConfig(TIM2, TIM_ICPolarity_Rising);
        }
    }
}

/**
 * 测量距离
 * 原理:发送10us高电平触发,等待Echo返回高电平
 *       高电平持续时间就是超声波往返时间
 *       距离 = 时间 * 声速 / 2 = 时间(us) * 0.034 / 2 (cm)
 * 
 * @return: 距离(厘米),最大400cm,失败返回0xFFFF
 */
uint16_t Ultrasonic_GetDistance(void)
{
    uint32_t timeout;
    
    // 发送触发信号(10us高电平)
    GPIO_SetBits(ULTRASONIC_TRIG_PORT, ULTRASONIC_TRIG_PIN);
    delay_us(15);  // 稍微延长确保稳定
    GPIO_ResetBits(ULTRASONIC_TRIG_PORT, ULTRASONIC_TRIG_PIN);
    
    // 等待捕获完成或超时
    ultrasonic_capture_flag = 0;
    timeout = get_tick() + 50;  // 50ms超时
    
    while (ultrasonic_capture_flag != 2)
    {
        if (get_tick() > timeout)
        {
            return 0xFFFF;  // 超时
        }
    }
    
    // 计算时间差(微秒)
    uint32_t duration;
    if (ultrasonic_capture_end >= ultrasonic_capture_start)
    {
        duration = ultrasonic_capture_end - ultrasonic_capture_start;
    }
    else
    {
        // 计数器溢出
        duration = (0xFFFF - ultrasonic_capture_start) + ultrasonic_capture_end + 1;
    }
    
    // 计算距离:距离(cm) = 时间(us) / 58
    // 声速340m/s,换算:us * 0.000001 * 34000 / 2 = us / 58.8
    uint16_t distance = duration / 58;
    
    // 限制最大距离
    if (distance > 400) distance = 400;
    
    return distance;
}

/**
 * 检测是否有障碍物
 * @param threshold: 距离阈值(厘米)
 * @return: 1-有障碍,0-无障碍
 */
uint8_t Ultrasonic_IsObstacle(uint16_t threshold)
{
    uint16_t distance = Ultrasonic_GetDistance();
    
    if (distance == 0xFFFF) return 0;  // 测量失败
    
    return (distance < threshold) ? 1 : 0;
}

/**
 * 连续测量多次取平均值
 * @param times: 测量次数
 * @return: 平均距离
 */
uint16_t Ultrasonic_GetDistanceAvg(uint8_t times)
{
    uint32_t sum = 0;
    uint8_t valid_count = 0;
    
    for (uint8_t i = 0; i < times; i++)
    {
        uint16_t dist = Ultrasonic_GetDistance();
        if (dist != 0xFFFF)
        {
            sum += dist;
            valid_count++;
        }
        delay_ms(10);  // 两次测量间隔
    }
    
    if (valid_count == 0) return 0xFFFF;
    
    return (uint16_t)(sum / valid_count);
}

5.6 主程序

📄 创建文件:User/main.c

c 复制代码
/**
 * STM32智能循迹避障小车
 * 主程序入口
 * 
 * 硬件平台:STM32F103C8T6
 * 功能:循迹 + 避障
 */

#include "stm32f10x.h"
#include "sys.h"
#include "motor.h"
#include "track.h"
#include "ultrasonic.h"
#include "beep.h"

// 系统状态定义
typedef enum {
    SYS_STATE_STOP = 0,
    SYS_STATE_RUNNING,
    SYS_STATE_AVOIDING
} System_State;

// 全局变量
static System_State g_system_state = SYS_STATE_STOP;
static uint16_t g_base_speed = 600;  // 基础速度

// 函数声明
void System_Init(void);
void System_Loop(void);
void Avoid_Obstacle(void);

/**
 * 主函数
 */
int main(void)
{
    // 系统初始化
    System_Init();
    
    // 启动提示
    Beep_On();
    delay_ms(200);
    Beep_Off();
    
    // 主循环
    while (1)
    {
        System_Loop();
    }
}

/**
 * 系统初始化
 */
void System_Init(void)
{
    // 配置系统时钟72MHz
    SystemClock_Config();
    
    // 初始化SysTick
    SysTick_Init();
    
    // 初始化各模块
    Motor_Init();        // 电机驱动
    Track_Init();        // 循迹传感器
    Ultrasonic_Init();   // 超声波模块
    Beep_Init();         // 蜂鸣器
    
    // 延时等待系统稳定
    delay_ms(500);
}

/**
 * 主循环
 * 10ms执行一次
 */
void System_Loop(void)
{
    static uint32_t last_time = 0;
    uint32_t current_time = get_tick();
    
    // 10ms周期控制
    if ((current_time - last_time) < 10) return;
    last_time = current_time;
    
    // 读取超声波距离
    uint16_t distance = Ultrasonic_GetDistanceAvg(3);
    
    // 状态机处理
    switch (g_system_state)
    {
        case SYS_STATE_STOP:
            Car_Stop();
            // 可以添加启动条件,如按键触发
            break;
            
        case SYS_STATE_RUNNING:
            // 检测障碍物
            if (distance < OBSTACLE_DISTANCE_NEAR && distance != 0xFFFF)
            {
                // 发现近距离障碍物,进入避障模式
                g_system_state = SYS_STATE_AVOIDING;
                Beep_On();  // 蜂鸣器提示
            }
            else if (distance < OBSTACLE_DISTANCE_MID && distance != 0xFFFF)
            {
                // 中距离障碍物,减速循迹
                Track_FollowLine(g_base_speed / 2);
            }
            else
            {
                // 正常循迹
                Track_FollowLine(g_base_speed);
            }
            break;
            
        case SYS_STATE_AVOIDING:
            Avoid_Obstacle();
            break;
    }
}

/**
 * 避障处理
 * 简单策略:后退->左转->前进
 */
void Avoid_Obstacle(void)
{
    // 停止
    Car_Stop();
    delay_ms(200);
    
    // 后退
    Car_Backward(g_base_speed / 2);
    delay_ms(500);
    
    // 原地左转
    Car_SpinLeft(g_base_speed / 2);
    delay_ms(800);
    
    // 恢复循迹状态
    g_system_state = SYS_STATE_RUNNING;
    Beep_Off();
}

/**
 * 启动小车
 * 可以绑定到按键中断
 */
void Car_Start(void)
{
    g_system_state = SYS_STATE_RUNNING;
    Beep_On();
    delay_ms(100);
    Beep_Off();
}

/**
 * 停止小车
 */
void Car_Stop_Manual(void)
{
    g_system_state = SYS_STATE_STOP;
    Car_Stop();
}

六、测试与调试

6.1 单元测试

6.1.1 电机测试
c 复制代码
/**
 * 电机测试程序
 * 依次测试:前进、后退、左转、右转、停止
 */
void Motor_Test(void)
{
    Motor_Init();
    
    while (1)
    {
        // 前进3秒
        Car_Forward(500);
        delay_ms(3000);
        
        // 停止1秒
        Car_Stop();
        delay_ms(1000);
        
        // 后退3秒
        Car_Backward(500);
        delay_ms(3000);
        
        // 停止1秒
        Car_Stop();
        delay_ms(1000);
        
        // 左转3秒
        Car_TurnLeft(500);
        delay_ms(3000);
        
        // 停止1秒
        Car_Stop();
        delay_ms(1000);
        
        // 右转3秒
        Car_TurnRight(500);
        delay_ms(3000);
        
        // 停止1秒
        Car_Stop();
        delay_ms(1000);
    }
}
6.1.2 循迹传感器测试
c 复制代码
/**
 * 循迹传感器测试
 * 通过串口输出传感器状态
 */
void Track_Test(void)
{
    Track_Init();
    USART1_Init(115200);  // 初始化串口
    
    while (1)
    {
        uint8_t state = Track_ReadAll();
        
        // 输出二进制状态
        printf("Sensor State: %d%d%d%d\r\n",
               (state >> 3) & 1,
               (state >> 2) & 1,
               (state >> 1) & 1,
               state & 1);
        
        // 输出位置状态
        Car_Position_State pos = Track_GetCarPosition();
        const char* pos_str[] = {
            "CENTER", "LEFT_LIGHT", "LEFT_HEAVY",
            "RIGHT_LIGHT", "RIGHT_HEAVY", "OFF_TRACK"
        };
        printf("Position: %s\r\n", pos_str[pos]);
        
        delay_ms(200);
    }
}
6.1.3 超声波测试
c 复制代码
/**
 * 超声波测距测试
 */
void Ultrasonic_Test(void)
{
    Ultrasonic_Init();
    USART1_Init(115200);
    
    while (1)
    {
        uint16_t distance = Ultrasonic_GetDistance();
        
        if (distance == 0xFFFF)
        {
            printf("Distance: Error\r\n");
        }
        else
        {
            printf("Distance: %d cm\r\n", distance);
        }
        
        delay_ms(500);
    }
}

6.2 集成测试

6.2.1 循迹测试
  1. 铺设黑色轨道(宽度约2-3cm)
  2. 将小车放在轨道中央
  3. 观察小车是否能沿轨道行驶
  4. 调整PID参数优化循迹效果
6.2.2 避障测试
  1. 在轨道上放置障碍物
  2. 观察小车是否能正确检测并避开
  3. 调整避障距离阈值
6.2.3 综合测试
  1. 铺设带弯道的轨道
  2. 在轨道上设置障碍物
  3. 测试循迹+避障的综合性能

七、故障排查

7.1 电机不转

现象: 程序运行但电机不转动

排查步骤:

  1. 检查电源电压(L298N需要7-12V)
  2. 检查电机接线是否牢固
  3. 用万用表测量PWM输出
  4. 检查L298N使能端是否接好

解决方案:

  • 确保电池电压充足
  • 检查ENA、ENB是否连接到PWM输出
  • 检查IN1-IN4接线

7.2 循迹失效

现象: 小车无法正确循迹

排查步骤:

  1. 检查传感器是否对准地面(距离2-3cm)
  2. 检查传感器输出电平
  3. 测试黑白检测是否相反

解决方案:

  • 调整传感器高度
  • 检查代码中高低电平判断是否正确
  • 在传感器上添加遮光罩减少干扰

7.3 超声波测距异常

现象: 距离值跳动大或一直为0

排查步骤:

  1. 检查Trig和Echo接线
  2. 测量Trig是否有10us脉冲
  3. 检查Echo引脚电平变化

解决方案:

  • 确保两次测量间隔至少60ms
  • 检查是否有其他中断干扰
  • 添加滤波算法平滑数据

7.4 程序跑飞

现象: 程序运行一段时间后停止响应

排查步骤:

  1. 检查数组越界
  2. 检查指针使用
  3. 检查栈溢出

解决方案:

  • 使能看门狗定时器
  • 增加栈大小
  • 检查所有数组访问边界

八、优化与扩展

8.1 算法优化

8.1.1 PID循迹算法
c 复制代码
// PID控制结构体
typedef struct {
    float Kp, Ki, Kd;
    float error, last_error;
    float integral;
    float output;
} PID_Controller;

// PID循迹
void Track_PID_FollowLine(uint16_t base_speed)
{
    static PID_Controller pid = {0.8, 0.0, 0.2, 0, 0, 0, 0};
    
    // 计算偏差(根据传感器状态)
    uint8_t sensor_state = Track_ReadAll();
    float position_error = 0;
    
    switch (sensor_state) {
        case 0b0110: position_error = 0; break;
        case 0b0100: position_error = -1; break;
        case 0b0010: position_error = 1; break;
        case 0b1100: position_error = -2; break;
        case 0b0011: position_error = 2; break;
        case 0b1000: position_error = -3; break;
        case 0b0001: position_error = 3; break;
        default: position_error = pid.last_error;
    }
    
    // PID计算
    pid.error = position_error;
    pid.integral += pid.error;
    
    // 积分限幅
    if (pid.integral > 100) pid.integral = 100;
    if (pid.integral < -100) pid.integral = -100;
    
    pid.output = pid.Kp * pid.error + 
                 pid.Ki * pid.integral + 
                 pid.Kd * (pid.error - pid.last_error);
    
    pid.last_error = pid.error;
    
    // 输出到电机
    int left_speed = base_speed + pid.output * 50;
    int right_speed = base_speed - pid.output * 50;
    
    // 限幅
    if (left_speed > 1000) left_speed = 1000;
    if (left_speed < 0) left_speed = 0;
    if (right_speed > 1000) right_speed = 1000;
    if (right_speed < 0) right_speed = 0;
    
    Motor_Left_Set(MOTOR_FORWARD, left_speed);
    Motor_Right_Set(MOTOR_FORWARD, right_speed);
}
8.1.2 速度闭环控制

使用编码器实现速度闭环:

c 复制代码
// 编码器初始化(使用TIM3、TIM4)
void Encoder_Init(void)
{
    // 配置TIM3、TIM4为编码器模式
    // ...
}

// 速度闭环控制
void Speed_Control(int16_t target_speed_left, int16_t target_speed_right)
{
    static float left_integral = 0, right_integral = 0;
    
    // 读取当前速度(编码器值)
    int16_t current_left = TIM_GetCounter(TIM3);
    int16_t current_right = TIM_GetCounter(TIM4);
    
    // 计算误差
    float left_error = target_speed_left - current_left;
    float right_error = target_speed_right - current_right;
    
    // PID计算
    left_integral += left_error;
    right_integral += right_error;
    
    float left_output = 0.5 * left_error + 0.01 * left_integral;
    float right_output = 0.5 * right_error + 0.01 * right_integral;
    
    // 输出PWM
    Motor_Left_Set(MOTOR_FORWARD, (uint16_t)left_output);
    Motor_Right_Set(MOTOR_FORWARD, (uint16_t)right_output);
}

8.2 功能扩展

8.2.1 蓝牙遥控

添加HC-05蓝牙模块,实现手机APP遥控:

c 复制代码
// 蓝牙命令处理
void Bluetooth_Process(uint8_t cmd)
{
    switch (cmd)
    {
        case 'F': Car_Forward(g_base_speed); break;
        case 'B': Car_Backward(g_base_speed); break;
        case 'L': Car_TurnLeft(g_base_speed); break;
        case 'R': Car_TurnRight(g_base_speed); break;
        case 'S': Car_Stop(); break;
        case 'A': g_system_state = SYS_STATE_RUNNING; break;  // 自动模式
        case 'M': g_system_state = SYS_STATE_STOP; break;     // 手动模式
    }
}
8.2.2 红外遥控

添加红外接收模块,实现遥控器控制:

c 复制代码
// 红外解码(使用NEC协议)
void IR_Process(void)
{
    uint32_t ir_code = IR_Receive();
    
    switch (ir_code)
    {
        case IR_KEY_UP:    Car_Forward(g_base_speed); break;
        case IR_KEY_DOWN:  Car_Backward(g_base_speed); break;
        case IR_KEY_LEFT:  Car_SpinLeft(g_base_speed); break;
        case IR_KEY_RIGHT: Car_SpinRight(g_base_speed); break;
        case IR_KEY_OK:    Car_Stop(); break;
    }
}

九、总结

9.1 知识点回顾

通过本项目,我们学习了:

  1. STM32外设应用

    • GPIO输入输出配置
    • TIM定时器PWM输出
    • TIM输入捕获
    • 外部中断和定时器中断
    • USART串口通信
  2. 电机控制技术

    • H桥驱动原理
    • PWM调速控制
    • 差速转向实现
  3. 传感器应用

    • 红外循迹传感器
    • 超声波测距模块
    • 传感器数据融合
  4. 控制算法

    • 状态机设计
    • PID控制算法
    • 简单的避障策略

9.2 项目扩展方向

  1. 算法优化

    • 实现更复杂的PID参数整定
    • 添加模糊控制算法
    • 实现路径规划算法
  2. 功能增强

    • 添加摄像头实现视觉循迹
    • 实现多车协同
    • 添加GPS定位功能
  3. 通信扩展

    • WiFi/蓝牙无线控制
    • MQTT物联网接入
    • 手机APP远程监控

9.3 学习资源

官方文档:


如果本教程对你有帮助,欢迎点赞、收藏、关注!

相关推荐
可乐鸡翅好好吃2 小时前
BLE服务和Freertos的任务(Task)、函数有什么区别
网络·单片机·嵌入式硬件
落羽的落羽3 小时前
【Linux系统】入门线程:线程介绍与线程控制
linux·服务器·c++·人工智能·stm32·单片机·机器学习
t1987512812 小时前
STM32通过SPI读取磁编码器AS5047P获取电机角度信息
stm32·单片机·嵌入式硬件
学嵌入式的小杨同学12 小时前
STM32 进阶封神之路(三十二):SPI 通信深度实战 —— 硬件 SPI 驱动 W25Q64 闪存(底层时序 + 寄存器配置 + 读写封装)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构
不做无法实现的梦~12 小时前
clion配置stm32(调试,烧录的详细教程)
stm32·单片机·嵌入式硬件
笨笨饿14 小时前
20_Git 仓库使用手册 - 初学者指南
c语言·开发语言·嵌入式硬件·mcu·学习
freshman_y15 小时前
STM32工程模板如何配置
stm32·单片机·嵌入式硬件
v先v关v住v获v取17 小时前
风电机变桨系统8张cad+设计说明书+三维图
科技·单片机·51单片机
如愿小李19 小时前
基于STM32的智能水质监测系统
stm32·单片机·嵌入式硬件