文章目录
-
- 一、前言
-
- [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 主程序)
- 五、故障排查与问题解决
-
- [5.1 超声波测距问题](#5.1 超声波测距问题)
- [5.2 通信问题](#5.2 通信问题)
- 六、测试验证
-
- [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 安装:
- 下载Keil MDK-ARM 5.38版本
- 安装STM32F1系列Device Family Pack
- 配置调试器为ST-Link
串口调试工具:
- 串口调试助手(用于查看日志)
- Modbus Poll(用于测试RS485通信)
2.3 开发环境验证
验证步骤:
- 创建新工程,选择STM32F103C8
- 编写简单的GPIO闪烁程序
- 编译并下载到开发板
- 确认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 现场部署测试
测试场景:
- 静态测试:测量空车位距离,记录基准值
- 车辆进入测试:不同类型车辆(轿车、SUV)进入,验证检测
- 车辆离开测试:车辆离开后,验证状态恢复
- 连续运行测试:24小时连续运行,统计误报率
七、总结
7.1 核心知识点回顾
通过本教程,我们学习了:
- 超声波测距原理:HC-SR04的工作机制和时序控制
- 输入捕获技术:使用TIM输入捕获精确测量脉冲宽度
- PWM+DMA控制:高效驱动WS2812B LED灯带
- 状态机设计:车位状态的检测与切换逻辑
- 多传感器融合:距离滤波和状态消抖处理
7.2 扩展方向
功能扩展:
- 添加车牌识别摄像头
- 集成支付系统
- 手机APP预约车位
- 反向寻车功能
系统扩展:
- 多节点组网(CAN总线)
- 云平台数据管理
- 大数据分析(停车热力图)
7.3 学习资源
官方文档:
官方GitHub: