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
+23 -19
View File
@@ -24,6 +24,7 @@
#include "app_main.h"
#include "usr_le_api.h"
#include "led_manager.h"
#include "asm/charge.h"
#include "asm/power/power_api.h"
#include "asm/power/power_reset.h"
@@ -34,6 +35,7 @@
extern void clr_wdt(void);
extern u32 timer_get_ms(void);
extern u8 is_ldo5v_wakeup(void);
/**
* @brief 忙等若干毫秒,并喂狗
@@ -116,9 +118,17 @@ static uint8_t boot_skip_key_wait(void)
{
uint8_t i;
/* LDO5V wake is latched by P33 and remains reliable while the live
* voltage/CHG detectors are still settling after a soft-poweroff reset. */
if (is_ldo5v_wakeup()) {
log_info("boot ldo5v wake, skip key wait\n");
return 1;
}
/* USB 在位(充电口有电):说明是插着电源,不是用户凭空按键开机 */
for (i = 0; i < 10; i++) {
if (bsp_chg_is_low() || bsp_vpwr_is_online()) {
if (bsp_chg_is_low() || bsp_vpwr_is_online()
|| get_ldo5v_online_hw()) {
log_info("boot usb, skip key wait\n");
return 1;
}
@@ -141,10 +151,7 @@ static uint8_t boot_skip_key_wait(void)
/**
* @brief 等待并判定开机按键
* @note 配对(5s)不再一到点就立即提交:需要继续按住观察是否会按到 12s
* 触发 OTA,真正的配对提交放在松手那一刻做。按满 5s 绿灯先切到配对
* 快闪提示,行为上仍是“按满 5s 再松手即进配对”,只是多给了继续按到
* 12s 改道 OTA 的机会。
* @note 按满 5s 立即进入配对;继续按住到 12s 时直接转入 OTA。
* 3s 常亮 / 5s 快闪直接复用 led_manager 的 LED_EVENT_STANDBY /
* LED_EVENT_PAIRING 事件(和正常运行时配对灯效完全一致的节奏),
* 不再手写一份闪烁定时。LED 事件库由调用方(usr_jb_init)在本函数
@@ -155,7 +162,7 @@ void app_boot_wait_key(void)
{
uint32_t t0;
uint32_t elapsed;
uint8_t pair_armed = 0;
uint8_t pair_started = 0;
usr_var.usr_goto_pair = 0;
@@ -190,15 +197,17 @@ void app_boot_wait_key(void)
return;
}
/* 5s 只武装配对并切到快闪,真正提交放到松手那一刻,给继续按到 12s 改道 OTA 的机会 */
if (!pair_armed && (elapsed >= BOARD_KEY_PAIR_MS)) {
pair_armed = 1;
/* 5s 立即进入配对;继续按住时仍检测 12s OTA */
if (!pair_started && (elapsed >= BOARD_KEY_PAIR_MS)) {
pair_started = 1;
usr_var.usr_goto_pair = 1;
usr_clear_pair_info_noreset();
app_pair_start();
LED_EventDelete(LED_EVENT_STANDBY);
LED_EventAdd(LED_EVENT_PAIRING);
log_info("boot hold %ums, pair hint blink (release now for pair, "
"keep holding to %ums for ota)\n",
log_info("boot hold %ums, power on + pair (keep holding to %ums for ota)\n",
(unsigned)elapsed, (unsigned)BOARD_KEY_OTA_MS);
} else if (!pair_armed && !LED_IsEventExist(LED_EVENT_STANDBY)
} else if (!pair_started && !LED_IsEventExist(LED_EVENT_STANDBY)
&& (elapsed >= BOARD_KEY_POWER_MS)) {
LED_EventAdd(LED_EVENT_STANDBY);
log_info("boot hold 3s, will power on\n");
@@ -213,13 +222,8 @@ void app_boot_wait_key(void)
boot_sleep();
}
/* 松手时已过 5s(且没按到 12s):提交配对;否则只是正常开机 */
if (pair_armed) {
usr_var.usr_goto_pair = 1;
usr_clear_pair_info_noreset();
app_pair_start();
log_info("boot hold %ums, power on + pair\n", (unsigned)elapsed);
} else {
/* 配对已在 5s 阈值提交;松手这里只记录普通开机 */
if (!pair_started) {
log_info("boot hold %ums, power on\n", (unsigned)elapsed);
}
}
+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");
}
}
+16 -4
View File
@@ -7,8 +7,8 @@
* @history
* - V1.0.0, 2026.08.25, cyWu, 首次发布,实现 DP0 开关、自动玩法、手动点动与按键动作
*
* @note 本模块决定“电机现在该不该转、转哪种”,会读 app_power/app_pair 的
* 状态作为阻塞条件(充电中/深睡中/配对中都不能转),也会触发
* @note 本模块决定“电机现在该不该转、转哪种”,会读电源与 OTA 状态作为
* 阻塞条件;配对是开机状态上的附加会话,不阻塞运动或按键。也会触发
* app_led 的一次性提示灯(切模式闪、开机绿灯),但不会被它们反向依赖。
******************************************************************************/
@@ -23,13 +23,25 @@
*/
void app_function_init(void);
/** CBC3 DP0 写入:排队到业务节拍统一执行。 */
void app_function_dp_power_write(uint8_t enable);
/** CBC3 DP1 写入:支持重复下发当前模式时停止。 */
void app_function_dp_play_write(uint8_t mode);
/**
* @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);
/** CBC3 DP6 震动开机开关。 */
void app_function_set_shake_wake(uint8_t enable);
/** 勿扰时段是否正在生效;生效时屏蔽震动开机。 */
void app_function_set_no_disturb(uint8_t enable);
/**
* @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动,每个业务 tick 调用一次
* @return 无
@@ -75,7 +87,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);
+12 -1
View File
@@ -124,6 +124,16 @@ void app_led_request_ota(void)
LED_EventAdd(LED_EVENT_OTA);
}
void app_led_exit_ota(void)
{
if (!s_initialized || !s_ota) {
return;
}
s_ota = 0;
LED_EventDelete(LED_EVENT_OTA);
bsp_led_all_off();
}
/**
* @brief 查询是否处于 OTA 灯效
* @return 1=OTA 灯效中,0=否
@@ -230,7 +240,8 @@ void app_led_run(uint16_t dt, uint8_t app_power)
charge_led = app_power_charge_led();
low_bat = app_power_low_bat();
func_on = app_power ? 1 : 0;
pairing = (uint8_t)(app_pair_is_active() && func_on);
pairing = (uint8_t)(app_pair_is_active() && func_on
&& !app_power_usb_online());
/* 充电红 / 充满绿:和功能开关无关,关着设备也可能在充电 */
if (charge_led == 1) {
+3
View File
@@ -57,6 +57,9 @@ void app_led_hint_power_on(void);
*/
void app_led_request_ota(void);
/** Leave the OTA indication and return LED control to the normal state. */
void app_led_exit_ota(void);
/**
* @brief 查询是否处于 OTA 灯效
* @return 1=OTA 灯效中,0=否
+285 -156
View File
@@ -1,15 +1,3 @@
/******************************************************************************
* @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"
@@ -19,200 +7,341 @@
#define LOG_INFO_ENABLE
#include "debug.h"
typedef struct {
BspMotorDir_t dir;
uint16_t ms;
uint16_t duty; /**< 0~10000STOP 忽略 */
} AppModeStep_t;
#define MAGIC_BOX_HIGH_PERCENT 100
#define MAGIC_BOX_MEDIUM_LOW_PERCENT 45
#define MAGIC_BOX_MODE1_FORWARD_MS 585
#define MAGIC_BOX_MODE1_BACKWARD_MS 600
#define MAGIC_BOX_MODE2_FORWARD_MS 260
#define MAGIC_BOX_MODE2_BACKWARD_MS 200
#define MAGIC_BOX_MODE3_FORWARD_MS 585
#define MAGIC_BOX_MODE3_BACKWARD_MS 600
#define MAGIC_BOX_MODE3_STOP_MS 2000
#define MAGIC_BOX_MODE3_LOWER_SEGMENTS 10
#define MAGIC_BOX_MODE4_FORWARD_MS 390
#define MAGIC_BOX_MODE4_BACKWARD_MS 300
#define MAGIC_BOX_MODE4_STOP_MS 3000
#define MAGIC_BOX_MODE5_BACKWARD_MS 600
#define MAGIC_BOX_MODE5_UPPER_STOP_MS 2000
#define MAGIC_BOX_MODE5_CYCLE_MS 20000
#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(500, DUTY_MID), STEP_OUT(1200, DUTY_RUN), STEP_OUT(500, DUTY_PUNCH),
STEP_STOP(400),
STEP_IN(500, DUTY_MID), STEP_IN(1200, DUTY_RUN), STEP_IN(500, DUTY_PUNCH),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode2[] = { /* 飞鸟俯冲:快冲出去,2s 慢慢收回 */
STEP_OUT(500, DUTY_PUNCH), STEP_OUT(500, DUTY_RUN),
STEP_STOP(400),
STEP_IN(2800, DUTY_MID),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode3[] = { /* 云阶陀螺:短步往返加长,力度一档档加 */
STEP_OUT(800, DUTY_SOFT), STEP_IN(800, DUTY_SOFT),
STEP_OUT(800, DUTY_MID), STEP_IN(800, DUTY_MID),
STEP_OUT(800, DUTY_RUN), STEP_IN(800, DUTY_RUN),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode4[] = { /* 起伏攻防:2s 慢逗再猛收 */
STEP_OUT(500, DUTY_SOFT), STEP_OUT(1200, DUTY_MID), STEP_OUT(500, DUTY_RUN),
STEP_STOP(400),
STEP_IN(500, DUTY_PUNCH), STEP_IN(500, DUTY_MID),
STEP_STOP(400),
STEP_END
};
static const AppModeStep_t s_mode5[] = { /* 微风拂动:轻点后歇 2s */
STEP_OUT(500, DUTY_MID), STEP_STOP(1000),
STEP_IN(500, DUTY_MID), STEP_STOP(1200),
STEP_OUT(500, DUTY_RUN), STEP_STOP(800),
STEP_IN(500, DUTY_RUN), STEP_STOP(1500),
STEP_END
};
static const AppModeStep_t s_mode6[] = { /* 闪电快闪:两下轻、两下重,短停防 LVD */
STEP_OUT(400, DUTY_MID), STEP_IN(400, DUTY_MID),
STEP_OUT(800, DUTY_RUN), STEP_IN(800, DUTY_RUN),
STEP_OUT(500, DUTY_PUNCH), STEP_IN(500, DUTY_PUNCH),
STEP_OUT(700, DUTY_PUNCH), STEP_IN(700, 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
};
/** 模块状态,替代原来散落的 4 个 static */
typedef struct {
uint8_t initialized;
uint8_t active; /**< 0=停转,1~6=当前玩法 */
uint8_t step; /**< 当前节拍在表里的下标 */
u32 step_t0; /**< 当前节拍起始时间戳,用于判断是否该跳下一步 */
uint8_t active;
uint8_t output_valid;
uint8_t upper_phase;
uint8_t lower_pattern;
uint8_t lower_segment;
int8_t upper_direction;
int8_t lower_direction;
uint8_t upper_speed;
uint16_t upper_duration_ms;
uint16_t lower_duration_ms;
uint32_t state_started_ms;
uint32_t upper_started_ms;
uint32_t lower_started_ms;
int8_t output_upper_direction;
int8_t output_lower_direction;
uint8_t output_upper_speed;
uint8_t output_lower_speed;
} AppModeCtx_t;
static AppModeCtx_t s_mode;
extern u32 timer_get_ms(void);
/**
* @brief 输出当前节拍的方向与占空比;ms=0 表示表尾,回到第 0 步循环
* @return 无
*/
static void app_mode_apply_step(void)
static void app_mode_outputs_set(int8_t upper_direction, uint8_t upper_speed,
int8_t lower_direction, uint8_t lower_speed)
{
const AppModeStep_t *tbl = s_tables[s_mode.active];
if (!tbl) {
bsp_motor_set(BSP_MOTOR_STOP);
if (s_mode.output_valid &&
upper_direction == s_mode.output_upper_direction &&
upper_speed == s_mode.output_upper_speed &&
lower_direction == s_mode.output_lower_direction &&
lower_speed == s_mode.output_lower_speed) {
return;
}
if (tbl[s_mode.step].ms == 0) {
s_mode.step = 0; /* 表尾,回到第 0 步循环 */
}
s_mode.step_t0 = timer_get_ms();
if (tbl[s_mode.step].dir == BSP_MOTOR_STOP) {
bsp_motor_set(BSP_MOTOR_STOP);
} else {
bsp_motor_set_pwm(tbl[s_mode.step].dir, tbl[s_mode.step].duty);
bsp_drive_set((BspMotorDir_t)upper_direction, (uint16_t)upper_speed * 100,
(BspMotorDir_t)lower_direction, (uint16_t)lower_speed * 100);
s_mode.output_upper_direction = upper_direction;
s_mode.output_upper_speed = upper_speed;
s_mode.output_lower_direction = lower_direction;
s_mode.output_lower_speed = lower_speed;
s_mode.output_valid = 1;
}
static uint16_t app_mode4_random_step_ms(void)
{
static const uint16_t step_ms[] = {80, 100, 150, 200};
return step_ms[rand32() % ARRAY_SIZE(step_ms)];
}
static uint16_t app_mode5_random_forward_ms(void)
{
static const uint16_t forward_ms[] = {555, 780, 1140};
return forward_ms[rand32() % ARRAY_SIZE(forward_ms)];
}
static void app_mode5_select_lower_pattern(uint32_t now_ms)
{
s_mode.lower_pattern = rand32() % 4;
s_mode.lower_segment = 0;
s_mode.lower_started_ms = now_ms;
switch (s_mode.lower_pattern) {
case 0:
s_mode.lower_direction = BSP_MOTOR_FORWARD;
s_mode.lower_duration_ms = 2000;
break;
case 1:
s_mode.lower_direction = BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = 2000;
break;
case 2:
s_mode.lower_direction = (rand32() & 1) ?
BSP_MOTOR_FORWARD : BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = 100;
break;
default:
s_mode.lower_direction = (rand32() & 1) ?
BSP_MOTOR_FORWARD : BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = 200;
break;
}
}
/**
* @brief 玩法初始化,电机停
* @return APP_MODE_OK=成功
*/
static void app_mode5_start_cycle(uint32_t now_ms)
{
s_mode.state_started_ms = now_ms;
s_mode.upper_phase = 0;
s_mode.upper_started_ms = now_ms;
s_mode.upper_duration_ms = app_mode5_random_forward_ms();
s_mode.upper_direction = BSP_MOTOR_FORWARD;
s_mode.upper_speed = MAGIC_BOX_HIGH_PERCENT;
app_mode5_select_lower_pattern(now_ms);
}
static void app_mode_start_current(uint32_t now_ms)
{
bsp_drive_stop();
s_mode.output_valid = 0;
s_mode.state_started_ms = now_ms;
s_mode.upper_phase = 0;
s_mode.upper_started_ms = now_ms;
s_mode.lower_started_ms = now_ms;
if (s_mode.active == 4) {
s_mode.lower_direction = (rand32() & 1) ?
BSP_MOTOR_FORWARD : BSP_MOTOR_BACKWARD;
s_mode.lower_duration_ms = app_mode4_random_step_ms();
} else if (s_mode.active == 5) {
app_mode5_start_cycle(now_ms);
}
}
static void app_mode1_process(uint32_t now_ms)
{
const uint16_t pair_ms = MAGIC_BOX_MODE1_FORWARD_MS +
MAGIC_BOX_MODE1_BACKWARD_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms) % pair_ms;
if (elapsed_ms < MAGIC_BOX_MODE1_FORWARD_MS) {
app_mode_outputs_set(BSP_MOTOR_FORWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
} else {
app_mode_outputs_set(BSP_MOTOR_BACKWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
}
}
static void app_mode2_process(uint32_t now_ms)
{
const uint16_t pair_ms = MAGIC_BOX_MODE2_FORWARD_MS +
MAGIC_BOX_MODE2_BACKWARD_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms) % pair_ms;
if (elapsed_ms < MAGIC_BOX_MODE2_FORWARD_MS) {
app_mode_outputs_set(BSP_MOTOR_FORWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
} else {
app_mode_outputs_set(BSP_MOTOR_BACKWARD, MAGIC_BOX_HIGH_PERCENT,
BSP_MOTOR_STOP, 0);
}
}
static void app_mode3_process(uint32_t now_ms)
{
const uint16_t upper_move_ms = MAGIC_BOX_MODE3_FORWARD_MS +
MAGIC_BOX_MODE3_BACKWARD_MS;
const uint16_t cycle_ms = upper_move_ms + MAGIC_BOX_MODE3_STOP_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms) % cycle_ms;
uint8_t lower_segment = ((uint32_t)elapsed_ms *
MAGIC_BOX_MODE3_LOWER_SEGMENTS) / cycle_ms;
int8_t upper_direction;
uint8_t upper_speed;
int8_t lower_direction;
if (elapsed_ms < MAGIC_BOX_MODE3_FORWARD_MS) {
upper_direction = BSP_MOTOR_FORWARD;
upper_speed = MAGIC_BOX_HIGH_PERCENT;
} else if (elapsed_ms < upper_move_ms) {
upper_direction = BSP_MOTOR_BACKWARD;
upper_speed = MAGIC_BOX_HIGH_PERCENT;
} else {
upper_direction = BSP_MOTOR_STOP;
upper_speed = 0;
}
lower_direction = (lower_segment & 1) ?
BSP_MOTOR_BACKWARD : BSP_MOTOR_FORWARD;
app_mode_outputs_set(upper_direction, upper_speed,
lower_direction, MAGIC_BOX_HIGH_PERCENT);
}
static void app_mode4_process(uint32_t now_ms)
{
const uint16_t upper_move_ms = MAGIC_BOX_MODE4_FORWARD_MS +
MAGIC_BOX_MODE4_BACKWARD_MS;
const uint16_t cycle_ms = upper_move_ms + MAGIC_BOX_MODE4_STOP_MS;
uint16_t elapsed_ms = (uint32_t)(now_ms - s_mode.state_started_ms);
int8_t upper_direction;
uint8_t upper_speed;
if (elapsed_ms >= cycle_ms) {
s_mode.state_started_ms = now_ms;
elapsed_ms = 0;
s_mode.lower_started_ms = now_ms;
s_mode.lower_direction = -s_mode.lower_direction;
s_mode.lower_duration_ms = app_mode4_random_step_ms();
}
if ((uint32_t)(now_ms - s_mode.lower_started_ms) >=
s_mode.lower_duration_ms) {
s_mode.lower_started_ms = now_ms;
s_mode.lower_direction = -s_mode.lower_direction;
s_mode.lower_duration_ms = app_mode4_random_step_ms();
}
if (elapsed_ms < MAGIC_BOX_MODE4_FORWARD_MS) {
upper_direction = BSP_MOTOR_FORWARD;
upper_speed = MAGIC_BOX_HIGH_PERCENT;
} else if (elapsed_ms < upper_move_ms) {
upper_direction = BSP_MOTOR_BACKWARD;
upper_speed = MAGIC_BOX_MEDIUM_LOW_PERCENT;
} else {
upper_direction = BSP_MOTOR_STOP;
upper_speed = 0;
}
app_mode_outputs_set(upper_direction, upper_speed,
s_mode.lower_direction, MAGIC_BOX_HIGH_PERCENT);
}
static void app_mode5_process(uint32_t now_ms)
{
if ((uint32_t)(now_ms - s_mode.state_started_ms) >=
MAGIC_BOX_MODE5_CYCLE_MS) {
app_mode5_start_cycle(now_ms);
}
if ((uint32_t)(now_ms - s_mode.upper_started_ms) >=
s_mode.upper_duration_ms) {
s_mode.upper_started_ms = now_ms;
s_mode.upper_phase++;
if (s_mode.upper_phase == 1) {
s_mode.upper_direction = BSP_MOTOR_BACKWARD;
s_mode.upper_speed = MAGIC_BOX_HIGH_PERCENT;
s_mode.upper_duration_ms = MAGIC_BOX_MODE5_BACKWARD_MS;
} else if (s_mode.upper_phase == 2) {
s_mode.upper_direction = BSP_MOTOR_STOP;
s_mode.upper_speed = 0;
s_mode.upper_duration_ms = MAGIC_BOX_MODE5_UPPER_STOP_MS;
} else {
s_mode.upper_phase = 0;
s_mode.upper_direction = BSP_MOTOR_FORWARD;
s_mode.upper_speed = MAGIC_BOX_HIGH_PERCENT;
s_mode.upper_duration_ms = app_mode5_random_forward_ms();
}
}
if ((uint32_t)(now_ms - s_mode.lower_started_ms) >=
s_mode.lower_duration_ms) {
if (s_mode.lower_pattern >= 2 && ++s_mode.lower_segment < 8) {
s_mode.lower_started_ms = now_ms;
s_mode.lower_direction = -s_mode.lower_direction;
} else {
app_mode5_select_lower_pattern(now_ms);
}
}
app_mode_outputs_set(s_mode.upper_direction, s_mode.upper_speed,
s_mode.lower_direction, MAGIC_BOX_HIGH_PERCENT);
}
AppMode_Ret_t app_mode_init(void)
{
s_mode.active = 0;
s_mode.step = 0;
s_mode.step_t0 = timer_get_ms();
memset(&s_mode, 0, sizeof(s_mode));
s_mode.initialized = 1;
bsp_motor_set(BSP_MOTOR_STOP);
bsp_drive_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_mode.initialized ? APP_MODE_OK : APP_MODE_ERR_NOT_INIT;
}
/**
* @brief 读取当前自动模式
* @return 0=停转,1~6=对应玩法
*/
uint8_t app_mode_get_active(void)
{
return s_mode.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_mode.initialized) {
return APP_MODE_ERR_NOT_INIT;
}
if (mode < 1 || mode > 6) {
if (mode < 1 || mode > 5) {
app_mode_stop();
return APP_MODE_ERR_PARAM;
}
s_mode.active = mode;
s_mode.step = 0; /* 从节拍表第 0 步重新开始 */
app_mode_apply_step();
log_info("auto mode %d start\n", mode);
app_mode_start_current(timer_get_ms());
log_info("magic box mode %d start\n", mode);
return APP_MODE_OK;
}
/**
* @brief 停止当前玩法并刹车
* @return 无
*/
void app_mode_stop(void)
{
if (s_mode.active) {
log_info("auto mode %d stop\n", s_mode.active);
}
s_mode.active = 0;
s_mode.step = 0;
s_mode.step_t0 = timer_get_ms();
bsp_motor_set(BSP_MOTOR_STOP);
s_mode.output_valid = 0;
bsp_drive_stop();
}
/**
* @brief 推进当前节拍(按 timer_get_ms 真实时间)
* @return 无
*/
void app_mode_run(void)
{
const AppModeStep_t *tbl;
uint32_t now_ms;
bsp_motor_switch_tick(); /* 换向死区:H 桥松开后再切方向 */
if (!s_mode.initialized || s_mode.active == 0) {
bsp_motor_switch_tick();
if (!s_mode.initialized || !s_mode.active) {
return;
}
tbl = s_tables[s_mode.active];
if (!tbl) {
return;
}
if ((timer_get_ms() - s_mode.step_t0) >= tbl[s_mode.step].ms) {
s_mode.step++;
app_mode_apply_step();
now_ms = timer_get_ms();
switch (s_mode.active) {
case 1:
app_mode1_process(now_ms);
break;
case 2:
app_mode2_process(now_ms);
break;
case 3:
app_mode3_process(now_ms);
break;
case 4:
app_mode4_process(now_ms);
break;
case 5:
app_mode5_process(now_ms);
break;
default:
app_mode_stop();
break;
}
}
+4 -4
View File
@@ -1,6 +1,6 @@
/******************************************************************************
* @file app_mode.h
* @brief 6 种自动玩法:节拍含方向与 PWM 占空比0=停,1~6=波形
* @brief 弹力魔盒 5 种双电机自动玩法0=停,1~5=玩法
* @author cyWu <1917507415@qq.com>
* @date 2026.08.24
* @version V1.2.0
@@ -28,8 +28,8 @@ typedef enum {
AppMode_Ret_t app_mode_init(void);
/**
* @brief 启动自动模式 1~6,循环播放节拍
* @param mode 玩法编号,范围 1~6
* @brief 启动自动模式 1~5,循环播放节拍
* @param mode 玩法编号,范围 1~5
* @return APP_MODE_OK=成功,APP_MODE_ERR_NOT_INIT=未初始化,APP_MODE_ERR_PARAM=参数非法
*/
AppMode_Ret_t app_mode_start(uint8_t mode);
@@ -48,7 +48,7 @@ void app_mode_run(void);
/**
* @brief 读取当前自动模式
* @return 0=停转,1~6=对应玩法
* @return 0=停转,1~5=对应玩法
*/
uint8_t app_mode_get_active(void);
+6
View File
@@ -36,6 +36,12 @@ void app_pair_start(void)
s_pair.elapsed_ms = 0;
}
void app_pair_stop(void)
{
s_pair.active = 0;
s_pair.elapsed_ms = 0;
}
/**
* @brief 推进配对计时并检查结果,每个业务 tick 调用一次
* @param dt 距上次调用的毫秒数
+3
View File
@@ -29,6 +29,9 @@ typedef enum {
*/
void app_pair_start(void);
/** End the current pairing window without changing saved binding data. */
void app_pair_stop(void);
/**
* @brief 推进配对计时并检查结果,每个业务 tick 调用一次
* @param dt 距上次调用的毫秒数
+5
View File
@@ -35,6 +35,7 @@
#define BAT_UPDATE_MS (BAT_SAMPLE_MS * BAT_AVG_N) /**< 凑够一次新均值的真实耗时,喂给 app_battery */
#define BAT_BOOT_MS 3000 /* 上电 3s 内不因低电深睡 */
#define BAT_LOG_MS 10000
#define BAT_PERIODIC_LOG_ENABLE 0 /* Production standby must not wake UART periodically. */
#define BAT_LOW_WIN 3 /* 连续约 2.4s 才进低电告警(仅闪灯,不关机) */
/** 充电检测:CHG/VPWR 消抖后的结果 */
@@ -401,6 +402,7 @@ static void power_battery_check_low(uint16_t mv)
*/
static void power_battery_periodic_log(uint16_t dt)
{
#if BAT_PERIODIC_LOG_ENABLE
AppBatterySample_t *s = &s_power.sample;
s->log_ms = app_add_ms(s->log_ms, dt);
@@ -408,6 +410,9 @@ static void power_battery_periodic_log(uint16_t dt)
s->log_ms = 0;
log_info("bat %d%% %dmV alm=%d\n", s->pct, s->mv, s_power.alarm.low_bat);
}
#else
(void)dt;
#endif
}
/**
+112
View File
@@ -0,0 +1,112 @@
/******************************************************************************
* @file app_shake_wake.c
* @brief PA3 vibration-switch wake detector.
*
* The fitted switch is normally closed to ground. A vibration opens the
* contact, so PA3 goes high, then returns low when the contact closes again.
* A wake is accepted only after the high level has been stable for the
* re-arm interval and is generated on the following high-to-low transition.
******************************************************************************/
#include "app_shake_wake.h"
#include "board_pin.h"
#include "asm/gpio.h"
typedef struct {
AppShakeWakeAllowedFn allowed;
AppShakeWakeTriggerFn trigger;
uint8_t enable;
uint8_t armed;
uint8_t trigger_pending;
uint8_t high_ticks;
} AppShakeWakeCtx_t;
static AppShakeWakeCtx_t s_shake;
static void shake_reset_detection(void)
{
s_shake.armed = 0;
s_shake.trigger_pending = 0;
s_shake.high_ticks = 0;
}
static void shake_pin_init(void)
{
#if BOARD_SHAKE_ENABLE
gpio_set_die(BOARD_SHAKE_PIN, 1);
gpio_direction_input(BOARD_SHAKE_PIN);
gpio_set_pull_up(BOARD_SHAKE_PIN, 1);
gpio_set_pull_down(BOARD_SHAKE_PIN, 0);
#endif
}
void app_shake_wake_init(AppShakeWakeAllowedFn allowed,
AppShakeWakeTriggerFn trigger)
{
s_shake.allowed = allowed;
s_shake.trigger = trigger;
s_shake.enable = 0;
shake_reset_detection();
shake_pin_init();
}
void app_shake_wake_set_enable(uint8_t enable)
{
s_shake.enable = !!enable;
shake_reset_detection();
}
void app_shake_wake_reset(void)
{
shake_reset_detection();
}
void app_shake_wake_poll(void)
{
uint8_t pin_high;
uint8_t rearm_ticks;
if (!s_shake.enable) {
shake_reset_detection();
return;
}
#if BOARD_SHAKE_ENABLE
pin_high = (uint8_t)(gpio_read(BOARD_SHAKE_PIN) == BOARD_SHAKE_ACTIVE_LEVEL);
#else
pin_high = 0;
#endif
rearm_ticks = (uint8_t)(BOARD_SHAKE_REARM_MS / BOARD_SHAKE_POLL_MS);
if (!rearm_ticks) {
rearm_ticks = 1;
}
if (pin_high) {
if (s_shake.high_ticks < rearm_ticks) {
s_shake.high_ticks++;
}
if (s_shake.high_ticks >= rearm_ticks) {
s_shake.armed = 1;
}
return;
}
/* The contact has closed again: this is the verified wake edge. */
s_shake.high_ticks = 0;
if (s_shake.armed) {
s_shake.armed = 0;
s_shake.trigger_pending = 1;
}
if (!s_shake.trigger_pending) {
return;
}
s_shake.trigger_pending = 0;
if (!s_shake.allowed || !s_shake.allowed()) {
shake_reset_detection();
return;
}
if (s_shake.trigger) {
s_shake.trigger();
}
}
+22
View File
@@ -0,0 +1,22 @@
/******************************************************************************
* @file app_shake_wake.h
* @brief PA3 vibration-switch wake detector.
******************************************************************************/
#ifndef __APP_SHAKE_WAKE_H__
#define __APP_SHAKE_WAKE_H__
#include <stdint.h>
typedef uint8_t (*AppShakeWakeAllowedFn)(void);
typedef void (*AppShakeWakeTriggerFn)(void);
/* The detector owns only PA3 sampling state. Product policy stays in callbacks.
* app_shake_wake_poll() is driven by the shared BOARD_SHAKE_POLL_MS business tick. */
void app_shake_wake_init(AppShakeWakeAllowedFn allowed,
AppShakeWakeTriggerFn trigger);
void app_shake_wake_set_enable(uint8_t enable);
void app_shake_wake_reset(void);
void app_shake_wake_poll(void);
#endif /* __APP_SHAKE_WAKE_H__ */