一、项目简介

智能道闸是智慧交通/智慧停车中最常见的 IoT 终端之一。本实验以 STM32L431VCTx 为主控,运行 Huawei LiteOS 实时操作系统,通过 ESP8285 WiFi 模块 以 MQTT 协议接入华为 OceanConnect IoT 平台(设备接入域名 iot.zj-huawei.com),构成一套"云平台远程下发指令 → 终端执行道闸开/关 → 状态回传平台"的闭环物联网系统。

实验实现的核心功能:

功能说明
云平台远程开/关闸平台下发指令,舵机带动道闸栏杆抬起/落下
自动落杆开闸 5 秒后无车辆通过自动关闸
车牌校验平台下发校验结果,LCD 显示"车牌校验:成功/失败"及车牌号
LCD 状态显示显示网络状态、设备 ID、道闸状态、校验结果、模块信息
二维码展示生成设备二维码与车牌二维码(可扫码跳转 H5 页面)
数据上报与心跳周期 5s 心跳上报,事件触发即时上报

二、系统架构

执行与感知

STM32L431VCTx + Huawei LiteOS

通信层

华为 OceanConnect IoT 平台

MQTT 命令下发

AT+MQTTPUB

串口数据

MQTT Broker
121.36.42.100:1883

设备接入/命令下发/数据查看

ESP8285 WiFi 模块
UART1 115200 AT指令

control_task
优先级0 业务控制

view_task
优先级1 界面显示

OC 通讯协议
cJSON 组包/解包

网络处理
Net_Module_Handle

道闸舵机
TIM1_CH4 PA11 / TIM2_CH1 PA15

2.4寸 LCD 240x320

LED 指示灯 1s闪烁

按键 x4

蜂鸣器

数据流说明:平台下发的 JSON 指令 → ESP8285 串口接收 → _Json_Data_Analyse 解析为自定义十六进制协议 → ptl_Receive_OCData_Handle 分发(道闸指令 / 车牌校验指令)→ 舵机动作 + LCD 刷新;终端侧每 5s(或事件触发)把道闸状态、车位、红外、路灯等数据组包成 JSON 上报平台。


三、硬件平台

3.1 主控芯片 STM32L431VCTx

项目参数
内核ARM Cortex-M4F(FPU)
主频最高 80MHz
Flash256KB
SRAM64KB
开发环境Keil MDK-ARM(STM32L4xx_DFP 2.0.0)
调试下载ST-Link / J-Link

3.2 外设模块清单

模块型号/器件接口用途
道闸电机(舵机)P0080 舵机驱动TIM1_CH4(PA11)、TIM2_CH1(PA15)PWM 控制道闸抬起/落下
WiFi 通信ESP8285UART1 @115200AT 指令 + MQTT 接入云平台
显示屏2.4 寸 TFT-LCDSPI状态/二维码显示(240x320)
按键4 个独立按键GPIOKey1 切换页面、Key3 调试模式等
指示灯LED1GPIO1s 周期闪烁(系统运行指示)
蜂鸣器BEEPGPIO提示音
RFID(扩展)RC522SPI刷卡识别(本场景可扩展)
调试串口USART115200打印调试信息

说明:工程 Hardware/ 目录下还包含 DHT11、BH1750、HC-SR04、MQ-5、SGP30 等大量传感器驱动,说明这套板卡是可复用的通用 IoT 主板,通过 oc_config.h 的场景宏可切换**家居(Home)/工业(Indu)/交通(Tran)/农业(Farm)**四种实训场景。本实验开启的是交通场景:#define ENABLE_TRAN 1。


四、软件架构(Huawei LiteOS 多任务)

4.1 任务划分

任务优先级栈大小周期职责
control_task0(最高)0xB00(约 2.8KB)10ms协议处理、心跳、网络管理、道闸控制、LED
view_task10x300(约 768B)10msLCD 刷新、二维码、按键扫描、错误显示

4.2 main 启动流程

int main(void)
{
    UINT32 uwRet = LOS_OK;
    HAL_Init();                 // 硬件初始化
    sysclkinit();               // 系统时钟初始化
    task_view_init();           // 显示任务初始化(LCD、按键)
    task_control_init();        // 控制任务初始化(GPIO、网络、舵机)

    uwRet = LOS_KernelInit();   // LiteOS 内核初始化

    uwRet = creat_control_task(); // 创建控制任务(优先级0)
    uwRet = creat_view_task();    // 创建显示任务(优先级1)
    LOS_Start();                // 启动内核调度
    while (1) { }
}

4.3 任务间通信

两个任务通过全局结构体 TranView(ViewDataStruct)共享状态,并用 Scene.state / Scene.req_state 实现"状态变化—应答"机制,避免多线程抢占:

typedef struct
{
    uint8_t state;        // 1=状态发生变化
    uint8_t req_state;    // 1=对方已应答
    uint8_t event_sendflag; // 事件触发上报标志
} StatusStruct;

控制任务改状态 → 置 Scene.state=1;显示任务读到变化后刷新 LCD → 置 Scene.req_state=1 应答。同时 event_sendflag 用于触发即时上报,不必等 5s 心跳周期。


五、核心功能实现

5.1 云平台接入:ESP8285 + MQTT

(1)AT 指令接入流程
AT+CWMODE=1                          // 设置 STA 模式
AT+CWJAP="SSID","PASSWORD"           // 连接 WiFi
AT+MQTTUSERCFG=0,1,"clientId","username","password",0,0,""  // MQTT 认证
AT+MQTTCONN=0,"121.36.42.100",1883,0 // 连接 MQTT Broker
AT+MQTTSUB=0,"123",0                 // 订阅平台命令下发主题
AT+MQTTPUBRAW=0,"$oc/devices/xxx/sys/properties/report",len,0,0  // 上报
(2)设备接入认证(HMAC-SHA256)

设备接入需要三要素:产品 ID(PROD_ID)、设备 ID(CLIENT_ID)、设备密钥。MQTT 登录密码由"设备密钥 + 时间戳"做 HMAC-SHA256 计算得到:

char key[64] = TIMESTAMP;      // 时间戳,如 "2023011215"
char data[64] = USER_PSWD;     // 设备密钥
hmac_sha256(key, lenKey, data, lenData, out, &lenOut);  // 输出 32 字节
// 再转成 64 位小写十六进制字符串作为 MQTT 密码

相关宏定义(Components/esp8285/esp8285.h,发布博客时请替换为你自己的平台设备信息):

#define WIFI_SSID    "your_ssid"        // 热点名
#define WIFI_PSWD    "your_password"    // 热点密码
#define PROD_ID      "productId_deviceId"  // 产品ID_设备ID
#define USER_PSWD    "your_device_secret"  // 设备密钥
#define TIMESTAMP    "2023011215"           // 与平台注册时保持一致
#define CLIENT_ID    (PROD_ID"_0_0_"TIMESTAMP)
#define MQTT_PUB_TOPIC  ("$oc/devices/"PROD_ID"/sys/properties/report")
#define MQTT_PUB_RESPONSE_TOPIC ("$oc/devices/"PROD_ID"/sys/commands/response/request_id=")
(3)连接状态机

esp8285.c 用一张 状态-事件迁移表 管理 WiFi/MQTT 连接,代码结构非常清晰:

STATE_RELATION _State_Relation[] = {
    {STATE_OFFLINE        ,START_SET               ,STATE_SET_WIFI_MODE ,_WIFI_Set_Mode},
    {STATE_SET_WIFI_MODE  ,OPT_OK                  ,STATE_WIFI_CONNECT  ,_WIFI_Set_Connect},
    {STATE_WIFI_CONNECT   ,WIFI_COM_OK|OPT_OK      ,STATE_SET_MQTT_INFO ,_MQTT_Set_User_Info},
    {STATE_SET_MQTT_INFO  ,OPT_OK                  ,STATE_MQTT_CONNECT  ,_MQTT_Connect},
    {STATE_MQTT_CONNECT   ,MQTT_COM_OK             ,STATE_CONNECTED_SUB ,_MQTT_Connected_Sub},
    {STATE_CONNECTED_SUB  ,OPT_OK                  ,STATE_CONNECTED_IDLE,NULL},
    {STATE_CONNECTED_IDLE ,MQTT_PUB_ACT            ,STATE_CONNECTED_PUB ,_MQTT_Connected_Pub},
    {STATE_CONNECTED_PUB  ,MQTT_PUB_OK|OPT_OK      ,STATE_CONNECTED_IDLE,NULL},
    {STATE_DROP           ,START_SET               ,STATE_SET_WIFI_MODE ,_WIFI_Set_Mode},
    // ... 异常路径统一回 STATE_DROP 重连
};

串口收到 +MQTTCONNECTED / +MQTTSUB / +MQTTPUB:OK / ERROR / +MQTTDISCONNECTED 等关键字后,投递对应事件驱动状态迁移;掉线后自动复位模块重新入网(看门狗 + 超时重发机制)。

(4)数据上报(cJSON 组包)

终端状态通过 ptl_send_data_wifi 组包成平台规定的 JSON 格式上报(交通场景 service_id="Tran"):

{
  "services": [{
    "service_id": "Tran",
    "properties": {
      "Header": 4,
      "Version": "200",
      "T_Light_Bool_Count": 0,
      "T_Car_Bool_Input": 0,
      "T_Bool_OutPut_switch": 0,
      "T_Road_gate_Bool_switch": 1,
      "T_run_red_light_Bool_Input": 0,
      "T_Light": "12345",
      "Error_msg": 0
    }
  }]
}

其中 Header=0x04 表示交通场景、Version="200" 为固件版本号,T_Road_gate_Bool_switch 即道闸开关状态(1 开 0 关)。

(5)命令接收与解析

平台下发的命令先被 _Json_Data_Analyse 解析,把 JSON 中的控制字段转成十六进制字符串,再交给协议回调函数处理:

// 平台下发 JSON 中的 paras 字段
item = cJSON_GetObjectItem(root, "paras");
// 依次取 Header / T_FunCode / T_Road_gate_Bool_switch /
//       T_Bool_OutPut_switch / T_License_plate_Bool
// 拼接为 databuf(hex 字符串)后:
ptl_Receive_OCData_Handle((char *)databuf);

5.2 道闸控制(舵机 PWM)

道闸由舵机带动,使用 TIM1_CH4(PA11)与 TIM2_CH1(PA15)双通道 PWM 输出,预分频 799、周期 1999,即 50Hz(20ms)标准舵机周期,通过比较值控制转角:

void Set_Servo_PWM(unsigned int degree)
{
    unsigned int pwm = 0;
    switch (degree)
    {
        case DEGREE_MINOR_90: pwm = 50;  break;  // -90°
        case DEGREE_MINOR_45: pwm = 100; break;  // -45°
        case DEGREE_0:        pwm = 150; break;  // 0°
        case DEGREE_45:       pwm = 200; break;  // +45°
        case DEGREE_90:       pwm = 250; break;  // +90°
    }
    __HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_4, pwm);
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, pwm);
}

void Servo_Open(void)  { Set_Servo_PWM(DEGREE_90); }  // 抬杆(开闸)
void Servo_Close(void) { Set_Servo_PWM(DEGREE_0);  }  // 落杆(关闸)

控制任务收到平台开闸指令后的处理(control_task.c):

void gate_deal(uint8_t state)
{
    gate_state = state;
    if (state == 1)
    {
        close_gate_time_cnt = 5;   // 开闸后 5 秒自动关闸
        Servo_Open();
    }
    else if (state == 0)
    {
        Servo_Close();
    }
}

自动关闸在 1s 心跳节拍里完成倒计时:

if (HAL_GetTick() - sys_time > 1000)
{
    sys_time = HAL_GetTick();
    led_toggle(LED1_PORT);               // LED 1s 翻转
    if (close_gate_time_cnt)
    {
        close_gate_time_cnt--;
        if (0 == close_gate_time_cnt)
        {
            gate_deal(0);                // 5s 到,自动落杆
            Scene.state = 1;
            Scene.event_sendflag = 1;    // 触发状态即时上报
            g_pltSendData.dOutput_Gate = 0;
        }
    }
}

5.3 平台指令分发与车牌校验

void ptl_Receive_OCData_Handle(char* data)
{
    char Funcode = *(data + 3);        // 功能码
    if (Funcode == 0x31)               // 0x31:道闸开/关指令
    {
        unsigned char gate_result = *(data + 5) - 0x30;
        gate_deal(gate_result);
        TranView.GateOutput = gate_state + 1;
        g_pltSendData.dOutput_Gate = gate_state;
        if (1 == gate_state)
            printf("** 成功打开道闸\r\n");
        Scene.state = 1;
        Scene.event_sendflag = 1;
    }
    else if (Funcode == 0x33)          // 0x33:车牌校验结果
    {
        unsigned char check_result = *(data + 9) - 0x30;
        TranView.CheckResult = check_result + 1;
        if (1 == check_result)
        {
            printf("** 车牌校验:成功...\r\n");
            printf("** 车牌号:%s\r\n", QR_STR_CARID);
        }
        else
        {
            printf("** 车牌校验:失败...\r\n");
        }
        Scene.state = 1;
    }
}

平台收到指令执行后,终端还会往响应主题回复 {"result_code":0} 确认执行结果(_MQTT_Response),保证命令执行的可靠性。

5.4 LCD 界面显示

2.4 寸 LCD(240x320)首页显示"智能道闸场景实训",实时刷新以下信息:

行内容
STATUS网络状态(Net_View 每 2s 刷新)
ID设备 ID / 平台信息
模块信息各模块状态汇总
状态显示道闸图标 + “道闸:打开 / 道闸:关闭”
车牌校验“车牌校验:成功 / 失败” + 车牌号

核心刷新逻辑(view_task.c):

if (TranView.GateOutput == 2)
{
    LCD_draw_bmp(30, 196, 48, gImage_icon_tran_dz_on);  // 道闸打开图标
    snprintf(Str_Code, sizeof(Str_Code), "道闸:打开");
    Show_Str(..., Str_Code, 16, 1);
}
else
{
    LCD_draw_bmp(30, 196, 48, gImage_icon_tran_dz_off); // 道闸关闭图标
    snprintf(Str_Code, sizeof(Str_Code), "道闸:关闭");
    Show_Str(..., Str_Code, 16, 1);
}

按 Key1 切换显示页,可显示二维码页面;Key3 切换调试打印。

5.5 二维码生成

工程集成了 QRENCODE 二维码生成库,可在 LCD 上生成两种二维码:

  • 设备二维码:https://iot.zj-huawei.com/wx/#/device? + 设备 ID(IMEI),手机扫码可查看设备在线状态与数据;
  • 车牌二维码:https://iot.zj-huawei.com/wx/#/device?carPlate=A123,模拟车辆出入扫码记录。
#define QR_STR_HEAD_CARID  "https://iot.zj-huawei.com/wx/#/device?"
#define QR_STR_CARID       "A123"   // 车牌号(示例)

strcat(strbuffer, QR_STR_HEAD_CARID);
strcat(strbuffer, "carPlate=");
strcat(strbuffer, (char *)QR_STR_CARID);
QR_CODE_Display_Carid((uint8_t *)strbuffer);

5.6 心跳与状态上报

ptl_Heartbeat_Handle 实现 5s 周期心跳,并在 Scene.event_sendflag == 1(有事件发生)时立即上报,兼顾实时性与流量开销:

void ptl_Heartbeat_Handle(void)
{
    static uint32_t time = 0;
    if (!Is_Net_Module_Connected()) return;
    if ((HAL_GetTick() - time > 5000) || (Scene.event_sendflag == 1))
    {
        time = HAL_GetTick();
        g_ptl_SendTack.TackName[g_ptl_SendTack.TackNo] = SendTask1;
        g_ptl_SendTack.TackNo = 1;
    }
}

发送任务还带 1.5s 超时、最多重发 3 次 的可靠传输机制(ptl_Send_TimerOutDeal),超时 3 次后暂停发送并打印 Send timeout 3 times,Stop!。


六、工程目录结构

ZHIoT_ITS_Gate/
├── MDK-ARM/              # Keil 工程(ZHIoT_ITS_Gate.uvprojx)
├── task_user/            # 用户任务:main.c / control_task / view_task / oc_config
├── Src/  Inc/            # 外设初始化(GPIO/USART/SPI/I2C/PWM/RTC...)
├── Components/
│   ├── esp8285/          # WiFi AT 驱动(状态机 + MQTT + HMAC-SHA256 认证)
│   ├── oc/               # OceanConnect 通讯协议(ocprotocol 组包/解包)
│   ├── cJSON/            # JSON 解析库
│   ├── QRENCODE/         # 二维码生成库
│   ├── GUI/  log/        # 图形与日志组件
├── Hardware/             # 传感器/外设驱动(舵机、RC522、LCD、按键、各传感器)
├── Liteos/               # Huawei LiteOS 内核源码
├── OS_CONFIG/            # 内核配置文件
└── Security/             # DES 加解密(可选)

七、实验操作流程

  1. 上电:串口打印"智能道闸场景实训",LCD 显示初始化界面;
  2. 入网:ESP8285 自动执行 AT+RST → 配 WiFi → MQTT 认证 → 订阅成功,LCD 的 STATUS 变为已连接,LED 1s 闪烁;
  3. 云平台开闸:在华为 OceanConnect 平台/调试页面下发"道闸打开"指令(T_Road_gate_Bool_switch=1)→ 舵机转 +90° 抬杆,LCD 显示"道闸:打开",打印 ** 成功打开道闸;
  4. 自动关闸:5 秒倒计时结束,舵机回 0° 落杆,LCD 显示"道闸:关闭",状态自动上报平台;
  5. 车牌校验:平台下发校验结果(T_License_plate_Bool=1)→ LCD 显示"车牌校验:成功"与车牌号 A123,串口打印校验信息;
  6. 二维码:按 Key1 切换到二维码页,手机扫码可访问设备 H5 页面。

八、踩坑与注意事项

  1. 源码编码:工程源码为 GBK/GB2312 编码,用 VS Code / 记事本直接打开中文会乱码,请用 GB2312 编码打开(或转成 UTF-8 后再阅读);
  2. 设备信息:esp8285.h 中的 WiFi 账号密码、PROD_ID、USER_PSWD、TIMESTAMP 必须与你在 OceanConnect 平台注册的设备一致,否则 MQTT 认证失败(错误表现为一直 STATE_DROP 循环重连);
  3. 平台协议字段:上报 JSON 的字段名(如 T_Road_gate_Bool_switch)必须与平台产品模型定义一致,否则平台解析不到数据;
  4. 编译环境:Keil MDK + Keil.STM32L4xx_DFP 2.0.0 设备支持包,芯片选择 STM32L431VCTx;
  5. 舵机供电:舵机启动电流较大,需独立供电或加电容,避免拉低主控电压导致复位;
  6. 串口调试:DEBUG_UART 波特率 115200,可直接在串口助手查看 AT 指令交互过程,方便定位入网问题;
  7. WiFi 环境:实验需要可联网的 2.4G 热点;校园网/5G 频段热点可能连接失败。

九、总结与收获

通过本实验可以完整走通"端 - 管 - 云"物联网开发链路:

  • 端:STM32 裸机驱动(PWM 舵机、LCD、串口)+ LiteOS 多任务调度(优先级、任务间通信);
  • 管:ESP8285 AT 指令、MQTT 协议、HMAC-SHA256 设备认证、状态机驱动的断线重连;
  • 云:OceanConnect 产品/设备模型、JSON 属性上报、命令下发与响应确认。

这套工程的状态机联网框架(_State_Relation 迁移表)、任务间应答机制(Scene.state/req_state)和带超时重发的协议发送队列都是可直接复用到其他物联网项目中的优秀设计,值得反复研读。


更多推荐