feat(power): add battery monitoring and status overlays
This commit is contained in:
@@ -11,6 +11,7 @@ set(platform_srcs
|
||||
"src/runtime/platform_multimodal_runtime.c"
|
||||
"src/runtime/platform_lcd_runtime.c"
|
||||
"src/platform_stepper_driver.c"
|
||||
"src/platform_battery_driver.c"
|
||||
"src/platform_digital_input_driver.c"
|
||||
"src/platform_io_effects.c"
|
||||
"src/lcd_direct.c"
|
||||
@@ -30,6 +31,7 @@ set(platform_priv_requires
|
||||
cxx
|
||||
driver
|
||||
esp_audio_codec
|
||||
esp_adc
|
||||
esp_codec_dev
|
||||
esp_driver_gpio
|
||||
esp_driver_i2c
|
||||
@@ -140,6 +142,8 @@ set(platform_lcd_png_assets
|
||||
icon_huo0
|
||||
icon_tu0
|
||||
icon_jin0
|
||||
charging
|
||||
lowbat
|
||||
qrcode
|
||||
)
|
||||
set(platform_lcd_png_asset_files)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -302,6 +302,33 @@ esp_err_t platform_stepper_rotate_angle_with_accel_timeout(float angle,
|
||||
int target_delay_us,
|
||||
int timeout_ms);
|
||||
void platform_stepper_power_off(void);
|
||||
|
||||
typedef enum {
|
||||
PLATFORM_BATTERY_CHARGE_UNKNOWN = 0,
|
||||
PLATFORM_BATTERY_CHARGE_NOT_CHARGING,
|
||||
PLATFORM_BATTERY_CHARGE_CHARGING,
|
||||
} platform_battery_charge_state_t;
|
||||
|
||||
typedef struct {
|
||||
int adc_voltage_mv;
|
||||
int voltage_mv;
|
||||
platform_battery_charge_state_t charge_state;
|
||||
bool voltage_valid;
|
||||
bool high_load_active;
|
||||
bool cooldown_active;
|
||||
} platform_battery_sample_t;
|
||||
|
||||
/** Initialize the board battery ADC and TP4057 status input. Idempotent. */
|
||||
esp_err_t platform_battery_init(void);
|
||||
|
||||
/**
|
||||
* Read one trimmed-mean battery voltage sample and a load-quality snapshot.
|
||||
* A successful call can still return voltage_valid=false for an implausible
|
||||
* electrical reading; callers must not interpret such a sample as an empty
|
||||
* battery.
|
||||
*/
|
||||
esp_err_t platform_battery_read_sample(platform_battery_sample_t *out_sample);
|
||||
|
||||
esp_err_t platform_configure_digital_input(const platform_digital_input_config_t *config);
|
||||
bool platform_is_digital_input_active(const platform_digital_input_config_t *config);
|
||||
|
||||
@@ -480,6 +507,19 @@ typedef struct {
|
||||
size_t step_count;
|
||||
} platform_lcd_presentation_request_t;
|
||||
|
||||
typedef struct {
|
||||
bool low_battery_visible;
|
||||
bool charging_visible;
|
||||
} platform_lcd_status_overlay_t;
|
||||
|
||||
/**
|
||||
* @brief Set persistent LCD status overlays composited over presentation frames.
|
||||
*
|
||||
* The state is copied synchronously and remains active until replaced. The
|
||||
* render task owns all PNG decoding and panel access.
|
||||
*/
|
||||
esp_err_t platform_lcd_set_status_overlay(const platform_lcd_status_overlay_t *overlay);
|
||||
|
||||
/**
|
||||
* @brief Request a LCD presentation playback.
|
||||
*
|
||||
|
||||
@@ -12,6 +12,7 @@ extern "C" {
|
||||
esp_err_t lcd_direct_init_device(void);
|
||||
void lcd_direct_start_render_loop(void);
|
||||
esp_err_t lcd_direct_stop_render_loop(int timeout_ms);
|
||||
esp_err_t lcd_direct_set_status_overlay(const platform_lcd_status_overlay_t *overlay);
|
||||
esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_request_t *request);
|
||||
esp_err_t lcd_direct_preheat_presentation(const platform_lcd_presentation_request_t *request);
|
||||
uint32_t lcd_direct_presentation_completion_count(void);
|
||||
|
||||
@@ -23,6 +23,8 @@ typedef struct {
|
||||
|
||||
#define LCD_DIRECT_NO_ICON 0xFFFFU
|
||||
#define LCD_DIRECT_DEFAULT_ICON_COUNT 5U
|
||||
#define LCD_DIRECT_ICON_CHARGING 5U
|
||||
#define LCD_DIRECT_ICON_LOW_BATTERY 6U
|
||||
|
||||
const lcd_direct_image_t *lcd_direct_default_image(void);
|
||||
const lcd_direct_image_t *lcd_direct_asset_frame(const char *asset_dir, uint8_t frame_index);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
PLATFORM_BATTERY_LOAD_AUDIO = 0,
|
||||
PLATFORM_BATTERY_LOAD_STEPPER,
|
||||
PLATFORM_BATTERY_LOAD_COUNT,
|
||||
} platform_battery_load_source_t;
|
||||
|
||||
void platform_battery_load_begin(platform_battery_load_source_t source);
|
||||
void platform_battery_load_end(platform_battery_load_source_t source);
|
||||
@@ -46,8 +46,15 @@
|
||||
#define LCD_DIRECT_PRESENTATION_WAIT_POLL_MS 20U
|
||||
#define LCD_DIRECT_TRANSFER_TIMEOUT_MS 100U
|
||||
#define LCD_DIRECT_PREHEAT_WAIT_TIMEOUT_MS 5000U
|
||||
#define LCD_DIRECT_FRAME_CACHE_MAX 4U
|
||||
#define LCD_DIRECT_FRAME_CACHE_MAX 6U
|
||||
#define LCD_DIRECT_PATH_MAX 96U
|
||||
#define LCD_DIRECT_CHARGING_ICON_WIDTH 50U
|
||||
#define LCD_DIRECT_CHARGING_ICON_X (LCD_DIRECT_WIDTH - LCD_DIRECT_CHARGING_ICON_WIDTH - 8U)
|
||||
#define LCD_DIRECT_CHARGING_ICON_Y 8U
|
||||
#define LCD_DIRECT_LOW_BATTERY_ICON_WIDTH 232U
|
||||
#define LCD_DIRECT_LOW_BATTERY_ICON_HEIGHT 36U
|
||||
#define LCD_DIRECT_LOW_BATTERY_ICON_X ((LCD_DIRECT_WIDTH - LCD_DIRECT_LOW_BATTERY_ICON_WIDTH) / 2U)
|
||||
#define LCD_DIRECT_LOW_BATTERY_ICON_Y (LCD_DIRECT_HEIGHT - LCD_DIRECT_LOW_BATTERY_ICON_HEIGHT - 8U)
|
||||
#define LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS 200U
|
||||
#define LCD_DIRECT_BASIC_FACE_FRAME_HOLD_MS 100U
|
||||
#define LCD_DIRECT_BASIC_FACE_LAST_FRAME_HOLD_MS 100U
|
||||
@@ -135,6 +142,7 @@ static bool s_render_loop_running;
|
||||
static bool s_stop_requested;
|
||||
static bool s_pending_valid;
|
||||
static lcd_direct_request_t s_pending;
|
||||
static platform_lcd_status_overlay_t s_status_overlay;
|
||||
static bool s_preheat_pending_valid;
|
||||
static bool s_preheat_in_progress;
|
||||
static bool s_preheat_waiter_active;
|
||||
@@ -683,6 +691,38 @@ static void lcd_direct_release_prepared_icons(lcd_direct_prepared_icon_t *prepar
|
||||
}
|
||||
}
|
||||
|
||||
static size_t lcd_direct_append_status_icons(const lcd_direct_image_t *image,
|
||||
lcd_direct_icon_draw_t *icons,
|
||||
size_t icon_count,
|
||||
size_t icon_capacity)
|
||||
{
|
||||
if (image == NULL || image->path == NULL || icons == NULL ||
|
||||
icon_count >= icon_capacity || strstr(image->path, "/qrcode.png") != NULL) {
|
||||
return icon_count;
|
||||
}
|
||||
|
||||
platform_lcd_status_overlay_t overlay = {0};
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
overlay = s_status_overlay;
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
|
||||
if (overlay.low_battery_visible && icon_count < icon_capacity) {
|
||||
icons[icon_count++] = (lcd_direct_icon_draw_t) {
|
||||
.icon_id = LCD_DIRECT_ICON_LOW_BATTERY,
|
||||
.x = LCD_DIRECT_LOW_BATTERY_ICON_X,
|
||||
.y = LCD_DIRECT_LOW_BATTERY_ICON_Y,
|
||||
};
|
||||
}
|
||||
if (overlay.charging_visible && icon_count < icon_capacity) {
|
||||
icons[icon_count++] = (lcd_direct_icon_draw_t) {
|
||||
.icon_id = LCD_DIRECT_ICON_CHARGING,
|
||||
.x = LCD_DIRECT_CHARGING_ICON_X,
|
||||
.y = LCD_DIRECT_CHARGING_ICON_Y,
|
||||
};
|
||||
}
|
||||
return icon_count;
|
||||
}
|
||||
|
||||
static void lcd_direct_overlay_icons(uint16_t *pixel_buffer,
|
||||
uint16_t row_start,
|
||||
uint16_t row_count,
|
||||
@@ -766,9 +806,23 @@ static esp_err_t lcd_direct_draw_png_image(const lcd_direct_image_t *image,
|
||||
decoded->pinned = true;
|
||||
}
|
||||
|
||||
lcd_direct_icon_draw_t combined_icons[LCD_DIRECT_DEFAULT_ICON_COUNT] = {0};
|
||||
size_t combined_icon_count = icon_count;
|
||||
if (combined_icon_count > LCD_DIRECT_DEFAULT_ICON_COUNT) {
|
||||
combined_icon_count = LCD_DIRECT_DEFAULT_ICON_COUNT;
|
||||
}
|
||||
if (icons != NULL && combined_icon_count > 0U) {
|
||||
memcpy(combined_icons, icons, combined_icon_count * sizeof(combined_icons[0]));
|
||||
}
|
||||
combined_icon_count = lcd_direct_append_status_icons(
|
||||
image,
|
||||
combined_icons,
|
||||
combined_icon_count,
|
||||
LCD_DIRECT_DEFAULT_ICON_COUNT);
|
||||
|
||||
lcd_direct_prepared_icon_t prepared_icons[LCD_DIRECT_DEFAULT_ICON_COUNT] = {0};
|
||||
size_t prepared_icon_count = lcd_direct_prepare_icons(icons,
|
||||
icon_count,
|
||||
size_t prepared_icon_count = lcd_direct_prepare_icons(combined_icons,
|
||||
combined_icon_count,
|
||||
prepared_icons,
|
||||
sizeof(prepared_icons) / sizeof(prepared_icons[0]));
|
||||
esp_err_t err = ESP_OK;
|
||||
@@ -1424,6 +1478,33 @@ esp_err_t lcd_direct_stop_render_loop(int timeout_ms)
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
esp_err_t lcd_direct_set_status_overlay(const platform_lcd_status_overlay_t *overlay)
|
||||
{
|
||||
if (overlay == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
TaskHandle_t render_task = NULL;
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
changed = s_status_overlay.low_battery_visible != overlay->low_battery_visible ||
|
||||
s_status_overlay.charging_visible != overlay->charging_visible;
|
||||
s_status_overlay = *overlay;
|
||||
render_task = s_render_task;
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
|
||||
if (changed && render_task != NULL) {
|
||||
xTaskNotifyGive(render_task);
|
||||
}
|
||||
if (changed) {
|
||||
ESP_LOGI(TAG,
|
||||
"LCD battery overlays low=%d charging=%d",
|
||||
overlay->low_battery_visible,
|
||||
overlay->charging_visible);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_request_t *request)
|
||||
{
|
||||
if (!s_initialized || request == NULL) {
|
||||
|
||||
@@ -217,6 +217,12 @@ static const lcd_direct_icon_t s_wuxing_icons[] = {
|
||||
{
|
||||
.path = LCD_ASSET_BASE_PATH "icon_jin0.png",
|
||||
},
|
||||
{
|
||||
.path = LCD_ASSET_BASE_PATH "charging.png",
|
||||
},
|
||||
{
|
||||
.path = LCD_ASSET_BASE_PATH "lowbat.png",
|
||||
},
|
||||
};
|
||||
|
||||
static const lcd_direct_image_t *lcd_direct_frame_from_array(const lcd_direct_image_t *frames,
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "platform_battery_internal.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_TQ_BATTERY_ENABLE
|
||||
|
||||
#define BATTERY_ADC_SAMPLE_COUNT 16
|
||||
#define BATTERY_ADC_TRIM_COUNT 4
|
||||
#define BATTERY_VALID_MIN_MV 2500
|
||||
#define BATTERY_VALID_MAX_MV 4500
|
||||
#define BATTERY_ADC_SAMPLE_DELAY_US 200
|
||||
|
||||
static const char *TAG = "platform_battery";
|
||||
static adc_oneshot_unit_handle_t s_adc_handle;
|
||||
static adc_cali_handle_t s_cali_handle;
|
||||
static adc_channel_t s_adc_channel;
|
||||
static bool s_initialized;
|
||||
static StaticSemaphore_t s_adc_lock_buffer;
|
||||
static SemaphoreHandle_t s_adc_lock;
|
||||
static portMUX_TYPE s_adc_lock_init_guard = portMUX_INITIALIZER_UNLOCKED;
|
||||
static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static uint16_t s_load_ref_count[PLATFORM_BATTERY_LOAD_COUNT];
|
||||
static int64_t s_cooldown_until_us;
|
||||
|
||||
static bool lock_adc(void)
|
||||
{
|
||||
if (s_adc_lock == NULL) {
|
||||
portENTER_CRITICAL(&s_adc_lock_init_guard);
|
||||
if (s_adc_lock == NULL) {
|
||||
s_adc_lock = xSemaphoreCreateMutexStatic(&s_adc_lock_buffer);
|
||||
}
|
||||
portEXIT_CRITICAL(&s_adc_lock_init_guard);
|
||||
}
|
||||
return s_adc_lock != NULL &&
|
||||
xSemaphoreTake(s_adc_lock, pdMS_TO_TICKS(100)) == pdTRUE;
|
||||
}
|
||||
|
||||
static void unlock_adc(void)
|
||||
{
|
||||
(void)xSemaphoreGive(s_adc_lock);
|
||||
}
|
||||
|
||||
static uint64_t gpio_mask(int gpio_num)
|
||||
{
|
||||
return 1ULL << (unsigned)gpio_num;
|
||||
}
|
||||
|
||||
static void sort_ints(int *values, size_t count)
|
||||
{
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
int value = values[i];
|
||||
size_t j = i;
|
||||
while (j > 0U && values[j - 1U] > value) {
|
||||
values[j] = values[j - 1U];
|
||||
--j;
|
||||
}
|
||||
values[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t create_calibration(adc_unit_t unit, adc_channel_t channel)
|
||||
{
|
||||
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
||||
{
|
||||
adc_cali_curve_fitting_config_t config = {
|
||||
.unit_id = unit,
|
||||
.chan = channel,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
esp_err_t err = adc_cali_create_scheme_curve_fitting(&config, &s_cali_handle);
|
||||
if (err == ESP_OK) {
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGW(TAG, "Curve-fitting ADC calibration unavailable: %s", esp_err_to_name(err));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
|
||||
{
|
||||
adc_cali_line_fitting_config_t config = {
|
||||
.unit_id = unit,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
.default_vref = 0,
|
||||
};
|
||||
esp_err_t err = adc_cali_create_scheme_line_fitting(&config, &s_cali_handle);
|
||||
if (err == ESP_OK) {
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGW(TAG, "Line-fitting ADC calibration unavailable: %s", esp_err_to_name(err));
|
||||
}
|
||||
#endif
|
||||
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t platform_battery_init(void)
|
||||
{
|
||||
if (!lock_adc()) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
if (s_initialized) {
|
||||
unlock_adc();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (CONFIG_TQ_BATTERY_CHARGE_GPIO >= GPIO_NUM_MAX) {
|
||||
unlock_adc();
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (CONFIG_TQ_BATTERY_CHARGE_GPIO >= 0) {
|
||||
gpio_config_t charge_config = {
|
||||
.pin_bit_mask = gpio_mask(CONFIG_TQ_BATTERY_CHARGE_GPIO),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
esp_err_t gpio_err = gpio_config(&charge_config);
|
||||
if (gpio_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Configure TP4057 status GPIO failed: %s", esp_err_to_name(gpio_err));
|
||||
unlock_adc();
|
||||
return gpio_err;
|
||||
}
|
||||
}
|
||||
|
||||
adc_unit_t unit = ADC_UNIT_1;
|
||||
adc_channel_t channel = ADC_CHANNEL_0;
|
||||
esp_err_t err = adc_oneshot_io_to_channel(CONFIG_TQ_BATTERY_ADC_GPIO,
|
||||
&unit,
|
||||
&channel);
|
||||
if (err != ESP_OK || unit != ADC_UNIT_1) {
|
||||
ESP_LOGE(TAG,
|
||||
"Battery ADC GPIO%d is not a supported ADC1 input: %s",
|
||||
CONFIG_TQ_BATTERY_ADC_GPIO,
|
||||
esp_err_to_name(err));
|
||||
unlock_adc();
|
||||
return err != ESP_OK ? err : ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
adc_oneshot_unit_init_cfg_t unit_config = {
|
||||
.unit_id = unit,
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
err = adc_oneshot_new_unit(&unit_config, &s_adc_handle);
|
||||
if (err != ESP_OK) {
|
||||
unlock_adc();
|
||||
return err;
|
||||
}
|
||||
|
||||
adc_oneshot_chan_cfg_t channel_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
err = adc_oneshot_config_channel(s_adc_handle, channel, &channel_config);
|
||||
if (err != ESP_OK) {
|
||||
(void)adc_oneshot_del_unit(s_adc_handle);
|
||||
s_adc_handle = NULL;
|
||||
unlock_adc();
|
||||
return err;
|
||||
}
|
||||
|
||||
err = create_calibration(unit, channel);
|
||||
if (err != ESP_OK) {
|
||||
(void)adc_oneshot_del_unit(s_adc_handle);
|
||||
s_adc_handle = NULL;
|
||||
unlock_adc();
|
||||
return err;
|
||||
}
|
||||
|
||||
s_adc_channel = channel;
|
||||
s_initialized = true;
|
||||
ESP_LOGI(TAG,
|
||||
"Battery monitor ready ADC_GPIO=%d divider=%d/%d charge_GPIO=%d",
|
||||
CONFIG_TQ_BATTERY_ADC_GPIO,
|
||||
CONFIG_TQ_BATTERY_DIVIDER_HIGH_OHM,
|
||||
CONFIG_TQ_BATTERY_DIVIDER_LOW_OHM,
|
||||
CONFIG_TQ_BATTERY_CHARGE_GPIO);
|
||||
unlock_adc();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void snapshot_load_state(bool *out_active, bool *out_cooldown)
|
||||
{
|
||||
bool active = false;
|
||||
int64_t cooldown_until_us = 0;
|
||||
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
for (size_t i = 0; i < PLATFORM_BATTERY_LOAD_COUNT; ++i) {
|
||||
if (s_load_ref_count[i] > 0U) {
|
||||
active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cooldown_until_us = s_cooldown_until_us;
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
|
||||
*out_active = active;
|
||||
*out_cooldown = !active && esp_timer_get_time() < cooldown_until_us;
|
||||
}
|
||||
|
||||
esp_err_t platform_battery_read_sample(platform_battery_sample_t *out_sample)
|
||||
{
|
||||
if (out_sample == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
memset(out_sample, 0, sizeof(*out_sample));
|
||||
|
||||
esp_err_t err = platform_battery_init();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
if (!lock_adc()) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
int raw = 0;
|
||||
for (size_t i = 0; i < 4U; ++i) {
|
||||
err = adc_oneshot_read(s_adc_handle, s_adc_channel, &raw);
|
||||
if (err != ESP_OK) {
|
||||
unlock_adc();
|
||||
return err;
|
||||
}
|
||||
esp_rom_delay_us(BATTERY_ADC_SAMPLE_DELAY_US);
|
||||
}
|
||||
|
||||
int samples_mv[BATTERY_ADC_SAMPLE_COUNT];
|
||||
for (size_t i = 0; i < BATTERY_ADC_SAMPLE_COUNT; ++i) {
|
||||
err = adc_oneshot_read(s_adc_handle, s_adc_channel, &raw);
|
||||
if (err != ESP_OK) {
|
||||
unlock_adc();
|
||||
return err;
|
||||
}
|
||||
err = adc_cali_raw_to_voltage(s_cali_handle, raw, &samples_mv[i]);
|
||||
if (err != ESP_OK) {
|
||||
unlock_adc();
|
||||
return err;
|
||||
}
|
||||
esp_rom_delay_us(BATTERY_ADC_SAMPLE_DELAY_US);
|
||||
}
|
||||
|
||||
sort_ints(samples_mv, BATTERY_ADC_SAMPLE_COUNT);
|
||||
int64_t sum_mv = 0;
|
||||
for (size_t i = BATTERY_ADC_TRIM_COUNT;
|
||||
i < BATTERY_ADC_SAMPLE_COUNT - BATTERY_ADC_TRIM_COUNT;
|
||||
++i) {
|
||||
sum_mv += samples_mv[i];
|
||||
}
|
||||
int adc_mv = (int)(sum_mv /
|
||||
(BATTERY_ADC_SAMPLE_COUNT - 2 * BATTERY_ADC_TRIM_COUNT));
|
||||
int64_t battery_mv = (int64_t)adc_mv *
|
||||
((int64_t)CONFIG_TQ_BATTERY_DIVIDER_HIGH_OHM +
|
||||
CONFIG_TQ_BATTERY_DIVIDER_LOW_OHM) /
|
||||
CONFIG_TQ_BATTERY_DIVIDER_LOW_OHM;
|
||||
battery_mv = battery_mv * CONFIG_TQ_BATTERY_CALIBRATION_GAIN_PPM / 1000000LL +
|
||||
CONFIG_TQ_BATTERY_CALIBRATION_OFFSET_MV;
|
||||
|
||||
out_sample->adc_voltage_mv = adc_mv;
|
||||
out_sample->voltage_mv = battery_mv > INT_MAX ? INT_MAX : (int)battery_mv;
|
||||
out_sample->voltage_valid = out_sample->voltage_mv >= BATTERY_VALID_MIN_MV &&
|
||||
out_sample->voltage_mv <= BATTERY_VALID_MAX_MV;
|
||||
snapshot_load_state(&out_sample->high_load_active, &out_sample->cooldown_active);
|
||||
|
||||
if (CONFIG_TQ_BATTERY_CHARGE_GPIO < 0) {
|
||||
out_sample->charge_state = PLATFORM_BATTERY_CHARGE_UNKNOWN;
|
||||
} else {
|
||||
int level = gpio_get_level((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO);
|
||||
out_sample->charge_state = level == CONFIG_TQ_BATTERY_CHARGE_ACTIVE_LEVEL
|
||||
? PLATFORM_BATTERY_CHARGE_CHARGING
|
||||
: PLATFORM_BATTERY_CHARGE_NOT_CHARGING;
|
||||
}
|
||||
unlock_adc();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void platform_battery_load_begin(platform_battery_load_source_t source)
|
||||
{
|
||||
if (source < 0 || source >= PLATFORM_BATTERY_LOAD_COUNT) {
|
||||
return;
|
||||
}
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
if (s_load_ref_count[source] < UINT16_MAX) {
|
||||
++s_load_ref_count[source];
|
||||
}
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
}
|
||||
|
||||
void platform_battery_load_end(platform_battery_load_source_t source)
|
||||
{
|
||||
if (source < 0 || source >= PLATFORM_BATTERY_LOAD_COUNT) {
|
||||
return;
|
||||
}
|
||||
int64_t cooldown_until_us = esp_timer_get_time() +
|
||||
(int64_t)CONFIG_TQ_BATTERY_LOAD_COOLDOWN_SEC * 1000000LL;
|
||||
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
if (s_load_ref_count[source] > 0U) {
|
||||
--s_load_ref_count[source];
|
||||
if (cooldown_until_us > s_cooldown_until_us) {
|
||||
s_cooldown_until_us = cooldown_until_us;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
esp_err_t platform_battery_init(void)
|
||||
{
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t platform_battery_read_sample(platform_battery_sample_t *out_sample)
|
||||
{
|
||||
if (out_sample != NULL) {
|
||||
memset(out_sample, 0, sizeof(*out_sample));
|
||||
}
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
void platform_battery_load_begin(platform_battery_load_source_t source)
|
||||
{
|
||||
(void)source;
|
||||
}
|
||||
|
||||
void platform_battery_load_end(platform_battery_load_source_t source)
|
||||
{
|
||||
(void)source;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "platform_internal.h"
|
||||
#include "platform_battery_internal.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifndef CONFIG_TQ_STEPPER_ENABLE
|
||||
@@ -256,9 +257,16 @@ esp_err_t platform_stepper_rotate_angle_with_accel_timeout(float angle,
|
||||
}
|
||||
}
|
||||
|
||||
return platform_stepper_driver_rotate_angle_with_accel(angle,
|
||||
target_delay_us,
|
||||
timeout_ms);
|
||||
if (angle == 0.0f) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
platform_battery_load_begin(PLATFORM_BATTERY_LOAD_STEPPER);
|
||||
esp_err_t err = platform_stepper_driver_rotate_angle_with_accel(angle,
|
||||
target_delay_us,
|
||||
timeout_ms);
|
||||
platform_battery_load_end(PLATFORM_BATTERY_LOAD_STEPPER);
|
||||
return err;
|
||||
}
|
||||
|
||||
void platform_stepper_power_off(void)
|
||||
|
||||
@@ -22,6 +22,11 @@ esp_err_t platform_stop_lcd_render_loop(int timeout_ms)
|
||||
return lcd_direct_stop_render_loop(timeout_ms);
|
||||
}
|
||||
|
||||
esp_err_t platform_lcd_set_status_overlay(const platform_lcd_status_overlay_t *overlay)
|
||||
{
|
||||
return lcd_direct_set_status_overlay(overlay);
|
||||
}
|
||||
|
||||
esp_err_t platform_lcd_request_presentation(const platform_lcd_presentation_request_t *request)
|
||||
{
|
||||
return lcd_direct_request_presentation(request);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "multimodal/audio_pipeline.h"
|
||||
#include "multimodal/transport_ws.h"
|
||||
#include "platform_battery_internal.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
|
||||
@@ -17,6 +18,38 @@ static platform_multimodal_transport_callbacks_t s_multimodal_transport_callback
|
||||
static portMUX_TYPE s_multimodal_transport_cb_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static platform_multimodal_downstream_playback_callbacks_t s_multimodal_downstream_playback_callbacks;
|
||||
static portMUX_TYPE s_multimodal_downstream_playback_cb_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static uint32_t s_battery_playback_generation;
|
||||
static bool s_battery_playback_active;
|
||||
|
||||
static void platform_battery_note_playback_event(
|
||||
multimodal_audio_downstream_playback_event_type_t type,
|
||||
uint32_t generation)
|
||||
{
|
||||
bool begin_load = false;
|
||||
bool end_load = false;
|
||||
|
||||
portENTER_CRITICAL(&s_multimodal_downstream_playback_cb_lock);
|
||||
if (type == MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STARTED) {
|
||||
if (!s_battery_playback_active) {
|
||||
s_battery_playback_active = true;
|
||||
begin_load = true;
|
||||
}
|
||||
s_battery_playback_generation = generation;
|
||||
} else if (s_battery_playback_active &&
|
||||
s_battery_playback_generation == generation) {
|
||||
s_battery_playback_active = false;
|
||||
s_battery_playback_generation = 0U;
|
||||
end_load = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_multimodal_downstream_playback_cb_lock);
|
||||
|
||||
if (begin_load) {
|
||||
platform_battery_load_begin(PLATFORM_BATTERY_LOAD_AUDIO);
|
||||
}
|
||||
if (end_load) {
|
||||
platform_battery_load_end(PLATFORM_BATTERY_LOAD_AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
static void snapshot_transport_callbacks(
|
||||
platform_multimodal_transport_callbacks_t *out_callbacks)
|
||||
@@ -228,6 +261,8 @@ static void platform_multimodal_downstream_playback_bridge(
|
||||
return;
|
||||
}
|
||||
|
||||
platform_battery_note_playback_event(event->type, event->playback_generation);
|
||||
|
||||
switch (event->type) {
|
||||
case MULTIMODAL_AUDIO_DOWNSTREAM_PLAYBACK_STARTED:
|
||||
if (callbacks.on_started != NULL) {
|
||||
|
||||
Reference in New Issue
Block a user