基本功能已完成,还差功耗和架构优化
This commit is contained in:
@@ -0,0 +1,621 @@
|
||||
/******************************************************************************
|
||||
* @file app_cbc2.c
|
||||
* @brief CBC2 电源 / 充电 / 按键 / 配对 / DP 协调
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.24
|
||||
* @version V1.0.2
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.0.2, 2026.08.24, cyWu, 上电即绑定广播,与 81bd2f7 行为对齐
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_main.h"
|
||||
#include "app_cbc2.h"
|
||||
#include "app_nv.h"
|
||||
#include "app_led.h"
|
||||
#include "app_rope.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_dnd.h"
|
||||
#include "bsp_hw.h"
|
||||
#include "usr_le_api.h"
|
||||
#include "asm/power/power_api.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_CBC2]"
|
||||
#define LOG_ERROR_ENABLE
|
||||
#define LOG_DEBUG_ENABLE
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
extern void sys_enter_soft_poweroff(void *priv);
|
||||
|
||||
/*============================================================================*/
|
||||
/* 类型 */
|
||||
/*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
APP_LIFE_CHARGE_ONLY = 0,
|
||||
APP_LIFE_ON,
|
||||
APP_LIFE_PAIR
|
||||
} AppLife_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t inited;
|
||||
AppLife_t life;
|
||||
uint8_t power; /* DP0 APP 功能开关 */
|
||||
uint8_t play_mode;
|
||||
uint8_t battery;
|
||||
uint8_t charge_dp; /* 1 充电中 2 未充电 */
|
||||
uint8_t alm_low;
|
||||
uint8_t charge_led; /* 0 无 1 充 2 满 */
|
||||
uint8_t usb_raw;
|
||||
uint8_t usb_debounced;
|
||||
uint8_t usb_cnt;
|
||||
uint8_t restore_on; /* 插电前是否开机 */
|
||||
uint8_t full_latched;
|
||||
uint8_t from_deep_boot;
|
||||
uint8_t boot_key_lock; /* 开机按键未松开前,忽略短按/关机 */
|
||||
uint8_t key_raw;
|
||||
uint8_t key_stable;
|
||||
uint8_t key_cnt;
|
||||
uint8_t key_pressed;
|
||||
uint16_t key_hold_ms;
|
||||
uint8_t req_click;
|
||||
uint8_t req_poweroff;
|
||||
uint8_t req_pair;
|
||||
uint32_t pair_ms;
|
||||
uint32_t lowbat_ms;
|
||||
uint8_t lowbat_run;
|
||||
uint16_t vbat_mv;
|
||||
uint8_t pause_mode;
|
||||
uint8_t pause_cal;
|
||||
uint8_t pause_percent;
|
||||
uint8_t pause_percent_valid;
|
||||
uint8_t run_timer;
|
||||
} AppCbc2_t;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有变量 */
|
||||
/*============================================================================*/
|
||||
|
||||
static AppCbc2_t s_app;
|
||||
|
||||
/*============================================================================*/
|
||||
/* 私有函数 */
|
||||
/*============================================================================*/
|
||||
|
||||
static uint8_t app_cbc2_ctrl_allowed(void)
|
||||
{
|
||||
return (uint8_t)(s_app.life != APP_LIFE_CHARGE_ONLY);
|
||||
}
|
||||
|
||||
static void app_cbc2_apply_stop_reset(void)
|
||||
{
|
||||
app_mode_stop();
|
||||
app_rope_stop();
|
||||
s_app.play_mode = 0;
|
||||
}
|
||||
|
||||
static void app_cbc2_apply_play_mode(uint8_t mode, uint8_t blink)
|
||||
{
|
||||
if (mode > 6) {
|
||||
mode = 0;
|
||||
}
|
||||
app_rope_stop();
|
||||
if (mode == 0) {
|
||||
app_mode_stop();
|
||||
} else {
|
||||
app_mode_start(mode);
|
||||
}
|
||||
s_app.play_mode = mode;
|
||||
if (blink) {
|
||||
app_led_request_mode_blink();
|
||||
}
|
||||
log_info("play_mode=%d\n", mode);
|
||||
}
|
||||
|
||||
static void app_cbc2_do_poweroff(void)
|
||||
{
|
||||
log_info("enter deep off\n");
|
||||
app_mode_stop();
|
||||
bsp_hw_enter_sleep_io();
|
||||
app_nv_save_if_dirty();
|
||||
sys_enter_soft_poweroff(NULL);
|
||||
}
|
||||
|
||||
static void app_cbc2_boot_poweroff(void)
|
||||
{
|
||||
log_info("boot deep off\n");
|
||||
bsp_hw_enter_sleep_io();
|
||||
app_nv_save_if_dirty();
|
||||
power_set_soft_poweroff();
|
||||
}
|
||||
|
||||
static void app_cbc2_enter_pair(void)
|
||||
{
|
||||
log_info("enter pair mode\n");
|
||||
app_cbc2_apply_stop_reset();
|
||||
s_app.life = APP_LIFE_PAIR;
|
||||
s_app.power = 1;
|
||||
s_app.pair_ms = 0;
|
||||
s_app.from_deep_boot = 0;
|
||||
usr_var.usr_goto_pair = 1;
|
||||
usr_goto_pair_mode();
|
||||
}
|
||||
|
||||
static void app_cbc2_pair_end(uint8_t ok)
|
||||
{
|
||||
log_info("pair %s\n", ok ? "ok" : "fail");
|
||||
app_led_start_pair_result(ok);
|
||||
s_app.life = APP_LIFE_ON;
|
||||
s_app.power = 1;
|
||||
s_app.play_mode = 0;
|
||||
usr_var.usr_goto_pair = 0;
|
||||
}
|
||||
|
||||
static void app_cbc2_pause_functions(void)
|
||||
{
|
||||
s_app.pause_mode = s_app.play_mode;
|
||||
s_app.pause_cal = app_rope_is_calibrating();
|
||||
s_app.pause_percent_valid = 0;
|
||||
if (app_rope_is_busy() && !s_app.pause_cal && app_rope_is_calibrated()
|
||||
&& app_rope_get_turns()) {
|
||||
int32_t max_x = (int32_t)app_rope_get_turns() * 100;
|
||||
if (max_x > 0) {
|
||||
int32_t pct = app_rope_remaining_x100() * 100 / max_x;
|
||||
if (pct < 0) {
|
||||
pct = 0;
|
||||
}
|
||||
if (pct > 100) {
|
||||
pct = 100;
|
||||
}
|
||||
s_app.pause_percent = (uint8_t)pct;
|
||||
s_app.pause_percent_valid = 1;
|
||||
}
|
||||
}
|
||||
app_mode_stop();
|
||||
app_rope_stop();
|
||||
log_info("app power pause mode=%d cal=%d\n", s_app.pause_mode, s_app.pause_cal);
|
||||
}
|
||||
|
||||
static void app_cbc2_resume_functions(void)
|
||||
{
|
||||
log_info("app power resume mode=%d cal=%d\n", s_app.pause_mode, s_app.pause_cal);
|
||||
if (s_app.pause_cal) {
|
||||
app_rope_cal_start();
|
||||
} else if (s_app.pause_percent_valid) {
|
||||
app_rope_goto_percent(s_app.pause_percent);
|
||||
} else if (s_app.pause_mode >= 1 && s_app.pause_mode <= 6) {
|
||||
app_cbc2_apply_play_mode(s_app.pause_mode, 0);
|
||||
}
|
||||
s_app.pause_cal = 0;
|
||||
s_app.pause_percent_valid = 0;
|
||||
}
|
||||
|
||||
static uint8_t app_cbc2_bat_percent(uint16_t mv)
|
||||
{
|
||||
int32_t span = BOARD_VBAT_FULL_MV - BOARD_VBAT_EMPTY_MV;
|
||||
int32_t pct;
|
||||
|
||||
if (mv <= BOARD_VBAT_EMPTY_MV) {
|
||||
return 0;
|
||||
}
|
||||
if (mv >= BOARD_VBAT_FULL_MV) {
|
||||
return 100;
|
||||
}
|
||||
pct = ((int32_t)mv - BOARD_VBAT_EMPTY_MV) * 100 / span;
|
||||
if (pct < 0) {
|
||||
pct = 0;
|
||||
}
|
||||
if (pct > 100) {
|
||||
pct = 100;
|
||||
}
|
||||
return (uint8_t)pct;
|
||||
}
|
||||
|
||||
static void app_cbc2_sample_charge(void)
|
||||
{
|
||||
uint8_t usb = bsp_vpwr_is_online();
|
||||
|
||||
if (usb == s_app.usb_raw) {
|
||||
if (s_app.usb_cnt < 5) {
|
||||
s_app.usb_cnt++;
|
||||
}
|
||||
} else {
|
||||
s_app.usb_raw = usb;
|
||||
s_app.usb_cnt = 0;
|
||||
}
|
||||
|
||||
if (s_app.usb_cnt >= 5 && s_app.usb_debounced != s_app.usb_raw) {
|
||||
s_app.usb_debounced = s_app.usb_raw;
|
||||
if (s_app.usb_debounced) {
|
||||
s_app.restore_on = (uint8_t)(s_app.life != APP_LIFE_CHARGE_ONLY);
|
||||
s_app.full_latched = 0;
|
||||
app_cbc2_apply_stop_reset();
|
||||
s_app.life = APP_LIFE_CHARGE_ONLY;
|
||||
log_info("usb in, charge-only (restore_on=%d)\n", s_app.restore_on);
|
||||
} else {
|
||||
log_info("usb out full_latched=%d restore_on=%d\n",
|
||||
s_app.full_latched, s_app.restore_on);
|
||||
if (s_app.full_latched || !s_app.restore_on) {
|
||||
s_app.req_poweroff = 1;
|
||||
} else {
|
||||
s_app.life = APP_LIFE_ON;
|
||||
app_cbc2_apply_stop_reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s_app.usb_debounced) {
|
||||
if (bsp_chg_is_low()) {
|
||||
s_app.charge_led = 1;
|
||||
s_app.charge_dp = 1;
|
||||
s_app.full_latched = 0;
|
||||
} else {
|
||||
s_app.charge_led = 2;
|
||||
s_app.charge_dp = 2; /* 充满:DP 报未充电 */
|
||||
s_app.full_latched = 1;
|
||||
}
|
||||
usr_var.usr_power_charge_flag = 1;
|
||||
} else {
|
||||
s_app.charge_led = 0;
|
||||
s_app.charge_dp = 2;
|
||||
usr_var.usr_power_charge_flag = 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void app_cbc2_sample_battery(void)
|
||||
{
|
||||
uint16_t mv = bsp_vbat_mv();
|
||||
uint8_t alarm_on;
|
||||
uint8_t alarm_off;
|
||||
|
||||
s_app.vbat_mv = mv;
|
||||
s_app.battery = app_cbc2_bat_percent(mv);
|
||||
|
||||
alarm_on = BOARD_VBAT_LOW_MV;
|
||||
alarm_off = BOARD_VBAT_LOW_MV + BOARD_VBAT_LOW_HYST_MV;
|
||||
|
||||
if (mv <= BOARD_VBAT_EMPTY_MV && !s_app.usb_debounced) {
|
||||
if (s_app.life != APP_LIFE_CHARGE_ONLY) {
|
||||
log_info("vbat %d mV lock\n", mv);
|
||||
s_app.req_poweroff = 1;
|
||||
}
|
||||
s_app.alm_low = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_app.alm_low && mv < alarm_on) {
|
||||
s_app.alm_low = 1;
|
||||
s_app.lowbat_ms = 0;
|
||||
s_app.lowbat_run = 1;
|
||||
log_info("low battery warn %d mV\n", mv);
|
||||
} else if (s_app.alm_low && mv > alarm_off) {
|
||||
s_app.alm_low = 0;
|
||||
s_app.lowbat_run = 0;
|
||||
s_app.lowbat_ms = 0;
|
||||
}
|
||||
|
||||
if (s_app.usb_debounced || s_app.life == APP_LIFE_CHARGE_ONLY) {
|
||||
return;
|
||||
}
|
||||
if (s_app.lowbat_run && s_app.alm_low) {
|
||||
s_app.lowbat_ms += 50;
|
||||
if (s_app.lowbat_ms >= BOARD_LOWBAT_SLEEP_MS) {
|
||||
log_info("low battery 1min, deep off\n");
|
||||
s_app.req_poweroff = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void app_cbc2_handle_click(void)
|
||||
{
|
||||
uint8_t next;
|
||||
|
||||
if (s_app.life != APP_LIFE_ON) {
|
||||
return;
|
||||
}
|
||||
if (s_app.power == 0) {
|
||||
return;
|
||||
}
|
||||
if (app_rope_is_calibrating()) {
|
||||
return;
|
||||
}
|
||||
next = (uint8_t)((s_app.play_mode + 1) % 7);
|
||||
app_cbc2_apply_play_mode(next, 1);
|
||||
}
|
||||
|
||||
static void app_cbc2_key_hold_check(void)
|
||||
{
|
||||
if (!s_app.key_pressed || s_app.boot_key_lock) {
|
||||
return;
|
||||
}
|
||||
s_app.key_hold_ms++;
|
||||
if (s_app.life == APP_LIFE_CHARGE_ONLY || s_app.life == APP_LIFE_PAIR) {
|
||||
return;
|
||||
}
|
||||
if (s_app.life == APP_LIFE_ON && s_app.key_hold_ms == BOARD_KEY_POWER_MS) {
|
||||
s_app.req_poweroff = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void app_cbc2_key_tick_1ms(void)
|
||||
{
|
||||
uint8_t raw = bsp_key_is_pressed();
|
||||
|
||||
if (raw == s_app.key_raw) {
|
||||
if (s_app.key_cnt < BOARD_KEY_DEBOUNCE_MS) {
|
||||
s_app.key_cnt++;
|
||||
}
|
||||
} else {
|
||||
s_app.key_raw = raw;
|
||||
s_app.key_cnt = 0;
|
||||
}
|
||||
|
||||
if (s_app.key_cnt != BOARD_KEY_DEBOUNCE_MS) {
|
||||
app_cbc2_key_hold_check();
|
||||
return;
|
||||
}
|
||||
|
||||
if (raw && !s_app.key_pressed) {
|
||||
s_app.key_pressed = 1;
|
||||
s_app.key_hold_ms = 0;
|
||||
} else if (!raw && s_app.key_pressed) {
|
||||
if (s_app.boot_key_lock) {
|
||||
s_app.boot_key_lock = 0;
|
||||
s_app.key_pressed = 0;
|
||||
s_app.key_hold_ms = 0;
|
||||
log_info("boot key released\n");
|
||||
return;
|
||||
}
|
||||
if (s_app.life == APP_LIFE_ON &&
|
||||
s_app.power &&
|
||||
s_app.key_hold_ms < BOARD_KEY_CLICK_MS &&
|
||||
s_app.key_hold_ms >= 10) {
|
||||
s_app.req_click = 1;
|
||||
}
|
||||
s_app.key_pressed = 0;
|
||||
s_app.key_hold_ms = 0;
|
||||
}
|
||||
|
||||
app_cbc2_key_hold_check();
|
||||
}
|
||||
|
||||
static void app_cbc2_boot_decide(void)
|
||||
{
|
||||
s_app.usb_raw = bsp_vpwr_is_online();
|
||||
s_app.usb_debounced = s_app.usb_raw;
|
||||
s_app.usb_cnt = 5;
|
||||
s_app.vbat_mv = bsp_vbat_mv();
|
||||
|
||||
if (s_app.usb_debounced) {
|
||||
s_app.life = APP_LIFE_CHARGE_ONLY;
|
||||
s_app.restore_on = 0;
|
||||
s_app.power = 0;
|
||||
log_info("boot CHARGE_ONLY vbat=%d\n", s_app.vbat_mv);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_app.vbat_mv <= BOARD_VBAT_EMPTY_MV) {
|
||||
log_info("boot vbat %d too low\n", s_app.vbat_mv);
|
||||
app_cbc2_boot_poweroff();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 与 81bd2f7 一致:上电即绑定广播,不等按键、不 deep off */
|
||||
s_app.power = 1;
|
||||
s_app.play_mode = 0;
|
||||
s_app.from_deep_boot = 0;
|
||||
s_app.boot_key_lock = bsp_key_is_pressed() ? 1 : 0;
|
||||
s_app.key_pressed = s_app.boot_key_lock;
|
||||
s_app.key_hold_ms = 0;
|
||||
s_app.key_raw = s_app.key_pressed;
|
||||
s_app.key_stable = s_app.key_pressed;
|
||||
s_app.key_cnt = BOARD_KEY_DEBOUNCE_MS;
|
||||
usr_var.usr_goto_pair = 1;
|
||||
s_app.life = APP_LIFE_PAIR;
|
||||
s_app.pair_ms = 0;
|
||||
log_info("boot PAIR power-on bind adv\n");
|
||||
}
|
||||
|
||||
/*============================================================================*/
|
||||
/* 公有函数 */
|
||||
/*============================================================================*/
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_init(void)
|
||||
{
|
||||
if (s_app.inited) {
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
|
||||
memset(&s_app, 0, sizeof(s_app));
|
||||
s_app.charge_dp = 2;
|
||||
s_app.power = 0;
|
||||
|
||||
bsp_hw_init();
|
||||
app_nv_init();
|
||||
app_led_init();
|
||||
app_rope_init();
|
||||
app_mode_init();
|
||||
app_dnd_init();
|
||||
|
||||
app_cbc2_boot_decide();
|
||||
|
||||
s_app.inited = 1;
|
||||
sys_timer_add(NULL, app_cbc2_run, 50);
|
||||
|
||||
log_info("cbc2 init life=%d power=%d\n", s_app.life, s_app.power);
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_get_status(void)
|
||||
{
|
||||
return s_app.inited ? APP_CBC2_OK : APP_CBC2_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
void app_cbc2_tick_1ms(void)
|
||||
{
|
||||
AppLedCtx_t led;
|
||||
|
||||
if (!s_app.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
app_rope_tick_1ms();
|
||||
app_cbc2_key_tick_1ms();
|
||||
(void)bsp_aloft_is_active();
|
||||
|
||||
memset(&led, 0, sizeof(led));
|
||||
led.charge_led = s_app.charge_led;
|
||||
led.pairing = (uint8_t)(s_app.life == APP_LIFE_PAIR);
|
||||
led.app_power = s_app.power;
|
||||
led.low_bat = s_app.alm_low;
|
||||
led.life_on = (uint8_t)(s_app.life != APP_LIFE_CHARGE_ONLY);
|
||||
app_led_tick_1ms(&led);
|
||||
}
|
||||
|
||||
void app_cbc2_run(void *priv)
|
||||
{
|
||||
(void)priv;
|
||||
if (!s_app.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
app_cbc2_sample_charge();
|
||||
app_cbc2_sample_battery();
|
||||
app_dnd_run();
|
||||
app_mode_run();
|
||||
|
||||
if (s_app.life == APP_LIFE_PAIR) {
|
||||
s_app.pair_ms += 50;
|
||||
if (usr_get_pair_flag()) {
|
||||
app_cbc2_pair_end(1);
|
||||
} else if (s_app.pair_ms >= BOARD_PAIR_TIMEOUT_MS) {
|
||||
app_cbc2_pair_end(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 仅 BLE 已起来后才允许再调 usr_goto_pair_mode,避免和 bt_ble_init 抢广播 */
|
||||
if (s_app.req_pair) {
|
||||
s_app.req_pair = 0;
|
||||
if (s_app.life != APP_LIFE_CHARGE_ONLY) {
|
||||
app_cbc2_enter_pair();
|
||||
}
|
||||
}
|
||||
if (s_app.req_click) {
|
||||
s_app.req_click = 0;
|
||||
app_cbc2_handle_click();
|
||||
}
|
||||
if (s_app.req_poweroff) {
|
||||
s_app.req_poweroff = 0;
|
||||
app_cbc2_do_poweroff();
|
||||
}
|
||||
|
||||
app_nv_save_if_dirty();
|
||||
}
|
||||
|
||||
void app_cbc2_fill_report(AppCbc2Report_t *out)
|
||||
{
|
||||
if (!out) {
|
||||
return;
|
||||
}
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->power = s_app.power;
|
||||
out->play_mode = s_app.play_mode;
|
||||
out->calibration = app_rope_is_calibrating();
|
||||
out->numberturns = app_rope_get_turns();
|
||||
out->alm_low_battery = s_app.alm_low;
|
||||
out->battery = s_app.battery;
|
||||
out->charge = s_app.charge_dp;
|
||||
app_dnd_get_raw(out->dnd);
|
||||
}
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_ctrl_power(uint8_t on)
|
||||
{
|
||||
if (!app_cbc2_ctrl_allowed()) {
|
||||
log_info("ctrl power ignored (charge)\n");
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
if (s_app.life == APP_LIFE_PAIR) {
|
||||
return APP_CBC2_ERR_BUSY;
|
||||
}
|
||||
on = on ? 1 : 0;
|
||||
if (on == s_app.power) {
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
if (on) {
|
||||
s_app.power = 1;
|
||||
app_cbc2_resume_functions();
|
||||
} else {
|
||||
app_cbc2_pause_functions();
|
||||
s_app.power = 0;
|
||||
}
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_ctrl_play_mode(uint8_t mode)
|
||||
{
|
||||
if (!app_cbc2_ctrl_allowed() || s_app.power == 0) {
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
if (app_rope_is_calibrating()) {
|
||||
return APP_CBC2_ERR_BUSY;
|
||||
}
|
||||
app_cbc2_apply_play_mode(mode, (uint8_t)(mode != s_app.play_mode));
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_ctrl_hand(uint8_t reserved, uint8_t percent)
|
||||
{
|
||||
(void)reserved;
|
||||
if (!app_cbc2_ctrl_allowed() || s_app.power == 0) {
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
if (app_rope_is_calibrating()) {
|
||||
return APP_CBC2_ERR_BUSY;
|
||||
}
|
||||
app_cbc2_apply_stop_reset();
|
||||
if (app_rope_goto_percent(percent) != APP_ROPE_OK) {
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_ctrl_cal(uint8_t on)
|
||||
{
|
||||
if (!app_cbc2_ctrl_allowed() || s_app.power == 0) {
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
if (on) {
|
||||
app_mode_stop();
|
||||
s_app.play_mode = 0;
|
||||
if (app_rope_cal_start() != APP_ROPE_OK) {
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
} else {
|
||||
app_rope_cal_stop();
|
||||
}
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_ctrl_turns(uint8_t turns)
|
||||
{
|
||||
if (!app_cbc2_ctrl_allowed()) {
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
app_rope_set_turns(turns);
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
|
||||
AppCbc2_Ret_t app_cbc2_ctrl_dnd(const uint8_t raw[APP_CBC2_DND_LEN])
|
||||
{
|
||||
if (!raw) {
|
||||
return APP_CBC2_ERR_PARAM;
|
||||
}
|
||||
if (!app_cbc2_ctrl_allowed()) {
|
||||
return APP_CBC2_ERR_DENIED;
|
||||
}
|
||||
app_dnd_set_raw(raw);
|
||||
return APP_CBC2_OK;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/******************************************************************************
|
||||
* @file app_led.c
|
||||
* @brief CBC2 灯效:充电 > 配对 > APP 暂停 > 低电 > 切模式闪 > 开机常亮
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.21
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#include "app_led.h"
|
||||
#include "bsp_hw.h"
|
||||
#include "board_pin.h"
|
||||
|
||||
static uint8_t s_initialized;
|
||||
static uint16_t s_phase_ms;
|
||||
static uint8_t s_mode_left;
|
||||
static uint16_t s_mode_ms;
|
||||
static uint8_t s_mode_on;
|
||||
static uint16_t s_pair_res_ms;
|
||||
static uint8_t s_pair_res; /* 0 none 1 ok 2 fail */
|
||||
|
||||
/**
|
||||
* @brief 灯效初始化,先全灭
|
||||
* @return APP_LED_OK=成功
|
||||
*/
|
||||
AppLed_Ret_t app_led_init(void)
|
||||
{
|
||||
if (s_initialized) {
|
||||
return APP_LED_OK;
|
||||
}
|
||||
bsp_led_all_off();
|
||||
s_initialized = 1;
|
||||
return APP_LED_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询模块是否已初始化
|
||||
* @return APP_LED_OK=已初始化,APP_LED_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppLed_Ret_t app_led_get_status(void)
|
||||
{
|
||||
return s_initialized ? APP_LED_OK : APP_LED_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 请求切模式绿灯闪两下
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_request_mode_blink(void)
|
||||
{
|
||||
s_mode_left = BOARD_LED_MODE_BLINK_N;
|
||||
s_mode_ms = 0;
|
||||
s_mode_on = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 播放一次性配对结果灯:成功常绿,失败红绿交替
|
||||
* @param ok 1=成功,0=失败
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_start_pair_result(uint8_t ok)
|
||||
{
|
||||
s_pair_res = ok ? 1 : 2;
|
||||
s_pair_res_ms = ok ? BOARD_LED_PAIR_OK_MS : BOARD_LED_PAIR_FAIL_MS;
|
||||
s_phase_ms = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按优先级刷新灯,1ms 调用
|
||||
* @param ctx 当前充电/配对/开关/低电;为空则直接返回
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_tick_1ms(const AppLedCtx_t *ctx)
|
||||
{
|
||||
uint8_t red = 0;
|
||||
uint8_t green = 0;
|
||||
|
||||
if (!s_initialized || !ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_phase_ms++;
|
||||
|
||||
/* 1-2 充电独占 */
|
||||
if (ctx->charge_led == 1) {
|
||||
red = 1;
|
||||
green = 0;
|
||||
bsp_led_red_set(1);
|
||||
bsp_led_green_set(0);
|
||||
return;
|
||||
}
|
||||
if (ctx->charge_led == 2) {
|
||||
bsp_led_red_set(0);
|
||||
bsp_led_green_set(1);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 3 配对快闪 */
|
||||
if (ctx->pairing) {
|
||||
uint16_t t = s_phase_ms % (BOARD_LED_FAST_ON_MS + BOARD_LED_FAST_OFF_MS);
|
||||
green = (t < BOARD_LED_FAST_ON_MS) ? 1 : 0;
|
||||
bsp_led_red_set(0);
|
||||
bsp_led_green_set(green);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 4 配对结果一次性 */
|
||||
if (s_pair_res && s_pair_res_ms) {
|
||||
if (s_pair_res == 1) {
|
||||
green = 1;
|
||||
} else {
|
||||
uint16_t t = s_phase_ms % (BOARD_LED_ALT_MS * 2);
|
||||
if (t < BOARD_LED_ALT_MS) {
|
||||
red = 1;
|
||||
} else {
|
||||
green = 1;
|
||||
}
|
||||
}
|
||||
if (s_pair_res_ms > 0) {
|
||||
s_pair_res_ms--;
|
||||
if (s_pair_res_ms == 0) {
|
||||
s_pair_res = 0;
|
||||
}
|
||||
}
|
||||
bsp_led_red_set(red);
|
||||
bsp_led_green_set(green);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 5 APP 暂停:功能灯灭 */
|
||||
if (ctx->life_on && ctx->app_power == 0) {
|
||||
bsp_led_all_off();
|
||||
return;
|
||||
}
|
||||
|
||||
/* 6 低电 */
|
||||
if (ctx->life_on && ctx->low_bat && ctx->app_power) {
|
||||
uint16_t t = s_phase_ms % BOARD_LED_LOWBAT_PERIOD_MS;
|
||||
red = (t < BOARD_LED_LOWBAT_ON_MS) ? 1 : 0;
|
||||
bsp_led_red_set(red);
|
||||
bsp_led_green_set(0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 7 切模式闪 2 下 */
|
||||
if (s_mode_left) {
|
||||
s_mode_ms++;
|
||||
if (!s_mode_on) {
|
||||
if (s_mode_ms >= BOARD_LED_MODE_OFF_MS) {
|
||||
s_mode_ms = 0;
|
||||
s_mode_on = 1;
|
||||
}
|
||||
green = 0;
|
||||
} else {
|
||||
if (s_mode_ms >= BOARD_LED_MODE_ON_MS) {
|
||||
s_mode_ms = 0;
|
||||
s_mode_on = 0;
|
||||
s_mode_left--;
|
||||
}
|
||||
green = 1;
|
||||
}
|
||||
bsp_led_red_set(0);
|
||||
bsp_led_green_set(green);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 8 开机绿灯常亮 */
|
||||
if (ctx->life_on && ctx->app_power) {
|
||||
bsp_led_red_set(0);
|
||||
bsp_led_green_set(1);
|
||||
return;
|
||||
}
|
||||
|
||||
bsp_led_all_off();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/******************************************************************************
|
||||
* @file app_led.h
|
||||
* @brief CBC2 灯效优先级:充电 > 配对 > 软关 > 低电 > 切模式闪 > 绿灯
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.21
|
||||
* @version V1.0.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_LED_H__
|
||||
#define __APP_LED_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
APP_LED_OK = 0,
|
||||
APP_LED_ERR_NOT_INIT
|
||||
} AppLed_Ret_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t charge_led; /**< 0=无 1=充电红常亮 2=充满绿常亮 */
|
||||
uint8_t pairing; /**< 1=配对中绿快闪 */
|
||||
uint8_t app_power; /**< DP0,为 0 则功能灯灭 */
|
||||
uint8_t low_bat; /**< 1=低电红闪 */
|
||||
uint8_t life_on; /**< 1=允许显示开机灯效 */
|
||||
} AppLedCtx_t;
|
||||
|
||||
/**
|
||||
* @brief 灯效初始化,先全灭
|
||||
* @return APP_LED_OK=成功
|
||||
*/
|
||||
AppLed_Ret_t app_led_init(void);
|
||||
|
||||
/**
|
||||
* @brief 1ms 按优先级刷新灯
|
||||
* @param ctx 充电/配对/开关/低电状态,不可为空
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_tick_1ms(const AppLedCtx_t *ctx);
|
||||
|
||||
/**
|
||||
* @brief 请求切模式绿灯闪两下
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_request_mode_blink(void);
|
||||
|
||||
/**
|
||||
* @brief 播放一次性配对结果灯
|
||||
* @param ok 1=成功常绿,0=失败红绿交替
|
||||
* @return 无
|
||||
*/
|
||||
void app_led_start_pair_result(uint8_t ok);
|
||||
|
||||
/**
|
||||
* @brief 查询是否已初始化
|
||||
* @return APP_LED_OK=已初始化,APP_LED_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppLed_Ret_t app_led_get_status(void);
|
||||
|
||||
#endif /* __APP_LED_H__ */
|
||||
@@ -0,0 +1,213 @@
|
||||
/******************************************************************************
|
||||
* @file app_mode.c
|
||||
* @brief 自动模式 1~6:按节拍改方向与 PWM 占空比
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.24
|
||||
* @version V1.2.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.1.0, 2026.08.24, cyWu, 接入 H 桥往返节拍
|
||||
* - V1.2.0, 2026.08.24, cyWu, 节拍带占空比;六档波形加花样
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_mode.h"
|
||||
#include "bsp_hw.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[APP_MODE]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
typedef struct {
|
||||
BspMotorDir_t dir;
|
||||
uint16_t ms;
|
||||
uint16_t duty; /**< 0~10000,STOP 忽略 */
|
||||
} AppModeStep_t;
|
||||
|
||||
#define DUTY_SOFT 3500 /* 轻 */
|
||||
#define DUTY_MID 5500 /* 中 */
|
||||
#define DUTY_RUN 8000 /* 主速度 80% */
|
||||
#define DUTY_PUNCH 9500 /* 猛一下 */
|
||||
|
||||
#define STEP_OUT(ms, duty) { BSP_MOTOR_OUT, (ms), (duty) }
|
||||
#define STEP_IN(ms, duty) { BSP_MOTOR_IN, (ms), (duty) }
|
||||
#define STEP_STOP(ms) { BSP_MOTOR_STOP, (ms), 0 }
|
||||
#define STEP_END { BSP_MOTOR_STOP, 0, 0 }
|
||||
|
||||
/* 节拍以 ms=0 结尾,循环播放。同向连续步进只改占空比,换向才刹车。 */
|
||||
|
||||
static const AppModeStep_t s_mode1[] = { /* 弹力呼吸:慢加速放、慢加速收,最长 2s */
|
||||
STEP_OUT(600, DUTY_MID), STEP_OUT(2000, DUTY_RUN), STEP_OUT(500, DUTY_PUNCH),
|
||||
STEP_STOP(400),
|
||||
STEP_IN(600, DUTY_MID), STEP_IN(2000, DUTY_RUN), STEP_IN(500, DUTY_PUNCH),
|
||||
STEP_STOP(400),
|
||||
STEP_END
|
||||
};
|
||||
|
||||
static const AppModeStep_t s_mode2[] = { /* 飞鸟俯冲:快冲出去,2s 慢慢收回 */
|
||||
STEP_OUT(150, DUTY_PUNCH), STEP_OUT(250, DUTY_RUN),
|
||||
STEP_STOP(150),
|
||||
STEP_IN(2000, DUTY_SOFT), STEP_IN(800, DUTY_MID),
|
||||
STEP_STOP(400),
|
||||
STEP_END
|
||||
};
|
||||
|
||||
static const AppModeStep_t s_mode3[] = { /* 云阶陀螺:短步往返加长,力度一档档加 */
|
||||
STEP_OUT(220, DUTY_SOFT), STEP_IN(220, DUTY_SOFT),
|
||||
STEP_OUT(220, DUTY_MID), STEP_IN(220, DUTY_MID),
|
||||
STEP_OUT(220, DUTY_RUN), STEP_IN(220, DUTY_RUN),
|
||||
STEP_STOP(400),
|
||||
STEP_END
|
||||
};
|
||||
|
||||
static const AppModeStep_t s_mode4[] = { /* 起伏攻防:2s 慢逗再猛收 */
|
||||
STEP_OUT(500, DUTY_SOFT), STEP_OUT(2000, DUTY_MID), STEP_OUT(400, DUTY_RUN),
|
||||
STEP_STOP(250),
|
||||
STEP_IN(180, DUTY_PUNCH), STEP_IN(400, DUTY_MID),
|
||||
STEP_STOP(500),
|
||||
STEP_END
|
||||
};
|
||||
|
||||
static const AppModeStep_t s_mode5[] = { /* 微风拂动:轻点后歇 2s */
|
||||
STEP_OUT(200, DUTY_MID), STEP_STOP(2000),
|
||||
STEP_IN(200, DUTY_MID), STEP_STOP(2000),
|
||||
STEP_OUT(400, DUTY_RUN), STEP_STOP(800),
|
||||
STEP_IN(400, DUTY_RUN), STEP_STOP(1500),
|
||||
STEP_END
|
||||
};
|
||||
|
||||
static const AppModeStep_t s_mode6[] = { /* 闪电快闪:两下轻、两下重,短停防 LVD */
|
||||
STEP_OUT(200, DUTY_MID), STEP_IN(200, DUTY_MID),
|
||||
STEP_OUT(400, DUTY_RUN), STEP_IN(400, DUTY_RUN),
|
||||
STEP_OUT(100, DUTY_PUNCH), STEP_IN(100, DUTY_PUNCH),
|
||||
STEP_OUT(100, DUTY_PUNCH), STEP_IN(100, DUTY_PUNCH),
|
||||
STEP_STOP(300),
|
||||
STEP_END
|
||||
};
|
||||
|
||||
static const AppModeStep_t *const s_tables[7] = {
|
||||
0, s_mode1, s_mode2, s_mode3, s_mode4, s_mode5, s_mode6
|
||||
};
|
||||
|
||||
static uint8_t s_initialized;
|
||||
static uint8_t s_active;
|
||||
static uint8_t s_step;
|
||||
static u32 s_step_t0;
|
||||
|
||||
extern u32 timer_get_ms(void);
|
||||
|
||||
/**
|
||||
* @brief 输出当前节拍的方向与占空比;ms=0 表示表尾,回到第 0 步循环
|
||||
* @return 无
|
||||
*/
|
||||
static void app_mode_apply_step(void)
|
||||
{
|
||||
const AppModeStep_t *tbl = s_tables[s_active];
|
||||
|
||||
if (!tbl) {
|
||||
bsp_motor_set(BSP_MOTOR_STOP);
|
||||
return;
|
||||
}
|
||||
if (tbl[s_step].ms == 0) {
|
||||
s_step = 0;
|
||||
}
|
||||
s_step_t0 = timer_get_ms();
|
||||
if (tbl[s_step].dir == BSP_MOTOR_STOP) {
|
||||
bsp_motor_set(BSP_MOTOR_STOP);
|
||||
} else {
|
||||
bsp_motor_set_pwm(tbl[s_step].dir, tbl[s_step].duty);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 玩法初始化,电机停
|
||||
* @return APP_MODE_OK=成功
|
||||
*/
|
||||
AppMode_Ret_t app_mode_init(void)
|
||||
{
|
||||
s_active = 0;
|
||||
s_step = 0;
|
||||
s_step_t0 = timer_get_ms();
|
||||
s_initialized = 1;
|
||||
bsp_motor_set(BSP_MOTOR_STOP);
|
||||
return APP_MODE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询模块是否已初始化
|
||||
* @return APP_MODE_OK=已初始化,APP_MODE_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppMode_Ret_t app_mode_get_status(void)
|
||||
{
|
||||
return s_initialized ? APP_MODE_OK : APP_MODE_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取当前自动模式
|
||||
* @return 0=停转,1~6=对应玩法
|
||||
*/
|
||||
uint8_t app_mode_get_active(void)
|
||||
{
|
||||
return s_active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启动自动模式 1~6,按节拍表循环
|
||||
* @param mode 玩法编号,范围 1~6
|
||||
* @return APP_MODE_OK=成功,APP_MODE_ERR_NOT_INIT=未初始化,APP_MODE_ERR_PARAM=参数非法
|
||||
*/
|
||||
AppMode_Ret_t app_mode_start(uint8_t mode)
|
||||
{
|
||||
if (!s_initialized) {
|
||||
return APP_MODE_ERR_NOT_INIT;
|
||||
}
|
||||
if (mode < 1 || mode > 6) {
|
||||
app_mode_stop();
|
||||
return APP_MODE_ERR_PARAM;
|
||||
}
|
||||
s_active = mode;
|
||||
s_step = 0;
|
||||
app_mode_apply_step();
|
||||
log_info("auto mode %d start\n", mode);
|
||||
return APP_MODE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 停止当前玩法并刹车
|
||||
* @return 无
|
||||
*/
|
||||
void app_mode_stop(void)
|
||||
{
|
||||
if (s_active) {
|
||||
log_info("auto mode %d stop\n", s_active);
|
||||
}
|
||||
s_active = 0;
|
||||
s_step = 0;
|
||||
s_step_t0 = timer_get_ms();
|
||||
bsp_motor_set(BSP_MOTOR_STOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推进当前节拍(按 timer_get_ms 真实时间)
|
||||
* @return 无
|
||||
*/
|
||||
void app_mode_run(void)
|
||||
{
|
||||
const AppModeStep_t *tbl;
|
||||
|
||||
bsp_motor_brake_tick();
|
||||
if (!s_initialized || s_active == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
tbl = s_tables[s_active];
|
||||
if (!tbl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((timer_get_ms() - s_step_t0) >= tbl[s_step].ms) {
|
||||
s_step++;
|
||||
app_mode_apply_step();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/******************************************************************************
|
||||
* @file app_mode.h
|
||||
* @brief 6 种自动玩法:节拍含方向与 PWM 占空比(0=停,1~6=波形)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.24
|
||||
* @version V1.2.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.1.0, 2026.08.24, cyWu, 接入 H 桥往返节拍
|
||||
* - V1.2.0, 2026.08.24, cyWu, 节拍带占空比;六档波形加花样
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef __APP_MODE_H__
|
||||
#define __APP_MODE_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
APP_MODE_OK = 0,
|
||||
APP_MODE_ERR_PARAM,
|
||||
APP_MODE_ERR_NOT_INIT
|
||||
} AppMode_Ret_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化,电机停
|
||||
* @return APP_MODE_OK=成功
|
||||
*/
|
||||
AppMode_Ret_t app_mode_init(void);
|
||||
|
||||
/**
|
||||
* @brief 启动自动模式 1~6,循环播放节拍
|
||||
* @param mode 玩法编号,范围 1~6
|
||||
* @return APP_MODE_OK=成功,APP_MODE_ERR_NOT_INIT=未初始化,APP_MODE_ERR_PARAM=参数非法
|
||||
*/
|
||||
AppMode_Ret_t app_mode_start(uint8_t mode);
|
||||
|
||||
/**
|
||||
* @brief 停止玩法并刹车
|
||||
* @return 无
|
||||
*/
|
||||
void app_mode_stop(void);
|
||||
|
||||
/**
|
||||
* @brief 推进当前节拍(按真实毫秒,不依赖回调是否刚好 1ms)
|
||||
* @return 无
|
||||
*/
|
||||
void app_mode_run(void);
|
||||
|
||||
/**
|
||||
* @brief 读取当前自动模式
|
||||
* @return 0=停转,1~6=对应玩法
|
||||
*/
|
||||
uint8_t app_mode_get_active(void);
|
||||
|
||||
/**
|
||||
* @brief 查询是否已初始化
|
||||
* @return APP_MODE_OK=已初始化,APP_MODE_ERR_NOT_INIT=未初始化
|
||||
*/
|
||||
AppMode_Ret_t app_mode_get_status(void);
|
||||
|
||||
#endif /* __APP_MODE_H__ */
|
||||
Reference in New Issue
Block a user