这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【NRF54Lxx专题】4体验Matter家居环境监测

共1条 1/1 1 跳转至

【NRF54Lxx专题】4体验Matter家居环境监测

高工
2026-06-07 09:40:58     打赏

一、前言

matter智能家居是非常有前景的配合home生态等可以实现远程无线控制,就是没有wifi接入,也可以通过蓝牙实现组网使用。

本篇将介绍如何快速的使用NRF54LM20-DK接入matter生态,实现智能家居环境监测。

二、硬件

1、NRF54LM20-DK开发板

2、Homepod

image.png

二、实现步骤

1、打开NRF CONNECT 导入一个temperature_sensor工程:

image.png

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

image.png

开发板需要选择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=y


4、修改板级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中添加好环境环感器

392ed9f82230aebca06b1ba4c8169588.jpg

10,到此我们就创建好了一个matter智能环境监测。

【使用】

1、我们可以打开Home app进行温湿度查看。

2、通过siri进行声命令实现语音交互等等。

【总结】

NRF 提供了非常友好的,快速体验Matter生态。






关键词: NRF54Lxx     Matter     智能     环境监测    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]