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 项目文件结构](#4.1 项目文件结构)
      • [4.2 系统配置文件](#4.2 系统配置文件)
      • [4.3 超声波测距驱动(输入捕获)](#4.3 超声波测距驱动(输入捕获))
      • [4.4 WS2812B LED灯带驱动](#4.4 WS2812B LED灯带驱动)
      • [4.5 车位状态管理](#4.5 车位状态管理)
      • [4.6 主程序](#4.6 主程序)
    • 五、故障排查与问题解决
    • 六、测试验证
      • [6.1 功能测试](#6.1 功能测试)
      • [6.2 现场部署测试](#6.2 现场部署测试)
    • 七、总结
      • [7.1 核心知识点回顾](#7.1 核心知识点回顾)
      • [7.2 扩展方向](#7.2 扩展方向)
      • [7.3 学习资源](#7.3 学习资源)

一、前言

1.1 技术背景

随着城市汽车保有量持续增长,停车难问题日益突出。传统停车场存在找车位难、停车效率低、管理成本高等问题。智能停车场车位引导系统通过实时检测车位状态、动态引导车辆停放,能够显著提升停车场使用效率,改善用户停车体验。

STM32F103系列微控制器凭借其高性能、低功耗、丰富外设等特点,成为智能停车场控制系统的理想选择。本系统采用超声波测距技术检测车位状态,结合LED指示灯和显示屏实现车位引导。

1.2 应用场景

本系统适用于以下场景:

  • 商业综合体地下停车场
  • 住宅小区停车场
  • 机场/车站停车场
  • 医院/学校公共停车场

1.3 读者收获

完成本教程后,你将能够:

  • 掌握STM32F103的GPIO、TIM、USART、SPI等外设使用
  • 理解超声波测距原理和实现方法
  • 实现车位状态检测与LED指示控制
  • 掌握多节点通信与数据汇总技术
  • 完成一个完整的嵌入式物联网项目

1.4 技术栈

硬件平台:

  • 主控芯片:STM32F103C8T6(Cortex-M3,72MHz)
  • 测距模块:HC-SR04超声波传感器
  • 显示模块:OLED 128x64(I2C接口)
  • 指示模块:WS2812B全彩LED灯带
  • 通信模块:RS485总线 / CAN总线
  • 网关模块:ESP8266 WiFi模块
  • 电源模块:5V/3.3V稳压电源

软件工具:

  • 开发环境:Keil MDK-ARM 5 / STM32CubeIDE
  • 固件库:STM32标准外设库 / HAL库
  • 编程语言:C语言
  • 调试工具:ST-Link V2

二、环境准备

2.1 硬件准备

核心控制器(每个车位一个):

  • STM32F103C8T6最小系统板 × N(车位数量)
  • ST-Link V2下载器 × 1

传感器模块:

  • HC-SR04超声波测距模块 × N
  • 光敏电阻模块(检测车灯)× N(可选)

指示与显示模块:

  • WS2812B LED灯带(每车位1-3个LED)
  • 0.96寸OLED显示屏(入口引导屏)
  • 8x8 LED点阵屏(区域引导屏,可选)

通信模块:

  • RS485转TTL模块 × N
  • CAN总线收发器TJA1050 × N(可选)
  • ESP8266-01S WiFi模块 × 1(网关)

其他配件:

  • 杜邦线若干
  • 5V/10A电源适配器 × 1
  • 面包板/洞洞板
  • 防水外壳(IP65)

2.2 软件安装

Keil MDK-ARM 安装:

  1. 下载Keil MDK-ARM 5.38版本
  2. 安装STM32F1系列Device Family Pack
  3. 配置调试器为ST-Link

串口调试工具:

  • 串口调试助手(用于查看日志)
  • Modbus Poll(用于测试RS485通信)

2.3 开发环境验证

验证步骤:

  1. 创建新工程,选择STM32F103C8
  2. 编写简单的GPIO闪烁程序
  3. 编译并下载到开发板
  4. 确认LED正常闪烁

三、系统架构设计

3.1 系统总体架构

入口引导
区域控制器2
区域控制器1
总线层
网关层
ESP8266 WiFi模块
云服务器/手机APP
RS485总线
CAN总线
STM32主控
超声波传感器1
超声波传感器2
超声波传感器3
WS2812B灯带
区域显示屏
STM32主控
超声波传感器4
超声波传感器5
超声波传感器6
WS2812B灯带
区域显示屏
入口显示屏
车位统计屏

3.2 车位检测节点硬件连接

STM32F103引脚分配(单节点):

功能模块 引脚分配 说明
HC-SR04_TRIG PA0 超声波触发
HC-SR04_ECHO PA1 超声波回波(TIM2_CH2输入捕获)
WS2812B_DATA PA8 LED灯带数据(TIM1_PWM+DMA)
RS485_TX PA9(USART1_TX) 总线发送
RS485_RX PA10(USART1_RX) 总线接收
RS485_DE PB0 收发使能控制
OLED_SDA PB7(I2C1_SDA) 显示屏数据
OLED_SCL PB6(I2C1_SCL) 显示屏时钟
STATUS_LED PB12 状态指示灯

3.3 软件架构设计

分层架构:
驱动层
中间层
应用层
车位状态管理
LED灯效控制
通信协议处理
显示界面更新
超声波测距服务
车位状态机
Modbus协议栈
灯效动画引擎
HC-SR04驱动
WS2812B驱动
RS485驱动
OLED驱动
TIM输入捕获
DMA控制器


四、核心代码实现

4.1 项目文件结构

本教程将创建以下代码文件:

复制代码
Parking_Guide_System/
├── main.c                        # 主程序入口
├── system_config.h               # 系统配置头文件
├── hardware/
│   ├── tim_ic_driver.c/h         # 定时器输入捕获驱动(超声波)
│   ├── tim_pwm_dma_driver.c/h    # PWM+DMA驱动(WS2812B)
│   ├── usart_rs485_driver.c/h    # RS485通信驱动
│   ├── i2c_oled_driver.c/h       # I2C驱动(OLED)
│   └── dma_driver.c/h            # DMA控制器驱动
├── modules/
│   ├── hcsr04_sensor.c/h         # HC-SR04超声波模块
│   ├── ws2812b_led.c/h           # WS2812B灯带控制模块
│   ├── parking_slot.c/h          # 车位状态管理模块
│   ├── modbus_slave.c/h          # Modbus从机协议
│   └── oled_ui.c/h               # OLED界面显示模块
├── algorithm/
│   └── distance_filter.c/h       # 距离滤波算法
└── utils/
    ├── delay.c/h
    └── buffer.c/h                # 环形缓冲区

4.2 系统配置文件

📄 创建文件:system_config.h

c 复制代码
/**
 * @file system_config.h
 * @brief 智能停车场系统配置文件
 */

#ifndef __SYSTEM_CONFIG_H
#define __SYSTEM_CONFIG_H

#include "stm32f10x.h"

/* ==================== 硬件引脚定义 ==================== */

// HC-SR04超声波传感器
#define HCSR04_TRIG_PORT    GPIOA
#define HCSR04_TRIG_PIN     GPIO_Pin_0
#define HCSR04_ECHO_PORT    GPIOA
#define HCSR04_ECHO_PIN     GPIO_Pin_1
#define HCSR04_TIM          TIM2
#define HCSR04_TIM_CH       TIM_Channel_2
#define HCSR04_TIM_IRQn     TIM2_IRQn

// WS2812B LED灯带
#define WS2812B_PORT        GPIOA
#define WS2812B_PIN         GPIO_Pin_8
#define WS2812B_TIM         TIM1
#define WS2812B_TIM_CH      TIM_Channel_1
#define WS2812B_DMA_CH      DMA1_Channel2

// RS485通信
#define RS485_USART         USART1
#define RS485_TX_PORT       GPIOA
#define RS485_TX_PIN        GPIO_Pin_9
#define RS485_RX_PORT       GPIOA
#define RS485_RX_PIN        GPIO_Pin_10
#define RS485_DE_PORT       GPIOB
#define RS485_DE_PIN        GPIO_Pin_0

// OLED显示屏
#define OLED_SDA_PORT       GPIOB
#define OLED_SDA_PIN        GPIO_Pin_7
#define OLED_SCL_PORT       GPIOB
#define OLED_SCL_PIN        GPIO_Pin_6

// 状态LED
#define STATUS_LED_PORT     GPIOB
#define STATUS_LED_PIN      GPIO_Pin_12

/* ==================== 系统参数配置 ==================== */

// 车位检测参数
#define PARKING_SLOT_COUNT      1           // 本节点管理的车位数量
#define ULTRASONIC_MAX_DIST     400         // 最大测量距离(cm)
#define ULTRASONIC_MIN_DIST     2           // 最小测量距离(cm)
#define CAR_DETECT_THRESHOLD    150         // 车辆检测阈值(cm)
#define CAR_LEAVE_THRESHOLD     180         // 车辆离开阈值(cm)
#define DETECT_DEBOUNCE_TIME    3000        // 检测消抖时间(ms)

// LED灯带参数
#define LED_COUNT               3           // 每个车位LED数量
#define LED_BRIGHTNESS_MAX      255         // 最大亮度
#define LED_COLOR_FREE          0x00FF00    // 绿色-空闲
#define LED_COLOR_OCCUPIED      0xFF0000    // 红色-占用
#define LED_COLOR_RESERVED      0xFFFF00    // 黄色-预留
#define LED_COLOR_FAULT         0x0000FF    // 蓝色-故障

// 通信参数
#define RS485_BAUDRATE          9600
#define MODBUS_SLAVE_ADDR       0x01        // Modbus从机地址
#define COMM_TIMEOUT_MS         500

// 系统参数
#define SENSOR_SAMPLE_PERIOD    200         // 传感器采样周期(ms)
#define DISPLAY_UPDATE_PERIOD   500         // 显示更新周期(ms)
#define HEARTBEAT_PERIOD        1000        // 心跳周期(ms)

/* ==================== 数据结构定义 ==================== */

/**
 * @brief 车位状态枚举
 */
typedef enum {
    SLOT_STATE_UNKNOWN = 0,     // 未知状态
    SLOT_STATE_FREE,            // 空闲
    SLOT_STATE_OCCUPIED,        // 占用
    SLOT_STATE_RESERVED,        // 预留
    SLOT_STATE_FAULT            // 故障
} SlotState_t;

/**
 * @brief 车位信息结构体
 */
typedef struct {
    uint8_t slot_id;                // 车位编号
    SlotState_t state;              // 当前状态
    SlotState_t last_state;         // 上次状态
    uint16_t distance_cm;           // 当前距离(cm)
    uint16_t distance_filtered;     // 滤波后距离
    uint32_t state_change_time;     // 状态改变时间
    uint32_t occupied_duration;     // 占用时长(s)
    uint8_t is_valid;               // 数据是否有效
} ParkingSlot_t;

/**
 * @brief 系统运行状态
 */
typedef struct {
    ParkingSlot_t slots[PARKING_SLOT_COUNT];
    uint8_t node_id;                // 节点ID
    uint32_t system_tick;           // 系统滴答
    uint8_t comm_status;            // 通信状态
    uint8_t sensor_status;          // 传感器状态
} SystemContext_t;

/* ==================== 全局变量 ==================== */

extern SystemContext_t g_system;
extern volatile uint32_t g_tick_ms;

/* ==================== 函数声明 ==================== */

void System_Init(void);
void System_Tick_Handler(void);
uint32_t Get_Tick(void);
void Delay_ms(uint32_t ms);

#endif /* __SYSTEM_CONFIG_H */

4.3 超声波测距驱动(输入捕获)

📄 创建文件:hardware/tim_ic_driver.c

c 复制代码
/**
 * @file tim_ic_driver.c
 * @brief 定时器输入捕获驱动
 * @details 使用TIM2_CH2输入捕获测量HC-SR04回波时间
 */

#include "tim_ic_driver.h"

// 输入捕获状态
static volatile uint8_t s_capture_state = 0;    // 捕获状态
static volatile uint16_t s_capture_value = 0;   // 捕获值
static volatile uint32_t s_high_time_us = 0;    // 高电平时间(us)

/**
 * @brief 定时器2输入捕获初始化
 * @details 配置PA1为TIM2_CH2输入捕获,用于测量超声波回波时间
 */
void TIM2_IC_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_ICInitTypeDef TIM_ICInitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 配置PA1为浮空输入
    GPIO_InitStruct.GPIO_Pin = HCSR04_ECHO_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(HCSR04_ECHO_PORT, &GPIO_InitStruct);
    
    // 定时器基础配置
    // 72MHz / 72 = 1MHz,计数周期1us
    TIM_TimeBaseStruct.TIM_Period = 0xFFFF;           // 最大计数
    TIM_TimeBaseStruct.TIM_Prescaler = 72 - 1;        // 1MHz
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
    
    // 输入捕获配置
    TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
    TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;    // 上升沿捕获
    TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStruct.TIM_ICFilter = 0x00;
    TIM_ICInit(TIM2, &TIM_ICInitStruct);
    
    // 配置中断
    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    
    // 使能捕获中断
    TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE);
    
    // 使能定时器
    TIM_Cmd(TIM2, ENABLE);
}

/**
 * @brief 定时器2中断服务函数
 * @details 处理输入捕获中断,计算高电平时间
 */
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
        
        if (s_capture_state == 0) {
            // 第一次捕获(上升沿)
            s_capture_value = TIM_GetCapture2(TIM2);
            s_capture_state = 1;
            
            // 切换为下降沿捕获
            TIM_OC2PolarityConfig(TIM2, TIM_ICPolarity_Falling);
            TIM_SetCounter(TIM2, 0);  // 清零计数器
            
        } else if (s_capture_state == 1) {
            // 第二次捕获(下降沿)
            uint16_t capture_value = TIM_GetCapture2(TIM2);
            s_high_time_us = capture_value;  // 高电平时间
            s_capture_state = 2;  // 捕获完成
            
            // 切换回上升沿捕获
            TIM_OC2PolarityConfig(TIM2, TIM_ICPolarity_Rising);
        }
    }
}

/**
 * @brief 获取捕获的高电平时间
 * @return 高电平时间(微秒)
 */
uint32_t TIM2_IC_Get_High_Time(void)
{
    return s_high_time_us;
}

/**
 * @brief 检查捕获是否完成
 * @return 0=未完成,1=已完成
 */
uint8_t TIM2_IC_Is_Captured(void)
{
    if (s_capture_state == 2) {
        s_capture_state = 0;  // 重置状态
        return 1;
    }
    return 0;
}

/**
 * @brief 重置捕获状态
 */
void TIM2_IC_Reset(void)
{
    s_capture_state = 0;
    s_high_time_us = 0;
}

📄 创建文件:modules/hcsr04_sensor.c

c 复制代码
/**
 * @file hcsr04_sensor.c
 * @brief HC-SR04超声波传感器驱动
 */

#include "hcsr04_sensor.h"
#include "tim_ic_driver.h"

/**
 * @brief HC-SR04初始化
 */
void HCSR04_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    
    // 使能GPIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置TRIG引脚为推挽输出
    GPIO_InitStruct.GPIO_Pin = HCSR04_TRIG_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(HCSR04_TRIG_PORT, &GPIO_InitStruct);
    
    // 初始状态拉低
    GPIO_ResetBits(HCSR04_TRIG_PORT, HCSR04_TRIG_PIN);
    
    // 初始化输入捕获定时器
    TIM2_IC_Init();
}

/**
 * @brief 触发超声波测量
 */
void HCSR04_Trigger(void)
{
    // 发送至少10us的高电平脉冲
    GPIO_SetBits(HCSR04_TRIG_PORT, HCSR04_TRIG_PIN);
    Delay_us(15);
    GPIO_ResetBits(HCSR04_TRIG_PORT, HCSR04_TRIG_PIN);
    
    // 重置捕获状态
    TIM2_IC_Reset();
}

/**
 * @brief 获取距离测量值
 * @param timeout_ms 超时时间(毫秒)
 * @return 距离值(厘米),-1表示超时或错误
 */
int16_t HCSR04_Get_Distance(uint32_t timeout_ms)
{
    uint32_t start_time = Get_Tick();
    
    // 触发测量
    HCSR04_Trigger();
    
    // 等待捕获完成或超时
    while (!TIM2_IC_Is_Captured()) {
        if ((Get_Tick() - start_time) > timeout_ms) {
            return -1;  // 超时
        }
    }
    
    // 计算距离:距离 = 高电平时间 * 声速 / 2
    // 声速340m/s = 0.034cm/us
    // 距离(cm) = 高电平时间(us) * 0.034 / 2 = 高电平时间 / 58
    uint32_t high_time = TIM2_IC_Get_High_Time();
    
    // 检查有效性
    if (high_time > 38000) {  // 超过38ms表示无回波
        return -1;
    }
    
    uint16_t distance = (uint16_t)(high_time / 58);
    
    // 限制范围
    if (distance > ULTRASONIC_MAX_DIST) {
        distance = ULTRASONIC_MAX_DIST;
    }
    
    return distance;
}

/**
 * @brief 多次采样取平均值
 * @param times 采样次数
 * @return 平均距离(厘米)
 */
int16_t HCSR04_Get_Average_Distance(uint8_t times)
{
    int32_t sum = 0;
    uint8_t valid_count = 0;
    int16_t dist;
    uint8_t i;
    
    for (i = 0; i < times; i++) {
        dist = HCSR04_Get_Distance(50);
        if (dist > 0) {
            sum += dist;
            valid_count++;
        }
        Delay_ms(30);  // 测量间隔至少60ms
    }
    
    if (valid_count == 0) {
        return -1;
    }
    
    return (int16_t)(sum / valid_count);
}

4.4 WS2812B LED灯带驱动

📄 创建文件:hardware/tim_pwm_dma_driver.c

c 复制代码
/**
 * @file tim_pwm_dma_driver.c
 * @brief TIM1 PWM + DMA驱动WS2812B
 * @details 使用PWM+DMA方式精确控制WS2812B时序
 */

#include "tim_pwm_dma_driver.h"

// DMA缓冲区大小:每个LED需要24bit数据,每bit用3个PWM周期表示
// 实际使用1.25us/bit,800Kbps
#define PWM_FREQ_HZ         800000      // 800KHz
#define PWM_PERIOD          (SystemCoreClock / PWM_FREQ_HZ)  // 90
#define PWM_HIGH_DUTY       60          // 0.8us高电平 (60/90 * 1.25us)
#define PWM_LOW_DUTY        30          // 0.4us高电平 (30/90 * 1.25us)
#define PWM_RESET_DUTY      0           // 低电平

// LED数据缓冲区(每个LED 24bit,GRB格式)
static uint8_t s_led_buffer[LED_COUNT][3];  // GRB格式
static uint16_t s_pwm_buffer[LED_COUNT * 24 + 50];  // +50用于RESET信号

/**
 * @brief TIM1 PWM + DMA初始化
 */
void TIM1_PWM_DMA_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    TIM_OCInitTypeDef TIM_OCInitStruct;
    DMA_InitTypeDef DMA_InitStruct;
    
    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1 | 
                           RCC_APB2Periph_AFIO, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    // 配置PA8为复用推挽输出
    GPIO_InitStruct.GPIO_Pin = WS2812B_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(WS2812B_PORT, &GPIO_InitStruct);
    
    // TIM1基础配置
    TIM_TimeBaseStruct.TIM_Period = PWM_PERIOD - 1;      // 90-1 = 89
    TIM_TimeBaseStruct.TIM_Prescaler = 0;                // 不分频
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);
    
    // PWM模式配置
    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse = 0;
    TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM1, &TIM_OCInitStruct);
    
    TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
    TIM_ARRPreloadConfig(TIM1, ENABLE);
    
    // DMA配置
    DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&TIM1->CCR1;
    DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)s_pwm_buffer;
    DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStruct.DMA_BufferSize = 0;  // 动态设置
    DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStruct.DMA_Priority = DMA_Priority_High;
    DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(WS2812B_DMA_CH, &DMA_InitStruct);
    
    // 使能TIM1 DMA请求
    TIM_DMACmd(TIM1, TIM_DMA_CC1, ENABLE);
    
    // 使能TIM1主输出(高级定时器必需)
    TIM_CtrlPWMOutputs(TIM1, ENABLE);
    
    // 使能定时器
    TIM_Cmd(TIM1, ENABLE);
}

/**
 * @brief 将LED数据转换为PWM缓冲区
 */
static void WS2812B_Prepare_Data(void)
{
    uint16_t i, j;
    uint16_t pwm_index = 0;
    uint8_t color_byte;
    
    // 转换GRB数据到PWM占空比
    for (i = 0; i < LED_COUNT; i++) {
        // 绿色
        color_byte = s_led_buffer[i][0];
        for (j = 0; j < 8; j++) {
            s_pwm_buffer[pwm_index++] = (color_byte & 0x80) ? PWM_HIGH_DUTY : PWM_LOW_DUTY;
            color_byte <<= 1;
        }
        
        // 红色
        color_byte = s_led_buffer[i][1];
        for (j = 0; j < 8; j++) {
            s_pwm_buffer[pwm_index++] = (color_byte & 0x80) ? PWM_HIGH_DUTY : PWM_LOW_DUTY;
            color_byte <<= 1;
        }
        
        // 蓝色
        color_byte = s_led_buffer[i][2];
        for (j = 0; j < 8; j++) {
            s_pwm_buffer[pwm_index++] = (color_byte & 0x80) ? PWM_HIGH_DUTY : PWM_LOW_DUTY;
            color_byte <<= 1;
        }
    }
    
    // 添加RESET信号(>50us低电平)
    for (i = 0; i < 50; i++) {
        s_pwm_buffer[pwm_index++] = PWM_RESET_DUTY;
    }
}

/**
 * @brief 刷新LED显示
 */
void WS2812B_Refresh(void)
{
    // 准备数据
    WS2812B_Prepare_Data();
    
    // 设置DMA传输长度
    DMA_SetCurrDataCounter(WS2812B_DMA_CH, LED_COUNT * 24 + 50);
    
    // 使能DMA通道
    DMA_Cmd(WS2812B_DMA_CH, ENABLE);
    
    // 等待传输完成
    while (DMA_GetFlagStatus(DMA1_FLAG_TC2) == RESET);
    
    // 清除标志
    DMA_ClearFlag(DMA1_FLAG_TC2);
    DMA_Cmd(WS2812B_DMA_CH, DISABLE);
}

/**
 * @brief 设置单个LED颜色
 * @param index LED索引
 * @param red 红色值(0-255)
 * @param green 绿色值(0-255)
 * @param blue 蓝色值(0-255)
 */
void WS2812B_Set_Color(uint8_t index, uint8_t red, uint8_t green, uint8_t blue)
{
    if (index >= LED_COUNT) return;
    
    s_led_buffer[index][0] = green;  // GRB顺序
    s_led_buffer[index][1] = red;
    s_led_buffer[index][2] = blue;
}

/**
 * @brief 设置所有LED颜色
 */
void WS2812B_Set_All_Color(uint8_t red, uint8_t green, uint8_t blue)
{
    uint8_t i;
    for (i = 0; i < LED_COUNT; i++) {
        WS2812B_Set_Color(i, red, green, blue);
    }
}

/**
 * @brief 关闭所有LED
 */
void WS2812B_Clear(void)
{
    WS2812B_Set_All_Color(0, 0, 0);
    WS2812B_Refresh();
}

4.5 车位状态管理

📄 创建文件:modules/parking_slot.c

c 复制代码
/**
 * @file parking_slot.c
 * @brief 车位状态管理模块
 */

#include "parking_slot.h"

// 车位状态机
static SlotState_t s_slot_state = SLOT_STATE_UNKNOWN;
static uint32_t s_state_enter_time = 0;
static uint16_t s_distance_history[5] = {0};  // 历史距离用于滤波
static uint8_t s_history_index = 0;

/**
 * @brief 初始化车位管理
 */
void Parking_Slot_Init(void)
{
    uint8_t i;
    
    s_slot_state = SLOT_STATE_UNKNOWN;
    s_state_enter_time = 0;
    s_history_index = 0;
    
    for (i = 0; i < 5; i++) {
        s_distance_history[i] = 0;
    }
    
    // 初始化系统上下文
    g_system.node_id = MODBUS_SLAVE_ADDR;
    g_system.system_tick = 0;
    g_system.comm_status = 0;
    g_system.sensor_status = 0;
    
    for (i = 0; i < PARKING_SLOT_COUNT; i++) {
        g_system.slots[i].slot_id = i + 1;
        g_system.slots[i].state = SLOT_STATE_UNKNOWN;
        g_system.slots[i].last_state = SLOT_STATE_UNKNOWN;
        g_system.slots[i].distance_cm = 0;
        g_system.slots[i].distance_filtered = 0;
        g_system.slots[i].state_change_time = 0;
        g_system.slots[i].occupied_duration = 0;
        g_system.slots[i].is_valid = 0;
    }
}

/**
 * @brief 距离中值滤波
 * @param new_distance 新距离值
 * @return 滤波后的距离
 */
static uint16_t Median_Filter(uint16_t new_distance)
{
    uint8_t i, j;
    uint16_t sorted[5];
    uint16_t temp;
    
    // 存入历史缓冲区
    s_distance_history[s_history_index] = new_distance;
    s_history_index = (s_history_index + 1) % 5;
    
    // 复制并排序
    for (i = 0; i < 5; i++) {
        sorted[i] = s_distance_history[i];
    }
    
    // 冒泡排序
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4 - i; j++) {
            if (sorted[j] > sorted[j + 1]) {
                temp = sorted[j];
                sorted[j] = sorted[j + 1];
                sorted[j + 1] = temp;
            }
        }
    }
    
    // 返回中值
    return sorted[2];
}

/**
 * @brief 更新车位状态
 * @param distance_cm 当前距离(cm)
 */
void Parking_Slot_Update(uint16_t distance_cm)
{
    uint32_t current_time = Get_Tick();
    uint8_t i;
    
    // 滤波处理
    uint16_t filtered_dist = Median_Filter(distance_cm);
    
    // 更新系统数据
    for (i = 0; i < PARKING_SLOT_COUNT; i++) {
        g_system.slots[i].distance_cm = distance_cm;
        g_system.slots[i].distance_filtered = filtered_dist;
        g_system.slots[i].is_valid = 1;
    }
    
    // 状态机处理
    switch (s_slot_state) {
        case SLOT_STATE_UNKNOWN:
            // 初始状态,根据距离判断
            if (filtered_dist < CAR_DETECT_THRESHOLD) {
                s_slot_state = SLOT_STATE_OCCUPIED;
                s_state_enter_time = current_time;
            } else {
                s_slot_state = SLOT_STATE_FREE;
                s_state_enter_time = current_time;
            }
            break;
            
        case SLOT_STATE_FREE:
            // 空闲状态,检测是否有车进入
            if (filtered_dist < CAR_DETECT_THRESHOLD) {
                // 检测消抖
                if ((current_time - s_state_enter_time) > DETECT_DEBOUNCE_TIME) {
                    s_slot_state = SLOT_STATE_OCCUPIED;
                    s_state_enter_time = current_time;
                    
                    // 更新系统状态
                    for (i = 0; i < PARKING_SLOT_COUNT; i++) {
                        g_system.slots[i].last_state = g_system.slots[i].state;
                        g_system.slots[i].state = SLOT_STATE_OCCUPIED;
                        g_system.slots[i].state_change_time = current_time;
                    }
                }
            } else {
                // 保持空闲,更新时间
                s_state_enter_time = current_time;
            }
            break;
            
        case SLOT_STATE_OCCUPIED:
            // 占用状态,检测是否离开
            if (filtered_dist > CAR_LEAVE_THRESHOLD) {
                // 离开消抖
                if ((current_time - s_state_enter_time) > DETECT_DEBOUNCE_TIME) {
                    // 计算占用时长
                    uint32_t occupied_time = (current_time - 
                        g_system.slots[0].state_change_time) / 1000;
                    
                    s_slot_state = SLOT_STATE_FREE;
                    s_state_enter_time = current_time;
                    
                    // 更新系统状态
                    for (i = 0; i < PARKING_SLOT_COUNT; i++) {
                        g_system.slots[i].last_state = g_system.slots[i].state;
                        g_system.slots[i].state = SLOT_STATE_FREE;
                        g_system.slots[i].state_change_time = current_time;
                        g_system.slots[i].occupied_duration = occupied_time;
                    }
                }
            } else {
                // 保持占用,更新时间
                s_state_enter_time = current_time;
            }
            break;
            
        default:
            s_slot_state = SLOT_STATE_UNKNOWN;
            break;
    }
}

/**
 * @brief 获取当前车位状态
 * @return 车位状态
 */
SlotState_t Parking_Slot_Get_State(void)
{
    return s_slot_state;
}

/**
 * @brief 更新LED指示灯
 */
void Parking_Slot_Update_LED(void)
{
    uint32_t color;
    
    switch (s_slot_state) {
        case SLOT_STATE_FREE:
            color = LED_COLOR_FREE;
            break;
        case SLOT_STATE_OCCUPIED:
            color = LED_COLOR_OCCUPIED;
            break;
        case SLOT_STATE_RESERVED:
            color = LED_COLOR_RESERVED;
            break;
        case SLOT_STATE_FAULT:
            color = LED_COLOR_FAULT;
            break;
        default:
            color = 0;  // 熄灭
            break;
    }
    
    // 提取RGB分量
    uint8_t r = (color >> 16) & 0xFF;
    uint8_t g = (color >> 8) & 0xFF;
    uint8_t b = color & 0xFF;
    
    // 设置所有LED
    WS2812B_Set_All_Color(r, g, b);
    WS2812B_Refresh();
}

4.6 主程序

📄 创建文件:main.c

c 复制代码
/**
 * @file main.c
 * @brief 智能停车场车位引导系统主程序
 */

#include "system_config.h"
#include "delay.h"
#include "hcsr04_sensor.h"
#include "parking_slot.h"
#include "ws2812b_led.h"

SystemContext_t g_system = {0};

/**
 * @brief 系统初始化
 */
void System_Init(void)
{
    // 初始化系统滴答
    SysTick_Init(SystemCoreClock / 1000);
    
    // 初始化各模块
    HCSR04_Init();
    Parking_Slot_Init();
    TIM1_PWM_DMA_Init();
    
    // 初始LED显示
    WS2812B_Clear();
    
    printf("Parking Guide System Started!\r\n");
}

/**
 * @brief 传感器采样任务
 */
void Task_Sensor_Sample(void)
{
    static uint32_t last_time = 0;
    uint32_t current_time = Get_Tick();
    
    if ((current_time - last_time) >= SENSOR_SAMPLE_PERIOD) {
        last_time = current_time;
        
        // 采集距离
        int16_t distance = HCSR04_Get_Average_Distance(3);
        
        if (distance > 0) {
            // 更新车位状态
            Parking_Slot_Update((uint16_t)distance);
            
            // 打印调试信息
            printf("Distance: %d cm, State: %d\r\n", 
                   distance, Parking_Slot_Get_State());
        } else {
            printf("Sensor error!\r\n");
        }
    }
}

/**
 * @brief LED更新任务
 */
void Task_LED_Update(void)
{
    static uint32_t last_time = 0;
    uint32_t current_time = Get_Tick();
    
    if ((current_time - last_time) >= 100) {  // 100ms更新一次
        last_time = current_time;
        
        // 更新LED显示
        Parking_Slot_Update_LED();
    }
}

/**
 * @brief 主函数
 */
int main(void)
{
    // 系统初始化
    System_Init();
    
    // 主循环
    while (1) {
        // 传感器采样任务
        Task_Sensor_Sample();
        
        // LED更新任务
        Task_LED_Update();
        
        // TODO: 添加通信任务、显示任务等
        
        Delay_ms(10);
    }
}

五、故障排查与问题解决

5.1 超声波测距问题

问题1:测距值跳变或不稳定

错误现象:

  • 距离值频繁跳动
  • 测量值与实际不符

原因分析:

  • 超声波反射干扰
  • 测量角度偏差
  • 未进行滤波处理
  • 电源噪声

解决方案:

方案1:硬件安装优化

复制代码
安装要求:
- 传感器垂直于地面安装
- 距离地面高度2.0-2.5米
- 避免正对金属物体
- 保持传感器表面清洁

方案2:软件滤波算法

c 复制代码
// 限幅平均滤波
#define FILTER_SIZE 5
#define FILTER_LIMIT 20  // 最大偏差限制

uint16_t Filtered_Distance(uint16_t new_value) {
    static uint16_t buffer[FILTER_SIZE] = {0};
    static uint8_t index = 0;
    static uint8_t count = 0;
    
    // 限幅检查
    if (count > 0) {
        uint16_t avg = buffer[(index + FILTER_SIZE - 1) % FILTER_SIZE];
        if (abs(new_value - avg) > FILTER_LIMIT) {
            // 偏差过大,可能是干扰
            return avg;  // 返回上次的值
        }
    }
    
    buffer[index] = new_value;
    index = (index + 1) % FILTER_SIZE;
    if (count < FILTER_SIZE) count++;
    
    // 计算平均值
    uint32_t sum = 0;
    for (int i = 0; i < count; i++) {
        sum += buffer[i];
    }
    return sum / count;
}

问题2:LED灯带显示异常

错误现象:

  • LED颜色不正确
  • LED闪烁或乱闪
  • 部分LED不亮

原因分析:

  • PWM时序不准确
  • 电源电压不足
  • 信号线过长
  • DMA配置错误

解决方案:

方案1:检查电源

复制代码
WS2812B电源要求:
- 电压:5V ± 0.5V
- 电流:每LED最大60mA(全白)
- 3个LED需要至少:3 × 60mA = 180mA
- 建议使用独立5V电源,不要从STM32取电

方案2:信号线优化

复制代码
信号线要求:
- 长度不超过1米
- 使用屏蔽线或双绞线
- 信号线串联300Ω电阻
- 首颗LED电源处并联100uF电容

方案3:时序校准

c 复制代码
// 根据实际示波器测量调整PWM占空比
// WS2812B时序要求:
// T0H = 0.4us ± 0.15us
// T1H = 0.8us ± 0.15us
// T0L = 0.85us ± 0.15us
// T1L = 0.45us ± 0.15us
// RESET > 50us

// 调整PWM占空比
#define PWM_PERIOD      90      // 1.25us @ 72MHz
#define PWM_HIGH_DUTY   58      // 0.8us
#define PWM_LOW_DUTY    28      // 0.4us

5.2 通信问题

问题3:RS485通信失败

错误现象:

  • 无法接收数据
  • 数据乱码
  • 通信不稳定

原因分析:

  • 波特率不匹配
  • 终端电阻未接
  • 收发切换时机错误
  • 总线冲突

解决方案:

方案1:硬件检查

复制代码
RS485总线要求:
- A、B线不要接反
- 总线两端接120Ω终端电阻
- 使用双绞屏蔽线
- 总线长度不超过1200米
- 节点数不超过32个

方案2:收发控制

c 复制代码
// RS485收发切换
#define RS485_TX_EN()   GPIO_SetBits(RS485_DE_PORT, RS485_DE_PIN)
#define RS485_RX_EN()   GPIO_ResetBits(RS485_DE_PORT, RS485_DE_PIN)

void RS485_Send_Data(uint8_t *data, uint16_t len) {
    RS485_TX_EN();  // 切换为发送
    Delay_us(10);   // 等待收发器切换
    
    USART_Send_Data(data, len);
    
    // 等待发送完成
    while (USART_GetFlagStatus(RS485_USART, USART_FLAG_TC) == RESET);
    Delay_us(10);   // 等待最后一位发送完成
    
    RS485_RX_EN();  // 切换回接收
}

六、测试验证

6.1 功能测试

测试1:距离测量精度

c 复制代码
void Test_Distance_Accuracy(void) {
    printf("Distance Test Start\r\n");
    
    // 使用标准距离(50cm、100cm、150cm、200cm)测试
    for (int i = 0; i < 20; i++) {
        int16_t dist = HCSR04_Get_Average_Distance(5);
        printf("Test %d: %d cm\r\n", i + 1, dist);
        Delay_ms(500);
    }
}

测试2:车位状态切换

c 复制代码
void Test_State_Transition(void) {
    printf("State Transition Test\r\n");
    printf("Current State: %d\r\n", Parking_Slot_Get_State());
    
    // 模拟车辆进入
    printf("Simulate car entering...\r\n");
    // 在传感器下方放置障碍物
    Delay_ms(5000);
    printf("State after enter: %d\r\n", Parking_Slot_Get_State());
    
    // 模拟车辆离开
    printf("Simulate car leaving...\r\n");
    // 移除障碍物
    Delay_ms(5000);
    printf("State after leave: %d\r\n", Parking_Slot_Get_State());
}

6.2 现场部署测试

测试场景:

  1. 静态测试:测量空车位距离,记录基准值
  2. 车辆进入测试:不同类型车辆(轿车、SUV)进入,验证检测
  3. 车辆离开测试:车辆离开后,验证状态恢复
  4. 连续运行测试:24小时连续运行,统计误报率

七、总结

7.1 核心知识点回顾

通过本教程,我们学习了:

  1. 超声波测距原理:HC-SR04的工作机制和时序控制
  2. 输入捕获技术:使用TIM输入捕获精确测量脉冲宽度
  3. PWM+DMA控制:高效驱动WS2812B LED灯带
  4. 状态机设计:车位状态的检测与切换逻辑
  5. 多传感器融合:距离滤波和状态消抖处理

7.2 扩展方向

功能扩展:

  • 添加车牌识别摄像头
  • 集成支付系统
  • 手机APP预约车位
  • 反向寻车功能

系统扩展:

  • 多节点组网(CAN总线)
  • 云平台数据管理
  • 大数据分析(停车热力图)

7.3 学习资源

官方文档:

官方GitHub:

相关推荐
WYH2871 小时前
【STM32 串口完全指南】从轮询到中断再到 DMA,一步步教你搞定串口收发!
stm32·单片机·嵌入式硬件
hrw_embedded1 小时前
STM32单片机增加全局内存增大导致ADC数据丢失,明明两个不相干的两个部分,为什么会相互干扰?
stm32·单片机·嵌入式硬件
余生皆假期-2 小时前
YuanHub 源码分析【六】MIT 模式
笔记·单片机·嵌入式硬件
玩转单片机与嵌入式2 小时前
别再只把 MCU 当控制器:新一代芯片正在把 AI 推理搬到设备端
人工智能·单片机·嵌入式硬件
三佛科技-134163842123 小时前
迷你除湿机方案开发,基于FT61E145-TRB单片机方案
单片机·嵌入式硬件·物联网·智能家居
czhaii3 小时前
STC15W408AS单片机不锈钢切割机C语言
单片机·嵌入式硬件
CHINA红旗下3 小时前
如何使用vscode开发STM32
ide·vscode·stm32
嵌入式小杰4 小时前
一阶低通滤波入门教程:从原理到单片机 C 代码实现
c语言·开发语言·stm32·单片机·算法