feat: 完成弹力魔盒初版固件

This commit is contained in:
2026-08-31 20:15:19 +08:00
parent 026d6d5da7
commit b63a928b92
34 changed files with 1320 additions and 781 deletions
+180 -62
View File
@@ -18,10 +18,12 @@
#include "app_pair.h"
#include "app_mode.h"
#include "app_led.h"
#include "app_shake_wake.h"
#include "app_time_util.h"
#include "bsp_hw.h"
#include "board_pin.h"
#include "jb_product.h"
#include "usr_le_api.h"
#include "system/includes.h"
#define LOG_TAG_CONST APP
@@ -32,13 +34,19 @@
/** 功能模块总状态,替代原来一堆散落的 static 变量 */
typedef struct {
uint8_t app_power; /**< DP00=软关,电机/玩法停,BLE 仍在 */
uint8_t play_mode; /**< 当前自动模式 0~6 */
uint8_t play_mode; /**< 当前自动模式 0~5 */
uint32_t play_ms; /**< 当前自动模式已运行时长,超时回待机 */
uint8_t hand_busy; /**< 手动点动进行中 */
uint8_t hand_cmd_last; /**< 边沿:同一 1/2/3 不重复触发 */
uint8_t hand_cmd_last; /**< 最近一次手动命令 */
uint16_t hand_ms;
volatile uint8_t hand_write_pending; /**< DP2 刚写入(03 停转要靠事件) */
volatile uint8_t hand_write_cmd;
volatile uint8_t power_write_pending;
volatile uint8_t power_write_value;
volatile uint8_t play_write_pending;
volatile uint8_t play_write_mode;
uint8_t shake_wake;
uint8_t no_disturb;
} AppFunctionCtx_t;
static AppFunctionCtx_t s_func;
@@ -49,6 +57,9 @@ static void function_play_mode_poll(void);
static void function_play_mode_timeout(uint16_t dt);
static void function_hand_poll(void);
static void function_on_click(void);
static uint8_t function_power_on(uint8_t show_hint);
static uint8_t function_shake_wake_allowed(void);
static void function_shake_wake_trigger(void);
/**
* @brief 初始化功能模块状态
@@ -57,11 +68,28 @@ static void function_on_click(void);
void app_function_init(void)
{
memset(&s_func, 0, sizeof(s_func));
app_shake_wake_init(function_shake_wake_allowed,
function_shake_wake_trigger);
s_func.shake_wake = !!gDevData.shake_wake;
s_func.no_disturb = !!gDevData.no_disturb_enable[0];
app_shake_wake_set_enable(s_func.shake_wake);
}
void app_function_dp_power_write(uint8_t enable)
{
s_func.power_write_value = !!enable;
s_func.power_write_pending = 1;
}
void app_function_dp_play_write(uint8_t mode)
{
s_func.play_write_mode = mode;
s_func.play_write_pending = 1;
}
/**
* @brief DP2 写入钩子:仅记录第 1 字节,真正执行在 app_function_poll
* @param cmd 0=空闲1=顺时针,2=逆时针,3=停
* @param cmd 0=忽略1=原地右转,2=原地左转
* @return 无
*/
void app_function_dp_hand_write(uint8_t cmd)
@@ -70,24 +98,48 @@ void app_function_dp_hand_write(uint8_t cmd)
s_func.hand_write_pending = 1;
}
void app_function_set_shake_wake(uint8_t enable)
{
s_func.shake_wake = !!enable;
gDevData.shake_wake = s_func.shake_wake;
app_shake_wake_set_enable(s_func.shake_wake);
}
void app_function_set_no_disturb(uint8_t enable)
{
s_func.no_disturb = !!enable;
app_shake_wake_reset();
}
/**
* @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动
* @return 无
*/
void app_function_poll(void)
{
/* DP0:APP 下发开关。充电中 / 正在关机时不跟,避免和 USB 软关抢状态 */
if (!app_power_usb_online() && !app_power_poweroff_pending()) {
if (gDevData.power && !s_func.app_power) {
s_func.app_power = 1;
app_led_hint_power_on();
log_info("APP power on\n");
} else if (!gDevData.power && s_func.app_power) {
if (s_func.shake_wake != !!gDevData.shake_wake) {
app_function_set_shake_wake(gDevData.shake_wake);
}
if (s_func.no_disturb != !!gDevData.no_disturb_enable[0]) {
app_function_set_no_disturb(gDevData.no_disturb_enable[0]);
}
if (s_func.power_write_pending) {
uint8_t enable = s_func.power_write_value;
s_func.power_write_pending = 0;
if (enable) {
if (function_power_on(1)) {
log_info("APP power on\n");
} else {
gDevData.power = s_func.app_power;
}
} else if (!enable && s_func.app_power) {
app_function_soft_off();
} else {
gDevData.power = s_func.app_power;
}
}
/* 先跟自动模式,再处理手动点动:同一拍里 APP 发自动模式时,能先把手动停掉 */
function_play_mode_poll();
function_hand_poll();
}
@@ -99,9 +151,18 @@ void app_function_poll(void)
*/
void app_function_tick_1ms(uint16_t dt)
{
/* 与业务节拍共用一次唤醒,避免震动检测另开 10ms sys_timer。 */
app_shake_wake_poll();
if (s_func.hand_busy) {
bsp_motor_switch_tick(); /* 手动点动期间也要走换向死区 */
s_func.hand_ms = app_add_ms(s_func.hand_ms, dt);
if (s_func.hand_ms >= BOARD_HAND_PULSE_MS) {
function_hand_stop();
gDevData.hand_mode = HAND_MODE_0;
app_led_request_mode_blink();
log_info("manual timeout %ums\n", (unsigned)BOARD_HAND_PULSE_MS);
}
} else {
app_mode_run();
function_play_mode_timeout(dt); /* 自动模式跑满 10 分钟回待机 */
@@ -116,7 +177,8 @@ static void function_hand_stop(void)
{
s_func.hand_busy = 0;
s_func.hand_ms = 0;
bsp_motor_set(BSP_MOTOR_STOP);
gDevData.hand_mode = HAND_MODE_0;
bsp_drive_stop();
}
/**
@@ -128,6 +190,7 @@ void app_function_stop_all(void)
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_mode = 0;
gDevData.hand_mode = HAND_MODE_0;
app_mode_stop();
function_hand_stop();
}
@@ -145,6 +208,10 @@ void app_function_soft_off(void)
gDevData.play_mode = 0;
app_mode_stop();
function_hand_stop();
/* 不依赖系统下一次轻睡回调,软关生效后立刻关四路 MCPWM。 */
bsp_hw_sleep_pwm_off();
app_shake_wake_reset();
usr_ble_set_standby(1);
log_info("soft off, wait APP power on\n");
}
@@ -154,8 +221,7 @@ void app_function_soft_off(void)
*/
void app_function_power_on_silent(void)
{
s_func.app_power = 1;
gDevData.power = 1;
(void)function_power_on(0);
}
/**
@@ -178,7 +244,7 @@ uint8_t app_function_is_hand_busy(void)
/**
* @brief 查询当前自动玩法编号
* @return 0=停转,1~6=对应玩法
* @return 0=停转,1~5=对应玩法
*/
uint8_t app_function_play_mode(void)
{
@@ -186,25 +252,25 @@ uint8_t app_function_play_mode(void)
}
/**
* @brief 切换自动模式 0~6,并统一在这里判定要不要闪灯提示
* @brief 切换自动模式 0~5,并统一在这里判定要不要闪灯提示
* @note 闪灯规则:只要 play_mode 真的变了就闪两下。手动点动期间 play_mode
* 恒为 0(见 function_hand_poll),所以“手动切到自动”落到这里时
* old_mode 必是 0、新 mode 非 0,天然满足“变了”,不用额外判断;
* 由充电/软关/配对等条件被动摁回 0 且本来就是 0 的情况才不闪,
* 避免刷屏式误闪
* @param mode 0=停转,1~6=对应玩法
* @param mode 0=停转,1~5=对应玩法
* @return 无
*/
static void function_play_mode_apply(uint8_t mode)
{
uint8_t old_mode = s_func.play_mode;
if (mode > 6) {
if (mode > PLAY_MODE_5) {
mode = 0;
}
/* 充电 / 软关 / 关机中 / 配对中:不允许跑自动,强制回到待机 */
/* Pairing is an online overlay; only charging, OTA and power-off stop motion. */
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_pair_is_active()) {
|| app_led_is_ota()) {
mode = 0;
}
if (mode != 0 && s_func.hand_busy) {
@@ -233,17 +299,21 @@ static void function_play_mode_poll(void)
{
uint8_t mode;
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_pair_is_active()) {
if (!s_func.play_write_pending) {
return;
}
mode = gDevData.play_mode;
if (mode > 6) {
mode = 0;
gDevData.play_mode = 0;
s_func.play_write_pending = 0;
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_led_is_ota()) {
gDevData.play_mode = s_func.play_mode;
return;
}
if (mode == s_func.play_mode) {
return; /* 没变化,不重复 start */
mode = s_func.play_write_mode;
if (mode > PLAY_MODE_5) {
mode = 0;
}
if (mode != PLAY_MODE_0 && mode == s_func.play_mode) {
mode = PLAY_MODE_0;
}
function_play_mode_apply(mode);
log_info("APP play_mode=%d\n", mode);
@@ -267,15 +337,11 @@ static void function_play_mode_timeout(uint16_t dt)
}
log_info("auto mode %d run %ums, timeout to standby\n",
s_func.play_mode, (unsigned)s_func.play_ms);
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_mode = 0;
app_mode_stop();
app_function_soft_off();
}
/**
* @brief DP2 手动:只看第 1 字节。0=空闲,1=顺时针,2=逆时针,3=停
* @note 00 00 是 DP 默认值,不当停止。百分比/力度、DP3、DP4 预留不接。
* @return 无
*/
static void function_hand_poll(void)
@@ -288,54 +354,63 @@ static void function_hand_poll(void)
s_func.hand_write_pending = 0;
cmd = s_func.hand_write_cmd;
} else {
cmd = gDevData.hand_mode[0];
cmd = gDevData.hand_mode;
}
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_pair_is_active()) {
if (s_func.hand_busy || (got_write && (cmd == 3))) {
|| app_led_is_ota()) {
if (s_func.hand_busy) {
app_function_stop_all();
}
s_func.hand_cmd_last = cmd;
return;
}
if (cmd == 0) {
s_func.hand_cmd_last = 0; /* 00 是 DP 默认值,不当停止命令 */
if (!got_write) {
s_func.hand_cmd_last = cmd;
return;
}
/* cmd==3 改边沿触发:只在“刚收到停止”这一拍才可能真的停一次,避免
* DP2 停留在 03(APP 没有再写别的值)时,后面随便什么原因一进自动/
* 手动就被这个陈旧的 03 立刻打断——这正是之前“自动运行中,APP 发
* 停止,再发运行自动模式却启动不了”的根因 */
if (cmd == 3) {
if ((got_write || (cmd != s_func.hand_cmd_last))
&& (s_func.hand_busy || app_mode_get_active())) {
app_function_stop_all();
log_info("hand stop cmd=3\n");
if (cmd == HAND_MODE_0) {
if (s_func.hand_busy) {
function_hand_stop();
app_led_request_mode_blink();
}
s_func.hand_cmd_last = 3;
s_func.hand_cmd_last = cmd;
return;
}
if ((cmd == 1 || cmd == 2) && (got_write || (cmd != s_func.hand_cmd_last))) {
if (cmd == HAND_MODE_1 || cmd == HAND_MODE_2) {
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_mode = 0;
app_mode_stop(); /* 自动切手动:先停自动玩法 */
s_func.hand_busy = 1;
s_func.hand_ms = 0;
/* 1=顺时针收绳,2=逆时针放绳(BOARD_MOTOR_IN_INA_HIGH=1 */
bsp_motor_set((cmd == 1) ? BSP_MOTOR_IN : BSP_MOTOR_OUT);
gDevData.hand_mode = cmd;
if (cmd == HAND_MODE_1) {
bsp_drive_set(BSP_MOTOR_FORWARD, 5500,
BSP_MOTOR_STOP, 0);
} else {
bsp_drive_set(BSP_MOTOR_STOP, 0,
BSP_MOTOR_FORWARD, 5500);
}
app_led_request_mode_blink(); /* 自动/空闲切到手动,闪两下提示 */
log_info("hand cmd=%d %s until 03\n", cmd, (cmd == 1) ? "cw" : "ccw");
log_info("hand cmd=%d %s max=%ums\n", cmd,
(cmd == HAND_MODE_1) ? "upper" : "lower",
(unsigned)BOARD_HAND_PULSE_MS);
} else {
gDevData.hand_mode = HAND_MODE_0;
if (s_func.hand_busy) {
function_hand_stop();
}
log_info("ignore unsupported hand cmd=%d\n", cmd);
}
s_func.hand_cmd_last = cmd;
}
/**
* @brief 短按:自动模式 0→1→…→6→0(闪灯提示交给 function_play_mode_apply
* @brief 短按:自动模式 0→1→…→5→0(闪灯提示交给 function_play_mode_apply
* @return 无
*/
static void function_on_click(void)
@@ -344,7 +419,8 @@ static void function_on_click(void)
log_info("key click ignored, hand busy\n");
return;
}
function_play_mode_apply((uint8_t)((s_func.play_mode + 1) % 7));
function_play_mode_apply((s_func.play_mode >= PLAY_MODE_5) ?
PLAY_MODE_1 : (uint8_t)(s_func.play_mode + 1));
log_info("key click play_mode=%d\n", s_func.play_mode);
}
@@ -361,16 +437,15 @@ void usr_on_key_short(void)
log_info("key click ignored, charging\n");
return;
}
if (app_pair_is_active()) {
log_info("key click ignored, pairing\n");
if (app_led_is_ota()) {
log_info("key click ignored, ota\n");
return;
}
if (!s_func.app_power) {
/* 软关状态下短按:只唤醒功能,不切模式 */
s_func.app_power = 1;
gDevData.power = 1;
app_led_hint_power_on();
log_info("key click cancel soft off\n");
if (function_power_on(1)) {
log_info("key click cancel soft off\n");
}
return;
}
function_on_click();
@@ -389,10 +464,53 @@ void usr_on_key_long(void)
log_info("key hold 3s ignored, charging\n");
return;
}
if (app_pair_is_active()) {
log_info("key hold 3s ignored, pairing\n");
if (app_led_is_ota()) {
log_info("key hold 3s, exit ota and poweroff\n");
usr_app_ota_exit();
app_led_exit_ota();
app_power_request_poweroff();
return;
}
log_info("key hold 3s, poweroff\n");
app_power_request_poweroff();
}
/** Shared standby-to-on transition for APP, key and vibration wake. */
static uint8_t function_power_on(uint8_t show_hint)
{
if (app_power_usb_online() || app_power_poweroff_pending() ||
app_led_is_ota()) {
return 0;
}
if (s_func.app_power) {
gDevData.power = 1;
return 1;
}
app_function_stop_all();
s_func.app_power = 1;
gDevData.power = 1;
app_shake_wake_reset();
usr_ble_set_standby(0);
if (show_hint) {
app_led_hint_power_on();
}
return 1;
}
static uint8_t function_shake_wake_allowed(void)
{
return s_func.shake_wake &&
!s_func.no_disturb &&
!s_func.app_power &&
!app_power_usb_online() &&
!app_power_poweroff_pending() &&
!app_led_is_ota();
}
static void function_shake_wake_trigger(void)
{
if (function_power_on(1)) {
log_info("shake wake power on\n");
}
}