Fixed LED driver bugs, and added serial command file

main
noah metz 2026-07-15 17:28:33 -06:00
parent f23a4bb9af
commit 97445401bf
6 changed files with 229 additions and 33 deletions

@ -83,7 +83,7 @@ endif
# #
# Define project name here # Define project name here
PROJECT = USB_TEST PROJECT = TWINKLE_STICK
# Target settings. # Target settings.
MCU = cortex-m0 MCU = cortex-m0
@ -109,12 +109,7 @@ include $(CHIBIOS)/os/rt/rt.mk
include $(CHIBIOS)/os/common/ports/ARMv6-M/compilers/GCC/mk/port.mk include $(CHIBIOS)/os/common/ports/ARMv6-M/compilers/GCC/mk/port.mk
# Auto-build files in ./source recursively. # Auto-build files in ./source recursively.
include $(CHIBIOS)/tools/mk/autobuild.mk include $(CHIBIOS)/tools/mk/autobuild.mk
# Other files (optional).
include $(CHIBIOS)/os/test/test.mk
include $(CHIBIOS)/test/rt/rt_test.mk
include $(CHIBIOS)/test/oslib/oslib_test.mk
include $(CHIBIOS)/os/hal/lib/streams/streams.mk include $(CHIBIOS)/os/hal/lib/streams/streams.mk
include $(CHIBIOS)/os/various/shell/shell.mk
# Define linker script file here. # Define linker script file here.
LDSCRIPT= $(STARTUPLD)/STM32F072xB.ld LDSCRIPT= $(STARTUPLD)/STM32F072xB.ld
@ -122,7 +117,6 @@ LDSCRIPT= $(STARTUPLD)/STM32F072xB.ld
# C sources that can be compiled in ARM or THUMB mode depending on the global # C sources that can be compiled in ARM or THUMB mode depending on the global
# setting. # setting.
CSRC = $(ALLCSRC) \ CSRC = $(ALLCSRC) \
$(TESTSRC) \
main.c main.c
# C++ sources that can be compiled in ARM or THUMB mode depending on the global # C++ sources that can be compiled in ARM or THUMB mode depending on the global
@ -136,7 +130,7 @@ ASMSRC = $(ALLASMSRC)
ASMXSRC = $(ALLXASMSRC) ASMXSRC = $(ALLXASMSRC)
# Inclusion directories. # Inclusion directories.
INCDIR = $(CONFDIR) $(ALLINC) $(TESTINC) INCDIR = $(CONFDIR) $(ALLINC)
# Define C warning options here. # Define C warning options here.
CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes
@ -192,6 +186,10 @@ flash: $(BUILDDIR)/$(PROJECT).bin
openocd -f tools/flash.cfg openocd -f tools/flash.cfg
rm -f tools/firmware.bin rm -f tools/firmware.bin
framesize: $(BUILDDIR)/$(PROJECT).elf
@base=$$(arm-none-eabi-nm $< | awk '/__heap_base__/{print $$1}'); \
end=$$(arm-none-eabi-nm $< | awk '/__heap_end__/{print $$1}'); \
echo "Framebuffer: $$(( 16#$$end - 16#$$base )) bytes"
# #
# Custom rules # Custom rules
############################################################################## ##############################################################################

@ -1,5 +1,6 @@
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
#include "ch.h" #include "ch.h"
#include "hal.h" #include "hal.h"
@ -7,6 +8,7 @@
#include "usbcfg.h" #include "usbcfg.h"
#include "led.h" #include "led.h"
#include "log.h" #include "log.h"
#include "cmd.h"
/* /*
* HID report sizes (data only, excluding the report ID byte prepended by the * HID report sizes (data only, excluding the report ID byte prepended by the
@ -74,6 +76,54 @@ static void serial_write(const uint8_t *data, size_t len) {
chnWrite((BaseChannel *)&SDU1, data, len); chnWrite((BaseChannel *)&SDU1, data, len);
} }
static void serial_writes(const char *data) {
chnWrite((BaseChannel *)&SDU1, (uint8_t *)data, strlen(data));
}
static void cmd_resize(int argc, char **argv) {
if (argc < 3) {
serial_writes("usage: resize <driver> <count>\r\n");
return;
}
uint8_t driver = (uint8_t)atoi(argv[1]);
uint8_t count = (uint8_t)atoi(argv[2]);
if(driver >= NUM_DRIVERS) {
serial_writes("invalid driver: ");
serial_writes(argv[1]);
serial_writes("\r\n");
return;
}
led_resize(driver, count);
serial_writes("ok\r\n");
}
static void cmd_set(int argc, char **argv) {
if (argc < 3) {
serial_writes("usage: set <driver> <count>\r\n");
return;
}
uint8_t driver = (uint8_t)atoi(argv[1]);
uint8_t color = (uint8_t)atoi(argv[2]);
if(driver >= NUM_DRIVERS) {
serial_writes("invalid driver: ");
serial_writes(argv[1]);
serial_writes("\r\n");
return;
}
if(color >= C_PALETTE_SIZE) {
serial_writes("invalid color: ");
serial_writes(argv[2]);
serial_writes("\r\n");
return;
}
memset(led_drivers[driver].framebuffer, color, led_drivers[driver].num_leds);
}
#define SERIAL_LINE_BUF 128 #define SERIAL_LINE_BUF 128
static THD_WORKING_AREA(waSerial, 512); static THD_WORKING_AREA(waSerial, 512);
@ -101,8 +151,8 @@ static THD_FUNCTION(SerialThread, arg) {
if (ch == '\r' || ch == '\n') { if (ch == '\r' || ch == '\n') {
chnWrite((BaseChannel *)&SDU1, (uint8_t *)"\r\n", 2); chnWrite((BaseChannel *)&SDU1, (uint8_t *)"\r\n", 2);
if (len > 0) { if (len > 0) {
chnWrite((BaseChannel *)&SDU1, (uint8_t *)buf, (size_t)len); buf[len] = '\0';
chnWrite((BaseChannel *)&SDU1, (uint8_t *)"\r\n", 2); cmd_dispatch(buf);
len = 0; len = 0;
} }
chnWrite((BaseChannel *)&SDU1, (uint8_t *)"> ", 2); chnWrite((BaseChannel *)&SDU1, (uint8_t *)"> ", 2);
@ -133,7 +183,7 @@ int main(void) {
usbStart(&USBD1, &usbcfg); usbStart(&USBD1, &usbcfg);
usbConnectBus(&USBD1); usbConnectBus(&USBD1);
chThdCreateStatic(waSerial, sizeof(waSerial), NORMALPRIO+1, SerialThread, NULL); chThdCreateStatic(waSerial, sizeof(waSerial), NORMALPRIO-1, SerialThread, NULL);
led_init(chThdGetSelfX()); led_init(chThdGetSelfX());
@ -142,14 +192,20 @@ int main(void) {
} }
log_init(serial_write); log_init(serial_write);
cmd_init(serial_write);
cmd_register("resize", cmd_resize);
cmd_register("set", cmd_set);
chThdSleepMilliseconds(3000); chThdSleepMilliseconds(3000);
uint8_t color = C_DARK_PURPLE; #define ANIM_FRAMERATE 60
while(true) { systime_t prev_frame = chVTGetSystemTimeX();
color = (color == C_DARK_PURPLE) ? C_DARK_CYAN : C_DARK_PURPLE; systime_t next_frame = chVTGetSystemTimeX();
memset(framebuffer, color, sizeof(framebuffer)); while (true) {
prev_frame = next_frame;
next_frame = chTimeAddX(prev_frame, TIME_US2I(1000000/ANIM_FRAMERATE));
//update animations
led_draw(event_listeners); led_draw(event_listeners);
chThdSleepMilliseconds(500); chThdSleepUntilWindowed(prev_frame, next_frame);
} }
chThdSleepMilliseconds(TIME_INFINITE); chThdSleepMilliseconds(TIME_INFINITE);
} }

@ -0,0 +1,65 @@
#include "cmd.h"
#include <string.h>
#define CMD_TABLE_SIZE 4
typedef struct {
const char *name;
cmd_fn_t fn;
} cmd_entry_t;
static cmd_entry_t table[CMD_TABLE_SIZE];
static cmd_write_fn write_fn;
static uint32_t djb2(const char *str) {
uint32_t hash = 5381;
unsigned char c;
while ((c = (unsigned char)*str++))
hash = ((hash << 5) + hash) + c;
return hash;
}
void cmd_init(cmd_write_fn fn) {
write_fn = fn;
memset(table, 0, sizeof(table));
}
void cmd_register(const char *name, cmd_fn_t fn) {
uint32_t idx = djb2(name) & (CMD_TABLE_SIZE - 1);
for (int i = 0; i < CMD_TABLE_SIZE; i++) {
uint32_t slot = (idx + (uint32_t)i) & (CMD_TABLE_SIZE - 1);
if (table[slot].name == NULL) {
table[slot].name = name;
table[slot].fn = fn;
return;
}
}
while (1) {} // table full
}
void cmd_dispatch(char *line) {
char *argv[8];
int argc = 0;
char *tok = strtok(line, " \t");
while (tok != NULL && argc < 8) {
argv[argc++] = tok;
tok = strtok(NULL, " \t");
}
if (argc == 0) return;
uint32_t idx = djb2(argv[0]) & (CMD_TABLE_SIZE - 1);
for (int i = 0; i < CMD_TABLE_SIZE; i++) {
uint32_t slot = (idx + (uint32_t)i) & (CMD_TABLE_SIZE - 1);
if (table[slot].name == NULL) break;
if (strcmp(table[slot].name, argv[0]) == 0) {
table[slot].fn(argc, argv);
return;
}
}
if (write_fn) {
write_fn((const uint8_t *)"unknown command\r\n", 17);
}
}

@ -0,0 +1,14 @@
#ifndef CMD_H
#define CMD_H
#include <stdint.h>
#include <stddef.h>
typedef void (*cmd_fn_t)(int argc, char **argv);
typedef void (*cmd_write_fn)(const uint8_t *data, size_t len);
void cmd_init(cmd_write_fn fn);
void cmd_register(const char *name, cmd_fn_t fn);
void cmd_dispatch(char *line);
#endif // CMD_H

@ -1,12 +1,14 @@
#include "led.h" #include "led.h"
#include "log.h"
#include "stm32_dma.h" #include "stm32_dma.h"
#include <string.h> #include <string.h>
extern uint8_t __heap_base__[];
extern uint8_t __heap_end__[];
led_ctx_t led_drivers[NUM_DRIVERS]; led_ctx_t led_drivers[NUM_DRIVERS];
ws2812_signal_t signals[C_PALETTE_SIZE] = {0}; ws2812_signal_t signals[C_PALETTE_SIZE] = {0};
dma_ctx_t transfer_dma[2] = {0}; dma_ctx_t transfer_dma[2] = {0};
uint8_t framebuffer[DRIVER_FB_SIZE]; uint8_t * const framebuffer = __heap_base__;
event_listener_t event_listeners[NUM_DRIVERS]; event_listener_t event_listeners[NUM_DRIVERS];
led_t palette[C_PALETTE_SIZE] = { led_t palette[C_PALETTE_SIZE] = {
@ -115,7 +117,7 @@ int setup_led_transfer(led_ctx_t * driver) {
driver->done = 0; driver->done = 0;
driver->tx_cnt = 0; driver->tx_cnt = 0;
driver->buf_cnt = 0; driver->buf_cnt = 0;
driver->tx_led_cnt = LEDS_PER_DRIVER; driver->tx_led_cnt = driver->num_leds;
// Setup the first LEDS_PER_IRQ leds // Setup the first LEDS_PER_IRQ leds
driver->dma_buf_ptr = &driver->dma_buf[0]; driver->dma_buf_ptr = &driver->dma_buf[0];
fill_dmabuf(driver); fill_dmabuf(driver);
@ -169,15 +171,22 @@ PWMConfig pwmcfg = {
0 0
}; };
extern led_cfg_t led_cfgs[NUM_DRIVERS];
void driver_init(led_cfg_t * cfg, thread_t * draw_thread) { void driver_init(led_cfg_t * cfg, thread_t * draw_thread) {
if(cfg->num > NUM_DRIVERS){ if(cfg->num >= NUM_DRIVERS){
chSysHalt("Tried to initialize a driver outside the max driver range"); chSysHalt("Tried to initialize a driver outside the max driver range");
} }
led_ctx_t * driver = &led_drivers[cfg->num]; led_ctx_t * driver = &led_drivers[cfg->num];
driver->num = cfg->num; driver->num = cfg->num;
// Set the driver framebuffer to the location in the global framebuffer driver->num_leds = cfg->num_leds;
driver->framebuffer = &framebuffer[cfg->num * LEDS_PER_DRIVER];
uint32_t offset = 0;
for (uint8_t i = 0; i < cfg->num; i++) {
offset += led_cfgs[i].num_leds;
}
driver->framebuffer = framebuffer + offset;
driver->pwm_driver = cfg->pwm_driver; driver->pwm_driver = cfg->pwm_driver;
driver->tim_channel = cfg->tim_channel; driver->tim_channel = cfg->tim_channel;
driver->draw_thread = draw_thread; driver->draw_thread = draw_thread;
@ -200,12 +209,12 @@ void driver_init(led_cfg_t * cfg, thread_t * draw_thread) {
pwmEnableChannel(driver->pwm_driver, driver->tim_channel, 0); pwmEnableChannel(driver->pwm_driver, driver->tim_channel, 0);
} }
led_cfg_t led_cfgs[5] = { led_cfg_t led_cfgs[NUM_DRIVERS] = {
{STM32_DMA_STREAM_ID(1, 5), &PWMD15, 0, GPIOA, 2, 0, 0}, {STM32_DMA_STREAM_ID(1, 4), &PWMD3, 0, GPIOB, 4, 1, 0, 10},
{STM32_DMA_STREAM_ID(1, 3), &PWMD16, 0, GPIOA, 6, 5, 1}, {STM32_DMA_STREAM_ID(1, 5), &PWMD15, 0, GPIOA, 2, 0, 1, 10},
{STM32_DMA_STREAM_ID(1, 1), &PWMD17, 0, GPIOA, 7, 5, 2}, {STM32_DMA_STREAM_ID(1, 3), &PWMD16, 0, GPIOA, 6, 5, 2, 10},
{STM32_DMA_STREAM_ID(1, 2), &PWMD1, 0, GPIOA, 8, 2, 3}, {STM32_DMA_STREAM_ID(1, 1), &PWMD17, 0, GPIOA, 7, 5, 3, 10},
{STM32_DMA_STREAM_ID(1, 4), &PWMD3, 0, GPIOB, 4, 1, 4}, {STM32_DMA_STREAM_ID(1, 2), &PWMD1, 0, GPIOA, 8, 2, 4, 10},
}; };
ws2812_signal_t convert_palette(led_t * colour) { ws2812_signal_t convert_palette(led_t * colour) {
@ -229,6 +238,14 @@ ws2812_signal_t convert_palette(led_t * colour) {
} }
void led_init(thread_t * draw_thread) { void led_init(thread_t * draw_thread) {
size_t total_leds = 0;
for (size_t i = 0; i < NUM_DRIVERS; i++) {
total_leds += led_cfgs[i].num_leds;
}
if (total_leds > (size_t)((uint8_t *)__heap_end__ - (uint8_t *)__heap_base__)) {
chSysHalt(NULL);
}
for(size_t i = 0; i < C_PALETTE_SIZE; i++) { for(size_t i = 0; i < C_PALETTE_SIZE; i++) {
signals[i] = convert_palette(&palette[i]); signals[i] = convert_palette(&palette[i]);
} }
@ -246,6 +263,8 @@ void led_init(thread_t * draw_thread) {
if(transfer_dma[0].stream == NULL || transfer_dma[1].stream == NULL) { if(transfer_dma[0].stream == NULL || transfer_dma[1].stream == NULL) {
chSysHalt("Failed to allocate DMA streams for led transfers"); chSysHalt("Failed to allocate DMA streams for led transfers");
} }
memset(framebuffer, 0x00, total_leds);
} }
void led_draw(event_listener_t* event_listeners) { void led_draw(event_listener_t* event_listeners) {
@ -278,3 +297,46 @@ void led_draw(event_listener_t* event_listeners) {
} }
} }
} }
size_t led_fb_used(void) {
size_t total = 0;
for (uint8_t i = 0; i < NUM_DRIVERS; i++) {
total += led_drivers[i].num_leds;
}
return total;
}
void led_resize(uint8_t driver_num, uint8_t new_num_leds) {
if (driver_num >= NUM_DRIVERS) return;
size_t new_total = 0;
size_t old_total = led_drivers[driver_num].num_leds;
for (uint8_t i = 0; i < NUM_DRIVERS; i++) {
new_total += (i == driver_num) ? new_num_leds : led_drivers[i].num_leds;
}
if (new_total > (size_t)((uint8_t *)__heap_end__ - (uint8_t *)__heap_base__)) return;
uint32_t new_offsets[NUM_DRIVERS];
uint32_t off = 0;
for (uint8_t i = 0; i < NUM_DRIVERS; i++) {
new_offsets[i] = off;
off += (i == driver_num) ? new_num_leds : led_drivers[i].num_leds;
}
if (new_num_leds >= led_drivers[driver_num].num_leds) {
for (int i = (int)NUM_DRIVERS - 1; i > (int)driver_num; i--) {
memmove(framebuffer + new_offsets[i], led_drivers[i].framebuffer, led_drivers[i].num_leds);
}
} else {
for (uint8_t i = driver_num + 1; i < NUM_DRIVERS; i++) {
memmove(framebuffer + new_offsets[i], led_drivers[i].framebuffer, led_drivers[i].num_leds);
}
}
led_drivers[driver_num].num_leds = new_num_leds;
for (uint8_t i = driver_num; i < NUM_DRIVERS; i++) {
led_drivers[i].framebuffer = framebuffer + new_offsets[i];
}
memset(led_drivers[driver_num].framebuffer + old_total, 0, new_total - old_total);
}

@ -17,7 +17,7 @@
#define SIG_HIGH (((PWM_PERIOD * 3) / 4) - 1) #define SIG_HIGH (((PWM_PERIOD * 3) / 4) - 1)
#define SIG_RESET (PWM_PERIOD - 1) #define SIG_RESET (PWM_PERIOD - 1)
#define LEDS_PER_IRQ (30) #define LEDS_PER_IRQ (8)
#define BYTES_PER_LED 3 #define BYTES_PER_LED 3
#define BITS_PER_LED (8*BYTES_PER_LED) #define BITS_PER_LED (8*BYTES_PER_LED)
#define DMA_BUF_HALF (LEDS_PER_IRQ * BITS_PER_LED) #define DMA_BUF_HALF (LEDS_PER_IRQ * BITS_PER_LED)
@ -32,9 +32,6 @@
#define LED_EVENT_BUFRDY LED_EVENT(1) #define LED_EVENT_BUFRDY LED_EVENT(1)
#define NUM_DRIVERS 5 #define NUM_DRIVERS 5
#define LEDS_PER_DRIVER 14
#define DRIVER_FB_SIZE (LEDS_PER_DRIVER*NUM_DRIVERS)
typedef struct { typedef struct {
uint8_t g; uint8_t g;
@ -87,6 +84,7 @@ typedef struct led_ctx {
stm32_gpio_t * port; stm32_gpio_t * port;
uint8_t pin; uint8_t pin;
uint8_t * framebuffer; uint8_t * framebuffer;
uint8_t num_leds;
uint8_t num; uint8_t num;
thread_t * draw_thread; thread_t * draw_thread;
} led_ctx_t; } led_ctx_t;
@ -99,6 +97,7 @@ typedef struct led_cfg {
uint8_t pin; uint8_t pin;
uint8_t af; uint8_t af;
uint8_t num; uint8_t num;
uint8_t num_leds;
} led_cfg_t; } led_cfg_t;
typedef struct dma_ctx { typedef struct dma_ctx {
@ -108,7 +107,7 @@ typedef struct dma_ctx {
} dma_ctx_t; } dma_ctx_t;
extern led_ctx_t led_drivers[NUM_DRIVERS]; extern led_ctx_t led_drivers[NUM_DRIVERS];
extern uint8_t framebuffer[NUM_DRIVERS*LEDS_PER_DRIVER]; extern uint8_t * const framebuffer;
extern led_t palette[C_PALETTE_SIZE]; extern led_t palette[C_PALETTE_SIZE];
extern event_listener_t event_listeners[NUM_DRIVERS]; extern event_listener_t event_listeners[NUM_DRIVERS];
@ -118,5 +117,7 @@ int setup_led_transfer(led_ctx_t * driver);
void led_init(thread_t * draw_thread); void led_init(thread_t * draw_thread);
void led_draw(event_listener_t* event_listeners); void led_draw(event_listener_t* event_listeners);
void fill_dmabuf(led_ctx_t * driver); void fill_dmabuf(led_ctx_t * driver);
void led_resize(uint8_t driver_num, uint8_t new_num_leds);
size_t led_fb_used(void);
#endif // LED_H #endif // LED_H