feat(battery): 添加电池管理模块和IP5305 PMIC支持
- 添加电池状态监测模块,包括ADC采样和SOC估算功能 - 集成IP5305电源管理芯片支持,配置I2C通信和保活机制 - 实现电池状态事件系统,包含充电状态、满电状态和电量百分比 - 添加电池使能GPIO控制和采样工作队列 - 配置设备树支持电池检测和PMIC控制 - 添加外部模块路径到CMakeLists.txt并更新.gitignore
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
build*/
|
||||
external*/
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
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})
|
||||
|
||||
project(new_kbd)
|
||||
@@ -8,7 +13,9 @@ zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/main.c
|
||||
src/events/battery_status_event.c
|
||||
src/events/mode_event.c
|
||||
src/modules/battery_module.c
|
||||
src/modules/button_map_module.c
|
||||
src/modules/mode_switch_module.c
|
||||
)
|
||||
|
||||
13
app.overlay
13
app.overlay
@@ -1,5 +1,8 @@
|
||||
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||
|
||||
/ {
|
||||
zephyr,user {
|
||||
vbat-en-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
|
||||
io-channels = <&adc 5>, <&adc 7>;
|
||||
};
|
||||
};
|
||||
@@ -24,3 +27,13 @@
|
||||
&adc {
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&i2c1 {
|
||||
status = "okay";
|
||||
|
||||
ip5305: pmic@75 {
|
||||
status = "okay";
|
||||
/* 试验项:调整 IP5305 KEY 保活周期,观察 I2C 失败窗口是否随周期移动。 */
|
||||
keepalive-interval-ms = <10000>;
|
||||
};
|
||||
};
|
||||
|
||||
3
prj.conf
3
prj.conf
@@ -12,6 +12,7 @@ CONFIG_CAF_POWER_MANAGER=y
|
||||
CONFIG_CAF_POWER_MANAGER_TIMEOUT=20
|
||||
CONFIG_CAF_POWER_MANAGER_ERROR_TIMEOUT=10
|
||||
CONFIG_REBOOT=y
|
||||
CONFIG_CAF_KEEP_ALIVE_EVENTS=y
|
||||
|
||||
CONFIG_CAF_BUTTONS=y
|
||||
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_ADC=y
|
||||
CONFIG_I2C=y
|
||||
CONFIG_IP5305=y
|
||||
|
||||
34
src/events/battery_status_event.c
Normal file
34
src/events/battery_status_event.c
Normal 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));
|
||||
20
src/events/battery_status_event.h
Normal file
20
src/events/battery_status_event.h
Normal 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
|
||||
345
src/modules/battery_module.c
Normal file
345
src/modules/battery_module.c
Normal 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);
|
||||
Reference in New Issue
Block a user