Compare commits

..
14 Commits
Author SHA1 Message Date
zengzhiheng 3d1ca8bda9 Update magic box firmware 2026-09-02 18:20:54 +08:00
zengzhiheng b63a928b92 feat: 完成弹力魔盒初版固件 2026-08-31 20:15:19 +08:00
wuchuyuan 026d6d5da7 1、开机按满 5s 绿灯先切配对快闪提示,松手才清绑定并进配网,继续按到 12s 仍可改道 OTA
2、app_led_init 提前到按键判定前,3s 常亮 / 5s 快闪 / 拒绝开机低电闪统一走灯效事件表,去掉开机阶段手搓 GPIO 闪烁
2026-08-27 20:19:57 +08:00
wuchuyuan 215e1be6bb 1、运行中空电关机改看显示百分比,到 5% 立刻停电机、关绿灯,红灯闪 5 下后再深睡,避免电压先掉、APP 还没到 5% 就关机
2、充电/放电电量缓冲加快,放电突变拦截先关掉,电量曲线满电点收到 4.12V
3、上电串口打印软硬件版本和编译时间
2026-08-27 11:40:57 +08:00
wuchuyuan fdb43011b7 1、低电 3.7V 只闪红灯不再 1 分钟后关机;电量低于约 5% 自动关机,未充电开机先闪 5 下红灯再睡回,只能充电后才能开机
2、自动/手动互相切换都闪 2 下灯;APP 发停止后再发自动模式可正常启动,手动残留停止不再挡住自动
3、自动模式 1~6 单次最长跑 10 分钟后回待机;关机时长按 12 秒进 OTA,OTA 期间红绿交替闪
2026-08-27 10:20:48 +08:00
wuchuyuanandCursor 3d445ae5c5 1、业务调度统一为 20ms 节拍,取消电机运转时的 1ms 高频定时器,避免配网后 BLE 收发与 1kHz 中断抢时序导致看门狗复位
2、调度器只保留可休眠/禁休眠两档,换向死区改由阈值检查判定,不再单独切 1ms 精度

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:16:08 +08:00
wuchuyuan 4ee0f86d13 1、Makefile 与 CodeBlocks 编译单元对齐,补 make pack / BURN_KEY,增加 build.bat 与固件打包脚本
2、下载脚本同时生成无 Key / 带 Key 镜像,USB 默认烧无 Key,build.bat key 烧带 Key
3、配对超时从 60s 改为 120s
4、增加需求文档
2026-08-26 17:06:28 +08:00
wuchuyuan 93c1943992 1、充电时不再叠待机绿灯;意外复位不再跳过 3s 开机按键判定
2、电机停转去掉反接刹车改自由滑行,避免电流冲击触发 LVD 复位;
3、关机直接 power_set_soft_poweroff,跳过耳机 SDK 优雅关机流程,按键关机从十几秒压到瞬间掉电
2026-08-25 20:05:59 +08:00
wuchuyuan 5e5f921a3a 添加音频智能语音功能配置,不添加待机功耗降不下去,现在LED亮着待机功耗有500多uA,LED灭着待机功耗有280uA,关机功耗8uA 2026-08-25 18:50:28 +08:00
wuchuyuan 5252471aca 优化程序架构 2026-08-25 14:59:39 +08:00
wuchuyuan d5ec6ecde0 功耗减低到900uA,还有优化空间,还要陈杰明那边支持一下 2026-08-25 11:16:54 +08:00
wuchuyuan e0f5da6f39 把静态库换成源码方便调试 2026-08-25 10:20:14 +08:00
wuchuyuan 0e6a93c12d 基本功能已完成,还差功耗和架构优化 2026-08-24 18:03:39 +08:00
wuchuyuan 81bd2f724d 生成了代码仓库 2026-08-21 18:41:42 +08:00
9 changed files with 276 additions and 77 deletions
+1
View File
@@ -411,6 +411,7 @@ c_SRC_FILES := \
apps/usr_app/app_pair.c \ apps/usr_app/app_pair.c \
apps/usr_app/app_boot.c \ apps/usr_app/app_boot.c \
apps/usr_app/app_function.c \ apps/usr_app/app_function.c \
apps/usr_app/app_no_disturb.c \
apps/usr_app/app_shake_wake.c \ apps/usr_app/app_shake_wake.c \
apps/usr_le_code/ble_proto.c \ apps/usr_le_code/ble_proto.c \
apps/usr_le_code/bleproto.c \ apps/usr_le_code/bleproto.c \
+2 -13
View File
@@ -19,6 +19,7 @@
#include "app_mode.h" #include "app_mode.h"
#include "app_led.h" #include "app_led.h"
#include "app_shake_wake.h" #include "app_shake_wake.h"
#include "app_no_disturb.h"
#include "app_time_util.h" #include "app_time_util.h"
#include "bsp_hw.h" #include "bsp_hw.h"
#include "board_pin.h" #include "board_pin.h"
@@ -46,7 +47,6 @@ typedef struct {
volatile uint8_t play_write_pending; volatile uint8_t play_write_pending;
volatile uint8_t play_write_mode; volatile uint8_t play_write_mode;
uint8_t shake_wake; uint8_t shake_wake;
uint8_t no_disturb;
} AppFunctionCtx_t; } AppFunctionCtx_t;
static AppFunctionCtx_t s_func; static AppFunctionCtx_t s_func;
@@ -71,7 +71,6 @@ void app_function_init(void)
app_shake_wake_init(function_shake_wake_allowed, app_shake_wake_init(function_shake_wake_allowed,
function_shake_wake_trigger); function_shake_wake_trigger);
s_func.shake_wake = !!gDevData.shake_wake; 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); app_shake_wake_set_enable(s_func.shake_wake);
} }
@@ -105,12 +104,6 @@ void app_function_set_shake_wake(uint8_t enable)
app_shake_wake_set_enable(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 手动点动 * @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动
* @return 无 * @return 无
@@ -120,10 +113,6 @@ void app_function_poll(void)
if (s_func.shake_wake != !!gDevData.shake_wake) { if (s_func.shake_wake != !!gDevData.shake_wake) {
app_function_set_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) { if (s_func.power_write_pending) {
uint8_t enable = s_func.power_write_value; uint8_t enable = s_func.power_write_value;
s_func.power_write_pending = 0; s_func.power_write_pending = 0;
@@ -501,7 +490,7 @@ static uint8_t function_power_on(uint8_t show_hint)
static uint8_t function_shake_wake_allowed(void) static uint8_t function_shake_wake_allowed(void)
{ {
return s_func.shake_wake && return s_func.shake_wake &&
!s_func.no_disturb && !app_no_disturb_is_active() &&
!s_func.app_power && !s_func.app_power &&
!app_power_usb_online() && !app_power_usb_online() &&
!app_power_poweroff_pending() && !app_power_poweroff_pending() &&
-2
View File
@@ -39,8 +39,6 @@ void app_function_dp_hand_write(uint8_t cmd);
/** CBC4 DP6 震动开机开关。 */ /** CBC4 DP6 震动开机开关。 */
void app_function_set_shake_wake(uint8_t enable); void app_function_set_shake_wake(uint8_t enable);
/** 勿扰时段是否正在生效;生效时屏蔽震动开机。 */
void app_function_set_no_disturb(uint8_t enable);
/** /**
* @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动,每个业务 tick 调用一次 * @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动,每个业务 tick 调用一次
+168
View File
@@ -0,0 +1,168 @@
#include "app_no_disturb.h"
#include "usr_rtc.h"
#include "system/includes.h"
#define APP_MINUTES_PER_DAY (24U * 60U)
#define APP_NO_DISTURB_VM_MAGIC 0x444e4431UL
#define APP_NO_DISTURB_VM_VERSION 1U
typedef struct {
uint8_t enabled;
uint8_t weekdays_mask;
uint16_t start_minute;
uint16_t end_minute;
} AppNoDisturbCtx_t;
typedef struct {
uint32_t magic;
uint8_t version;
uint8_t length;
uint16_t checksum;
uint8_t data[APP_NO_DISTURB_DATA_LEN];
} AppNoDisturbVm_t;
static AppNoDisturbCtx_t s_no_disturb;
static uint8_t s_saved_data[APP_NO_DISTURB_DATA_LEN];
static uint8_t s_saved_valid;
static uint16_t app_no_disturb_checksum(const uint8_t *data)
{
uint16_t checksum = 0xa55aU;
uint8_t i;
for (i = 0; i < APP_NO_DISTURB_DATA_LEN; i++) {
checksum = (uint16_t)((checksum << 5) | (checksum >> 11));
checksum ^= data[i];
}
return checksum;
}
static uint8_t app_no_disturb_vm_valid(const AppNoDisturbVm_t *vm)
{
return vm->magic == APP_NO_DISTURB_VM_MAGIC &&
vm->version == APP_NO_DISTURB_VM_VERSION &&
vm->length == APP_NO_DISTURB_DATA_LEN &&
vm->checksum == app_no_disturb_checksum(vm->data);
}
static uint8_t app_no_disturb_weekday_monday0(const struct sys_time *time)
{
static const uint8_t month_offset[12] = {
0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4
};
uint32_t year = time->year;
uint8_t sunday0;
if (time->month < 3U) {
year--;
}
sunday0 = (uint8_t)((year + year / 4U - year / 100U + year / 400U +
month_offset[time->month - 1U] + time->day) % 7U);
return (uint8_t)((sunday0 + 6U) % 7U);
}
static uint8_t app_no_disturb_day_selected(uint8_t weekday)
{
return (uint8_t)((s_no_disturb.weekdays_mask & (1U << weekday)) != 0U);
}
void app_no_disturb_configure(uint8_t enable, uint8_t weekdays_mask,
uint16_t start_minute, uint16_t end_minute)
{
s_no_disturb.enabled = !!enable;
s_no_disturb.weekdays_mask = weekdays_mask & 0x7fU;
s_no_disturb.start_minute = start_minute;
s_no_disturb.end_minute = end_minute;
}
uint8_t app_no_disturb_is_active_at(const struct sys_time *time)
{
uint8_t weekday;
uint16_t now_minute;
if (!time || !s_no_disturb.enabled || !s_no_disturb.weekdays_mask ||
s_no_disturb.start_minute >= APP_MINUTES_PER_DAY ||
s_no_disturb.end_minute >= APP_MINUTES_PER_DAY ||
time->month < 1U || time->month > 12U ||
time->day < 1U || time->day > 31U ||
time->hour > 23U || time->min > 59U) {
return 0;
}
weekday = app_no_disturb_weekday_monday0(time);
now_minute = (uint16_t)(time->hour * 60U + time->min);
if (s_no_disturb.start_minute == s_no_disturb.end_minute) {
return app_no_disturb_day_selected(weekday);
}
if (s_no_disturb.start_minute < s_no_disturb.end_minute) {
return app_no_disturb_day_selected(weekday) &&
now_minute >= s_no_disturb.start_minute &&
now_minute < s_no_disturb.end_minute;
}
if (now_minute >= s_no_disturb.start_minute) {
return app_no_disturb_day_selected(weekday);
}
if (now_minute < s_no_disturb.end_minute) {
return app_no_disturb_day_selected((uint8_t)((weekday + 6U) % 7U));
}
return 0;
}
uint8_t app_no_disturb_is_active(void)
{
struct sys_time time;
if (!usr_rtc_get_time(&time)) {
return 0;
}
return app_no_disturb_is_active_at(&time);
}
uint8_t app_no_disturb_storage_load(uint8_t data[APP_NO_DISTURB_DATA_LEN])
{
AppNoDisturbVm_t vm;
int ret;
if (!data) {
return 0;
}
memset(&vm, 0, sizeof(vm));
ret = syscfg_read(CFG_USER_NO_DISTURB, (uint8_t *)&vm, sizeof(vm));
if (ret != (int)sizeof(vm) || !app_no_disturb_vm_valid(&vm)) {
s_saved_valid = 0;
return 0;
}
memcpy(data, vm.data, APP_NO_DISTURB_DATA_LEN);
memcpy(s_saved_data, vm.data, APP_NO_DISTURB_DATA_LEN);
s_saved_valid = 1;
return 1;
}
uint8_t app_no_disturb_storage_save(const uint8_t data[APP_NO_DISTURB_DATA_LEN])
{
AppNoDisturbVm_t vm;
int ret;
if (!data) {
return 0;
}
if (s_saved_valid &&
!memcmp(s_saved_data, data, APP_NO_DISTURB_DATA_LEN)) {
return 1;
}
memset(&vm, 0, sizeof(vm));
vm.magic = APP_NO_DISTURB_VM_MAGIC;
vm.version = APP_NO_DISTURB_VM_VERSION;
vm.length = APP_NO_DISTURB_DATA_LEN;
memcpy(vm.data, data, APP_NO_DISTURB_DATA_LEN);
vm.checksum = app_no_disturb_checksum(vm.data);
ret = syscfg_write(CFG_USER_NO_DISTURB, (uint8_t *)&vm, sizeof(vm));
if (ret != (int)sizeof(vm)) {
return 0;
}
memcpy(s_saved_data, data, APP_NO_DISTURB_DATA_LEN);
s_saved_valid = 1;
return 1;
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef __APP_NO_DISTURB_H__
#define __APP_NO_DISTURB_H__
#include <stdint.h>
#include "system/sys_time.h"
#define APP_NO_DISTURB_DATA_LEN 13U
/* weekdays_mask bit 0..6 means Monday..Sunday. */
void app_no_disturb_configure(uint8_t enable, uint8_t weekdays_mask,
uint16_t start_minute, uint16_t end_minute);
uint8_t app_no_disturb_is_active(void);
uint8_t app_no_disturb_is_active_at(const struct sys_time *time);
uint8_t app_no_disturb_storage_load(uint8_t data[APP_NO_DISTURB_DATA_LEN]);
uint8_t app_no_disturb_storage_save(const uint8_t data[APP_NO_DISTURB_DATA_LEN]);
#endif /* __APP_NO_DISTURB_H__ */
+24 -62
View File
@@ -14,7 +14,7 @@
#include "jb_product.h" #include "jb_product.h"
#include "app_function.h" #include "app_function.h"
#include "usr_rtc.h" #include "app_no_disturb.h"
/* ════════════════════════════════════════════════════════════════ /* ════════════════════════════════════════════════════════════════
* 全局变量 * 全局变量
@@ -30,70 +30,27 @@ static volatile uint32_t timerMs;
#define JB_DND_START_MIN_OFFSET 10 #define JB_DND_START_MIN_OFFSET 10
#define JB_DND_END_HOUR_OFFSET 11 #define JB_DND_END_HOUR_OFFSET 11
#define JB_DND_END_MIN_OFFSET 12 #define JB_DND_END_MIN_OFFSET 12
static uint8_t jbLastNoDisturbActive;
/* Return weekday with Monday=0, matching DP7 bytes 2..8. */ static void jbNoDisturbApply(uint8_t *cfg)
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; uint8_t i;
uint8_t weekdays = 0;
uint16_t start_minute;
uint16_t end_minute;
cfg[JB_DND_ENABLE_OFFSET] = !!cfg[JB_DND_ENABLE_OFFSET]; cfg[JB_DND_ENABLE_OFFSET] = !!cfg[JB_DND_ENABLE_OFFSET];
for (i = 0; i < 7; i++) { for (i = 0; i < 7; i++) {
cfg[JB_DND_WEEK_OFFSET + i] = !!cfg[JB_DND_WEEK_OFFSET + 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]) { if (cfg[JB_DND_WEEK_OFFSET + i]) {
return 1; weekdays |= (uint8_t)(1U << i);
} }
} }
return 0; start_minute = (uint16_t)(cfg[JB_DND_START_HOUR_OFFSET] * 60U +
} cfg[JB_DND_START_MIN_OFFSET]);
end_minute = (uint16_t)(cfg[JB_DND_END_HOUR_OFFSET] * 60U +
static uint8_t jbNoDisturbActive(void) cfg[JB_DND_END_MIN_OFFSET]);
{ app_no_disturb_configure(cfg[JB_DND_ENABLE_OFFSET], weekdays,
/* Match the verified product behavior: DP7 byte 0 is the global start_minute, end_minute);
* 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);
} }
/* ════════════════════════════════════════════════════════════════ /* ════════════════════════════════════════════════════════════════
@@ -111,10 +68,10 @@ void userInit(void)
gDevData.battery = 0; /* DP5 电量上报 */ gDevData.battery = 0; /* DP5 电量上报 */
gDevData.shake_wake = 0; /* DP6 震动触发开机 0=关 1=开 */ gDevData.shake_wake = 0; /* DP6 震动触发开机 0=关 1=开 */
memset(gDevData.no_disturb_enable, 0, 13); /* DP7 勿扰模式开关 */ memset(gDevData.no_disturb_enable, 0, 13); /* DP7 勿扰模式开关 */
app_no_disturb_storage_load(gDevData.no_disturb_enable);
gDevData.charge = 0; /* CHARGE_0 (不支持充电) */ gDevData.charge = 0; /* CHARGE_0 (不支持充电) */
gDevData.reserva_dp3 = 0; /* DP10 预留DP3 */ gDevData.reserva_dp3 = 0; /* DP10 预留DP3 */
jbLastNoDisturbActive = 0xff; jbNoDisturbApply(gDevData.no_disturb_enable);
jbNoDisturbUpdate();
} }
/* ════════════════════════════════════════════════════════════════ /* ════════════════════════════════════════════════════════════════
@@ -123,8 +80,6 @@ void userInit(void)
* ════════════════════════════════════════════════════════════════ */ * ════════════════════════════════════════════════════════════════ */
void userHandle(void) void userHandle(void)
{ {
jbNoDisturbUpdate();
/* ── 可上报 DP (RO / RW): 用户数据采集 ── */ /* ── 可上报 DP (RO / RW): 用户数据采集 ── */
// gDevData.power = read_sensor(); /* DP0 开关 */ // gDevData.power = read_sensor(); /* DP0 开关 */
// gDevData.play_mode = read_sensor(); /* DP1 自动模式 */ // gDevData.play_mode = read_sensor(); /* DP1 自动模式 */
@@ -193,12 +148,19 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
break; break;
/* ── DP7 勿扰模式开关 (raw) ── */ /* ── DP7 勿扰模式开关 (raw) ── */
case DP_ID_NO_DISTURB_ENABLE: case DP_ID_NO_DISTURB_ENABLE: {
uint8_t previous[JB_DND_LEN];
memcpy(previous, gDevData.no_disturb_enable, JB_DND_LEN);
memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable, memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable,
JB_DND_LEN); JB_DND_LEN);
jbNoDisturbNormalize(gDevData.no_disturb_enable); jbNoDisturbApply(gDevData.no_disturb_enable);
jbNoDisturbUpdate(); if (memcmp(previous, gDevData.no_disturb_enable, JB_DND_LEN) &&
!app_no_disturb_storage_save(gDevData.no_disturb_enable)) {
printf("dnd vm write failed\n");
}
break; break;
}
/* ── DP10 预留DP3 (uint32) ── */ /* ── DP10 预留DP3 (uint32) ── */
case DP_ID_RESERVA_DP3: case DP_ID_RESERVA_DP3:
@@ -0,0 +1 @@
3DBA0ACD41A68B534365C721F7B23619914B4106E1024B5592A0A49D1DD417152E4DCF18
+62
View File
@@ -0,0 +1,62 @@
# SOC 代码生成模板 — 分支索引
本仓库按 **「厂商-型号」** 组织分支,每个分支是一套完整的 SOC 工程模板。
远程仓库:[soc-codegen-template](https://git.jianbao-tech.com/pets-device/soc-codegen-template)
---
## 分支列表
| 分支 | 厂商 | 型号 | 平台 | 默认能力 | 状态 |
|------|------|------|------|----------|------|
| [`JieLi-JL7016`](https://git.jianbao-tech.com/pets-device/soc-codegen-template/src/branch/JieLi-JL7016) | 杰理 JieLi | JL7016 / AC7016C | BR28 | 耳机 SDK + BLE + jb_protocol + OTA | ✅ 可用 |
> 新模板上线后在此表追加一行即可。
---
## 如何获取某 SOC 模板
### 见宝工具箱 / 自动化脚本
```bash
git clone https://git.jianbao-tech.com/pets-device/soc-codegen-template.git
cd soc-codegen-template
git checkout JieLi-JL7016
```
### 仅下载指定分支(浅克隆)
```bash
git clone --branch JieLi-JL7016 --single-branch \
https://git.jianbao-tech.com/pets-device/soc-codegen-template.git jl7016-template
```
---
## 分支命名规范
```
<厂商>-<型号>
```
示例:`JieLi-JL7016``JieLi-AC701N`
- 默认包含耳机应用与 jb_protocol,分支名**不加**多余后缀
- 同一芯片多种工程结构差异过大时,再考虑加后缀:`JieLi-JL7016-dongle`
---
## 新增 SOC 模板(维护者)
1.`main` 拉新分支:`git checkout -b <厂商>-<型号> main`
2. 合入该 SOC 完整工程(apps + cpu + include_lib + tools
3. 更新本文件分支表 + `main/README.md` 快速索引
4. 推送分支并在 Gitea 设为受保护分支(可选)
---
## 各分支 README
每个 SOC 分支根目录有独立的 `README.md`(构建、打包、协议对接说明)。
+1
View File
@@ -172,6 +172,7 @@ u8 *syscfg_ptr_read(u16 item_id, u16 *len);
#define CFG_USER_PLAN_INFO1 25+1 #define CFG_USER_PLAN_INFO1 25+1
#define CFG_USER_BATTERY_LEVEL 26+1 #define CFG_USER_BATTERY_LEVEL 26+1
#define CFG_USER_NO_DISTURB 28
#define CFG_USER_DEFINE_END 49 #define CFG_USER_DEFINE_END 49