feat: 完成弹力魔盒初版固件
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
#include "classic/tws_api.h"
|
||||
#include "rcsp_adv_user_update.h"
|
||||
#include "bt_tws.h"
|
||||
#include "usr_le_api.h"
|
||||
|
||||
#if (TCFG_BLE_DEMO_SELECT == DEF_BLE_DEMO_ADV_RCSP)
|
||||
|
||||
@@ -106,6 +107,14 @@ static const struct conn_update_param_t connection_param_table[] = {
|
||||
/* {12, 28, 4, 600},//3.7 */
|
||||
/* {12, 24, 30, 600},//3.05 */
|
||||
};
|
||||
/* DP0=0 keeps BLE connected, but can trade a little command latency for much
|
||||
* fewer idle RF events. The preferred setting has a worst-case idle response
|
||||
* interval of about 640 ms; later entries are gateway compatibility fallbacks. */
|
||||
static const struct conn_update_param_t connection_param_table_standby[] = {
|
||||
{24, 32, 15, 600},
|
||||
{20, 32, 15, 600},
|
||||
{16, 24, 10, 600},
|
||||
};
|
||||
static const struct conn_update_param_t connection_param_table_ota[] = {
|
||||
{16, 24, 0, 600},
|
||||
{12, 28, 0, 600},//11
|
||||
@@ -129,6 +138,8 @@ static u8 scan_rsp_data[ADV_RSP_PACKET_MAX];//max is 31
|
||||
/* #define scan_rsp_data &att_ram_buffer[32] */
|
||||
|
||||
static u32 ble_timer_handle = 0;
|
||||
static u16 connection_update_timer;
|
||||
static uint8_t connection_standby_mode;
|
||||
static char *gap_device_name = "br22_ble_test";
|
||||
static u8 gap_device_name_len = 0;
|
||||
volatile static u8 ble_work_state = 0;
|
||||
@@ -165,7 +176,10 @@ static int get_buffer_vaild_len(void *priv);
|
||||
//------------------------------------------------------
|
||||
static void send_request_connect_parameter(u8 table_index)
|
||||
{
|
||||
struct conn_update_param_t *param = (void *)&connection_param_table[table_index];//static ram
|
||||
const struct conn_update_param_t *table = connection_standby_mode ?
|
||||
connection_param_table_standby :
|
||||
connection_param_table;
|
||||
struct conn_update_param_t *param = (void *)&table[table_index];//static ram
|
||||
|
||||
log_info("update_request:-%d-%d-%d-%d-\n", param->interval_min, param->interval_max, param->latency, param->timeout);
|
||||
if (con_handle) {
|
||||
@@ -182,6 +196,35 @@ static void check_connetion_updata_deal(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void connection_param_update_apply(void *priv)
|
||||
{
|
||||
(void)priv;
|
||||
connection_update_timer = 0;
|
||||
connection_update_cnt = 0;
|
||||
check_connetion_updata_deal();
|
||||
}
|
||||
|
||||
void usr_ble_set_standby(uint8_t standby)
|
||||
{
|
||||
standby = !!standby;
|
||||
if (connection_standby_mode == standby) {
|
||||
return;
|
||||
}
|
||||
|
||||
connection_standby_mode = standby;
|
||||
connection_update_cnt = 0;
|
||||
if (!con_handle) {
|
||||
return;
|
||||
}
|
||||
if (connection_update_timer) {
|
||||
sys_timeout_del(connection_update_timer);
|
||||
connection_update_timer = 0;
|
||||
}
|
||||
/* DP writes are consumed from a usr_timer callback. Defer the controller
|
||||
* command to system-timer context. */
|
||||
connection_update_timer = sys_timeout_add(NULL, connection_param_update_apply, 100);
|
||||
}
|
||||
|
||||
|
||||
static void send_request_connect_parameter_ota(u8 table_index)
|
||||
{
|
||||
|
||||
@@ -734,6 +734,17 @@ struct port_wakeup port0 = {
|
||||
.iomap = TCFG_IOKEY_POWER_ONE_PORT, //唤醒口选择
|
||||
};
|
||||
|
||||
#if BOARD_CHG_ENABLE
|
||||
/* R18 is not fitted on CBC3, so VBUS never reaches VPWR in softoff. Wake on
|
||||
* the fitted ME4054 CHG_ON signal instead; it falls when charging starts. */
|
||||
struct port_wakeup board_chg_wakeup_port = {
|
||||
.pullup_down_enable = ENABLE,
|
||||
.edge = FALLING_EDGE,
|
||||
.filter = PORT_FLT_8ms,
|
||||
.iomap = BOARD_CHG_PIN,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if (TCFG_TEST_BOX_ENABLE || TCFG_CHARGESTORE_ENABLE || TCFG_ANC_BOX_ENABLE || TCFG_UMIDIGI_BOX_ENABLE)
|
||||
struct port_wakeup port1 = {
|
||||
.pullup_down_enable = DISABLE, //配置I/O 内部上下拉是否使能
|
||||
@@ -779,6 +790,9 @@ const struct wakeup_param wk_param = {
|
||||
.aport[1] = &vbat_port,
|
||||
.aport[2] = &ldoin_port,
|
||||
#endif
|
||||
#if BOARD_CHG_ENABLE
|
||||
.port[BOARD_CHG_WAKEUP_INDEX] = &board_chg_wakeup_port,
|
||||
#endif
|
||||
};
|
||||
|
||||
void gSensor_wkupup_disable(void)
|
||||
@@ -986,6 +1000,10 @@ void board_set_soft_poweroff(void)
|
||||
#if TCFG_IOKEY_ENABLE
|
||||
soff_gpio_protect(TCFG_IOKEY_POWER_ONE_PORT);
|
||||
#endif
|
||||
#if BOARD_CHG_ENABLE
|
||||
/* Preserve PG8 input state and its external pull-up for softoff wake. */
|
||||
soff_gpio_protect(BOARD_CHG_PIN);
|
||||
#endif
|
||||
|
||||
#if (!(TCFG_LP_TOUCH_KEY_ENABLE && TCFG_LP_TOUCH_KEY1_EN))
|
||||
//默认唤醒io
|
||||
@@ -1015,8 +1033,6 @@ void board_set_soft_poweroff(void)
|
||||
void sleep_exit_callback(u32 usec)
|
||||
{
|
||||
sleep_exit_callback_common(NULL);
|
||||
|
||||
putchar('>');
|
||||
}
|
||||
|
||||
void sleep_enter_callback(u8 step)
|
||||
@@ -1024,7 +1040,6 @@ void sleep_enter_callback(u8 step)
|
||||
/* 此函数禁止添加打印 */
|
||||
if (step == 1) {
|
||||
bsp_hw_sleep_pwm_off();
|
||||
putchar('<');
|
||||
} else {
|
||||
gpio_set_pull_up(TCFG_CHARGESTORE_PORT, 0);
|
||||
gpio_set_pull_down(TCFG_CHARGESTORE_PORT, 0);
|
||||
|
||||
+23
-19
@@ -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
@@ -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; /**< DP0:0=软关,电机/玩法停,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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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~10000,STOP 忽略 */
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 距上次调用的毫秒数
|
||||
|
||||
@@ -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 距上次调用的毫秒数
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -1,6 +1,6 @@
|
||||
/* ================================================================
|
||||
* jb_product.c — 用户层实现(请填补空白处)
|
||||
* 设备: 门夹弹力绳 | 生成时间: 2026-08-21 18:01:47
|
||||
* 设备: 猫猫捕猎罩 | 生成时间: 2026-08-18 16:24:47
|
||||
*
|
||||
* ══ 开发流程 ══
|
||||
* 1. 实现硬件抽象: jbGetTimerCount(定时器由 SOC SDK 提供)
|
||||
@@ -13,6 +13,8 @@
|
||||
*/
|
||||
|
||||
#include "jb_product.h"
|
||||
#include "app_function.h"
|
||||
#include "usr_rtc.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 全局变量
|
||||
@@ -21,6 +23,79 @@
|
||||
jb_data_point_t gDevData;
|
||||
static volatile uint32_t timerMs;
|
||||
|
||||
#define JB_DND_LEN 13
|
||||
#define JB_DND_ENABLE_OFFSET 0
|
||||
#define JB_DND_WEEK_OFFSET 2
|
||||
#define JB_DND_START_HOUR_OFFSET 9
|
||||
#define JB_DND_START_MIN_OFFSET 10
|
||||
#define JB_DND_END_HOUR_OFFSET 11
|
||||
#define JB_DND_END_MIN_OFFSET 12
|
||||
static uint8_t jbLastNoDisturbActive;
|
||||
|
||||
/* Return weekday with Monday=0, matching DP7 bytes 2..8. */
|
||||
static uint8_t jbWeekdayMonday0(const struct sys_time *time)
|
||||
{
|
||||
static const uint8_t monthOffset[12] = {
|
||||
0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4
|
||||
};
|
||||
uint16_t year = time->year;
|
||||
uint8_t sunday0;
|
||||
|
||||
if (time->month < 3) {
|
||||
year--;
|
||||
}
|
||||
sunday0 = (uint8_t)((year + year / 4U - year / 100U + year / 400U +
|
||||
monthOffset[time->month - 1U] + time->day) % 7U);
|
||||
return (uint8_t)((sunday0 + 6U) % 7U);
|
||||
}
|
||||
|
||||
static void jbNoDisturbNormalize(uint8_t *cfg)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
cfg[JB_DND_ENABLE_OFFSET] = !!cfg[JB_DND_ENABLE_OFFSET];
|
||||
for (i = 0; i < 7; i++) {
|
||||
cfg[JB_DND_WEEK_OFFSET + i] = !!cfg[JB_DND_WEEK_OFFSET + i];
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t jbNoDisturbScheduleValid(const uint8_t *cfg)
|
||||
{
|
||||
uint8_t i;
|
||||
|
||||
if (cfg[JB_DND_START_HOUR_OFFSET] >= 24 ||
|
||||
cfg[JB_DND_END_HOUR_OFFSET] >= 24 ||
|
||||
cfg[JB_DND_START_MIN_OFFSET] >= 60 ||
|
||||
cfg[JB_DND_END_MIN_OFFSET] >= 60) {
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < 7; i++) {
|
||||
if (cfg[JB_DND_WEEK_OFFSET + i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t jbNoDisturbActive(void)
|
||||
{
|
||||
/* Match the verified product behavior: DP7 byte 0 is the global
|
||||
* no-disturb switch. Schedule bytes are retained and reported, but do
|
||||
* not gate the local vibration wake path. */
|
||||
return !!gDevData.no_disturb_enable[JB_DND_ENABLE_OFFSET];
|
||||
}
|
||||
|
||||
static void jbNoDisturbUpdate(void)
|
||||
{
|
||||
uint8_t active = jbNoDisturbActive();
|
||||
|
||||
if (active == jbLastNoDisturbActive) {
|
||||
return;
|
||||
}
|
||||
jbLastNoDisturbActive = active;
|
||||
app_function_set_no_disturb(active);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* userInit — DP 默认值
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
@@ -30,16 +105,16 @@ void userInit(void)
|
||||
|
||||
gDevData.power = 0; /* DP0 开关 0=关 1=开 */
|
||||
gDevData.play_mode = 0; /* PLAY_MODE_0 (停止复位) */
|
||||
memset(gDevData.hand_mode, 0, 2); /* DP2 手动模式 */
|
||||
gDevData.calibration = 0; /* DP3 绳索校准 0=关 1=开 */
|
||||
gDevData.numberturns = 0; /* DP4 绳索圈数 */
|
||||
gDevData.alm_low_battery = 0; /* DP5 低电量报警 0=关 1=开 */
|
||||
gDevData.battery = 0; /* DP6 电量上报 */
|
||||
gDevData.charge = 1; /* CHARGE_1 (充电中) */
|
||||
memset(gDevData.no_disturb_enable, 0, 13); /* DP8 勿扰模式开关 */
|
||||
gDevData.reserva_dp1 = 0; /* DP9 预留DP1 */
|
||||
gDevData.reserva_dp2 = 0; /* DP10 预留DP2 */
|
||||
gDevData.reserva_dp3 = 0; /* DP11 预留DP3 */
|
||||
gDevData.hand_mode = 0; /* HAND_MODE_0 (忽略) */
|
||||
gDevData.alm_low_battery = 0; /* DP3 低电量报警 0=关 1=开 */
|
||||
gDevData.beep_trig = 0; /* DP4 声音开关 0=关 1=开 */
|
||||
gDevData.battery = 0; /* DP5 电量上报 */
|
||||
gDevData.shake_wake = 0; /* DP6 震动触发开机 0=关 1=开 */
|
||||
memset(gDevData.no_disturb_enable, 0, 13); /* DP7 勿扰模式开关 */
|
||||
gDevData.charge = 0; /* CHARGE_0 (不支持充电) */
|
||||
gDevData.reserva_dp3 = 0; /* DP10 预留DP3 */
|
||||
jbLastNoDisturbActive = 0xff;
|
||||
jbNoDisturbUpdate();
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
@@ -48,16 +123,18 @@ void userInit(void)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
void userHandle(void)
|
||||
{
|
||||
jbNoDisturbUpdate();
|
||||
|
||||
/* ── 可上报 DP (RO / RW): 用户数据采集 ── */
|
||||
// gDevData.power = read_sensor(); /* DP0 开关 */
|
||||
// gDevData.play_mode = read_sensor(); /* DP1 自动模式 */
|
||||
// gDevData.alm_low_battery = read_sensor(); /* DP5 低电量报警 */
|
||||
// gDevData.battery = read_sensor(); /* DP6 电量上报 */
|
||||
// gDevData.charge = read_sensor(); /* DP7 充电状态 */
|
||||
// memcpy(gDevData.no_disturb_enable, sensor_read(), 13); /* DP8 勿扰模式开关 */
|
||||
// gDevData.reserva_dp1 = read_sensor(); /* DP9 预留DP1 */
|
||||
// gDevData.reserva_dp2 = read_sensor(); /* DP10 预留DP2 */
|
||||
// gDevData.reserva_dp3 = read_sensor(); /* DP11 预留DP3 */
|
||||
// gDevData.alm_low_battery = read_sensor(); /* DP3 低电量报警 */
|
||||
// gDevData.beep_trig = read_sensor(); /* DP4 声音开关 */
|
||||
// gDevData.battery = read_sensor(); /* DP5 电量上报 */
|
||||
// gDevData.shake_wake = read_sensor(); /* DP6 震动触发开机 */
|
||||
// memcpy(gDevData.no_disturb_enable, sensor_read(), 13); /* DP7 勿扰模式开关 */
|
||||
// gDevData.charge = read_sensor(); /* DP8 充电状态 */
|
||||
// gDevData.reserva_dp3 = read_sensor(); /* DP10 预留DP3 */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
@@ -69,6 +146,8 @@ void userHandle(void)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
{
|
||||
int8_t result = JB_OK;
|
||||
|
||||
if (!info || !ctrl)
|
||||
{
|
||||
return -1;
|
||||
@@ -81,98 +160,47 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
|
||||
/* ── DP0 开关 (bool) ── */
|
||||
case DP_ID_POWER:
|
||||
if (ctrl->power)
|
||||
{
|
||||
gDevData.power = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.power = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
}
|
||||
app_function_dp_power_write(ctrl->power);
|
||||
break;
|
||||
|
||||
/* ── DP1 自动模式 (enum) ── */
|
||||
case DP_ID_PLAY_MODE:
|
||||
gDevData.play_mode = ctrl->play_mode;
|
||||
switch (ctrl->play_mode)
|
||||
{
|
||||
case PLAY_MODE_0: /* 停止复位 */
|
||||
// TODO: 处理 停止复位
|
||||
break;
|
||||
case PLAY_MODE_1: /* 弹力伪装 */
|
||||
// TODO: 处理 弹力伪装
|
||||
break;
|
||||
case PLAY_MODE_2: /* 飞鸟俯冲 */
|
||||
// TODO: 处理 飞鸟俯冲
|
||||
break;
|
||||
case PLAY_MODE_3: /* 云阶小陀螺 */
|
||||
// TODO: 处理 云阶小陀螺
|
||||
break;
|
||||
case PLAY_MODE_4: /* 起伏攻防战 */
|
||||
// TODO: 处理 起伏攻防战
|
||||
break;
|
||||
case PLAY_MODE_5: /* 微风拂动 */
|
||||
// TODO: 处理 微风拂动
|
||||
break;
|
||||
case PLAY_MODE_6: /* 闪电快闪 */
|
||||
// TODO: 处理 闪电快闪
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
app_function_dp_play_write(ctrl->play_mode);
|
||||
break;
|
||||
|
||||
/* ── DP2 手动模式 (raw) ── */
|
||||
/* ── DP2 手动模式 (enum) ── */
|
||||
case DP_ID_HAND_MODE:
|
||||
memcpy(gDevData.hand_mode, ctrl->hand_mode, 2);
|
||||
/* 薄适配:通知业务层;03=停,00=空闲 */
|
||||
{
|
||||
extern void usr_dp_hand_write(uint8_t cmd);
|
||||
usr_dp_hand_write(ctrl->hand_mode[0]);
|
||||
}
|
||||
app_function_dp_hand_write(ctrl->hand_mode);
|
||||
break;
|
||||
|
||||
/* ── DP3 绳索校准 (bool) ── */
|
||||
case DP_ID_CALIBRATION:
|
||||
if (ctrl->calibration)
|
||||
/* ── DP4 声音开关 (bool) ── */
|
||||
case DP_ID_BEEP_TRIG:
|
||||
if (ctrl->beep_trig)
|
||||
{
|
||||
gDevData.calibration = 1;
|
||||
gDevData.beep_trig = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.calibration = 0;
|
||||
gDevData.beep_trig = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
}
|
||||
break;
|
||||
|
||||
/* ── DP4 绳索圈数 (uint8) ── */
|
||||
case DP_ID_NUMBERTURNS:
|
||||
gDevData.numberturns = ctrl->numberturns;
|
||||
// TODO: 根据 ctrl->numberturns 执行动作
|
||||
/* ── DP6 震动触发开机 (bool) ── */
|
||||
case DP_ID_SHAKE_WAKE:
|
||||
app_function_set_shake_wake(ctrl->shake_wake);
|
||||
break;
|
||||
|
||||
/* ── DP8 勿扰模式开关 (raw) ── */
|
||||
/* ── DP7 勿扰模式开关 (raw) ── */
|
||||
case DP_ID_NO_DISTURB_ENABLE:
|
||||
memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable, 13);
|
||||
// TODO: 根据 gDevData.no_disturb_enable 执行动作 (长度 13 字节)
|
||||
memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable,
|
||||
JB_DND_LEN);
|
||||
jbNoDisturbNormalize(gDevData.no_disturb_enable);
|
||||
jbNoDisturbUpdate();
|
||||
break;
|
||||
|
||||
/* ── DP9 预留DP1 (uint32) ── */
|
||||
case DP_ID_RESERVA_DP1:
|
||||
gDevData.reserva_dp1 = ctrl->reserva_dp1;
|
||||
// TODO: 根据 ctrl->reserva_dp1 执行动作
|
||||
break;
|
||||
|
||||
/* ── DP10 预留DP2 (uint32) ── */
|
||||
case DP_ID_RESERVA_DP2:
|
||||
gDevData.reserva_dp2 = ctrl->reserva_dp2;
|
||||
// TODO: 根据 ctrl->reserva_dp2 执行动作
|
||||
break;
|
||||
|
||||
/* ── DP11 预留DP3 (uint32) ── */
|
||||
/* ── DP10 预留DP3 (uint32) ── */
|
||||
case DP_ID_RESERVA_DP3:
|
||||
gDevData.reserva_dp3 = ctrl->reserva_dp3;
|
||||
// TODO: 根据 ctrl->reserva_dp3 执行动作
|
||||
@@ -182,21 +210,16 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 0x30 OTA 升级请求
|
||||
* 返回 0=接受升级(协议层应答 JB_OK + MaxDataLen)
|
||||
* 非 0=拒绝,返回值即错误码(JB_OTA_*)
|
||||
*
|
||||
* ⚠ SOC 侧 OTA 尚未实现:当前仅应答,不执行任何升级动作。 */
|
||||
int8_t jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
|
||||
/* 0x30 OTA 升级请求 */
|
||||
void jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
|
||||
{
|
||||
(void)fwVersion; (void)fwLength; (void)fwCrc32;
|
||||
// TODO: 检查 Flash 空间与版本,决定是否接收升级
|
||||
// 允许: 返回 JB_OK(协议层应答 JB_OK + MaxDataLen)
|
||||
// 拒绝: 返回对应 JB_OTA_* 错误码(如 JB_OTA_VER_LOW / JB_OTA_SPACE_ERR)
|
||||
return JB_OK;
|
||||
// 允许: 直接返回(协议层默认应答 JB_OK + MaxDataLen=128)
|
||||
// 拒绝: 在 jbHandleOtaNotify 中改为 jbSendFrame(CMD_OTA_NOTIFY_ACK, sn, 错误码, 3)
|
||||
}
|
||||
|
||||
/* 0x32 OTA 数据(含分片序号)*/
|
||||
@@ -219,8 +242,7 @@ void jbOnOtaStop(void)
|
||||
- 可在本回调中设置"XX 完成/失败"标志,供主循环判断"确认成功才继续" */
|
||||
void jbOnAck(uint8_t cmd, uint8_t result)
|
||||
{
|
||||
(void)cmd;
|
||||
(void)result;
|
||||
(void)cmd; (void)result;
|
||||
// TODO: switch(cmd){ case CMD_OTA_COMPLETE_ACK: gOtaDone = (result==JB_OK); break; case CMD_OTA_STOP_MCU_ACK: ... }
|
||||
}
|
||||
|
||||
@@ -232,7 +254,7 @@ void jbOnAck(uint8_t cmd, uint8_t result)
|
||||
void jbTimerIrq(void)
|
||||
{
|
||||
timerMs++;
|
||||
} /* 在 1ms 中断中调用本函数 */
|
||||
} /* 在 1ms 中断中调用本函数 */
|
||||
|
||||
uint32_t jbGetTimerCount(void)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* ================================================================
|
||||
* jb_product.h — 用户层头文件(自动生成)
|
||||
* 设备: 门夹弹力绳 | 生成时间: 2026-08-21 18:01:47
|
||||
* 设备: 猫猫捕猎罩 | 生成时间: 2026-08-18 16:24:47
|
||||
*
|
||||
* 声明用户需实现的函数。数据结构定义见 jb_protocol.h
|
||||
* ================================================================
|
||||
@@ -43,7 +43,7 @@ void jbTimerIrq(void); /* Call from 1ms timer ISR */
|
||||
|
||||
/* =========== 协议回调(声明见 jb_protocol.h)================== */
|
||||
/* int8_t jbEventProcess(...); // 0x03 控制事件 */
|
||||
/* int8_t jbOnOtaNotify(...); // 0x30 OTA 请求 */
|
||||
/* void jbOnOtaNotify(...); // 0x30 OTA 请求 */
|
||||
/* void jbOnOtaData(...); // 0x32 OTA 数据 */
|
||||
/* void jbOnOtaStop(); // 0x36/0x38 停止 OTA */
|
||||
/* void jbOnAck(...); // 任意请求 ACK(结果) */
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/* ================================================================
|
||||
* jb_protocol.c — 见宝 IOT 协议实现(自动生成)
|
||||
* 设备: 门夹弹力绳 | 生成时间: 2026-08-21 18:01:47
|
||||
* 设备: 猫猫捕猎罩 | 生成时间: 2026-08-18 16:24:47
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
#include "jb_protocol.h"
|
||||
#include "jb_common.h"
|
||||
#include "jb_product.h"
|
||||
#include "jb_ringbuffer.h"
|
||||
#include "stdio.h"
|
||||
#include "jb_common.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 全局实例
|
||||
@@ -168,46 +167,44 @@ static uint16_t jbPackDps(const jb_data_point_t *dp,
|
||||
bitmap[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
|
||||
out[valIdx++] = (uint8_t)dp->play_mode;
|
||||
}
|
||||
/* DP5 低电量报警 (bool, 1B, RO) */
|
||||
/* DP3 低电量报警 (bool, 1B, RO) */
|
||||
if (mask[DP_ID_ALM_LOW_BATTERY / 8] & (1 << (DP_ID_ALM_LOW_BATTERY % 8)))
|
||||
{
|
||||
bitmap[DP_ID_ALM_LOW_BATTERY / 8] |= (1 << (DP_ID_ALM_LOW_BATTERY % 8));
|
||||
out[valIdx++] = dp->alm_low_battery ? 1 : 0;
|
||||
}
|
||||
/* DP6 电量上报 (uint8, 1B, RO) */
|
||||
/* DP4 声音开关 (bool, 1B, RW) */
|
||||
if (mask[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8)))
|
||||
{
|
||||
bitmap[DP_ID_BEEP_TRIG / 8] |= (1 << (DP_ID_BEEP_TRIG % 8));
|
||||
out[valIdx++] = dp->beep_trig ? 1 : 0;
|
||||
}
|
||||
/* DP5 电量上报 (uint8, 1B, RO) */
|
||||
if (mask[DP_ID_BATTERY / 8] & (1 << (DP_ID_BATTERY % 8)))
|
||||
{
|
||||
bitmap[DP_ID_BATTERY / 8] |= (1 << (DP_ID_BATTERY % 8));
|
||||
out[valIdx++] = (uint8_t)dp->battery;
|
||||
}
|
||||
/* DP7 充电状态 (enum, 1B, RO) */
|
||||
if (mask[DP_ID_CHARGE / 8] & (1 << (DP_ID_CHARGE % 8)))
|
||||
/* DP6 震动触发开机 (bool, 1B, RW) */
|
||||
if (mask[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
|
||||
out[valIdx++] = (uint8_t)dp->charge;
|
||||
bitmap[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
|
||||
out[valIdx++] = dp->shake_wake ? 1 : 0;
|
||||
}
|
||||
/* DP8 勿扰模式开关 (raw, 13B, RW) */
|
||||
/* DP7 勿扰模式开关 (raw, 13B, RW) */
|
||||
if (mask[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_NO_DISTURB_ENABLE / 8] |= (1 << (DP_ID_NO_DISTURB_ENABLE % 8));
|
||||
memcpy(&out[valIdx], dp->no_disturb_enable, 13);
|
||||
valIdx += 13;
|
||||
}
|
||||
/* DP9 预留DP1 (uint32, 4B, RW) */
|
||||
if (mask[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8)))
|
||||
/* DP8 充电状态 (enum, 1B, RO) */
|
||||
if (mask[DP_ID_CHARGE / 8] & (1 << (DP_ID_CHARGE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
|
||||
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp1);
|
||||
valIdx += 4;
|
||||
bitmap[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
|
||||
out[valIdx++] = (uint8_t)dp->charge;
|
||||
}
|
||||
/* DP10 预留DP2 (uint32, 4B, RW) */
|
||||
if (mask[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8));
|
||||
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp2);
|
||||
valIdx += 4;
|
||||
}
|
||||
/* DP11 预留DP3 (uint32, 4B, RW) */
|
||||
/* DP10 预留DP3 (uint32, 4B, RW) */
|
||||
if (mask[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
|
||||
{
|
||||
bitmap[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
|
||||
@@ -256,44 +253,43 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_PLAY_MODE;
|
||||
}
|
||||
}
|
||||
/* DP2 手动模式 (raw, 2B, WO) */
|
||||
/* DP2 手动模式 (enum, 1B, WO) */
|
||||
if (bitmap[DP_ID_HAND_MODE / 8] & (1 << (DP_ID_HAND_MODE % 8)))
|
||||
{
|
||||
if (valIdx + 2 <= valLen)
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
memcpy(dp->hand_mode, &values[valIdx], 2);
|
||||
valIdx += 2;
|
||||
dp->hand_mode = values[valIdx++];
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_HAND_MODE;
|
||||
}
|
||||
}
|
||||
/* DP3 绳索校准 (bool, 1B, WO) */
|
||||
if (bitmap[DP_ID_CALIBRATION / 8] & (1 << (DP_ID_CALIBRATION % 8)))
|
||||
/* DP4 声音开关 (bool, 1B, RW) */
|
||||
if (bitmap[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->calibration = (values[valIdx++] != 0);
|
||||
dp->beep_trig = (values[valIdx++] != 0);
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_CALIBRATION;
|
||||
info->dp_ids[info->count++] = DP_ID_BEEP_TRIG;
|
||||
}
|
||||
}
|
||||
/* DP4 绳索圈数 (uint8, 1B, WO) */
|
||||
if (bitmap[DP_ID_NUMBERTURNS / 8] & (1 << (DP_ID_NUMBERTURNS % 8)))
|
||||
/* DP6 震动触发开机 (bool, 1B, RW) */
|
||||
if (bitmap[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->numberturns = values[valIdx++];
|
||||
dp->shake_wake = (values[valIdx++] != 0);
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_NUMBERTURNS;
|
||||
info->dp_ids[info->count++] = DP_ID_SHAKE_WAKE;
|
||||
}
|
||||
}
|
||||
/* DP8 勿扰模式开关 (raw, 13B, RW) */
|
||||
/* DP7 勿扰模式开关 (raw, 13B, RW) */
|
||||
if (bitmap[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8)))
|
||||
{
|
||||
if (valIdx + 13 <= valLen)
|
||||
@@ -306,33 +302,7 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
info->dp_ids[info->count++] = DP_ID_NO_DISTURB_ENABLE;
|
||||
}
|
||||
}
|
||||
/* DP9 预留DP1 (uint32, 4B, RW) */
|
||||
if (bitmap[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8)))
|
||||
{
|
||||
if (valIdx + 4 <= valLen)
|
||||
{
|
||||
dp->reserva_dp1 = (uint32_t)jbReadU32BE(&values[valIdx]);
|
||||
valIdx += 4;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_RESERVA_DP1;
|
||||
}
|
||||
}
|
||||
/* DP10 预留DP2 (uint32, 4B, RW) */
|
||||
if (bitmap[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8)))
|
||||
{
|
||||
if (valIdx + 4 <= valLen)
|
||||
{
|
||||
dp->reserva_dp2 = (uint32_t)jbReadU32BE(&values[valIdx]);
|
||||
valIdx += 4;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_RESERVA_DP2;
|
||||
}
|
||||
}
|
||||
/* DP11 预留DP3 (uint32, 4B, RW) */
|
||||
/* DP10 预留DP3 (uint32, 4B, RW) */
|
||||
if (bitmap[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
|
||||
{
|
||||
if (valIdx + 4 <= valLen)
|
||||
@@ -370,43 +340,43 @@ static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
|
||||
mask[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP5 低电量报警 */
|
||||
/* DP3 低电量报警 */
|
||||
if (last->alm_low_battery != cur->alm_low_battery)
|
||||
{
|
||||
mask[DP_ID_ALM_LOW_BATTERY / 8] |= (1 << (DP_ID_ALM_LOW_BATTERY % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP6 电量上报 */
|
||||
/* DP4 声音开关 */
|
||||
if (last->beep_trig != cur->beep_trig)
|
||||
{
|
||||
mask[DP_ID_BEEP_TRIG / 8] |= (1 << (DP_ID_BEEP_TRIG % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP5 电量上报 */
|
||||
if (last->battery != cur->battery)
|
||||
{
|
||||
mask[DP_ID_BATTERY / 8] |= (1 << (DP_ID_BATTERY % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP7 充电状态 */
|
||||
if (last->charge != cur->charge)
|
||||
/* DP6 震动触发开机 */
|
||||
if (last->shake_wake != cur->shake_wake)
|
||||
{
|
||||
mask[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
|
||||
mask[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP8 勿扰模式开关 */
|
||||
/* DP7 勿扰模式开关 */
|
||||
if (memcmp(last->no_disturb_enable, cur->no_disturb_enable, 13))
|
||||
{
|
||||
mask[DP_ID_NO_DISTURB_ENABLE / 8] |= (1 << (DP_ID_NO_DISTURB_ENABLE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP9 预留DP1 */
|
||||
if (last->reserva_dp1 != cur->reserva_dp1)
|
||||
/* DP8 充电状态 */
|
||||
if (last->charge != cur->charge)
|
||||
{
|
||||
mask[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
|
||||
mask[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP10 预留DP2 */
|
||||
if (last->reserva_dp2 != cur->reserva_dp2)
|
||||
{
|
||||
mask[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP11 预留DP3 */
|
||||
/* DP10 预留DP3 */
|
||||
if (last->reserva_dp3 != cur->reserva_dp3)
|
||||
{
|
||||
mask[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
|
||||
@@ -419,11 +389,9 @@ static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
|
||||
/* 置全量掩码(所有可上报 DP)*/
|
||||
static void jbSetFullMask(uint8_t *mask)
|
||||
{
|
||||
memset(mask, 0xFF, DP_BITMAP_SIZE);
|
||||
if (DP_COUNT % 8)
|
||||
{
|
||||
mask[DP_BITMAP_SIZE - 1] &= (1 << (DP_COUNT % 8)) - 1;
|
||||
}
|
||||
/* DP IDs are sparse; list only the fields that can be reported/read. */
|
||||
mask[0] = 0xFB; /* 0, 1, 3, 4, 5, 6, 7 */
|
||||
mask[1] = 0x05; /* 8, 10 */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
@@ -582,14 +550,25 @@ static void jbHandleControl(uint8_t *frame, uint16_t len)
|
||||
uint8_t *values = &frame[6 + DP_BITMAP_SIZE];
|
||||
uint16_t valLen = len - 7 - DP_BITMAP_SIZE;
|
||||
|
||||
/* Only writable fields are legal in a control frame. */
|
||||
if ((bitmap[0] & (uint8_t)~0xD7U) ||
|
||||
(bitmap[1] & (uint8_t)~0x04U))
|
||||
{
|
||||
jbSendAck(CMD_CONTROL_ACK, sn, JB_NOT_SUPPORT);
|
||||
return;
|
||||
}
|
||||
|
||||
jb_event_info_t info;
|
||||
jb_data_point_t ctrlData;
|
||||
memset(&ctrlData, 0, sizeof(ctrlData));
|
||||
jbUnpackDps(bitmap, values, valLen, &ctrlData, &info);
|
||||
|
||||
/* 分发给用户层;用户在回调中更新 gDevData */
|
||||
jbEventProcess(&info, &ctrlData);
|
||||
jbSendAck(CMD_CONTROL_ACK, sn, JB_OK);
|
||||
/* 分发给用户层;用户在回调中更新 gDevData。 */
|
||||
{
|
||||
int8_t result = jbEventProcess(&info, &ctrlData);
|
||||
jbSendAck(CMD_CONTROL_ACK, sn,
|
||||
(result < 0) ? JB_PARAM_ERR : (uint8_t)result);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 0x05 APP 读取请求 ── */
|
||||
@@ -632,10 +611,12 @@ static void jbHandleOtaNotify(uint8_t *frame, uint16_t len)
|
||||
uint32_t fwLength = jbReadU32BE(&frame[9]);
|
||||
uint32_t fwCrc32 = jbReadU32BE(&frame[13]);
|
||||
|
||||
/* 应答: 结果码 + 最大单包长度(2B)(协议原格式,3 字节) */
|
||||
int8_t otaRet = jbOnOtaNotify(fwVersion, fwLength, fwCrc32);
|
||||
jbOnOtaNotify(fwVersion, fwLength, fwCrc32);
|
||||
|
||||
/* 默认应答: 结果码 + 最大单包长度(2B) */
|
||||
/* maxDataLen 由 MCU 自身能力决定,填入 0x31 应答,BLE 据此分片 */
|
||||
uint8_t ack[3];
|
||||
ack[0] = (otaRet == 0) ? JB_OK : (uint8_t)otaRet;
|
||||
ack[0] = JB_OK;
|
||||
ack[1] = (uint8_t)(JB_OTA_MAX_PAYLOAD >> 8);
|
||||
ack[2] = (uint8_t)(JB_OTA_MAX_PAYLOAD & 0xFF);
|
||||
jbSendFrame(CMD_OTA_NOTIFY_ACK, sn, ack, 3);
|
||||
@@ -649,9 +630,8 @@ static void jbHandleOtaData(uint8_t *frame, uint16_t len)
|
||||
uint16_t totalShards = jbReadU16BE(&frame[8]);
|
||||
const uint8_t *pData = &frame[10];
|
||||
uint16_t dataLen = (len > 11) ? (len - 11) : 0;
|
||||
/* 先发本包 0x33 应答,再调用户回调满足「先回复、再重启」 */
|
||||
jbSendAck(CMD_OTA_DATA_ACK, sn, JB_OK);
|
||||
jbOnOtaData(pData, dataLen, shardIdx, totalShards);
|
||||
jbSendAck(CMD_OTA_DATA_ACK, sn, JB_OK);
|
||||
}
|
||||
|
||||
/* ── 0x36 BLE 通知停止 OTA ── */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ================================================================
|
||||
* jb_protocol.h — 见宝 IOT 协议层(自动生成)
|
||||
* 设备: 门夹弹力绳 | DP 数: 12 | Bitmap: 2B
|
||||
* 协议版本: V1.0 | 生成时间: 2026-08-21 18:01:47
|
||||
* 设备: 猫猫捕猎罩 | DP 数: 10 | Bitmap: 2B
|
||||
* 协议版本: V1.0 | 生成时间: 2026-08-18 16:24:47
|
||||
*
|
||||
* 帧格式: [55 AA] [LEN_H LEN_L] [CMD] [SN] [payload...] [CKSUM]
|
||||
* LEN = CMD(1) + SN(1) + payload + CKSUM(1) = payload + 3
|
||||
@@ -26,7 +26,7 @@
|
||||
#define MAX_PACKAGE_LEN 128
|
||||
#define JB_OTA_MAX_PAYLOAD 96 /* OTA 单包 Payload 上限(Byte); 须 <= MAX_PACKAGE_LEN - 11(0x32 帧开销) */
|
||||
|
||||
#define DP_COUNT 12
|
||||
#define DP_COUNT 10
|
||||
#define DP_BITMAP_SIZE 2
|
||||
|
||||
#define SEND_MAX_TIME 2500 /* ACK 重传超时(ms);BLE Indication往返常需数百ms~1s,过短会误重传导致双应答 */
|
||||
@@ -34,14 +34,13 @@
|
||||
#define REPORT_DEBOUNCE 2000 /* 变化上报防抖时间(ms) */
|
||||
#define REPORT_PERIOD 600000 /* 定时全量上报周期(ms) */
|
||||
|
||||
/* Device identity: 取自 Excel 设备信息表的 ProdtCode / productCode。
|
||||
/* Device identity: change these values for the target product.
|
||||
* JB_CATEGORY_CODE: 必须 4 字符 ASCII,供 BLE 广播 prodcode 使用
|
||||
* JB_PRODUCT_CODE : 必须 6 字符 ASCII,供 BLE 广播 pcode 使用
|
||||
* 模板未填时回退占位 XXXX / XXXXXX(仍需用户修改)。
|
||||
*/
|
||||
#define JB_PROTOCOL_VERSION 0x0100 /* V1.0 */
|
||||
#define JB_CATEGORY_CODE "CBC2"
|
||||
#define JB_PRODUCT_CODE "PICBC2"
|
||||
#define JB_CATEGORY_CODE "CBC4"
|
||||
#define JB_PRODUCT_CODE "PICBC4"
|
||||
#define JB_DEVICE_SN 0x00
|
||||
#define JB_DEVICE_CAPABILITY 0ULL
|
||||
|
||||
@@ -93,14 +92,14 @@ typedef enum
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* BLE 工作状态位 (0x14)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define WS_CONNECTING (1 << 0) /* 配网中 */
|
||||
#define WS_PAIRING (1 << 0) /* 配网中 */
|
||||
#define WS_CONFIGURED (1 << 1) /* 已配网 */
|
||||
#define WS_RECONNECTING (1 << 2) /* 断线重连中 */
|
||||
#define WS_CLOUD_CONNECTED (1 << 3) /* 已连接到云 */
|
||||
#define WS_APP_ONLINE (1 << 4) /* App 在线 */
|
||||
#define WS_APP_DIRECT (1 << 5) /* App 直连 */
|
||||
#define WS_APP_ONLINE (1 << 4) /* APP 在线 */
|
||||
#define WS_APP_DIRECT (1 << 5) /* APP 直连 */
|
||||
#define WS_OTA_UPGRADING (1 << 6) /* 模组 OTA 升级中 */
|
||||
#define WS_RESERVED (1 << 7) /* 保留 */
|
||||
#define WS_RESERVED (1 << 7) /* Reserved */
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 设备能力位图 (0x02 Capability Bitmap, 8B 大端)
|
||||
@@ -120,31 +119,27 @@ typedef enum
|
||||
{
|
||||
DP_ID_POWER = 0, /* 开关 (bool, RW) */
|
||||
DP_ID_PLAY_MODE = 1, /* 自动模式 (enum, RW) */
|
||||
DP_ID_HAND_MODE = 2, /* 手动模式 (raw, WO) */
|
||||
DP_ID_CALIBRATION = 3, /* 绳索校准 (bool, WO) */
|
||||
DP_ID_NUMBERTURNS = 4, /* 绳索圈数 (uint8, WO) */
|
||||
DP_ID_ALM_LOW_BATTERY = 5, /* 低电量报警 (bool, RO) */
|
||||
DP_ID_BATTERY = 6, /* 电量上报 (uint8, RO) */
|
||||
DP_ID_CHARGE = 7, /* 充电状态 (enum, RO) */
|
||||
DP_ID_NO_DISTURB_ENABLE = 8, /* 勿扰模式开关 (raw, RW) */
|
||||
DP_ID_RESERVA_DP1 = 9, /* 预留DP1 (uint32, RW) */
|
||||
DP_ID_RESERVA_DP2 = 10, /* 预留DP2 (uint32, RW) */
|
||||
DP_ID_RESERVA_DP3 = 11, /* 预留DP3 (uint32, RW) */
|
||||
DP_ID_HAND_MODE = 2, /* 手动模式 (enum, WO) */
|
||||
DP_ID_ALM_LOW_BATTERY = 3, /* 低电量报警 (bool, RO) */
|
||||
DP_ID_BEEP_TRIG = 4, /* 声音开关 (bool, RW) */
|
||||
DP_ID_BATTERY = 5, /* 电量上报 (uint8, RO) */
|
||||
DP_ID_SHAKE_WAKE = 6, /* 震动触发开机 (bool, RW) */
|
||||
DP_ID_NO_DISTURB_ENABLE = 7, /* 勿扰模式开关 (raw, RW) */
|
||||
DP_ID_CHARGE = 8, /* 充电状态 (enum, RO) */
|
||||
DP_ID_RESERVA_DP3 = 10, /* 预留DP3 (uint32, RW) */
|
||||
DP_ID_MAX
|
||||
} dp_id_t;
|
||||
|
||||
/* DP 数据长度(字节) */
|
||||
#define DP_LEN_POWER 1
|
||||
#define DP_LEN_PLAY_MODE 1
|
||||
#define DP_LEN_HAND_MODE 2
|
||||
#define DP_LEN_CALIBRATION 1
|
||||
#define DP_LEN_NUMBERTURNS 1
|
||||
#define DP_LEN_HAND_MODE 1
|
||||
#define DP_LEN_ALM_LOW_BATTERY 1
|
||||
#define DP_LEN_BEEP_TRIG 1
|
||||
#define DP_LEN_BATTERY 1
|
||||
#define DP_LEN_CHARGE 1
|
||||
#define DP_LEN_SHAKE_WAKE 1
|
||||
#define DP_LEN_NO_DISTURB_ENABLE 13
|
||||
#define DP_LEN_RESERVA_DP1 4
|
||||
#define DP_LEN_RESERVA_DP2 4
|
||||
#define DP_LEN_CHARGE 1
|
||||
#define DP_LEN_RESERVA_DP3 4
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
@@ -154,17 +149,25 @@ typedef enum
|
||||
typedef enum
|
||||
{
|
||||
PLAY_MODE_0 = 0, /* 停止复位 */
|
||||
PLAY_MODE_1 = 1, /* 弹力伪装 */
|
||||
PLAY_MODE_2 = 2, /* 飞鸟俯冲 */
|
||||
PLAY_MODE_3 = 3, /* 云阶小陀螺 */
|
||||
PLAY_MODE_4 = 4, /* 起伏攻防战 */
|
||||
PLAY_MODE_5 = 5, /* 微风拂动 */
|
||||
PLAY_MODE_6 = 6, /* 闪电快闪 */
|
||||
PLAY_MODE_1 = 1, /* 上部慢速往复 */
|
||||
PLAY_MODE_2 = 2, /* 上部快速往复 */
|
||||
PLAY_MODE_3 = 3, /* 上部间歇、下部均匀摇摆 */
|
||||
PLAY_MODE_4 = 4, /* 上部慢探、下部随机摇摆 */
|
||||
PLAY_MODE_5 = 5, /* 上下电机随机组合 */
|
||||
} PLAY_MODE_t;
|
||||
|
||||
/* 手动模式 */
|
||||
typedef enum
|
||||
{
|
||||
HAND_MODE_0 = 0, /* 停止 */
|
||||
HAND_MODE_1 = 1, /* 上部电机探出 */
|
||||
HAND_MODE_2 = 2, /* 下部电机探出 */
|
||||
} HAND_MODE_t;
|
||||
|
||||
/* 充电状态 */
|
||||
typedef enum
|
||||
{
|
||||
CHARGE_0 = 0, /* 不支持充电 */
|
||||
CHARGE_1 = 1, /* 充电中 */
|
||||
CHARGE_2 = 2, /* 未充电 */
|
||||
} CHARGE_t;
|
||||
@@ -178,16 +181,14 @@ typedef struct
|
||||
{
|
||||
uint8_t power; /* DP0 开关 */
|
||||
uint8_t play_mode; /* DP1 自动模式 */
|
||||
uint8_t hand_mode[2]; /* DP2 手动模式 */
|
||||
uint8_t calibration; /* DP3 绳索校准 */
|
||||
uint8_t numberturns; /* DP4 绳索圈数 */
|
||||
uint8_t alm_low_battery;/* DP5 低电量报警 */
|
||||
uint8_t battery; /* DP6 电量上报 */
|
||||
uint8_t charge; /* DP7 充电状态 */
|
||||
uint8_t no_disturb_enable[13];/* DP8 勿扰模式开关 */
|
||||
uint32_t reserva_dp1; /* DP9 预留DP1 */
|
||||
uint32_t reserva_dp2; /* DP10 预留DP2 */
|
||||
uint32_t reserva_dp3; /* DP11 预留DP3 */
|
||||
uint8_t hand_mode; /* DP2 手动模式 */
|
||||
uint8_t alm_low_battery;/* DP3 低电量报警 */
|
||||
uint8_t beep_trig; /* DP4 声音开关 */
|
||||
uint8_t battery; /* DP5 电量上报 */
|
||||
uint8_t shake_wake; /* DP6 震动触发开机 */
|
||||
uint8_t no_disturb_enable[13];/* DP7 勿扰模式开关 */
|
||||
uint8_t charge; /* DP8 充电状态 */
|
||||
uint32_t reserva_dp3; /* DP10 预留DP3 */
|
||||
} jb_data_point_t;
|
||||
|
||||
/* 控制事件信息 */
|
||||
@@ -260,7 +261,7 @@ extern void ble_send_data(uint8_t *data, uint16_t len);
|
||||
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *data);
|
||||
|
||||
/* BLE → MCU 事件回调 */
|
||||
int8_t jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32); /* 0x30, 返回0=接受,非0=拒绝码 */
|
||||
void jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32); /* 0x30 */
|
||||
void jbOnOtaData(const uint8_t *data, uint16_t len,
|
||||
uint16_t shardIdx, uint16_t totalShards); /* 0x32 */
|
||||
void jbOnOtaStop(void); /* 0x36 / 0x38 停止 */
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/******************************************************************************
|
||||
* @file usr_jb_main.c
|
||||
* @brief CBC2 业务组合根:只做“先后调用谁、谁通知谁”,不含具体业务算法
|
||||
* @brief CBC1 猫猫捕猎罩业务组合根:只做调用编排,不含具体业务算法
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.27
|
||||
* @version V2.2.0
|
||||
* @version V2.3.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.1.0, 2026.08.24, cyWu, 收口未接线模块;DP2 仅第 1 字节点动 1s
|
||||
@@ -16,11 +16,13 @@
|
||||
* - V2.1.0, 2026.08.26, cyWu, 电机运转期间长期占用 1ms 硬件定时器中断,
|
||||
* 配网连接后与 BLE 协议栈抢时序触发看门狗复位;
|
||||
* 换向死区判定改用阈值检查而非精确计时,
|
||||
* 取消专门的 1ms 高频档,全系统统一 20ms 节拍,
|
||||
* 取消专门的 1ms 高频档,全系统统一 10ms 节拍,
|
||||
* 只保留"能不能休眠"这一个维度
|
||||
* - V2.2.0, 2026.08.27, cyWu, app_led_init() 提前到 app_boot_wait_key() 之前,
|
||||
* 开机 3s/5s/拒绝开机低电闪直接用灯效事件表,
|
||||
* 不用再各自摸裸 GPIO 手搓一份闪烁定时
|
||||
* - V2.3.0, 2026.08.31, cyWu, DP0=0 待机允许系统轻睡;震动检测并入现有
|
||||
* 10ms 业务节拍,避免重复定时器唤醒
|
||||
*
|
||||
* @note 协议生成文件 jb_product.c 不要填业务。APP 下行写 gDevData。
|
||||
* usr_timer_loop 由 BLE 回调,勿再注册第二份。
|
||||
@@ -45,6 +47,7 @@
|
||||
#include "usr_le_product.h"
|
||||
#include "usr_le_api.h"
|
||||
#include "bsp_hw.h"
|
||||
#include "board_pin.h"
|
||||
#include "app_power.h"
|
||||
#include "app_pair.h"
|
||||
#include "app_boot.h"
|
||||
@@ -69,7 +72,7 @@ const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN] = {
|
||||
JB_PRODUCT_CODE[3], JB_PRODUCT_CODE[4], JB_PRODUCT_CODE[5]
|
||||
};
|
||||
|
||||
#define USR_TIMER_IDLE_MS 20 /**< 全局统一节拍:按键消抖、换向死区判定都按它对齐 */
|
||||
#define USR_TIMER_IDLE_MS BOARD_SHAKE_POLL_MS /**< 按键、换向和震动检测共用同一节拍 */
|
||||
|
||||
/**
|
||||
* @brief 业务节拍档位
|
||||
@@ -78,7 +81,7 @@ const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN] = {
|
||||
* 单独开过 1ms 硬件定时器中断,配网连接后跟 BLE 协议栈自己的收发/ACK
|
||||
* 处理抢时序,长时间叠加导致看门狗复位。
|
||||
* 换向死区的判定是"距上次换向是否已过 switch_ms"这种阈值检查,不是
|
||||
* 精确计时:用 20ms 节拍去查,查到时实际已经过了不少于 20ms,天然
|
||||
* 精确计时:用 10ms 节拍去查,查到时实际已经过了不少于 10ms,天然
|
||||
* 满足"至少 1ms"的死区要求,只会让保护更充分,不会失效。所以死区
|
||||
* 不需要专门的 1ms 精度,可以和其它业务共用同一个节拍。
|
||||
*/
|
||||
@@ -108,11 +111,20 @@ static void usr_on_power_evt(AppPowerEvt_t evt)
|
||||
{
|
||||
switch (evt) {
|
||||
case APP_POWER_EVT_USB_IN:
|
||||
app_function_stop_all();
|
||||
log_info("usb in, motor stop\n");
|
||||
if (!app_led_is_ota()) {
|
||||
app_pair_stop();
|
||||
usr_var.usr_goto_pair = 0;
|
||||
app_function_soft_off();
|
||||
log_info("usb in, charge standby\n");
|
||||
} else {
|
||||
app_function_stop_all();
|
||||
log_info("usb in, keep ota\n");
|
||||
}
|
||||
break;
|
||||
case APP_POWER_EVT_USB_OUT:
|
||||
app_function_soft_off();
|
||||
if (!app_led_is_ota()) {
|
||||
app_function_soft_off();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -161,6 +173,8 @@ static void usr_poweroff_finalize_hook(void)
|
||||
*/
|
||||
static UsrTickMode_t usr_timer_pick_mode(void)
|
||||
{
|
||||
/* DP0=0 仍保留低优先级业务节拍、BLE 和外部唤醒源,不需要禁止轻睡。
|
||||
* 定时器到期会正常唤醒 CPU,按键/震动/USB 检测功能不会因此停掉。 */
|
||||
if (bsp_key_is_pressed() || app_power_poweroff_pending()
|
||||
|| app_mode_get_active() || bsp_motor_is_busy()
|
||||
|| app_function_is_hand_busy()) {
|
||||
@@ -194,7 +208,7 @@ static void usr_timer_rearm(UsrTickMode_t mode)
|
||||
* 只有这一种,没有"任务线程 + 禁睡"的组合可选。
|
||||
* 代价是这里执行的每一行代码都会占用中断时间,跟 BLE 协议栈自己的
|
||||
* 收发中断抢时序;这次的看门狗复位就是因为这条路径长期以 1kHz
|
||||
* 触发。现在虽然降到了 20ms 一拍,但仍要守住底线:
|
||||
* 触发。现在虽然降到了 10ms 一拍,但仍要守住底线:
|
||||
* - 禁止阻塞等待(信号量/循环等 ACK 之类)
|
||||
* - 禁止耗时操作(flash 擦写、大块 memcpy、长循环)
|
||||
* 按键短按/长按走到的 usr_on_key_short/long、jbSendReqWithAck
|
||||
@@ -291,6 +305,7 @@ void usr_jb_init(void)
|
||||
user_key_init();
|
||||
user_key_lock_if_held();
|
||||
app_function_power_on_silent();
|
||||
usr_on_power_evt(app_power_tick_1ms(0));
|
||||
|
||||
usr_timer_rearm(USR_TICK_IDLE);
|
||||
log_info("timer idle %ums, sleep ok\n", (unsigned)USR_TIMER_IDLE_MS);
|
||||
|
||||
@@ -66,6 +66,7 @@ void usr_ble_adv_init(void);
|
||||
void usr_ble_adv_updata(void);
|
||||
|
||||
static int le_timer_handle = 0;
|
||||
static u8 ota_process_cnt;
|
||||
|
||||
/**
|
||||
* @brief BLE 是否已连接
|
||||
@@ -301,29 +302,27 @@ void usr_ble_adv_updata_OTA(void)
|
||||
|
||||
void usr_ota_process(void)
|
||||
{
|
||||
static u8 cnt = 0;
|
||||
|
||||
if (cnt == 0)
|
||||
if (ota_process_cnt == 0)
|
||||
{
|
||||
ble_multi_trans_disconnect();
|
||||
}
|
||||
|
||||
if (!usr_ble_is_connected())
|
||||
{
|
||||
cnt++;
|
||||
if (cnt == 2)
|
||||
ota_process_cnt++;
|
||||
if (ota_process_cnt == 2)
|
||||
{
|
||||
usr_ble_adv_updata_OTA();
|
||||
}
|
||||
if (cnt >= 60)
|
||||
if (ota_process_cnt >= 60)
|
||||
{
|
||||
cnt = 0;
|
||||
ota_process_cnt = 0;
|
||||
cpu_reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cnt = 1;
|
||||
ota_process_cnt = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,8 +332,23 @@ void usr_ota_process(void)
|
||||
void usr_app_ota_init(void)
|
||||
{
|
||||
usr_var.usr_goto_updata = 1;
|
||||
ota_process_cnt = 0;
|
||||
if (usr_var.ota_timer == 0)
|
||||
{
|
||||
usr_var.ota_timer = sys_timer_add(NULL, usr_ota_process, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void usr_app_ota_exit(void)
|
||||
{
|
||||
if (usr_var.ota_timer) {
|
||||
sys_timer_del((u16)usr_var.ota_timer);
|
||||
usr_var.ota_timer = 0;
|
||||
}
|
||||
ota_process_cnt = 0;
|
||||
if (usr_get_ota_status() == 0) {
|
||||
usr_ota_exit();
|
||||
}
|
||||
usr_var.usr_goto_updata = 0;
|
||||
usr_ble_disconnect_and_stop_adv();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,12 @@ uint8_t usr_get_pair_flag(void);
|
||||
*/
|
||||
void usr_app_ota_init(void);
|
||||
|
||||
/** Stop the application OTA timer/advertising state without resetting. */
|
||||
void usr_app_ota_exit(void);
|
||||
|
||||
/** Select normal or DP0 standby BLE connection parameters. */
|
||||
void usr_ble_set_standby(uint8_t standby);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+25
-13
@@ -1,6 +1,6 @@
|
||||
/******************************************************************************
|
||||
* @file board_pin.h
|
||||
* @brief CBC2 门夹弹力绳板级引脚、电平与时序(换板只改本文件)
|
||||
* @brief CBC1 猫猫捕猎罩板级引脚、电平与时序(换板只改本文件)
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.27
|
||||
* @version V1.5.0
|
||||
@@ -27,29 +27,28 @@
|
||||
#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 */
|
||||
#define BOARD_MOTOR_ENABLE 1 /**< 1=双电机 U13 + U18 */
|
||||
#define BOARD_SHAKE_ENABLE 1 /**< 1=震动输入 PA3 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* GPIO */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_MOTOR_INA_PIN IO_PORTA_06 /**< 电机 INA */
|
||||
#define BOARD_MOTOR_INB_PIN IO_PORTC_02 /**< 电机 INB */
|
||||
#define BOARD_LEFT_FORWARD_PIN IO_PORTA_06 /**< U13-INA,左轮前进 */
|
||||
#define BOARD_LEFT_BACKWARD_PIN IO_PORTC_02 /**< U13-INB,左轮后退 */
|
||||
#define BOARD_RIGHT_BACKWARD_PIN IO_PORTC_03 /**< U18-INA,右轮后退 */
|
||||
#define BOARD_RIGHT_FORWARD_PIN IO_PORTC_04 /**< U18-INB,右轮前进 */
|
||||
#define BOARD_SHAKE_PIN IO_PORTA_03 /**< 震动开机,常闭开关断开时高脉冲有效 */
|
||||
#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_SHAKE_ACTIVE_LEVEL 1 /**< 常闭震动开关:断开时高脉冲有效 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 电量 / 充电 */
|
||||
@@ -105,8 +104,8 @@
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_MOTOR_REVERSE_MS 1 /**< 换向前先松开,防 H 桥直通 */
|
||||
#define BOARD_HAND_PULSE_MS 1000 /**< 预留;手动 1/2 一直转到写 03 停止 */
|
||||
#define BOARD_MOTOR_PWM_HZ 20000 /**< MCPWM 频率(Hz),20k 避开可听啸叫 */
|
||||
#define BOARD_HAND_PULSE_MS 5000 /**< 手动右转/左转单次最长 5s */
|
||||
#define BOARD_MOTOR_PWM_HZ 200 /**< 猫猫捕猎罩原项目验证过的 MCPWM 频率(Hz) */
|
||||
#define BOARD_MOTOR_PWM_DUTY 8000 /**< 占空比 0~10000=0%~100%,80% */
|
||||
#define BOARD_MOTOR_PWM_IDLE_CLOSE 1 /**< 1=空闲/轻睡关 MCPWM;0=保持开着,方便对比待机电流 */
|
||||
|
||||
@@ -114,6 +113,19 @@
|
||||
/* 自动玩法 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_AUTO_MODE_MAX_MS (10u * 60u * 1000u) /**< 自动模式 1~6 单次最长运行时长,到点自动回待机 */
|
||||
#define BOARD_AUTO_MODE_MAX_MS (10u * 60u * 1000u) /**< 自动模式 1~5 单次最长运行时长,到点自动回待机 */
|
||||
|
||||
/*============================================================================*/
|
||||
/* 震动开机 */
|
||||
/*============================================================================*/
|
||||
|
||||
#define BOARD_SHAKE_REARM_MS 50u /**< 高电平稳定后重新武装 */
|
||||
#define BOARD_SHAKE_TRIGGER_EDGE_COUNT 1u /**< 验证阶段:单个高脉冲立即触发 */
|
||||
#define BOARD_SHAKE_TRIGGER_WINDOW_MS 250u /**< 保留窗口参数,便于恢复多脉冲策略 */
|
||||
#define BOARD_SHAKE_POLL_MS 10u /**< 与业务调度器共用的采样节拍 */
|
||||
|
||||
/* PG8/CHG_ON is the only fitted USB-related signal that changes while the
|
||||
* product is in softoff; VPWR is disconnected on boards with R18 not fitted. */
|
||||
#define BOARD_CHG_WAKEUP_INDEX 3u
|
||||
|
||||
#endif /* __BOARD_PIN_H__ */
|
||||
|
||||
+180
-182
@@ -1,6 +1,6 @@
|
||||
/******************************************************************************
|
||||
* @file bsp_hw.c
|
||||
* @brief CBC2 板级 GPIO / ADC:电机 H 桥、LED、按键、充电检测
|
||||
* @brief CBC1 板级 GPIO / ADC:单电机 H 桥、震动、LED、按键、充电检测
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.24
|
||||
* @version V1.5.0
|
||||
@@ -28,21 +28,16 @@
|
||||
#include "debug.h"
|
||||
|
||||
static uint8_t s_initialized;
|
||||
|
||||
/** 电机状态,替代原来散落的 7 个 static */
|
||||
typedef struct {
|
||||
BspMotorDir_t dir; /**< 当前实际方向 */
|
||||
BspMotorDir_t pending; /**< 换向死区结束后要切到的方向 */
|
||||
uint16_t duty; /**< 当前实际占空比 */
|
||||
uint16_t pending_duty; /**< 换向死区结束后要用的占空比 */
|
||||
uint16_t switch_ms; /**< 换向死区剩余时长,0=空闲/不在换向中 */
|
||||
u32 switch_t0; /**< 换向死区起始时间戳 */
|
||||
uint8_t pwm_clk_on; /**< 1=MCPWM 时钟已打开 */
|
||||
} BspMotorCtx_t;
|
||||
|
||||
static BspMotorCtx_t s_motor;
|
||||
|
||||
extern u32 timer_get_ms(void);
|
||||
static uint8_t s_motor_active;
|
||||
static uint8_t s_pwm_clk_on;
|
||||
static uint8_t s_motor_switch_pending;
|
||||
static BspMotorDir_t s_left_direction;
|
||||
static BspMotorDir_t s_right_direction;
|
||||
static BspMotorDir_t s_pending_left_direction;
|
||||
static BspMotorDir_t s_pending_right_direction;
|
||||
static uint16_t s_pending_left_duty;
|
||||
static uint16_t s_pending_right_duty;
|
||||
static uint32_t s_motor_switch_started_ms;
|
||||
|
||||
#if (BOARD_LED_ENABLE || BOARD_MOTOR_ENABLE)
|
||||
/**
|
||||
@@ -60,7 +55,7 @@ static void bsp_pin_output(u32 pin, int value)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (BOARD_KEY_ENABLE || BOARD_CHG_ENABLE)
|
||||
#if (BOARD_KEY_ENABLE || BOARD_CHG_ENABLE || BOARD_SHAKE_ENABLE)
|
||||
/**
|
||||
* @brief 配置 GPIO 为数字输入(可内部上拉)
|
||||
* @param pin GPIO 编号
|
||||
@@ -78,40 +73,50 @@ static void bsp_pin_input(u32 pin, int pull_up)
|
||||
#endif
|
||||
|
||||
#if BOARD_MOTOR_ENABLE
|
||||
static void bsp_motor_pwm_channel_init(pwm_ch_num_type channel, u8 pin)
|
||||
{
|
||||
struct pwm_platform_data pwm;
|
||||
|
||||
memset(&pwm, 0, sizeof(pwm));
|
||||
pwm.pwm_aligned_mode = pwm_edge_aligned;
|
||||
pwm.pwm_ch_num = channel;
|
||||
pwm.frequency = BOARD_MOTOR_PWM_HZ;
|
||||
pwm.duty = 0;
|
||||
pwm.h_pin = pin;
|
||||
pwm.l_pin = (u8) - 1;
|
||||
pwm.complementary_en = 0;
|
||||
mcpwm_init(&pwm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化 INA/INB 两路 MCPWM,初始占空比 0(停转)
|
||||
* @return 无
|
||||
*/
|
||||
static void bsp_motor_pwm_init(void)
|
||||
{
|
||||
struct pwm_platform_data pwm;
|
||||
const u32 pins[] = {
|
||||
BOARD_LEFT_FORWARD_PIN, BOARD_LEFT_BACKWARD_PIN,
|
||||
BOARD_RIGHT_BACKWARD_PIN, BOARD_RIGHT_FORWARD_PIN
|
||||
};
|
||||
uint8_t i;
|
||||
|
||||
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);
|
||||
for (i = 0; i < ARRAY_SIZE(pins); i++) {
|
||||
gpio_set_pull_up(pins[i], 0);
|
||||
gpio_set_pull_down(pins[i], 0);
|
||||
gpio_set_die(pins[i], 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);
|
||||
bsp_motor_pwm_channel_init(pwm_ch0, BOARD_LEFT_FORWARD_PIN);
|
||||
bsp_motor_pwm_channel_init(pwm_ch1, BOARD_LEFT_BACKWARD_PIN);
|
||||
bsp_motor_pwm_channel_init(pwm_ch2, BOARD_RIGHT_BACKWARD_PIN);
|
||||
bsp_motor_pwm_channel_init(pwm_ch3, BOARD_RIGHT_FORWARD_PIN);
|
||||
|
||||
mcpwm_set_duty(pwm_ch0, 0);
|
||||
mcpwm_set_duty(pwm_ch1, 0);
|
||||
s_motor.pwm_clk_on = 1;
|
||||
log_info("motor pwm %uHz duty=%u/10000\n",
|
||||
(unsigned)BOARD_MOTOR_PWM_HZ, (unsigned)BOARD_MOTOR_PWM_DUTY);
|
||||
mcpwm_set_duty(pwm_ch2, 0);
|
||||
mcpwm_set_duty(pwm_ch3, 0);
|
||||
s_pwm_clk_on = 1;
|
||||
log_info("dual motor pwm %uHz\n", (unsigned)BOARD_MOTOR_PWM_HZ);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,16 +125,22 @@ static void bsp_motor_pwm_init(void)
|
||||
*/
|
||||
static void bsp_motor_pwm_clock_on(void)
|
||||
{
|
||||
if (s_motor.pwm_clk_on) {
|
||||
if (s_pwm_clk_on) {
|
||||
return;
|
||||
}
|
||||
mcpwm_open(pwm_ch0);
|
||||
mcpwm_open(pwm_ch1);
|
||||
gpio_och_sel_output_signal(BOARD_MOTOR_INA_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
|
||||
gpio_och_sel_output_signal(BOARD_MOTOR_INB_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H);
|
||||
gpio_set_direction(BOARD_MOTOR_INA_PIN, 0);
|
||||
gpio_set_direction(BOARD_MOTOR_INB_PIN, 0);
|
||||
s_motor.pwm_clk_on = 1;
|
||||
mcpwm_open(pwm_ch2);
|
||||
mcpwm_open(pwm_ch3);
|
||||
gpio_och_sel_output_signal(BOARD_LEFT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
|
||||
gpio_och_sel_output_signal(BOARD_LEFT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H);
|
||||
gpio_och_sel_output_signal(BOARD_RIGHT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM2_H);
|
||||
gpio_och_sel_output_signal(BOARD_RIGHT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM3_H);
|
||||
gpio_set_direction(BOARD_LEFT_FORWARD_PIN, 0);
|
||||
gpio_set_direction(BOARD_LEFT_BACKWARD_PIN, 0);
|
||||
gpio_set_direction(BOARD_RIGHT_BACKWARD_PIN, 0);
|
||||
gpio_set_direction(BOARD_RIGHT_FORWARD_PIN, 0);
|
||||
s_pwm_clk_on = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,61 +151,63 @@ static void bsp_motor_pwm_clock_off(void)
|
||||
{
|
||||
mcpwm_set_duty(pwm_ch0, 0);
|
||||
mcpwm_set_duty(pwm_ch1, 0);
|
||||
if (s_motor.pwm_clk_on) {
|
||||
mcpwm_set_duty(pwm_ch2, 0);
|
||||
mcpwm_set_duty(pwm_ch3, 0);
|
||||
if (s_pwm_clk_on) {
|
||||
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);
|
||||
mcpwm_close(pwm_ch2);
|
||||
mcpwm_close(pwm_ch3);
|
||||
gpio_och_disable_output_signal(BOARD_LEFT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM0_H);
|
||||
gpio_och_disable_output_signal(BOARD_LEFT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM1_H);
|
||||
gpio_och_disable_output_signal(BOARD_RIGHT_BACKWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM2_H);
|
||||
gpio_och_disable_output_signal(BOARD_RIGHT_FORWARD_PIN, OUTPUT_CH_SIGNAL_MC_PWM3_H);
|
||||
JL_MCPWM->MCPWM_CON0 = 0;
|
||||
s_motor.pwm_clk_on = 0;
|
||||
s_pwm_clk_on = 0;
|
||||
}
|
||||
gpio_direction_output(BOARD_MOTOR_INA_PIN, 0);
|
||||
gpio_direction_output(BOARD_MOTOR_INB_PIN, 0);
|
||||
gpio_direction_output(BOARD_LEFT_FORWARD_PIN, 0);
|
||||
gpio_direction_output(BOARD_LEFT_BACKWARD_PIN, 0);
|
||||
gpio_direction_output(BOARD_RIGHT_BACKWARD_PIN, 0);
|
||||
gpio_direction_output(BOARD_RIGHT_FORWARD_PIN, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
static void bsp_drive_gpio(BspMotorDir_t left_dir, uint16_t left_duty,
|
||||
BspMotorDir_t right_dir, uint16_t right_duty)
|
||||
{
|
||||
u16 ina_duty = 0;
|
||||
u16 inb_duty = 0;
|
||||
|
||||
if (duty > 10000) {
|
||||
duty = 10000;
|
||||
if (left_duty > 10000) {
|
||||
left_duty = 10000;
|
||||
}
|
||||
if (right_duty > 10000) {
|
||||
right_duty = 10000;
|
||||
}
|
||||
if (left_dir == BSP_MOTOR_STOP) {
|
||||
left_duty = 0;
|
||||
}
|
||||
if (right_dir == BSP_MOTOR_STOP) {
|
||||
right_duty = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
/* Crazy Car's proven driver clears all four channels at every action
|
||||
* boundary, then applies the two wheel outputs as one atomic command. */
|
||||
mcpwm_set_duty(pwm_ch0, 0);
|
||||
mcpwm_set_duty(pwm_ch1, 0);
|
||||
mcpwm_set_duty(pwm_ch2, 0);
|
||||
mcpwm_set_duty(pwm_ch3, 0);
|
||||
|
||||
if ((ina_duty == 0) && (inb_duty == 0)) {
|
||||
/* 换向死区(BOARD_MOTOR_REVERSE_MS)、刹车瞬间也会短暂占空比为 0,
|
||||
* 但马上(1~30ms 内)还要再驱动,不能当成“真空闲”去关时钟:
|
||||
* 频繁 close/open MCPWM 外设比单纯调占空比激进得多,玩法换向越快
|
||||
* 关得越勤,有卡死风险。这里只清占空比(两脚拉低,电气效果一样),
|
||||
* 真正的空闲关时钟统一交给 bsp_hw_sleep_pwm_off()——它在系统真的
|
||||
* 要进轻睡前才调用,并且会先查 bsp_motor_is_busy()。 */
|
||||
mcpwm_set_duty(pwm_ch0, 0);
|
||||
mcpwm_set_duty(pwm_ch1, 0);
|
||||
return;
|
||||
s_motor_active = (uint8_t)(left_duty || right_duty);
|
||||
if (s_motor_active) {
|
||||
bsp_motor_pwm_clock_on();
|
||||
}
|
||||
if (left_dir == BSP_MOTOR_FORWARD) {
|
||||
mcpwm_set_duty(pwm_ch0, left_duty);
|
||||
} else if (left_dir == BSP_MOTOR_BACKWARD) {
|
||||
mcpwm_set_duty(pwm_ch1, left_duty);
|
||||
}
|
||||
if (right_dir == BSP_MOTOR_FORWARD) {
|
||||
mcpwm_set_duty(pwm_ch3, right_duty);
|
||||
} else if (right_dir == BSP_MOTOR_BACKWARD) {
|
||||
mcpwm_set_duty(pwm_ch2, right_duty);
|
||||
}
|
||||
bsp_motor_pwm_clock_on();
|
||||
mcpwm_set_duty(pwm_ch0, ina_duty);
|
||||
mcpwm_set_duty(pwm_ch1, inb_duty);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -231,20 +244,19 @@ BspHw_Ret_t bsp_hw_init(void)
|
||||
|
||||
#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_motor.pending_duty = 0;
|
||||
s_motor.switch_ms = 0;
|
||||
s_motor_active = 0;
|
||||
s_motor_switch_pending = 0;
|
||||
s_left_direction = BSP_MOTOR_STOP;
|
||||
s_right_direction = BSP_MOTOR_STOP;
|
||||
#if BOARD_MOTOR_PWM_IDLE_CLOSE
|
||||
bsp_motor_pwm_clock_off();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
s_initialized = 1;
|
||||
log_info("hw init led=%d key=%d chg=%d motor=%d\n",
|
||||
log_info("hw init led=%d key=%d chg=%d motor=%d shake=%d\n",
|
||||
BOARD_LED_ENABLE, BOARD_KEY_ENABLE, BOARD_CHG_ENABLE,
|
||||
BOARD_MOTOR_ENABLE);
|
||||
BOARD_MOTOR_ENABLE, BOARD_SHAKE_ENABLE);
|
||||
return BSP_HW_OK;
|
||||
}
|
||||
|
||||
@@ -295,101 +307,93 @@ void bsp_led_all_off(void)
|
||||
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~10000;STOP 时按 0 处理
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_motor_set_pwm(BspMotorDir_t dir, uint16_t duty)
|
||||
void bsp_drive_set(BspMotorDir_t left_dir, uint16_t left_duty,
|
||||
BspMotorDir_t right_dir, uint16_t right_duty)
|
||||
{
|
||||
#if !BOARD_MOTOR_ENABLE
|
||||
(void)dir;
|
||||
(void)duty;
|
||||
return;
|
||||
(void)left_dir;
|
||||
(void)left_duty;
|
||||
(void)right_dir;
|
||||
(void)right_duty;
|
||||
#else
|
||||
if (duty > 10000) {
|
||||
duty = 10000;
|
||||
uint8_t left_reversing;
|
||||
uint8_t right_reversing;
|
||||
|
||||
if (left_dir < BSP_MOTOR_BACKWARD || left_dir > BSP_MOTOR_FORWARD ||
|
||||
right_dir < BSP_MOTOR_BACKWARD || right_dir > BSP_MOTOR_FORWARD) {
|
||||
return;
|
||||
}
|
||||
if (dir == BSP_MOTOR_STOP) {
|
||||
duty = 0;
|
||||
if (left_dir == BSP_MOTOR_STOP) {
|
||||
left_duty = 0;
|
||||
}
|
||||
if (right_dir == BSP_MOTOR_STOP) {
|
||||
right_duty = 0;
|
||||
}
|
||||
|
||||
/* 同向只改占空比:直接改 PWM,不走换向死区 */
|
||||
if (dir == s_motor.dir && s_motor.switch_ms == 0) {
|
||||
if (duty == s_motor.duty) {
|
||||
return;
|
||||
}
|
||||
s_motor.duty = duty;
|
||||
s_motor.pending = dir;
|
||||
s_motor.pending_duty = duty;
|
||||
bsp_motor_gpio(dir, duty);
|
||||
if (left_duty == 0 && right_duty == 0) {
|
||||
s_motor_switch_pending = 0;
|
||||
/* Keep the last energized directions so an immediate opposite command
|
||||
* still observes the H-bridge dead time. */
|
||||
bsp_drive_gpio(BSP_MOTOR_STOP, 0, BSP_MOTOR_STOP, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 停转:直接切断驱动,让电机自由滑行到停,不做反接刹车——反接刹车电流比
|
||||
* 正常驱动大(电池要同时顶住反向驱动电流和电机反电动势),实测会把供电轨
|
||||
* 拉低触发 LVD 复位。哪怕正在换向死区里,也直接取消死区改成停。 */
|
||||
if (dir == BSP_MOTOR_STOP) {
|
||||
s_motor.switch_ms = 0;
|
||||
s_motor.dir = BSP_MOTOR_STOP;
|
||||
s_motor.pending = BSP_MOTOR_STOP;
|
||||
s_motor.duty = 0;
|
||||
s_motor.pending_duty = 0;
|
||||
bsp_motor_gpio(BSP_MOTOR_STOP, 0);
|
||||
if (s_motor_switch_pending) {
|
||||
s_pending_left_direction = left_dir;
|
||||
s_pending_left_duty = left_duty;
|
||||
s_pending_right_direction = right_dir;
|
||||
s_pending_right_duty = right_duty;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 换向:先松开再输出,避免 H 桥直通 */
|
||||
if (s_motor.dir != BSP_MOTOR_STOP && dir != s_motor.dir) {
|
||||
bsp_motor_gpio(BSP_MOTOR_STOP, 0);
|
||||
s_motor.pending = dir;
|
||||
s_motor.pending_duty = duty;
|
||||
s_motor.switch_ms = BOARD_MOTOR_REVERSE_MS;
|
||||
s_motor.switch_t0 = timer_get_ms();
|
||||
s_motor.dir = BSP_MOTOR_STOP;
|
||||
s_motor.duty = 0;
|
||||
left_reversing = (uint8_t)(s_left_direction != BSP_MOTOR_STOP &&
|
||||
left_dir != BSP_MOTOR_STOP &&
|
||||
left_dir != s_left_direction);
|
||||
right_reversing = (uint8_t)(s_right_direction != BSP_MOTOR_STOP &&
|
||||
right_dir != BSP_MOTOR_STOP &&
|
||||
right_dir != s_right_direction);
|
||||
if (left_reversing || right_reversing) {
|
||||
bsp_drive_gpio(BSP_MOTOR_STOP, 0, BSP_MOTOR_STOP, 0);
|
||||
s_pending_left_direction = left_dir;
|
||||
s_pending_left_duty = left_duty;
|
||||
s_pending_right_direction = right_dir;
|
||||
s_pending_right_duty = right_duty;
|
||||
s_motor_switch_started_ms = timer_get_ms();
|
||||
s_motor_switch_pending = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
s_motor.switch_ms = 0;
|
||||
s_motor.dir = dir;
|
||||
s_motor.pending = dir;
|
||||
s_motor.duty = duty;
|
||||
s_motor.pending_duty = duty;
|
||||
bsp_motor_gpio(dir, duty);
|
||||
bsp_drive_gpio(left_dir, left_duty, right_dir, right_duty);
|
||||
s_left_direction = left_duty ? left_dir : BSP_MOTOR_STOP;
|
||||
s_right_direction = right_duty ? right_dir : BSP_MOTOR_STOP;
|
||||
#endif
|
||||
}
|
||||
|
||||
void bsp_drive_stop(void)
|
||||
{
|
||||
bsp_drive_set(BSP_MOTOR_STOP, 0, BSP_MOTOR_STOP, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推进换向死区计时,按真实毫秒计时;死区结束后切到目标方向/占空比
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_motor_switch_tick(void)
|
||||
{
|
||||
#if !BOARD_MOTOR_ENABLE
|
||||
return;
|
||||
#else
|
||||
if (s_motor.switch_ms == 0) {
|
||||
#if BOARD_MOTOR_ENABLE
|
||||
if (!s_motor_switch_pending ||
|
||||
(uint32_t)(timer_get_ms() - s_motor_switch_started_ms) <
|
||||
BOARD_MOTOR_REVERSE_MS) {
|
||||
return;
|
||||
}
|
||||
if ((timer_get_ms() - s_motor.switch_t0) < s_motor.switch_ms) {
|
||||
return;
|
||||
}
|
||||
s_motor.switch_ms = 0;
|
||||
s_motor.dir = s_motor.pending;
|
||||
s_motor.duty = s_motor.pending_duty;
|
||||
bsp_motor_gpio(s_motor.dir, s_motor.duty);
|
||||
|
||||
s_motor_switch_pending = 0;
|
||||
bsp_drive_gpio(s_pending_left_direction, s_pending_left_duty,
|
||||
s_pending_right_direction, s_pending_right_duty);
|
||||
s_left_direction = s_pending_left_duty ?
|
||||
s_pending_left_direction : BSP_MOTOR_STOP;
|
||||
s_right_direction = s_pending_right_duty ?
|
||||
s_pending_right_direction : BSP_MOTOR_STOP;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -450,10 +454,15 @@ uint8_t bsp_vpwr_is_online(void)
|
||||
if (mv >= BOARD_VPWR_ONLINE_MV) {
|
||||
return 1;
|
||||
}
|
||||
/* LDO5V_DET is valid immediately after a charge wake, before the ADC has
|
||||
* produced a stable sample. Unlike LVCMP it also drops promptly on pull. */
|
||||
if (get_ldo5v_online_hw()) {
|
||||
return 1;
|
||||
}
|
||||
if (mv <= BOARD_VPWR_OFF_MV) {
|
||||
return 0;
|
||||
}
|
||||
if (get_ldo5v_online_hw() || get_lvcmp_det()) {
|
||||
if (get_lvcmp_det()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
@@ -488,16 +497,7 @@ uint8_t bsp_motor_is_busy(void)
|
||||
#if !BOARD_MOTOR_ENABLE
|
||||
return 0;
|
||||
#else
|
||||
if (s_motor.switch_ms) {
|
||||
return 1;
|
||||
}
|
||||
if (s_motor.dir != BSP_MOTOR_STOP) {
|
||||
return 1;
|
||||
}
|
||||
if (s_motor.pending != BSP_MOTOR_STOP) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return (uint8_t)(s_motor_active || s_motor_switch_pending);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -507,13 +507,11 @@ uint8_t bsp_motor_is_busy(void)
|
||||
*/
|
||||
void bsp_hw_enter_sleep_io(void)
|
||||
{
|
||||
bsp_motor_set(BSP_MOTOR_STOP);
|
||||
bsp_drive_stop();
|
||||
#if BOARD_MOTOR_ENABLE
|
||||
s_motor.switch_ms = 0;
|
||||
s_motor.dir = BSP_MOTOR_STOP;
|
||||
s_motor.pending = BSP_MOTOR_STOP;
|
||||
s_motor.duty = 0;
|
||||
s_motor_active = 0;
|
||||
bsp_motor_pwm_clock_off();
|
||||
#endif
|
||||
bsp_led_all_off();
|
||||
|
||||
}
|
||||
|
||||
+11
-15
@@ -1,6 +1,6 @@
|
||||
/******************************************************************************
|
||||
* @file bsp_hw.h
|
||||
* @brief CBC2 板级硬件:电机 / LED / 按键 / 充电 / 电量
|
||||
* @brief CBC1 板级硬件:单电机 / 震动 / LED / 按键 / 充电 / 电量
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.25
|
||||
* @version V1.6.0
|
||||
@@ -27,9 +27,9 @@ typedef enum {
|
||||
} BspHw_Ret_t;
|
||||
|
||||
typedef enum {
|
||||
BSP_MOTOR_STOP = 0, /**< 滑行停(INA/INB 都低) */
|
||||
BSP_MOTOR_IN, /**< 收绳(顺时针,见 BOARD_MOTOR_IN_INA_HIGH) */
|
||||
BSP_MOTOR_OUT /**< 放绳 */
|
||||
BSP_MOTOR_BACKWARD = -1,
|
||||
BSP_MOTOR_STOP = 0,
|
||||
BSP_MOTOR_FORWARD = 1
|
||||
} BspMotorDir_t;
|
||||
|
||||
/**
|
||||
@@ -70,24 +70,20 @@ void bsp_led_all_off(void);
|
||||
* @param dir 收绳 / 放绳 / 停转
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_motor_set(BspMotorDir_t dir);
|
||||
void bsp_drive_set(BspMotorDir_t left_dir, uint16_t left_duty,
|
||||
BspMotorDir_t right_dir, uint16_t right_duty);
|
||||
|
||||
/** 停止左右轮,四路 PWM 全部清零。 */
|
||||
void bsp_drive_stop(void);
|
||||
|
||||
/**
|
||||
* @brief 设置电机方向与 PWM 占空比
|
||||
* @param dir 收绳 / 放绳 / 停转
|
||||
* @param duty 占空比 0~10000(0%~100%);STOP 时忽略
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_motor_set_pwm(BspMotorDir_t dir, uint16_t duty);
|
||||
|
||||
/**
|
||||
* @brief 推进换向死区计时,按真实毫秒计时;死区结束后切到目标方向/占空比
|
||||
* @brief 兼容旧调用;猫猫捕猎罩每个动作更新左轮对应的两路 PWM
|
||||
* @return 无
|
||||
*/
|
||||
void bsp_motor_switch_tick(void);
|
||||
|
||||
/**
|
||||
* @brief 电机是否还在转或处于换向死区
|
||||
* @brief 电机是否还在转
|
||||
* @return 1=忙碌,0=已停且 PWM 已关
|
||||
*/
|
||||
uint8_t bsp_motor_is_busy(void);
|
||||
|
||||
Reference in New Issue
Block a user