From 3fa39721d79a7548c34ed2a486e81636287bb0e1 Mon Sep 17 00:00:00 2001 From: cnlohr <lohr85@gmail.com> Date: Mon, 27 Feb 2023 06:06:31 -0500 Subject: [PATCH] Update ws2812b demo to be more robust. --- ch32v003evt/startup_ch32v003.c | 41 ++++++----- examples/ws2812demo/README.md | 36 ++++++++-- .../ws2812demo/ws2812b_dma_spi_led_driver.h | 72 ++++++++++--------- examples/ws2812demo/ws2812bdemo.c | 68 +++++++++--------- 4 files changed, 128 insertions(+), 89 deletions(-) diff --git a/ch32v003evt/startup_ch32v003.c b/ch32v003evt/startup_ch32v003.c index af0f40c..8e486b8 100644 --- a/ch32v003evt/startup_ch32v003.c +++ b/ch32v003evt/startup_ch32v003.c @@ -9,7 +9,7 @@ int main() __attribute__((used)); void SystemInit( void ) __attribute__((used)); -void _start() __attribute__((naked)) __attribute((section(".init"))) __attribute__((used)); +void InterruptVector() __attribute__((naked)) __attribute((section(".init"))) __attribute__((used)); void handle_reset() __attribute__((naked)) __attribute((section(".text.handle_reset"))) __attribute__((used)); void DefaultIRQHandler( void ) __attribute__((section(".text.vector_handler"))) __attribute__((naked)) __attribute__((used)); @@ -55,7 +55,7 @@ void TIM1_TRG_COM_IRQHandler( void ) __attribute__((section(".text.vector_ha void TIM1_CC_IRQHandler( void ) __attribute__((section(".text.vector_handler"))) __attribute((weak,alias("DefaultIRQHandler"))) __attribute__((used)); void TIM2_IRQHandler( void ) __attribute__((section(".text.vector_handler"))) __attribute((weak,alias("DefaultIRQHandler"))) __attribute__((used)); -void _start() +void InterruptVector() { asm volatile( "\n\ .align 2\n\ @@ -111,29 +111,38 @@ void handle_reset() .option norelax\n\ la gp, __global_pointer$\n\ .option pop\n\ - la sp, _eusrstack\n" ); + la sp, _eusrstack\n" - // Load data section from flash to RAM - uint32_t * tempin = _data_lma; - uint32_t * tempout = _data_vma; - while( tempout != _edata ) - *(tempout++) = *(tempin++); - - tempout = _sbss; - while( tempout < _ebss ) - *(tempout++) = 0; + // Setup the interrupt vector. -asm volatile( "\n\ - li t0, 0x80\n\ +" li t0, 0x80\n\ csrw mstatus, t0\n\ li t0, 0x3\n\ csrw 0x804, t0\n\ - la t0, _start\n\ + la t0, InterruptVector\n\ ori t0, t0, 3\n\ - csrw mtvec, t0" ); + csrw mtvec, t0\n" + + // Clear BSS + // Has to be in assembly otherwise it will overwrite the stack. + "la t0, _sbss\n\ + la t1, _ebss\n\ + bgeu t0, t1, 2f\n\ +1:\n\ + sw zero, (t0)\n\ + addi t0, t0, 4\n\ + bltu t0, t1, 1b\n\ +2:" ); + + // Load data section from flash to RAM + uint32_t * tempin = _data_lma; + uint32_t * tempout = _data_vma; + while( tempout != _edata ) + *(tempout++) = *(tempin++); SystemInit(); + // set mepc to be main as the root interrupt. asm volatile( "\n\ la t0, main\n\ csrw mepc, t0\n\ diff --git a/examples/ws2812demo/README.md b/examples/ws2812demo/README.md index 6894ecd..a2dcdbb 100644 --- a/examples/ws2812demo/README.md +++ b/examples/ws2812demo/README.md @@ -1,6 +1,6 @@ # WS2812B SPI DMA example -This example uses SPI-DMA to output WS2812B LEDs. By chunking the outputs using the center-interrupt, it is possible to double-buffer the WS2812B output data while only storing a few LEDs worth of data in memory at once. +Single-file-header for SPI-DMA to output WS2812B LEDs. By chunking the outputs using the center-interrupt, it is possible to double-buffer the WS2812B output data while only storing a few LEDs worth of data in memory at once. This outputs the LED data on the MOSI (PC6) pin of the CH32V003. @@ -8,10 +8,34 @@ Additionally, this demo only uses 6% CPU while it's outputting LEDs and free whi The timing on the SPI bus is not terribly variable, the best I was able to find was: -Ton0 = 324ns -Ton1 = 990ns -Toff0 = 990ns -Toff1 = 324ns -Treset = 68us +* Ton0 = 324ns +* Ton1 = 990ns +* Toff0 = 990ns +* Toff1 = 324ns +* Treset = 68us +## Usage + +If you are including this in main, simply +```c +#define WS2812DMA_IMPLEMENTATION +#include "ws2812b_dma_spi_led_driver.h" +``` + +You will need to implement the following two functions, as callbacks from the ISR. +```c +uint32_t CallbackWS2812BLED( int ledno ); +``` + +You willalso need to call +```c +InitWS2812DMA(); +``` + +Then, whenyou want to update the LEDs, call: +```c +WS2812BStart( int num_leds ); +``` + +If you want to see if it's done sending, examine `WS2812BLEDInUse`. diff --git a/examples/ws2812demo/ws2812b_dma_spi_led_driver.h b/examples/ws2812demo/ws2812b_dma_spi_led_driver.h index f0f143c..fd1d428 100644 --- a/examples/ws2812demo/ws2812b_dma_spi_led_driver.h +++ b/examples/ws2812demo/ws2812b_dma_spi_led_driver.h @@ -29,9 +29,6 @@ void WS2812BDMAStart( int leds ); // Callbacks that you must implement. uint32_t WS2812BLEDCallback( int ledno ); -extern volatile int WS2812BLEDDone; - - #ifdef WS2812DMA_IMPLEMENTATION // Note first 2 LEDs of DMA Buffer are 0's as a "break" @@ -40,17 +37,16 @@ extern volatile int WS2812BLEDDone; // 1: Divisble by 2. // 2: #define DMALEDS 16 -#define WS2812B_RESET_PERIOD 6 +#define WS2812B_RESET_PERIOD 2 #define DMA_BUFFER_LEN (((DMALEDS+1)/2)*6) static uint16_t WS2812dmabuff[DMA_BUFFER_LEN]; -static int WS2812LEDs; -static int WS2812LEDPlace; -volatile int WS2812BLEDDone; - +static volatile int WS2812LEDs; +static volatile int WS2812LEDPlace; +static volatile int WS2812BLEDInUse; // This is the code that updates a portion of the WS2812dmabuff with new data. // This effectively creates the bitstream that outputs to the LEDs. -static void WS2812FillBuffSec( uint16_t * ptr, int numhalfwords, int ending ) +static void WS2812FillBuffSec( uint16_t * ptr, int numhalfwords, int tce ) { const static uint16_t bitquartets[16] = { 0b1000100010001000, 0b1000100010001110, 0b1000100011101000, 0b1000100011101110, @@ -65,6 +61,10 @@ static void WS2812FillBuffSec( uint16_t * ptr, int numhalfwords, int ending ) while( place < 0 && ptr != end ) { + (*ptr++) = 0; + (*ptr++) = 0; + (*ptr++) = 0; + (*ptr++) = 0; (*ptr++) = 0; (*ptr++) = 0; place++; @@ -78,22 +78,18 @@ static void WS2812FillBuffSec( uint16_t * ptr, int numhalfwords, int ending ) while( ptr != end ) (*ptr++) = 0;//0xffff; - // If we're "totally finished" we can set the flag. - if( place == ledcount+1 ) - WS2812BLEDDone = 1; - // Only safe to do this when we're on the second leg. - if( ending ) + if( tce ) { if( place == ledcount ) { // Take the DMA out of circular mode and let it expire. DMA1_Channel3->CFGR &= ~DMA_Mode_Circular; + WS2812BLEDInUse = 0; } place++; } - break; } @@ -113,42 +109,48 @@ static void WS2812FillBuffSec( uint16_t * ptr, int numhalfwords, int ending ) void DMA1_Channel3_IRQHandler( void ) __attribute__((interrupt)); void DMA1_Channel3_IRQHandler( void ) { - //GPIOD->BSHR = 1; // Turn on GPIOD0 + //GPIOD->BSHR = 1; // Turn on GPIOD0 for profiling // Backup flags. - int intfr = DMA1->INTFR; - - // Clear all possible flags. - DMA1->INTFCR = DMA1_IT_GL3; - - if( intfr & DMA1_IT_HT3 ) + volatile int intfr = DMA1->INTFR; + do { - // Complete (Fill in second part) - WS2812FillBuffSec( WS2812dmabuff + DMA_BUFFER_LEN / 2, DMA_BUFFER_LEN / 2, 1 ); - } - if( intfr & DMA1_IT_TC3 ) - { - // Halfwaay (Fill in first part) - WS2812FillBuffSec( WS2812dmabuff, DMA_BUFFER_LEN / 2, 0 ); - } + // Clear all possible flags. + DMA1->INTFCR = DMA1_IT_GL3; - //GPIOD->BSHR = 1<<16; // Turn off GPIOD0 + if( intfr & DMA1_IT_HT3 ) + { + // Halfwaay (Fill in first part) + WS2812FillBuffSec( WS2812dmabuff, DMA_BUFFER_LEN / 2, 1 ); + } + if( intfr & DMA1_IT_TC3 ) + { + // Complete (Fill in second part) + WS2812FillBuffSec( WS2812dmabuff + DMA_BUFFER_LEN / 2, DMA_BUFFER_LEN / 2, 0 ); + } + intfr = DMA1->INTFR; + } while( intfr ); + + //GPIOD->BSHR = 1<<16; // Turn off GPIOD0 for profiling } void WS2812BDMAStart( int leds ) { - WS2812BLEDDone = 0; + __disable_irq(); + WS2812BLEDInUse = 1; + DMA1_Channel3->CFGR &= ~DMA_Mode_Circular; + DMA1_Channel3->CNTR = 0; + DMA1_Channel3->MADDR = (uint32_t)WS2812dmabuff; WS2812LEDs = leds; WS2812LEDPlace = -WS2812B_RESET_PERIOD; WS2812FillBuffSec( WS2812dmabuff, DMA_BUFFER_LEN, 0 ); - DMA1_Channel3->CFGR |= DMA_Mode_Circular; DMA1_Channel3->CNTR = DMA_BUFFER_LEN; // Number of unique uint16_t entries. + DMA1_Channel3->CFGR |= DMA_Mode_Circular; + __enable_irq(); } void WS2812BDMAInit( ) { - WS2812BLEDDone = 1; - // Enable DMA + Peripherals RCC->AHBPCENR |= RCC_AHBPeriph_DMA1; RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1; diff --git a/examples/ws2812demo/ws2812bdemo.c b/examples/ws2812demo/ws2812bdemo.c index a74a420..32ce933 100644 --- a/examples/ws2812demo/ws2812bdemo.c +++ b/examples/ws2812demo/ws2812bdemo.c @@ -65,22 +65,22 @@ void SystemInit(void) USART1->CTLR1 |= CTLR1_UE_Set; } -#define NR_LEDS 197 +#define NR_LEDS 191 uint16_t phases[NR_LEDS]; int frameno; -int tween = 0; -int tweendir = 0; +volatile int tween = 0; // Callbacks that you must implement. uint32_t WS2812BLEDCallback( int ledno ) { uint8_t index = (phases[ledno])>>8; uint8_t rs = sintable[index]>>3; -// uint32_t fire = huetable[(rs+190&0xff)] | (huetable[(rs+30&0xff)]<<8) | (huetable[(rs+0)]<<16); + uint32_t fire = huetable[(rs+190&0xff)] | (huetable[(rs+30&0xff)]<<8) | (huetable[(rs+0)]<<16); uint32_t ice = 0xff | ((rs)<<8) | (rs<<16); - return ice; - //return TweenHexColors( fire, ice, tween ); // Where "tween" is a value from 0 ... 255 + + // Because this chip doesn't natively support multiplies, this can be very slow. + return TweenHexColors( fire, ice, tween ); // Where "tween" is a value from 0 ... 255 } int main() @@ -101,37 +101,41 @@ int main() for( k = 0; k < NR_LEDS; k++ ) phases[k] = k<<8; + int tweendir = 0; + while(1) { // Wait for LEDs to totally finish. - if( WS2812BLEDDone ) + Delay_Ms( 12 ); + + while( WS2812BLEDInUse ); + + frameno++; + + if( frameno == 1024 ) + { + tweendir = 4; + } + if( frameno == 2048 ) { - frameno++; - - if( frameno == 1024 ) - { - tweendir = 4; - } - if( frameno == 2048 ) - { - tweendir = -4; - frameno = 0; - } - - if( tweendir ) - { - tween += tweendir; - if( tween > 255 ) tween = 255; - if( tween < 0 ) tween = 0; - } - - for( k = 0; k < NR_LEDS; k++ ) - { - phases[k] += ((((rands[k&0xff])+0xf)<<2) + (((rands[k&0xff])+0xf)<<1))>>1; - } - - WS2812BDMAStart( NR_LEDS ); + tweendir = -4; + frameno = 0; } + + if( tweendir ) + { + int t = tween + tweendir; + if( t > 255 ) t = 255; + if( t < 0 ) t = 0; + tween = t; + } + + for( k = 0; k < NR_LEDS; k++ ) + { + phases[k] += ((((rands[k&0xff])+0xf)<<2) + (((rands[k&0xff])+0xf)<<1))>>1; + } + + WS2812BDMAStart( NR_LEDS ); } } -- GitLab