一、前言
matter智能家居是非常有前景的配合home生态等可以实现远程无线控制,就是没有wifi接入,也可以通过蓝牙实现组网使用。
本篇将介绍如何快速的使用NRF54LM20-DK接入matter生态,实现智能家居环境监测。
二、硬件
1、NRF54LM20-DK开发板
2、Homepod

二、实现步骤
1、打开NRF CONNECT 导入一个temperature_sensor工程:

2、导入完毕后占击添加配置:

开发板需要选择nrf54lm20dk/nrf54lm20b/cpuapp
3、修改prj.conf添加sht30的开关,修改好后的文件如下:
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
# Enable CHIP
CONFIG_CHIP=y
CONFIG_CHIP_PROJECT_CONFIG="src/chip_project_config.h"
CONFIG_CHIP_DEVICE_PRODUCT_NAME="Matter Temperature Sensor"
# Configure ZAP file name
CONFIG_NCS_SAMPLE_MATTER_ZAP_FILE_PATH="${APPLICATION_CONFIG_DIR}/src/default_zap/temperature_sensor.zap"
# Based on https://github.com/project-chip/connectedhomeip/blob/482e6fd03196a6de45465a90003947ef4b86e0b1/docs/examples/discussion/PID_allocation_for_example_apps.md)
CONFIG_CHIP_DEVICE_PRODUCT_ID=32781
CONFIG_STD_CPP17=y
# Enable Matter pairing automatically on application start.
CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y
# Enable Matter extended announcement and increase duration to 1 hour.
CONFIG_CHIP_BLE_EXT_ADVERTISING=y
CONFIG_CHIP_BLE_ADVERTISING_DURATION=60
# Add support for LEDs and buttons on Nordic development kits
CONFIG_DK_LIBRARY=y
# Bluetooth Low Energy configuration
CONFIG_BT_DEVICE_NAME="MatterTemperature"
CONFIG_BT_DEVICE_NAME_MAX=18
# Suspend devices when the CPU goes into sleep
CONFIG_PM_DEVICE=y
# Other settings
CONFIG_THREAD_NAME=y
CONFIG_MPU_STACK_GUARD=y
CONFIG_RESET_ON_FATAL_ERROR=n
CONFIG_CHIP_LIB_SHELL=y
# Reduce application size
CONFIG_USE_SEGGER_RTT=n
# Enable Factory Data feature
CONFIG_CHIP_FACTORY_DATA=y
CONFIG_CHIP_FACTORY_DATA_BUILD=y
# SHT30 temperature & humidity sensor (TWIM21 on P1.13/P1.23)
CONFIG_SENSOR=y
CONFIG_I2C=y
CONFIG_SHT3XD=y
# Single-shot mode (simpler, no timing issues with the 10 s timer)
CONFIG_SHT3XD_SINGLE_SHOT_MODE=y
# High repeatability (~15 ms per measurement, well within the 10 s interval)
CONFIG_SHT3XD_REPEATABILITY_HIGH=y4、修改板级overlay,让SHT30连上I2C21
文件地址:boards/nrf54lm20dk_nrf54lm20b_cpuapp.overlay
在尾部添加,添加好后的源代码如下:
/*
* Copyright (c) 2026 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/ {
chosen {
nordic,pm-ext-flash = &mx25r64;
};
aliases {
/* Use watchdog wdt31 as the application watchdog */
watchdog0 = &wdt31;
};
};
&rram_controller {
cpuapp_rram: rram@0 {
reg = <0x0 DT_SIZE_K(2036)>;
ranges = <0x0 0x0 DT_SIZE_K(2036)>;
};
};
&mx25r64 {
status = "okay";
};
&wdt31 {
status = "okay";
};
/* SHT30 temperature and humidity sensor on I2C21 (SDA = P1.13, SCL = P1.23) */
&pinctrl {
i2c21_default: i2c21_default {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 1, 13)>,
<NRF_PSEL(TWIM_SCL, 1, 23)>;
bias-pull-up;
};
};
i2c21_sleep: i2c21_sleep {
group1 {
psels = <NRF_PSEL(TWIM_SDA, 1, 13)>,
<NRF_PSEL(TWIM_SCL, 1, 23)>;
low-power-enable;
bias-pull-up;
};
};
};
&i2c21 {
status = "okay";
pinctrl-0 = <&i2c21_default>;
pinctrl-1 = <&i2c21_sleep>;
pinctrl-names = "default", "sleep";
sht3xd@44 {
compatible = "sensirion,sht3xd";
reg = <0x44>;
status = "okay";
};
};5、由于原来的示例工程只有模拟的温度,因此需要修改app_task.h,添加humidity字段与sensor线程。修改后代码如下:
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#pragma once
#include "board/board.h"
#include <platform/CHIPDeviceLayer.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
struct Identify;
#ifndef CONFIG_NCS_SAMPLE_MATTER_USE_DEFAULT_BUTTON_HANDLER
enum class ButtonState { None, SoftwareUpdate, UAT };
#endif
class AppTask {
public:
static AppTask &Instance()
{
static AppTask sAppTask;
return sAppTask;
};
CHIP_ERROR StartApp();
/* Defined by cluster temperature measured value = 100 x temperature in degC with resolution of
* 0.01 degC. */
void UpdateTemperatureMeasurement();
int16_t GetCurrentTemperature() const { return mCurrentTemperature; }
/* Defined by cluster relative humidity measured value = 100 x relative humidity in %RH with
* resolution of 0.01 %RH. */
int16_t GetCurrentHumidity() const { return mCurrentHumidity; }
/* Dedicated thread + semaphore for the blocking I2C sample fetch (SHT3xD).
* The Matter main thread must not be blocked by I2C transactions. */
static constexpr int kSensorThreadPriority = 5;
static constexpr int kSensorThreadStackSize = 1024;
private:
CHIP_ERROR Init();
k_timer mTimer;
static constexpr uint16_t kTemperatureMeasurementIntervalMs = 10000; /* 10 seconds */
static void UpdateTemperatureTimeoutCallback(k_timer *timer);
static void ButtonEventHandler(Nrf::ButtonState state, Nrf::ButtonMask hasChanged);
static void SensorThreadMain(void *arg1, void *arg2, void *arg3);
const device *mSensor = nullptr;
k_thread mSensorThread;
k_thread_stack_t *mSensorThreadStack = nullptr;
k_sem mSensorSem;
int16_t mCurrentTemperature = 0;
int16_t mCurrentHumidity = 0;
};在这里我们修改每10秒钟更新一次数据。
6、app_task.cpp: 改 UpdateTemperatureMeasurement 把测量下沉到 sensor 线程
修改好源代码如下:
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
#include "app_task.h"
#include "app/matter_init.h"
#include "app/task_executor.h"
#include "board/board.h"
#include "clusters/identify.h"
#include "lib/core/CHIPError.h"
#include <app-common/zap-generated/attributes/Accessors.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#ifdef CONFIG_BME680
#include <zephyr/drivers/sensor.h>
const device *sBme688SensorDev = DEVICE_DT_GET_ONE(bosch_bme680);
#endif
LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL);
using namespace ::chip;
using namespace ::chip::app;
using namespace ::chip::DeviceLayer;
namespace
{
constexpr chip::EndpointId kTemperatureSensorEndpointId = 1;
Nrf::Matter::IdentifyCluster sIdentifyCluster(kTemperatureSensorEndpointId);
#ifdef CONFIG_CHIP_ICD_UAT_SUPPORT
#ifdef CONFIG_NCS_SAMPLE_MATTER_USE_DEFAULT_BUTTON_HANDLER
#define UAT_BUTTON_MASK DK_BTN3_MSK
#endif
#endif
#ifndef CONFIG_NCS_SAMPLE_MATTER_USE_DEFAULT_BUTTON_HANDLER
constexpr int kSoftwareUpdateTimeout = 1500;
constexpr int kUatTimeout = 1500;
constexpr int kFactoryResetTimeout = 3000;
constexpr int kUatBlinkPeriod = 200;
ButtonState sBtnState = ButtonState::None;
k_timer sBtn1Timer;
#endif
#ifdef CONFIG_SHT3XD
K_THREAD_STACK_DEFINE(sSensorThreadStack, AppTask::kSensorThreadStackSize);
#endif
} /* namespace */
void HandleUAT()
{
#ifdef CONFIG_CHIP_ICD_UAT_SUPPORT
LOG_INF("ICD UserActiveMode has been triggered.");
Server::GetInstance().GetICDManager().OnNetworkActivity();
#endif
}
/* nRF54T15 TAG has only a single button,
* therefore the default handler for factory data and software update had to be overriden.
* On other DK's only UAT is handled by the application.
*/
void AppTask::ButtonEventHandler(Nrf::ButtonState state, Nrf::ButtonMask hasChanged)
{
#ifdef CONFIG_NCS_SAMPLE_MATTER_USE_DEFAULT_BUTTON_HANDLER
if (UAT_BUTTON_MASK & state & hasChanged) {
HandleUAT();
}
#else
if (DK_BTN1_MSK & hasChanged) {
if (DK_BTN1_MSK & state) {
LOG_INF("Release the button within %ums to trigger Software Update", kSoftwareUpdateTimeout);
k_timer_start(&sBtn1Timer, K_MSEC(kSoftwareUpdateTimeout), K_NO_WAIT);
sBtnState = ButtonState::SoftwareUpdate;
} else {
if (sBtnState == ButtonState::SoftwareUpdate) {
#ifndef CONFIG_NCS_SAMPLE_MATTER_CUSTOM_BLUETOOTH_ADVERTISING
if (Nrf::GetBoard().GetDeviceState() == Nrf::DeviceState::DeviceProvisioned) {
/* In this case we need to run only Bluetooth LE SMP advertising if it is available */
#ifdef CONFIG_MCUMGR_TRANSPORT_BT
GetDFUOverSMP().StartServer();
#else
LOG_INF("Software update is disabled");
#endif
} else {
/* In this case we start both Bluetooth LE SMP and Matter advertising at the
* same time */
Nrf::GetBoard().StartBLEAdvertisement();
}
#endif
} else if (sBtnState == ButtonState::UAT) {
HandleUAT();
}
/* Restore LED's state and cancel the timer */
k_timer_stop(&sBtn1Timer);
sBtnState = ButtonState::None;
Nrf::GetBoard().RestoreAllLedsState();
Nrf::GetBoard().RunLedStateHandler();
}
}
#endif
}
#ifndef CONFIG_NCS_SAMPLE_MATTER_USE_DEFAULT_BUTTON_HANDLER
void ButtonTimerEventHandler()
{
if (sBtnState == ButtonState::SoftwareUpdate) {
LOG_INF("Release the button within %ums to trigger UAT", kUatTimeout);
k_timer_start(&sBtn1Timer, K_MSEC(kUatTimeout), K_NO_WAIT);
sBtnState = ButtonState::UAT;
/* Turn off all LEDs before starting blink to make sure blink is coordinated. */
Nrf::GetBoard().ResetAllLeds();
Nrf::GetBoard().ForEachLED([](Nrf::LEDWidget &led) { led.Blink(kUatBlinkPeriod); });
} else if (sBtnState == ButtonState::UAT) {
LOG_INF("Factory reset has been triggered. Release button within %ums to cancel.",
kFactoryResetTimeout);
/* Start timer for sFactoryResetTimeout to allow user to cancel, if required. */
k_timer_start(&sBtn1Timer, K_MSEC(kFactoryResetTimeout), K_NO_WAIT);
sBtnState = ButtonState::None;
Nrf::GetBoard().ForEachLED([](Nrf::LEDWidget &led) { led.Blink(Nrf::LedConsts::kBlinkRate_ms); });
/* If we reached here, the button was held past FactoryResetTriggerTimeout, initiate factory reset */
} else if (sBtnState == ButtonState::None) {
/* Actually trigger Factory Reset */
chip::Server::GetInstance().ScheduleFactoryReset();
}
}
void ButtonTimerTimeoutCallback(k_timer *timer)
{
Nrf::PostTask([] { ButtonTimerEventHandler(); });
}
#endif
#ifdef CONFIG_BME680
/* Keep the original BME680 read path for boards that opt in via CONFIG_BME680 (e.g. nRF54L15 TAG). */
void AppTask::UpdateTemperatureMeasurement()
{
int result = sensor_sample_fetch(sBme688SensorDev);
if (result == 0) {
sensor_value temperature;
int result = sensor_channel_get(sBme688SensorDev, SENSOR_CHAN_AMBIENT_TEMP, &temperature);
if (result == 0) {
mCurrentTemperature = static_cast<int16_t>(temperature.val1 * 100 + temperature.val2 / 10000);
LOG_DBG("New temperature measurement %d.%d *C", temperature.val1, temperature.val2);
} else {
LOG_ERR("Getting temperature measurement data from BME688 failed with: %d", result);
}
} else {
LOG_ERR("Fetching data from BME688 sensor failed with: %d", result);
}
}
#else
/* SHT3xD path: the actual sample fetch happens in the dedicated SensorThreadMain,
* which then schedules the attribute write back to the Matter main thread.
* The 10 s timer simply signals the thread's semaphore here. */
void AppTask::UpdateTemperatureMeasurement()
{
/* Intentionally empty on SHT3xD targets; the measurement is performed in SensorThreadMain
* after being triggered by UpdateTemperatureTimeoutCallback. */
}
#endif
void AppTask::UpdateTemperatureTimeoutCallback(k_timer *timer)
{
if (!timer || !timer->user_data) {
return;
}
#ifdef CONFIG_SHT3XD
AppTask &app = *reinterpret_cast<AppTask *>(timer->user_data);
k_sem_give(&app.mSensorSem);
#endif
}
void AppTask::SensorThreadMain(void *arg1, void *arg2, void *arg3)
{
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
AppTask &app = AppTask::Instance();
while (true) {
k_sem_take(&app.mSensorSem, K_FOREVER);
if (app.mSensor == nullptr) {
continue;
}
struct sensor_value temp_val{};
struct sensor_value humid_val{};
int ret = sensor_sample_fetch(app.mSensor);
if (ret == 0) {
ret = sensor_channel_get(app.mSensor, SENSOR_CHAN_AMBIENT_TEMP, &temp_val);
}
if (ret == 0) {
ret = sensor_channel_get(app.mSensor, SENSOR_CHAN_HUMIDITY, &humid_val);
}
if (ret == 0) {
app.mCurrentTemperature =
static_cast<int16_t>(sensor_value_to_double(&temp_val) * 100);
app.mCurrentHumidity = static_cast<int16_t>(sensor_value_to_double(&humid_val) * 100);
DeviceLayer::PlatformMgr().ScheduleWork(
[](intptr_t) {
const auto &inst = AppTask::Instance();
Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Set(
kTemperatureSensorEndpointId, inst.GetCurrentTemperature());
Clusters::RelativeHumidityMeasurement::Attributes::MeasuredValue::Set(
kTemperatureSensorEndpointId, inst.GetCurrentHumidity());
},
0);
} else {
LOG_ERR("SHT3xD read failed: %d", ret);
}
}
}
CHIP_ERROR AppTask::Init()
{
/* Initialize Matter stack */
ReturnErrorOnFailure(Nrf::Matter::PrepareServer());
#ifdef CONFIG_SHT3XD
mSensor = DEVICE_DT_GET_ONE(sensirion_sht3xd);
if (!device_is_ready(mSensor)) {
LOG_ERR("SHT3xD sensor device not ready");
return chip::System::MapErrorZephyr(-ENODEV);
}
mSensorThreadStack = sSensorThreadStack;
k_sem_init(&mSensorSem, 0, 1);
k_thread_create(&mSensorThread, mSensorThreadStack, K_THREAD_STACK_SIZEOF(sSensorThreadStack),
SensorThreadMain, nullptr, nullptr, nullptr, kSensorThreadPriority, 0, K_NO_WAIT);
k_thread_name_set(&mSensorThread, "sht3xd");
#endif
if (!Nrf::GetBoard().Init(ButtonEventHandler)) {
LOG_ERR("User interface initialization failed.");
return CHIP_ERROR_INCORRECT_STATE;
}
/* Register Matter event handler that controls the connectivity status LED based on the captured Matter network
* state. */
ReturnErrorOnFailure(Nrf::Matter::RegisterEventHandler(Nrf::Board::DefaultMatterEventHandler, 0));
#ifdef CONFIG_BME680
if (!device_is_ready(sBme688SensorDev)) {
LOG_ERR("BME688 sensor device not ready");
return chip::System::MapErrorZephyr(-ENODEV);
}
#endif
ReturnErrorOnFailure(sIdentifyCluster.Init());
return Nrf::Matter::StartServer();
}
CHIP_ERROR AppTask::StartApp()
{
ReturnErrorOnFailure(Init());
k_timer_init(&mTimer, AppTask::UpdateTemperatureTimeoutCallback, nullptr);
k_timer_user_data_set(&mTimer, this);
k_timer_start(&mTimer, K_MSEC(kTemperatureMeasurementIntervalMs), K_MSEC(kTemperatureMeasurementIntervalMs));
#ifndef CONFIG_NCS_SAMPLE_MATTER_USE_DEFAULT_BUTTON_HANDLER
k_timer_init(&sBtn1Timer, &ButtonTimerTimeoutCallback, nullptr);
#endif
while (true) {
Nrf::DispatchNextTask();
}
return CHIP_NO_ERROR;
}7、ZAP 文件: 在 endpoint 1 加 Relative Humidity Measurement cluster
打开 ZAP GUI (或者直接编辑 JSON), 在 endpoint 1 (Anonymous Endpoint Type) 上:
加 cluster Relative Humidity Measurement (code 0x0405), side = server
5 个 attribute 全部 included: MeasuredValue, MinMeasuredValue, MaxMeasuredValue, FeatureMap, ClusterRevision
MeasuredValue: storageOption = RAM, nullable, no default
MinMeasuredValue: default = 0
MaxMeasuredValue: default = 10000 (100.00 %RH)
在 endpointTypes[2].clusters[] 数组末尾追加:
{
"name": "Relative Humidity Measurement",
"code": 1029,
"mfgCode": null,
"define": "RELATIVE_HUMIDITY_MEASUREMENT_CLUSTER",
"side": "server",
"enabled": 1,
"attributes": [
{"name": "MeasuredValue", "code": 0, "type": "int16s", "included": 1, "storageOption": "RAM", "reportable": 1, ...},
{"name": "MinMeasuredValue", "code": 1, "type": "int16s", "included": 1, "storageOption": "RAM", "defaultValue": "0", "reportable": 1, ...},
{"name": "MaxMeasuredValue", "code": 2, "type": "int16s", "included": 1, "storageOption": "RAM", "defaultValue": "10000", "reportable": 1, ...},
{"name": "FeatureMap", "code": 65532, "type": "bitmap32", "included": 1, "storageOption": "RAM", "defaultValue": "0", "reportable": 1, ...},
{"name": "ClusterRevision", "code": 65533, "type": "int16u", "included": 1, "storageOption": "RAM", "defaultValue": "3", "reportable": 1, ...}
]}到此代码修改完毕,我们编译构建。并将代码下载到开发板。
由于我们在前面用这个开发板做了智能灯项目,需要把开发板擦除,在home上把原来的项目也要移除。
8、在home中添加好环境环感器

10,到此我们就创建好了一个matter智能环境监测。
【使用】
1、我们可以打开Home app进行温湿度查看。
2、通过siri进行声命令实现语音交互等等。
【总结】
NRF 提供了非常友好的,快速体验Matter生态。
我要赚赏金
