1. 项目概述与设计思路

大家好,今天我想和大家分享一个非常实用的STM32实战项目——智能停车场系统。这个项目是我去年为一个商业停车场做的物联网化改造方案,从传感器选型到云端通信全部自己动手实现,中间踩了不少坑,也积累了很多经验。

智能停车场系统的核心目标很简单:实时监测车位状态、自动控制道闸、并将数据上传到云端管理平台。听起来简单,但实际做起来需要考虑很多细节。比如,如何准确检测车辆?用超声波还是地磁?通信方式选Wi-Fi还是LoRa?云端协议用MQTT还是HTTP?这些都是需要根据实际场景来做权衡的。

我最终选择的方案是:超声波传感器+RFID做本地车辆检测,ESP8266做Wi-Fi通信,LoRa作为备用通信方案,云端采用MQTT协议进行实时数据传输。这个组合在实际运行中表现很稳定,成本也控制得不错。

2. 硬件选型与电路设计

2.1 主控芯片选择

STM32系列是我一直推荐的选择,性价比高,生态完善。对于停车场系统,我建议用STM32F4系列,主频够用,外设丰富,价格也合适。具体型号我用的STM32F407VET6,有足够的GPIO和通信接口,还能跑一些轻量级的算法。

如果预算充足,可以考虑STM32H7系列,性能更强,后期想要加车牌识别或者更复杂的功能都撑得住。不过对于大多数停车场项目,F4系列已经完全够用了。

2.2 传感器选型心得

超声波传感器我用的HC-SR04,便宜又好用,但要注意安装位置和角度。实际测试中发现,如果安装角度不对,很容易误检测。我的经验是:传感器要稍微向下倾斜,距离地面50-60厘米最合适。

RFID读卡器选的MFRC522模块,用来做车辆身份识别。这里有个小技巧:读卡距离可以通过调整天线匹配电路来优化,我一般调到5-8厘米,既保证识别率,又避免误读。

2.3 通信模块选择

Wi-Fi模块用的ESP8266,通过串口和STM32通信。这个模块性价比极高,支持STA和AP模式,固件稳定,AT指令也简单易用。

LoRa模块作为备用通信方案,选的SX1278。虽然传输速率不高,但传输距离远,功耗低,适合做数据备份通道。实际部署时,我在停车场角落装了LoRa网关,确保信号全覆盖。

3. 开发环境搭建

3.1 软件工具准备

我习惯用STM32CubeIDE,免费又好用。配合STM32CubeMX做引脚配置,能节省大量时间。这里分享一个配置技巧:在用CubeMX生成代码时,记得把所有用到的外设初始化代码单独生成到对应的.c/.h文件中,这样后期维护起来更方便。

调试工具首推ST-LINK V2,便宜又稳定。如果要做功耗测试,可以加个电流表,我用的UT61E,精度足够做停车场这种项目的功耗分析了。

3.2 库文件配置

HAL库是必须的,毕竟官方维护,资料也多。FreeRTOS我也用上了,虽然增加了些复杂性,但多任务管理确实方便。比如传感器数据采集、通信、控制逻辑都可以放在不同任务中,互不干扰。

这里有个坑要注意:FreeRTOS的堆栈大小要合理设置,我之前就因为堆栈设小了,系统运行一段时间就死机。建议每个任务至少预留256字,通信任务可以更大些。

4. 传感器数据采集实现

4.1 超声波传感器驱动

超声波传感器的原理很简单,发个触发信号,然后测回波时间。但实际写代码时要注意抗干扰处理。这是我的实现代码:

#define TRIG_PIN GPIO_PIN_0
#define ECHO_PIN GPIO_PIN_1
#define GPIO_PORT GPIOA

void Ultrasonic_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // TRIG引脚配置为输出
    GPIO_InitStruct.Pin = TRIG_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct);
    
    // ECHO引脚配置为输入
    GPIO_InitStruct.Pin = ECHO_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct);
}

float Get_Distance(void) {
    uint32_t start_time, end_time;
    float distance;
    
    // 发送10us高电平脉冲
    HAL_GPIO_WritePin(GPIO_PORT, TRIG_PIN, GPIO_PIN_SET);
    delay_us(10);
    HAL_GPIO_WritePin(GPIO_PORT, TRIG_PIN, GPIO_PIN_RESET);
    
    // 等待回波信号
    while(HAL_GPIO_ReadPin(GPIO_PORT, ECHO_PIN) == GPIO_PIN_RESET);
    start_time = HAL_GetTick();
    
    while(HAL_GPIO_ReadPin(GPIO_PORT, ECHO_PIN) == GPIO_PIN_SET);
    end_time = HAL_GetTick();
    
    // 计算距离(厘米)
    distance = (end_time - start_time) * 0.034 / 2;
    return distance;
}

实际使用中要加滤波算法,我用的滑动平均滤波,取10次测量值求平均,效果不错。

4.2 RFID读卡器集成

RFID读卡器通过SP接口通信,配置时要注意时钟相位和极性的匹配。我遇到过因为SPI模式设错导致读卡失败的情况,调试了好久才发现。

void RFID_Init(void) {
    SPI_HandleTypeDef hspi1;
    
    hspi1.Instance = SPI1;
    hspi1.Init.Mode = SPI_MODE_MASTER;
    hspi1.Init.Direction = SPI_DIRECTION_2LINES;
    hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
    hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
    hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
    hspi1.Init.NSS = SPI_NSS_SOFT;
    hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
    hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
    hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
    hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    HAL_SPI_Init(&hspi1);
}

读卡操作要加超时判断,避免卡死。我一般设置500ms超时,超时后重新初始化读卡器。

5. 数据处理与控制逻辑

5.1 车辆检测算法

单纯的超声波测距容易误检,比如有人走过也会触发。我结合了以下策略来提高准确性:

  • 连续多次检测到距离小于阈值(比如1米)
  • 距离变化规律符合车辆驶入特征
  • 配合RFID卡认证信息
#define VEHICLE_DETECT_THRESHOLD 100.0 // 1米
#define DETECT_CONSISTENT_COUNT 3

typedef struct {
    float distance_buffer[10];
    uint8_t buffer_index;
    uint8_t vehicle_detected;
} VehicleDetect_t;

uint8_t Check_Vehicle(VehicleDetect_t* detect) {
    // 更新距离缓冲区
    detect->distance_buffer[detect->buffer_index] = Get_Distance();
    detect->buffer_index = (detect->buffer_index + 1) % 10;
    
    // 检查最近3次测量
    uint8_t count = 0;
    for(int i = 0; i < 3; i++) {
        int index = (detect->buffer_index - i + 10) % 10;
        if(detect->distance_buffer[index] < VEHICLE_DETECT_THRESHOLD) {
            count++;
        }
    }
    
    return (count >= DETECT_CONSISTENT_COUNT);
}

5.2 道闸控制逻辑

道闸控制要考虑安全性和可靠性。我的实现包括:

  • 开闸前确认车辆已到位
  • 关闸前确认车辆已离开
  • 异常状态自动保护
void Control_Barrier(uint8_t should_open, uint8_t has_rfid) {
    static uint8_t is_open = 0;
    
    if(should_open && has_rfid && !is_open) {
        // 开闸条件:有车、有权限、当前关闭
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
        is_open = 1;
        printf("Barrier opened\r\n");
    } 
    else if(!should_open && is_open) {
        // 关闸条件:无车、当前开启
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
        is_open = 0;
        printf("Barrier closed\r\n");
    }
}

6. 通信模块集成

6.1 Wi-Fi模块配置

ESP8266配置要注意AT指令的超时处理。我封装了一个发送指令的函数,带重试机制:

uint8_t ESP8266_Send_Command(char* cmd, char* expect, uint32_t timeout) {
    uint8_t retry = 3;
    while(retry--) {
        HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000);
        
        uint32_t start = HAL_GetTick();
        char response[256] = {0};
        uint8_t index = 0;
        
        while((HAL_GetTick() - start) < timeout) {
            if(HAL_UART_Receive(&huart2, (uint8_t*)&response[index], 1, 50) == HAL_OK) {
                if(strstr(response, expect) != NULL) {
                    return 1; // 成功
                }
                index = (index + 1) % 255;
            }
        }
        HAL_Delay(100);
    }
    return 0; // 失败
}

连接Wi-Fi时建议保存多个AP配置,自动选择信号最好的连接。

6.2 LoRa模块配置

LoRa配置要注意参数匹配,收发双方必须使用相同的参数:

void LoRa_Init(void) {
    // 设置频率
    LoRa_Write_Register(REG_FRF_MSB, 0xE4); // 434MHz
    LoRa_Write_Register(REG_FRF_MID, 0xC0);
    LoRa_Write_Register(REG_FRF_LSB, 0x00);
    
    // 设置功率
    LoRa_Write_Register(REG_PA_CONFIG, 0xFF);
    
    // 设置扩频因子和带宽
    LoRa_Write_Register(REG_MODEM_CONFIG2, 0x74); // SF7, BW125kHz
}

实际测试中,LoRa在停车场环境能稳定传输200-300米,完全够用。

7. 云端通信实现

7.1 MQTT协议集成

我用的Paho MQTT嵌入式客户端,体积小,功能足够。连接云端时要处理好重连机制:

void MQTT_Connect(void) {
    while(1) {
        if(mqtt_connect(&client, &options) == MQTT_SUCCESS) {
            printf("MQTT connected\r\n");
            break;
        }
        printf("MQTT connect failed, retry in 5s...\r\n");
        HAL_Delay(5000);
    }
}

主题设计要合理,我用了这样的结构:

  • parking/status:发布车位状态
  • parking/command:接收控制命令
  • parking/error:发布错误信息

7.2 数据格式设计

我用JSON格式传输数据,虽然稍微占点带宽,但可读性好,扩展方便:

void Publish_Parking_Data(uint8_t spot_id, uint8_t occupied, float distance) {
    char json[128];
    snprintf(json, sizeof(json),
        "{\"id\":%d,\"status\":%d,\"distance\":%.2f,\"time\":%lu}",
        spot_id, occupied, distance, HAL_GetTick());
    
    mqtt_publish(&client, "parking/status", json, strlen(json), MQTT_QOS1);
}

云端解析起来也很方便,各种语言都有现成的JSON库。

8. 电源管理与低功耗设计

停车场系统通常要7x24小时运行,功耗很重要。我做了这些优化:

  • 空闲时进入Stop模式
  • 传感器定时唤醒采集
  • 通信模块不用时断电
void Enter_Low_Power_Mode(void) {
    // 关闭外设时钟
    __HAL_RCC_GPIOA_CLK_DISABLE();
    __HAL_RCC_GPIOB_CLK_DISABLE();
    
    // 设置唤醒源
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0x1000, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
    
    // 进入Stop模式
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    
    // 唤醒后重新初始化
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
}

实测下来,整个系统待机功耗不到10mA,用太阳能板供电都能撑得住。

9. 现场部署与调试

9.1 安装注意事项

超声波传感器安装要避开金属物体,否则反射干扰很严重。我建议先用示波器看看回波信号质量,再固定安装位置。

通信天线要远离金属结构,最好引出到室外。我在一个地下停车场项目中就因为天线位置没选好,信号质量很差,后来重新布设了天线才好。

9.2 现场调试技巧

带个便携式示波器很有用,可以看传感器信号和通信波形。我用的DSO150,便宜又便携。

准备个调试终端软件,比如SecureCRT或者Putty,同时监控日志输出和发送调试命令。

10. 常见问题解决

10.1 传感器误检测

环境干扰是最大问题。我的解决办法:

  • 加软件滤波算法
  • 调整检测阈值
  • 增加检测一致性判断

10.2 通信不稳定

Wi-Fi信号不好的地方可以考虑用LoRa做补充。我还加了数据缓存机制,网络中断时先存本地,恢复后再上传。

10.3 电源干扰

电机启停会对电源造成干扰。建议道闸电机电源和控制系统电源分开,加磁珠和电容滤波。

我在电源输入端加了TVS管和稳压电路,有效抑制了电压波动。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐