嵌入式消费品商业开发需求导出与便捷调试
AI时代坚持古法手搓
客户需求(讨论出就是一句话)
按键5S,蓝牙开可发现广播,新平板可连接,60s超时回连旧设备
需求拆解
- 蓝牙身份地址没有强制要求变跟
- 旧平板需要超时回连,说明有状态回退
- 常规流程配对上后只有定向广播,除非用户强制主动打破
状态流转
- 商业开发需要涉及到为不懂技术的人演示一定要做到,所见即所得
- PPT的示例图就是最后的效果,以后有争议就以立项时的PPT为主
- 越形象越好,给客户留下的印象更深(客户看我们的东西就和我们到商场买衣服一样,只关心感受,方便,以及值不值,会关心技术够不够高端,不会关心后面怎么做的)
(为什么说这么多。远超平均价格的产品,对于大众来说就像奢侈品,就像是什么非洲高级原木做的梳子)
1. 质量与安全顾虑 (品质恐惧)
- 心理预期:高端客户默认“一分钱一分货”。
- 潜意识反应:太低的价格会引发客户怀疑产品使用了劣质原材料、技术落后或制造工艺粗糙。
- 潜在风险:担心产品使用寿命短、维护成本高,甚至出现安全隐患,这对于追求品质生活的客户是不可接受的。
2. 服务与售后保障 (隐性成本)
- 重视体验:相比单次购买,高端客户更看重长期的服务体验。
- 忧虑售后:极低的价格通常意味着厂家压缩了利润空间,进而压缩了售后服务的质量。客户担心在需要支持时无法得到及时的响应,产生“购买后被抛弃”的恐惧。
3. 品牌与身份认同 (社会信号)
- 品牌溢价:高端客户购买的产品往往是其身份、地位和品位的象征。
- 品牌价值:太便宜的产品不仅无法提升形象,反而可能降低身份。他们购买的是高价格背后所代表的品牌积淀、社会声誉和稀缺性。
4. 信任与确定性 (避险心理)
- 决策成本:高端客户更愿意花高价购买一种“确定性”,即不用担心产品翻车、不会浪费精力和时间去处理故障。
- 反向恐惧:过低的价格会让他们觉得“这事儿太不踏实了”,因此宁愿选择价格合理、知名度高、风险可控的方案。
总结:对于高端客户,销售策略不应是“低价诱惑”,而应是“价值展现”。要强调产品的专属性、高品质保证、完善的售后以及能带来的增值服务。
现在的消费电子早就超过了电子的功能属性而是消费品的属性,特别是能触达女性/幼年群体的还要务必向化妆品的产品一样做有吸引力的设计。也就是功能属性的基本可以忽略不计(有时候苦哈哈coding无法理解)
方案设计
因为只是按键开新广播,先画个草图
- 正常状态退出直接进入广播流程
rtos设计
| application task | process | 优先级 | stack/ram | response time | OS service |
| BLE task | Reset state | 中 | 64 | <200ms | |
Button task | 按键状态检测 | 高 | 32 | <50ms | |
| ble event callback一般由原厂提供 | ble状态机维护 | 低 | 128 |
os basic service 很多时候没有被考虑到,但是作为平台基础能力,别忘了,有时候供应商代码完成度高还好各种系统服务都有,如果没有集成我们还要自己去移植,这部分工作也要在前期考虑进去
后续客户需求变更要添加一个LED
客户需求
- 按键5S,蓝牙开可发现广播,新平板可连接,60s超时回连旧设备
- LED显示蓝牙状态、充电状态、电源z
| application task | process | 优先级 | stack/ram | response time | OS service |
| BLE task | Reset state | 中 | 32k | <200ms | |
Button task | 按键状态检测 | 高 | 4k | <50ms | |
| Led task | ble状态机维护 | 低 | 4k | ||
| ble event callback一般由原厂提供 | ble状态机维护 | 低 | |||
| workspace task | |||||
| power mange task | 4k | ||||
| debug port / logging task | 4k | ||||
| watch dog | 4k | ||||
| system IRQ | |||||
| gpio | 按键检测 | 4k | |||
| driver | |||||
| flash | store key |
有了这些需求后,产品才算完善起来,其他需求不在本文讨论之列,暂时不涉及
大概需求评估到位了,需要选项,前期选型主要关心的实现需求会遇到的瓶颈问题,例如屏幕,这种选项与项目经验有关,AI时代可以借助AI弥补一下。
| 需求 | 选型瓶颈 |
| 60fps oled12864 显示屏 | ram、cpu>128M |
下面进入原型的设计阶段,这时就是实际代码的编写了,原有逻辑可拆分为这两个
- 按键5S,蓝牙开可发现广播,新平板可连接
- 60s超时回连旧设备
https://www.doubao.com/thread/we80b6591467f8eea
(AI 生成的代码框架)
typedef enum { BLE_STANDBY, // 待机 BLE_INIT, // 初始化 BLE_ADVERTISING,// 广播 BLE_CONNECTED // 已连接 } ble_state_t; static ble_state_t s_ble_state = BLE_STANDBY; // 外部任何地方都能调用,获取当前 BLE 状态 ble_state_t ble_get_state(void) { return s_ble_state; } void ble_task(void *arg) { // 一开始 Standby s_ble_state = BLE_STANDBY; for (;;) { // 阻塞等待事件(Task Notify 核心) ulTaskNotifyTake(pdTRUE, portMAX_DELAY); switch (s_ble_state) { case BLE_STANDBY: // 收到启动通知 → 进 Init ble_init(); s_ble_state = BLE_INIT; break; case BLE_INIT: // Init 完成 → 开广播 ble_start_advertising(); s_ble_state = BLE_ADVERTISING; break; case BLE_ADVERTISING: // 收到连接事件 → 进连接态 s_ble_state = BLE_CONNECTED; on_connected(); break; case BLE_CONNECTED: // 断开 → 回 Standby 或重启广播 if (disconnect_event) { s_ble_state = BLE_STANDBY; } break; } } } #include "FreeRTOS.h" #include "task.h" // -------------------------- // 按键事件类型(对应流程图) // -------------------------- typedef enum { BTN_EVENT_NONE, BTN_SINGLE_PRESS, BTN_DOUBLE_PRESS, BTN_LONG_PRESS } btn_event_t; // -------------------------- // 任务句柄 // -------------------------- static TaskHandle_t btn_task_handle = NULL; static TaskHandle_t ble_task_handle = NULL; // 蓝牙任务句柄(你已有的) // -------------------------- // 外部获取按键状态接口(可选) // -------------------------- btn_event_t btn_get_last_event(void); // -------------------------- // 硬件按键检测(简化版,你自己实现) // -------------------------- static bool btn_is_pressed(void) { // 这里替换成你的 GPIO 读值逻辑 return false; } // -------------------------- // 按键任务主循环(完全对应你的流程图) // -------------------------- void button_task(void *arg) { btn_event_t last_event = BTN_EVENT_NONE; TickType_t press_start_tick = 0; uint8_t press_count = 0; for (;;) { // 1. 等待按键中断/定时器通知(Task Notify 核心) ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 2. Button Pressure check(进入检测逻辑) if (btn_is_pressed()) { press_start_tick = xTaskGetTickCount(); press_count++; // 等待按键释放 while (btn_is_pressed()) { vTaskDelay(pdMS_TO_TICKS(10)); } TickType_t press_duration = xTaskGetTickCount() - press_start_tick; // 3. 分支判断:长按/双击/单击 if (press_duration > pdMS_TO_TICKS(1000)) { // 长按阈值1s // long press → Process Reset State last_event = BTN_LONG_PRESS; xTaskNotifyGive(user_ble_task_handle); } else { // 检查是否双击(在300ms内再次按下) vTaskDelay(pdMS_TO_TICKS(300)); if (btn_is_pressed()) { // double press → Process 1 last_event = BTN_DOUBLE_PRESS; Process_1(); // 你的双击处理函数 } else { // single press → Process 2 last_event = BTN_SINGLE_PRESS; Process_2(); // 你的单击处理函数 } } } } } // -------------------------- // 按键中断通知任务(ISR 版本) // -------------------------- void EXTI_Button_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 通知按键任务开始检测 vTaskNotifyGiveFromISR(btn_task_handle, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } // -------------------------- // 任务创建 // -------------------------- void button_task_init(TaskHandle_t ble_task_hdl) { ble_task_handle = ble_task_hdl; xTaskCreate( button_task, "btn_task", 1024, NULL, 2, &btn_task_handle ); } void user_ble_task(void *arg) { for (;;) { // 等待按键长按通知 ulTaskNotifyTake(pdTRUE, portMAX_DELAY); ble_state_t current = ble_get_state(); switch (current) { case BLE_STANDBY: // 直接通知开可发现广播 xTaskNotifyGive(ble_task_handle_discoverable_adv); s_ble_state = BLE_ADVERTISING; break; case BLE_INIT: // 等待进入Standby while (ble_get_state() != BLE_STANDBY) { vTaskDelay(pdMS_TO_TICKS(10)); } // 通知开可发现广播 xTaskNotifyGive(ble_task_handle_discoverable_adv); s_ble_state = BLE_ADVERTISING; break; case BLE_ADVERTISING: // 先关广播 ble_stop_advertising(); // 通知开可发现广播 xTaskNotifyGive(ble_task_handle_discoverable_adv); break; case BLE_CONNECTED: // 断连 ble_disconnect(); // 等待断开完成 while (ble_get_state() == BLE_CONNECTED) { vTaskDelay(pdMS_TO_TICKS(10)); } // 通知开可发现广播 xTaskNotifyGive(ble_task_handle_discoverable_adv); s_ble_state = BLE_ADVERTISING; break; } } }流程图在迭代一下就长这样了
这个作为测试部门同事使用,用来排列组合出testcase测试最终效果
最后实际验证发现还是会回连到原有设备上。因为每开一次广播地址不变就会回连,所以要使用
固定地址+IRK的方式区分新旧连接,所以需求又被细化到了IRK上
- 正常模式:
- 休眠下按键开广播,有配对,固定地址+旧IRK生成RPA,开不可发现广播,回连旧设备;无配对,开可发现广播,固定地址+新IRK生成RPA,连接成功储存IRK
- 新设备连接模式:
- 按键5S,固定地址+新IRK生成RPA,开可发现广播,新平板可连接,连接成功储存IRK
- 在情况1后,60s超时,固定地址+旧IRK生成RPA,开不可发现广播,回连旧设备
- 生产模式
- 处于正常模式,有无配对,都是,固定地址,开可发现广播。
这个时候就体现了流程图的好处了
---流程图,能够快速迭代添加功能需求,分离设计需求导入,实际代码过程
这个时候要回到最原始的生产的需求怎么办,难不成删掉?不用直接在模块函数的开头加宏判断是否跳过即可实现 流程图 回退
- 生产模式
- 处于正常模式,有无配对,都是,固定地址,开可发现广播。
// 总功能宏 #define FEATURE_NEW_IRK_EN 1 // 1=开启 0=关闭整个IRK模块 // IRK 内部数据结构 typedef struct { uint8_t irk[16]; // 身份解析密钥 uint8_t addr[6]; // 绑定地址 bool is_paired; // 是否已配对 bool flag; // 运行时功能开关(从宏初始化) } IRKData; // 事件回调类型定义 typedef void (*irk_callback_t)(void); // 面向对象封装:Data + Event + Feature Flag typedef struct { // 数据区 IRKData data; // 事件/方法区(你要的三个函数) void (*rand_new_irk)(void); void (*use_stored_irk)(void); void (*rand_replace_stored_irk)(void); } IRK_reset_feature; // 全局唯一IRK功能对象 IRK_reset_feature irk_feature = { // 数据初始化:flag 从宏赋值 .data = { .flag = FEATURE_NEW_IRK_EN, }, // 方法绑定 .rand_new_irk = irk_rand_new_irk, .use_stored_irk = irk_use_stored_irk, .rand_replace_stored_irk = irk_rand_replace_stored_irk, }; // --------------------------------------- // 1. 生成新IRK // --------------------------------------- static void irk_rand_new_irk(void) { // 入口 feature 判断 if (!FEATURE_NEW_IRK_EN || !irk_feature.data.flag) { return; } // 你的逻辑:生成随机IRK // ... } // --------------------------------------- // 2. 使用存储的IRK // --------------------------------------- static void irk_use_stored_irk(void) { // 入口 feature 判断 if (!FEATURE_NEW_IRK_EN || !irk_feature.data.flag) { return; } // 你的逻辑:使用Flash存储的IRK // ... } // --------------------------------------- // 3. 随机替换存储IRK // --------------------------------------- static void irk_rand_replace_stored_irk(void) { // 入口 feature 判断 if (!FEATURE_NEW_IRK_EN || !irk_feature.data.flag) { return; } // 你的逻辑:随机新IRK并覆盖存储 // ... } // 外部调用示例 irk_feature.rand_new_irk(); irk_feature.use_stored_irk(); irk_feature.rand_replace_stored_irk();| feature | 函数 |
| new IRK | rand_new_irk use_stored_irk rand_relapce_stored_irk |
| press start discover adv | 。。。 |
这种表格就可以交给项目去评估具体的项目开发难度并且排期
这样就是需求的快速导入,合规所有的开发流程,产品项目方可以只对流程图的细节
coding只需要实现feature 模块子函数,实现高效的需求、流程、编码隔离,后期debug也方便
一般公司会维护多个产品的代码,新的需求导入客户需要快速体验,有和没有的差距,这一部分coding是没办法自己替代的但是出了一版又一版,不仅麻烦,自己也容易糊涂,其他人review起来也困难。使用逻辑化的语言能够使得大家对代码状态有清晰的认知,不会说责任都在coding身上
