diff --git a/CMakeLists.txt b/CMakeLists.txt index cc7a368..bcf99cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,13 +28,20 @@ set(NL_FW_SOURCES src/app_main.c src/util/nl_sha256.c src/util/nl_crc.c - src/sys/fwup_hook_default.c - src/sys/sdk_stubs.c src/board/nl_board.c src/light/nl_color.c src/light/nl_engine.c src/light/nl_control.c - src/light/nl_boot_state.c) + src/light/nl_boot_state.c + src/config/nl_settings.c + src/config/nl_flash_w600.c + src/drivers/nl_led.c + src/drivers/nl_pwm_leds.c + src/drivers/nl_sm2135eh.c + src/sys/nl_console_cmd.c + src/sys/nl_console.c + src/sys/fwup_hook_default.c + src/sys/sdk_stubs.c) set(NL_FW ninlight_${NINLIGHT_DEVICE}) add_executable(${NL_FW} ${NL_SDK_BOOT_SOURCES} ${NL_FW_SOURCES} ${NL_SDK_OBJECTS}) diff --git a/docs/hardware-checklist.md b/docs/hardware-checklist.md new file mode 100644 index 0000000..9488be3 --- /dev/null +++ b/docs/hardware-checklist.md @@ -0,0 +1,28 @@ +# Hardware-Checkliste (EVK) + +Konsole: `tools/monitor.sh /dev/cu.` (115200 8N1). Flashen: `tools/flash.sh evk `. + +## Licht (Plan 1, Task 11) +- [ ] Nach dem Boot blendet warmweiß 100 % in 0,5 s ein +- [ ] `rgb 255 0 0` → nur Rot, `rgb 0 255 0` → nur Grün, `rgb 0 0 255` → nur Blau +- [ ] `ct 2700` → nur Warmweiß, `ct 6500` → nur Kaltweiß, `ct 4000` → Mischung +- [ ] `bri 1` sichtbar sehr dunkel, `bri 100` hell; `bri 1 3000` → gleichmäßig wirkender Fade +- [ ] `off 2000` → blendet mit aktueller Farbe aus; `on 2000` → blendet ein +- [ ] `rgb 255 0 0` dann `ct 3000 2000` → erst dunkel, dann weiß (Moduswechsel) +- [ ] `blink 3` → 3× Blinken, danach alter Zustand; `pulse on` / `pulse off` +- [ ] Kein sichtbares Flackern bei konstanten Werten (5 kHz) + +## Persistenz und Boot (Plan 1, Task 12) +- [ ] `pob last`: Farbe setzen, 4 s warten, Strom aus/an → gleicher Zustand, mit Fade +- [ ] `pob off`: Strom aus/an → bleibt aus; `on` → letzte Farbe +- [ ] `pob on` bei ausgeschaltetem Licht: Strom aus/an → an +- [ ] `fade 2000 2000` bleibt nach Reboot erhalten (`state`) +- [ ] 5× Strom an/aus mit jeweils < 5 s Laufzeit → 3× Blinken, danach Defaults (`state`) +- [ ] Normaler Boot nach > 5 s Laufzeit → kein Werksreset +- [ ] Watchdog: `reboot` funktioniert; nach einem Hänger (nicht provozierbar) wäre Reset nach 20 s + +## Später (Lampen) +- [ ] E27 Color (SM2135EH): RGB und WY getrennt, Ströme plausibel (nicht zu heiß) +- [ ] E27 CW: Gegenphase CW sichtbar flackerfrei +- [ ] G95: Pinbelegung WW=PB15/CW=PB13 prüfen +- [ ] Farbtemperaturbereiche aller Varianten diff --git a/src/app_main.c b/src/app_main.c index 84d5377..5eeabf3 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -2,21 +2,101 @@ #include "wm_include.h" -#define NL_APP_TASK_PRIO 40 -static u32 s_app_stack[512]; +#include "board/nl_board.h" +#include "config/nl_settings.h" +#include "drivers/nl_led.h" +#include "light/nl_boot_state.h" +#include "light/nl_control.h" +#include "light/nl_engine.h" +#include "sys/nl_app.h" +#include "sys/nl_console.h" + +#define NL_LIGHT_PRIO 30 +#define NL_APP_PRIO 40 + +static tls_os_mutex_t *s_lock; +static nl_settings_t s_settings; +static volatile uint32_t s_light_beat; +static u32 s_app_stack[1024]; +static u32 s_light_stack[512]; + +void nl_app_lock(void) { tls_os_mutex_acquire(s_lock, 0); } +void nl_app_unlock(void) { tls_os_mutex_release(s_lock); } + +uint32_t nl_app_now_ms(void) +{ + return (uint32_t)((uint64_t)tls_os_get_time() * 1000u / HZ); +} + +nl_settings_t *nl_app_settings(void) { return &s_settings; } + +int nl_app_save_settings(void) +{ + return 0; /* persistence follows in task 12 */ +} + +void nl_app_factory_reset(void) +{ + printf("factory reset: not implemented yet\r\n"); +} + +static void led_out(const uint8_t ch[NL_CH_COUNT], void *ctx) +{ + (void)ctx; + nl_led_set(ch); +} + +static void light_task(void *arg) +{ + uint32_t last = nl_app_now_ms(); + (void)arg; + for (;;) { + uint32_t now; + tls_os_time_delay(HZ / 100 ? HZ / 100 : 1); /* ~10 ms */ + now = nl_app_now_ms(); + nl_app_lock(); + nl_engine_tick(now - last); + nl_app_unlock(); + last = now; + s_light_beat++; + } +} static void app_task(void *arg) { - unsigned int n = 0; + const nl_board_t *board = nl_board_get(); + nl_engine_cfg_t cfg; + nl_light_state_t initial; + (void)arg; - for (;;) { - printf("ninlight %s alive %u\r\n", NINLIGHT_DEVICE_ID, n++); + nl_settings_defaults(&s_settings); + + nl_led_init(board); + cfg.ct_min = board->ct_min; + cfg.ct_max = board->ct_max; + cfg.output = led_out; + cfg.ctx = NULL; + nl_app_lock(); + nl_engine_init(&cfg); + nl_ctrl_init(board, s_settings.fade_in_ms, s_settings.fade_out_ms); + initial = nl_boot_default_state(board); + nl_ctrl_boot(&initial); + nl_app_unlock(); + + tls_os_task_create(NULL, "nl_light", light_task, NULL, (u8 *)s_light_stack, sizeof(s_light_stack), NL_LIGHT_PRIO, 0); + nl_console_start(); + + for (;;) tls_os_time_delay(HZ); - } } void UserMain(void) { printf("\r\nninlight %s for %s (built %s)\r\n", NINLIGHT_FW_VERSION, NINLIGHT_DEVICE_ID, NINLIGHT_BUILD_DATE); - tls_os_task_create(NULL, "nl_app", app_task, NULL, (u8 *)s_app_stack, sizeof(s_app_stack), NL_APP_TASK_PRIO, 0); + if (nl_board_get() == NULL) { + printf("FATAL: unknown board %s\r\n", NINLIGHT_DEVICE_ID); + return; + } + tls_os_mutex_create(0, &s_lock); + tls_os_task_create(NULL, "nl_app", app_task, NULL, (u8 *)s_app_stack, sizeof(s_app_stack), NL_APP_PRIO, 0); } diff --git a/src/config/nl_flash_w600.c b/src/config/nl_flash_w600.c new file mode 100644 index 0000000..78a44e3 --- /dev/null +++ b/src/config/nl_flash_w600.c @@ -0,0 +1,7 @@ +#include "config/nl_flash.h" + +/* Temporary stub, replaced by the real W600 implementation in task 12. */ +int nl_flash_read(uint32_t addr, void *buf, uint32_t len) { (void)addr; (void)buf; (void)len; return -1; } +int nl_flash_write(uint32_t addr, const void *buf, uint32_t len) { (void)addr; (void)buf; (void)len; return -1; } +int nl_flash_erase_sector(uint32_t addr) { (void)addr; return -1; } +uint32_t nl_flash_region_base(void) { return 0; } diff --git a/src/drivers/nl_led.c b/src/drivers/nl_led.c new file mode 100644 index 0000000..cb7ef3f --- /dev/null +++ b/src/drivers/nl_led.c @@ -0,0 +1,24 @@ +#include "drivers/nl_led.h" + +#include + +static const nl_board_t *s_board; + +void nl_led_init(const nl_board_t *board) +{ + s_board = board; + if (board->driver == NL_DRV_PWM) + nl_pwm_leds_init(board); + else + nl_sm2135eh_init(board); +} + +void nl_led_set(const uint8_t ch[NL_CH_COUNT]) +{ + if (s_board == NULL) + return; + if (s_board->driver == NL_DRV_PWM) + nl_pwm_leds_set(ch); + else + nl_sm2135eh_set(ch); +} diff --git a/src/drivers/nl_led.h b/src/drivers/nl_led.h new file mode 100644 index 0000000..3d14fd8 --- /dev/null +++ b/src/drivers/nl_led.h @@ -0,0 +1,17 @@ +#ifndef NL_LED_H +#define NL_LED_H + +#include + +#include "board/nl_board.h" + +void nl_led_init(const nl_board_t *board); +void nl_led_set(const uint8_t ch[NL_CH_COUNT]); + +/* back ends */ +void nl_pwm_leds_init(const nl_board_t *board); +void nl_pwm_leds_set(const uint8_t ch[NL_CH_COUNT]); +void nl_sm2135eh_init(const nl_board_t *board); +void nl_sm2135eh_set(const uint8_t ch[NL_CH_COUNT]); + +#endif diff --git a/src/drivers/nl_pwm_leds.c b/src/drivers/nl_pwm_leds.c new file mode 100644 index 0000000..85b2459 --- /dev/null +++ b/src/drivers/nl_pwm_leds.c @@ -0,0 +1,94 @@ +#include + +#include "wm_include.h" +#include "wm_gpio_afsel.h" + +#include "drivers/nl_led.h" + +static const nl_board_t *s_board; +static bool s_on_pwm[NL_CH_COUNT]; /* pin currently routed to the PWM unit */ +static uint8_t s_last[NL_CH_COUNT]; +static bool s_have_last; + +static void route_to_pwm(int8_t ch, uint8_t pin) +{ + enum tls_io_name io = (enum tls_io_name)pin; + switch (ch) { + case 0: wm_pwm1_config(io); break; + case 1: wm_pwm2_config(io); break; + case 2: wm_pwm3_config(io); break; + case 3: wm_pwm4_config(io); break; + case 4: wm_pwm5_config(io); break; + default: break; + } +} + +static void drive_gpio(const nl_pwm_chan_t *c, bool led_on) +{ + bool level = c->active_low ? !led_on : led_on; + tls_gpio_cfg((enum tls_io_name)c->pin, WM_GPIO_DIR_OUTPUT, WM_GPIO_ATTR_FLOATING); + tls_gpio_write((enum tls_io_name)c->pin, level ? 1 : 0); +} + +void nl_pwm_leds_init(const nl_board_t *board) +{ + s_board = board; + s_have_last = false; + for (int i = 0; i < NL_CH_COUNT; i++) { + const nl_pwm_chan_t *c = &board->pwm[i]; + s_on_pwm[i] = false; + if (c->pwm_ch < 0) + continue; + if (c->antiphase) + tls_pwm_ineverse_init((u8)c->pwm_ch, board->pwm_freq_hz, 128, 0); + else + tls_pwm_init((u8)c->pwm_ch, board->pwm_freq_hz, 128, 0); + tls_pwm_start((u8)c->pwm_ch); + drive_gpio(c, false); /* dark until the engine sets a value */ + } +} + +void nl_pwm_leds_set(const uint8_t ch[NL_CH_COUNT]) +{ + u32 cpu_sr; + uint32_t cmp, ch4; + + if (s_have_last && memcmp(ch, s_last, NL_CH_COUNT) == 0) + return; + memcpy(s_last, ch, NL_CH_COUNT); + s_have_last = true; + + /* 1. update all duty cycles at once */ + cpu_sr = tls_os_set_critical(); + cmp = tls_reg_read32(HR_PWM_CMPDAT); + ch4 = tls_reg_read32(HR_PWM_CH4_REG2); + for (int i = 0; i < NL_CH_COUNT; i++) { + const nl_pwm_chan_t *c = &s_board->pwm[i]; + uint8_t v = ch[i], duty; + if (c->pwm_ch < 0 || v == 0 || v == 255) + continue; + duty = (c->active_low != c->antiphase) ? (uint8_t)(255 - v) : v; + if (c->pwm_ch == 4) + ch4 = (ch4 & ~0x0000FF00u) | ((uint32_t)duty << 8); + else + cmp = (cmp & ~(0xFFu << (c->pwm_ch * 8))) | ((uint32_t)duty << (c->pwm_ch * 8)); + } + tls_reg_write32(HR_PWM_CMPDAT, cmp); + tls_reg_write32(HR_PWM_CH4_REG2, ch4); + tls_os_release_critical(cpu_sr); + + /* 2. switch pins between GPIO (0 / 255) and PWM */ + for (int i = 0; i < NL_CH_COUNT; i++) { + const nl_pwm_chan_t *c = &s_board->pwm[i]; + uint8_t v = ch[i]; + if (c->pwm_ch < 0) + continue; + if (v == 0 || v == 255) { + drive_gpio(c, v == 255); + s_on_pwm[i] = false; + } else if (!s_on_pwm[i]) { + route_to_pwm(c->pwm_ch, c->pin); + s_on_pwm[i] = true; + } + } +} diff --git a/src/drivers/nl_sm2135eh.c b/src/drivers/nl_sm2135eh.c new file mode 100644 index 0000000..da0f95e --- /dev/null +++ b/src/drivers/nl_sm2135eh.c @@ -0,0 +1,87 @@ +#include + +#include "wm_include.h" + +#include "drivers/nl_led.h" + +#define SM_ADDR_CURRENT 0xC0u +#define SM_ADDR_WY 0xC5u +#define SM_CHANNEL_WY 0x80u /* channel select bit 7: 0 = RGB, 1 = WY */ + +extern void delay_us(unsigned int us); /* provided by libwlan.a */ + +static uint8_t s_clk, s_dat, s_current; +static uint8_t s_last[NL_CH_COUNT]; +static bool s_have_last; + +static void pin(uint8_t p, int v) +{ + tls_gpio_write((enum tls_io_name)p, (u8)v); +} + +static void bus_start(void) +{ + pin(s_clk, 1); pin(s_dat, 1); delay_us(1); + pin(s_dat, 0); delay_us(1); + pin(s_clk, 0); delay_us(1); +} + +static void bus_stop(void) +{ + pin(s_clk, 0); pin(s_dat, 0); delay_us(1); + pin(s_clk, 1); delay_us(1); + pin(s_dat, 1); delay_us(1); +} + +static void write_byte(uint8_t b) +{ + for (uint8_t m = 0x80; m; m >>= 1) { + pin(s_dat, (b & m) ? 1 : 0); delay_us(1); + pin(s_clk, 1); delay_us(1); + pin(s_clk, 0); delay_us(1); + } + /* ack clock; the SM2135EH does not drive an ack */ + pin(s_dat, 0); delay_us(1); + pin(s_clk, 1); delay_us(1); + pin(s_clk, 0); pin(s_dat, 0); +} + +static void sm_send(const uint8_t *d, int n) +{ + bus_start(); + for (int i = 0; i < n; i++) + write_byte(d[i]); + bus_stop(); +} + +void nl_sm2135eh_init(const nl_board_t *board) +{ + uint8_t off[NL_CH_COUNT] = {0}; + s_clk = board->sm_clk_pin; + s_dat = board->sm_dat_pin; + s_current = board->sm_current; + s_have_last = false; + tls_gpio_cfg((enum tls_io_name)s_clk, WM_GPIO_DIR_OUTPUT, WM_GPIO_ATTR_PULLHIGH); + tls_gpio_cfg((enum tls_io_name)s_dat, WM_GPIO_DIR_OUTPUT, WM_GPIO_ATTR_PULLHIGH); + nl_sm2135eh_set(off); +} + +void nl_sm2135eh_set(const uint8_t ch[NL_CH_COUNT]) +{ + bool white = ch[NL_CH_CW] || ch[NL_CH_WW]; + + if (s_have_last && memcmp(ch, s_last, NL_CH_COUNT) == 0) + return; + memcpy(s_last, ch, NL_CH_COUNT); + s_have_last = true; + + if (!white) { + uint8_t d[6] = {SM_ADDR_CURRENT, s_current, 0x00, ch[NL_CH_B], ch[NL_CH_G], ch[NL_CH_R]}; + sm_send(d, 6); + } else { + uint8_t h[3] = {SM_ADDR_CURRENT, s_current, SM_CHANNEL_WY}; + uint8_t d[3] = {SM_ADDR_WY, ch[NL_CH_WW], ch[NL_CH_CW]}; + sm_send(h, 3); + sm_send(d, 3); + } +} diff --git a/src/sys/nl_app.h b/src/sys/nl_app.h new file mode 100644 index 0000000..548bf54 --- /dev/null +++ b/src/sys/nl_app.h @@ -0,0 +1,15 @@ +#ifndef NL_APP_H +#define NL_APP_H + +#include + +#include "config/nl_settings.h" + +void nl_app_lock(void); +void nl_app_unlock(void); +uint32_t nl_app_now_ms(void); +nl_settings_t *nl_app_settings(void); +int nl_app_save_settings(void); +void nl_app_factory_reset(void); + +#endif diff --git a/src/sys/nl_console.c b/src/sys/nl_console.c new file mode 100644 index 0000000..2b8b61f --- /dev/null +++ b/src/sys/nl_console.c @@ -0,0 +1,154 @@ +#include +#include + +#include "wm_include.h" +#include "wm_watchdog.h" + +#include "light/nl_control.h" +#include "light/nl_engine.h" +#include "sys/nl_app.h" +#include "sys/nl_console.h" +#include "sys/nl_console_cmd.h" + +#define NL_CONSOLE_PRIO 45 +static u32 s_stack[768]; + +static void print_state(void) +{ + nl_light_state_t s; + nl_settings_t *cfg = nl_app_settings(); + nl_app_lock(); + nl_ctrl_get(&s); + nl_app_unlock(); + printf("state: %s bri=%u mode=%s rgb=%u,%u,%u ct=%u | pob=%u fade=%u/%u\r\n", + s.on ? "on" : "off", s.brightness, s.mode == NL_MODE_COLOR ? "color" : "white", + s.r, s.g, s.b, s.ct, cfg->power_on_behaviour, cfg->fade_in_ms, cfg->fade_out_ms); +} + +static void set_transition(nl_light_change_t *ch, const nl_cmd_t *c, uint8_t index) +{ + if (c->argc > index) { + ch->has_transition = true; + ch->transition_ms = (uint32_t)c->arg[index]; + } +} + +static void exec(const nl_cmd_t *c) +{ + nl_light_change_t ch; + nl_ctrl_err_t err; + nl_settings_t *cfg = nl_app_settings(); + + memset(&ch, 0, sizeof ch); + switch (c->type) { + case NL_CMD_NONE: + return; + case NL_CMD_ON: + case NL_CMD_OFF: + ch.has_on = true; + ch.on = c->type == NL_CMD_ON; + set_transition(&ch, c, 0); + break; + case NL_CMD_BRI: + ch.has_brightness = true; + ch.brightness = (uint8_t)c->arg[0]; + set_transition(&ch, c, 1); + break; + case NL_CMD_RGB: + ch.has_color = true; + ch.r = (uint8_t)c->arg[0]; + ch.g = (uint8_t)c->arg[1]; + ch.b = (uint8_t)c->arg[2]; + set_transition(&ch, c, 3); + break; + case NL_CMD_CT: + ch.has_ct = true; + ch.ct = (uint16_t)c->arg[0]; + set_transition(&ch, c, 1); + break; + case NL_CMD_POB: + nl_app_lock(); + cfg->power_on_behaviour = (uint8_t)c->arg[0]; + nl_app_unlock(); + printf(nl_app_save_settings() == 0 ? "ok\r\n" : "error: save failed\r\n"); + print_state(); + return; + case NL_CMD_FADE: + nl_app_lock(); + cfg->fade_in_ms = (uint16_t)c->arg[0]; + cfg->fade_out_ms = (uint16_t)c->arg[1]; + nl_ctrl_set_fades(cfg->fade_in_ms, cfg->fade_out_ms); + nl_app_unlock(); + printf(nl_app_save_settings() == 0 ? "ok\r\n" : "error: save failed\r\n"); + print_state(); + return; + case NL_CMD_STATE: + print_state(); + return; + case NL_CMD_BLINK: + nl_app_lock(); + nl_engine_blink((uint8_t)c->arg[0]); + nl_app_unlock(); + return; + case NL_CMD_PULSE: + nl_app_lock(); + nl_engine_pulse(c->arg[0] != 0); + nl_app_unlock(); + return; + case NL_CMD_FACTORY: + nl_app_factory_reset(); + return; + case NL_CMD_REBOOT: + tls_sys_reset(); + return; + case NL_CMD_HELP: + printf("%s", nl_console_help); + return; + default: + printf("error: unknown command or bad argument (try help)\r\n"); + return; + } + + nl_app_lock(); + err = nl_ctrl_apply(&ch, nl_app_now_ms()); + nl_app_unlock(); + if (err == NL_CTRL_OK) + printf("ok\r\n"); + else + printf("error %d\r\n", (int)err); + print_state(); +} + +static void console_task(void *arg) +{ + char line[64]; + int len = 0; + u8 buf[16]; + + (void)arg; + printf("console ready, type help\r\n"); + for (;;) { + int n = tls_uart_read(TLS_UART_0, buf, sizeof buf); + for (int i = 0; i < n; i++) { + char c = (char)buf[i]; + if (c == '\r' || c == '\n') { + if (len) { + nl_cmd_t cmd; + line[len] = '\0'; + cmd = nl_console_parse(line); + exec(&cmd); + len = 0; + } + } else if (len < (int)sizeof(line) - 1) { + line[len++] = c; + } + } + tls_os_time_delay(HZ / 50); /* 20 ms */ + } +} + +void nl_console_start(void) +{ + tls_uart_port_init(TLS_UART_0, NULL, 0); + tls_os_task_create(NULL, "nl_con", console_task, NULL, (u8 *)s_stack, sizeof(s_stack), NL_CONSOLE_PRIO, 0); +} diff --git a/src/sys/nl_console.h b/src/sys/nl_console.h new file mode 100644 index 0000000..767d380 --- /dev/null +++ b/src/sys/nl_console.h @@ -0,0 +1,7 @@ +#ifndef NL_CONSOLE_H +#define NL_CONSOLE_H + +/* Line based test console on UART0 (115200 8N1), shares the port with printf. */ +void nl_console_start(void); + +#endif