基本功能已完成,还差功耗和架构优化

This commit is contained in:
2026-08-24 18:03:39 +08:00
parent 81bd2f724d
commit 0e6a93c12d
25 changed files with 4847 additions and 33 deletions
+14
View File
@@ -177,6 +177,7 @@
<Add directory="apps/usr_jb_proto/Protocol" />
<Add directory="apps/usr_jb_proto/Utils" />
<Add directory="apps/usr_periph" />
<Add directory="apps/usr_app" />
</Compiler>
<Linker>
<Add option="-flto" />
@@ -783,6 +784,19 @@
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_periph/usr_rtc.h" />
<Unit filename="apps/usr_periph/board_pin.h" />
<Unit filename="apps/usr_periph/bsp_hw.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_periph/bsp_hw.h" />
<Unit filename="apps/usr_app/app_led.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_app/app_led.h" />
<Unit filename="apps/usr_app/app_mode.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_app/app_mode.h" />
<Unit filename="apps/usr_le_code/ble_proto.h" />
<Unit filename="apps/usr_le_code/bleproto.h" />
<Unit filename="apps/usr_le_code/bleproto_api.h" />
+4
View File
@@ -251,6 +251,7 @@ INCLUDES := \
-Iapps/usr_jb_proto/Protocol \
-Iapps/usr_jb_proto/Utils \
-Iapps/usr_periph \
-Iapps/usr_app \
-I$(SYS_INC_DIR) \
@@ -621,6 +622,9 @@ c_SRC_FILES := \
apps/usr_jb_proto/Utils/jb_ringbuffer.c \
apps/usr_jb_proto/usr_jb_main.c \
apps/usr_periph/usr_rtc.c \
apps/usr_periph/bsp_hw.c \
apps/usr_app/app_led.c \
apps/usr_app/app_mode.c \
# usr_le_code 预编译静态库(Release 不附带协议源码,禁止本地重建)
+3 -7
View File
@@ -156,7 +156,6 @@ extern void uart_dev_test_main();
extern int ana_demo();
extern void uart_dev_main_init();
extern void usr_check_mcu_all_sta();
extern void usr_clear_pair_info_noreset(void);
extern void usr_disable_intterupt();
extern void usr_disable_charge_intterupt();
u8 power_reset_flag=0;
@@ -327,13 +326,10 @@ void app_main()
}
/*
* 上电强制进入配对模式
* 1) usr_goto_pair=1:后续 usr_ble_adv_init() 走绑定广播(encode4bind),不走重连广播
* 2) usr_clear_pair_info_noreset():清 VM 中三元组,保证 paired_flag=0
* 注意:须在 BLE 协议栈调用 usr_ble_adv_init() 之前设置;此处不清复位,避免上电死循环复位
* 配对只在 usr_jb_init() 里、BLE 起来之前处理
* 深睡长按 5s → usr_goto_pair=1 + 清绑定;长按 3s 仅开机。
* 此处不要再强制配对,也不要在协议栈起来后调用 usr_goto_pair_mode()。
*/
usr_var.usr_goto_pair = 1;
usr_clear_pair_info_noreset();
ui_manage_init();
ui_update_status(STATUS_POWERON);
+621
View File
@@ -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;
}
+176
View File
@@ -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();
}
+61
View File
@@ -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__ */
+213
View File
@@ -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~10000STOP 忽略 */
} 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();
}
}
+61
View File
@@ -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__ */
+5 -1
View File
@@ -127,7 +127,11 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
/* ── DP2 手动模式 (raw) ── */
case DP_ID_HAND_MODE:
memcpy(gDevData.hand_mode, ctrl->hand_mode, 2);
// TODO: 根据 gDevData.hand_mode 执行动作 (长度 2 字节)
/* 薄适配:通知业务层;03=停,00=空闲 */
{
extern void usr_dp_hand_write(uint8_t cmd);
usr_dp_hand_write(ctrl->hand_mode[0]);
}
break;
/* ── DP3 绳索校准 (bool) ── */
+783 -8
View File
@@ -1,16 +1,43 @@
/******************************************************************************
* @file usr_jb_main.c
* @brief CBC2 业务主循环:按键 / 充电 / 电量 / 灯 / 6 档玩法 / 手动点动
* @author cyWu <1917507415@qq.com>
* @date 2026.08.24
* @version V1.3.0
* @history
* - V1.0.0, 2026.08.21, cyWu, 首次发布
* - V1.1.0, 2026.08.24, cyWu, 收口未接线模块;DP2 仅第 1 字节点动 1s
* - V1.2.0, 2026.08.24, cyWu, 按键:深睡 3s 开机、5s 开机+配对;开机中 3s 深睡
* - V1.3.0, 2026.08.24, cyWu, DP200=空闲,01/02 转,03 停
*
* @note 协议生成文件 jb_product.c 不要填业务。APP 下行写 gDevData
* 本文件 1ms 轮询执行。usr_timer_loop 由 BLE 库回调,勿再注册第二份。
******************************************************************************/
#include "system/includes.h"
#include "app_config.h"
#include "app_main.h"
#include "app_task.h"
#include "user_cfg.h"
#include "vm.h"
#include "sys_time.h"
#include "jb_protocol.h"
#include "jb_product.h"
#include "jb_ringbuffer.h"
#include "jb_common.h"
#include "usr_le_product.h"
#include "usr_le_api.h"
#include "bsp_hw.h"
#include "app_led.h"
#include "app_mode.h"
#include "ui_manage.h"
#include "asm/power/power_api.h"
#include "asm/power/power_reset.h"
#include <string.h>
#define LOG_TAG_CONST APP
#define LOG_TAG "[USR_JB]"
#define LOG_INFO_ENABLE
#include "debug.h"
/* 向 usr_le_code 库提供产品身份(换产品只改 jb_protocol.h 宏即可) */
const uint8_t g_usr_le_prodcode[USR_LE_PRODCODE_LEN] = {
@@ -22,27 +49,775 @@ const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN] = {
JB_PRODUCT_CODE[3], JB_PRODUCT_CODE[4], JB_PRODUCT_CODE[5]
};
void usr_timer_1_ms();
static int jb_timer_id=0;
static void usr_timer_1_ms(void *priv);
void usr_timer_loop(void);
extern void clr_wdt(void);
extern u32 timer_get_ms(void);
void usr_jb_init()
static int jb_timer_id;
static uint8_t s_pairing; /**< 1=深睡 5s 进入配对中 */
static uint8_t s_play_mode; /**< 当前自动模式 0~6 */
static uint8_t s_key_raw;
static uint8_t s_key_cnt;
static uint8_t s_key_pressed;
static uint8_t s_boot_key_lock; /**< 上电按着键,松开前不当短按 */
static uint16_t s_key_hold_ms;
static uint16_t s_key_led_override_ms; /**< 切模式闪灯期间不显示配对快闪 */
static uint8_t s_req_poweroff;
static uint8_t s_usb_online;
static uint8_t s_charge_led; /**< 0=无 1=充电红 2=充满绿 */
static uint8_t s_charge_led_last;
static uint16_t s_full_ms;
static uint8_t s_app_power; /**< DP00=软关,电机/玩法停,BLE 仍在 */
static uint16_t s_bat_tick;
static uint16_t s_bat_acc;
static uint8_t s_bat_n;
static uint16_t s_bat_mv;
static uint8_t s_bat_pct;
static uint8_t s_low_bat;
static uint32_t s_low_ms;
static uint8_t s_empty_win;
static uint8_t s_low_enter_win;
static uint8_t s_low_exit_win;
static uint16_t s_bat_boot_ms;
static uint16_t s_bat_log_ms;
static uint8_t s_hand_busy; /**< 手动点动进行中 */
static uint8_t s_hand_cmd_last; /**< 边沿:同一 1/2 不重复启动 */
static uint16_t s_hand_ms;
static volatile uint8_t s_hand_write_pending; /**< DP2 刚写入(03 停转要靠事件) */
static volatile uint8_t s_hand_write_cmd;
static uint32_t s_pair_ms; /**< 配对已持续 ms,满 60s 判失败 */
#define BAT_SAMPLE_MS 100
#define BAT_AVG_N 8
#define BAT_BOOT_MS 3000 /* 上电 3s 内不因低电深睡 */
#define BAT_LOG_MS 10000
#define BAT_LOW_WIN 3 /* 连续约 2.4s 才进低电告警 */
#define BAT_EMPTY_WIN 5 /* 连续约 4s 才 3.2V 深睡 */
/**
* @brief 忙等若干毫秒,并喂狗
* @param ms 等待时长
* @return 无
*/
static void usr_boot_delay_ms(uint32_t ms)
{
uint32_t t0 = timer_get_ms();
while ((timer_get_ms() - t0) < ms) {
clr_wdt();
}
}
/**
* @brief BLE 起来前进深睡(1ms 定时器尚未启动)
* @return 无
*/
static void usr_boot_sleep(void)
{
log_info("boot sleep\n");
bsp_hw_enter_sleep_io();
power_set_soft_poweroff();
while (1) {
clr_wdt();
}
}
/**
* @brief 上电是否跳过 3s/5s 按键判定
* @return 1=USB / 软复位 / LVD / 看门狗,保持开机且不进配对
*/
static uint8_t usr_boot_skip_key_wait(void)
{
uint8_t i;
for (i = 0; i < 10; i++) {
if (bsp_chg_is_low() || bsp_vpwr_is_online()) {
log_info("boot usb, skip key wait\n");
return 1;
}
usr_boot_delay_ms(5);
}
if (cpu_reset_by_soft()
|| is_reset_source(MSYS_SOFT_RST)
|| is_reset_source(P33_SOFT_RST)
|| is_reset_source(P33_VDDIO_LVD_RST)
|| is_reset_source(P11_WDT_RST)) {
log_info("boot reset-keep, skip key wait\n");
return 1;
}
return 0;
}
/**
* @brief 深睡唤醒/上电:长按 3s 开机,长按 5s 开机并配对;不足 3s 松手再睡
* @note 必须在 BLE 起来之前调用。配对只置 usr_goto_pair,不调用 usr_goto_pair_mode
* @return 无
*/
static void usr_boot_key_wait(void)
{
uint32_t t0;
uint32_t elapsed;
uint8_t green_on = 0;
usr_var.usr_goto_pair = 0;
s_pairing = 0;
s_pair_ms = 0;
if (usr_boot_skip_key_wait()) {
return;
}
if (!bsp_key_is_pressed()) {
log_info("boot no key, sleep\n");
usr_boot_sleep();
}
t0 = timer_get_ms();
bsp_led_all_off();
log_info("boot key hold, wait 3s on / 5s pair\n");
while (bsp_key_is_pressed()) {
elapsed = timer_get_ms() - t0;
clr_wdt();
if (elapsed >= BOARD_KEY_PAIR_MS) {
usr_var.usr_goto_pair = 1;
usr_clear_pair_info_noreset();
s_pairing = 1;
s_pair_ms = 0;
log_info("boot hold %ums, power on + pair\n", (unsigned)elapsed);
return;
}
if (!green_on && (elapsed >= BOARD_KEY_POWER_MS)) {
green_on = 1;
bsp_led_green_set(1);
log_info("boot hold 3s, will power on\n");
}
}
elapsed = timer_get_ms() - t0;
if (elapsed < BOARD_KEY_POWER_MS) {
log_info("boot hold %ums < 3s, sleep\n", (unsigned)elapsed);
usr_boot_sleep();
}
log_info("boot hold %ums, power on\n", (unsigned)elapsed);
}
/**
* @brief 停手动点动(走电机反转刹车)
* @return 无
*/
static void usr_hand_stop(void)
{
s_hand_busy = 0;
s_hand_ms = 0;
bsp_motor_set(BSP_MOTOR_STOP);
}
/**
* @brief 停手动 + 自动模式,并清 DP1
* @return 无
*/
static void usr_motor_all_stop(void)
{
s_play_mode = 0;
gDevData.play_mode = 0;
app_mode_stop();
usr_hand_stop();
}
/**
* @brief DP2 写入钩子(jb_product 薄适配调用,不在此驱电机)
* @param cmd 第 1 字节:0=空闲,1=顺时针,2=逆时针,3=停
* @return 无
*/
void usr_dp_hand_write(uint8_t cmd)
{
s_hand_write_cmd = cmd;
s_hand_write_pending = 1;
}
/**
* @brief 软关:电机停、DP0=0,等 APP/短按再开。不断 BLE
* @return 无
*/
static void usr_app_soft_off(void)
{
s_app_power = 0;
gDevData.power = 0;
s_play_mode = 0;
gDevData.play_mode = 0;
app_mode_stop();
usr_hand_stop();
log_info("soft off, wait APP power on\n");
}
/**
* @brief 切换自动模式 0~6
* @param mode 0=停转,1~6=对应玩法
* @return 无
*/
static void usr_play_mode_apply(uint8_t mode)
{
if (mode > 6) {
mode = 0;
}
if (s_usb_online || !s_app_power || s_req_poweroff || s_hand_busy || s_pairing) {
mode = 0;
}
s_play_mode = mode;
gDevData.play_mode = mode;
if (mode == 0) {
app_mode_stop();
} else {
app_mode_start(mode);
}
}
/**
* @brief 跟随 APP 下发的 DP1 自动模式
* @return 无
*/
static void usr_play_mode_poll(void)
{
uint8_t mode;
if (s_usb_online || !s_app_power || s_req_poweroff || s_hand_busy || s_pairing) {
return;
}
mode = gDevData.play_mode;
if (mode > 6) {
mode = 0;
gDevData.play_mode = 0;
}
if (mode == s_play_mode) {
return;
}
usr_play_mode_apply(mode);
log_info("APP play_mode=%d\n", mode);
}
/**
* @brief 跟随 APP 下发的 DP0 开关
* @return 无
*/
static void usr_app_power_poll(void)
{
if (s_usb_online || s_req_poweroff) {
return;
}
if (gDevData.power && !s_app_power) {
s_app_power = 1;
log_info("APP power on\n");
} else if (!gDevData.power && s_app_power) {
usr_app_soft_off();
}
}
/**
* @brief DP2 手动:只看第 1 字节。0=空闲,1=顺时针,2=逆时针,3=停
* @note 00 00 是 DP 默认值,不当停止。百分比/力度、DP3、DP4 预留不接。
* @return 无
*/
static void usr_hand_poll(void)
{
uint8_t cmd;
uint8_t got_write = s_hand_write_pending;
if (got_write) {
s_hand_write_pending = 0;
cmd = s_hand_write_cmd;
} else {
cmd = gDevData.hand_mode[0];
}
if (s_usb_online || !s_app_power || s_req_poweroff || s_pairing) {
if (s_hand_busy || (got_write && (cmd == 3))) {
usr_motor_all_stop();
}
s_hand_cmd_last = cmd;
return;
}
if (cmd == 0) {
s_hand_cmd_last = 0;
return;
}
if (cmd == 3) {
if (got_write || s_hand_busy || app_mode_get_active()) {
usr_motor_all_stop();
log_info("hand stop cmd=3\n");
}
s_hand_cmd_last = 3;
return;
}
if ((cmd == 1 || cmd == 2) && (got_write || (cmd != s_hand_cmd_last))) {
s_play_mode = 0;
gDevData.play_mode = 0;
app_mode_stop();
s_hand_busy = 1;
s_hand_ms = 0;
/* 1=顺时针收绳,2=逆时针放绳(BOARD_MOTOR_IN_INA_HIGH=1 */
bsp_motor_set((cmd == 1) ? BSP_MOTOR_IN : BSP_MOTOR_OUT);
log_info("hand cmd=%d %s until 03\n", cmd,
(cmd == 1) ? "cw" : "ccw");
}
s_hand_cmd_last = cmd;
}
/**
* @brief 手动运行时推进刹车;停转改由 DP2 写 03
* @return 无
*/
static void usr_hand_tick_1ms(void)
{
if (!s_hand_busy) {
return;
}
bsp_motor_brake_tick();
if (s_hand_ms < 0xffff) {
s_hand_ms++;
}
}
/**
* @brief 深睡入口(须在 1ms 高优定时器之外调用)
* @param priv 未使用
* @return 无
*/
static void usr_do_poweroff(void *priv)
{
(void)priv;
log_info("enter deep off\n");
app_mode_stop();
usr_hand_stop();
bsp_hw_enter_sleep_io();
sys_enter_soft_poweroff(NULL);
}
/**
* @brief 请求深睡:先停电机,50ms 后真正关机
* @return 无
*/
static void usr_key_request_poweroff(void)
{
if (s_req_poweroff) {
return;
}
s_req_poweroff = 1;
log_info("request deep off\n");
app_mode_stop();
usr_hand_stop();
bsp_led_all_off();
sys_timeout_add(NULL, usr_do_poweroff, 50);
}
/**
* @brief 电压换算电量:4.2V=100%3.2V=0%
* @param mv 电池电压,单位 mV
* @return 电量百分比,范围 0~100
*/
static uint8_t usr_bat_percent(uint16_t mv)
{
int32_t span = (int32_t)BOARD_VBAT_FULL_MV - (int32_t)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;
}
/**
* @brief 电量采样与低电策略
* @note 电机运转时只记电压、不改百分比/告警,避免电流拉垮 VBAT 误报
* @return 无
*/
static void usr_bat_tick_1ms(void)
{
uint16_t mv;
uint8_t pct;
uint8_t motor_busy;
if (s_bat_boot_ms < BAT_BOOT_MS) {
s_bat_boot_ms++;
}
if (++s_bat_tick >= BAT_SAMPLE_MS) {
s_bat_tick = 0;
s_bat_acc += bsp_vbat_mv();
s_bat_n++;
if (s_bat_n >= BAT_AVG_N) {
mv = (uint16_t)(s_bat_acc / s_bat_n);
s_bat_acc = 0;
s_bat_n = 0;
s_bat_mv = mv;
motor_busy = (uint8_t)(app_mode_get_active() != 0 || s_hand_busy);
if (!motor_busy) {
pct = usr_bat_percent(mv);
if (pct != s_bat_pct) {
s_bat_pct = pct;
gDevData.battery = pct;
}
if (s_bat_boot_ms >= BAT_BOOT_MS) {
if (!s_usb_online && mv <= BOARD_VBAT_EMPTY_MV) {
if (s_empty_win < 0xFF) {
s_empty_win++;
}
if (s_empty_win >= BAT_EMPTY_WIN) {
s_low_bat = 1;
gDevData.alm_low_battery = 1;
if (!s_req_poweroff) {
log_info("vbat %dmV empty, deep off\n", mv);
usr_key_request_poweroff();
}
}
} else {
s_empty_win = 0;
}
if (mv < BOARD_VBAT_LOW_MV) {
s_low_exit_win = 0;
if (!s_low_bat && s_low_enter_win < 0xFF) {
s_low_enter_win++;
if (s_low_enter_win >= BAT_LOW_WIN) {
s_low_bat = 1;
s_low_ms = 0;
gDevData.alm_low_battery = 1;
log_info("low battery %dmV %d%%\n", mv, s_bat_pct);
}
}
} else if (mv > (BOARD_VBAT_LOW_MV + BOARD_VBAT_LOW_HYST_MV)) {
s_low_enter_win = 0;
if (s_low_bat) {
if (s_low_exit_win < 0xFF) {
s_low_exit_win++;
}
if (s_low_exit_win >= BAT_LOW_WIN) {
s_low_bat = 0;
s_low_ms = 0;
gDevData.alm_low_battery = 0;
log_info("battery recover %dmV %d%%\n", mv, s_bat_pct);
}
}
} else {
s_low_enter_win = 0;
s_low_exit_win = 0;
}
}
}
}
}
if (s_usb_online || s_req_poweroff || (s_bat_boot_ms < BAT_BOOT_MS)) {
s_low_ms = 0;
} else if (s_low_bat && (s_low_ms < BOARD_LOWBAT_SLEEP_MS)) {
s_low_ms++;
if (s_low_ms == BOARD_LOWBAT_SLEEP_MS) {
log_info("low battery 1min, deep off\n");
usr_key_request_poweroff();
}
}
if (++s_bat_log_ms >= BAT_LOG_MS) {
s_bat_log_ms = 0;
log_info("bat %d%% %dmV alm=%d\n", s_bat_pct, s_bat_mv, s_low_bat);
}
}
/**
* @brief 短按:自动模式 0→1→…→6→0
* @return 无
*/
static void usr_key_on_click(void)
{
if (s_hand_busy) {
log_info("key click ignored, hand busy\n");
return;
}
usr_play_mode_apply((uint8_t)((s_play_mode + 1) % 7));
app_led_request_mode_blink();
s_key_led_override_ms = (uint16_t)((BOARD_LED_MODE_OFF_MS + BOARD_LED_MODE_ON_MS)
* BOARD_LED_MODE_BLINK_N + 50);
log_info("key click play_mode=%d\n", s_play_mode);
}
/**
* @brief 按键状态初值
* @return 无
*/
static void usr_key_init(void)
{
s_play_mode = 0;
s_boot_key_lock = bsp_key_is_pressed() ? 1 : 0;
s_key_pressed = s_boot_key_lock;
s_key_raw = s_key_pressed;
s_key_cnt = BOARD_KEY_DEBOUNCE_MS;
s_key_hold_ms = 0;
if (s_boot_key_lock) {
log_info("boot key held, wait release\n");
}
}
/**
* @brief 按键消抖:开机后 1s 内短按切模式,按住 3s 深睡;充电时忽略
* @note 开机中即使按满 5s 也不进配对。配对只在深睡长按 5s、BLE 起来前判定
* @return 无
*/
static void usr_key_tick_1ms(void)
{
uint8_t raw;
if (s_req_poweroff) {
return;
}
raw = bsp_key_is_pressed();
if (raw == s_key_raw) {
if (s_key_cnt < BOARD_KEY_DEBOUNCE_MS) {
s_key_cnt++;
}
} else {
s_key_raw = raw;
s_key_cnt = 0;
}
if (s_key_cnt != BOARD_KEY_DEBOUNCE_MS) {
if (s_key_pressed && !s_boot_key_lock) {
s_key_hold_ms++;
}
return;
}
if (raw && !s_key_pressed) {
s_key_pressed = 1;
s_key_hold_ms = 0;
} else if (!raw && s_key_pressed) {
if (s_boot_key_lock) {
s_boot_key_lock = 0;
log_info("boot key released\n");
} else if (s_key_hold_ms < BOARD_KEY_CLICK_MS && s_key_hold_ms >= 10) {
if (s_usb_online) {
log_info("key click ignored, charging\n");
} else if (s_pairing) {
log_info("key click ignored, pairing\n");
} else if (!s_app_power) {
s_app_power = 1;
gDevData.power = 1;
log_info("key click cancel soft off\n");
} else {
usr_key_on_click();
}
}
s_key_pressed = 0;
s_key_hold_ms = 0;
}
if (s_key_pressed && !s_boot_key_lock) {
s_key_hold_ms++;
if (s_key_hold_ms == BOARD_KEY_POWER_MS) {
if (s_usb_online) {
log_info("key hold 3s ignored, charging\n");
} else if (s_pairing) {
log_info("key hold 3s ignored, pairing\n");
} else {
log_info("key hold 3s, poweroff\n");
usr_key_request_poweroff();
}
}
}
}
/**
* @brief 充电检测:CHG 低=充电中;VPWR 在且 CHG 高满 1s=充满;拔电软关
* @return 无
*/
static void usr_chg_tick_1ms(void)
{
uint8_t chg_low = bsp_chg_is_low();
uint8_t vpwr = bsp_vpwr_is_online();
uint8_t usb;
if (chg_low) {
s_full_ms = 0;
s_charge_led = 1;
gDevData.charge = CHARGE_1;
usb = 1;
} else if (vpwr) {
/* 拔电时 CHG 先变高、VPWR 还没掉,不能立刻当充满 */
if (s_full_ms < 0xFFFF) {
s_full_ms++;
}
if (s_full_ms >= BOARD_CHG_FULL_MS) {
s_charge_led = 2;
}
gDevData.charge = (s_charge_led == 1) ? CHARGE_1 : CHARGE_2;
usb = 1;
} else {
s_full_ms = 0;
s_charge_led = 0;
gDevData.charge = CHARGE_2;
usb = 0;
}
if (usb != s_usb_online) {
s_usb_online = usb;
log_info("usb %s vpwr=%d chg=%d mv=%d pair=%d goto=%d\n",
usb ? "in" : "out", vpwr, chg_low, bsp_vpwr_mv(),
usr_get_pair_flag(), usr_var.usr_goto_pair);
if (usb) {
app_mode_stop();
usr_hand_stop();
s_play_mode = 0;
gDevData.play_mode = 0;
log_info("usb in, motor stop\n");
} else {
ui_update_status(STATUS_NORMAL_POWER);
usr_app_soft_off();
}
}
usr_var.usr_power_charge_flag = s_usb_online ? 1 : 2;
if (s_charge_led != s_charge_led_last) {
s_charge_led_last = s_charge_led;
log_info("charge_led=%d chg=%d vpwr=%d mv=%d\n",
s_charge_led, chg_low, vpwr, bsp_vpwr_mv());
}
}
/**
* @brief 组装灯效上下文:充电 > 配对 > 软关灭灯 > 低电 > 切模式闪 > 绿灯
* @return 无
*/
static void usr_led_tick_1ms(void)
{
AppLedCtx_t led;
uint8_t paired;
if (s_req_poweroff) {
bsp_led_all_off();
return;
}
paired = usr_get_pair_flag();
if (s_pairing) {
if (paired) {
app_led_start_pair_result(1);
s_pairing = 0;
s_pair_ms = 0;
log_info("pair ok\n");
} else if (++s_pair_ms >= BOARD_PAIR_TIMEOUT_MS) {
s_pairing = 0;
s_pair_ms = 0;
app_led_start_pair_result(0);
log_info("pair timeout 60s, stay on\n");
}
}
if (s_key_led_override_ms) {
s_key_led_override_ms--;
}
memset(&led, 0, sizeof(led));
led.charge_led = s_charge_led;
led.pairing = (uint8_t)(s_pairing && s_app_power && (s_key_led_override_ms == 0));
led.app_power = s_app_power;
led.low_bat = s_low_bat;
led.life_on = 1;
app_led_tick_1ms(&led);
}
/**
* @brief 业务初始化(在 BLE 栈起来之前由 app_main 调用)
* @note 内部会等按键 3s/5s;不足 3s 直接深睡,不会启动 1ms 定时器
* @return 无
*/
void usr_jb_init(void)
{
jbInit();
userInit();
if(jb_timer_id==0)jb_timer_id = sys_s_hi_timer_add(NULL, usr_timer_1_ms, TIMER_PERIOD_MS);
bsp_hw_init();
app_led_init();
app_mode_init();
usr_boot_key_wait();
usr_key_init();
s_app_power = 1;
gDevData.power = 1;
s_hand_busy = 0;
s_hand_cmd_last = 0;
s_hand_write_pending = 0;
s_hand_write_cmd = 0;
s_charge_led_last = 0xff;
if (jb_timer_id == 0) {
jb_timer_id = sys_hi_timer_add(NULL, usr_timer_1_ms, TIMER_PERIOD_MS);
}
}
void usr_timer_1_ms()
/**
* @brief 约 1ms 高优定时器:按键/充电/电量/DP 轮询/电机节拍/灯
* @param priv 未使用
* @note 用 sys_hi_timerpriority=1)避免休眠把节拍拉长。禁止在这里直接 sys_enter_soft_poweroff
* @return 无
*/
static void usr_timer_1_ms(void *priv)
{
(void)priv;
jbTimerIrq();
usr_key_tick_1ms();
usr_chg_tick_1ms();
usr_bat_tick_1ms();
usr_app_power_poll();
usr_hand_poll();
usr_play_mode_poll();
if (s_hand_busy) {
usr_hand_tick_1ms();
} else {
app_mode_run();
}
usr_led_tick_1ms();
}
/**
* @brief BLE 收到见宝载荷,送入协议环形缓冲
* @param data 载荷指针
* @param len 载荷长度,单位字节
* @return 无
*/
void usr_jb_le_recieve_data(u8 *data, u16 len)
{
jbPutData(data, len);
}
void usr_timer_loop()///500ms
/**
* @brief 约 500ms:协议收包与 DP 上报(由 usr_le_code 库回调,不要再注册一份)
* @return 无
*/
void usr_timer_loop(void)
{
userHandle();
jbProtocolHandle(&gDevData);
+4
View File
@@ -24,16 +24,19 @@ extern "C" {
/**
* @brief 进入配对/配网模式
* @note 断开当前连接、清除配对信息(不复位),重新发绑定广播
* @return 无
*/
void usr_goto_pair_mode(void);
/**
* @brief 清除配对信息但不复位
* @return 无
*/
void usr_clear_pair_info_noreset(void);
/**
* @brief 清除配对信息并延时复位
* @return 无
*/
void usr_clear_pair_info(void);
@@ -46,6 +49,7 @@ uint8_t usr_get_pair_flag(void);
/**
* @brief 启动应用侧 OTA 流程
* @note 置位 usr_goto_updata,并创建 1s 周期定时器驱动断连与 OTA 广播切换
* @return 无
*/
void usr_app_ota_init(void);
+94
View File
@@ -0,0 +1,94 @@
/******************************************************************************
* @file board_pin.h
* @brief CBC2 门夹弹力绳板级引脚、电平与时序(换板只改本文件)
* @author cyWu <1917507415@qq.com>
* @date 2026.08.24
* @version V1.3.0
* @history
* - V1.0.0, 2026.08.21, cyWu, 首次发布
* - V1.1.0, 2026.08.24, cyWu, 收口:去掉实验开关与未贴件限位
* - V1.2.0, 2026.08.24, cyWu, 按键:深睡 3s 开机、5s 开机+配对
* - V1.3.0, 2026.08.24, cyWu, 电机改 MCPWM 50% 占空比降速
******************************************************************************/
#ifndef __BOARD_PIN_H__
#define __BOARD_PIN_H__
#include "asm/gpio.h"
/*============================================================================*/
/* 功能开关:某路硬件异常时可单独关掉,不影响配网 */
/*============================================================================*/
#define BOARD_LED_ENABLE 1 /**< 1=红绿 LED */
#define BOARD_KEY_ENABLE 1 /**< 1=按键 PB1 */
#define BOARD_CHG_ENABLE 1 /**< 1=充电检测 PG8 + VPWR */
#define BOARD_MOTOR_ENABLE 1 /**< 1=电机 PA6/PC2 */
/*============================================================================*/
/* GPIO */
/*============================================================================*/
#define BOARD_MOTOR_INA_PIN IO_PORTA_06 /**< 电机 INA */
#define BOARD_MOTOR_INB_PIN IO_PORTC_02 /**< 电机 INB */
#define BOARD_LED_RED_PIN IO_PORTA_08 /**< 红灯 LED_PWR,低电平亮 */
#define BOARD_LED_GREEN_PIN IO_PORTA_07 /**< 绿灯 LED_CHG,低电平亮 */
#define BOARD_KEY_PIN IO_PORTB_01 /**< 按键,低电平按下 */
#define BOARD_CHG_PIN IO_PORTG_08 /**< ME4054 CHG,外部 2.2k 上拉 */
/* PA3 限位未贴件,空脚不初始化 */
#define BOARD_LED_ON_LEVEL 0 /**< LED 点亮电平:0=低亮 */
#define BOARD_KEY_ACTIVE_LEVEL 0 /**< 按键有效电平:0=按下为低 */
#define BOARD_CHG_CHARGING_LEVEL 0 /**< CHG 充电中为低 */
/**
* 收绳方向:1 = INA 高 / INB 低 为收绳(顺时针)
* 若实测方向反了,把本宏改为 0
*/
#define BOARD_MOTOR_IN_INA_HIGH 1
/*============================================================================*/
/* 电量 / 充电 */
/*============================================================================*/
#define BOARD_VBAT_FULL_MV 4200 /**< 100% */
#define BOARD_VBAT_EMPTY_MV 3200 /**< 0%,持续低于此值深睡 */
#define BOARD_VBAT_LOW_MV 3700 /**< 低电预警阈值 */
#define BOARD_VBAT_LOW_HYST_MV 50 /**< 低电恢复回差 */
#define BOARD_VPWR_ONLINE_MV 4000 /**< VPWR 判定 USB 插入 */
#define BOARD_VPWR_OFF_MV 1500 /**< 低于此值认定已拔出 */
#define BOARD_CHG_FULL_MS 1000 /**< CHG 已高且 VPWR 在,满 1s 认充满 */
#define BOARD_LOWBAT_SLEEP_MS (60u * 1000u) /**< 低电告警满 1 分钟后深睡 */
/*============================================================================*/
/* 按键 / 灯效 */
/*============================================================================*/
#define BOARD_KEY_CLICK_MS 1000 /**< 开机后:按下 <1s 松开为短按切模式 */
#define BOARD_KEY_POWER_MS 3000 /**< 深睡:按住 3s 开机;开机中:按住 3s 深睡 */
#define BOARD_KEY_PAIR_MS 5000 /**< 深睡:按住 5s 开机并进配对(开机中继续按不住配对) */
#define BOARD_KEY_DEBOUNCE_MS 20 /**< 按键消抖 */
#define BOARD_PAIR_TIMEOUT_MS (60u * 1000u) /**< 配对超时,失败后保持开机 */
#define BOARD_LED_FAST_ON_MS 100 /**< 配对绿快闪:亮 */
#define BOARD_LED_FAST_OFF_MS 100 /**< 配对绿快闪:灭 */
#define BOARD_LED_ALT_MS 200 /**< 配对失败红绿交替半周期 */
#define BOARD_LED_PAIR_OK_MS 3000 /**< 配对成功绿灯常亮时长 */
#define BOARD_LED_PAIR_FAIL_MS 3000 /**< 配对失败提示时长 */
#define BOARD_LED_LOWBAT_ON_MS 200 /**< 低电红闪:亮 */
#define BOARD_LED_LOWBAT_PERIOD_MS 2000 /**< 低电红闪:周期 */
#define BOARD_LED_MODE_OFF_MS 150 /**< 切模式闪:灭 */
#define BOARD_LED_MODE_ON_MS 150 /**< 切模式闪:亮 */
#define BOARD_LED_MODE_BLINK_N 2 /**< 切模式闪次数 */
/*============================================================================*/
/* 电机 */
/*============================================================================*/
#define BOARD_MOTOR_REVERSE_MS 1 /**< 换向前先松开,防 H 桥直通 */
#define BOARD_MOTOR_STOP_BRAKE_MS 30 /**< 停转先反转刹车(ms),太长会倒转 */
#define BOARD_HAND_PULSE_MS 1000 /**< 预留;手动 1/2 一直转到写 03 停止 */
#define BOARD_MOTOR_PWM_HZ 20000 /**< MCPWM 频率(Hz)20k 避开可听啸叫 */
#define BOARD_MOTOR_PWM_DUTY 8000 /**< 占空比 0~10000=0%~100%80% */
#endif /* __BOARD_PIN_H__ */
+429
View File
@@ -0,0 +1,429 @@
/******************************************************************************
* @file bsp_hw.c
* @brief CBC2 板级 GPIO / ADC:电机 H 桥、LED、按键、充电检测
* @author cyWu <1917507415@qq.com>
* @date 2026.08.24
* @version V1.4.0
* @history
* - V1.0.0, 2026.08.21, cyWu, 首次发布
* - V1.1.0, 2026.08.24, cyWu, 停转反转刹车;去掉限位脚
* - V1.3.0, 2026.08.24, cyWu, 电机改 MCPWM 占空比降速
* - V1.4.0, 2026.08.24, cyWu, 增加按占空比驱动,供玩法变速
******************************************************************************/
#include "system/includes.h"
#include "asm/gpio.h"
#include "asm/adc_api.h"
#include "asm/charge.h"
#include "asm/mcpwm.h"
#include "bsp_hw.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[BSP_HW]"
#define LOG_ERROR_ENABLE
#define LOG_INFO_ENABLE
#include "debug.h"
static uint8_t s_initialized;
static BspMotorDir_t s_motor_dir;
static BspMotorDir_t s_motor_pending;
static uint16_t s_motor_duty;
static uint16_t s_pending_duty;
static uint16_t s_brake_ms;
static u32 s_brake_t0;
extern u32 timer_get_ms(void);
#if (BOARD_LED_ENABLE || BOARD_MOTOR_ENABLE)
/**
* @brief 配置 GPIO 为推挽输出
* @param pin GPIO 编号
* @param value 输出初值
* @return 无
*/
static void bsp_pin_output(u32 pin, int value)
{
gpio_set_pull_up(pin, 0);
gpio_set_pull_down(pin, 0);
gpio_set_die(pin, 1);
gpio_direction_output(pin, value);
}
#endif
#if (BOARD_KEY_ENABLE || BOARD_CHG_ENABLE)
/**
* @brief 配置 GPIO 为数字输入(可内部上拉)
* @param pin GPIO 编号
* @param pull_up 1=内部上拉,0=不上拉
* @return 无
*/
static void bsp_pin_input(u32 pin, int pull_up)
{
gpio_direction_output(pin, 1);
gpio_set_direction(pin, 1);
gpio_set_pull_down(pin, 0);
gpio_set_pull_up(pin, pull_up ? 1 : 0);
gpio_set_die(pin, 1);
}
#endif
#if BOARD_MOTOR_ENABLE
/**
* @brief 初始化 INA/INB 两路 MCPWM,初始占空比 0(停转)
* @return 无
*/
static void bsp_motor_pwm_init(void)
{
struct pwm_platform_data pwm;
gpio_set_pull_up(BOARD_MOTOR_INA_PIN, 0);
gpio_set_pull_down(BOARD_MOTOR_INA_PIN, 0);
gpio_set_die(BOARD_MOTOR_INA_PIN, 1);
gpio_set_pull_up(BOARD_MOTOR_INB_PIN, 0);
gpio_set_pull_down(BOARD_MOTOR_INB_PIN, 0);
gpio_set_die(BOARD_MOTOR_INB_PIN, 1);
pwm.pwm_aligned_mode = pwm_edge_aligned;
pwm.frequency = BOARD_MOTOR_PWM_HZ;
pwm.duty = 0;
pwm.l_pin = (u8) - 1;
pwm.complementary_en = 0;
pwm.pwm_ch_num = pwm_ch0;
pwm.h_pin = BOARD_MOTOR_INA_PIN;
mcpwm_init(&pwm);
pwm.pwm_ch_num = pwm_ch1;
pwm.h_pin = BOARD_MOTOR_INB_PIN;
mcpwm_init(&pwm);
mcpwm_set_duty(pwm_ch0, 0);
mcpwm_set_duty(pwm_ch1, 0);
log_info("motor pwm %uHz duty=%u/10000\n",
(unsigned)BOARD_MOTOR_PWM_HZ, (unsigned)BOARD_MOTOR_PWM_DUTY);
}
/**
* @brief 立刻改 INA/INB PWM。运转脚输出 duty,另一脚 0%STOP=两路 0%
* @param dir 目标方向
* @param duty 占空比 0~10000
* @return 无
*/
static void bsp_motor_gpio(BspMotorDir_t dir, uint16_t duty)
{
u16 ina_duty = 0;
u16 inb_duty = 0;
if (duty > 10000) {
duty = 10000;
}
if (dir == BSP_MOTOR_IN) {
if (BOARD_MOTOR_IN_INA_HIGH) {
ina_duty = duty;
} else {
inb_duty = duty;
}
} else if (dir == BSP_MOTOR_OUT) {
if (BOARD_MOTOR_IN_INA_HIGH) {
inb_duty = duty;
} else {
ina_duty = duty;
}
}
mcpwm_set_duty(pwm_ch0, ina_duty);
mcpwm_set_duty(pwm_ch1, inb_duty);
}
#endif
/**
* @brief 初始化 GPIO / 充电 ADC
* @return BSP_HW_OK=成功
*/
BspHw_Ret_t bsp_hw_init(void)
{
if (s_initialized) {
return BSP_HW_OK;
}
#if BOARD_LED_ENABLE
bsp_pin_output(BOARD_LED_RED_PIN, !BOARD_LED_ON_LEVEL);
bsp_pin_output(BOARD_LED_GREEN_PIN, !BOARD_LED_ON_LEVEL);
#endif
#if BOARD_KEY_ENABLE
bsp_pin_input(BOARD_KEY_PIN, 1);
#endif
#if BOARD_CHG_ENABLE
/* PORTG 作数字输入必须开 dieh,否则读不到 ME4054 CHG */
gpio_disable_fun_output_port(BOARD_CHG_PIN);
gpio_set_pull_up(BOARD_CHG_PIN, 0);
gpio_set_pull_down(BOARD_CHG_PIN, 0);
gpio_set_die(BOARD_CHG_PIN, 1);
gpio_set_dieh(BOARD_CHG_PIN, 1);
gpio_direction_input(BOARD_CHG_PIN);
adc_add_sample_ch(AD_CH_LDO5V);
adc_set_sample_freq(AD_CH_LDO5V, 500);
#endif
#if BOARD_MOTOR_ENABLE
bsp_motor_pwm_init();
s_motor_dir = BSP_MOTOR_STOP;
s_motor_pending = BSP_MOTOR_STOP;
s_motor_duty = 0;
s_pending_duty = 0;
s_brake_ms = 0;
#endif
s_initialized = 1;
log_info("hw init led=%d key=%d chg=%d motor=%d\n",
BOARD_LED_ENABLE, BOARD_KEY_ENABLE, BOARD_CHG_ENABLE,
BOARD_MOTOR_ENABLE);
return BSP_HW_OK;
}
/**
* @brief 查询板级硬件是否已初始化
* @return BSP_HW_OK=已初始化,BSP_HW_ERR_NOT_INIT=未初始化
*/
BspHw_Ret_t bsp_hw_get_status(void)
{
return s_initialized ? BSP_HW_OK : BSP_HW_ERR_NOT_INIT;
}
/**
* @brief 红灯开关
* @param on 1=亮,0=灭
* @return 无
*/
void bsp_led_red_set(uint8_t on)
{
#if BOARD_LED_ENABLE
gpio_direction_output(BOARD_LED_RED_PIN, on ? BOARD_LED_ON_LEVEL : !BOARD_LED_ON_LEVEL);
#else
(void)on;
#endif
}
/**
* @brief 绿灯开关
* @param on 1=亮,0=灭
* @return 无
*/
void bsp_led_green_set(uint8_t on)
{
#if BOARD_LED_ENABLE
gpio_direction_output(BOARD_LED_GREEN_PIN, on ? BOARD_LED_ON_LEVEL : !BOARD_LED_ON_LEVEL);
#else
(void)on;
#endif
}
/**
* @brief 红绿灯全灭
* @return 无
*/
void bsp_led_all_off(void)
{
bsp_led_red_set(0);
bsp_led_green_set(0);
}
/**
* @brief 设置电机方向。STOP 时若正在转,先反转刹车再滑行
* @param dir 收绳 / 放绳 / 停转
* @return 无
*/
void bsp_motor_set(BspMotorDir_t dir)
{
bsp_motor_set_pwm(dir, (dir == BSP_MOTOR_STOP) ? 0 : BOARD_MOTOR_PWM_DUTY);
}
/**
* @brief 设置电机方向与 PWM 占空比
* @param dir 收绳 / 放绳 / 停转
* @param duty 占空比 0~10000STOP 时按 0 处理
* @return 无
*/
void bsp_motor_set_pwm(BspMotorDir_t dir, uint16_t duty)
{
#if !BOARD_MOTOR_ENABLE
(void)dir;
(void)duty;
return;
#else
if (duty > 10000) {
duty = 10000;
}
if (dir == BSP_MOTOR_STOP) {
duty = 0;
}
/* 同向只改占空比:直接改 PWM,不走换向死区 */
if (dir == s_motor_dir && s_brake_ms == 0) {
if (duty == s_motor_duty) {
return;
}
s_motor_duty = duty;
s_motor_pending = dir;
s_pending_duty = duty;
bsp_motor_gpio(dir, duty);
return;
}
/* 已经在反转刹车等到停,忽略重复 STOP */
if (dir == BSP_MOTOR_STOP && s_motor_pending == BSP_MOTOR_STOP && s_brake_ms) {
return;
}
/* 正在转时要停:先反向驱动再滑行,短接对此驱动无效 */
if (dir == BSP_MOTOR_STOP
&& (s_motor_dir == BSP_MOTOR_IN || s_motor_dir == BSP_MOTOR_OUT)) {
BspMotorDir_t rev = (s_motor_dir == BSP_MOTOR_OUT) ? BSP_MOTOR_IN : BSP_MOTOR_OUT;
uint16_t brake_duty = s_motor_duty ? s_motor_duty : BOARD_MOTOR_PWM_DUTY;
bsp_motor_gpio(rev, brake_duty);
s_motor_dir = rev;
s_motor_duty = brake_duty;
s_motor_pending = BSP_MOTOR_STOP;
s_pending_duty = 0;
s_brake_ms = BOARD_MOTOR_STOP_BRAKE_MS;
s_brake_t0 = timer_get_ms();
log_info("motor rev-brake %ums duty=%u\n",
(unsigned)BOARD_MOTOR_STOP_BRAKE_MS, (unsigned)brake_duty);
return;
}
/* 换向:先松开再输出,避免 H 桥直通 */
if (dir != BSP_MOTOR_STOP && s_motor_dir != BSP_MOTOR_STOP && dir != s_motor_dir) {
bsp_motor_gpio(BSP_MOTOR_STOP, 0);
s_motor_pending = dir;
s_pending_duty = duty;
s_brake_ms = BOARD_MOTOR_REVERSE_MS;
s_brake_t0 = timer_get_ms();
s_motor_dir = BSP_MOTOR_STOP;
s_motor_duty = 0;
return;
}
s_brake_ms = 0;
s_motor_dir = dir;
s_motor_pending = dir;
s_motor_duty = duty;
s_pending_duty = duty;
bsp_motor_gpio(dir, duty);
#endif
}
/**
* @brief 推进换向死区与停转反转刹车,按真实毫秒计时
* @return 无
*/
void bsp_motor_brake_tick(void)
{
#if !BOARD_MOTOR_ENABLE
return;
#else
if (s_brake_ms == 0) {
return;
}
if ((timer_get_ms() - s_brake_t0) < s_brake_ms) {
return;
}
s_brake_ms = 0;
s_motor_dir = s_motor_pending;
s_motor_duty = s_pending_duty;
bsp_motor_gpio(s_motor_dir, s_motor_duty);
#endif
}
/**
* @brief 读取按键电平
* @return 1=按下,0=未按下
*/
uint8_t bsp_key_is_pressed(void)
{
#if BOARD_KEY_ENABLE
return (uint8_t)(gpio_read(BOARD_KEY_PIN) == BOARD_KEY_ACTIVE_LEVEL);
#else
return 0;
#endif
}
/**
* @brief 读取 ME4054 CHG 脚:充电中为低
* @return 1=充电中,0=未充电
*/
uint8_t bsp_chg_is_low(void)
{
#if BOARD_CHG_ENABLE
return (uint8_t)(gpio_read(BOARD_CHG_PIN) == BOARD_CHG_CHARGING_LEVEL);
#else
return 0;
#endif
}
/**
* @brief 读取电池电压
* @return 电池电压,单位 mV
*/
uint16_t bsp_vbat_mv(void)
{
return (uint16_t)(adc_get_voltage(AD_CH_VBAT) * 4);
}
/**
* @brief 读取 5V 口电压,用于判断 USB
* @return VPWR 电压,单位 mV
*/
uint16_t bsp_vpwr_mv(void)
{
return (uint16_t)(adc_get_voltage(AD_CH_LDO5V) * 4);
}
/**
* @brief USB 是否在位(以 ADC 为主,比较器为辅)
* @return 1=插入,0=未插入
*/
uint8_t bsp_vpwr_is_online(void)
{
#if BOARD_CHG_ENABLE
uint16_t mv = bsp_vpwr_mv();
/* 以 ADC 为准:拔电后比较器可能仍为 1,但电压已经掉到几百 mV */
if (mv >= BOARD_VPWR_ONLINE_MV) {
return 1;
}
if (mv <= BOARD_VPWR_OFF_MV) {
return 0;
}
if (get_ldo5v_online_hw() || get_lvcmp_det()) {
return 1;
}
return 0;
#else
return 0;
#endif
}
/**
* @brief 进深睡前把电机 PWM 关掉并拉低,灯全灭
* @return 无
*/
void bsp_hw_enter_sleep_io(void)
{
bsp_motor_set(BSP_MOTOR_STOP);
#if BOARD_MOTOR_ENABLE
mcpwm_set_duty(pwm_ch0, 0);
mcpwm_set_duty(pwm_ch1, 0);
mcpwm_close(pwm_ch0);
mcpwm_close(pwm_ch1);
gpio_och_disable_output_signal(BOARD_MOTOR_INA_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
gpio_och_disable_output_signal(BOARD_MOTOR_INB_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H);
gpio_direction_output(BOARD_MOTOR_INA_PIN, 0);
gpio_direction_output(BOARD_MOTOR_INB_PIN, 0);
#endif
bsp_led_all_off();
}
+122
View File
@@ -0,0 +1,122 @@
/******************************************************************************
* @file bsp_hw.h
* @brief CBC2 板级硬件:电机 / LED / 按键 / 充电 / 电量
* @author cyWu <1917507415@qq.com>
* @date 2026.08.24
* @version V1.4.0
* @history
* - V1.0.0, 2026.08.21, cyWu, 首次发布
* - V1.1.0, 2026.08.24, cyWu, 停转改为反转刹车;去掉未贴件限位
* - V1.3.0, 2026.08.24, cyWu, 电机改 MCPWM 50% 占空比降速
* - V1.4.0, 2026.08.24, cyWu, 增加按占空比驱动,供玩法变速
******************************************************************************/
#ifndef __BSP_HW_H__
#define __BSP_HW_H__
#include <stdint.h>
#include "board_pin.h"
typedef enum {
BSP_HW_OK = 0,
BSP_HW_ERR_PARAM,
BSP_HW_ERR_NOT_INIT
} BspHw_Ret_t;
typedef enum {
BSP_MOTOR_STOP = 0, /**< 滑行停(INA/INB 都低) */
BSP_MOTOR_IN, /**< 收绳(顺时针,见 BOARD_MOTOR_IN_INA_HIGH */
BSP_MOTOR_OUT /**< 放绳 */
} BspMotorDir_t;
/**
* @brief 初始化 GPIO / 充电 ADC
* @return BSP_HW_OK=成功
*/
BspHw_Ret_t bsp_hw_init(void);
/**
* @brief 查询是否已初始化
* @return BSP_HW_OK=已初始化,BSP_HW_ERR_NOT_INIT=未初始化
*/
BspHw_Ret_t bsp_hw_get_status(void);
/**
* @brief 红灯开关
* @param on 1=亮,0=灭
* @return 无
*/
void bsp_led_red_set(uint8_t on);
/**
* @brief 绿灯开关
* @param on 1=亮,0=灭
* @return 无
*/
void bsp_led_green_set(uint8_t on);
/**
* @brief 红绿灯全灭
* @return 无
*/
void bsp_led_all_off(void);
/**
* @brief 设置电机方向。STOP 时若正在转,先反转刹车再滑行
* @note 占空比用 BOARD_MOTOR_PWM_DUTY(手动点动)
* @param dir 收绳 / 放绳 / 停转
* @return 无
*/
void bsp_motor_set(BspMotorDir_t dir);
/**
* @brief 设置电机方向与 PWM 占空比
* @param dir 收绳 / 放绳 / 停转
* @param duty 占空比 0~100000%~100%);STOP 时忽略
* @return 无
*/
void bsp_motor_set_pwm(BspMotorDir_t dir, uint16_t duty);
/**
* @brief 推进换向死区与停转反转刹车,按真实毫秒计时
* @return 无
*/
void bsp_motor_brake_tick(void);
/**
* @brief 读取按键
* @return 1=按下,0=未按下
*/
uint8_t bsp_key_is_pressed(void);
/**
* @brief 读取 ME4054 CHG:充电中为低
* @return 1=充电中,0=未充电
*/
uint8_t bsp_chg_is_low(void);
/**
* @brief 读取电池电压
* @return 电池电压,单位 mV
*/
uint16_t bsp_vbat_mv(void);
/**
* @brief 读取 5V 口电压,用于判断 USB
* @return VPWR 电压,单位 mV
*/
uint16_t bsp_vpwr_mv(void);
/**
* @brief USB 是否在位(以 ADC 为主,比较器为辅)
* @return 1=插入,0=未插入
*/
uint8_t bsp_vpwr_is_online(void);
/**
* @brief 进深睡前把电机 PWM 关掉并拉低,灯全灭
* @return 无
*/
void bsp_hw_enter_sleep_io(void);
#endif /* __BSP_HW_H__ */
+12 -9
View File
@@ -64,7 +64,7 @@ static void usr_rtc_save_tz(int32_t tz_s);
/**
* @brief 打开 rtc 设备并加载时区
* @return UsrRtc_Ret_t
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=设备打开失败或 RTC 未使能
*/
UsrRtc_Ret_t usr_rtc_init(void)
{
@@ -101,7 +101,7 @@ UsrRtc_Ret_t usr_rtc_init(void)
/**
* @brief 获取当前本地时间
* @param t 成功时赋值;失败不改动
* @return 1/0
* @return 1=已对时且读成功,0=未初始化/未对时/参数非法
*/
uint8_t usr_rtc_get_time(struct sys_time *t)
{
@@ -127,7 +127,7 @@ uint8_t usr_rtc_get_time(struct sys_time *t)
* @brief UTC 秒 + 时区 → 本地日历写入 RTC
* @param unix_utc UTC 秒
* @param tz_s 时区偏移秒
* @return UsrRtc_Ret_t
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=未初始化,USR_RTC_ERR_PARAM=参数非法,USR_RTC_ERR_INVALID=年份非法
*/
UsrRtc_Ret_t usr_rtc_set_from_unix(uint64_t unix_utc, int32_t tz_s)
{
@@ -184,7 +184,7 @@ UsrRtc_Ret_t usr_rtc_set_from_unix(uint64_t unix_utc, int32_t tz_s)
/**
* @brief 写硬件日历
* @param t 本地日历
* @return UsrRtc_Ret_t
* @return USR_RTC_OK=成功,USR_RTC_ERR_PARAM=参数非法,USR_RTC_ERR_NOT_INIT=未初始化
*/
static UsrRtc_Ret_t usr_rtc_write(const struct sys_time *t)
{
@@ -216,7 +216,7 @@ static UsrRtc_Ret_t usr_rtc_write(const struct sys_time *t)
/**
* @brief 是否闰年
* @param year 公历年
* @return 1/0
* @return 1=闰年,0=平年
*/
static uint8_t usr_rtc_is_leap(uint16_t year)
{
@@ -235,7 +235,7 @@ static uint8_t usr_rtc_is_leap(uint16_t year)
* @brief 某月天数
* @param year 年
* @param month 月 1~12
* @return 天数非法月返回 0
* @return 该月天数非法月返回 0
*/
static uint8_t usr_rtc_days_in_month(uint16_t year, uint8_t month)
{
@@ -256,7 +256,7 @@ static uint8_t usr_rtc_days_in_month(uint16_t year, uint8_t month)
/**
* @brief 年份是否在合法窗口
* @param year 年
* @return 1/0
* @return 1=合法,0=非法
*/
static uint8_t usr_rtc_year_ok(uint16_t year)
{
@@ -266,8 +266,9 @@ static uint8_t usr_rtc_year_ok(uint16_t year)
/**
* @brief UTC 秒 + 时区 → 本地日历
* @param unix_utc UTC 秒
* @param tz_s 时区偏移
* @param t 输出
* @param tz_s 时区偏移
* @param t 输出本地日历
* @return 无
*/
static void usr_rtc_unix_to_calendar(uint32_t unix_utc, int32_t tz_s,
struct sys_time *t)
@@ -324,6 +325,7 @@ static void usr_rtc_unix_to_calendar(uint32_t unix_utc, int32_t tz_s,
/**
* @brief 从 VM 加载时区
* @return 无
*/
static void usr_rtc_load_tz(void)
{
@@ -350,6 +352,7 @@ static void usr_rtc_load_tz(void)
/**
* @brief 时区写入 VM
* @param tz_s 时区偏移秒
* @return 无
*/
static void usr_rtc_save_tz(int32_t tz_s)
{
+3 -4
View File
@@ -40,9 +40,8 @@ typedef enum {
/*============================================================================*/
/**
* @brief 打开 "rtc" 设备并加载时区
* @return UsrRtc_Ret_t
* @note app_main 启动时调用一次即可
* @brief 打开 rtc 设备并加载时区
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=设备打开失败或 RTC 未使能
*/
UsrRtc_Ret_t usr_rtc_init(void);
@@ -57,7 +56,7 @@ uint8_t usr_rtc_get_time(struct sys_time *t);
* @brief App/网关 DEVICEINFOUTC 秒 + 时区写入 RTC
* @param unix_utc UTC Unix 秒;0 无效
* @param tz_s 时区偏移秒(东八区 28800)
* @return UsrRtc_Ret_t
* @return USR_RTC_OK=成功,USR_RTC_ERR_NOT_INIT=未初始化,USR_RTC_ERR_PARAM=参数非法,USR_RTC_ERR_INVALID=年份非法
*/
UsrRtc_Ret_t usr_rtc_set_from_unix(uint64_t unix_utc, int32_t tz_s);
+2079
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
sdfile_vfs_ops
sbc_decoder
msbc_decoder
sbc_hwaccel
cvsd_decoder
pcm_decoder
mp3_decoder
View File
View File
View File
+48
View File
@@ -0,0 +1,48 @@
@echo off
Setlocal enabledelayedexpansion
@echo ********************************************************************************
@echo SDK BR28
@echo ********************************************************************************
@echo %date%
cd /d %~dp0
set OBJDUMP=C:\JL\pi32\bin\llvm-objdump.exe
set OBJCOPY=C:\JL\pi32\bin\llvm-objcopy.exe
set ELFFILE=sdk.elf
set bankfiles=
set LZ4_PACKET=.\lz4_packet.exe
REM %OBJDUMP% -D -address-mask=0x1ffffff -print-dbg $1.elf > $1.lst
%OBJCOPY% -O binary -j .text %ELFFILE% text.bin
%OBJCOPY% -O binary -j .data %ELFFILE% data.bin
%OBJCOPY% -O binary -j .data_code %ELFFILE% data_code.bin
%OBJCOPY% -O binary -j .overlay_aec %ELFFILE% aec.bin
%OBJCOPY% -O binary -j .overlay_aac %ELFFILE% aac.bin
%OBJCOPY% -O binary -j .overlay_aptx %ELFFILE% aptx.bin
%OBJCOPY% -O binary -j .common %ELFFILE% common.bin
for /L %%i in (0,1,20) do (
%OBJCOPY% -O binary -j .overlay_bank%%i %ELFFILE% bank%%i.bin
set bankfiles=!bankfiles! bank%%i.bin 0x0
)
%LZ4_PACKET% -dict text.bin -input common.bin 0 aec.bin 0 aac.bin 0 !bankfiles! -o bank.bin
%OBJDUMP% -section-headers -address-mask=0x1ffffff %ELFFILE%
REM %OBJDUMP% -t %ELFFILE% > symbol_tbl.txt
copy /b text.bin + data.bin + mov_slot.bin + data_code.bin + aec.bin + aac.bin + psr_data_code.bin app.bin
del !bankfiles! common.bin text.bin data.bin bank.bin
copy eq_cfg_hw_less.bin eq_cfg_hw.bin
call download/earphone/download_app_ota.bat
@@ -10,9 +10,9 @@ copy ..\..\ota.bin .
copy ..\..\anc_coeff.bin .
copy ..\..\anc_gains.bin .
..\..\isd_download.exe ..\..\isd_config.ini -tonorflash -dev br28 -boot 0x120000 -div8 -wait 300 -uboot ..\..\uboot.boot -app ..\..\app.bin -res ..\..\cfg_tool.bin tone.cfg p11_code.bin -uboot_compress
..\..\isd_download.exe ..\..\isd_config.ini -tonorflash -dev br28 -boot 0x120000 -div8 -wait 300 -uboot ..\..\uboot.boot -app ..\..\app.bin -res ..\..\cfg_tool.bin tone.cfg p11_code.bin -uboot_compress -key RS-5B88.key
:: -format all -key RS-5B88.key
:: -format all
::-reboot 2500
@rem delete temp files
Binary file not shown.
+81
View File
@@ -0,0 +1,81 @@
[EXTRA_CFG_PARAM]
BR22_TWS_DB = YES;
FLASH_SIZE = 0x100000;
BR22_TWS_VERSION = 0;
FORCE_4K_ALIGN = YES;
SPECIAL_OPT = 0;
DB_UPDATE_DATA = YES;
CHIP_NAME = AC701N;
ENTRY = 0x6000100;
PID = AC701N;
VID = 0.01;
SDK_VERSION = JL701N_V1.6.1;
RESERVED_OPT = 0;
UFW_ELEMENT = 0x10 - 0x0, 0x1 - 0x0;
DOWNLOAD_MODEL = USB;
SERIAL_DEVICE_NAME = JlVirtualJtagSerial;
SERIAL_BARD_RATE = 1000000;
SERIAL_CMD_OPT = 2;
SERIAL_CMD_RATE = 100;
SERIAL_CMD_RES = 0;
SERIAL_INIT_BAUD_RATE = 9600;
LOADER_BAUD_RATE = 1000000;
LOADER_ASK_BAUD_RATE = 1000000;
BEFORE_LOADER_WAIT_TIME = 150;
SERIAL_SEND_KEY = YES;
[CHIP_VERSION]
SUPPORTED_LIST = P, B;
[SYS_CFG_PARAM]
SPI = 2_3_0_0;
UTTX = PB02;
UTBD = 1000000;
UTRX = -1;
RESET1 = PB01_16_0;
VLVD = 3;
[FW_ADDITIONAL]
FILE_LIST = (file = ota.bin: type = 100);
[RESERVED_CONFIG]
BTIF_ADR = AUTO;
BTIF_LEN = 0x1000;
BTIF_OPT = 1;
PRCT_ADR = 0;
PRCT_LEN = CODE_LEN;
PRCT_OPT = 2;
VM_ADR = 0;
VM_LEN = 8K;
VM_OPT = 1;
[BURNER_CONFIG]
SIZE = 32;