Hey,
I am working with the Matter light bulb sample from the 2.9.1 SDK, which I have modified to support the occupancy cluster. (nRF-52840 and nRF-52840DK)
This worked fine on version 2.5.0, so I tried to follow the migration guides and implement my custom changes to the new SDK.
While the image builds successfully, there is no Bluetooth beacon, serial communication or any functionality at all. The debugger gets stuck in this IRQ-lock function:
FUNC_NORETURN __weak void arch_system_halt(unsigned int reason) { ARG_UNUSED(reason); /* TODO: What's the best way to totally halt the system if SMP * is enabled? */ (void)arch_irq_lock(); for (;;) { /* Spin endlessly */ } }
The only interrupt related change I made is in SensorMeasureHandler() for the occupancy functionality in the apptask.cpp:
/* * Copyright (c) 2021 Nordic Semiconductor ASA * * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause */ #include "app_task.h" #include <app-common/zap-generated/attributes/Accessors.h> #include "BGT60.h" #include "zephyr/drivers/gpio.h" #ifdef CONFIG_AWS_IOT_INTEGRATION #include "aws_iot_integration.h" #endif #include "app/matter_init.h" #include "app/task_executor.h" #if defined(CONFIG_PWM) #include "pwm/pwm_device.h" #endif #ifdef CONFIG_CHIP_OTA_REQUESTOR #include "dfu/ota/ota_util.h" #endif #include <app-common/zap-generated/attributes/Accessors.h> #include <app/DeferredAttributePersistenceProvider.h> #include <app/clusters/identify-server/identify-server.h> #include <app/server/OnboardingCodesUtil.h> #include <app/server/Server.h> #include <zephyr/logging/log.h> LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace ::chip; using namespace ::chip::app; using namespace ::chip::DeviceLayer; namespace { constexpr EndpointId kLightEndpointId = 1; constexpr uint8_t kDefaultMinLevel = 0; constexpr uint8_t kDefaultMaxLevel = 254; constexpr uint16_t kTriggerEffectTimeout = 5000; constexpr uint16_t kTriggerEffectFinishTimeout = 1000; constexpr uint16_t kOccupancyOccupiedToUnoccupiedTransitionTimeMs = 15 * 1000; // milliseconds constexpr uint8_t kOccupancyEndpointId = 2; /* Sensor */ k_timer sTriggerEffectTimer; k_timer sSensorTimer; BGT60 bgt60; const struct device *dev = device_get_binding("GPIO_0"); struct gpio_callback cb_data; /* End Sensor */ Identify sIdentify = { kLightEndpointId, AppTask::IdentifyStartHandler, AppTask::IdentifyStopHandler, Clusters::Identify::IdentifyTypeEnum::kVisibleIndicator, AppTask::TriggerIdentifyEffectHandler }; bool sIsTriggerEffectActive = false; #if defined(CONFIG_PWM) const struct pwm_dt_spec sLightPwmDevice = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1)); #endif // Define a custom attribute persister which makes actual write of the CurrentLevel attribute value // to the non-volatile storage only when it has remained constant for 5 seconds. This is to reduce // the flash wearout when the attribute changes frequently as a result of MoveToLevel command. // DeferredAttribute object describes a deferred attribute, but also holds a buffer with a value to // be written, so it must live so long as the DeferredAttributePersistenceProvider object. DeferredAttribute gCurrentLevelPersister(ConcreteAttributePath(kLightEndpointId, Clusters::LevelControl::Id, Clusters::LevelControl::Attributes::CurrentLevel::Id)); DeferredAttributePersistenceProvider gDeferredAttributePersister(Server::GetInstance().GetDefaultAttributePersister(), Span<DeferredAttribute>(&gCurrentLevelPersister, 1), System::Clock::Milliseconds32(5000)); #define APPLICATION_BUTTON_MASK DK_BTN2_MSK } /* namespace */ /* Start Occupancy specific code */ void SensorTimerHandler(k_timer *timer) { Nrf::PostTask([] { AppTask::SensorMeasureHandler(); }); } void StartSensorTimer(uint32_t aTimeoutMs) { k_timer_start(&sSensorTimer, K_MSEC(aTimeoutMs), K_MSEC(aTimeoutMs)); } void StopSensorTimer() { k_timer_stop(&sSensorTimer); } void AppTask::SensorActivateHandler() { StartSensorTimer(500); } void AppTask::SensorDeactivateHandler() { StopSensorTimer(); } void AppTask::SensorMeasureHandler() { uint64_t now = k_uptime_get(); uint64_t motion_time = bgt60.GetLastTdTime(); // Waiting for the hold-up time to expire if (now - motion_time < kOccupancyOccupiedToUnoccupiedTransitionTimeMs) { // Set Occupancy true chip::app::Clusters::OccupancySensing::Attributes::Occupancy::Set(kOccupancyEndpointId, chip::app::Clusters::OccupancySensing::OccupancyBitmap::kOccupied); } // Checking the logic level after hold-up time expires (is it inactive?) else if (bgt60.GetTd() == false) { // Set Occupancy false chip::app::Clusters::OccupancySensing::Attributes::Occupancy::Set(kOccupancyEndpointId, 0x00); } else { // Reset the hold-up time uint32_t pins = BIT(td.pin); // Sensor TD pin number ::td_activated(dev, &cb_data, pins); } } /* end occ */ void AppTask::IdentifyStartHandler(Identify *) { Nrf::PostTask( [] { Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Blink(Nrf::LedConsts::kIdentifyBlinkRate_ms); }); } void AppTask::IdentifyStopHandler(Identify *) { Nrf::PostTask([] { Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Set(false); #if defined(CONFIG_PWM) Instance().mPWMDevice.ApplyLevel(); #endif }); } void AppTask::TriggerEffectTimerTimeoutCallback(k_timer *timer) { LOG_INF("Identify effect completed"); sIsTriggerEffectActive = false; Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Set(false); #if defined(CONFIG_PWM) Instance().mPWMDevice.ApplyLevel(); #endif } void AppTask::TriggerIdentifyEffectHandler(Identify *identify) { switch (identify->mCurrentEffectIdentifier) { /* Just handle all effects in the same way. */ case Clusters::Identify::EffectIdentifierEnum::kBlink: case Clusters::Identify::EffectIdentifierEnum::kBreathe: case Clusters::Identify::EffectIdentifierEnum::kOkay: case Clusters::Identify::EffectIdentifierEnum::kChannelChange: LOG_INF("Identify effect identifier changed to %d", static_cast<uint8_t>(identify->mCurrentEffectIdentifier)); sIsTriggerEffectActive = false; k_timer_stop(&sTriggerEffectTimer); k_timer_start(&sTriggerEffectTimer, K_MSEC(kTriggerEffectTimeout), K_NO_WAIT); #if defined(CONFIG_PWM) Instance().mPWMDevice.SuppressOutput(); #endif Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Blink(Nrf::LedConsts::kIdentifyBlinkRate_ms); break; case Clusters::Identify::EffectIdentifierEnum::kFinishEffect: LOG_INF("Identify effect finish triggered"); k_timer_stop(&sTriggerEffectTimer); k_timer_start(&sTriggerEffectTimer, K_MSEC(kTriggerEffectFinishTimeout), K_NO_WAIT); break; case Clusters::Identify::EffectIdentifierEnum::kStopEffect: if (sIsTriggerEffectActive) { sIsTriggerEffectActive = false; k_timer_stop(&sTriggerEffectTimer); Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Set(false); #if defined(CONFIG_PWM) Instance().mPWMDevice.ApplyLevel(); #endif } break; default: LOG_ERR("Received invalid effect identifier."); break; } } void AppTask::LightingActionEventHandler(const LightingEvent &event) { #if defined(CONFIG_PWM) Nrf::PWMDevice::Action_t action = Nrf::PWMDevice::INVALID_ACTION; int32_t actor = 0; if (event.Actor == LightingActor::Button) { action = Instance().mPWMDevice.IsTurnedOn() ? Nrf::PWMDevice::OFF_ACTION : Nrf::PWMDevice::ON_ACTION; actor = static_cast<int32_t>(event.Actor); } if (action == Nrf::PWMDevice::INVALID_ACTION || !Instance().mPWMDevice.InitiateAction(action, actor, NULL)) { LOG_INF("An action could not be initiated."); } #else Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).Set(!Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).GetState()); #endif } void AppTask::ButtonEventHandler(Nrf::ButtonState state, Nrf::ButtonMask hasChanged) { if ((APPLICATION_BUTTON_MASK & hasChanged) & state) { Nrf::PostTask([] { LightingEvent event; event.Actor = LightingActor::Button; LightingActionEventHandler(event); }); } } #ifdef CONFIG_AWS_IOT_INTEGRATION bool AppTask::AWSIntegrationCallback(struct aws_iot_integration_cb_data *data) { LOG_INF("Attribute change requested from AWS IoT: %d", data->value); Protocols::InteractionModel::Status status; VerifyOrDie(data->error == 0); if (data->attribute_id == ATTRIBUTE_ID_ONOFF) { /* write the new on/off value */ status = Clusters::OnOff::Attributes::OnOff::Set(kLightEndpointId, data->value); if (status != Protocols::InteractionModel::Status::Success) { LOG_ERR("Updating on/off cluster failed: %x", to_underlying(status)); return false; } } else if (data->attribute_id == ATTRIBUTE_ID_LEVEL_CONTROL) { /* write the current level */ status = Clusters::LevelControl::Attributes::CurrentLevel::Set(kLightEndpointId, data->value); if (status != Protocols::InteractionModel::Status::Success) { LOG_ERR("Updating level cluster failed: %x", to_underlying(status)); return false; } } return true; } #endif /* CONFIG_AWS_IOT_INTEGRATION */ #if defined(CONFIG_PWM) void AppTask::ActionInitiated(Nrf::PWMDevice::Action_t action, int32_t actor) { if (action == Nrf::PWMDevice::ON_ACTION) { LOG_INF("Turn On Action has been initiated"); } else if (action == Nrf::PWMDevice::OFF_ACTION) { LOG_INF("Turn Off Action has been initiated"); } else if (action == Nrf::PWMDevice::LEVEL_ACTION) { LOG_INF("Level Action has been initiated"); } } void AppTask::ActionCompleted(Nrf::PWMDevice::Action_t action, int32_t actor) { if (action == Nrf::PWMDevice::ON_ACTION) { LOG_INF("Turn On Action has been completed"); } else if (action == Nrf::PWMDevice::OFF_ACTION) { LOG_INF("Turn Off Action has been completed"); } else if (action == Nrf::PWMDevice::LEVEL_ACTION) { LOG_INF("Level Action has been completed"); } if (actor == static_cast<int32_t>(LightingActor::Button)) { Instance().UpdateClusterState(); } } #endif /* CONFIG_PWM */ void AppTask::UpdateClusterState() { SystemLayer().ScheduleLambda([this] { #if defined(CONFIG_PWM) /* write the new on/off value */ Protocols::InteractionModel::Status status = Clusters::OnOff::Attributes::OnOff::Set(kLightEndpointId, mPWMDevice.IsTurnedOn()); #else Protocols::InteractionModel::Status status = Clusters::OnOff::Attributes::OnOff::Set( kLightEndpointId, Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).GetState()); #endif if (status != Protocols::InteractionModel::Status::Success) { LOG_ERR("Updating on/off cluster failed: %x", to_underlying(status)); } #if defined(CONFIG_PWM) /* write the current level */ status = Clusters::LevelControl::Attributes::CurrentLevel::Set(kLightEndpointId, mPWMDevice.GetLevel()); #else /* write the current level */ if (Nrf::GetBoard().GetLED(Nrf::DeviceLeds::LED2).GetState()) { status = Clusters::LevelControl::Attributes::CurrentLevel::Set(kLightEndpointId, 100); } else { status = Clusters::LevelControl::Attributes::CurrentLevel::Set(kLightEndpointId, 0); } #endif if (status != Protocols::InteractionModel::Status::Success) { LOG_ERR("Updating level cluster failed: %x", to_underlying(status)); } }); } void AppTask::InitPWMDDevice() { #if defined(CONFIG_PWM) /* Initialize lighting device (PWM) */ uint8_t minLightLevel = kDefaultMinLevel; Clusters::LevelControl::Attributes::MinLevel::Get(kLightEndpointId, &minLightLevel); uint8_t maxLightLevel = kDefaultMaxLevel; Clusters::LevelControl::Attributes::MaxLevel::Get(kLightEndpointId, &maxLightLevel); Clusters::LevelControl::Attributes::CurrentLevel::TypeInfo::Type currentLevel; Clusters::LevelControl::Attributes::CurrentLevel::Get(kLightEndpointId, currentLevel); int ret = mPWMDevice.Init(&sLightPwmDevice, minLightLevel, maxLightLevel, currentLevel.ValueOr(kDefaultMaxLevel)); if (ret != 0) { LOG_ERR("Failed to initialize PWD device."); } mPWMDevice.SetCallbacks(ActionInitiated, ActionCompleted); #endif } CHIP_ERROR AppTask::Init() { /* Initialize Matter stack */ ReturnErrorOnFailure(Nrf::Matter::PrepareServer(Nrf::Matter::InitData{ .mPostServerInitClbk = [] { app::SetAttributePersistenceProvider(&gDeferredAttributePersister); return CHIP_NO_ERROR; } })); 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_AWS_IOT_INTEGRATION int retAws = aws_iot_integration_register_callback(AWSIntegrationCallback); if (retAws) { LOG_ERR("aws_iot_integration_register_callback() failed"); return chip::System::MapErrorZephyr(retAws); } #endif /* Initialize trigger effect timer */ k_timer_init(&sTriggerEffectTimer, &AppTask::TriggerEffectTimerTimeoutCallback, nullptr); /* Occupancy specific code */ k_timer_init(&sSensorTimer, &SensorTimerHandler, nullptr); k_timer_user_data_set(&sSensorTimer, this); /* Initialize radar sensor */ bgt60 = BGT60::Instance(); bgt60.Init(); if (bgt60.IsReady()) { LOG_INF("BGT60 initialized: ready."); } else { LOG_ERR("BGT60 initialization failed."); return CHIP_ERROR_INTERNAL; } return Nrf::Matter::StartServer(); } CHIP_ERROR AppTask::StartApp() { ReturnErrorOnFailure(Init()); while (true) { Nrf::DispatchNextTask(); } return CHIP_NO_ERROR; }
This is the BGT60.cpp file, covering the functions for the sensor. td_activated() mainly resets the timer:
/* * BGT60.cpp * * Simple 'driver'/access class for the Infineon BGT60LTR11 Radar Shield2Go * * Copyright (c) 2023 dxcfl * * SPDX-License-Identifier: Apache-2.0 */ #include "zephyr/kernel.h" #include "zephyr/device.h" #include "zephyr/devicetree.h" #include "zephyr/drivers/gpio.h" #include "BGT60.h" #define TD_NODE DT_ALIAS(targetd) #define PD_NODE DT_ALIAS(phased) const struct gpio_dt_spec td = GPIO_DT_SPEC_GET(TD_NODE, gpios); const struct gpio_dt_spec pd = GPIO_DT_SPEC_GET(PD_NODE, gpios); static bool ready = false; static uint64_t last_td_time = 0; static bool last_pd = false; static struct gpio_callback td_cb_data; void td_activated(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { uint64_t now = k_uptime_get(); bool pd_pin = gpio_pin_get_dt(&pd); last_td_time = now; last_pd = pd_pin; } void BGT60::Init() { int ret; printk("BGT60: Init ...\n\r"); if (!device_is_ready(td.port)) { printk("BGT60: Target detection port (TD) not available ...\n\r"); return; } if (!device_is_ready(pd.port)) { printk("BGT60: Phase detection port (PD) not available ...\n\r"); return; } ret = gpio_pin_configure_dt(&td, GPIO_INPUT); if (ret < 0) { printk("BGT60: Target detection port (TD) not available for input ...\n\r"); return; } ret = gpio_pin_configure_dt(&pd, GPIO_INPUT); if (ret < 0) { printk("BGT60: Phase detection port (PD) not available for input ...\n\r"); return; } ret = gpio_pin_interrupt_configure_dt(&td, GPIO_INT_EDGE_BOTH); if (ret < 0) { printk("BGT60: Could not configure ISR for TD ...\n\r"); return; } gpio_init_callback(&td_cb_data, td_activated, BIT(td.pin)); gpio_add_callback(td.port, &td_cb_data); ready = true; } bool BGT60::IsReady() { return ready; } uint64_t BGT60::GetLastTdTime() { return last_td_time; } bool BGT60::GetLastPd() { return last_pd; } bool BGT60::GetTd() { return gpio_pin_get_dt(&td); } bool BGT60::GetPd() { return gpio_pin_get_dt(&pd); }
BGT60.h:
/* * BGT60.h * * Simple 'driver'/access class for the Infineon BGT60LTR11 Radar Shield2Go * * Copyright (c) 2023 dxcfl * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include <zephyr/kernel.h> void td_activated(const struct device *dev, struct gpio_callback *cb, uint32_t pins); extern const struct gpio_dt_spec td; class BGT60 { public: static BGT60 &Instance() { static BGT60 instance; return instance; }; static void Init(); static bool IsReady(); static uint64_t GetLastTdTime(); static bool GetLastPd(); static bool GetTd(); static bool GetPd(); static void Reoccupy(); };
This worked fine with the old SDK, but I can't see what needs to be changed. I appreciate any help.