参考链接:https://www.cnblogs.com/HW-liu/p/17480372.html
ncs文档:Technical Documentation
简介
本文在periphal_uart例程的基础上添加OTA配置。
环境
1、zephyr v3.5.99
2、nrf connect sdk v2.6.1
配置步骤
1、添加config
MCUboot相关config
# #~~~~~~~~MCUboot加入~~~~~~~~~~~~
# Enable MCUmgr and dependencies.
CONFIG_NET_BUF=y
CONFIG_ZCBOR=y
CONFIG_CRC=y
CONFIG_MCUMGR=y
CONFIG_STREAM_FLASH=y
# Enable statistics and statistic names.
CONFIG_STATS=y
CONFIG_STATS_NAMES=y
#确保生成与MCUboot兼容的二进制文件
CONFIG_BOOTLOADER_MCUBOOT=y
# Enable most core commands.
CONFIG_IMG_MANAGER=y
CONFIG_MCUMGR_GRP_IMG=y
CONFIG_MCUMGR_GRP_OS=y
CONFIG_MCUMGR_GRP_STAT=y
# #~~~~~~~~MCUboot加入结束~~~~~~~~~~~~
蓝牙OTA相关config
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
# Allow for large Bluetooth data packets.
CONFIG_BT_L2CAP_TX_MTU=498
CONFIG_BT_BUF_ACL_RX_SIZE=502
CONFIG_BT_BUF_ACL_TX_SIZE=502
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
# Enable the Bluetooth mcumgr transport (unauthenticated).
CONFIG_MCUMGR_TRANSPORT_BT=y
CONFIG_MCUMGR_TRANSPORT_BT_AUTHEN=n
CONFIG_MCUMGR_TRANSPORT_BT_CONN_PARAM_CONTROL=y
# Enable the Shell mcumgr transport.
CONFIG_BASE64=y
CONFIG_CRC=y
# CONFIG_SHELL=y #加上会报错,暂时没找到原因
# CONFIG_SHELL_BACKEND_SERIAL=y
# CONFIG_MCUMGR_TRANSPORT_SHELL=y
# Enable the mcumgr Packet Reassembly feature over Bluetooth and its configuration dependencies.
# MCUmgr buffer size is optimized to fit one SMP packet divided into five Bluetooth Write Commands,
# transmitted with the maximum possible MTU value: 498 bytes.
CONFIG_MCUMGR_TRANSPORT_BT_REASSEMBLY=y
CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE=2475
CONFIG_MCUMGR_GRP_OS_MCUMGR_PARAMS=y
CONFIG_MCUMGR_TRANSPORT_WORKQUEUE_STACK_SIZE=4608
# Enable the LittleFS file system.
CONFIG_FILE_SYSTEM=y
CONFIG_FILE_SYSTEM_LITTLEFS=y
# Enable file system commands
CONFIG_MCUMGR_GRP_FS=y
# Enable the storage erase command.
CONFIG_MCUMGR_GRP_ZBASIC=y
CONFIG_MCUMGR_GRP_ZBASIC_STORAGE_ERASE=y
# Disable Bluetooth ping support
CONFIG_BT_CTLR_LE_PING=n
更改栈空间
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
2、添加相关头文件和代码
添加头文件
#include <zephyr/stats/stats.h>
#ifdef CONFIG_MCUMGR_GRP_FS
#include <zephyr/device.h>
#include <zephyr/fs/fs.h>
#include <zephyr/fs/littlefs.h>
#endif
#ifdef CONFIG_MCUMGR_GRP_STAT
#include <zephyr/mgmt/mcumgr/grp/stat_mgmt/stat_mgmt.h>
#endif
添加数据结构,及相应变量
#define STORAGE_PARTITION_LABEL storage_partition
#define STORAGE_PARTITION_ID FIXED_PARTITION_ID(STORAGE_PARTITION_LABEL)
/* Define an example stats group; approximates seconds since boot. */
STATS_SECT_START(smp_svr_stats)
STATS_SECT_ENTRY(ticks)
STATS_SECT_END;
// struct stats_smp_svr_stats {
// struct stats_hdr s_hdr;
// uint32_t ticks;
// };
/* Assign a name to the `ticks` stat. */
STATS_NAME_START(smp_svr_stats)
STATS_NAME(smp_svr_stats, ticks)
STATS_NAME_END(smp_svr_stats);
// static const struct stats_name_map stats_map_smp_svr_stats[] = {
// { offsetof(struct stats_smp_svr_stats, ticks), "ticks" }
// };
/* Define an instance of the stats group. */
STATS_SECT_DECL(smp_svr_stats) smp_svr_stats;
// struct stats_smp_svr_stats smp_svr_stats;
#ifdef CONFIG_MCUMGR_GRP_FS
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(cstorage);
struct fs_mount_t littlefs_mnt = {
.type = FS_LITTLEFS,
.fs_data = &cstorage,
.storage_dev = (void *)STORAGE_PARTITION_ID,
.mnt_point = "/lfs1"
};
#endif
在main函数开头添加初始化fs代码
/* 初始化并注册统计数据结构,用于监控和报告系统运行状态 */
rc = STATS_INIT_AND_REG(smp_svr_stats, STATS_SIZE_32, "smp_svr_stats");
if (rc < 0) {
/* 如果统计数据初始化失败,则记录错误日志 */
LOG_ERR("Error initializing stats system [%d]", rc);
}
/* 注册内置的mcumgr命令处理程序,以便能够通过mcumgr管理设备 */
#ifdef CONFIG_MCUMGR_GRP_FS
/* 尝试挂载littlefs文件系统 */
rc = fs_mount(&littlefs_mnt);
if (rc < 0) {
/* 如果文件系统挂载失败,记录错误日志 */
LOG_ERR("Error mounting littlefs [%d]", rc);
}
#endif
测试结果
其他
1.版本管理
为了增加版本管理,我们需要添加以下config:
# app version
CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="1.0.0+0"
CONFIG_BINDESC=y
CONFIG_BINDESC_DEFINE=y
CONFIG_BINDESC_DEFINE_VERSION=y
CONFIG_BINDESC_APP_VERSION_STRING=y
# KERNEL VERSION
CONFIG_BINDESC_KERNEL_VERSION_STRING=y
# build time
CONFIG_BINDESC_DEFINE_BUILD_TIME=y
CONFIG_BINDESC_BUILD_TIME_ALWAYS_REBUILD=n
CONFIG_BINDESC_BUILD_DATE_TIME_STRING=y
这样我们就可以在VERSION文件中配置我们的版本号
VERSION_MAJOR = 1
VERSION_MINOR = 0
PATCHLEVEL = 1
VERSION_TWEAK = 0
EXTRAVERSION =
在代码中我们可以打印版本号:
printk("Zephyr version: %s\n", BINDESC_GET_STR(kernel_version_string));
printk("App version: %s\n", BINDESC_GET_STR(app_version_string));
printk("Build time: %s\n", BINDESC_GET_STR(build_date_time_string));
2.状态验证
在MCUboot中是根据其定义的一个变量swap_type,其确定了是等待升级还是跳转APP。swap_type的值由三个参数决定,官网上有这样一张图,通过它们不同的组合,导致swap_type有6种不同的值,如下图所示:
BOOT_SWAP_TYPE_NONE (1)
描述:尝试从槽 0 启动固件。这意味着当前没有任何固件更新动作需要执行,系统将尝试从主槽(槽 0)启动。
应用场景:正常启动,没有固件更新请求或需要回滚到旧版本。
BOOT_SWAP_TYPE_TEST (2)
描述:交换到槽 1。这是一个测试启动,系统将尝试从次槽(槽 1)启动固件,通常用于测试新固件的可行性。
应用场景:在固件被最终确认前,暂时从次槽启动以验证其性能和稳定性。
BOOT_SWAP_TYPE_PERM (3)
描述:交换到槽 1,并永久切换到从该槽启动。一旦新固件通过测试,此选项将确认从次槽启动,并更新引导信息以永久从此槽启动。
应用场景:新固件经过充分测试并被认为稳定后,进行永久切换。
BOOT_SWAP_TYPE_REVERT (4)
描述:回滚到备用槽。如果次槽中的新固件在测试期间失败,或者在没有确认的情况下设备重启,系统将尝试回滚到主槽。
应用场景:新固件测试失败,需要恢复到原来的固件版本。
BOOT_SWAP_TYPE_FAIL (5)
描述:交换失败,因为要运行的镜像无效。这表示交换过程中检测到新固件存在问题,无法启动。
应用场景:固件损坏或未通过验证,阻止了启动过程,系统需要采取措施防止损坏的固件启动。
因此我们可以编写简易的更新状态函数:
/*
* @description: 检查更新状态
* @param: 无
* @return: 无
*/
static void check_app_fota_status(void)
{
/**
* 当测试镜像被MCUboot交换到主分区并启动时,
* API mcuboot_swap_type() 将返回 BOOT_SWAP_TYPE_REVERT。这种类型
* 表示测试镜像已成功启动。如果不确认此镜像,
* 它将被交换回次分区,并将原始应用程序镜像恢复到主分区(称为回退)。
*/
const int type = mcuboot_swap_type();
printk("boot swap type: %d\n", type);
switch (type) {
case BOOT_SWAP_TYPE_NONE:
/* 正常重置,没有任何变化,继续正常执行。 */
return;
case BOOT_SWAP_TYPE_TEST:
/* BOOT_SWAP_TYPE_TEST 表示我们已启动一个测试镜像。
我们请求将此镜像固化。 */
boot_request_upgrade(true);
printk("BOOT_SWAP_TYPE_TEST: boot_request_upgrade(true)\n");
sys_reboot(SYS_REBOOT_WARM); // 重启以应用固化升级。
return;
case BOOT_SWAP_TYPE_PERM:
/* BOOT_SWAP_TYPE_PERM 表示当前镜像已永久激活,
且无需进一步操作。 */
return;
case BOOT_SWAP_TYPE_REVERT:
/* 若前一次启动是测试启动且固件尚未确认,则会发生此情况。
这里,我们确认镜像以避免回退到旧镜像。 */
const int ret = boot_write_img_confirmed();
printk("boot_write_img_confirmed() ret:%d\n", ret);
sys_reboot(SYS_REBOOT_WARM); // 重启以完成确认过程。
break;
case BOOT_SWAP_TYPE_FAIL:
/* BOOT_SWAP_TYPE_FAIL 表示尝试启动的镜像无效。 */
printk("Boot failure: invalid image\n");
// 这里可以添加错误处理逻辑。
break;
}
}
3.OTA源码
zephyr\subsys\mgmt\mcumgr\grp\img_mgmt\src\img_mgmt.c:826行
static const struct mgmt_handler img_mgmt_handlers[] = {
[IMG_MGMT_ID_STATE] = {
.mh_read = img_mgmt_state_read,
#ifdef CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP
.mh_write = NULL
#else
.mh_write = img_mgmt_state_write,
#endif
},
[IMG_MGMT_ID_UPLOAD] = {
.mh_read = NULL,
.mh_write = img_mgmt_upload
},
[IMG_MGMT_ID_ERASE] = {
.mh_read = NULL,
.mh_write = img_mgmt_erase
},
};
这段代码定义了一个常量数组 img_mgmt_handlers,它包含了一组处理程序(handlers),每个处理程序都有一个读函数(mh_read)和一个写函数(mh_write)。
IMG_MGMT_ID_STATE: 处理固件状态的读取和写入。根据编译时配置 (CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) 的不同,写函数可能为空。
IMG_MGMT_ID_UPLOAD: 处理固件上传,只有写函数。
IMG_MGMT_ID_ERASE: 处理固件擦除,只有写函数。