1、将固件改造为 BLE 串口模组:新增 usr_mcu_bridge,移除旧 jb_proto

2、通过 UART 55AA 对接外部 MCU,完成身份/透传/配对/心跳;桥接忙时注册 LP target 拦住休眠,空闲可进 SLEEP,兼顾串口可靠与低功耗。
This commit is contained in:
2026-08-10 16:03:55 +08:00
parent 75195c7290
commit 4bfe10d728
30 changed files with 2078 additions and 1681 deletions
+10 -13
View File
@@ -174,8 +174,7 @@
<Add directory="apps/usr_le_code" />
<Add directory="apps/usr_le_code/thirdpart/mjson" />
<Add directory="apps/usr_le_code/thirdpart" />
<Add directory="apps/usr_jb_proto/Protocol" />
<Add directory="apps/usr_jb_proto/Utils" />
<Add directory="apps/usr_mcu_bridge" />
</Compiler>
<Linker>
<Add option="-flto" />
@@ -758,25 +757,23 @@
<Unit filename="apps/earphone/version.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_jb_proto/Protocol/jb_product.c">
<Unit filename="apps/usr_mcu_bridge/mcu_bridge.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_jb_proto/Protocol/jb_product.h" />
<Unit filename="apps/usr_jb_proto/Protocol/jb_protocol.c">
<Unit filename="apps/usr_mcu_bridge/mcu_bridge.h" />
<Unit filename="apps/usr_mcu_bridge/mcu_cmd.h" />
<Unit filename="apps/usr_mcu_bridge/mcu_frame.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_jb_proto/Protocol/jb_protocol.h" />
<Unit filename="apps/usr_jb_proto/Utils/jb_common.c">
<Unit filename="apps/usr_mcu_bridge/mcu_frame.h" />
<Unit filename="apps/usr_mcu_bridge/mcu_uart.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_jb_proto/Utils/jb_common.h" />
<Unit filename="apps/usr_jb_proto/Utils/jb_ringbuffer.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_jb_proto/Utils/jb_ringbuffer.h" />
<Unit filename="apps/usr_jb_proto/usr_jb_main.c">
<Unit filename="apps/usr_mcu_bridge/mcu_uart.h" />
<Unit filename="apps/usr_mcu_bridge/mcu_util.c">
<Option compilerVar="CC" />
</Unit>
<Unit filename="apps/usr_mcu_bridge/mcu_util.h" />
<Unit filename="apps/usr_le_code/ble_proto.c">
<Option compilerVar="CC" />
</Unit>
+5 -7
View File
@@ -248,8 +248,7 @@ INCLUDES := \
-Iapps/usr_le_code \
-Iapps/usr_le_code/thirdpart \
-Iapps/usr_le_code/thirdpart/mjson \
-Iapps/usr_jb_proto/Protocol \
-Iapps/usr_jb_proto/Utils \
-Iapps/usr_mcu_bridge \
-I$(SYS_INC_DIR) \
@@ -614,11 +613,10 @@ c_SRC_FILES := \
cpu/br28/tws_audio.c \
cpu/br28/uart_dev.c \
cpu/br28/umidigi_chargestore.c \
apps/usr_jb_proto/Protocol/jb_product.c \
apps/usr_jb_proto/Protocol/jb_protocol.c \
apps/usr_jb_proto/Utils/jb_common.c \
apps/usr_jb_proto/Utils/jb_ringbuffer.c \
apps/usr_jb_proto/usr_jb_main.c \
apps/usr_mcu_bridge/mcu_bridge.c \
apps/usr_mcu_bridge/mcu_frame.c \
apps/usr_mcu_bridge/mcu_uart.c \
apps/usr_mcu_bridge/mcu_util.c \
apps/usr_le_code/ble_proto.c \
apps/usr_le_code/bleproto.c \
apps/usr_le_code/bleproto_packer.c \
+6 -8
View File
@@ -16,6 +16,7 @@
#include "bt_profile_cfg.h"
#include "dev_manager/dev_manager.h"
#include "update_loader_download.h"
#include "mcu_bridge.h"
#ifndef CONFIG_MEDIA_NEW_ENABLE
#ifndef CONFIG_MEDIA_DEVELOP_ENABLE
@@ -254,7 +255,6 @@ const struct rtc_dev_platform_data rtc_init_data=
};
#endif // 0
extern void bt_osc_offset_ext_updata(s32 offset);
extern void usr_jb_init();
void app_main()
{
int update = 0;
@@ -292,7 +292,7 @@ void app_main()
}
app_var_init();
usr_jb_init();
mcu_bridge_init();
#if TCFG_MC_BIAS_AUTO_ADJUST
mc_trim_init(update);
@@ -325,13 +325,11 @@ void app_main()
}
/*
* 上电强制进入配对模式:
* 1) usr_goto_pair=1:后续 usr_ble_adv_init() 走绑定广播(encode4bind),不走重连广播
* 2) usr_clear_pair_info_noreset():清 VM 中三元组,保证 paired_flag=0
* 注意:须在 BLE 协议栈调用 usr_ble_adv_init() 之前设置;此处不清复位,避免上电死循环复位
* BLE+MCU上电强制配网。
* - 未配网:等 MCU 0x10 后再绑定广播
* - 已配网:身份就绪后由 usr_ble_adv_init() 发回连广播
*/
usr_var.usr_goto_pair = 1;
usr_clear_pair_info_noreset();
usr_var.usr_goto_pair = 0;
ui_manage_init();
ui_update_status(STATUS_POWERON);
@@ -23,7 +23,7 @@
//*********************************************************************************//
#define TCFG_UART0_ENABLE ENABLE_THIS_MOUDLE //串口打印模块使能
#define TCFG_UART0_RX_PORT NO_CONFIG_PORT //串口接收脚配置(用于打印可以选择NO_CONFIG_PORT
#define TCFG_UART0_TX_PORT IO_PORTC_05 //串口发送脚配置
#define TCFG_UART0_TX_PORT IO_PORTC_08 //串口发送脚配置
#define TCFG_UART0_BAUDRATE 1000000 //串口波特率配置
//*********************************************************************************//
@@ -733,6 +733,7 @@ DAC硬件上的连接方式,可选的配置:
//*********************************************************************************//
#define TCFG_LOWPOWER_POWER_SEL PWR_LDO15//PWR_LDO15 //电源模式设置,可选DCDC和LDO
#define TCFG_LOWPOWER_BTOSC_DISABLE 0 //低功耗模式下BTOSC是否保持
/* MCU 串口方案:配合 mcu_bridge LP target,忙时保持唤醒、空闲可进低功耗 */
#define TCFG_LOWPOWER_LOWPOWER_SEL SLEEP_EN//DEEP_SLEEP_EN//SLEEP_EN//DEEP_SLEEP_EN //低功耗使能,蓝牙&&系统空闲可进入低功耗
#define TCFG_LOWPOWER_VDDIOM_LEVEL VDDIOM_VOL_28V //vddiom等级
#define TCFG_LOWPOWER_OSC_TYPE OSC_TYPE_LRC //低功耗晶振类型,btosc/lrc
@@ -6,9 +6,21 @@ typedef struct _USR_VAR {
u8 usr_plan_cnt ;
u8 usr_intterupt_reinit_falg ;
u8 usr_goto_updata ;
/**
* 是否处于“主动配网”流程(RAM 运行态,掉电丢失)
* 0:正常态。未配网则不广播;已配网则发回连广播
* 1MCU 下发 0x10 / 调用 usr_goto_pair_mode() 后置位,强制发绑定广播
* 配网成功(AUTHSETUP 写对)或配网超时后应清 0
*/
u8 usr_goto_pair ;
u8 usr_can_pair ;
u8 usr_ota_succ_flag ;
/**
* 广播数据是否需要刷新到协议栈(一次性触发位)
* 1usr_ble_adv_updata() 会把 muti_adv_data 写入栈并 enable 广播,然后清 0
* 0:本周期不更新广播数据
* 注意:置 1 不等于“一直在广播”,只是请求“应用一次新的广播包”
*/
u8 usr_adv_flag ;
u8 usr_low_power_flag ;
u8 usr_allow_to_sleep_cnt;
@@ -97,7 +97,7 @@ const char log_tag_const_e_APP_POWER AT(.LOG_TAG_CONST) = TRUE;
const char log_tag_const_v_APP AT(.LOG_TAG_CONST) = FALSE;
const char log_tag_const_i_APP AT(.LOG_TAG_CONST) = TRUE;
const char log_tag_const_d_APP AT(.LOG_TAG_CONST) = FALSE;
const char log_tag_const_d_APP AT(.LOG_TAG_CONST) = TRUE; /* MCU_BR 等 APP 标签的 log_debug 开关 */
const char log_tag_const_w_APP AT(.LOG_TAG_CONST) = TRUE;
const char log_tag_const_e_APP AT(.LOG_TAG_CONST) = TRUE;
@@ -1,249 +0,0 @@
/* ================================================================
* jb_product.c — 用户层实现(请填补空白处)
* 设备: 侠客猫激光 | 生成时间: 2026-07-31 17:08:51
*
* ══ 开发流程 ══
* 1. 实现硬件抽象: jbGetTimerCount(定时器由 SOC SDK 提供)
* 发送由 SOC SDK 的 ble_send_data 承担,协议层已直接调用,无需 uartWrite
* 2. userInit() 设置 DP 默认值与外设初始化
* 3. userHandle() 用户数据采集
* 4. jbEventProcess() 处理 APP 下发的控制命令
* 5. 主动上报 / OTA 的网关应答在收到 REPORTCMD_V2 后调用 jbReportAckCheck 清除等待
* 6. main.c / 调用方需周期性调用 jbReportAckCheck(cmd, cnt, int0)
* ================================================================
*/
#include "jb_product.h"
/* ════════════════════════════════════════════════════════════════
* 全局变量
* ════════════════════════════════════════════════════════════════ */
jb_data_point_t gDevData;
static volatile uint32_t timerMs;
/* ════════════════════════════════════════════════════════════════
* userInit — DP 默认值
* ════════════════════════════════════════════════════════════════ */
void userInit(void)
{
memset(&gDevData, 0, sizeof(jb_data_point_t));
gDevData.power = 0; /* DP0 开关 0=关 1=开 */
gDevData.play_mode = 0; /* PLAY_MODE_0 (关) */
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.reserva_dp1 = 0; /* DP8 预留DP1 */
gDevData.reserva_dp2 = 0; /* DP9 预留DP2 */
gDevData.reserva_dp3 = 0; /* DP10 预留DP3 */
}
/* ════════════════════════════════════════════════════════════════
* userHandle — 用户数据采集
* 协议层自动检测变化并上报(注意:此处是上报,不是下发控制)
* ════════════════════════════════════════════════════════════════ */
void userHandle(void)
{
/* ── 可上报 DP (RO / RW): 用户数据采集 ── */
// gDevData.power = read_sensor(); /* DP0 开关 */
// gDevData.play_mode = read_sensor(); /* DP1 自动模式 */
// 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.reserva_dp1 = read_sensor(); /* DP8 预留DP1 */
// gDevData.reserva_dp2 = read_sensor(); /* DP9 预留DP2 */
// gDevData.reserva_dp3 = read_sensor(); /* DP10 预留DP3 */
}
/* ════════════════════════════════════════════════════════════════
* jbEventProcess — 0x03 APP 控制事件处理(下行 / DP 赋值方向)
*
* 协议层自动解包 0x03 帧后调用本函数。
* info->dp_ids[] = 被控制的 DP ID 列表
* ctrl 已填入下发的值;复制到 gDevData 并执行动作
* ════════════════════════════════════════════════════════════════ */
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
{
if (!info || !ctrl)
{
return -1;
}
for (uint8_t i = 0; i < info->count; i++)
{
switch (info->dp_ids[i])
{
/* ── DP0 开关 (bool) ── */
case DP_ID_POWER:
if (ctrl->power)
{
gDevData.power = 1;
// TODO: 开 — 执行动作
}
else
{
gDevData.power = 0;
// TODO: 关 — 执行动作
}
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: /* mode1 */
// TODO: 处理 mode1
break;
case PLAY_MODE_2: /* mode2 */
// TODO: 处理 mode2
break;
case PLAY_MODE_3: /* mode3 */
// TODO: 处理 mode3
break;
case PLAY_MODE_4: /* mode4 */
// TODO: 处理 mode4
break;
default:
break;
}
break;
/* ── DP2 手动模式 (enum) ── */
case DP_ID_HAND_MODE:
gDevData.hand_mode = ctrl->hand_mode;
switch (ctrl->hand_mode)
{
case HAND_MODE_0: /* 关 */
// TODO: 处理 关
break;
case HAND_MODE_1: /* 顺时针 */
// TODO: 处理 顺时针
break;
case HAND_MODE_2: /* 逆时针 */
// TODO: 处理 逆时针
break;
default:
break;
}
break;
/* ── DP4 声音开关 (bool) ── */
case DP_ID_BEEP_TRIG:
if (ctrl->beep_trig)
{
gDevData.beep_trig = 1;
// TODO: 开 — 执行动作
}
else
{
gDevData.beep_trig = 0;
// TODO: 关 — 执行动作
}
break;
/* ── DP6 震动触发开机 (bool) ── */
case DP_ID_SHAKE_WAKE:
if (ctrl->shake_wake)
{
gDevData.shake_wake = 1;
// TODO: 开 — 执行动作
}
else
{
gDevData.shake_wake = 0;
// TODO: 关 — 执行动作
}
break;
/* ── DP7 勿扰模式开关 (raw) ── */
case DP_ID_NO_DISTURB_ENABLE:
memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable, 13);
// TODO: 根据 gDevData.no_disturb_enable 执行动作 (长度 13 字节)
break;
/* ── DP8 预留DP1 (uint32) ── */
case DP_ID_RESERVA_DP1:
gDevData.reserva_dp1 = ctrl->reserva_dp1;
// TODO: 根据 ctrl->reserva_dp1 执行动作
break;
/* ── DP9 预留DP2 (uint32) ── */
case DP_ID_RESERVA_DP2:
gDevData.reserva_dp2 = ctrl->reserva_dp2;
// TODO: 根据 ctrl->reserva_dp2 执行动作
break;
/* ── DP10 预留DP3 (uint32) ── */
case DP_ID_RESERVA_DP3:
gDevData.reserva_dp3 = ctrl->reserva_dp3;
// TODO: 根据 ctrl->reserva_dp3 执行动作
break;
default:
break;
}
}
return 0;
}
/* 0x30 OTA 升级请求 */
void jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
{
(void)fwVersion; (void)fwLength; (void)fwCrc32;
// TODO: 检查 Flash 空间与版本,决定是否接收升级
// 允许: 直接返回(协议层默认应答 JB_OK + MaxDataLen=128
// 拒绝: 在 jbHandleOtaNotify 中改为 jbSendFrame(CMD_OTA_NOTIFY_ACK, sn, 错误码, 3)
}
/* 0x32 OTA 数据(含分片序号)*/
void jbOnOtaData(const uint8_t *data, uint16_t len,
uint16_t shardIdx, uint16_t totalShards)
{
(void)data; (void)len; (void)shardIdx; (void)totalShards;
// TODO: 写入 Flash,累计 CRC32
// 全部数据接收完成后调用 jbSendOtaResult(JB_OK, mcuVersion)
}
/* 0x36 / 0x38 停止 OTA */
void jbOnOtaStop(void)
{
// TODO: 终止 OTA,释放资源
}
/* 任意 MCU→BLE 请求的 ACK 回调(cmd 标识是哪条请求的应答,result 为结果码)
- SOC 仅保留 OTA 主动请求应答: OTA完成 0x35 / 停止 0x39
- 可在本回调中设置"XX 完成/失败"标志,供主循环判断"确认成功才继续" */
void jbOnAck(uint8_t cmd, uint8_t result)
{
(void)cmd; (void)result;
// TODO: switch(cmd){ case CMD_OTA_COMPLETE_ACK: gOtaDone = (result==JB_OK); break; case CMD_OTA_STOP_MCU_ACK: ... }
}
/* ════════════════════════════════════════════════════════════════
* 硬件抽象(按实际平台实现)
* ════════════════════════════════════════════════════════════════ */
/* ── 定时器 ── */
void jbTimerIrq(void)
{
timerMs++;
} /* 在 1ms 中断中调用本函数 */
uint32_t jbGetTimerCount(void)
{
return timerMs;
}
/* SOC: 发送由 SOC SDK 的 ble_send_data 承担,无需 uartInit / uartWrite
* 定时器由 SOC SDK 提供,仅保留 jbTimerIrq / jbGetTimerCount 接入即可。 */
@@ -1,55 +0,0 @@
/* ================================================================
* jb_product.h — 用户层头文件(自动生成)
* 设备: 侠客猫激光 | 生成时间: 2026-07-31 17:08:51
*
* 声明用户需实现的函数。数据结构定义见 jb_protocol.h
* ================================================================
*/
#ifndef _JB_PRODUCT_H
#define _JB_PRODUCT_H
#include "jb_protocol.h"
/* ════════════════════════════════════════════════════════════════
* 硬件配置(SOC SDK 已提供定时器,无需 UART 波特率配置)
* ════════════════════════════════════════════════════════════════ */
#define TIMER_PERIOD_MS 1
/* ════════════════════════════════════════════════════════════════
* 版本管理
* ════════════════════════════════════════════════════════════════ */
/* MCU software version: Vmajor.minor.patch */
#define JB_MCU_SW_VERSION_MAJOR 1
#define JB_MCU_SW_VERSION_MINOR 0
#define JB_MCU_SW_VERSION_PATCH 0
/* MCU hardware version: Vmajor.minor.patch */
#define JB_MCU_HW_VERSION_MAJOR 1
#define JB_MCU_HW_VERSION_MINOR 0
#define JB_MCU_HW_VERSION_PATCH 0
/* ════════════════════════════════════════════════════════════════
* 用户 API
* ════════════════════════════════════════════════════════════════ */
/* 全局设备数据 — 协议层直接读写该变量 */
extern jb_data_point_t gDevData;
/* ==================== 用户必须实现 ============================== */
void userInit(void); /* DP 默认值与外设初始化 */
void userHandle(void); /* 用户数据采集 */
void jbTimerIrq(void); /* Call from 1ms timer ISR */
/* =========== 协议回调(声明见 jb_protocol.h================== */
/* int8_t jbEventProcess(...); // 0x03 控制事件 */
/* void jbOnOtaNotify(...); // 0x30 OTA 请求 */
/* void jbOnOtaData(...); // 0x32 OTA 数据 */
/* void jbOnOtaStop(); // 0x36/0x38 停止 OTA */
/* void jbOnAck(...); // 任意请求 ACK(结果) */
/* ============= 主动请求(声明见 jb_protocol.h================ */
/* int32_t jbNotifyOtaStop(); // 0x38 通知 BLE 停止 OTA */
/* int32_t jbSendOtaResult(...); // 0x34 OTA 结果 */
#endif /* _JB_PRODUCT_H */
@@ -1,749 +0,0 @@
/* ================================================================
* jb_protocol.c — 见宝 IOT 协议实现(自动生成)
* 设备: 侠客猫激光 | 生成时间: 2026-07-31 17:08:51
* ================================================================
*/
#include "jb_protocol.h"
#include "jb_product.h"
#include "jb_ringbuffer.h"
#include "jb_common.h"
/* ════════════════════════════════════════════════════════════════
* 全局实例
* ════════════════════════════════════════════════════════════════ */
jb_protocol_t jbProtocol;
static jb_ringbuffer_t rb; /* UART 接收缓冲区 */
/* ════════════════════════════════════════════════════════════════
* jbInit — 协议层初始化
* ════════════════════════════════════════════════════════════════ */
void jbInit(void)
{
memset(&jbProtocol, 0, sizeof(jb_protocol_t));
jbRbInit(&rb);
}
/* ════════════════════════════════════════════════════════════════
* jbPutData — UART 中断将数据压入环形缓冲区
* ════════════════════════════════════════════════════════════════ */
int32_t jbPutData(uint8_t *buf, uint32_t len)
{
if (!buf)
{
return -1;
}
for (uint32_t i = 0; i < len; i++)
{
jbRbPush(&rb, buf[i]);
}
return (int32_t)len;
}
/* ════════════════════════════════════════════════════════════════
* jbSendFrame — 组装协议帧并经 ble_send_data 发送(SOC 无串口)
* 返回帧总长度,< 0 表示出错
* ════════════════════════════════════════════════════════════════ */
static int32_t jbSendFrame(uint8_t cmd, uint8_t sn,
const uint8_t *payload, uint16_t pLen)
{
uint8_t buf[MAX_PACKAGE_LEN];
uint16_t lenField = pLen + 3; /* CMD + SN + payload + CKSUM */
uint16_t total = lenField + 4;
if (total > MAX_PACKAGE_LEN)
{
return -1;
}
/* 55 AA | LEN_H LEN_L | CMD | SN | payload | CKSUM */
buf[0] = PKT_HEAD_0;
buf[1] = PKT_HEAD_1;
jbWriteU16BE(&buf[2], lenField);
buf[4] = cmd;
buf[5] = sn;
if (payload && pLen > 0)
{
memcpy(&buf[6], payload, pLen);
}
buf[total - 1] = jbChecksum(buf, total);
ble_send_data(buf, total);
return (int32_t)total;
}
/* 发送单字节结果应答 */
static void jbSendAck(uint8_t cmd, uint8_t sn, uint8_t result)
{
jbSendFrame(cmd, sn, &result, 1);
}
/* ════════════════════════════════════════════════════════════════
* jbGetOnePacket — 从环形缓冲区提取完整帧
* 返回: 0 = 成功, 1 = 需要更多数据, -2 = 校验和错误
* ════════════════════════════════════════════════════════════════ */
static int8_t jbGetOnePacket(uint8_t *out, uint16_t *outLen)
{
static uint8_t state = 0;
static uint16_t expect = 0;
static uint16_t idx = 0;
static uint8_t prev = 0;
while (jbRbAvailable(&rb))
{
uint8_t b = jbRbPop(&rb);
if (state == 0)
{
/* 搜索同步头 55 AA */
if (prev == PKT_HEAD_0 && b == PKT_HEAD_1)
{
out[0] = PKT_HEAD_0;
out[1] = PKT_HEAD_1;
idx = 2;
state = 1;
}
}
else if (state == 1)
{
/* 接收 LEN(2B) + CMD + SN */
out[idx++] = b;
if (idx == 6)
{
expect = 4 + jbReadU16BE(&out[2]);
if (expect > MAX_PACKAGE_LEN || expect < 7)
{
state = 0;
}
else
{
state = 2;
}
}
}
else
{
/* 接收剩余数据 */
out[idx++] = b;
if (idx >= expect)
{
state = 0;
if (out[expect - 1] == jbChecksum(out, expect))
{
*outLen = expect;
return 0;
}
return -2;
}
}
prev = b;
}
return 1;
}
/* ════════════════════════════════════════════════════════════════
* jbPackDps — dataPoint → outBuf[bitmap | values]
* mask 指定要打包的 DPbit 序号 = DP ID
* 返回 outBuf 总字节数(bitmap + values
* 仅打包 RO / RW 方向的 DP
* ════════════════════════════════════════════════════════════════ */
static uint16_t jbPackDps(const jb_data_point_t *dp,
const uint8_t *mask, uint8_t *outBuf)
{
uint8_t *bitmap = outBuf;
uint8_t *out = outBuf + DP_BITMAP_SIZE;
uint16_t valIdx = 0;
memset(bitmap, 0, DP_BITMAP_SIZE);
/* DP0 开关 (bool, 1B, RW) */
if (mask[DP_ID_POWER / 8] & (1 << (DP_ID_POWER % 8)))
{
bitmap[DP_ID_POWER / 8] |= (1 << (DP_ID_POWER % 8));
out[valIdx++] = dp->power ? 1 : 0;
}
/* DP1 自动模式 (enum, 1B, RW) */
if (mask[DP_ID_PLAY_MODE / 8] & (1 << (DP_ID_PLAY_MODE % 8)))
{
bitmap[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
out[valIdx++] = (uint8_t)dp->play_mode;
}
/* 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;
}
/* 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;
}
/* DP6 震动触发开机 (bool, 1B, RW) */
if (mask[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
{
bitmap[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
out[valIdx++] = dp->shake_wake ? 1 : 0;
}
/* 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;
}
/* DP8 预留DP1 (uint32, 4B, RW) */
if (mask[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8)))
{
bitmap[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp1);
valIdx += 4;
}
/* DP9 预留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;
}
/* 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));
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp3);
valIdx += 4;
}
return DP_BITMAP_SIZE + valIdx;
}
/* ════════════════════════════════════════════════════════════════
* jbUnpackDps — bitmap+values → dataPoint + event_info
* 仅解包 WO / RW 方向的 DP
* ════════════════════════════════════════════════════════════════ */
static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
uint16_t valLen, jb_data_point_t *dp,
jb_event_info_t *info)
{
uint16_t valIdx = 0;
if (info)
{
info->count = 0;
}
/* DP0 开关 (bool, 1B, RW) */
if (bitmap[DP_ID_POWER / 8] & (1 << (DP_ID_POWER % 8)))
{
if (valIdx < valLen)
{
dp->power = (values[valIdx++] != 0);
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_POWER;
}
}
/* DP1 自动模式 (enum, 1B, RW) */
if (bitmap[DP_ID_PLAY_MODE / 8] & (1 << (DP_ID_PLAY_MODE % 8)))
{
if (valIdx < valLen)
{
dp->play_mode = values[valIdx++];
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_PLAY_MODE;
}
}
/* DP2 手动模式 (enum, 1B, WO) */
if (bitmap[DP_ID_HAND_MODE / 8] & (1 << (DP_ID_HAND_MODE % 8)))
{
if (valIdx < valLen)
{
dp->hand_mode = values[valIdx++];
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_HAND_MODE;
}
}
/* DP4 声音开关 (bool, 1B, RW) */
if (bitmap[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8)))
{
if (valIdx < valLen)
{
dp->beep_trig = (values[valIdx++] != 0);
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_BEEP_TRIG;
}
}
/* DP6 震动触发开机 (bool, 1B, RW) */
if (bitmap[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
{
if (valIdx < valLen)
{
dp->shake_wake = (values[valIdx++] != 0);
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_SHAKE_WAKE;
}
}
/* DP7 勿扰模式开关 (raw, 13B, RW) */
if (bitmap[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8)))
{
if (valIdx + 13 <= valLen)
{
memcpy(dp->no_disturb_enable, &values[valIdx], 13);
valIdx += 13;
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_NO_DISTURB_ENABLE;
}
}
/* DP8 预留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;
}
}
/* DP9 预留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;
}
}
/* DP10 预留DP3 (uint32, 4B, RW) */
if (bitmap[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
{
if (valIdx + 4 <= valLen)
{
dp->reserva_dp3 = (uint32_t)jbReadU32BE(&values[valIdx]);
valIdx += 4;
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_RESERVA_DP3;
}
}
}
/* ════════════════════════════════════════════════════════════════
* jbBuildDiffMask — 比较上次与当前值,为变化的可上报 DP 置 mask 位
* 返回: 1 = 有 DP 发生变化, 0 = 全部未变化
* ════════════════════════════════════════════════════════════════ */
static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
const jb_data_point_t *cur,
uint8_t *mask)
{
uint8_t hasChange = 0;
memset(mask, 0, DP_BITMAP_SIZE);
/* DP0 开关 */
if (last->power != cur->power)
{
mask[DP_ID_POWER / 8] |= (1 << (DP_ID_POWER % 8));
hasChange = 1;
}
/* DP1 自动模式 */
if (last->play_mode != cur->play_mode)
{
mask[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
hasChange = 1;
}
/* 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;
}
/* 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;
}
/* DP6 震动触发开机 */
if (last->shake_wake != cur->shake_wake)
{
mask[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
hasChange = 1;
}
/* 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;
}
/* DP8 预留DP1 */
if (last->reserva_dp1 != cur->reserva_dp1)
{
mask[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
hasChange = 1;
}
/* DP9 预留DP2 */
if (last->reserva_dp2 != cur->reserva_dp2)
{
mask[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8));
hasChange = 1;
}
/* DP10 预留DP3 */
if (last->reserva_dp3 != cur->reserva_dp3)
{
mask[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
hasChange = 1;
}
return hasChange;
}
/* 置全量掩码(所有可上报 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;
}
}
/* ════════════════════════════════════════════════════════════════
* jbSendReqWithAck — 发送请求帧并登记 ACK 重传
*
* 与 jbSendFrame 不同,本函数把完整帧缓存到 waitAck.buf
* 并启动 ACK 等待。主动上报 / OTA 等网关应答均走 REPORTCMD_V2
* 由 jbReportAckCheck() 清除等待;若 SEND_MAX_TIME 内未收到,
* jbAckRetry() 自动重传,最多 SEND_MAX_NUM 次。
* 返回 ble_send_data 结果(<0 出错)。
* ════════════════════════════════════════════════════════════════ */
static int32_t jbSendReqWithAck(uint8_t cmd, uint8_t sn,
const uint8_t *payload, uint16_t pLen)
{
uint16_t lenField = pLen + 3; /* CMD + SN + payload + CKSUM */
uint16_t total = lenField + 4; /* + 55 AA + LEN(2) */
if (total > MAX_PACKAGE_LEN)
{
return -1;
}
uint8_t buf[MAX_PACKAGE_LEN];
buf[0] = PKT_HEAD_0;
buf[1] = PKT_HEAD_1;
jbWriteU16BE(&buf[2], lenField);
buf[4] = cmd;
buf[5] = sn;
if (payload && pLen > 0)
{
memcpy(&buf[6], payload, pLen);
}
buf[total - 1] = jbChecksum(buf, total);
/* 缓存整帧,供超时重传 */
memcpy(jbProtocol.waitAck.buf, buf, total);
jbProtocol.waitAck.len = total;
jbProtocol.waitAck.active = 1;
jbProtocol.waitAck.sn = sn;
jbProtocol.waitAck.retry = 0;
jbProtocol.waitAck.sendTime = jbGetTimerCount();
ble_send_data(buf, total);
return (int32_t)total;
}
/* ════════════════════════════════════════════════════════════════
* jbAckRetry — ACK 超时重传
* ════════════════════════════════════════════════════════════════ */
static void jbAckRetry(void)
{
if (!jbProtocol.waitAck.active)
{
return;
}
if (jbProtocol.waitAck.retry >= SEND_MAX_NUM)
{
memset(&jbProtocol.waitAck, 0, sizeof(jb_wait_ack_t));
return;
}
if (jbGetTimerCount() - jbProtocol.waitAck.sendTime > SEND_MAX_TIME)
{
ble_send_data(jbProtocol.waitAck.buf, jbProtocol.waitAck.len);
jbProtocol.waitAck.retry++;
jbProtocol.waitAck.sendTime = jbGetTimerCount();
}
}
/* ════════════════════════════════════════════════════════════════
* jbReportAckCheck — 网关应答确认(主动上报 / OTA 共用)
*
* 网关通过 REPORTCMD_V2 回传(非 55AA)。成功条件:
* cmd == 0
* gateway_int_cnt == 1
* gateway_int0 == 0
* 满足则清除 waitAck,停止重传,并回调 jbOnAck(wait_cmd, 0)。
*
* 返回: 1=成功并清除等待; 0=未命中/条件不符。
* ════════════════════════════════════════════════════════════════ */
int8_t jbReportAckCheck(uint16_t cmd, uint8_t gateway_int_cnt, int32_t gateway_int0)
{
if (!jbProtocol.waitAck.active)
{
return 0;
}
if (cmd == 0 && gateway_int_cnt == 1 && gateway_int0 == 0)
{
uint8_t wait_cmd = jbProtocol.waitAck.buf[4];
memset(&jbProtocol.waitAck, 0, sizeof(jb_wait_ack_t));
jbOnAck(wait_cmd, 0); /* result=0 表示应答成功 */
return 1;
}
return 0;
}
/* ════════════════════════════════════════════════════════════════
* jbSendReport — 发送主动上报 (0x07)
*
* mask 指定本次上报包含哪些 DP:
* - 差分掩码(来自 jbBuildDiffMask) → 仅上报变化的 DP
* - 全量掩码(来自 jbSetFullMask) → 定时全量同步
* ════════════════════════════════════════════════════════════════ */
static void jbSendReport(jb_data_point_t *data, const uint8_t *mask)
{
uint8_t payload[MAX_PACKAGE_LEN];
uint16_t plen = jbPackDps(data, mask, payload);
uint8_t sn = jbProtocol.sn++;
jbSendReqWithAck(CMD_REPORT, sn, payload, plen);
}
/* ════════════════════════════════════════════════════════════════
* jbReportPolicy — 变化上报(差分) + 定时上报(全量)
*
* 策略:
* 1. DP 发生变化 → 仅上报变化的 DP(差分掩码)
* 2. 每隔 REPORT_PERIOD ms → 上报全部可上报 DP(全量同步)
* ════════════════════════════════════════════════════════════════ */
static void jbReportPolicy(jb_data_point_t *data)
{
uint32_t now = jbGetTimerCount();
uint8_t diffMask[DP_BITMAP_SIZE];
/* ── 1. 变化上报(差分) ── */
uint8_t hasChange = jbBuildDiffMask(&jbProtocol.lastData, data, diffMask);
if (hasChange && !jbProtocol.waitAck.active &&
(now - jbProtocol.lastChangeTime > REPORT_DEBOUNCE))
{
jbSendReport(data, diffMask);
memcpy(&jbProtocol.lastData, data, sizeof(jb_data_point_t));
jbProtocol.lastChangeTime = now;
jbProtocol.lastReportTime = now;
return;
}
/* ── 2. 定时全量上报 ── */
if (!jbProtocol.waitAck.active &&
(now - jbProtocol.lastReportTime > REPORT_PERIOD))
{
uint8_t fullMask[DP_BITMAP_SIZE];
jbSetFullMask(fullMask);
jbSendReport(data, fullMask);
memcpy(&jbProtocol.lastData, data, sizeof(jb_data_point_t));
jbProtocol.lastReportTime = now;
}
}
/* ════════════════════════════════════════════════════════════════
* 命令处理函数(BLE → MCU 下行帧)
* ════════════════════════════════════════════════════════════════ */
/* ── 0x03 APP 下发控制 ── */
static void jbHandleControl(uint8_t *frame, uint16_t len)
{
uint8_t sn = frame[5];
uint8_t *bitmap = &frame[6];
uint8_t *values = &frame[6 + DP_BITMAP_SIZE];
uint16_t valLen = len - 7 - DP_BITMAP_SIZE;
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);
}
/* ── 0x05 APP 读取请求 ── */
static void jbHandleRead(uint8_t *frame, uint16_t len,
jb_data_point_t *data)
{
uint8_t sn = frame[5];
uint8_t *reqBitmap = &frame[6];
uint8_t mask[DP_BITMAP_SIZE];
memcpy(mask, reqBitmap, DP_BITMAP_SIZE);
/* bitmap 全 1(=0xFF...) 表示读取全部 DP */
uint8_t allOnes = 1;
for (int i = 0; i < DP_BITMAP_SIZE; i++)
{
if (mask[i] != 0xFF)
{
allOnes = 0;
break;
}
}
if (allOnes)
{
jbSetFullMask(mask);
}
uint8_t payload[MAX_PACKAGE_LEN];
uint16_t plen = jbPackDps(data, mask, payload);
jbSendFrame(CMD_READ_ACK, sn, payload, plen);
}
/* ── 0x30 OTA 升级请求 ── */
static void jbHandleOtaNotify(uint8_t *frame, uint16_t len)
{
uint8_t sn = frame[5];
uint32_t fwVersion = ((uint32_t)frame[6] << 16)
| ((uint32_t)frame[7] << 8)
| frame[8];
uint32_t fwLength = jbReadU32BE(&frame[9]);
uint32_t fwCrc32 = jbReadU32BE(&frame[13]);
jbOnOtaNotify(fwVersion, fwLength, fwCrc32);
/* 默认应答: 结果码 + 最大单包长度(2B) */
/* maxDataLen 由 MCU 自身能力决定,填入 0x31 应答,BLE 据此分片 */
uint8_t ack[3];
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);
}
/* ── 0x32 OTA 数据(含分片序号)── */
static void jbHandleOtaData(uint8_t *frame, uint16_t len)
{
uint8_t sn = frame[5];
uint16_t shardIdx = jbReadU16BE(&frame[6]);
uint16_t totalShards = jbReadU16BE(&frame[8]);
const uint8_t *pData = &frame[10];
uint16_t dataLen = (len > 11) ? (len - 11) : 0;
jbOnOtaData(pData, dataLen, shardIdx, totalShards);
jbSendAck(CMD_OTA_DATA_ACK, sn, JB_OK);
}
/* ── 0x36 BLE 通知停止 OTA ── */
static void jbHandleOtaStop(uint8_t *frame, uint16_t len)
{
uint8_t sn = frame[5];
jbOnOtaStop();
jbSendAck(CMD_OTA_STOP_BLE_ACK, sn, JB_OK);
}
/* 0x38 通知 BLE 停止 OTAMCU 主动,等待 ACK 重传)*/
int32_t jbNotifyOtaStop(void)
{
uint8_t sn = jbProtocol.sn++;
return jbSendReqWithAck(CMD_OTA_STOP_MCU, sn, NULL, 0);
}
/* 0x34 OTA 升级完成(result + MCU 版本 3B,等待 ACK 重传)*/
int32_t jbSendOtaResult(uint8_t result, const uint8_t *mcuVersion)
{
uint8_t sn = jbProtocol.sn++;
uint8_t payload[4];
payload[0] = result;
if (mcuVersion)
{
memcpy(&payload[1], mcuVersion, 3);
}
else
{
memset(&payload[1], 0, 3);
}
return jbSendReqWithAck(CMD_OTA_COMPLETE, sn, payload, 4);
}
/* ════════════════════════════════════════════════════════════════
* jbProtocolHandle — 协议主循环(在 while(1) 中调用)
* ════════════════════════════════════════════════════════════════ */
int32_t jbProtocolHandle(jb_data_point_t *data)
{
if (!data)
{
return -1;
}
/* ACK 超时重传 */
jbAckRetry();
/* 提取一帧 */
uint16_t pktLen = 0;
int8_t ret = jbGetOnePacket(jbProtocol.rxBuf, &pktLen);
uint8_t cmd;
if (ret == 0)
{
cmd = jbProtocol.rxBuf[4];
/* 按命令码统一分发 BLE → MCU 的所有帧(含 MCU 主动请求的应答)*/
switch (cmd)
{
/* ── APP-BLE 数据 ── */
case CMD_CONTROL:
jbHandleControl(jbProtocol.rxBuf, pktLen);
break;
case CMD_READ:
jbHandleRead(jbProtocol.rxBuf, pktLen, data);
break;
/* ── BLE-MCU OTA ── */
case CMD_OTA_NOTIFY:
jbHandleOtaNotify(jbProtocol.rxBuf, pktLen);
break;
case CMD_OTA_DATA:
jbHandleOtaData(jbProtocol.rxBuf, pktLen);
break;
case CMD_OTA_STOP_BLE:
jbHandleOtaStop(jbProtocol.rxBuf, pktLen);
break;
/* 主动上报 / OTA 完成&停止 的网关应答均走 REPORTCMD_V2 → jbReportAckCheck */
default:
break;
}
}
jbReportPolicy(data);
return 0;
}
@@ -1,266 +0,0 @@
/* ================================================================
* jb_protocol.h — 见宝 IOT 协议层(自动生成)
* 设备: 侠客猫激光 | DP 数: 11 | Bitmap: 2B
* 协议版本: V1.0 | 生成时间: 2026-07-31 17:08:51
*
* 帧格式: [55 AA] [LEN_H LEN_L] [CMD] [SN] [payload...] [CKSUM]
* LEN = CMD(1) + SN(1) + payload + CKSUM(1) = payload + 3
* CKSUM = sum(LEN_H .. payload_last) & 0xFF
* Bitmap = 每个 DP 占 1 bitbit 序号 = DP ID0 起始)
* Values = 按 bitmap 置位顺序排列,每个 DP 占完整 byte_length
* ================================================================
*/
#ifndef _JB_PROTOCOL_H
#define _JB_PROTOCOL_H
#include <stdint.h>
#include <stddef.h>
#include <string.h>
/* ════════════════════════════════════════════════════════════════
* 协议常量
* ════════════════════════════════════════════════════════════════ */
#define PKT_HEAD_0 0x55
#define PKT_HEAD_1 0xAA
#define MAX_PACKAGE_LEN 128
#define JB_OTA_MAX_PAYLOAD 96 /* OTA 单包 Payload 上限(Byte); 须 <= MAX_PACKAGE_LEN - 11(0x32 帧开销) */
#define DP_COUNT 11
#define DP_BITMAP_SIZE 2
#define SEND_MAX_TIME 2500 /* ACK 重传超时(ms)BLE Indication往返常需数百ms~1s,过短会误重传导致双应答 */
#define SEND_MAX_NUM 3 /* 最大重传次数 */
#define REPORT_DEBOUNCE 2000 /* 变化上报防抖时间(ms) */
#define REPORT_PERIOD 600000 /* 定时全量上报周期(ms) */
/* Device identity: change these values for the target product.
* JB_CATEGORY_CODE: 必须 4 字符 ASCII,供 BLE 广播 prodcode 使用
* JB_PRODUCT_CODE : 必须 6 字符 ASCII,供 BLE 广播 pcode 使用
*/
#define JB_PROTOCOL_VERSION 0x0100 /* V1.0 */
#define JB_CATEGORY_CODE "CBC0"
#define JB_PRODUCT_CODE "PICBC0"
#define JB_DEVICE_SN 0x00
#define JB_DEVICE_CAPABILITY 0ULL
/* ════════════════════════════════════════════════════════════════
* 命令码(见《MCU方案接入协议通讯文档》)
* ════════════════════════════════════════════════════════════════ */
typedef enum
{
/* ── APP-BLE 数据(6) ── */
CMD_CONTROL = 0x03, /* APP/BLE → MCU: 下发控制 */
CMD_CONTROL_ACK = 0x04, /* MCU → APP/BLE: 控制应答 */
CMD_READ = 0x05, /* APP/BLE → MCU: 读取请求 */
CMD_READ_ACK = 0x06, /* MCU → APP/BLE: 读取应答 */
CMD_REPORT = 0x07, /* MCU → APP/BLE: 主动上报 */
CMD_REPORT_ACK = 0x08, /* APP/BLE → MCU: 上报应答 */
/* ── OTA(10) ── */
CMD_OTA_NOTIFY = 0x30, /* BLE → MCU: OTA 升级请求 */
CMD_OTA_NOTIFY_ACK = 0x31, /* MCU → BLE: OTA 应答(含MaxLen) */
CMD_OTA_DATA = 0x32, /* BLE → MCU: OTA 数据 */
CMD_OTA_DATA_ACK = 0x33, /* MCU → BLE: OTA 数据应答 */
CMD_OTA_COMPLETE = 0x34, /* MCU → BLE: OTA 完成 */
CMD_OTA_COMPLETE_ACK = 0x35, /* BLE → MCU: OTA 完成应答 */
CMD_OTA_STOP_BLE = 0x36, /* BLE → MCU: 停止 OTA */
CMD_OTA_STOP_BLE_ACK = 0x37, /* MCU → BLE: 停止应答 */
CMD_OTA_STOP_MCU = 0x38, /* MCU → BLE: 通知停止 OTA */
CMD_OTA_STOP_MCU_ACK = 0x39, /* BLE → MCU: 停止应答 */
} jb_cmd_t;
/* ════════════════════════════════════════════════════════════════
* 结果码
* ════════════════════════════════════════════════════════════════ */
typedef enum
{
JB_OK = 0x00,
JB_PARAM_ERR = 0x01,
JB_BUSY = 0x02,
JB_NOT_SUPPORT = 0x03,
JB_CRC_ERR = 0x04,
JB_FAIL = 0x05,
/* ── OTA 专用错误码 ── */
JB_OTA_NO_NEED = 0x20, /* 无需升级(版本一致) */
JB_OTA_VER_LOW = 0x21, /* OTA 版本低于当前版本 */
JB_OTA_SPACE_ERR = 0x22, /* Flash 空间不足 */
JB_OTA_STATE_ERR = 0x23, /* OTA 状态错误 */
JB_OTA_FILE_ERR = 0x24, /* OTA 文件检验失败 */
JB_OTA_FAIL = 0x25, /* 升级失败 */
} jb_result_t;
/* ════════════════════════════════════════════════════════════════
* BLE 工作状态位 (0x14)
* ════════════════════════════════════════════════════════════════ */
#define WS_CONFIGURED (1 << 0) /* 已配网 */
#define WS_INTERNET (1 << 1) /* 已连外网 */
#define WS_APP_ONLINE (1 << 2) /* App 在线 */
#define WS_BLE_CONNECTED (1 << 3) /* 手机 BLE 已连接 */
#define WS_OTA_UPGRADING (1 << 4) /* OTA 升级中 */
#define WS_PAIRING_MODE (1 << 5) /* 配对模式激活 */
/* ════════════════════════════════════════════════════════════════
* 设备能力位图 (0x02 Capability Bitmap, 8B 大端)
* ════════════════════════════════════════════════════════════════ */
#define CAP_BLE_OTA (1ULL << 0) /* BLE OTA */
#define CAP_MCU_OTA (1ULL << 1) /* MCU OTA */
#define CAP_APP_DIRECT (1ULL << 2) /* APP 直连 BLE */
#define CAP_IPC_GATEWAY (1ULL << 3) /* IPC 网关连接 */
#define CAP_PRODUCTION (1ULL << 4) /* 产测模式 */
#define CAP_PARAM_CFG (1ULL << 5) /* 参数配置 */
#define CAP_LOG (1ULL << 6) /* 日志功能 */
/* ════════════════════════════════════════════════════════════════
* 身份信息 (0x02) / MCU 信息 (0x41)
* ════════════════════════════════════════════════════════════════ */
/* ════════════════════════════════════════════════════════════════
* DP ID 枚举(从 Excel 自动生成)
* ════════════════════════════════════════════════════════════════ */
typedef enum
{
DP_ID_POWER = 0, /* 开关 (bool, RW) */
DP_ID_PLAY_MODE = 1, /* 自动模式 (enum, 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_RESERVA_DP1 = 8, /* 预留DP1 (uint32, RW) */
DP_ID_RESERVA_DP2 = 9, /* 预留DP2 (uint32, RW) */
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 1
#define DP_LEN_ALM_LOW_BATTERY 1
#define DP_LEN_BEEP_TRIG 1
#define DP_LEN_BATTERY 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_RESERVA_DP3 4
/* ════════════════════════════════════════════════════════════════
* DP 枚举值定义
* ════════════════════════════════════════════════════════════════ */
/* 自动模式 */
typedef enum
{
PLAY_MODE_0 = 0, /* 关 */
PLAY_MODE_1 = 1, /* mode1 */
PLAY_MODE_2 = 2, /* mode2 */
PLAY_MODE_3 = 3, /* mode3 */
PLAY_MODE_4 = 4, /* mode4 */
} PLAY_MODE_t;
/* 手动模式 */
typedef enum
{
HAND_MODE_0 = 0, /* 关 */
HAND_MODE_1 = 1, /* 顺时针 */
HAND_MODE_2 = 2, /* 逆时针 */
} HAND_MODE_t;
/* ════════════════════════════════════════════════════════════════
* 数据结构
* ════════════════════════════════════════════════════════════════ */
/* 数据点 — 所有 DP 的当前值 */
typedef struct
{
uint8_t power; /* DP0 开关 */
uint8_t play_mode; /* DP1 自动模式 */
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 勿扰模式开关 */
uint32_t reserva_dp1; /* DP8 预留DP1 */
uint32_t reserva_dp2; /* DP9 预留DP2 */
uint32_t reserva_dp3; /* DP10 预留DP3 */
} jb_data_point_t;
/* 控制事件信息 */
typedef struct
{
uint8_t count;
uint8_t dp_ids[DP_ID_MAX];
} jb_event_info_t;
/* ACK 等待 / 重传管理器 */
typedef struct
{
uint8_t active; /* 是否有等待中的请求 */
uint8_t sn; /* 帧序列号 */
uint8_t retry;
uint8_t buf[MAX_PACKAGE_LEN];
uint16_t len;
uint32_t sendTime;
} jb_wait_ack_t;
/* 协议全局状态 */
typedef struct
{
uint8_t sn; /* 设备 SN 计数器 */
uint8_t rxBuf[MAX_PACKAGE_LEN];
jb_wait_ack_t waitAck; /* ACK 重传管理器 */
jb_data_point_t lastData; /* 上次上报快照 */
uint32_t lastReportTime; /* 上次上报时刻 */
uint32_t lastChangeTime; /* 上次变化上报时刻 */
} jb_protocol_t;
/* ════════════════════════════════════════════════════════════════
* 全局实例
* ════════════════════════════════════════════════════════════════ */
extern jb_protocol_t jbProtocol;
/* ════════════════════════════════════════════════════════════════
* 协议层 API(自动生成)
* ════════════════════════════════════════════════════════════════ */
void jbInit(void);
int32_t jbPutData(uint8_t *buf, uint32_t len);
int32_t jbNotifyOtaStop(void);
int32_t jbSendOtaResult(uint8_t result, const uint8_t *mcuVersion);
int32_t jbProtocolHandle(jb_data_point_t *data);
/*
* 网关应答确认(主动上报 / OTA 共用,REPORTCMD_V2,非 55AA
* cmd: ble_proto 包 cmd
* gateway_int_cnt: gateway int 个数
* gateway_int0: gateway_int[0]
* 成功条件: cmd==0 && gateway_int_cnt==1 && gateway_int0==0
* 返回: 1=成功并清除等待; 0=未命中/条件不符
*/
int8_t jbReportAckCheck(uint16_t cmd, uint8_t gateway_int_cnt, int32_t gateway_int0);
/* ════════════════════════════════════════════════════════════════
* 硬件抽象(用户实现)
* ════════════════════════════════════════════════════════════════ */
uint32_t jbGetTimerCount(void); /* ms 计数器(定时器由 SOC SDK 提供) */
/* 发送接口:经 SOC SDK 提供的 BLE 发送封装(由用户/SDK 实现,非本生成代码范围)*/
extern void ble_send_data(uint8_t *data, uint16_t len);
/* ════════════════════════════════════════════════════════════════
* 用户回调(用户实现)
* ════════════════════════════════════════════════════════════════ */
/* 0x03 APP 下发控制事件 */
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *data);
/* BLE → MCU 事件回调 */
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 停止 */
/* MCU → BLE 应答回调(收到 BLE 应答时触发)*/
void jbOnAck(uint8_t cmd, uint8_t result); /* 任意请求 ACK(结果) */
#endif /* _JB_PROTOCOL_H */
@@ -1,69 +0,0 @@
/* ================================================================
* jb_common.c — 工具函数实现
* ================================================================
*/
#include "jb_common.h"
/* ════════════════════════════════════════════════════════════════
* 校验和
* ════════════════════════════════════════════════════════════════ */
uint8_t jbChecksum(const uint8_t *frame, uint16_t totalLen)
{
if (!frame || totalLen < 4)
{
return 0;
}
uint16_t sum = 0;
/* 从 LEN_H(buf[2]) 累加到最后一个 payload 字节(buf[totalLen-2]) */
for (uint16_t i = 2; i < totalLen - 1; i++)
{
sum += frame[i];
}
return (uint8_t)(sum & 0xFF);
}
/* ════════════════════════════════════════════════════════════════
* 大端序读写
* ════════════════════════════════════════════════════════════════ */
uint16_t jbReadU16BE(const uint8_t *buf)
{
if (!buf)
{
return 0;
}
return ((uint16_t)buf[0] << 8) | buf[1];
}
uint32_t jbReadU32BE(const uint8_t *buf)
{
if (!buf)
{
return 0;
}
return ((uint32_t)buf[0] << 24)
| ((uint32_t)buf[1] << 16)
| ((uint32_t)buf[2] << 8)
| buf[3];
}
void jbWriteU16BE(uint8_t *buf, uint16_t val)
{
if (!buf)
{
return;
}
buf[0] = (uint8_t)(val >> 8);
buf[1] = (uint8_t)(val & 0xFF);
}
void jbWriteU32BE(uint8_t *buf, uint32_t val)
{
if (!buf)
{
return;
}
buf[0] = (uint8_t)(val >> 24);
buf[1] = (uint8_t)(val >> 16);
buf[2] = (uint8_t)(val >> 8);
buf[3] = (uint8_t)(val & 0xFF);
}
@@ -1,37 +0,0 @@
/* ================================================================
* jb_common.h — 工具函数(校验和、大端序转换)
*
* 说明: 见宝协议采用大端序(Big-Endian)。
* 多字节值(uint16/uint32)按高位在前传输。
* ================================================================
*/
#ifndef _JB_COMMON_H
#define _JB_COMMON_H
#include <stdint.h>
#include <stddef.h>
/* ════════════════════════════════════════════════════════════════
* 校验和
* sum(LEN_H, LEN_L, CMD, SN, ...payload_last) & 0xFF
* buf[0]=55, buf[1]=AA,累加范围从 buf[2] 到 buf[totalLen-2]
* ════════════════════════════════════════════════════════════════ */
uint8_t jbChecksum(const uint8_t *frame, uint16_t totalLen);
/* ════════════════════════════════════════════════════════════════
* 大端序转换
* ════════════════════════════════════════════════════════════════ */
/* 从字节数组读取 uint16(大端序)*/
uint16_t jbReadU16BE(const uint8_t *buf);
/* 从字节数组读取 uint32(大端序)*/
uint32_t jbReadU32BE(const uint8_t *buf);
/* 将 uint16 写入字节数组(大端序)*/
void jbWriteU16BE(uint8_t *buf, uint16_t val);
/* 将 uint32 写入字节数组(大端序)*/
void jbWriteU32BE(uint8_t *buf, uint32_t val);
#endif /* _JB_COMMON_H */
@@ -1,75 +0,0 @@
/* ================================================================
* jb_ringbuffer.c — 环形缓冲区实现
* ================================================================
*/
#include "jb_ringbuffer.h"
/* ── 初始化 ── */
void jbRbInit(jb_ringbuffer_t *rb)
{
if (!rb)
{
return;
}
rb->write = rb->read = rb->count = 0;
}
/* ── 压入一个字节 ── */
void jbRbPush(jb_ringbuffer_t *rb, uint8_t byte)
{
if (!rb)
{
return;
}
if (rb->count >= JB_RB_CAPACITY)
{
return; /* 已满 — 丢弃 */
}
rb->buf[rb->write] = byte;
rb->write = (rb->write + 1) % JB_RB_CAPACITY;
rb->count++;
}
/* ── 弹出一个字节 ── */
uint8_t jbRbPop(jb_ringbuffer_t *rb)
{
if (!rb || rb->count == 0)
{
return 0;
}
uint8_t byte = rb->buf[rb->read];
rb->read = (rb->read + 1) % JB_RB_CAPACITY;
rb->count--;
return byte;
}
/* ── 查看指定偏移字节(不消费)── */
uint8_t jbRbPeek(const jb_ringbuffer_t *rb, uint16_t offset)
{
if (!rb || offset >= rb->count)
{
return 0;
}
uint16_t idx = (rb->read + offset) % JB_RB_CAPACITY;
return rb->buf[idx];
}
/* ── 可读字节数 ── */
uint16_t jbRbAvailable(const jb_ringbuffer_t *rb)
{
if (!rb)
{
return 0;
}
return rb->count;
}
/* ── 清空缓冲区 ── */
void jbRbClear(jb_ringbuffer_t *rb)
{
if (!rb)
{
return;
}
rb->write = rb->read = rb->count = 0;
}
@@ -1,30 +0,0 @@
/* ================================================================
* jb_ringbuffer.h — 环形缓冲区(UART 接收)
* ================================================================
*/
#ifndef _JB_RINGBUFFER_H
#define _JB_RINGBUFFER_H
#include <stdint.h>
#include <stddef.h>
/* 缓冲区配置 */
#define JB_RB_CAPACITY 256 /* 缓冲区容量(2 的幂可优化取模运算)*/
/* 环形缓冲区实例 */
typedef struct
{
uint8_t buf[JB_RB_CAPACITY];
uint16_t write; /* 写索引 */
uint16_t read; /* 读索引 */
uint16_t count; /* 已用字节数 */
} jb_ringbuffer_t;
void jbRbInit(jb_ringbuffer_t *rb);
void jbRbPush(jb_ringbuffer_t *rb, uint8_t byte);
uint8_t jbRbPop(jb_ringbuffer_t *rb);
uint8_t jbRbPeek(const jb_ringbuffer_t *rb, uint16_t offset);
uint16_t jbRbAvailable(const jb_ringbuffer_t *rb);
void jbRbClear(jb_ringbuffer_t *rb);
#endif /* _JB_RINGBUFFER_H */
@@ -1,49 +0,0 @@
#include "system/includes.h"
#include "app_config.h"
#include "app_main.h"
#include "app_task.h"
#include "user_cfg.h"
#include "vm.h"
#include "sys_time.h"
#include "jb_protocol.h"
#include "jb_product.h"
#include "jb_ringbuffer.h"
#include "jb_common.h"
#include "usr_le_product.h"
/* 向 usr_le_code 提供产品身份(换产品只改 jb_protocol.h 宏即可) */
const uint8_t g_usr_le_prodcode[USR_LE_PRODCODE_LEN] = {
JB_CATEGORY_CODE[0], JB_CATEGORY_CODE[1],
JB_CATEGORY_CODE[2], JB_CATEGORY_CODE[3]
};
const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN] = {
JB_PRODUCT_CODE[0], JB_PRODUCT_CODE[1], JB_PRODUCT_CODE[2],
JB_PRODUCT_CODE[3], JB_PRODUCT_CODE[4], JB_PRODUCT_CODE[5]
};
void usr_timer_1_ms();
static int jb_timer_id=0;
void usr_jb_init()
{
jbInit();
userInit();
if(jb_timer_id==0)jb_timer_id = sys_s_hi_timer_add(NULL, usr_timer_1_ms, TIMER_PERIOD_MS);
}
void usr_timer_1_ms()
{
jbTimerIrq();
}
void usr_jb_le_recieve_data(u8* data,u16 len)
{
jbPutData(data,len);
}
void usr_timer_loop()///500ms
{
userHandle();
jbProtocolHandle(&gDevData);
}
@@ -403,6 +403,11 @@ typedef struct bleproto_authsetup_req {
uint8_t gwmac[6]; ///< 主控mac地址, 设备需要保存
char devid[36 + 1]; ///< 主控分配的子设备id, 设备需要保存
char authcode[32 + 1]; ///< 主控分配的子设备认证码, 设备需要保存
/**
* paired_flag:设备是否已完成配网(持久化在 VM)
* 0 = 未配网:默认不广播,除非 usr_goto_pair=1(绑定广播)
* 1 = 已配网:断连后发回连广播;AUTHSETUP 写三元组成功后置 1
*/
uint8_t paired_flag;
// clang-format on
} bleproto_authsetup_req_t;
@@ -15,8 +15,9 @@
#include "bleproto.h"
#include "bleproto_api.h"
#include "usr_le_product.h"
#include "mcu_bridge.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP]"
#define LOG_TAG "[LE_ADV]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
@@ -24,6 +25,27 @@
#define LOG_CLI_ENABLE
#include "debug.h"
/******************************************************************************
* 广播相关三个标志(务必区分):
*
* 1) usr_auth_data.paired_flag —— 配网结果(存 VM,掉电保持)
* - 0:未配网
* - 1:已配网(三元组已保存)
*
* 2) usr_var.usr_goto_pair —— 是否主动进入配网流程(RAM,掉电丢失)
* - 0:非配网流程
* - 1MCU 0x10 / usr_goto_pair_mode() 触发,发【绑定广播】
*
* 3) usr_var.usr_adv_flag —— 广播内容是否需要刷新到协议栈(一次性)
* - 1usr_ble_adv_updata() 会重新 set adv data 并 enable
* - 0:本周期不刷新
*
* 决策表(身份就绪后):
* usr_goto_pair==1 → 绑定广播(重新配网)
* paired_flag==0 且未配网 → 不广播
* paired_flag==1 → 回连广播
******************************************************************************/
extern u8 muti_adv_data[31];//={0};
extern u8 muti_adv_data_len;
@@ -46,6 +68,10 @@ extern u8 usr_get_work_status();
extern void usr_printf_sta();
extern int muti_make_set_adv_data_updata();
/**
* @brief 从 VM 读取配对信息到 usr_auth_data
* @note 失败则清零,并将 paired_flag 置 0 写回
*/
void usr_get_pair_info()
{
int ret = 0xffff;
@@ -53,46 +79,69 @@ void usr_get_pair_info()
if(ret<=0)
{
memset(&usr_auth_data,0x00,sizeof(usr_auth_data));
usr_auth_data.paired_flag=0;
usr_auth_data.paired_flag=0; /* 未配网 */
syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data));
}
}
/**
* @brief 标记已配网并保存三元组到 VM
* @note AUTHSETUP 成功后调用,paired_flag = 1
*/
void usr_write_pair_info()
{
usr_auth_data.paired_flag=1;
usr_auth_data.paired_flag=1; /* 已配网 */
syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data));
}
/**
* @brief 清除配对信息并延时复位
*/
void usr_clear_pair_info()
{
printf("usr_clear_pair_info\n");
memset(&usr_auth_data,0x00,sizeof(usr_auth_data));
usr_auth_data.paired_flag=0;
usr_auth_data.paired_flag=0; /* 回到未配网 */
syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data));
mcu_bridge_on_pair_cleared();
sys_timeout_add(NULL,cpu_reset,600);
}
/**
* @brief 清除配对信息但不复位(用于重新配网)
*/
void usr_clear_pair_info_noreset()
{
printf("usr_clear_pair_info2\n");
memset(&usr_auth_data,0x00,sizeof(usr_auth_data));
usr_auth_data.paired_flag=0;
usr_auth_data.paired_flag=0; /* 回到未配网,后续由 usr_goto_pair 决定是否绑广播 */
syscfg_write(CFG_USER_PAIR_INFO, (u8 *)&usr_auth_data, sizeof(usr_auth_data));
mcu_bridge_on_pair_cleared();
}
extern void usr_set_adv_interval(u16 val);
extern u16 usr_get_adv_interval();
extern void ble_multi_trans_disconnect(void);
extern void ble_trans_module_enable(u8 flag);
void usr_ble_adv_init();
/**
* @brief 进入配网模式:清配对 + 置 usr_goto_pair + 发绑定广播
* @note MCU 0x10 配网请求最终会走到这里
*/
void usr_goto_pair_mode()
{
ble_multi_trans_disconnect();
usr_clear_pair_info_noreset();
usr_var.usr_goto_pair=1;
usr_var.usr_goto_pair=1; /* 进入主动配网流程 → 绑定广播 */
os_time_dly(5);
usr_ble_adv_init();
}
/**
* @brief 按当前标志刷新广播策略(不一定开广播)
* @note 见文件头「三个标志」说明;未拿身份 / 未配网时保持不广播
*/
void usr_ble_adv_init()
{
int8_t txpower = 0;
@@ -101,36 +150,48 @@ void usr_ble_adv_init()
uint8_t gmac[6] = {0};
uint8_t pcode[BLE_PROTO_ADV_LENGTH_PCODE];
/* 产品身份由应用层 g_usr_le_* 提供(见 usr_jb_main.c */
/* 产品身份由 MCU 桥写入 g_usr_le_* */
memcpy(prodcode, g_usr_le_prodcode, BLEPROTO_ADV_TLV_LENGTH_PRODCODE);
memcpy(pcode, g_usr_le_pcode, BLE_PROTO_ADV_LENGTH_PCODE);
usr_set_adv_interval(80);
if(usr_var.usr_ota_succ_flag)return;
//muti_adv_data
/* 未拿到 MCU 身份:完全不广播 */
if (!mcu_bridge_is_identity_ready()) {
printf("usr_ble_adv_init: wait MCU identity, no adv\n");
usr_var.usr_adv_flag = 0; /* 不请求刷新广播 */
ble_trans_module_enable(0);
if (le_timer_handle == 0) {
le_timer_handle = sys_timer_add(NULL, usr_ble_adv_updata, 500);
}
return;
}
if(usr_var.usr_goto_pair)
{
/* app_main 上电已置 usr_goto_pair=1 并清配对信息,此处发绑定广播供网关配网 */
/* 主动配网中:发绑定广播,请求刷新到协议栈 */
usr_clear_pair_info_noreset();
printf("power on force to par flag= %d\n",usr_auth_data.paired_flag);
{
printf("goto pair bind adv, paired_flag= %d\n",usr_auth_data.paired_flag);
muti_adv_data_len=bleproto_advdata_encode4bind(muti_adv_data,txpower,manucode,pcode,prodcode);
}
usr_var.usr_adv_flag=1;
usr_var.usr_adv_flag=1; /* 请求:写入绑定广播并开启 */
}
else
{
usr_get_pair_info();
if(usr_auth_data.paired_flag==0)
{
muti_adv_data_len=bleproto_advdata_encode4bind(muti_adv_data,txpower,manucode,pcode,prodcode);
/* 未配网且未进入配网流程:完全不广播 */
printf("usr_ble_adv_init: not paired, no adv\n");
usr_var.usr_adv_flag=0;
ble_trans_module_enable(0);
}
else
{
/* 已配网:回连广播 */
memcpy(gmac,usr_auth_data.gwmac,6);
muti_adv_data_len=bleproto_advdata_encode4reconn(muti_adv_data,txpower,gmac,manucode,pcode,prodcode);
usr_var.usr_adv_flag=1;
usr_var.usr_adv_flag=1; /* 请求:写入回连广播并开启 */
}
}
@@ -138,6 +199,18 @@ void usr_ble_adv_init()
if(le_timer_handle==0)le_timer_handle = sys_timer_add(NULL, usr_ble_adv_updata, 500);
}
/**
* @brief 获取是否已配网
* @return 1=已配网(paired_flag)0=未配网
*/
/**
* @brief 获取是否已配网
* @return 1=已配网(paired_flag)0=未配网
*/
/**
* @brief 获取是否已配网
* @return paired_flag1=已配网,0=未配网
*/
u8 usr_get_pair_flag()
{
return usr_auth_data.paired_flag;
@@ -154,9 +227,12 @@ void usr_reconn_adv()
muti_adv_data_len=bleproto_advdata_encode4reconn(muti_adv_data,txpower,gmac,manucode,pcode,prodcode);
usr_adv_flag=1;
}*/
extern void ble_trans_module_enable(u8 flag);
extern void usr_timer_loop();
extern u8 usr_get_ota_status();
/**
* @brief 周期处理广播刷新(由 500ms 定时器调用)
* @note 仅当 usr_adv_flag==1 时:停广播 → 写入 muti_adv_data → 再开广播,并清 usr_adv_flag
*/
void usr_ble_adv_updata()
{
//printf("usr_ble_adv_updata\n");
@@ -164,7 +240,7 @@ void usr_ble_adv_updata()
if(usr_var.usr_adv_flag)
{
ble_trans_module_enable(0);
usr_var.usr_adv_flag=0;
usr_var.usr_adv_flag=0; /* 一次性触发,用完即清 */
muti_make_set_adv_data(muti_adv_data,muti_adv_data_len);
printf("###muti_adv_data = ");
printf_buf(muti_adv_data,muti_adv_data_len);
@@ -208,9 +284,6 @@ void usr_ble_adv_updata()
if(usr_var.usr_ota_succ_flag==0)usr_ota_exit();
}
}
/* 协议层周期处理(上报/应答) */
usr_timer_loop();
}
void usr_soft_off()
@@ -1,11 +1,12 @@
/******************************************************************************
* @file usr_le_api.h
* @brief usr_le_code 对外 API(广播 / 收发 / 配网)
* @brief usr_le_code 对外 API(广播 / 收发 / 配网 / MCU 帧转发
* @author cyWu <1917507415@qq.com>
* @date 2026.08.03
* @version V1.0.0
* @date 2026.08.06
* @version V1.1.0
* @history
* - V1.0.0, 2026.08.03, cyWu, 首次发布
* - V1.1.0, 2026.08.06, cyWu, 增加 MCU 帧 BLE 发送接口,解耦模组桥
******************************************************************************/
#ifndef __USR_LE_API_H__
@@ -18,8 +19,12 @@ extern "C" {
#endif
/**
* @brief 初始化 BLE 广播数据(绑定广播或重连广播)
* @note 根据配对状态与 usr_goto_pair 选择 bind/reconn 广播,并启动 500ms 周期更新
* @brief 初始化/刷新 BLE 广播策略(不一定开启广播)
* @note 决策依赖三个标志:
* - paired_flag:是否已配网(VM
* - usr_goto_pair:是否主动配网中(发绑定广播)
* - usr_adv_flag:是否请求把新广播包刷到协议栈
* 详见 usr_le_adv.c 文件头注释
*/
void usr_ble_adv_init(void);
@@ -27,18 +32,25 @@ void usr_ble_adv_init(void);
* @brief BLE 协议栈接收数据入口
* @param data 接收缓冲区
* @param len 本次接收长度
* @note 组包后解析 bleproto,并分发 DEVICEINFO/AUTH/RUNCMD/OTA 等服务
*/
void usr_le_data_recieve(uint8_t *data, uint16_t len);
/**
* @brief 将 MCU 协议 55AA 帧封装为 bleproto 后经 GATT 发送
* @param data 完整 55AA 帧
* @param len 帧长度
* @note 0x07/0x34/0x38 走 REPORTCMD_V2 主动上报;其余走 RUNCMD_V2 应答
*/
void usr_le_send_mcu_frame(uint8_t *data, uint16_t len);
/**
* @brief 进入配对/配网模式
* @note 断开当前连接、清除配对信息(不复位),重新发绑定广播
* @note 清配对信息,置 usr_goto_pair=1,发绑定广播(非回连)
*/
void usr_goto_pair_mode(void);
/**
* @brief 清除配对信息但不复位
* @brief 清除配对信息但不复位paired_flag→0
*/
void usr_clear_pair_info_noreset(void);
@@ -48,14 +60,13 @@ void usr_clear_pair_info_noreset(void);
void usr_clear_pair_info(void);
/**
* @brief 获取当前是否已配
* @return 1=已配0=未配
* @brief 获取是否已配
* @return paired_flag1=已配0=未配
*/
uint8_t usr_get_pair_flag(void);
/**
* @brief 启动应用侧 OTA 流程
* @note 置位 usr_goto_updata,并创建 1s 周期定时器驱动断连与 OTA 广播切换
*/
void usr_app_ota_init(void);
@@ -1,11 +1,12 @@
/******************************************************************************
* @file usr_le_product.h
* @brief usr_le_code 产品身份配置接口(由应用层提供符号,模块内不写死
* @brief usr_le_code 产品身份配置接口(由 MCU 桥运行时填充
* @author cyWu <1917507415@qq.com>
* @date 2026.08.03
* @version V1.0.0
* @version V1.1.0
* @history
* - V1.0.0, 2026.08.03, cyWu, 首次发布
* - V1.1.0, 2026.08.06, cyWu, 改为非 const,由 mcu_bridge 从 MCU 0x02 写入
******************************************************************************/
#ifndef __USR_LE_PRODUCT_H__
@@ -29,15 +30,15 @@ extern "C" {
/**
* @brief 产品类别码,对应 BLE 广播 prodcode(固定 4 字节 ASCII
* @note 应用层用 JB_CATEGORY_CODE 填充,见 usr_jb_main.c
* @note 由 mcu_bridge 从 MCU 身份应答取前 4 字节写入
*/
extern const uint8_t g_usr_le_prodcode[USR_LE_PRODCODE_LEN];
extern uint8_t g_usr_le_prodcode[USR_LE_PRODCODE_LEN];
/**
* @brief 平台产品码,对应 BLE 广播 pcode(固定 6 字节 ASCII
* @note 应用层用 JB_PRODUCT_CODE 填充,见 usr_jb_main.c
* @note 由 mcu_bridge 从 MCU 身份应答取前 6 字节写入
*/
extern const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN];
extern uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN];
#ifdef __cplusplus
}
@@ -16,6 +16,7 @@
#include "bleproto.h"
#include "bleproto_api.h"
#include "ble_proto.h"
#include "mcu_frame.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP]"
#define LOG_ERROR_ENABLE
@@ -43,8 +44,12 @@ extern void app_audio_volume_set(u8 value);
extern int dual_ota_app_data_deal(u32 msg, u8 *buf, u32 len);
extern int uart_tr_send_data(u8 *data, u32 len);
extern void usr_intterupt_close();
extern void usr_jb_le_recieve_data(u8 *data, u16 len);
extern int8_t jbReportAckCheck(u16 cmd, u8 gateway_int_cnt, int32_t gateway_int0);
extern void mcu_bridge_on_paired(void);
extern void mcu_bridge_on_pair_cleared(void);
extern void mcu_bridge_on_app_frame(const uint8_t *data, uint16_t len);
extern void mcu_bridge_on_gateway_report_ack(uint16_t cmd,
uint8_t gateway_int_cnt,
int32_t gateway_int0);
void usr_ota_deal_task();
void usr_data_ana(bleproto_appdata_desc_t *desc);
@@ -61,6 +66,14 @@ void usr_le_data_recieve(u8 *data, u16 len) /// BLE数据接收
int rc_result = 0;
if ((u32)usr_len + len > sizeof(rxbuf))
{
/* 累积超界:多半是丢包/半包导致 decode 一直返回 0,防止缓冲区溢出 */
printf("### rxbuf overflow, usr_len=%u len=%u, reset\n", usr_len, len);
usr_len = 0;
return;
}
memcpy(&rxbuf[usr_len], data, len);
usr_len += len;
rc_result = bleproto_appdata_decode(rxbuf, usr_len, &desc);
@@ -149,6 +162,7 @@ void usr_data_ana(bleproto_appdata_desc_t *desc)
if (usr_auth_data.paired_flag == 0)
{
usr_write_pair_info();
mcu_bridge_on_paired();
printf("usr_auth_data %s %s ", usr_auth_data.authcode, usr_auth_data.devid);
printf_buf(usr_auth_data.gwmac, 6);
}
@@ -305,14 +319,14 @@ void usr_data_ana(bleproto_appdata_desc_t *desc)
break;
case E_BLEPROTO_SERVICE_ID_REPORTCMD_V2:
{
/* 网关对主动上报 / OTA 的应答(非 55AA):
* cmd==0 && gateway_int_cnt==1 && gateway_int[0]==0 → 应答成功 */
/* 网关对 MCU 主动上报的应答:成功后由桥接层回 MCU 0x08 */
ble_proto_packet_t *rpt_ack = &desc->datast.reportcmd_v2_rsp;
printf("E_BLEPROTO_SERVICE_ID_REPORTCMD_V2 cmd=%u cnt=%u int0=%ld\n",
(unsigned)rpt_ack->cmd,
(unsigned)rpt_ack->gateway_int_cnt,
(long)((rpt_ack->gateway_int_cnt > 0) ? rpt_ack->gateway_int[0] : 0));
jbReportAckCheck(rpt_ack->cmd,
mcu_bridge_on_gateway_report_ack(
rpt_ack->cmd,
rpt_ack->gateway_int_cnt,
(rpt_ack->gateway_int_cnt > 0) ? rpt_ack->gateway_int[0] : -1);
break;
@@ -431,7 +445,7 @@ void usr_ota_deal_task()
static u8 s_last_runcmd_msgid = 0;
void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id)
{
u8 jb_frame[128]; /* 与 jb_protocol.h MAX_PACKAGE_LEN 一致 */
u8 mcu_frame[MCU_FRAME_MAX_LEN];
u16 copy_len;
if (!runcmd_par || !runcmd_par->bytes_data || runcmd_par->device_bytes_len == 0)
@@ -444,45 +458,40 @@ void usr_run_cmd_code_v2(ble_proto_packet_t *runcmd_par, u8 usr_msg_id)
printf("runcmd_par id = %d, bytes_len = %u\n",
runcmd_par->cmd, runcmd_par->device_bytes_len);
/* 先拷到本地再入 JB 环缓,避免零拷贝指针被后续逻辑覆盖 */
copy_len = runcmd_par->device_bytes_len;
if (copy_len > sizeof(jb_frame))
if (copy_len > sizeof(mcu_frame))
{
printf("usr_run_cmd_code_v2 bytes too long %u\n", copy_len);
return;
}
memcpy(jb_frame, runcmd_par->bytes_data, copy_len);
memcpy(mcu_frame, runcmd_par->bytes_data, copy_len);
s_last_runcmd_msgid = usr_msg_id;
usr_jb_le_recieve_data(jb_frame, copy_len);
mcu_bridge_on_app_frame(mcu_frame, copy_len);
}
/**
* @brief 将 JB 协议帧封装为 bleproto V2 后通过 BLE 发送
* @param data JB 协议原始帧(55 AA | LEN | CMD | ...
* @brief 将 MCU 协议帧封装为 bleproto V2 后通过 BLE 发送
* @param data 55AA 完整帧
* @param len 帧长度
* @note 根据 JB CMD 区分主动上报 / 应答
* - 主动(0x07/0x34/0x38): REPORTCMD_V2 + REQ + cmd=100
* - 应答(其余): RUNCMD_V2 + RSP + cmd=110
* @note 主动上报(0x07/0x34/0x38) → REPORTCMD_V2;其余 → RUNCMD_V2 应答
*/
void ble_send_data(u8 *data, u16 len)
void usr_le_send_mcu_frame(u8 *data, u16 len)
{
bleproto_appdata_desc_t encode_desc = {0};
ble_proto_packet_t *pkt = NULL;
u8 jb_cmd;
u8 mcu_cmd;
u8 is_active_send;
int elen;
if (!data || len < 5)
{
printf("ble_send_data invalid param, len=%d\n", len);
printf("usr_le_send_mcu_frame invalid param, len=%d\n", len);
return;
}
/* JB帧: 55 AA | LEN_H LEN_L | CMD | SN | ...CMD 在偏移 4 */
jb_cmd = data[4];
/* 主动上报 0x07MCU 主动 OTA 完成/停止 0x34/0x38;其余按应答 */
is_active_send = (jb_cmd == 0x07) || (jb_cmd == 0x34) || (jb_cmd == 0x38);
mcu_cmd = data[4];
is_active_send = (mcu_cmd == 0x07) || (mcu_cmd == 0x34) || (mcu_cmd == 0x38);
encode_desc.header.version = 0;
encode_desc.header.datafmt = BLEPROTO_APPDATA_DATAFMT_JSON;
@@ -490,7 +499,6 @@ void ble_send_data(u8 *data, u16 len)
if (is_active_send)
{
/* 主动发送:REPORTCMD_V2 / REQ / cmd=100 */
encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_REQ;
encode_desc.header.msgid = 0;
encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_REPORTCMD_V2;
@@ -502,7 +510,6 @@ void ble_send_data(u8 *data, u16 len)
}
else
{
/* 应答命令:RUNCMD_V2 / RSP / cmd=110,回填最新 msgid */
encode_desc.header.msgtype = BLEPROTO_APPDATA_MSGTYPE_RSP;
encode_desc.header.msgid = s_last_runcmd_msgid;
encode_desc.header.serviceid = E_BLEPROTO_SERVICE_ID_RUNCMD_V2;
@@ -521,8 +528,8 @@ void ble_send_data(u8 *data, u16 len)
pkt->bytes_data = data;
elen = bleproto_appdata_encode(txbuf, sizeof(txbuf), sizeof(txbuf), &encode_desc);
printf("ble_send_data jb_cmd=0x%02X active=%d encode_len=%d msgid=%d\n",
jb_cmd, is_active_send, elen, encode_desc.header.msgid);
printf("usr_le_send_mcu_frame cmd=0x%02X active=%d encode_len=%d msgid=%d\n",
mcu_cmd, is_active_send, elen, encode_desc.header.msgid);
if (elen > 0)
{
usr_ble_send_data(txbuf, elen);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,100 @@
/******************************************************************************
* @file mcu_bridge.h
* @brief BLE+MCU 协议桥对外 API(模组侧唯一业务入口)
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.1.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
* - V1.1.0, 2026.08.06, cyWu, 内部改为事件队列 + 独立任务驱动;
* 所有 on_xxx 接口均可在任意任务上下文安全调用(生产者-消费者模型)
******************************************************************************/
#ifndef __MCU_BRIDGE_H__
#define __MCU_BRIDGE_H__
#include <stdint.h>
#include "mcu_cmd.h"
/*============================================================================*/
/* 错误码 */
/*============================================================================*/
typedef enum {
MCU_BRIDGE_OK = 0,
MCU_BRIDGE_ERR_PARAM,
MCU_BRIDGE_ERR_BUSY,
MCU_BRIDGE_ERR_TIMEOUT,
MCU_BRIDGE_ERR_NOT_INIT,
MCU_BRIDGE_ERR_UNKNOWN
} McuBridge_Ret_t;
/*============================================================================*/
/* 外部接口 */
/*============================================================================*/
/**
* @brief 初始化 MCU 协议桥(UART + 事件队列 + 独立任务)
* @return McuBridge_Ret_t
* @note 在 app_main 中调用一次即可;内部会创建 "mcu_bridge" 任务,
* 所有身份/心跳/透传/状态机逻辑均在该任务单一上下文中处理
*/
McuBridge_Ret_t mcu_bridge_init(void);
/**
* @brief UART 收到原始数据(由 mcu_uart 接收任务调用)
* @param data 数据
* @param len 长度
* @note 仅做逐字节组帧;帧就绪后投递事件到 mcu_bridge 任务处理,
* 可安全在 UART 接收任务上下文调用
*/
void mcu_bridge_on_uart_rx(const uint8_t *data, uint16_t len);
/**
* @brief APP/网关经 BLE 下发的 55AA 帧
* @param data 帧数据
* @param len 长度
* @note 对 0x03/0x05:转发 MCU,等 0x04/0x06 后再回传 BLE
* 可安全在 BLE 协议栈任务上下文调用(内部投递事件异步处理)
*/
void mcu_bridge_on_app_frame(const uint8_t *data, uint16_t len);
/**
* @brief 是否已从 MCU 获取到设备身份
* @return 1=已就绪,0=未就绪
* @note 单字节只读快照,可在任意任务直接调用
*/
uint8_t mcu_bridge_is_identity_ready(void);
/**
* @brief 配网成功通知(AUTHSETUP 写对后由 BLE 层调用)
* @note 可安全在 BLE 协议栈任务上下文调用(内部投递事件异步处理)
*/
void mcu_bridge_on_paired(void);
/**
* @brief 配对信息被清除通知
* @note 可安全在 BLE 协议栈任务上下文调用(内部投递事件异步处理)
*/
void mcu_bridge_on_pair_cleared(void);
/**
* @brief 获取当前工作状态字节
* @return 工作状态 bitmap
* @note 单字节只读快照,可在任意任务直接调用
*/
uint8_t mcu_bridge_get_work_status(void);
/**
* @brief 网关对 REPORTCMD_V2 主动上报的应答
* @param cmd bleproto 包 cmd
* @param gateway_int_cnt gateway_int 个数
* @param gateway_int0 gateway_int[0]
* @note 成功条件:cmd==0 && cnt==1 && int0==0
* 成功后才向 MCU 回 0x08;可安全在 BLE 协议栈任务上下文调用
*/
void mcu_bridge_on_gateway_report_ack(uint16_t cmd,
uint8_t gateway_int_cnt,
int32_t gateway_int0);
#endif /* __MCU_BRIDGE_H__ */
@@ -0,0 +1,110 @@
/******************************************************************************
* @file mcu_cmd.h
* @brief MCU 接入协议命令字 / 结果码 / 工作状态位定义
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
******************************************************************************/
#ifndef __MCU_CMD_H__
#define __MCU_CMD_H__
#include <stdint.h>
/*============================================================================*/
/* 工作状态 Bit(命令 0x14 */
/*============================================================================*/
#define MCU_WS_CONFIGURED (1u << 0) /**< 已完成配网 */
#define MCU_WS_INTERNET (1u << 1) /**< 已连互联网(云),预留:暂无法获取,固定为 0 */
#define MCU_WS_APP_ONLINE (1u << 2) /**< APP 在线,预留:暂无法获取,固定为 0 */
#define MCU_WS_BLE_CONNECTED (1u << 3) /**< 手机已连接 BLE,预留:暂无法获取,固定为 0 */
#define MCU_WS_OTA_UPGRADING (1u << 4) /**< OTA 升级中 */
#define MCU_WS_PAIRING_MODE (1u << 5) /**< 配网模式中 */
/*============================================================================*/
/* 命令字 */
/*============================================================================*/
typedef enum {
/* 身份 */
MCU_CMD_GET_IDENTITY = 0x01,
MCU_CMD_GET_IDENTITY_ACK = 0x02,
/* 数据透传 */
MCU_CMD_CONTROL = 0x03,
MCU_CMD_CONTROL_ACK = 0x04,
MCU_CMD_READ = 0x05,
MCU_CMD_READ_ACK = 0x06,
MCU_CMD_REPORT = 0x07,
MCU_CMD_REPORT_ACK = 0x08,
/* 配网 / 心跳 / 状态 */
MCU_CMD_PAIR_CFG = 0x10,
MCU_CMD_PAIR_CFG_ACK = 0x11,
MCU_CMD_HEARTBEAT = 0x12,
MCU_CMD_HEARTBEAT_ACK = 0x13,
MCU_CMD_WORK_STATUS = 0x14,
MCU_CMD_WORK_STATUS_ACK = 0x15,
/* 模组管理(后续扩展) */
MCU_CMD_RESET = 0x16,
MCU_CMD_RESET_ACK = 0x17,
MCU_CMD_REBOOT = 0x18,
MCU_CMD_REBOOT_ACK = 0x19,
MCU_CMD_MCU_REBOOT = 0x1A,
MCU_CMD_MCU_REBOOT_ACK = 0x1B,
MCU_CMD_GET_TIME = 0x1C,
MCU_CMD_GET_TIME_ACK = 0x1D,
/* 扩展功能(后续扩展) */
MCU_CMD_FACTORY = 0x20,
MCU_CMD_FACTORY_ACK = 0x21,
MCU_CMD_PHOTO = 0x22,
MCU_CMD_PHOTO_ACK = 0x23,
MCU_CMD_RECORD = 0x24,
MCU_CMD_RECORD_ACK = 0x25,
MCU_CMD_AUDIO = 0x26,
MCU_CMD_AUDIO_ACK = 0x27,
/* OTA(后续扩展) */
MCU_CMD_OTA_NOTIFY = 0x30,
MCU_CMD_OTA_NOTIFY_ACK = 0x31,
MCU_CMD_OTA_DATA = 0x32,
MCU_CMD_OTA_DATA_ACK = 0x33,
MCU_CMD_OTA_COMPLETE = 0x34,
MCU_CMD_OTA_COMPLETE_ACK = 0x35,
MCU_CMD_OTA_STOP_BLE = 0x36,
MCU_CMD_OTA_STOP_BLE_ACK = 0x37,
MCU_CMD_OTA_STOP_MCU = 0x38,
MCU_CMD_OTA_STOP_MCU_ACK = 0x39,
/* MCU 信息 */
MCU_CMD_GET_MCU_INFO = 0x40,
MCU_CMD_GET_MCU_INFO_ACK = 0x41
} McuCmd_t;
/*============================================================================*/
/* 结果码 */
/*============================================================================*/
typedef enum {
MCU_RESULT_OK = 0x00,
MCU_RESULT_PARAM_ERR = 0x01,
MCU_RESULT_BUSY = 0x02,
MCU_RESULT_NOT_SUPPORT = 0x03,
MCU_RESULT_CRC_ERR = 0x04,
MCU_RESULT_FAIL = 0x05,
/* OTA 专用 */
MCU_RESULT_OTA_NO_NEED = 0x20,
MCU_RESULT_OTA_VER_LOW = 0x21,
MCU_RESULT_OTA_SPACE_ERR = 0x22,
MCU_RESULT_OTA_STATE_ERR = 0x23,
MCU_RESULT_OTA_FILE_ERR = 0x24,
MCU_RESULT_OTA_FAIL = 0x25
} McuResult_t;
#endif /* __MCU_CMD_H__ */
@@ -0,0 +1,140 @@
/******************************************************************************
* @file mcu_frame.c
* @brief MCU 接入协议 55AA 帧组包/解包
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
******************************************************************************/
#include "mcu_frame.h"
#include "mcu_util.h"
#include <string.h>
/*============================================================================*/
/* 私有变量 */
/*============================================================================*/
static uint8_t s_state = 0; /* 0=寻头 1=收长度高 2=收长度低 3=收载荷 */
static uint8_t s_prev = 0;
static uint16_t s_expect = 0;
static uint16_t s_idx = 0;
static uint8_t s_buf[MCU_FRAME_MAX_LEN];
/*============================================================================*/
/* 公有函数 */
/*============================================================================*/
/**
* @brief 复位帧解析状态机
*/
void mcu_frame_parser_reset(void)
{
s_state = 0;
s_prev = 0;
s_expect = 0;
s_idx = 0;
}
/**
* @brief 组帧并写入 out(含包头与校验)
* @param out 输出缓冲
* @param out_max 缓冲最大长度
* @param cmd 命令字
* @param sn 序列号
* @param payload 数据区,可为 NULL
* @param plen 数据长度
* @return 帧总长度;<0 失败
*/
int32_t mcu_frame_pack(uint8_t *out, uint16_t out_max,
uint8_t cmd, uint8_t sn,
const uint8_t *payload, uint16_t plen)
{
uint16_t len_field;
uint16_t total;
if (!out) {
return -1;
}
/* LEN = CMD + SN + payload + CKSUM */
len_field = (uint16_t)(plen + 3);
total = (uint16_t)(len_field + 4);
if (total > out_max || total > MCU_FRAME_MAX_LEN) {
return -1;
}
out[0] = MCU_PKT_HEAD_0;
out[1] = MCU_PKT_HEAD_1;
mcu_util_write_u16_be(&out[2], len_field);
out[4] = cmd;
out[5] = sn;
if (payload && plen > 0) {
memcpy(&out[6], payload, plen);
}
out[total - 1] = mcu_util_checksum(out, total);
return (int32_t)total;
}
/**
* @brief 向解析器喂入原始字节,尝试提取完整帧
* @param byte 输入字节
* @param out 输出完整帧缓冲
* @param out_len 输出帧长度
* @return 1=得到完整帧,0=继续,-1=校验错误丢弃
*/
int8_t mcu_frame_feed(uint8_t byte, uint8_t *out, uint16_t *out_len)
{
if (!out || !out_len) {
return 0;
}
if (s_state == 0) {
if (s_prev == MCU_PKT_HEAD_0 && byte == MCU_PKT_HEAD_1) {
s_buf[0] = MCU_PKT_HEAD_0;
s_buf[1] = MCU_PKT_HEAD_1;
s_idx = 2;
s_state = 1;
}
s_prev = byte;
return 0;
}
if (s_state == 1) {
s_buf[s_idx++] = byte;
s_state = 2;
return 0;
}
if (s_state == 2) {
s_buf[s_idx++] = byte;
s_expect = mcu_util_read_u16_be(&s_buf[2]);
if (s_expect < 3 || (s_expect + 4) > MCU_FRAME_MAX_LEN) {
mcu_frame_parser_reset();
s_prev = byte;
return -1;
}
s_state = 3;
return 0;
}
s_buf[s_idx++] = byte;
if (s_idx >= (uint16_t)(s_expect + 4)) {
uint16_t total = (uint16_t)(s_expect + 4);
uint8_t ck = mcu_util_checksum(s_buf, total);
int8_t ret;
if (ck == s_buf[total - 1]) {
memcpy(out, s_buf, total);
*out_len = total;
ret = 1;
} else {
ret = -1;
}
mcu_frame_parser_reset();
return ret;
}
return 0;
}
@@ -0,0 +1,57 @@
/******************************************************************************
* @file mcu_frame.h
* @brief MCU 接入协议 55AA 帧组包/解包
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
******************************************************************************/
#ifndef __MCU_FRAME_H__
#define __MCU_FRAME_H__
#include <stdint.h>
#include "mcu_cmd.h"
/*============================================================================*/
/* 宏定义 */
/*============================================================================*/
#define MCU_PKT_HEAD_0 (0x55)
#define MCU_PKT_HEAD_1 (0xAA)
#define MCU_FRAME_MAX_LEN (128)
/*============================================================================*/
/* 外部接口 */
/*============================================================================*/
/**
* @brief 组帧并写入 out(含包头与校验)
* @param out 输出缓冲
* @param out_max 缓冲最大长度
* @param cmd 命令字
* @param sn 序列号
* @param payload 数据区,可为 NULL
* @param plen 数据长度
* @return 帧总长度;<0 失败
*/
int32_t mcu_frame_pack(uint8_t *out, uint16_t out_max,
uint8_t cmd, uint8_t sn,
const uint8_t *payload, uint16_t plen);
/**
* @brief 向解析器喂入原始字节,尝试提取完整帧
* @param byte 输入字节
* @param out 输出完整帧缓冲
* @param out_len 输出帧长度
* @return 1=得到完整帧,0=继续,-1=校验错误丢弃
*/
int8_t mcu_frame_feed(uint8_t byte, uint8_t *out, uint16_t *out_len);
/**
* @brief 复位帧解析状态机
*/
void mcu_frame_parser_reset(void);
#endif /* __MCU_FRAME_H__ */
@@ -0,0 +1,141 @@
/******************************************************************************
* @file mcu_uart.c
* @brief BLE 模组与外部 MCU 的 UART 驱动(PA7 TX / PA8 RX, 9600
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.3.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
* - V1.1.0, 2026.08.07, cyWu, TX 使用静态对齐缓冲 + write()
* - V1.2.0, 2026.08.07, cyWu, 对齐供应商 demo:关闭低功耗
* - V1.3.0, 2026.08.07, cyWu, 发送与厂家一致,直接 write(data),去掉 TX 拷贝
*
* @note 串口收发期间由 mcu_bridge 的 LP target 挡住休眠;
* 板级可开 SLEEP_EN,勿在无 LP 保护时直接开 DEEP_SLEEP。
******************************************************************************/
#include "system/includes.h"
#include "asm/uart_dev.h"
#include "asm/gpio.h"
#include "mcu_uart.h"
#include "mcu_bridge.h"
#include "mcu_frame.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[MCU_UART]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#define LOG_DUMP_ENABLE
#define LOG_CLI_ENABLE
#include "debug.h"
/*============================================================================*/
/* 私有变量 */
/*============================================================================*/
static uint8_t s_initialized = 0;
static const uart_bus_t *s_uart_bus = NULL;
static u8 s_rx_cbuf[MCU_UART_RX_CBUF_SIZE] __attribute__((aligned(4)));
static u8 s_rx_tmp[MCU_UART_RX_CBUF_SIZE];
static OS_MUTEX s_tx_lock;
/*============================================================================*/
/* 私有函数 */
/*============================================================================*/
/**
* @brief UART 接收任务:阻塞读串口,喂入协议桥
* @param arg 未使用
*/
static void mcu_uart_rx_task(void *arg)
{
u32 rxcnt;
(void)arg;
log_info("rx task start");
while (1) {
if (!s_uart_bus) {
os_time_dly(10);
continue;
}
/* timeout=0:无数据时 pend,有 RX/OT 中断后唤醒 */
rxcnt = s_uart_bus->read(s_rx_tmp, sizeof(s_rx_tmp), 0);
if (rxcnt > 0) {
mcu_bridge_on_uart_rx(s_rx_tmp, (uint16_t)rxcnt);
}
}
}
/*============================================================================*/
/* 公有函数 */
/*============================================================================*/
/**
* @brief 打开 MCU 串口并创建接收任务
* @return McuUart_Ret_t
*/
McuUart_Ret_t mcu_uart_init(void)
{
struct uart_platform_data_t u_arg = {0};
if (s_initialized) {
return MCU_UART_OK;
}
u_arg.tx_pin = IO_PORTA_07;
u_arg.rx_pin = IO_PORTA_08;
u_arg.rx_cbuf = s_rx_cbuf;
u_arg.rx_cbuf_size = MCU_UART_RX_CBUF_SIZE;
u_arg.frame_length = MCU_UART_RX_CBUF_SIZE;
u_arg.rx_timeout = 10; /* ms,帧间隙产生 OT */
u_arg.isr_cbfun = NULL;
u_arg.baud = MCU_UART_BAUD;
u_arg.is_9bit = 0;
s_uart_bus = uart_dev_open(&u_arg);
if (s_uart_bus == NULL) {
log_error("init open fail");
return MCU_UART_ERR_OPEN;
}
os_mutex_create(&s_tx_lock);
os_task_create(mcu_uart_rx_task, NULL, 31, 1024, 0, "mcu_uart");
s_initialized = 1;
log_info("init ok, PA7/PA8 @ %d", MCU_UART_BAUD);
return MCU_UART_OK;
}
/**
* @brief 向 MCU 发送数据
* @param data 发送缓冲
* @param len 长度
* @return McuUart_Ret_t
* @note 与 usr_hw_uart.c 一致:uart_bus->write(data, len)
*/
McuUart_Ret_t mcu_uart_send(const uint8_t *data, uint16_t len)
{
if (!s_initialized || !s_uart_bus) {
return MCU_UART_ERR_NOT_INIT;
}
if (!data || len == 0 || len > MCU_FRAME_MAX_LEN) {
return MCU_UART_ERR_PARAM;
}
os_mutex_pend(&s_tx_lock, 0);
s_uart_bus->write((void *)data, len);
os_mutex_post(&s_tx_lock);
return MCU_UART_OK;
}
/**
* @brief 获取串口是否已初始化
* @return 1=已初始化,0=未初始化
*/
uint8_t mcu_uart_is_ready(void)
{
return s_initialized;
}
@@ -0,0 +1,62 @@
/******************************************************************************
* @file mcu_uart.h
* @brief BLE 模组与外部 MCU 的 UART 驱动接口(PA7/PA8, 9600
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.0.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
******************************************************************************/
#ifndef __MCU_UART_H__
#define __MCU_UART_H__
#include <stdint.h>
/*============================================================================*/
/* 宏定义 */
/*============================================================================*/
#define MCU_UART_BAUD (9600) /**< 协议默认波特率 */
#define MCU_UART_RX_CBUF_SIZE (512) /**< DMA 循环缓冲,须为 2 的幂 */
/* 引脚:TX=PA7(IO_PORTA_07)RX=PA8(IO_PORTA_08),在 mcu_uart.c 中配置 */
/*============================================================================*/
/* 错误码 */
/*============================================================================*/
typedef enum {
MCU_UART_OK = 0,
MCU_UART_ERR_PARAM,
MCU_UART_ERR_BUSY,
MCU_UART_ERR_TIMEOUT,
MCU_UART_ERR_NOT_INIT,
MCU_UART_ERR_OPEN,
MCU_UART_ERR_UNKNOWN
} McuUart_Ret_t;
/*============================================================================*/
/* 外部接口 */
/*============================================================================*/
/**
* @brief 打开 MCU 串口并创建接收任务
* @return McuUart_Ret_t
*/
McuUart_Ret_t mcu_uart_init(void);
/**
* @brief 向 MCU 发送数据
* @param data 发送缓冲
* @param len 长度
* @return McuUart_Ret_t
*/
McuUart_Ret_t mcu_uart_send(const uint8_t *data, uint16_t len);
/**
* @brief 获取串口是否已初始化
* @return 1=已初始化,0=未初始化
*/
uint8_t mcu_uart_is_ready(void);
#endif /* __MCU_UART_H__ */
@@ -0,0 +1,108 @@
/******************************************************************************
* @file mcu_util.c
* @brief MCU 桥通用工具实现
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.2.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
* - V1.1.0, 2026.08.07, cyWu, get_ms 改用 timer_get_ms
* - V1.2.0, 2026.08.10, cyWu, 删除空壳 timer_init
******************************************************************************/
#include "system/includes.h"
#include "asm/timer.h"
#include "mcu_util.h"
/*============================================================================*/
/* 公有函数 */
/*============================================================================*/
/**
* @brief 获取开机以来的毫秒计数
* @return 毫秒计数
* @note 直接使用系统 tick 换算的毫秒,不受低功耗拉长 usr_timer 影响
*/
uint32_t mcu_util_get_ms(void)
{
return timer_get_ms();
}
/**
* @brief 协议累加和校验
* @param frame 完整帧(含 55 AA
* @param total_len 帧总长度
* @return 校验字节
*/
uint8_t mcu_util_checksum(const uint8_t *frame, uint16_t total_len)
{
uint16_t sum = 0;
uint16_t i;
if (!frame || total_len < 4) {
return 0;
}
for (i = 2; i < (uint16_t)(total_len - 1); i++) {
sum = (uint16_t)(sum + frame[i]);
}
return (uint8_t)(sum & 0xFF);
}
/**
* @brief 大端读取 uint16
* @param buf 字节缓冲
* @return 数值
*/
uint16_t mcu_util_read_u16_be(const uint8_t *buf)
{
if (!buf) {
return 0;
}
return (uint16_t)(((uint16_t)buf[0] << 8) | buf[1]);
}
/**
* @brief 大端读取 uint32
* @param buf 字节缓冲
* @return 数值
*/
uint32_t mcu_util_read_u32_be(const uint8_t *buf)
{
if (!buf) {
return 0;
}
return ((uint32_t)buf[0] << 24)
| ((uint32_t)buf[1] << 16)
| ((uint32_t)buf[2] << 8)
| (uint32_t)buf[3];
}
/**
* @brief 大端写入 uint16
* @param buf 字节缓冲
* @param val 数值
*/
void mcu_util_write_u16_be(uint8_t *buf, uint16_t val)
{
if (!buf) {
return;
}
buf[0] = (uint8_t)(val >> 8);
buf[1] = (uint8_t)(val & 0xFF);
}
/**
* @brief 大端写入 uint32
* @param buf 字节缓冲
* @param val 数值
*/
void mcu_util_write_u32_be(uint8_t *buf, uint32_t val)
{
if (!buf) {
return;
}
buf[0] = (uint8_t)(val >> 24);
buf[1] = (uint8_t)(val >> 16);
buf[2] = (uint8_t)(val >> 8);
buf[3] = (uint8_t)(val & 0xFF);
}
@@ -0,0 +1,65 @@
/******************************************************************************
* @file mcu_util.h
* @brief MCU 桥通用工具:校验和、大端序、毫秒节拍
* @author cyWu <1917507415@qq.com>
* @date 2026.08.06
* @version V1.1.0
* @history
* - V1.0.0, 2026.08.06, cyWu, 首次发布
* - V1.1.0, 2026.08.10, cyWu, 删除空壳 timer_init
******************************************************************************/
#ifndef __MCU_UTIL_H__
#define __MCU_UTIL_H__
#include <stdint.h>
/*============================================================================*/
/* 外部接口 */
/*============================================================================*/
/**
* @brief 获取开机以来的毫秒计数
* @return 毫秒计数
* @note 基于系统 timer_get_ms,不受低功耗拉长 usr_timer 影响
*/
uint32_t mcu_util_get_ms(void);
/**
* @brief 协议累加和校验
* @param frame 完整帧(含 55 AA
* @param total_len 帧总长度
* @return 校验字节
* @note 累加范围:LEN 字段起至校验和前一字节
*/
uint8_t mcu_util_checksum(const uint8_t *frame, uint16_t total_len);
/**
* @brief 大端读取 uint16
* @param buf 字节缓冲
* @return 数值
*/
uint16_t mcu_util_read_u16_be(const uint8_t *buf);
/**
* @brief 大端读取 uint32
* @param buf 字节缓冲
* @return 数值
*/
uint32_t mcu_util_read_u32_be(const uint8_t *buf);
/**
* @brief 大端写入 uint16
* @param buf 字节缓冲
* @param val 数值
*/
void mcu_util_write_u16_be(uint8_t *buf, uint16_t val);
/**
* @brief 大端写入 uint32
* @param buf 字节缓冲
* @param val 数值
*/
void mcu_util_write_u32_be(uint8_t *buf, uint32_t val);
#endif /* __MCU_UTIL_H__ */
@@ -10,10 +10,10 @@ copy ..\..\ota.bin .
copy ..\..\anc_coeff.bin .
copy ..\..\anc_gains.bin .
..\..\isd_download.exe ..\..\isd_config.ini -tonorflash -dev br28 -boot 0x120000 -div8 -wait 300 -uboot ..\..\uboot.boot -app ..\..\app.bin -res ..\..\cfg_tool.bin tone.cfg p11_code.bin -uboot_compress -key RS-5B88.key
..\..\isd_download.exe ..\..\isd_config.ini -tonorflash -dev br28 -boot 0x120000 -div8 -wait 300 -uboot ..\..\uboot.boot -app ..\..\app.bin -res ..\..\cfg_tool.bin tone.cfg p11_code.bin -uboot_compress
:: -format all
:: -format all -key RS-5B88.key
::-reboot 2500
@rem 删除临时文件-format all