Skip to content

概念先知道

  • OTA(Over-The-Air):不接烧录线,通过网络远程升级固件。物联网设备出厂后靠它修复 bug、更新功能。
  • A/B 分区(双备份):Flash 里有两个固件区(FW A / FW B)。新固件写入非活动区,校验成功后切换活动分区;新固件起不来时 Boot2 自动回滚到旧固件。
  • 分区表(Partition Table):记录每个区域的地址与大小。例程启动时调用 wifi_ota_dump_partition 打印当前分区表,OTA 成功后 age 字段 +1。
  • 版本与哈希校验:例程开启 CONFIG_OTA_VERSION_CHECK(拒绝降级/同版本)并校验 OTA 头 SHA256,损坏文件不会切换分区。
  • 命令https_ota_start <url> [reboot](HTTP/HTTPS)、tcp_ota_start <ip> <port> [reboot](TCP),reboot 默认 1。

例程功能简介

本页对应博流官方 SDK 的 wifi_ota_by_http 例程(examples/wifi/sta/wifi_ota_by_http):

  • 启动时 wifi_ota_dump_partition() 读取并打印 Flash 分区表(pt_table_get_active_partition_from_ram);
  • Wi-Fi 初始化完成后通过 shell 命令触发 OTA;
  • ota_tls_config.c 通过 app_https_ota_fill_config 注入 CA 与客户端证书(HTTPS 双向认证用),https_fota 组件执行下载、校验、写分区;
  • 支持 HTTP、HTTPS、TCP 三种下载通道;升级流程为:读分区表 → 擦除非活动区 → 连服务器 → 校验 OTA 头 → 校验哈希 → 更新分区表(age++)→ 按 reboot 决定是否重启。
  • 同族参考:分区表机制见系统控制栏目分区表页;BLE OTA 的 GenerateOAD.pyexamples/btble/peripheral

操作步骤

1
进入例程目录

在终端进入 SDK 的 OTA 例程目录(前提:环境已按快速开始(Linux)Windows搭好):

cd examples/wifi/sta/wifi_ota_by_http
2
编译工程

统一填写 bl616。例程会把 PROJECT_SDK_VERSION 写进 OTA 头并做版本检查(低于当前版本会被拒绝):

make CHIP=bl616 BOARD=bl616dk PROJECT_SDK_VERSION=2.1.5
3
烧录固件

按住 BOOT 键短按 EN/RST 进入下载模式后烧录:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
准备 OTA 服务器

在电脑上进入存放固件包的目录(含 build/build_out/wifi_ota_bl616.bin.ota),启动 HTTP 服务器:

python3 https.py
5
连接 Wi-Fi 并触发 OTA

串口助手波特率 2000000,连接路由器后执行(reboot 默认 1,升级成功后自动重启):

wifi_sta_connect Your_SSID 12345678
https_ota_start http://192.168.18.125:5000/build/build_out/wifi_ota_bl616.bin.ota
6
运行验证

例程启动时先打印 Flash 分区表(Active PT:...),OTA 过程中下载固件并校验 SHA256,成功后更新分区表(age++)并重启;重启后日志里的分区表 age 增加,说明升级生效。

代码执行流程

HTTP OTA 从启动到升级完成的完整流程如下:

例程调用的 API 介绍

pt_table_get_active_partition_from_ram(pt_table_stuff)

从 RAM 中读取当前生效的分区表,返回活动分区 ID;失败返回 PT_TABLE_ID_INVALID

参数

  • pt_table_stuffpt_table_stuff_config 数组,接收分区表内容

返回值:活动分区 ID;PT_TABLE_ID_INVALID 表示无有效分区表

pt_table_set_flash_operation(erase, write, read)

设置分区表使用的 Flash 读写函数(例程传 bflb_flash_erase/write/read)。

参数

  • erase / write / read:Flash 操作回调

返回值:无

app_https_ota_fill_config(url, config)

ota_tls_config.c)填充 OTA 使用的 TLS 配置:CA 证书、客户端证书与私钥(来自 https_ota_tls_material.h)。

参数

  • url:OTA 固件 URL
  • configstruct https_fota_config

返回值0 成功

https_ota_start / tcp_ota_start(url/ip, port, reboot)

shell 命令,触发 HTTP(S)/TCP 通道的 OTA 下载与升级流程;reboot 控制升级成功后是否自动重启。

参数

  • url:固件 .ota 文件地址(HTTP/HTTPS)
  • ip / port:TCP 服务器地址(TCP 通道)
  • reboot1 自动重启 / 0 不重启

返回值:无(命令)

完整代码

以下为 main.c 完整源码,与官方示例(examples/wifi/sta/wifi_ota_by_http)一致,默认折叠,点击展开:

📜 点击展开 wifi_ota_by_http/main.c 完整代码
c
/****************************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include "FreeRTOS.h"
#include "task.h"

#include <lwip/tcpip.h>

#include "wifi_mgmr_ext.h"

#include "bflb_uart.h"
#include "bflb_flash.h"

#include "rfparam_adapter.h"

#include "board.h"
#include "shell.h"
#include "partition.h"

#ifndef BL602
#include "fhost_api.h"
#include "wifi_mgmr.h"
#endif
#include "async_event.h"
#include "mm.h"

#define DBG_TAG "MAIN"
#include "log.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct bflb_device_s *uart0;

extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);
#ifdef BL602
extern void wifi_task_create(void);
extern int fhost_init(void);
extern int wifi_mgmr_task_start(void);
#endif

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Functions
 ****************************************************************************/

void wifi_start_firmware_task(void *param)
{
    LOG_I("Starting wifi ...\r\n");

    async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);


    wifi_task_create();

    LOG_I("Starting fhost ...\r\n");
    fhost_init();

    vTaskDelete(NULL);
}

void wifi_event_handler(async_input_event_t ev, void *priv)
{
    uint32_t code = ev->code;

    switch (code) {
        case CODE_WIFI_ON_INIT_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_INIT_DONE\r\n", __func__);
            wifi_mgmr_task_start();
        } break;
        case CODE_WIFI_ON_MGMR_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_MGMR_DONE\r\n", __func__);
        } break;
        case CODE_WIFI_ON_SCAN_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_SCAN_DONE\r\n", __func__);
            wifi_mgmr_sta_scanlist();
        } break;
        case CODE_WIFI_ON_CONNECTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_CONNECTED\r\n", __func__);
            void mm_sec_keydump();
            mm_sec_keydump();
        } break;
        #ifdef CODE_WIFI_ON_GOT_IP_ABORT
        case CODE_WIFI_ON_GOT_IP_ABORT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_ABORT\r\n", __func__);
        } break;
        #endif
        #ifdef CODE_WIFI_ON_GOT_IP_TIMEOUT
        case CODE_WIFI_ON_GOT_IP_TIMEOUT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP_TIMEOUT\r\n", __func__);
        } break;
        #endif
        case CODE_WIFI_ON_GOT_IP: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP\r\n", __func__);
            LOG_I("[SYS] Memory left is %d Bytes\r\n", kfree_size(0));
        } break;
        case CODE_WIFI_ON_DISCONNECT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_DISCONNECT\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STARTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STARTED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STOPPED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STOPPED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STA_ADD: {
            LOG_I("[APP] [EVT] [AP] [ADD] %lld\r\n", xTaskGetTickCount());
        } break;
        case CODE_WIFI_ON_AP_STA_DEL: {
            LOG_I("[APP] [EVT] [AP] [DEL] %lld\r\n", xTaskGetTickCount());
        } break;
        default: {
            LOG_I("[APP] [EVT] Unknown code %u \r\n", code);
        }
    }
}

static void wifi_ota_dump_partition()
{
    pt_table_stuff_config pt_table_stuff[2];
    pt_table_id_type active_id;
    pt_table_stuff_config *pt_stuff;

    /* Set flash operation function, read via xip */
    pt_table_set_flash_operation(bflb_flash_erase, bflb_flash_write, bflb_flash_read);

    active_id = pt_table_get_active_partition_from_ram(pt_table_stuff);
    if (PT_TABLE_ID_INVALID == active_id) {
        printf("No valid PT\r\n");
        return;
    }
    printf("Active PT:%d,Age %d\r\n", active_id, pt_table_stuff[active_id].pt_table.age);

    pt_stuff = &pt_table_stuff[active_id];

    printf("======= PtTable_Config @%p=======\r\n", pt_stuff);
    printf("magicCode 0x%08X;", (unsigned int)(pt_stuff->pt_table.magicCode));
    printf(" version 0x%04X;", pt_stuff->pt_table.version);
    printf(" entryCnt %u;", pt_stuff->pt_table.entryCnt);
    printf(" age %lu;", pt_stuff->pt_table.age);
    printf(" crc32 0x%08X\r\n", (unsigned int)pt_stuff->pt_table.crc32);
    printf(" idx  type device active_index    name    address[0]   address[1]   length[0]    length[1]   age \r\n");
    for (uint32_t i = 0; i < pt_stuff->pt_table.entryCnt; i++) {
        printf("[%02d] ", i);
        printf("  %02u", (pt_stuff->pt_entries[i].type));
        printf("     %u", (pt_stuff->pt_entries[i].device));
        printf("        %u", (pt_stuff->pt_entries[i].active_index));
        printf("       %8s", (pt_stuff->pt_entries[i].name));
        printf("   0x%08lx", (pt_stuff->pt_entries[i].start_address[0]));
        printf("   0x%08lx", (pt_stuff->pt_entries[i].start_address[1]));
        printf("   0x%08lx", (pt_stuff->pt_entries[i].max_len[0]));
        printf("   0x%08lx", (pt_stuff->pt_entries[i].max_len[1]));
        printf("   %lu\r\n", (pt_stuff->pt_entries[i].age));
    }
}

int main(void)
{
    board_init();

    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);

    if (0 != rfparam_init(0, NULL, 0)) {
        LOG_I("PHY RF init failed!\r\n");
        return 0;
    }

    LOG_I("PHY RF init success!\r\n");

    wifi_ota_dump_partition();

    tcpip_init(NULL, NULL);
    xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);

    vTaskStartScheduler();

    while (1) {
    }
}

FAQ

下载失败 / 校验不过

确认 https.py 服务器与模组同网段、URL 指向正确的 .ota 文件;文件损坏或版本不高于当前固件时会被拒绝(CONFIG_OTA_VERSION_CHECK);HTTPS 通道需保证证书链与模组内置一致。

升级后没变化 / 版本号没变

重新编译时把 PROJECT_SDK_VERSION 调高(如 2.1.5 → 2.1.6),否则同版本会被跳过;升级成功后重启,启动日志中分区表 age 会 +1,可通过它确认是否真的切换了活动分区。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23