T5_3.13.6 附属固件ota问题

Wi-Fi 设备、蜂窝设备、WuKongAI、开发板、TuyaOS 移植等


Post Reply
chen12
Posts: 76

请问下,t5附属固件的ota升级,升级固件能不能先从云端完全下载到设备后,设备再传给待升级的设备进行升级
也就是t5有没有能保存完整固件的分区呢

chen12
Posts: 76

Re: T5_3.13.6 附属固件ota问题

我看t5有一个ota分区,大小为2056K,我的附属固件升级文件大小为1500kb,请问我能先存放在这个分区里面吗,如果可以,我如何操作到这个分区呢,还是只能根据配置的分区首地址读写

愚者千虑必有一得
Posts: 916

Re: T5_3.13.6 附属固件ota问题

您好!针对您的问题逐一解答:

  1. T5 OTA 默认流程:流式边下载边烧录,不支持预下载完整固件

TuyaOS OTA 采用流式升级机制,在 tuya_svc_upgrade.c 中通过 HTTP 分片下载,每片仅 1KB 或 16KB(OTA_DOWMLOAD_BUF_SIZE),每片下载完成后立即回调写入 Flash,不存在完整的固件预缓存区。

  1. T5 Flash 分区布局

T5(BK7258)Flash 分区由芯片 SDK 的 tkl_flash_get_one_type_info() 提供,核心分区:

  • TUYA_FLASH_TYPE_APP — APP 主固件运行区
  • TUYA_FLASH_TYPE_OTA — OTA 缓存/管理区
  • TUYA_FLASH_TYPE_APP_BIN — 固件镜像大小信息区
  • TUYA_FLASH_TYPE_BTL0/BTL1 — 双备份 Bootloader

具体 OTA 分区大小需查看 T5/BK7258 芯片 Flash Layout 文件。

  1. 如何实现「先完整下载再升级」

如果业务场景需要先下载完整固件再升级(如设备转发场景),需要自定义实现:
① 禁用默认 OTA 流程,手动下载完整固件到 OTA 缓存区(需确保分区足够大)
② 下载完成后,通过 tal_ota_start_notify() + tal_ota_data_process() 分段写入目标分区
③ 写入完成后调用 tal_ota_end_notify(TRUE) 触发重启

关键接口参考 components/tal_system/include/tal_ota.h:

Code: Select all

// 查询最大允许固件大小
OPERATE_RET tal_ota_get_ability(UINT32_T *image_size, TUYA_OTA_TYPE_E *type);

// 启动 OTA(可指定目标路径 TUYA_OTA_PATH_AIR/SEC_A/SEC_B)
OPERATE_RET tal_ota_start_notify(UINT32_T image_size, TUYA_OTA_TYPE_E type, TUYA_OTA_PATH_E path);

// 分段写入数据
OPERATE_RET tal_ota_data_process(TUYA_OTA_DATA_T *pack, UINT32_T* remain_len);

// 结束并可选重启
OPERATE_RET tal_ota_end_notify(BOOL_T reset);
  1. 附属固件(Attach OTA)

附属固件通过 DEV_ATTACH_MOD_1 等通道扩展,数据通常通过 UART/总线转发给外部 MCU,下载机制与主固件相同,均为流式。示例代码参考 example_attach_ota.c。

请问您使用附属固件 OTA 的具体场景是什么?是模块内部附属固件升级,还是需要通过 T5 模块转发给其他设备?

chen12
Posts: 76

Re: T5_3.13.6 附属固件ota问题

目前对接了一个蓝牙设备,需要把ota功能做到t5这边,现在想以附属固件升级的形式对接
目前的想法是,能不能先完整将ota固件下载下来,然后再通过串口发给蓝牙设备

愚者千虑必有一得
Posts: 916

Re: T5_3.13.6 附属固件ota问题

看了下您的需求,总结一下:T5 做 OTA 中继,先完整下载固件到本地,再通过串口转发给蓝牙设备。

1. TuyaOS OTA 默认是流式架构,不支持预缓存

TuyaOS OTA()采用的是流式边下载边烧录机制:

  • 云端 CDN 分片下发,每片 1KB 或 16KB
  • 每收到一片,立即调用 写入 Flash
  • 不存在完整的固件预缓存区

2. 附属固件 OTA 的数据流

附属固件(扩展 OTA 通道,channel >= 10)走 → ,数据同样以流式推送给应用层的 回调,每来一片推一片。

文档里有 MCU 串口转发 OTA 的示例代码:

Code: Select all

OPERATE_RET get_file_data_cb(IN CONST FW_UG_S *fw, IN CONST UINT_T total_len,
                             IN CONST UINT_T offset, IN const BYTE_T *data,
                             IN CONST UINT_T len, OUT UINT_T *remain_len,
                             IN PVOID_T pri_data) {
    tkl_uart_write(UART_NUM_0, data, len);  // 每片直接转发
    *remain_len = 0;
    return OPRT_OK;
}

这是每片立即转发的模式,不能满足您「先完整下载再转发」的需求。

3. 如何实现先完整下载再转发

您说的 OTA 分区 2056K 够用(固件 1500KB),可以这样实现:

① 注册一个扩展 OTA 通道(如 channel=10),在 里声明

② 中收到您的 channel 时,调用 并注册一个自定义

③ 在 中,先不转发,把所有数据片缓存到 OTA 分区(通过 + 循环调用 ,实际上 SDK 本身就是这么存的,只是以片为单位自动写入 OTA 分区的连续地址)

④ 下载完成后,从 OTA 分区读取完整固件,再通过 UART 转发给蓝牙设备

关键约束:OTA 分区必须在 T5 芯片内置 Flash 上,TKL 层接口 可以读取 OTA 分区数据。转发给蓝牙设备时,蓝牙芯片的 OTA 协议需要您自行实现(涂鸦蓝牙芯片推荐走 BLE Notify/Indicate 推送固件片)。

4. 整体方案建议

Code: Select all

云端 → T5(Wi-Fi) → 内置Flash OTA分区(2056K) → UART → 蓝牙设备
                        ↑
              tuya_svc_upgrade 写入

优点:T5 只做透传,不关心蓝牙固件内容
注意:蓝牙 OTA 协议需要和蓝牙芯片原厂确认;T5 本身是 Wi-Fi 芯片不含 BLE 能力,靠串口和蓝牙芯片通信

总结:SDK 原生不提供「先完整下载再处理」的开关,但借助 OTA 分区作为缓存区 + 自定义 ,可以实现您的需求。

chen12
Posts: 76

Re: T5_3.13.6 附属固件ota问题

如果我的需求是借助 OTA 分区作为缓存区,能提供一份简单的写入ota分区和从ota分区读取的demo代码吗

愚者千虑必有一得
Posts: 916

您好!针对借助 OTA 分区作为固件缓存区的需求,以下给出完整的操作流程和 demo 代码。

■ 第一步:获取 OTA 分区的起始地址和大小

Code: Select all

#include "tal_flash.h"   // 组件层 Flash API

int get_ota_partition_info(UINT32_T *start_addr, UINT32_T *part_size, UINT32_T *block_size)
{
    TUYA_FLASH_BASE_INFO_T info = {0};
    OPERATE_RET ret;

ret = tal_flash_get_one_type_info(TUYA_FLASH_TYPE_OTA, &info);
if (ret != OPRT_OK || info.partition_num == 0) {
    TAL_PR_ERR("get OTA partition info failed: %d", ret);
    return ret;
}

*start_addr = info.partition[0].start_addr;
*part_size  = info.partition[0].size;
*block_size = info.partition[0].block_size;  // 通常为 4096(4KB sector)

TAL_PR_INFO("OTA partition: start=0x%08X, size=%u KB, block=%u B",
            *start_addr, *part_size / 1024, *block_size);
return OPRT_OK;
}

■ 第二步:写入 OTA 分区(先擦再写)

Flash 写入前必须按 sector(通常 4096B)对齐擦除,否则写入无效:

Code: Select all

#define OTA_SECTOR_SIZE   4096   // T5/BK7258 Flash sector 大小

OPERATE_RET ota_cache_write(UINT32_T ota_start, UINT32_T offset,
                             const UINT8_T *data, UINT32_T len)
{
    UINT32_T abs_addr = ota_start + offset;
    OPERATE_RET ret;

// 写前必须按 4KB 对齐擦除
UINT32_T erase_addr = abs_addr & ~(OTA_SECTOR_SIZE - 1);
UINT32_T erase_size = ((len + OTA_SECTOR_SIZE - 1) / OTA_SECTOR_SIZE) * OTA_SECTOR_SIZE;

ret = tal_flash_erase(erase_addr, erase_size);
if (ret != OPRT_OK) {
    TAL_PR_ERR("flash erase failed at 0x%08X: %d", erase_addr, ret);
    return ret;
}

ret = tal_flash_write(abs_addr, data, len);
if (ret != OPRT_OK) {
    TAL_PR_ERR("flash write failed at 0x%08X: %d", abs_addr, ret);
}
return ret;
}

■ 第三步:从 OTA 分区读取数据

Code: Select all

OPERATE_RET ota_cache_read(UINT32_T ota_start, UINT32_T offset,
                            UINT8_T *buf, UINT32_T len)
{
    return tal_flash_read(ota_start + offset, buf, len);
}

■ 完整使用示例(附属固件 OTA → 缓存到 OTA 分区 → 串口转发蓝牙设备)

Code: Select all

static UINT32_T s_ota_start = 0;
static UINT32_T s_ota_size  = 0;

// 初始化:获取分区信息并整块擦除
void ota_cache_init(void)
{
    UINT32_T block = 0;
    get_ota_partition_info(&s_ota_start, &s_ota_size, &block);
    TAL_PR_INFO("OTA cache: start=0x%08X size=%uKB", s_ota_start, s_ota_size/1024);
    // 整块擦除,后续按偏移直接写无需再擦
    tal_flash_erase(s_ota_start, s_ota_size);
}

// 在 get_file_cb 中逐片写入 OTA 分区
OPERATE_RET get_file_data_cb(IN CONST FW_UG_S *fw, IN CONST UINT_T total_len,
                              IN CONST UINT_T offset, IN const BYTE_T *data,
                              IN CONST UINT_T len, OUT UINT_T *remain)
{
    // 整块擦除后直接写,offset 处无需再对齐擦除
    tal_flash_write(s_ota_start + offset, data, len);
    TAL_PR_DEBUG("OTA cache [%u/%u]", offset + len, total_len);

if (offset + len >= total_len) {
    TAL_PR_INFO("Firmware fully cached (%u bytes), start forwarding...", total_len);
    ota_forward_via_uart(total_len);
}

*remain = 0;
return OPRT_OK;
}

// 从缓存区读出,通过 UART 逐片转发给蓝牙设备
void ota_forward_via_uart(UINT32_T fw_size)
{
    #define UART_CHUNK 512
    UINT8_T buf[UART_CHUNK];
    UINT32_T sent = 0;

while (sent < fw_size) {
    UINT32_T chunk = (fw_size - sent > UART_CHUNK) ? UART_CHUNK : (fw_size - sent);
    tal_flash_read(s_ota_start + sent, buf, chunk);
    // 按您的蓝牙 OTA 协议封包后发送
    // tal_uart_write(TAL_UART_ID_2, buf, chunk);
    sent += chunk;
}
TAL_PR_INFO("OTA forward done: %u bytes", fw_size);
}

■ 关键注意事项

  1. 动态查询分区地址:不要硬编码地址,通过

    Code: Select all

    tal_flash_get_one_type_info(TUYA_FLASH_TYPE_OTA, &info)
    动态获取,不同版本的分区布局可能不同;
  2. 擦除粒度:T5/BK7258 的 Flash sector 为 4KB,整块擦除时 addr 和 size 必须是 4096 的整数倍;
  3. 避免 OTA 分区竞争:TuyaOS 默认的云端 OTA 也使用该分区,缓存期间需确保云端没有下发 OTA 任务,否则双方会互相覆盖;
  4. 头文件:应用层只需

    Code: Select all

    #include "tal_flash.h"
    (组件层接口),不需要直接 include tkl_flash.h。

如有问题欢迎继续追问!

chen12
Posts: 76

Re: T5_3.13.6 附属固件ota问题

如果用tal的接口,我看了sdk里面只有tal_flash.h,并没有tal_flash.c,看不到具体的实现
但是我发现sdk里面有tkl_flash.c有tkl_flash_get_one_type_info接口,但ota的定义为
#define OTA_START 0x7c9000
#define OTA_SIZE (8 * 1024)
看着也不太对?

愚者千虑必有一得
Posts: 916

Re: T5_3.13.6 附属固件ota问题

您好!关于这两个困惑,逐一解释:

■ 1. tal_flash.c 在哪?

tal_flash.c 确实存在,路径为:
components/tal_driver/src/os/tal_flash.c

SDK 发布包通常将 components 组件层预编译为静态库(.a),所以在 SDK 目录里看不到 .c 源文件,只能看到 .h 头文件。但接口是完整可用的,只要 #include "tal_flash.h" 即可直接调用。

tal_flash_get_one_type_info() 内部实现(第133行)如下:

Code: Select all

OPERATE_RET tal_flash_get_one_type_info(TUYA_FLASH_TYPE_E type, TUYA_FLASH_BASE_INFO_T* info)
{
    TKL_FLASH_DESC_T* flash_desc = tkl_flash_desc_get();
    if (flash_desc->get_one_type_info == NULL) {
        return OPRT_NOT_SUPPORTED;
    }
    return flash_desc->get_one_type_info(type, info);
}

它内部会自动转调 T5 平台 TKL 层,您无需直接调用 tkl_ 层函数。

■ 2. #define OTA_START 0x7c9000 / OTA_SIZE (8*1024) 是什么?

您在 tkl_flash.c 里看到的这两个宏是 BK7258 平台层(TKL 层)内部的硬件宏定义,仅供 tkl_flash.c 内部处理分区表时使用,并不是 OTA 存储区的实际容量。

T5 的 OTA 分区实际大小约为 2MB(您之前看到的 2056KB 才是正确的分区大小)。那个 8KB 是 TKL 内部元数据区,不代表整个 OTA 分区。

■ 正确的应用层用法(仅使用 tal_ 层接口):

Code: Select all

#include "tal_flash.h"

void get_ota_partition_info(void)
{
    TUYA_FLASH_BASE_INFO_T info;
    memset(&info, 0, sizeof(info));

if (tal_flash_get_one_type_info(TUYA_FLASH_TYPE_OTA, &info) == OPRT_OK) {
    if (info.partition_num > 0) {
        uint32_t ota_start = info.partition[0].start_addr;
        uint32_t ota_size  = info.partition[0].size;
        TAL_PR_INFO("OTA partition: start=0x%08X, size=%u bytes",
                    ota_start, ota_size);
        // T5 预期输出:size=2097152 (约2MB)
    }
}
}

TUYA_FLASH_TYPE_OTA 是枚举值(值=5),定义在 tuya_cloud_types.h。

函数返回后,info.partition[0].start_addr 才是可以安全读写的 OTA 分区物理地址,info.partition[0].size 才是实际约 2MB 的分区大小。

■ 小结:

  • tal_flash.h 对应的 tal_flash.c 已预编译进 .a,接口完整可用
  • #define OTA_START/OTA_SIZE 是 TKL 平台内部宏,应用层不要直接使用
  • 应用层统一通过 tal_flash_get_one_type_info(TUYA_FLASH_TYPE_OTA, &info) 获取运行时地址
Post Reply