feat(app): 使用传感器驱动重构电池和模式切换模块

- 将电池模块从ADC直接采样改为使用sensor子系统,通过battery_sense传感器获取电压
- 将模式切换模块从ADC采样改为使用mode_sense传感器获取模式电压
- 移除GPIO控制的电池使能脚,改用传感器的PM管理机制
- 更新DTS配置,移除大量设备状态设置,添加boot-mode保留内存支持

perf(pm): 调整分区大小以支持单应用引导模式

- 将mcuboot分区从0xc000扩大到0x100,为单应用模式提供更大空间
- 相应调整app分区地址布局,确保内存分配合理
- 移除secondary镜像相关配置,优化flash使用

refactor(boot): 添加MCUBOOT单应用模式配置

- 在sysbuild中启用单应用模式支持
- 为引导加载程序添加保留内存和启动模式配置
- 配置CDC ACM串口用于引导模式通信
This commit is contained in:
2026-03-31 15:09:23 +08:00
parent 82be5cae52
commit 302df0230d
8 changed files with 243 additions and 273 deletions

View File

@@ -2,16 +2,7 @@
/ {
chosen {
zephyr,display = &st7789v3;
};
aliases {
backlight = &backlight;
};
zephyr,user {
vbat-en-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
io-channels = <&adc 5>, <&adc 7>;
zephyr,boot-mode = &boot_mode0;
};
hid_dev_0: hid_dev_0 {
@@ -45,64 +36,12 @@
};
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&gpiote {
status = "okay";
};
&led_0 {
status = "okay";
};
&led_1 {
status = "okay";
};
/* 使能 SAADCmode_switch_module 使用 channel 7 采样模式拨码电压。 */
&adc {
status = "okay";
};
&i2c1 {
&gpregret1 {
status = "okay";
ip5305: pmic@75 {
boot_mode0: boot_mode@0 {
compatible = "zephyr,retention";
status = "okay";
keepalive-interval-ms = <10000>;
reg = <0x0 0x1>;
};
};
&usbd {
status = "okay";
};
qdec: &qdec {
status = "okay";
};
&spi3 {
status = "okay";
};
&mipi_dbi {
status = "okay";
};
&st7789v3 {
status = "okay";
};
&pwm_leds {
status = "okay";
};
&pwm0 {
status = "okay";
};

View File

@@ -1,37 +1,31 @@
mcuboot:
address: 0x0
end_address: 0xc000
end_address: 0x10000
region: flash_primary
size: 0xc000
size: 0x10000
mcuboot_pad:
address: 0xc000
end_address: 0xc200
address: 0x10000
end_address: 0x10200
region: flash_primary
size: 0x200
app:
address: 0xc200
end_address: 0x82000
address: 0x10200
end_address: 0xf8000
region: flash_primary
size: 0x75e00
size: 0xe7e00
mcuboot_primary:
address: 0xc000
end_address: 0x82000
address: 0x10000
end_address: 0xf8000
orig_span: &id001
- mcuboot_pad
- app
region: flash_primary
size: 0x76000
size: 0xe8000
span: *id001
mcuboot_secondary:
address: 0x82000
end_address: 0xf8000
region: flash_primary
size: 0x76000
settings_storage:
address: 0xf8000
end_address: 0x100000

View File

@@ -16,6 +16,10 @@ CONFIG_MCUMGR_GRP_OS=n
CONFIG_IMG_MANAGER=n
CONFIG_STREAM_FLASH=n
CONFIG_RETAINED_MEM=y
CONFIG_RETENTION=y
CONFIG_RETENTION_BOOT_MODE=y
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_SMP=y

View File

@@ -2,9 +2,9 @@
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/power/ip5305.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
@@ -20,11 +20,8 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
#define BATTERY_USER_NODE DT_PATH(zephyr_user)
#define BATTERY_ADC_IO_CH_IDX 1
#define BATTERY_SENSE_NODE DT_NODELABEL(battery_sense)
#define BATTERY_SAMPLE_INTERVAL_MS 1000
#define BATTERY_VDIV_NUM 2
#define BATTERY_VDIV_DEN 1
#define BATTERY_MV_WINDOW_SIZE 10
/*
@@ -35,18 +32,26 @@ LOG_MODULE_REGISTER(MODULE);
#define BATTERY_EMPTY_MV 3300
#define BATTERY_FULL_MV 4100
static const struct adc_dt_spec battery_adc =
ADC_DT_SPEC_GET_BY_IDX(BATTERY_USER_NODE, BATTERY_ADC_IO_CH_IDX);
static const struct device *const ip5305_dev = DEVICE_DT_GET(DT_NODELABEL(ip5305));
static const struct device *const battery_sensor_dev = DEVICE_DT_GET(BATTERY_SENSE_NODE);
/*
* 电池采样使能脚从 zephyr,user/vbat-en-gpios 读取,避免把引脚号硬编码在 C 里。
* 后续板级改脚位只需改 DTS不需要改固件代码。
* 板级电源采样结果:
* - 由 board provider 负责给出“原始但可用”的充电状态与电压值;
* - 本模块基于这些采样结果做滤波、SOC 估算和事件发布。
*/
static const struct gpio_dt_spec battery_en_gpio =
GPIO_DT_SPEC_GET(BATTERY_USER_NODE, vbat_en_gpios);
static const struct device *const ip5305_dev = DEVICE_DT_GET(DT_NODELABEL(ip5305));
struct board_power_sample
{
int32_t voltage_mv;
bool charging;
bool full;
};
/*
* 对外上报状态:
* - 保持现有 battery_status_event 语义不变;
* - 只承载业务层需要的 charging/full/soc 三元组。
*/
struct battery_status
{
bool charging;
@@ -62,10 +67,16 @@ struct battery_filter_state
size_t index;
};
/*
* 模块上下文:
* - sample_work 周期性拉取 board power sample
* - filter 负责平滑电池电压;
* - last_status 用于抑制重复事件;
* - pm_restrict_level 跟踪当前对 power manager 的限制等级。
*/
struct battery_ctx
{
struct k_work_delayable sample_work;
int16_t adc_sample_buffer;
atomic_t active;
struct battery_status last_status;
bool has_last_status;
@@ -77,17 +88,14 @@ static struct battery_ctx battery = {
.pm_restrict_level = POWER_MANAGER_LEVEL_MAX,
};
static void battery_module_resume(void);
/* 线性 SOC 估算:把平滑后的电池电压映射到 0~100%。 */
static uint8_t soc_from_mv(int32_t mv)
{
if (mv <= BATTERY_EMPTY_MV)
{
if (mv <= BATTERY_EMPTY_MV) {
return 0;
}
if (mv >= BATTERY_FULL_MV)
{
if (mv >= BATTERY_FULL_MV) {
return 100;
}
@@ -95,109 +103,126 @@ static uint8_t soc_from_mv(int32_t mv)
return (uint8_t)CLAMP(soc, 0, 100);
}
static int adc_sample_once_mv(int32_t *mv)
/* 初始化/恢复时清空滤波器,避免旧样本影响新一轮估算。 */
static void battery_filter_reset(void)
{
struct adc_sequence sequence = {0};
int err = adc_sequence_init_dt(&battery_adc, &sequence);
if (err)
{
return err;
}
sequence.buffer = &battery.adc_sample_buffer;
sequence.buffer_size = sizeof(battery.adc_sample_buffer);
err = adc_read_dt(&battery_adc, &sequence);
if (err)
{
LOG_WRN("adc_read_dt failed (err=%d)", err);
return err;
}
*mv = battery.adc_sample_buffer;
err = adc_raw_to_millivolts_dt(&battery_adc, mv);
if (err)
{
LOG_WRN("adc_raw_to_millivolts_dt failed (err=%d raw=%d)",
err, battery.adc_sample_buffer);
}
return err;
}
static int read_battery_mv(int32_t *mv)
{
int err;
int32_t sensed_mv;
err = adc_sample_once_mv(&sensed_mv);
if (err)
{
return err;
battery.filter.sum = 0;
battery.filter.count = 0;
battery.filter.index = 0;
}
/*
* 板级电池检测存在分压ADC 读到的是分压后电压
* 这里按分压比还原电池端真实电压,供 SOC 估算与事件上报使用
* 将最新电压样本写入固定窗口平均滤波器
* 返回值始终是“窗口平均后的电池电压,供上层做 SOC 估算。
*/
int32_t battery_mv = (sensed_mv * BATTERY_VDIV_NUM) / BATTERY_VDIV_DEN;
/*
* 使用固定窗口平均抑制采样抖动。
* 窗口未填满前按当前样本数求平均,填满后使用环形缓冲滚动更新。
*/
if (battery.filter.count < BATTERY_MV_WINDOW_SIZE)
static int32_t battery_filter_apply(int32_t voltage_mv)
{
battery.filter.window[battery.filter.index] = battery_mv;
battery.filter.sum += battery_mv;
if (battery.filter.count < BATTERY_MV_WINDOW_SIZE) {
battery.filter.window[battery.filter.index] = voltage_mv;
battery.filter.sum += voltage_mv;
battery.filter.count++;
}
else
{
} else {
battery.filter.sum -= battery.filter.window[battery.filter.index];
battery.filter.window[battery.filter.index] = battery_mv;
battery.filter.sum += battery_mv;
battery.filter.window[battery.filter.index] = voltage_mv;
battery.filter.sum += voltage_mv;
}
battery.filter.index = (battery.filter.index + 1U) % BATTERY_MV_WINDOW_SIZE;
*mv = (int32_t)(battery.filter.sum / (int64_t)battery.filter.count);
return (int32_t)(battery.filter.sum / (int64_t)battery.filter.count);
}
/*
* 控制 board-provided battery_sense sensor 的供电状态。
* 这里不直接操纵 GPIO而是走 sensor 的 PM action让 power-gpios
* 与 ADC runtime PM 都由 voltage-divider 驱动统一管理。
*/
static int board_power_monitor_set_voltage_sensor_enabled(bool enable)
{
return pm_device_action_run(battery_sensor_dev,
enable ? PM_DEVICE_ACTION_RESUME :
PM_DEVICE_ACTION_SUSPEND);
}
/* 从 battery_sense 读取一次当前电池电压(单位 mV。 */
static int board_power_monitor_read_voltage_mv(int32_t *voltage_mv)
{
struct sensor_value value;
int err = sensor_sample_fetch(battery_sensor_dev);
if (err) {
LOG_WRN("sensor_sample_fetch(battery) failed (err=%d)", err);
return err;
}
err = sensor_channel_get(battery_sensor_dev, SENSOR_CHAN_VOLTAGE, &value);
if (err) {
LOG_WRN("sensor_channel_get(battery) failed (err=%d)", err);
return err;
}
*voltage_mv = (int32_t)sensor_value_to_milli(&value);
return 0;
}
/* 从 IP5305 读取一次充电态与满电态。 */
static int board_power_monitor_read_charge_state(bool *charging, bool *full)
{
int err = ip5305_is_charging(ip5305_dev, charging);
if (err) {
LOG_WRN("ip5305_is_charging failed (err=%d)", err);
return err;
}
err = ip5305_is_charge_full(ip5305_dev, full);
if (err) {
LOG_WRN("ip5305_is_charge_full failed (err=%d)", err);
return err;
}
return 0;
}
static int read_battery_status(struct battery_status *status)
/*
* 聚合一次完整的 board power sample
* 1) 先读 PMIC 状态;
* 2) 再读 battery_sense 电压;
* 3) 最后对电压做窗口平均,输出稳定值。
*/
static int board_power_monitor_collect_sample(struct board_power_sample *sample)
{
int err;
int32_t mv;
int32_t voltage_mv;
int err = board_power_monitor_read_charge_state(&sample->charging, &sample->full);
err = ip5305_is_charging(ip5305_dev, &status->charging);
if (err)
{
if (err) {
return err;
}
err = ip5305_is_charge_full(ip5305_dev, &status->full);
if (err)
{
err = board_power_monitor_read_voltage_mv(&voltage_mv);
if (err) {
return err;
}
err = read_battery_mv(&mv);
if (err)
{
LOG_WRN("read_battery_mv failed (err=%d)", err);
return err;
}
status->soc = soc_from_mv(mv);
sample->voltage_mv = battery_filter_apply(voltage_mv);
return 0;
}
/* 将 board sample 映射成对外 battery status。 */
static void battery_status_from_sample(const struct board_power_sample *sample,
struct battery_status *status)
{
status->charging = sample->charging;
status->full = sample->full;
status->soc = soc_from_mv(sample->voltage_mv);
}
/* 统一封装 battery_status_event 发布,隔离事件总线细节。 */
static void publish_battery_status_event(const struct battery_status *status)
{
battery_status_event_submit(status->charging, status->full, status->soc);
}
/* 判断本轮状态是否值得上报,避免重复事件淹没总线。 */
static bool battery_status_changed(const struct battery_status *lhs,
const struct battery_status *rhs)
{
@@ -223,99 +248,79 @@ static void update_power_restrict_by_charging(bool charging)
power_manager_restrict(MODULE_IDX(MODULE), target);
}
/*
* 启停采样:
* - enable=true 时恢复 battery_sense等待前端稳定后开始周期采样
* - enable=false 时停止 work 并挂起 battery_sense避免持续耗电。
*/
static void battery_sampling_set_enabled(bool enable)
{
atomic_set(&battery.active, enable);
(void)gpio_pin_set_dt(&battery_en_gpio, enable ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE);
int err = board_power_monitor_set_voltage_sensor_enabled(enable);
if (enable)
{
// 延迟2s开始采样等待电池电压稳定
k_work_reschedule(&battery.sample_work, K_MSEC(2000));
if (err) {
LOG_WRN("board_power_monitor_set_voltage_sensor_enabled(%d) failed (err=%d)",
enable, err);
}
else
{
if (enable) {
/* 延迟开始采样,等待板上采样前端和分压网络稳定。 */
k_work_reschedule(&battery.sample_work, K_MSEC(2000));
} else {
(void)k_work_cancel_delayable(&battery.sample_work);
}
}
/* 周期性读取 board power sample并在需要时上报业务状态。 */
static void battery_sample_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (!atomic_get(&battery.active))
{
if (!atomic_get(&battery.active)) {
return;
}
struct board_power_sample sample;
struct battery_status status;
int err = board_power_monitor_collect_sample(&sample);
struct battery_status sampled;
int err = read_battery_status(&sampled);
if (err)
{
if (err) {
goto out_reschedule;
}
update_power_restrict_by_charging(sampled.charging);
battery_status_from_sample(&sample, &status);
update_power_restrict_by_charging(status.charging);
/*
* 仅在状态发生变化时上报,避免重复事件淹没总线。
* 变化条件充电标志、满电标志、SOC 任意一个变化。
*/
if (!battery.has_last_status ||
battery_status_changed(&sampled, &battery.last_status))
{
battery.last_status = sampled;
battery_status_changed(&status, &battery.last_status)) {
battery.last_status = status;
battery.has_last_status = true;
publish_battery_status_event(&sampled);
publish_battery_status_event(&status);
}
out_reschedule:
k_work_reschedule(&battery.sample_work, K_MSEC(BATTERY_SAMPLE_INTERVAL_MS));
}
/* 初始化 board power monitor consumer并拉起首轮采样。 */
static int battery_module_init(void)
{
if (!device_is_ready(ip5305_dev))
{
if (!device_is_ready(ip5305_dev)) {
LOG_ERR("IP5305 device not ready");
return -ENODEV;
}
if (!adc_is_ready_dt(&battery_adc))
{
LOG_ERR("Battery ADC device not ready");
if (!device_is_ready(battery_sensor_dev)) {
LOG_ERR("Battery sense device not ready");
return -ENODEV;
}
if (!device_is_ready(battery_en_gpio.port))
{
LOG_ERR("Battery EN GPIO device not ready");
return -ENODEV;
}
int err = gpio_pin_configure_dt(&battery_en_gpio, GPIO_OUTPUT_INACTIVE);
if (err)
{
LOG_ERR("Battery EN GPIO configure failed (err=%d)", err);
return err;
}
err = adc_channel_setup_dt(&battery_adc);
if (err)
{
LOG_ERR("Battery ADC channel setup failed (err=%d)", err);
return err;
}
/* 默认非充电态允许进入 SUSPENDED但禁止进入 OFF。 */
update_power_restrict_by_charging(false);
k_work_init_delayable(&battery.sample_work, battery_sample_fn);
battery.has_last_status = false;
battery.filter.sum = 0;
battery.filter.count = 0;
battery.filter.index = 0;
battery_filter_reset();
atomic_set(&battery.active, false);
battery_sampling_set_enabled(true);
@@ -323,10 +328,10 @@ static int battery_module_init(void)
return 0;
}
/* 响应系统挂起:停止采样,并把本模块切到 STANDBY。 */
static void battery_module_suspend(void)
{
if (!atomic_get(&battery.active))
{
if (!atomic_get(&battery.active)) {
/* 已经处于挂起态,避免重复上报 STANDBY 造成 power_down 循环。 */
return;
}
@@ -335,10 +340,10 @@ static void battery_module_suspend(void)
module_set_state(MODULE_STATE_STANDBY);
}
/* 响应系统唤醒:恢复电压传感器并重启周期采样。 */
static void battery_module_resume(void)
{
if (atomic_get(&battery.active))
{
if (atomic_get(&battery.active)) {
return;
}
@@ -346,21 +351,18 @@ static void battery_module_resume(void)
module_set_state(MODULE_STATE_READY);
}
/* 仅处理模块 ready 和系统电源状态事件,保持模块职责单一。 */
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh))
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY))
{
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err = battery_module_init();
if (err)
{
if (err) {
module_set_state(MODULE_STATE_ERROR);
}
else
{
} else {
module_set_state(MODULE_STATE_READY);
}
}

View File

@@ -1,7 +1,6 @@
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
#include <stdlib.h>
@@ -18,35 +17,25 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
#define MODE_USER_NODE DT_PATH(zephyr_user)
#define MODE_ADC_IO_CH_IDX 0
#define MODE_SENSE_NODE DT_NODELABEL(mode_sense)
#define MODE_SAMPLE_INTERVAL_MS 50
static const struct adc_dt_spec mode_adc =
ADC_DT_SPEC_GET_BY_IDX(MODE_USER_NODE, MODE_ADC_IO_CH_IDX);
static const struct device *const mode_sensor_dev = DEVICE_DT_GET(MODE_SENSE_NODE);
static struct k_work_delayable mode_sample_work;
static int16_t adc_sample_buffer;
static atomic_t active;
static mode_type_t current_mode;
static uint8_t mode_stable_status;
static int init_adc(void)
static int init_mode_sensor(void)
{
if (!adc_is_ready_dt(&mode_adc))
if (!device_is_ready(mode_sensor_dev))
{
LOG_ERR("ADC device not ready");
LOG_ERR("Mode sense device not ready");
return -ENODEV;
}
int err = adc_channel_setup_dt(&mode_adc);
if (err)
{
LOG_ERR("ADC channel setup failed (err=%d)", err);
return err;
}
return 0;
}
@@ -81,29 +70,22 @@ static mode_type_t classify_mode_from_mv(int32_t mv)
static int read_mode(mode_type_t *mode)
{
struct adc_sequence sequence = {0};
int err = adc_sequence_init_dt(&mode_adc, &sequence);
struct sensor_value value;
int err = sensor_sample_fetch(mode_sensor_dev);
if (err)
{
LOG_WRN("sensor_sample_fetch(mode) failed (err=%d)", err);
return err;
}
sequence.buffer = &adc_sample_buffer;
sequence.buffer_size = sizeof(adc_sample_buffer);
err = adc_read_dt(&mode_adc, &sequence);
if (err)
{
return err;
}
int32_t v = adc_sample_buffer;
err = adc_raw_to_millivolts_dt(&mode_adc, &v);
err = sensor_channel_get(mode_sensor_dev, SENSOR_CHAN_VOLTAGE, &value);
if (err)
{
LOG_WRN("sensor_channel_get(mode) failed (err=%d)", err);
return err;
}
int32_t v = (int32_t)sensor_value_to_milli(&value);
*mode = classify_mode_from_mv(v);
return 0;
@@ -187,7 +169,7 @@ static void init_mode_switch(void)
if (atomic_get(&active))
return;
if (init_adc())
if (init_mode_sensor())
{
module_set_state(MODULE_STATE_ERROR);
return;

View File

@@ -1 +1,2 @@
SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_MCUBOOT_MODE_SINGLE_APP=y

25
sysbuild/mcuboot.conf Normal file
View File

@@ -0,0 +1,25 @@
CONFIG_LOG=n
CONFIG_CONSOLE=n
CONFIG_UART_CONSOLE=n
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_REMOTE_WAKEUP=n
CONFIG_USB_CDC_ACM=y
CONFIG_USB_NRFX=y
CONFIG_USB_DEVICE_PRODUCT="MCUBOOT"
CONFIG_USB_DEVICE_MANUFACTURER="new_kbd"
CONFIG_USB_DEVICE_VID=0x1209
CONFIG_USB_DEVICE_PID=0x0002
CONFIG_RETAINED_MEM=y
CONFIG_RETENTION=y
CONFIG_RETENTION_BOOT_MODE=y
CONFIG_MCUBOOT_SERIAL=y
CONFIG_BOOT_SERIAL_CDC_ACM=y
CONFIG_BOOT_SERIAL_BOOT_MODE=y
CONFIG_BOOT_SERIAL_NO_APPLICATION=y

23
sysbuild/mcuboot.overlay Normal file
View File

@@ -0,0 +1,23 @@
/ {
chosen {
zephyr,boot-mode = &boot_mode0;
};
};
&usbd {
status = "okay";
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
};
};
&gpregret1 {
status = "okay";
boot_mode0: boot_mode@0 {
compatible = "zephyr,retention";
status = "okay";
reg = <0x0 0x1>;
};
};