feat: LED-Treiber, Licht-Task und UART-Testkonsole

This commit is contained in:
Michael Sauer
2026-09-11 13:51:31 +02:00
parent 1074a8f034
commit 757620f222
11 changed files with 530 additions and 10 deletions
+87 -7
View File
@@ -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);
}
+7
View File
@@ -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; }
+24
View File
@@ -0,0 +1,24 @@
#include "drivers/nl_led.h"
#include <stddef.h>
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);
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef NL_LED_H
#define NL_LED_H
#include <stdint.h>
#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
+94
View File
@@ -0,0 +1,94 @@
#include <string.h>
#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;
}
}
}
+87
View File
@@ -0,0 +1,87 @@
#include <string.h>
#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);
}
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef NL_APP_H
#define NL_APP_H
#include <stdint.h>
#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
+154
View File
@@ -0,0 +1,154 @@
#include <stdio.h>
#include <string.h>
#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);
}
+7
View File
@@ -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