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