From aaef4cec07040b89c66c7e053130a0c44bc1d5f0 Mon Sep 17 00:00:00 2001 From: TobiasRoeddiger Date: Sat, 30 May 2026 12:45:45 +0200 Subject: [PATCH 1/2] fix(led): handle expected KTD2026 reset NACK --- src/drivers/LED_Controller/KTD2026.cpp | 146 ++++++++++++++++++++----- src/drivers/LED_Controller/KTD2026.h | 27 ++++- 2 files changed, 141 insertions(+), 32 deletions(-) diff --git a/src/drivers/LED_Controller/KTD2026.cpp b/src/drivers/LED_Controller/KTD2026.cpp index f42e7bac..66d06f78 100644 --- a/src/drivers/LED_Controller/KTD2026.cpp +++ b/src/drivers/LED_Controller/KTD2026.cpp @@ -1,5 +1,7 @@ #include "KTD2026.h" +#include + #include #include @@ -8,24 +10,84 @@ #include LOG_MODULE_REGISTER(LED, CONFIG_MAIN_LOG_LEVEL); -bool KTD2026::readReg(uint8_t reg, uint8_t * buffer, uint16_t len) { - _i2c->aquire(); +namespace { - int ret = i2c_burst_read(_i2c->master, address, reg, buffer, len); - if (ret) LOG_WRN("I2C read failed: %d\n", ret); +constexpr uint8_t KTD2026_CTRL_RESET_REGISTERS = 0x05; +constexpr uint8_t KTD2026_CTRL_RESET_COMPLETE_CHIP = 0x07; +constexpr uint8_t KTD2026_CTRL_SHUTDOWN = 0x08; +constexpr uint8_t KTD2026_REGISTER_COUNT = KTD2026::I_B + 1; - _i2c->release(); +constexpr uint8_t KTD2026_RESET_DEFAULTS[] = { + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4F, 0x4F, 0x4F +}; +static_assert(sizeof(KTD2026_RESET_DEFAULTS) == KTD2026_REGISTER_COUNT, + "KTD2026 reset defaults must cover every cached register"); + +bool isResetCommand(uint8_t reg, const uint8_t *buffer, uint16_t len) { + if ((reg != KTD2026::CTRL) || (buffer == NULL) || (len != 1)) { + return false; + } + + return (buffer[0] == KTD2026_CTRL_RESET_REGISTERS) || + (buffer[0] == KTD2026_CTRL_RESET_COMPLETE_CHIP); +} + +bool isValidRegisterRange(uint8_t reg, uint16_t len) { + return (len > 0) && (reg < KTD2026_REGISTER_COUNT) && + (len <= (KTD2026_REGISTER_COUNT - reg)); +} - return (ret == 0); +void clearColor(RGBColor color) { + memset(color, 0, sizeof(RGBColor)); } -void KTD2026::writeReg(uint8_t reg, uint8_t *buffer, uint16_t len) { +} + +bool KTD2026::readReg(uint8_t reg, uint8_t * buffer, uint16_t len) { + if ((buffer == NULL) || !isValidRegisterRange(reg, len)) { + LOG_WRN("Invalid KTD2026 register read: reg=0x%02x len=%u", + (unsigned int)reg, (unsigned int)len); + return false; + } + + memcpy(buffer, &_register_cache[reg], len); + + return true; +} + +bool KTD2026::writeReg(uint8_t reg, const uint8_t *buffer, uint16_t len, bool ignore_reset_nack) { + bool reset_command = isResetCommand(reg, buffer, len); + + if ((buffer == NULL) || !isValidRegisterRange(reg, len)) { + LOG_WRN("Invalid KTD2026 register write: reg=0x%02x len=%u", + (unsigned int)reg, (unsigned int)len); + return false; + } + _i2c->aquire(); int ret = i2c_burst_write(_i2c->master, address, reg, buffer, len); - if (ret) LOG_WRN("I2C write failed: %d", ret); + if (ret) { + if (ignore_reset_nack && reset_command) { + LOG_DBG("Ignoring expected KTD2026 reset NACK: %d", ret); + } else { + LOG_WRN("I2C write failed: %d", ret); + } + } _i2c->release(); + + if (ret && !(ignore_reset_nack && reset_command)) { + return false; + } + + if (reset_command) { + resetRegisterCache(); + } else { + memcpy(&_register_cache[reg], buffer, len); + } + + return true; } void KTD2026::begin() { @@ -44,14 +106,18 @@ void KTD2026::begin() { } void KTD2026::reset() { - uint8_t val = 0x7; - writeReg(registers::CTRL, &val, sizeof(val)); + uint8_t val = KTD2026_CTRL_RESET_COMPLETE_CHIP; + if (writeReg(registers::CTRL, &val, sizeof(val), true)) { + clearColor(current_color); + } k_usleep(200); } void KTD2026::power_off() { - uint8_t val = 0x8; - writeReg(registers::CTRL, &val, sizeof(val)); + uint8_t val = KTD2026_CTRL_SHUTDOWN; + if (writeReg(registers::CTRL, &val, sizeof(val))) { + clearColor(current_color); + } int ret = pm_device_runtime_put(ls_1_8); ret = pm_device_runtime_put(ls_3_3); @@ -70,8 +136,12 @@ void KTD2026::setColor(const RGBColor& color) { } } - writeReg(registers::I_R, _color, sizeof(RGBColor)); - writeReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); + bool color_written = writeReg(registers::I_R, _color, sizeof(RGBColor)); + bool channel_written = writeReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); + + if (color_written && channel_written) { + getColor(¤t_color); + } } void KTD2026::blink(const RGBColor& color, const int time_on_millis, const int period_millis) { @@ -94,13 +164,18 @@ void KTD2026::pulse(const RGBColor& color, const int time_on_millis, const int t } } - writeReg(registers::I_R, _color, sizeof(RGBColor)); - writeReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); + bool color_written = writeReg(registers::I_R, _color, sizeof(RGBColor)); + bool channel_written = writeReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); + + bool flash_written = writeReg(registers::FP, &flash_period, sizeof(flash_period)); + bool ramp_written = writeReg(registers::RAMP, &t_rise_fall, sizeof(t_rise_fall)); + bool pwm1_written = writeReg(registers::PWM1, &time_on, sizeof(time_on)); + bool pwm2_written = writeReg(registers::PWM2, &time_on_2, sizeof(time_on_2)); - writeReg(registers::FP, &flash_period, sizeof(flash_period)); - writeReg(registers::RAMP, &t_rise_fall, sizeof(t_rise_fall)); - writeReg(registers::PWM1, &time_on, sizeof(time_on)); - writeReg(registers::PWM2, &time_on_2, sizeof(time_on_2)); + if (color_written && channel_written && flash_written && ramp_written && pwm1_written && + pwm2_written) { + getColor(¤t_color); + } } void KTD2026::pulse2(const RGBColor& color, const RGBColor& color2, const int time_on_millis, const int time_rise_millis, const int time_fall_millis, const int period_millis) { @@ -126,31 +201,44 @@ void KTD2026::pulse2(const RGBColor& color, const RGBColor& color2, const int ti } } - writeReg(registers::I_R, _color, sizeof(RGBColor)); - writeReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); + bool color_written = writeReg(registers::I_R, _color, sizeof(RGBColor)); + bool channel_written = writeReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); uint8_t val = 0x2; // slot 3 - writeReg(registers::CTRL, &val, sizeof(val)); + bool ctrl_written = writeReg(registers::CTRL, &val, sizeof(val)); + + bool flash_written = writeReg(registers::FP, &flash_period, sizeof(flash_period)); + bool ramp_written = writeReg(registers::RAMP, &t_rise_fall, sizeof(t_rise_fall)); - writeReg(registers::FP, &flash_period, sizeof(flash_period)); - writeReg(registers::RAMP, &t_rise_fall, sizeof(t_rise_fall)); + bool pwm_written = writeReg(registers::PWM2, &time_on, sizeof(time_on)); - writeReg(registers::PWM2, &time_on, sizeof(time_on)); + if (color_written && channel_written && ctrl_written && flash_written && ramp_written && + pwm_written) { + getColor(¤t_color); + } } -/* Not working */ void KTD2026::getColor(RGBColor * color) { uint8_t channel_enable = 0; + if (color == NULL) { + LOG_WRN("KTD2026 getColor called without output buffer"); + return; + } + readReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); readReg(registers::I_R, *color, sizeof(RGBColor)); for (int i = 0; i < 3; i++) { (*color)[i] ++; - if ((channel_enable & BIT(2 * i)) == 0) { + if ((channel_enable & (0x3 << (2 * i))) == 0) { (*color)[i] = 0; } } } -KTD2026 led_controller; \ No newline at end of file +void KTD2026::resetRegisterCache() { + memcpy(_register_cache, KTD2026_RESET_DEFAULTS, sizeof(_register_cache)); +} + +KTD2026 led_controller; diff --git a/src/drivers/LED_Controller/KTD2026.h b/src/drivers/LED_Controller/KTD2026.h index 510415f3..27700dd8 100644 --- a/src/drivers/LED_Controller/KTD2026.h +++ b/src/drivers/LED_Controller/KTD2026.h @@ -22,30 +22,51 @@ class KTD2026 { I_B = 0x08, }; - RGBColor current_color; + RGBColor current_color = {0, 0, 0}; + /** @brief Enable the LED driver power rails and reset the KTD2026. */ void begin(); + + /** @brief Reset the KTD2026 register bank and clear the cached LED color. */ void reset(); + + /** @brief Put the LED driver into shutdown and release its power rails. */ void power_off(); + + /** @brief Set a steady RGB color. */ void setColor(const RGBColor& color); + + /** @brief Return the cached RGB current values for enabled channels. */ void getColor(RGBColor * color); + /** @brief Blink an RGB color with a fixed on-time and period. */ void blink(const RGBColor& color, const int time_on_millis, const int period_millis); + + /** @brief Pulse an RGB color using the KTD2026 timer and ramp registers. */ void pulse(const RGBColor& color, const int time_on_millis, const int time_rise_millis, const int time_fall_millis, const int period_millis); + + /** @brief Pulse two RGB colors using the KTD2026 timer slots. */ void pulse2(const RGBColor& color, const RGBColor& color2, const int time_on_millis, const int time_rise_millis, const int time_fall_millis, const int period_millis); //void setPower(); private: bool readReg(uint8_t reg, uint8_t *buffer, uint16_t len); - void writeReg(uint8_t reg, uint8_t *buffer, uint16_t len); + bool writeReg(uint8_t reg, const uint8_t *buffer, uint16_t len, bool ignore_reset_nack = false); + void resetRegisterCache(); int address = 0x30; //TwoWire * _pWire = &Wire; TWIM * _i2c = &I2C1; + static constexpr uint8_t REGISTER_COUNT = I_B + 1; + + uint8_t _register_cache[REGISTER_COUNT] = { + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4F, 0x4F, 0x4F + }; + bool _active = false; }; extern KTD2026 led_controller; -#endif \ No newline at end of file +#endif From 338ad67580e4242c20878551c4c2b54f46d015ad Mon Sep 17 00:00:00 2001 From: TobiasRoeddiger Date: Tue, 23 Jun 2026 15:27:12 +0200 Subject: [PATCH 2/2] fix(led): clear cached color on shutdown --- src/drivers/LED_Controller/KTD2026.cpp | 15 ++++++++++++--- src/drivers/LED_Controller/KTD2026.h | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/drivers/LED_Controller/KTD2026.cpp b/src/drivers/LED_Controller/KTD2026.cpp index 66d06f78..feb3ead9 100644 --- a/src/drivers/LED_Controller/KTD2026.cpp +++ b/src/drivers/LED_Controller/KTD2026.cpp @@ -114,13 +114,17 @@ void KTD2026::reset() { } void KTD2026::power_off() { + uint8_t channel_enable = 0; + (void)writeReg(registers::EN_CH, &channel_enable, sizeof(channel_enable)); + uint8_t val = KTD2026_CTRL_SHUTDOWN; - if (writeReg(registers::CTRL, &val, sizeof(val))) { - clearColor(current_color); - } + (void)writeReg(registers::CTRL, &val, sizeof(val)); + int ret = pm_device_runtime_put(ls_1_8); ret = pm_device_runtime_put(ls_3_3); + clearCachedColor(); + _active = false; } @@ -241,4 +245,9 @@ void KTD2026::resetRegisterCache() { memcpy(_register_cache, KTD2026_RESET_DEFAULTS, sizeof(_register_cache)); } +void KTD2026::clearCachedColor() { + _register_cache[registers::EN_CH] = 0; + clearColor(current_color); +} + KTD2026 led_controller; diff --git a/src/drivers/LED_Controller/KTD2026.h b/src/drivers/LED_Controller/KTD2026.h index 27700dd8..9ad0a039 100644 --- a/src/drivers/LED_Controller/KTD2026.h +++ b/src/drivers/LED_Controller/KTD2026.h @@ -52,6 +52,7 @@ class KTD2026 { bool readReg(uint8_t reg, uint8_t *buffer, uint16_t len); bool writeReg(uint8_t reg, const uint8_t *buffer, uint16_t len, bool ignore_reset_nack = false); void resetRegisterCache(); + void clearCachedColor(); int address = 0x30;