From f23a4bb9af858b6b154722d9ff86229c8c7283fc Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Fri, 10 Jul 2026 14:14:37 -0600 Subject: [PATCH] Fixed firmware to support multiple LED drivers --- Makefile | 2 +- cfg/halconf.h | 2 +- cfg/mcuconf.h | 20 +++- main.c | 27 ++++- source/led.c | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++ source/led.h | 122 ++++++++++++++++++++++ source/log.c | 21 ++++ source/log.h | 17 +++ 8 files changed, 484 insertions(+), 7 deletions(-) create mode 100644 source/led.c create mode 100644 source/led.h create mode 100644 source/log.c create mode 100644 source/log.h diff --git a/Makefile b/Makefile index 84f8421..1b9a2a2 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ endif # Stack size to be allocated to the Cortex-M process stack. This stack is # the stack used by the main() thread. ifeq ($(USE_PROCESS_STACKSIZE),) - USE_PROCESS_STACKSIZE = 0x400 + USE_PROCESS_STACKSIZE = 0x200 endif # Stack size to the allocated to the Cortex-M main/exceptions stack. This diff --git a/cfg/halconf.h b/cfg/halconf.h index 3807352..be80513 100644 --- a/cfg/halconf.h +++ b/cfg/halconf.h @@ -121,7 +121,7 @@ * @brief Enables the PWM subsystem. */ #if !defined(HAL_USE_PWM) || defined(__DOXYGEN__) -#define HAL_USE_PWM FALSE +#define HAL_USE_PWM TRUE #endif /** diff --git a/cfg/mcuconf.h b/cfg/mcuconf.h index 78ae801..67f0248 100644 --- a/cfg/mcuconf.h +++ b/cfg/mcuconf.h @@ -60,6 +60,11 @@ #define STM32_USART1SW STM32_USART1SW_PCLK #define STM32_RTCSEL STM32_RTCSEL_LSI +/* + * Ensure DMA is compiled + */ +#define STM32_DMA_REQUIRED TRUE + /* * IRQ system settings. */ @@ -164,13 +169,22 @@ /* * PWM driver system settings. */ -#define STM32_PWM_USE_ADVANCED FALSE -#define STM32_PWM_USE_TIM1 FALSE +#define STM32_PWM_USE_ADVANCED TRUE +#define STM32_PWM_USE_TIM1 TRUE #define STM32_PWM_USE_TIM2 FALSE -#define STM32_PWM_USE_TIM3 FALSE +#define STM32_PWM_USE_TIM3 TRUE +#define STM32_PWM_USE_TIM15 TRUE +#define STM32_PWM_USE_TIM16 TRUE +#define STM32_PWM_USE_TIM17 TRUE #define STM32_PWM_TIM1_IRQ_PRIORITY 3 #define STM32_PWM_TIM2_IRQ_PRIORITY 3 #define STM32_PWM_TIM3_IRQ_PRIORITY 3 +#define STM32_PWM_TIM15_IRQ_PRIORITY 3 +#define STM32_PWM_TIM16_IRQ_PRIORITY 3 +#define STM32_PWM_TIM17_IRQ_PRIORITY 3 +#define STM32_TIM15_SUPPRESS_ISR TRUE +#define STM32_TIM16_SUPPRESS_ISR TRUE +#define STM32_TIM17_SUPPRESS_ISR TRUE /* * SERIAL driver system settings. diff --git a/main.c b/main.c index d5ccf4c..85f03c8 100644 --- a/main.c +++ b/main.c @@ -5,6 +5,8 @@ #include "hal.h" #include "usbcfg.h" +#include "led.h" +#include "log.h" /* * HID report sizes (data only, excluding the report ID byte prepended by the @@ -67,6 +69,11 @@ void isr_hid_set_report(uint8_t id, const uint8_t *buf, size_t len) { } } + +static void serial_write(const uint8_t *data, size_t len) { + chnWrite((BaseChannel *)&SDU1, data, len); +} + #define SERIAL_LINE_BUF 128 static THD_WORKING_AREA(waSerial, 512); @@ -126,7 +133,23 @@ int main(void) { usbStart(&USBD1, &usbcfg); usbConnectBus(&USBD1); - chThdCreateStatic(waSerial, sizeof(waSerial), NORMALPRIO, SerialThread, NULL); + chThdCreateStatic(waSerial, sizeof(waSerial), NORMALPRIO+1, SerialThread, NULL); + + led_init(chThdGetSelfX()); - chThdSleep(TIME_INFINITE); + for (uint32_t i = 0; i < NUM_DRIVERS; i++) { + chEvtRegisterMask(&led_drivers[i].event_source, &event_listeners[i], EVENT_MASK(i)); + } + + log_init(serial_write); + chThdSleepMilliseconds(3000); + + uint8_t color = C_DARK_PURPLE; + while(true) { + color = (color == C_DARK_PURPLE) ? C_DARK_CYAN : C_DARK_PURPLE; + memset(framebuffer, color, sizeof(framebuffer)); + led_draw(event_listeners); + chThdSleepMilliseconds(500); + } + chThdSleepMilliseconds(TIME_INFINITE); } diff --git a/source/led.c b/source/led.c new file mode 100644 index 0000000..cfda108 --- /dev/null +++ b/source/led.c @@ -0,0 +1,280 @@ +#include "led.h" +#include "log.h" +#include "stm32_dma.h" +#include + +led_ctx_t led_drivers[NUM_DRIVERS]; +ws2812_signal_t signals[C_PALETTE_SIZE] = {0}; +dma_ctx_t transfer_dma[2] = {0}; +uint8_t framebuffer[DRIVER_FB_SIZE]; +event_listener_t event_listeners[NUM_DRIVERS]; + +led_t palette[C_PALETTE_SIZE] = { + {0, 0, 0}, // C_OFF + {10, 0, 0}, // C_DARK_GREEN + {0, 10, 0}, // C_DARK_RED + {0, 0, 10}, // C_DARK_BLUE + {0, 6, 6}, // C_DARK_PURPLE + {6, 0, 6}, // C_DARK_CYAN + {3, 3, 3}, // C_DARK_WHITE + {100, 0, 0}, // C_GREEN + {0, 100, 0}, // C_RED + {0, 0, 100}, // C_BLUE + {255, 255, 255}, // C_WHITE + {2, 2, 2}, // C_GRAY +}; + +void dma_copy_led(uint8_t palette_index, uint8_t * buf); + +void fill_dmabuf(led_ctx_t * driver) { + uint32_t curr = 0; + for(uint32_t i = 0; i < LEDS_PER_IRQ; i++) { + curr = driver->buf_cnt + i; + if(curr < RESET_PRE){ + memset(driver->dma_buf_ptr + (i*BITS_PER_LED), 0x00, BITS_PER_LED); + } else if(curr < (driver->tx_led_cnt + RESET_PRE)) { + uint32_t led_index = curr - RESET_PRE; + uint8_t palette_idx = driver->framebuffer[led_index]; + uint8_t * buf = driver->dma_buf_ptr + (i*BITS_PER_LED); + uint32_t cndtr0, cndtr1; + size_t dma_idx; + // Loop until a DMA is ready + while(true) { + // Check if transfer_dma 0 is ready + cndtr0 = transfer_dma[0].stream->channel->CNDTR; + if(cndtr0 == 0) { + dma_idx = 0; + dmaStreamDisable(transfer_dma[0].stream); + break; + } + // Check if transfer_dma 1 is ready + cndtr1 = transfer_dma[1].stream->channel->CNDTR; + if(cndtr1 == 0) { + dma_idx = 1; + dmaStreamDisable(transfer_dma[1].stream); + break; + } + } + dmaStartMemCopy(transfer_dma[dma_idx].stream, STM32_DMA_CR_PSIZE_WORD | STM32_DMA_CR_MSIZE_WORD | STM32_DMA_CR_PL(2), signals[palette_idx].bit_sigs, buf, BITS_PER_LED/4); + } else { + uint8_t * ptr = driver->dma_buf_ptr + (i*BITS_PER_LED); + memset(ptr, 0x00, DMA_BUF_HALF - (i*BITS_PER_LED)); + break; + } + } + driver->buf_cnt += LEDS_PER_IRQ; +} + +void dma_irq(void * args, uint32_t flags) { + chSysLockFromISR(); + led_ctx_t * driver = args; + + driver->tx_cnt += LEDS_PER_IRQ; + + if(flags & STM32_DMA_ISR_HTIF) { // during HT the first half is done, so overwrite it + driver->dma_buf_ptr = &driver->dma_buf[0]; + } else if(flags & STM32_DMA_ISR_TCIF) { // during TC the second half is done, so overwrite it + driver->dma_buf_ptr = &driver->dma_buf[DMA_BUF_HALF]; + } else if(flags & STM32_DMA_ISR_TEIF) { // Error flag + chSysHalt("dma error interrupt flag"); + } else { + chSysHalt("dma isr called without flags"); + } + + // If the interrupt was called after the number of LEDs needed has been written, + // stop the transfer and signal the thread + if(driver->tx_cnt >= (driver->tx_led_cnt + RESET_PRE)) { + driver->dma_stream->channel->CCR &= ~(STM32_DMA_CR_EN); + // Disable timer DMA request and pull CCR low so the WS2812B sees a reset pulse + switch(driver->tim_channel) { + case 0: driver->pwm_driver->tim->DIER &= ~STM32_TIM_DIER_CC1DE; break; + case 1: driver->pwm_driver->tim->DIER &= ~STM32_TIM_DIER_CC2DE; break; + case 2: driver->pwm_driver->tim->DIER &= ~STM32_TIM_DIER_CC3DE; break; + case 3: driver->pwm_driver->tim->DIER &= ~STM32_TIM_DIER_CC4DE; break; + } + driver->pwm_driver->tim->CCR[driver->tim_channel] = 0; + driver->upd_lck = 0; + driver->done = 0; + driver->tx_cnt = 0; + driver->tx_led_cnt = 0; + chEvtBroadcastFlagsI(&driver->event_source, LED_EVENT_DONE); + } else { // otherwise fill the dma buffer with new leds + chEvtBroadcastFlagsI(&driver->event_source, LED_EVENT_BUFRDY); + } + + chSysUnlockFromISR(); +} + +int setup_led_transfer(led_ctx_t * driver) { + if(driver->upd_lck) { + return 1; + } + + // Setup the transfer buffer, and initialize the current count + driver->upd_lck = 1; + driver->done = 0; + driver->tx_cnt = 0; + driver->buf_cnt = 0; + driver->tx_led_cnt = LEDS_PER_DRIVER; + // Setup the first LEDS_PER_IRQ leds + driver->dma_buf_ptr = &driver->dma_buf[0]; + fill_dmabuf(driver); + driver->dma_buf_ptr = &driver->dma_buf[DMA_BUF_HALF]; + fill_dmabuf(driver); + + // Wait for all pending transfer_dma copies to land before the PWM DMA starts reading + while (transfer_dma[0].stream->channel->CNDTR != 0 || + transfer_dma[1].stream->channel->CNDTR != 0) {} + + // Set DMA circular + dmaStreamSetTransactionSize(driver->dma_stream, DMA_BUF_LEN); + dmaStreamSetMemory0(driver->dma_stream, driver->dma_buf); + dmaStreamClearInterrupt(driver->dma_stream); + return 0; +} + +int start_led_transfer(led_ctx_t * driver) { + driver->dma_stream->channel->CCR |= STM32_DMA_CR_CIRC | STM32_DMA_CR_HTIE | STM32_DMA_CR_TCIE | STM32_DMA_CR_EN; + switch(driver->tim_channel) { + case 0: + driver->pwm_driver->tim->DIER |= STM32_TIM_DIER_CC1DE; + break; + case 1: + driver->pwm_driver->tim->DIER |= STM32_TIM_DIER_CC2DE; + break; + case 2: + driver->pwm_driver->tim->DIER |= STM32_TIM_DIER_CC3DE; + break; + case 3: + driver->pwm_driver->tim->DIER |= STM32_TIM_DIER_CC4DE; + break; + } + driver->pwm_driver->tim->CCR[driver->tim_channel] = 0; + + return 0; +} + +PWMConfig pwmcfg = { + PWM_TIM_FREQ, + PWM_PERIOD, // PWM Period of 800 kHz + NULL, + { + {PWM_OUTPUT_ACTIVE_HIGH, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL} + }, + 0, + 0, + 0 + }; + +void driver_init(led_cfg_t * cfg, thread_t * draw_thread) { + if(cfg->num > NUM_DRIVERS){ + chSysHalt("Tried to initialize a driver outside the max driver range"); + } + + led_ctx_t * driver = &led_drivers[cfg->num]; + driver->num = cfg->num; + // Set the driver framebuffer to the location in the global framebuffer + driver->framebuffer = &framebuffer[cfg->num * LEDS_PER_DRIVER]; + driver->pwm_driver = cfg->pwm_driver; + driver->tim_channel = cfg->tim_channel; + driver->draw_thread = draw_thread; + chEvtObjectInit(&driver->event_source); + driver->dma_stream = dmaStreamAlloc(cfg->dma_stream_id, 0, dma_irq, driver); + if(driver->dma_stream == NULL) { + chSysHalt("Failed to allocate DMA stream for driver"); + } + + pwmStart(cfg->pwm_driver, &pwmcfg); + cfg->pwm_driver->tim->CR1 |= STM32_TIM_CR1_ARPE; + cfg->pwm_driver->tim->CR2 |= STM32_TIM_CR2_CCDS; + + dmaStreamSetPeripheral(driver->dma_stream, &(cfg->pwm_driver->tim->CCR[cfg->tim_channel])); + dmaStreamSetMode(driver->dma_stream, STM32_DMA_CR_DIR_M2P | STM32_DMA_CR_PL(1) | STM32_DMA_CR_MINC | STM32_DMA_CR_PSIZE_HWORD | STM32_DMA_CR_MSIZE_BYTE); + + driver->port = cfg->port; + driver->pin = cfg->pin; + palSetPadMode(driver->port, driver->pin, PAL_MODE_ALTERNATE(cfg->af)); + pwmEnableChannel(driver->pwm_driver, driver->tim_channel, 0); +} + +led_cfg_t led_cfgs[5] = { + {STM32_DMA_STREAM_ID(1, 5), &PWMD15, 0, GPIOA, 2, 0, 0}, + {STM32_DMA_STREAM_ID(1, 3), &PWMD16, 0, GPIOA, 6, 5, 1}, + {STM32_DMA_STREAM_ID(1, 1), &PWMD17, 0, GPIOA, 7, 5, 2}, + {STM32_DMA_STREAM_ID(1, 2), &PWMD1, 0, GPIOA, 8, 2, 3}, + {STM32_DMA_STREAM_ID(1, 4), &PWMD3, 0, GPIOB, 4, 1, 4}, +}; + +ws2812_signal_t convert_palette(led_t * colour) { + ws2812_signal_t ret = {0}; + memset(ret.bit_sigs, SIG_LOW, BITS_PER_LED); + for(uint32_t i = 0; i < 8; i++) { + if(colour->g & (1 << i)) { + ret.bit_sigs[7 - i] = SIG_HIGH; + } + + if(colour->r & (1 << i)) { + ret.bit_sigs[15 - i] = SIG_HIGH; + } + + if(colour->b & (1 << i)) { + ret.bit_sigs[23 - i] = SIG_HIGH; + } + } + + return ret; +} + +void led_init(thread_t * draw_thread) { + for(size_t i = 0; i < C_PALETTE_SIZE; i++) { + signals[i] = convert_palette(&palette[i]); + } + + for(size_t i = 0; i < NUM_DRIVERS; i++) { + driver_init(&led_cfgs[i], draw_thread); + } + + transfer_dma[0].id = 0; + transfer_dma[0].stream = dmaStreamAlloc(STM32_DMA_STREAM_ID(1, 6), 3, NULL, NULL); + + transfer_dma[1].id = 1; + transfer_dma[1].stream = dmaStreamAlloc(STM32_DMA_STREAM_ID(1, 7), 3, NULL, NULL); + + if(transfer_dma[0].stream == NULL || transfer_dma[1].stream == NULL) { + chSysHalt("Failed to allocate DMA streams for led transfers"); + } +} + +void led_draw(event_listener_t* event_listeners) { + uint32_t draws_done = 0; + eventmask_t evt = {0}; + + for(uint32_t driver_index = 0; driver_index < NUM_DRIVERS; driver_index++) { + setup_led_transfer(&led_drivers[driver_index]); + } + for(uint32_t driver_index = 0; driver_index < NUM_DRIVERS; driver_index++) { + start_led_transfer(&led_drivers[driver_index]); + for(uint32_t i = 0; i < 1000; i++) { + __asm volatile("NOP"); + } + } + + // wait for all the drivers to finish drawing + while(draws_done < NUM_DRIVERS) { + evt = chEvtWaitAny(ALL_EVENTS); + for(uint8_t index = 0; index < NUM_DRIVERS; index++) { + if(!(evt & EVENT_MASK(index))) { + continue; + } + eventflags_t flags = chEvtGetAndClearFlags(&event_listeners[index]); + if(flags & LED_EVENT_DONE) { + draws_done += 1; + } else if(flags & LED_EVENT_BUFRDY) { + fill_dmabuf(&led_drivers[index]); + } + } + } +} diff --git a/source/led.h b/source/led.h new file mode 100644 index 0000000..acc8a6f --- /dev/null +++ b/source/led.h @@ -0,0 +1,122 @@ +#ifndef LED_H +#define LED_H + +#include "ch.h" +#include "hal.h" +#include + +#define PWM_TIM_FREQ STM32_PCLK +#define PWM_FREQ 800000 +#define PWM_PERIOD (PWM_TIM_FREQ/PWM_FREQ) + +#define ACT_PWM_TIM_FREQ STM32_PCLK +#define ACT_PWM_FREQ 1 +#define ACT_PWM_PERIOD (ACT_PWM_TIM_FREQ/ACT_PWM_FREQ) + +#define SIG_LOW (((PWM_PERIOD * 1) / 4) - 1) +#define SIG_HIGH (((PWM_PERIOD * 3) / 4) - 1) +#define SIG_RESET (PWM_PERIOD - 1) + +#define LEDS_PER_IRQ (30) +#define BYTES_PER_LED 3 +#define BITS_PER_LED (8*BYTES_PER_LED) +#define DMA_BUF_HALF (LEDS_PER_IRQ * BITS_PER_LED) +#define DMA_BUF_LEN (2 * DMA_BUF_HALF) +#define RESET_PERIOD_US (25) +#define LED_UNIT_US ((BITS_PER_LED * 1000000) / PWM_FREQ) // one "led" is BITS_PER_LED bits / PWM_FREQ seconds long +#define RESET_UNITS ((((RESET_PERIOD_US*10) / (LED_UNIT_US)) / 10)) +#define RESET_PRE 2 + +#define LED_EVENT(x) ((eventflags_t)(1 << x)) +#define LED_EVENT_DONE LED_EVENT(0) +#define LED_EVENT_BUFRDY LED_EVENT(1) + +#define NUM_DRIVERS 5 +#define LEDS_PER_DRIVER 14 + +#define DRIVER_FB_SIZE (LEDS_PER_DRIVER*NUM_DRIVERS) + +typedef struct { + uint8_t g; + uint8_t r; + uint8_t b; +} led_t; + +typedef enum { + C_OFF = 0, + C_DARK_GREEN = 1, + C_DARK_RED = 2, + C_DARK_BLUE = 3, + C_DARK_PURPLE = 4, + C_DARK_CYAN = 5, + C_DARK_WHITE = 6, + C_GREEN = 7, + C_RED = 8, + C_BLUE = 9, + C_WHITE = 10, + C_GRAY = 11, + C_PALETTE_SIZE = 12, +} palette_colour_t; + +typedef struct led_ctx led_ctx_t; + +typedef void (*led_done_callback_t)(led_ctx_t * driver); + +typedef struct { + uint8_t bit_sigs[BITS_PER_LED]; +} ws2812_signal_t; + +// Instance of LED driver +typedef struct led_ctx { + // 1 if the leds are currently updating, 0 if the driver is idle + volatile int upd_lck; + // dma storage + uint8_t dma_buf[DMA_BUF_LEN]; + uint8_t * dma_buf_ptr; + // number of leds(reset and real) that have been transmitted + uint32_t tx_cnt; + // number of real and reset leds that have been commited to the buffer + uint32_t buf_cnt; + // number of leds(reset and real) to be transmitted + uint32_t tx_led_cnt; + const stm32_dma_stream_t * dma_stream; + PWMDriver * pwm_driver; + uint8_t tim_channel; + uint8_t done; + event_source_t event_source; + stm32_gpio_t * port; + uint8_t pin; + uint8_t * framebuffer; + uint8_t num; + thread_t * draw_thread; +} led_ctx_t; + +typedef struct led_cfg { + uint32_t dma_stream_id; + PWMDriver * pwm_driver; + uint8_t tim_channel; + stm32_gpio_t * port; + uint8_t pin; + uint8_t af; + uint8_t num; +} led_cfg_t; + +typedef struct dma_ctx { + uint8_t id; + volatile bool done; + const stm32_dma_stream_t * stream; +} dma_ctx_t; + +extern led_ctx_t led_drivers[NUM_DRIVERS]; +extern uint8_t framebuffer[NUM_DRIVERS*LEDS_PER_DRIVER]; +extern led_t palette[C_PALETTE_SIZE]; +extern event_listener_t event_listeners[NUM_DRIVERS]; + +int start_led_transfer(led_ctx_t * driver); +int setup_led_transfer(led_ctx_t * driver); + +void led_init(thread_t * draw_thread); +void led_draw(event_listener_t* event_listeners); +void fill_dmabuf(led_ctx_t * driver); + +#endif // LED_H diff --git a/source/log.c b/source/log.c new file mode 100644 index 0000000..f00885e --- /dev/null +++ b/source/log.c @@ -0,0 +1,21 @@ +#include "log.h" +#include + +static log_write_fn write_fn = NULL; +static const char level_chars[] = "DIWE"; + +void log_init(log_write_fn fn) { + write_fn = fn; +} + +void log_msg(uint8_t level, const char *cat, const char *msg) { + if (!write_fn) return; + char lc = (level < 4) ? level_chars[level] : '?'; + write_fn((const uint8_t *)"[", 1); + write_fn((const uint8_t *)&lc, 1); + write_fn((const uint8_t *)"][", 2); + write_fn((const uint8_t *)cat, strlen(cat)); + write_fn((const uint8_t *)"] ", 2); + write_fn((const uint8_t *)msg, strlen(msg)); + write_fn((const uint8_t *)"\r\n", 2); +} diff --git a/source/log.h b/source/log.h new file mode 100644 index 0000000..873dcc8 --- /dev/null +++ b/source/log.h @@ -0,0 +1,17 @@ +#ifndef LOG_H +#define LOG_H + +#include +#include + +#define LOG_DEBUG 0 +#define LOG_INFO 1 +#define LOG_WARN 2 +#define LOG_ERROR 3 + +typedef void (*log_write_fn)(const uint8_t *data, size_t len); + +void log_init(log_write_fn fn); +void log_msg(uint8_t level, const char *cat, const char *msg); + +#endif // LOG_H