NRF52840 OTA升级

参考链接: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: 处理固件擦除,只有写函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值