From 6e6cfeaf3a734308f5003bb8a49c2850e5c58b5f Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Fri, 11 Sep 2026 13:00:02 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Board-Tabelle=20f=C3=BCr=20alle=20acht?= =?UTF-8?q?=20Ger=C3=A4tetypen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 3 +- src/board/nl_board.c | 50 +++++++++++++++++++ src/board/nl_board.h | 44 +++++++++++++++++ test/CMakeLists.txt | 1 + test/test_board.c | 114 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 src/board/nl_board.c create mode 100644 src/board/nl_board.h create mode 100644 test/test_board.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f21e29c..b700633 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,8 @@ set(NL_FW_SOURCES src/util/nl_sha256.c src/util/nl_crc.c src/sys/fwup_hook_default.c - src/sys/sdk_stubs.c) + src/sys/sdk_stubs.c + src/board/nl_board.c) set(NL_FW ninlight_${NINLIGHT_DEVICE}) add_executable(${NL_FW} ${NL_SDK_BOOT_SOURCES} ${NL_FW_SOURCES} ${NL_SDK_OBJECTS}) diff --git a/src/board/nl_board.c b/src/board/nl_board.c new file mode 100644 index 0000000..a0140a1 --- /dev/null +++ b/src/board/nl_board.c @@ -0,0 +1,50 @@ +#include "board/nl_board.h" + +#include + +#define NONE {-1, NL_PIN_NONE, false, false} +#define CH(c, p) {(c), (p), false, false} +#define CHL(c, p) {(c), (p), true, false} /* active low */ +#define CHA(c, p) {(c), (p), false, true} /* antiphase */ + +/* SM2135EH current codes: RGB high nibble (15 mA = 1, 40 mA = 6, 45 mA = 7), + * WY low nibble (15 mA = 3, 35 mA = 7). Values from qcl_build_config_lamp.h. */ +#define NO_SM NL_PIN_NONE, NL_PIN_NONE, 0 + +const nl_board_t nl_boards[] = { + /* R G B CW WW */ + {"evk", "W600 EVK v1.2", NL_DRV_PWM, true, 2700, 6500, 5000, + {CHL(0, NL_PB(18)), CHL(2, NL_PB(16)), CHL(3, NL_PB(15)), CHL(1, NL_PB(13)), CHL(4, NL_PB(8))}, NO_SM}, + {"e27_rgb", "Klyqa E27 RGB CW/WW", NL_DRV_SM2135EH, true, 2700, 6500, 0, + {NONE, NONE, NONE, NONE, NONE}, NL_PB(13), NL_PB(8), 0x17}, + {"e14_rgb", "Klyqa E14 RGB CW/WW", NL_DRV_SM2135EH, true, 2700, 6500, 0, + {NONE, NONE, NONE, NONE, NONE}, NL_PB(8), NL_PB(13), 0x73}, + {"gu10_rgb", "Klyqa GU10 RGB CW/WW", NL_DRV_SM2135EH, true, 2700, 6500, 0, + {NONE, NONE, NONE, NONE, NONE}, NL_PB(8), NL_PB(13), 0x63}, + {"e27_cw", "Klyqa E27 CW/WW", NL_DRV_PWM, false, 2500, 5000, 600, + {NONE, NONE, NONE, CHA(1, NL_PB(13)), CH(4, NL_PB(8))}, NO_SM}, + {"e14_cw", "Klyqa E14 CW/WW", NL_DRV_PWM, false, 2500, 5000, 600, + {NONE, NONE, NONE, CH(2, NL_PB(16)), CH(4, NL_PB(8))}, NO_SM}, + {"gu10_cw", "Klyqa GU10 CW/WW", NL_DRV_PWM, false, 2500, 5000, 600, + {NONE, NONE, NONE, CH(2, NL_PB(16)), CH(4, NL_PB(8))}, NO_SM}, + /* G95: pin assignment still to be verified on a series bulb (old note: PB16=WW, PB15=CW). */ + {"g95_cw", "Klyqa G95 Vintage CW/WW", NL_DRV_PWM, false, 2500, 5000, 600, + {NONE, NONE, NONE, CH(1, NL_PB(13)), CH(3, NL_PB(15))}, NO_SM}, +}; + +const size_t nl_boards_count = sizeof(nl_boards) / sizeof(nl_boards[0]); + +const nl_board_t *nl_board_find(const char *id) +{ + for (size_t i = 0; i < nl_boards_count; i++) + if (strcmp(nl_boards[i].id, id) == 0) + return &nl_boards[i]; + return NULL; +} + +#ifdef NINLIGHT_DEVICE_ID +const nl_board_t *nl_board_get(void) +{ + return nl_board_find(NINLIGHT_DEVICE_ID); +} +#endif diff --git a/src/board/nl_board.h b/src/board/nl_board.h new file mode 100644 index 0000000..0a26785 --- /dev/null +++ b/src/board/nl_board.h @@ -0,0 +1,44 @@ +#ifndef NL_BOARD_H +#define NL_BOARD_H + +#include +#include +#include + +/* GPIO numbers are identical to enum tls_io_name of the W600 SDK (PA_00 = 0, PB_00 = 16). */ +#define NL_PB(n) ((uint8_t)(16u + (n))) +#define NL_PIN_NONE 0xFFu + +typedef enum { NL_CH_R = 0, NL_CH_G, NL_CH_B, NL_CH_CW, NL_CH_WW, NL_CH_COUNT } nl_channel_t; +typedef enum { NL_DRV_PWM = 0, NL_DRV_SM2135EH } nl_led_driver_t; + +typedef struct { + int8_t pwm_ch; /* PWM unit channel 0..4, -1 = channel not populated */ + uint8_t pin; /* NL_PB(x) or NL_PIN_NONE */ + bool active_low; /* LED lights up while the pin is low */ + bool antiphase; /* inverted PWM output, shifts the pulse (e27_cw cold white) */ +} nl_pwm_chan_t; + +typedef struct { + const char *id; + const char *name; + nl_led_driver_t driver; + bool has_rgb; + uint16_t ct_min; /* Kelvin, warmest */ + uint16_t ct_max; /* Kelvin, coldest */ + uint32_t pwm_freq_hz; /* PWM boards */ + nl_pwm_chan_t pwm[NL_CH_COUNT]; + uint8_t sm_clk_pin; /* SM2135EH boards */ + uint8_t sm_dat_pin; + uint8_t sm_current; /* (rgb_code << 4) | wy_code, see SM2135EH datasheet */ +} nl_board_t; + +extern const nl_board_t nl_boards[]; +extern const size_t nl_boards_count; + +const nl_board_t *nl_board_find(const char *id); + +/* Board selected at build time via NINLIGHT_DEVICE_ID. */ +const nl_board_t *nl_board_get(void); + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cab7b54..adbcef2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,3 +24,4 @@ endfunction() nl_test(test_sha256 test_sha256.c ${NL_SRC}/util/nl_sha256.c) nl_test(test_crc test_crc.c ${NL_SRC}/util/nl_crc.c) +nl_test(test_board test_board.c ${NL_SRC}/board/nl_board.c) diff --git a/test/test_board.c b/test/test_board.c new file mode 100644 index 0000000..0ebbbdf --- /dev/null +++ b/test/test_board.c @@ -0,0 +1,114 @@ +#include +#include "unity.h" +#include "board/nl_board.h" + +void setUp(void) {} +void tearDown(void) {} + +static const char *IDS[] = {"evk", "e27_rgb", "e14_rgb", "gu10_rgb", "e27_cw", "e14_cw", "gu10_cw", "g95_cw"}; + +static void test_all_devices_present(void) +{ + TEST_ASSERT_EQUAL_UINT(8, nl_boards_count); + for (size_t i = 0; i < 8; i++) { + const nl_board_t *b = nl_board_find(IDS[i]); + TEST_ASSERT_NOT_NULL_MESSAGE(b, IDS[i]); + TEST_ASSERT_EQUAL_STRING(IDS[i], b->id); + TEST_ASSERT_NOT_NULL(b->name); + } +} + +static void test_unknown_is_null(void) +{ + TEST_ASSERT_NULL(nl_board_find("e27")); + TEST_ASSERT_NULL(nl_board_find("")); +} + +static void test_rgb_flags_and_ct_ranges(void) +{ + for (size_t i = 0; i < nl_boards_count; i++) { + const nl_board_t *b = &nl_boards[i]; + bool rgb = strstr(b->id, "rgb") != NULL || strcmp(b->id, "evk") == 0; + TEST_ASSERT_EQUAL_MESSAGE(rgb, b->has_rgb, b->id); + TEST_ASSERT_EQUAL_UINT16(rgb ? 2700 : 2500, b->ct_min); + TEST_ASSERT_EQUAL_UINT16(rgb ? 6500 : 5000, b->ct_max); + } +} + +static void test_pwm_boards_consistent(void) +{ + for (size_t i = 0; i < nl_boards_count; i++) { + const nl_board_t *b = &nl_boards[i]; + if (b->driver != NL_DRV_PWM) + continue; + TEST_ASSERT_TRUE(b->pwm_freq_hz > 0); + TEST_ASSERT_TRUE(b->pwm[NL_CH_CW].pwm_ch >= 0); + TEST_ASSERT_TRUE(b->pwm[NL_CH_WW].pwm_ch >= 0); + for (int c = NL_CH_R; c <= NL_CH_B; c++) + TEST_ASSERT_EQUAL_MESSAGE(b->has_rgb, b->pwm[c].pwm_ch >= 0, b->id); + for (int c = 0; c < NL_CH_COUNT; c++) { + if (b->pwm[c].pwm_ch < 0) + continue; + TEST_ASSERT_TRUE(b->pwm[c].pwm_ch <= 4); + for (int d = c + 1; d < NL_CH_COUNT; d++) { + if (b->pwm[d].pwm_ch < 0) + continue; + TEST_ASSERT_NOT_EQUAL(b->pwm[c].pwm_ch, b->pwm[d].pwm_ch); + TEST_ASSERT_NOT_EQUAL(b->pwm[c].pin, b->pwm[d].pin); + } + } + } +} + +static void test_sm_boards_consistent(void) +{ + for (size_t i = 0; i < nl_boards_count; i++) { + const nl_board_t *b = &nl_boards[i]; + if (b->driver != NL_DRV_SM2135EH) + continue; + TEST_ASSERT_TRUE(b->has_rgb); + TEST_ASSERT_NOT_EQUAL(NL_PIN_NONE, b->sm_clk_pin); + TEST_ASSERT_NOT_EQUAL(NL_PIN_NONE, b->sm_dat_pin); + TEST_ASSERT_NOT_EQUAL(b->sm_clk_pin, b->sm_dat_pin); + TEST_ASSERT_NOT_EQUAL(0, b->sm_current); + } +} + +static void test_known_values_from_old_firmware(void) +{ + const nl_board_t *evk = nl_board_find("evk"); + TEST_ASSERT_EQUAL(NL_DRV_PWM, evk->driver); + TEST_ASSERT_EQUAL_UINT32(5000, evk->pwm_freq_hz); + TEST_ASSERT_EQUAL_INT8(0, evk->pwm[NL_CH_R].pwm_ch); + TEST_ASSERT_EQUAL_UINT8(NL_PB(18), evk->pwm[NL_CH_R].pin); + TEST_ASSERT_TRUE(evk->pwm[NL_CH_R].active_low); + + const nl_board_t *e27 = nl_board_find("e27_rgb"); + TEST_ASSERT_EQUAL(NL_DRV_SM2135EH, e27->driver); + TEST_ASSERT_EQUAL_UINT8(NL_PB(13), e27->sm_clk_pin); + TEST_ASSERT_EQUAL_UINT8(NL_PB(8), e27->sm_dat_pin); + TEST_ASSERT_EQUAL_HEX8(0x17, e27->sm_current); + TEST_ASSERT_EQUAL_HEX8(0x73, nl_board_find("e14_rgb")->sm_current); + TEST_ASSERT_EQUAL_HEX8(0x63, nl_board_find("gu10_rgb")->sm_current); + + const nl_board_t *e27cw = nl_board_find("e27_cw"); + TEST_ASSERT_TRUE(e27cw->pwm[NL_CH_CW].antiphase); + TEST_ASSERT_EQUAL_UINT8(NL_PB(13), e27cw->pwm[NL_CH_CW].pin); + TEST_ASSERT_EQUAL_UINT32(600, e27cw->pwm_freq_hz); + + const nl_board_t *g95 = nl_board_find("g95_cw"); + TEST_ASSERT_EQUAL_UINT8(NL_PB(15), g95->pwm[NL_CH_WW].pin); + TEST_ASSERT_EQUAL_INT8(3, g95->pwm[NL_CH_WW].pwm_ch); +} + +int main(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_all_devices_present); + RUN_TEST(test_unknown_is_null); + RUN_TEST(test_rgb_flags_and_ct_ranges); + RUN_TEST(test_pwm_boards_consistent); + RUN_TEST(test_sm_boards_consistent); + RUN_TEST(test_known_values_from_old_firmware); + return UNITY_END(); +}