当前位置: 首页 > news >正文

嵌入式消费品商业开发需求导出与便捷调试

AI时代坚持古法手搓

客户需求(讨论出就是一句话)

按键5S,蓝牙开可发现广播,新平板可连接,60s超时回连旧设备

需求拆解

  • 蓝牙身份地址没有强制要求变跟
  • 旧平板需要超时回连,说明有状态回退
  • 常规流程配对上后只有定向广播,除非用户强制主动打破

状态流转

  • 商业开发需要涉及到为不懂技术的人演示一定要做到,所见即所得
  • PPT的示例图就是最后的效果,以后有争议就以立项时的PPT为主
  • 越形象越好,给客户留下的印象更深(客户看我们的东西就和我们到商场买衣服一样,只关心感受,方便,以及值不值,会关心技术够不够高端,不会关心后面怎么做的)

(为什么说这么多。远超平均价格的产品,对于大众来说就像奢侈品,就像是什么非洲高级原木做的梳子)

1. 质量与安全顾虑 (品质恐惧)

  • 心理预期:高端客户默认“一分钱一分货”。
  • 潜意识反应:太低的价格会引发客户怀疑产品使用了劣质原材料、技术落后或制造工艺粗糙。
  • 潜在风险:担心产品使用寿命短、维护成本高,甚至出现安全隐患,这对于追求品质生活的客户是不可接受的。

2. 服务与售后保障 (隐性成本)

  • 重视体验:相比单次购买,高端客户更看重长期的服务体验。
  • 忧虑售后:极低的价格通常意味着厂家压缩了利润空间,进而压缩了售后服务的质量。客户担心在需要支持时无法得到及时的响应,产生“购买后被抛弃”的恐惧。

3. 品牌与身份认同 (社会信号)

  • 品牌溢价:高端客户购买的产品往往是其身份、地位和品位的象征。
  • 品牌价值:太便宜的产品不仅无法提升形象,反而可能降低身份。他们购买的是高价格背后所代表的品牌积淀、社会声誉和稀缺性。

4. 信任与确定性 (避险心理)

  • 决策成本:高端客户更愿意花高价购买一种“确定性”,即不用担心产品翻车、不会浪费精力和时间去处理故障。
  • 反向恐惧:过低的价格会让他们觉得“这事儿太不踏实了”,因此宁愿选择价格合理、知名度高、风险可控的方案。

总结:对于高端客户,销售策略不应是“低价诱惑”,而应是“价值展现”。要强调产品的专属性、高品质保证、完善的售后以及能带来的增值服务。

现在的消费电子早就超过了电子的功能属性而是消费品的属性,特别是能触达女性/幼年群体的还要务必向化妆品的产品一样做有吸引力的设计。也就是功能属性的基本可以忽略不计(有时候苦哈哈coding无法理解)


方案设计

因为只是按键开新广播,先画个草图

  • 正常状态退出直接进入广播流程

rtos设计

application taskprocess优先级stack/ramresponse timeOS service
BLE taskReset state64<200ms

Button task

按键状态检测32<50ms
ble event callback一般由原厂提供ble状态机维护128

os basic service 很多时候没有被考虑到,但是作为平台基础能力,别忘了,有时候供应商代码完成度高还好各种系统服务都有,如果没有集成我们还要自己去移植,这部分工作也要在前期考虑进去


后续客户需求变更要添加一个LED

客户需求

  • 按键5S,蓝牙开可发现广播,新平板可连接,60s超时回连旧设备
  • LED显示蓝牙状态、充电状态、电源z
application taskprocess优先级stack/ramresponse timeOS service
BLE taskReset state32k<200ms

Button task

按键状态检测4k<50ms
Led taskble状态机维护4k
ble event callback一般由原厂提供ble状态机维护
workspace task
power mange task4k
debug port / logging task4k
watch dog4k
system IRQ
gpio按键检测4k
driver
flashstore key

有了这些需求后,产品才算完善起来,其他需求不在本文讨论之列,暂时不涉及


大概需求评估到位了,需要选项,前期选型主要关心的实现需求会遇到的瓶颈问题,例如屏幕,这种选项与项目经验有关,AI时代可以借助AI弥补一下。

需求选型瓶颈
60fps oled12864 显示屏ram、cpu>128M


下面进入原型的设计阶段,这时就是实际代码的编写了,原有逻辑可拆分为这两个

  1. 按键5S,蓝牙开可发现广播,新平板可连接
  2. 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上

  • 正常模式:
  1. 休眠下按键开广播,有配对,固定地址+旧IRK生成RPA,开不可发现广播,回连旧设备;无配对,开可发现广播,固定地址+新IRK生成RPA,连接成功储存IRK
  • 新设备连接模式:
  1. 按键5S,固定地址+新IRK生成RPA,开可发现广播,新平板可连接,连接成功储存IRK
  2. 在情况1后,60s超时,固定地址+旧IRK生成RPA,开不可发现广播,回连旧设备
  • 生产模式
  1. 处于正常模式,有无配对,都是,固定地址,开可发现广播。

这个时候就体现了流程图的好处了

---流程图,能够快速迭代添加功能需求,分离设计需求导入,实际代码过程


这个时候要回到最原始的生产的需求怎么办,难不成删掉?不用直接在模块函数的开头加宏判断是否跳过即可实现 流程图 回退

  • 生产模式
  1. 处于正常模式,有无配对,都是,固定地址,开可发现广播。
// 总功能宏 #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 IRKrand_new_irk
use_stored_irk
rand_relapce_stored_irk
press start discover adv。。。

这种表格就可以交给项目去评估具体的项目开发难度并且排期

这样就是需求的快速导入,合规所有的开发流程,产品项目方可以只对流程图的细节

coding只需要实现feature 模块子函数,实现高效的需求、流程、编码隔离,后期debug也方便


一般公司会维护多个产品的代码,新的需求导入客户需要快速体验,有和没有的差距,这一部分coding是没办法自己替代的但是出了一版又一版,不仅麻烦,自己也容易糊涂,其他人review起来也困难。使用逻辑化的语言能够使得大家对代码状态有清晰的认知,不会说责任都在coding身上

http://www.jsqmd.com/news/668877/

相关文章:

  • SpringBoot+Vue企业人事管理系统源码+论文
  • 5G手机第一次联网时,基站是怎么知道你在哪个方向的?聊聊PRACH Occasion与波束的‘暗号’映射
  • Substance 3D Painter Pt 2025 v11.0.1详细图文安装教程
  • 山东大学软件学院项目实训-创新实训-计科智伴(一)——个人博客(后端搭建)
  • 常识不是知识,而是推理操作系统:解密AGI底层常识架构的5层抽象模型与2个已被验证的轻量化嵌入方案
  • 第 4 篇 - Redis 数据类型总览:5 种核心类型
  • 10分钟掌握Fideo:跨平台直播录制终极指南
  • SpringBoot+Vue基于爬虫的在线新闻聚合平台源码+论文
  • MongoPlus 教程
  • 2026奇点智能技术大会核心洞察(AGI-VR协同架构白皮书首发)
  • 【2026奇点智能技术大会权威内参】:AGI人才争夺战已打响,HR必须掌握的5大精准匹配模型与实时评估框架
  • 如何同步SQL冗余字段信息_通过触发器实现自动反向填充
  • 从模糊到通透:CSS filter与backdrop-filter打造沉浸式视觉体验
  • 告别ThreadLocal!Spring WebFlux中如何用Reactor Context优雅传递用户Token?
  • 湖南华商文化商务有限公司官网介绍
  • 还在用简单 AI 对话?Spring AI 自定义工具 + MCP 协议直接打通外部服务!
  • SpringBoot+Vue编程语言学习辅导网站源码+论文
  • ImageMagick进阶玩法:结合Windows批处理,自动备份并生成网站缩略图与社交分享图
  • 打造简易Agent,深度解析LLM与工具的完美协作!
  • 深入AUTOSAR内存管理:拆解vLinkGen如何配置数据段的多阶段初始化(Early/One/HardReset)
  • async,future,packaged_task,promise
  • 从毛玻璃到沉浸式界面:探索CSS filter与backdrop-filter的进阶应用
  • 别再只会用‘w‘和‘r‘了!Matlab fopen函数权限参数全解析(含编码与字节序)
  • 项目实训博客2 刻画能力画像:动态用户与岗位画像建模
  • 怎样设计一块独特的牌匾?
  • 深度空间装饰回头客多
  • Notion 白屏故障排查:从客户端到浏览器的全方位修复指南
  • 手机无限重启怎么办
  • [MYSQL/K8s] 基于 Kubenetes 集群安装 MYSQL
  • 实战指南|3类高频软件漏洞,从识别到修复一步到位