Matter Silabs 平台通用应用行为详解:LCD 屏幕、按键与 LED 状态机
【免费下载链接】connectedhomeipMatter (formerly Project CHIP) creates more connections between more objects, simplifying development for manufacturers and increasing compatibility for consumers, guided by the Connectivity Standards Alliance.项目地址: https://gitcode.com/GitHub_Trending/co/connectedhomeip
Matter(原 Project CHIP)SDK 中 Silicon Labs(Silabs)平台的所有示例应用共享一套由examples/platform/silabs目录实现的“通用行为”:LCD 三屏切换与二维码展示、BTN0/BTN1 双按键的配网与恢复出厂流程、LED0/LED1 双 LED 的配网状态指示。本文基于官方文档docs/platforms/silabs/silabs_common_app_behavior.md完整展开这套行为规范,并结合BaseApplication、SilabsLCD、LEDWidget等源码逐条印证其底层实现,帮助你在移植或调试 Silabs Matter 示例时,快速理解每一个按钮、屏幕与 LED 闪烁模式背后的状态机。
一、总体架构:通用行为代码在哪里
文档开宗明义:所有 Silabs 示例应用(lighting-app、all-clusters-app、contact-sensor-app 等)的通用功能实现,位于示例平台目录 examples/platform/silabs 下。核心的几个文件为:
| 文件 | 职责 |
|---|---|
| BaseApplication.h / BaseApplication.cpp | 应用基类:事件队列、按键处理、工厂重置、状态 LED 模式、二维码输出、LCD 状态屏刷新 |
| display/lcd.h / display/lcd.cpp | SilabsLCD类:屏幕枚举、Demo/Status/QRCode 三屏绘制与循环切换 |
| LEDWidget.h / LEDWidget.cpp | LED 抽象:Set/Blink/Animate闪烁时序控制 |
| main.cpp / MatterConfig.cpp | app_init()入口,调用SilabsMatterConfig::AppInit()启动 Matter 应用 |
从源码结构看,这套行为以FreeRTOS 任务 + CMSIS-OS 消息队列事件驱动的方式组织(见 BaseApplication.cpp):
- App 任务:
StartAppTask()创建一个默认 4096 字节栈(APP_TASK_STACK_SIZE)、默认 10 个事件容量(APP_EVENT_QUEUE_SIZE)的静态内存任务与消息队列,所有 UI/按键/定时器回调都通过PostEvent()把AppEvent投递进队列,由任务统一DispatchEvent()分发,避免在不同线程上下文中直接操作 CHIP 栈; - 两个 CMSIS 软件定时器:
sFunctionTimer(一次性,按键功能/工厂重置计时)与sLightTimer(周期 10ms 的kLightTimerPeriod,驱动 LED 动画); - 初始化链路:
BaseApplication::Init()依次调用BaseInit()(注册平台事件处理器、创建任务与定时器、初始化 LCD、输出初始二维码、读取配网状态、注册FabricTable::Delegate)再调用各示例实现的虚函数AppInit()(见 BaseApplication.cpp)。
理解了这个“事件队列 + 定时器”骨架,下面所有按钮动作、LED 动画、LCD 刷新就都是这条链路上的不同事件处理分支。
二、LCD 屏幕:三个窗口与循环切换
2.1 三屏结构:Demo / Status / QRCode
文档指出:支持 LCD 的开发套件上,应用拥有三个独立窗口,按BTN0可在三者之间循环;当应用 UI 更新而当前正停在其他窗口时,LCD 会自动切换到对应窗口。
源码中这三个窗口对应SilabsLCD::Screen_e枚举(lcd.h):
typedef enum screen { DemoScreen = 0, // 应用 UI(每个示例自定义) StatusScreen, // 设备状态屏 #if SL_MATTER_QR_CODE_ENABLED QRCodeScreen, // 配网二维码屏 #endif CycleScreen, InvalidScreen, } Screen_e;切换逻辑在SilabsLCD::CycleScreens()(lcd.cpp):当前屏加一,超过最后一屏则回卷到DemoScreen。按键触发路径为BaseApplication::ButtonHandler()中对未配网/已配网均会执行的PostUpdateDisplayEvent(SilabsLCD::Screen_e::CycleScreen)(BaseApplication.cpp),事件最终由UpdateDisplayHandler()处理:收到CycleScreen时调用UpdateDisplay()(刷新状态屏数据并CycleScreens()),收到具体屏名时直接SetScreen()(BaseApplication.cpp)。
LCD 硬件层面:SilabsLCD::Init()依次使能板载显示屏(sl_board_enable_display())、初始化 DMD 显示驱动、初始化 glib 2D 图形上下文(白底黑字),并初始化 128×128 点阵(LCD_SIZE 128,见 lcd.cpp)。这些功能受SL_MATTER_DISPLAY_ENABLED(总开关)与SL_MATTER_QR_CODE_ENABLED(二维码屏)两个编译开关保护,无 LCD 的套件(如部分 ESP 风格或最小化构建)关闭后即可完全剔除这部分代码。
2.2 应用 UI(DemoScreen)
每个示例应用有自己的“应用 UI”,用于可视化该示例的应用状态(如灯的开关/亮度)。DemoScreen的默认绘制是 display/demo-ui.c 中的demoUIDisplayApp();应用也可以注册自定义绘制回调:SilabsLCD::SetCustomUI(cb)之后,WriteDemoUI()会优先调用该回调而不是默认画面(lcd.cpp)。各示例具体的 UI 内容需参考对应示例的文档(如docs/platforms/silabs/下 Getting Started 与examples/lighting-app等示例的 README)。
2.3 状态屏(StatusScreen):字段从哪来
文档给出的状态屏字段表完整保留如下:
OpenThread 与 Wi-Fi 设备共有信息:
| LCD UI | 说明 |
|---|---|
| # fabrics | 设备上已配网的 fabric 数量 |
| Connected | 设备是否已连接到 OpenThread 或 Wi-Fi 网络 |
| Advertising | 设备当前是否正在广播开放的配网窗口 |
| Is ICD | 设备是否为间歇连接设备(Intermittently Connected device) |
OpenThread 设备特有:
| LCD UI | 说明 |
|---|---|
| PANID | 已配置 OpenThread 网络的 PANID |
| OT Type | OpenThread 设备类型(FTD / MTD) |
Wi-Fi 设备特有:
| LCD UI | 说明 |
|---|---|
| SSID | 已连接 Wi-Fi 网络的 SSID |
文档原文标注:PANID 信息尚未打印到 LCD、SSID 信息尚未打印到 LCD、ICD 状态屏支持“尚待完成”。注意这与当前仓库源码已有差异:
BaseApplication::UpdateLCDStatusScreen()(BaseApplication.cpp)现在已经通过NetworkCommissioning::GetConnectedNetwork()取到 SSID 并拷入status.networkName,WriteStatus()也会绘制 SSID/PANID 两行,并依据ICDConfigurationData输出ICD : SIT/LIT(lcd.cpp)。换言之,文档中的两条 “not yet printed” 注释属于历史遗留,当前实现中这些字段已经在状态屏呈现;而 “ICD 状态屏支持待完成” 指的是 ICD 设备专属的完整状态展示,状态屏对 ICD 的支持目前主要体现在 ICD 模式标识上。
各字段的数据来源在UpdateLCDStatusScreen()中可逐一对应:
chip::DeviceLayer::PlatformMgr().LockChipStack(); #ifdef SL_WIFI enabled = ConnectivityMgr().IsWiFiStationEnabled(); attached = ConnectivityMgr().IsWiFiStationConnected(); // 从已连接网络对象中取出 SSID 拷贝到 status.networkName #endif #if CHIP_ENABLE_OPENTHREAD enabled = ConnectivityMgr().IsThreadEnabled(); attached = ConnectivityMgr().IsThreadAttached(); #endif status.connected = enabled && attached; status.advertising = chip::Server::GetInstance().GetCommissioningWindowManager() .IsCommissioningWindowOpen(); status.nbFabric = chip::Server::GetInstance().GetFabricTable().FabricCount(); ... chip::DeviceLayer::PlatformMgr().UnlockChipStack(); slLCD.SetStatus(status);要点:
- Connected = enabled && attached:对 Wi-Fi 即“站点已使能且已关联”,对 Thread 即“Thread 已使能且已附着”;
- Advertising直接查询
CommissioningWindowManager::IsCommissioningWindowOpen(),因此配网窗口一开,状态屏的 Advertising 立即变 Y; - 状态屏的自动刷新:
OnPlatformEvent()监听到kThreadConnectivityChange/kInternetConnectivityChange时,若当前正在显示StatusScreen,就PostUpdateDisplayEvent(StatusScreen)触发重绘(BaseApplication.cpp)——这就是文档所说“UI 更新时 LCD 自动切换/刷新”的机制之一。 - 整个读取过程通过
LockChipStack()/UnlockChipStack()保护,因为 App 任务与 CHIP 栈任务并发运行。
DisplayStatus_t结构体(lcd.h)即文档表格的内存映射:nbFabric、connected、networkName(Wi-Fi SSID)、advertising、icdMode。
2.4 二维码屏(QRCodeScreen)
文档说明:二维码屏显示的是可用于 BLE 配网与 Basic Commissioning Mode(基本配网模式)的默认二维码,其编码内容遵循 Matter 规范定义;并提醒Basic Commissioning Mode 不如 Enhanced Commissioning Mode 安全,不推荐使用。
源码侧的生成链路:
BaseApplication::OutputQrCode(bool refreshLCD)(BaseApplication.cpp)通过Provision::Manager::GetInstance().GetStorage().GetSetupPayload(setupPayload)取出 Base38 格式的 Setup Payload;- 若
refreshLCD为真且开启二维码,则slLCD.SetQRCode(...)+slLCD.ShowQRCode(true)切到QRCodeScreen绘制; - 同时
PrintQrCodeURL(setupPayload)把二维码 URL 打印到日志——这正是文档中“按键短按后在 Log 中打印 initial 与 BCM 配网二维码”的实现; - 绘制使用
qrcodegen库:版本 4 二维码、每个模块 3 像素、误差等级 LOW([lcd.cpp](https://link.gitcode.com/i/77dde88e58994f08b08c07be17443d6a#L36-L38, L260-L289))。
两个关键触发点:
- 启动时:
BaseInit()末尾调用OutputQrCode(true),上电后 LCD 默认就显示二维码屏(BaseApplication.cpp); - 配网窗口关闭时自动回退:
BaseApplicationDelegate::OnCommissioningWindowClosed()中,若设备已配网且当前停在QRCodeScreen,会PostUpdateDisplayEvent(DemoScreen)自动切回应用 UI(BaseApplication.cpp)。
三、按键:操作键与应用键
3.1 按键定义与硬件差异
文档规定所有示例应用按两个按键设计:
- BTN0 = 操作键(Operation Button);
- BTN1 = 应用键(Application Button),各示例定义其专属功能(详见各示例文档)。
源码中APP_FUNCTION_BUTTON 0、APP_ACTION_BUTTON 1([BaseApplication.cpp](https://link.gitcode.com/i/ca0c78e6469912e97aebe0a118294e41#L33, L117))与之对应。文档同时给出硬件注意事项,此处完整保留:
SparkFun 开发套件(BRD2704A)没有任何按键;部分套件只支持按键或只支持 LED——默认配置以按键支持为准,仅有 LED 的套件默认配置为应用 LED。
3.2 操作键(BTN0)行为:短按与长按
文档给出的 BTN0 动作表:
| 执行方式 | 行为 |
|---|---|
| 短按释放(Press and Release) | ① 若设备尚未配网,开始以快速模式广播 30 秒;30 秒后切换为较慢间隔广播;15 分钟后停止广播。② 在日志中打印 initial 与 BCM 配网二维码 |
| 按压并持有 6 秒 | 工厂重置(Factory Reset)设备 |
广播“30 秒快播 → 慢速间隔 → 15 分钟停止”是 Matter 规范对 BLE 广播的标准时序,由 CHIP 栈的配网窗口机制实现;示例应用只负责“开窗”这个动作。
源码中 6 秒长按的构成是两个 3 秒超时(BaseApplication.cpp):
#define FACTORY_RESET_TRIGGER_TIMEOUT 3000 // 按住 3 秒触发“工厂重置序列” #define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 // 触发后的 3 秒取消窗口完整状态机(ButtonHandler+FunctionTimerEventHandler,[BaseApplication.cpp](https://link.gitcode.com/i/ca0c78e6469912e97aebe0a118294e41#L392-L410, L590-L649)):
- 按下(
SilabsPlatform::ButtonAction::ButtonPressed):StartFunctionTimer(FACTORY_RESET_TRIGGER_TIMEOUT),3 秒一次性计时开始; - 3 秒内释放:
sIsFactoryResetTriggered仍为假,走“短按”分支:CancelFunctionTimer();- 若未配网(Wi-Fi 查
ConnectivityMgr().IsWiFiStationProvisioned(),其余查sIsProvisioned):锁住 CHIP 栈调用GetCommissioningWindowManager().OpenBasicCommissioningWindow()打开基本配网窗口——这会启动 BLE 广播; - 若已配网:打印 "Network is already provisioned, Ble advertisement not enabled";ICD 构建下还会通过
ICDNotifier::NotifyNetworkActivityNotification()临时声明网络活动; OutputQrCode(false)打印二维码到日志;PostUpdateDisplayEvent(CycleScreen)循环 LCD 屏。
- 按住满 3 秒(计时器到期):
FunctionEventHandler发现sIsFactoryResetTriggered == false,调用StartFactoryResetSequence():再启动 3 秒的取消窗口计时,置位sIsFactoryResetTriggered,状态 LED 切换为 500ms 均匀闪烁(sStatusLED.Blink(500))提示“重置已启动”; - 在取消窗口内松开:
CancelFactoryResetSequence(),取消重置,恢复原 LED 模式; - 继续按住满 3+3=6 秒:第二次计时器到期时
sIsFactoryResetTriggered已为真,直接ScheduleFactoryReset()执行工厂重置。
ScheduleFactoryReset()的实现(BaseApplication.cpp)值得注意:它并非逐键删除 fabric,而是走ConfigurationMgr().InitiateFactoryReset(),借助 Silabs NVM3 驱动整段删除 KVS 分区——源码注释明确说明这比Server::ScheduleFactoryReset()逐 Key 删除更快,并顺带移除 Wi-Fi 场景下的 Matter DNS-SD 服务广告。
应用键(BTN1)文档将其留给各示例自行定义(例如灯光应用用于控制灯),通用层只保留钩子:ScheduleFactoryReset()中若检测到APP_ACTION_BUTTON(即 BTN1)同时被按下,会调用Provision::Manager::SetProvisionRequired(true)标记需要重新配网。
四、LED:状态 LED 与应用 LED
4.1 双 LED 约定
文档约定:所有示例应用按两个 LED 设计——LED0 为状态 LED(Status LED),LED1 为应用 LED(Application LED);并在硬件受限时给出降级规则(只有按键的套件默认启用按键配置,只有 LED 的套件默认启用应用 LED)。
通用层通过BaseApplication::LinkAppLed(LEDWidget*)/UnlinkAppLed()把应用 LED 挂到基类上下文,使两枚 LED 的动画在同一LightEventHandler()中同步推进(BaseApplication.h)。状态 LED 实例sStatusLED仅在组件目录确认SL_CATALOG_SIMPLE_LED_LED1_PRESENT(即ENABLE_WSTK_LEDS且 LED1 存在)时启用,编号SYSTEM_STATE_LED = 0([BaseApplication.cpp](https://link.gitcode.com/i/ca0c78e6469912e97aebe0a118294e41#L114-L116, L135-L137))——这解释了“部分套件无 LED”时的编译期剔除。
4.2 状态 LED 的 5 种状态
文档的状态 LED 状态表完整如下:
| 状态 | 含义 |
|---|---|
| Short Flash On(50ms 亮 / 950ms 灭) | 设备处于未配网(未配对)状态,等待配网应用连接 |
| Rapid Even Flashing(100ms 亮 / 100ms 灭) | 设备未配网,但已有配网应用通过 BLE 连接 |
| Short Flash Off(950ms 亮 / 50ms 灭) | 设备已完全配网,但尚未具备完整的 Thread 网络或服务连通性 |
| Solid On | 设备已完全配网,且具备完整的 Thread 网络与服务连通性 |
| Long Even Flashing(500ms 亮 / 500ms 灭) | 工厂重置流程已启动 |
这些模式与源码严格对应。ActivateStatusLedPatterns()(BaseApplication.cpp)中按优先级判断:
if (sIsProvisioned && sIsEnabled) { if (sIsAttached) sStatusLED.Set(true); // Solid On else sStatusLED.Blink(950, 50); // Short Flash Off } else if (sHaveBLEConnections) { sStatusLED.Blink(100, 100); // Rapid Even Flashing } else { sStatusLED.Blink(50, 950); // Short Flash On }其中sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0),即“BLE 有连接”这一行由栈内 BLE 连接数直接判定。工厂重置的 500ms 均匀闪烁则来自StartFactoryResetSequence()的sStatusLED.Blink(500)。
驱动节拍是 10ms 的周期定时器sLightTimer(kLightTimerPeriod = pdMS_TO_TICKS(10)),LightEventHandler()每次先(在非 ICD 构建下)用TryLockChipStack()非阻塞锁刷新sIsProvisioned/sIsEnabled/sIsAttached/sHaveBLEConnections,再调用ActivateStatusLedPatterns()与两枚 LED 的Animate()(BaseApplication.cpp)。使用非阻塞锁是为了在 CHIP 任务执行长加密操作时不阻塞 UI 动画。
4.3 ICD 构建下的 LED 差异(源码级补充)
从源码结构看,文档未展开的一个重要细节:上述“持续轮询刷新 LED 状态”的分支被#if !(CHIP_CONFIG_ENABLE_ICD_SERVER)包裹。也就是说ICD(间歇连接设备)构建不常态运行 LED 状态机,而是按需启停sLightTimer:
- 工厂重置序列启动/取消时
StartStatusLEDTimer()/StopStatusLEDTimer(); - Identify 集群的
OnIdentifyStart/OnIdentifyStop、Trigger Effect 开始/完成时同样启停([BaseApplication.cpp](https://link.gitcode.com/i/ca0c78e6469912e97aebe0a118294e41#L729-L793, L822-L895))。
这与 ICD“大部分时间休眠”的功耗模型一致:LED 动画只在需要提示用户(重置中、Identify 中)时才消耗计时与唤醒资源。若你的示例启用了CHIP_CONFIG_ENABLE_ICD_SERVER,状态 LED 的日常配网状态指示(上表前四种)将不会自动点亮,这是设计使然而非缺陷。
4.4 应用 LED
文档将应用 LED 的语义留给各示例定义(例如用亮度同步灯亮、用闪烁提示动作结果)。通用层只负责:应用通过LinkAppLed()注册后,LightEventHandler()每 10ms 会同时推进sAppActionLed->Animate(),保证应用 LED 动画与状态 LED 动画时钟一致;不需要应用 LED 的示例可不链接,基类对sAppActionLed == nullptr已做保护。
五、把文档与源码串起来:一次典型交互的完整调用链
以“未配网设备 + 短按 BTN0”为例,把本文涉及的机制串成一条可验证链路:
- 板级按钮 ISR 把
ButtonPressed/ButtonReleased包装成AppEvent投递队列; ButtonHandler():按下 →StartFunctionTimer(3000);释放 → 取消计时器,OpenBasicCommissioningWindow()(BLE 开始广播),OutputQrCode(false)打印二维码 URL,PostUpdateDisplayEvent(CycleScreen);- LCD 事件
UpdateDisplayHandler()切屏:SetScreen()按屏名分派到WriteDemoUI()/WriteStatus()/WriteQRCode(); - 状态 LED 在 10ms 节拍下检测到“未配网且无 BLE 连接”进入 50/950ms 闪烁;配网应用通过 BLE 连上后(
NumBLEConnections() != 0)自动变为 100/100ms 快闪; - 配网完成、
OnCommissioningWindowClosed()触发后,LCD 若停在二维码屏会自动切回应用 UI;OnFabricCommitted()(FabricCount() == 1)把UpdateCommissioningStatus(true),LED 进入 Solid On(已附着网络时)。
反向流程同样闭环:最后一个 fabric 被删除时,OnFabricRemoved()触发UpdateCommissioningStatus(false)与DoProvisioningReset()——清理 Thread 栈、清除 Wi-Fi 站点配置、强制保存 NVM3 KeyMap,并重新打开基本配网窗口(BaseApplication.cpp),设备回到等待配网的初始状态。
六、参考文件索引
| 类别 | 路径 |
|---|---|
| 官方行为文档 | docs/platforms/silabs/silabs_common_app_behavior.md |
| Silabs 平台文档索引 | docs/platforms/silabs/index.md |
| 通用应用基类 | examples/platforms/silabs/BaseApplication.h、BaseApplication.cpp |
| LCD 实现 | examples/platforms/silabs/display/lcd.h、lcd.cpp、demo-ui.c |
| LED 抽象 | examples/platforms/silabs/LEDWidget.h、LEDWidget.cpp |
| 应用入口 | examples/platforms/silabs/main.cpp、MatterConfig.cpp |
适用前提与限制:本文行为描述基于当前仓库 Silabs 平台示例(EFR32/SiWx917 等套件,GN 构建)的BaseApplication公共层实现;具体某个示例的最终表现还受其.defaults/.conf组件配置影响(如是否启用SL_MATTER_DISPLAY_ENABLED、SL_MATTER_QR_CODE_ENABLED、是否 ICD 构建),且应用键 BTN1 与部分 LCD 画面由各示例自定义,需结合对应示例文档阅读。文档中关于 PANID/SSID 尚未上屏、ICD 状态屏待完成等注释,在当前源码中已有部分演进,实际以源码行为为准。
【免费下载链接】connectedhomeipMatter (formerly Project CHIP) creates more connections between more objects, simplifying development for manufacturers and increasing compatibility for consumers, guided by the Connectivity Standards Alliance.项目地址: https://gitcode.com/GitHub_Trending/co/connectedhomeip
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考