feat(battery): 添加电池管理模块和IP5305 PMIC支持

- 添加电池状态监测模块,包括ADC采样和SOC估算功能
- 集成IP5305电源管理芯片支持,配置I2C通信和保活机制
- 实现电池状态事件系统,包含充电状态、满电状态和电量百分比
- 添加电池使能GPIO控制和采样工作队列
- 配置设备树支持电池检测和PMIC控制
- 添加外部模块路径到CMakeLists.txt并更新.gitignore
This commit is contained in:
2026-03-11 18:30:24 +08:00
parent 86af0d2373
commit b3516b988a
7 changed files with 423 additions and 0 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
build*/ build*/
external*/

View File

@@ -1,4 +1,9 @@
cmake_minimum_required(VERSION 3.20.0) cmake_minimum_required(VERSION 3.20.0)
if(EXISTS "E:/extra/modules/ip5305")
list(APPEND ZEPHYR_EXTRA_MODULES "E:/extra/modules/ip5305")
endif()
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(new_kbd) project(new_kbd)
@@ -8,7 +13,9 @@ zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events)
target_sources(app PRIVATE target_sources(app PRIVATE
src/main.c src/main.c
src/events/battery_status_event.c
src/events/mode_event.c src/events/mode_event.c
src/modules/battery_module.c
src/modules/button_map_module.c src/modules/button_map_module.c
src/modules/mode_switch_module.c src/modules/mode_switch_module.c
) )

View File

@@ -1,5 +1,8 @@
#include <zephyr/dt-bindings/gpio/gpio.h>
/ { / {
zephyr,user { zephyr,user {
vbat-en-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
io-channels = <&adc 5>, <&adc 7>; io-channels = <&adc 5>, <&adc 7>;
}; };
}; };
@@ -24,3 +27,13 @@
&adc { &adc {
status = "okay"; status = "okay";
}; };
&i2c1 {
status = "okay";
ip5305: pmic@75 {
status = "okay";
/* 试验项:调整 IP5305 KEY 保活周期,观察 I2C 失败窗口是否随周期移动。 */
keepalive-interval-ms = <10000>;
};
};

View File

@@ -12,6 +12,7 @@ CONFIG_CAF_POWER_MANAGER=y
CONFIG_CAF_POWER_MANAGER_TIMEOUT=20 CONFIG_CAF_POWER_MANAGER_TIMEOUT=20
CONFIG_CAF_POWER_MANAGER_ERROR_TIMEOUT=10 CONFIG_CAF_POWER_MANAGER_ERROR_TIMEOUT=10
CONFIG_REBOOT=y CONFIG_REBOOT=y
CONFIG_CAF_KEEP_ALIVE_EVENTS=y
CONFIG_CAF_BUTTONS=y CONFIG_CAF_BUTTONS=y
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h" CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
@@ -19,3 +20,5 @@ CONFIG_CAF_BUTTONS_SCAN_INTERVAL=5
CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10 CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10
CONFIG_ADC=y CONFIG_ADC=y
CONFIG_I2C=y
CONFIG_IP5305=y

View File

@@ -0,0 +1,34 @@
#include "battery_status_event.h"
static void log_battery_status_event(const struct app_event_header *aeh)
{
const struct battery_status_event *event = cast_battery_status_event(aeh);
APP_EVENT_MANAGER_LOG(aeh,
"charging=%u full=%u soc=%u",
event->charging,
event->full,
event->soc);
}
static void profile_battery_status_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct battery_status_event *event = cast_battery_status_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->charging ? 1U : 0U);
nrf_profiler_log_encode_uint8(buf, event->full ? 1U : 0U);
nrf_profiler_log_encode_uint8(buf, event->soc);
}
APP_EVENT_INFO_DEFINE(battery_status_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("charging", "full", "soc"),
profile_battery_status_event);
APP_EVENT_TYPE_DEFINE(battery_status_event,
log_battery_status_event,
&battery_status_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,20 @@
#ifndef BATTERY_STATUS_EVENT_H
#define BATTERY_STATUS_EVENT_H
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
struct battery_status_event
{
struct app_event_header header;
bool charging;
bool full;
uint8_t soc;
};
APP_EVENT_TYPE_DECLARE(battery_status_event);
#endif

View File

@@ -0,0 +1,345 @@
#include <errno.h>
#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/sys/atomic.h>
#include <zephyr/sys/util.h>
#include <app_event_manager.h>
#include <caf/events/power_event.h>
#include <caf/events/power_manager_event.h>
#define MODULE battery
#include <caf/events/module_state_event.h>
#include "battery_status_event.h"
#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_SAMPLE_INTERVAL_MS 1000
#define BATTERY_VDIV_NUM 2
#define BATTERY_VDIV_DEN 1
#define BATTERY_MV_WINDOW_SIZE 10
/*
* SOC 估算策略:
* 这里采用线性估算,默认把 3.30V 映射为 0%4.20V 映射为 100%。
* 若后续实测曲线和电压分压比例不同,只需调整两个阈值即可。
*/
#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);
/*
* 电池采样使能脚从 zephyr,user/vbat-en-gpios 读取,避免把引脚号硬编码在 C 里。
* 后续板级改脚位只需改 DTS不需要改固件代码。
*/
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 battery_status
{
bool charging;
bool full;
uint8_t soc;
};
static struct k_work_delayable battery_sample_work;
static int16_t battery_adc_sample_buffer;
static atomic_t active;
static struct battery_status last_status;
static bool has_last_status;
static int32_t battery_mv_window[BATTERY_MV_WINDOW_SIZE];
static int64_t battery_mv_sum;
static size_t battery_mv_count;
static size_t battery_mv_index;
static void battery_module_resume(void);
static uint8_t soc_from_mv(int32_t mv)
{
if (mv <= BATTERY_EMPTY_MV)
{
return 0;
}
if (mv >= BATTERY_FULL_MV)
{
return 100;
}
int32_t soc = ((mv - BATTERY_EMPTY_MV) * 100) / (BATTERY_FULL_MV - BATTERY_EMPTY_MV);
return (uint8_t)CLAMP(soc, 0, 100);
}
static int adc_sample_once_mv(int32_t *mv)
{
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;
}
/*
* 板级电池检测存在分压ADC 读到的是分压后电压。
* 这里按分压比还原电池端真实电压,供 SOC 估算与事件上报使用。
*/
int32_t battery_mv = (sensed_mv * BATTERY_VDIV_NUM) / BATTERY_VDIV_DEN;
/*
* 使用固定窗口平均抑制采样抖动。
* 窗口未填满前按当前样本数求平均,填满后使用环形缓冲滚动更新。
*/
if (battery_mv_count < BATTERY_MV_WINDOW_SIZE)
{
battery_mv_window[battery_mv_index] = battery_mv;
battery_mv_sum += battery_mv;
battery_mv_count++;
}
else
{
battery_mv_sum -= battery_mv_window[battery_mv_index];
battery_mv_window[battery_mv_index] = battery_mv;
battery_mv_sum += battery_mv;
}
battery_mv_index = (battery_mv_index + 1U) % BATTERY_MV_WINDOW_SIZE;
*mv = (int32_t)(battery_mv_sum / (int64_t)battery_mv_count);
return 0;
}
static int read_battery_status(struct battery_status *status)
{
int err;
int32_t mv;
err = ip5305_is_charging(ip5305_dev, &status->charging);
if (err)
{
return err;
}
err = ip5305_is_charge_full(ip5305_dev, &status->full);
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);
return 0;
}
static void publish_battery_status_event(const struct battery_status *status)
{
struct battery_status_event *event = new_battery_status_event();
event->charging = status->charging;
event->full = status->full;
event->soc = status->soc;
APP_EVENT_SUBMIT(event);
}
static void battery_sample_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (!atomic_get(&active))
{
return;
}
struct battery_status sampled;
int err = read_battery_status(&sampled);
if (err)
{
goto out_reschedule;
}
/*
* 仅在状态发生变化时上报,避免重复事件淹没总线。
* 变化条件充电标志、满电标志、SOC 任意一个变化。
*/
if (!has_last_status ||
(sampled.charging != last_status.charging) ||
(sampled.full != last_status.full) ||
(sampled.soc != last_status.soc))
{
last_status = sampled;
has_last_status = true;
publish_battery_status_event(&sampled);
}
out_reschedule:
k_work_reschedule(&battery_sample_work, K_MSEC(BATTERY_SAMPLE_INTERVAL_MS));
}
static int battery_module_init(void)
{
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");
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。
* 这样即使 CPU 进入挂起IP5305 的硬件保活后端仍可持续输出保活脉冲。
*/
power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED);
k_work_init_delayable(&battery_sample_work, battery_sample_fn);
has_last_status = false;
battery_mv_sum = 0;
battery_mv_count = 0;
battery_mv_index = 0;
atomic_set(&active, false);
atomic_set(&active, true);
(void)gpio_pin_set_dt(&battery_en_gpio, 1);
k_work_reschedule(&battery_sample_work, K_NO_WAIT);
return 0;
}
static void battery_module_suspend(void)
{
atomic_set(&active, false);
(void)k_work_cancel_delayable(&battery_sample_work);
(void)gpio_pin_set_dt(&battery_en_gpio, 0);
module_set_state(MODULE_STATE_STANDBY);
}
static void battery_module_resume(void)
{
if (atomic_get(&active))
{
return;
}
atomic_set(&active, true);
(void)gpio_pin_set_dt(&battery_en_gpio, 1);
k_work_reschedule(&battery_sample_work, K_NO_WAIT);
module_set_state(MODULE_STATE_READY);
}
static bool app_event_handler(const struct app_event_header *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))
{
int err = battery_module_init();
if (err)
{
module_set_state(MODULE_STATE_ERROR);
}
else
{
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
if (is_power_down_event(aeh))
{
battery_module_suspend();
return false;
}
if (is_wake_up_event(aeh))
{
battery_module_resume();
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);