diff --git a/examples/sandbox/sandbox.c b/examples/sandbox/sandbox.c index 4aefb859c55d3d7a9825c2af16016ff3c9f412a0..338cb9f9368ad602d91dec69d3c20f82dabc72ad 100644 --- a/examples/sandbox/sandbox.c +++ b/examples/sandbox/sandbox.c @@ -5,6 +5,10 @@ #include <stdio.h> #include <string.h> +#define WS2812DMA_IMPLEMENTATION + +#include "ws2812b_dma_spi_led_driver.h" + #define APB_CLOCK SYSTEM_CORE_CLOCK // Working on WS2812 driving. @@ -61,9 +65,6 @@ void SystemInit(void) USART1->CTLR1 |= CTLR1_UE_Set; } - - - uint32_t EHSVtoHEX( uint8_t hue, uint8_t sat, uint8_t val ) { #define SIXTH1 43 @@ -135,6 +136,25 @@ uint32_t EHSVtoHEX( uint8_t hue, uint8_t sat, uint8_t val ) return or | (og<<8) | ((uint32_t)ob<<16); } + +const unsigned char huetable[] = { + 0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x2a, 0x30, 0x36, 0x3c, 0x42, 0x48, 0x4e, 0x54, 0x5a, + 0x60, 0x66, 0x6c, 0x72, 0x78, 0x7e, 0x84, 0x8a, 0x90, 0x96, 0x9c, 0xa2, 0xa8, 0xae, 0xb4, 0xba, + 0xc0, 0xc6, 0xcc, 0xd2, 0xd8, 0xde, 0xe4, 0xea, 0xf0, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0xf3, 0xed, 0xe7, 0xe1, 0xdb, 0xd5, 0xcf, 0xc9, 0xc3, 0xbd, 0xb7, 0xb1, 0xab, 0xa5, + 0x9f, 0x99, 0x93, 0x8d, 0x87, 0x81, 0x7b, 0x75, 0x6f, 0x69, 0x63, 0x5d, 0x57, 0x51, 0x4b, 0x45, + 0x3f, 0x39, 0x33, 0x2d, 0x27, 0x21, 0x1b, 0x15, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + const unsigned char rands[] = { 0x67, 0xc6, 0x69, 0x73, 0x51, 0xff, 0x4a, 0xec, 0x29, 0xcd, 0xba, 0xab, 0xf2, 0xfb, 0xe3, 0x46, 0x7c, 0xc2, 0x54, 0xf8, 0x1b, 0xe8, 0xe7, 0x8d, 0x76, 0x5a, 0x2e, 0x63, 0x33, 0x9f, 0xc9, 0x9a, @@ -153,146 +173,91 @@ const unsigned char rands[] = { 0xac, 0x86, 0x21, 0x2b, 0xaa, 0x1a, 0x55, 0xa2, 0xbe, 0x70, 0xb5, 0x73, 0x3b, 0x04, 0x5c, 0xd3, 0x36, 0x94, 0xb3, 0xaf, 0xe2, 0xf0, 0xe4, 0x9e, 0x4f, 0x32, 0x15, 0x49, 0xfd, 0x82, 0x4e, 0xa9, }; - -#define NR_LEDS 90 -uint16_t bufferset[12+NR_LEDS*6+1]; - -// Note first 2 LEDs of DMA Buffer are 0's as a "break" -// Need one extra LED at end to leave line high. -#define DMALEDS 16 -#define DMA_BUFFER_LEN (16*6) - -void EncodeLED( uint16_t * ptr, uint32_t ledval24bit ) -{ - // This encodes 4 bits of incoming signal into one uint16_t - const static uint16_t bitquartets[16] = { - 0b1000100010001000, 0b1000100010001110, 0b1000100011101000, 0b1000100011101110, - 0b1000111010001000, 0b1000111010001110, 0b1000111011101000, 0b1000111011101110, - 0b1110100010001000, 0b1110100010001110, 0b1110100011101000, 0b1110100011101110, - 0b1110111010001000, 0b1110111010001110, 0b1110111011101000, 0b1110111011101110, }; - ptr[0] = bitquartets[(ledval24bit>>20)&0xf]; - ptr[1] = bitquartets[(ledval24bit>>16)&0xf]; - ptr[2] = bitquartets[(ledval24bit>>12)&0xf]; - ptr[3] = bitquartets[(ledval24bit>>8)&0xf]; - ptr[4] = bitquartets[(ledval24bit>>4)&0xf]; - ptr[5] = bitquartets[(ledval24bit>>0)&0xf]; -} - -void DMA1_Channel3_IRQHandler( void ) __attribute__((interrupt)); -void DMA1_Channel3_IRQHandler( void ) +const unsigned char sintable[] = { + 0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x95, 0x99, 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xad, + 0xb0, 0xb3, 0xb6, 0xb9, 0xbc, 0xbe, 0xc1, 0xc4, 0xc6, 0xc9, 0xcb, 0xce, 0xd0, 0xd3, 0xd5, 0xd7, + 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xe9, 0xeb, 0xed, 0xee, 0xf0, 0xf1, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfc, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, + 0xf5, 0xf4, 0xf3, 0xf1, 0xf0, 0xee, 0xed, 0xeb, 0xe9, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, 0xde, 0xdc, + 0xda, 0xd7, 0xd5, 0xd3, 0xd0, 0xce, 0xcb, 0xc9, 0xc6, 0xc4, 0xc1, 0xbe, 0xbc, 0xb9, 0xb6, 0xb3, + 0xb0, 0xad, 0xab, 0xa8, 0xa5, 0xa2, 0x9f, 0x9c, 0x99, 0x95, 0x92, 0x8f, 0x8c, 0x89, 0x86, 0x83, + 0x80, 0x7d, 0x79, 0x76, 0x73, 0x70, 0x6d, 0x6a, 0x67, 0x64, 0x61, 0x5e, 0x5b, 0x58, 0x55, 0x52, + 0x4f, 0x4c, 0x49, 0x47, 0x44, 0x41, 0x3e, 0x3c, 0x39, 0x36, 0x34, 0x31, 0x2f, 0x2d, 0x2a, 0x28, + 0x26, 0x24, 0x21, 0x1f, 0x1d, 0x1b, 0x1a, 0x18, 0x16, 0x14, 0x13, 0x11, 0x10, 0x0e, 0x0d, 0x0b, + 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, 0x16, 0x18, 0x1a, 0x1b, 0x1d, 0x1f, 0x21, 0x24, + 0x26, 0x28, 0x2a, 0x2d, 0x2f, 0x31, 0x34, 0x36, 0x39, 0x3c, 0x3e, 0x41, 0x44, 0x47, 0x49, 0x4c, + 0x4f, 0x52, 0x55, 0x58, 0x5b, 0x5e, 0x61, 0x64, 0x67, 0x6a, 0x6d, 0x70, 0x73, 0x76, 0x79, 0x7d, }; + +#define NR_LEDS 197 + +uint16_t phases[NR_LEDS]; +int frameno; +int tween = 0; + +uint32_t TweenHexColors( uint32_t hexa, uint32_t hexb, int tween ) { - // Check DMA1->INTFR - // Clear all possible flags. - GPIOD->BSHR = 1; // Turn on GPIOD0 - DMA1->INTFCR = DMA1_IT_GL3; - GPIOD->BSHR = 1<<16; // Turn off GPIOD0 + int32_t aamt = 255-tween; + int32_t bamt = tween; + int32_t hab = hexa & 0xff; + int32_t har = (hexa>>8) & 0xff; + int32_t hag = (hexa>>16) & 0xff; + int32_t hbb = hexb & 0xff; + int32_t hbr = (hexb>>8) & 0xff; + int32_t hbg = (hexb>>16) & 0xff; + int32_t b = (hab * aamt + hbb * bamt + 128) >> 8; + int32_t r = (har * aamt + hbr * bamt + 128) >> 8; + int32_t g = (hag * aamt + hbg * bamt + 128) >> 8; + return b | (r<<8) | (g<<16); } -void ConfigSpeedyLED() + +// Callbacks that you must implement. +uint32_t WS2812BLEDCallback( int ledno ) { - // Enable DMA + Peripherals - RCC->AHBPCENR |= RCC_AHBPeriph_DMA1; - RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1; - - // MOSI, Configure GPIO Pin - GPIOC->CFGLR &= ~(0xf<<(4*6)); - GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF)<<(4*6); - - // Configure SPI - SPI1->CTLR1 = - SPI_NSS_Soft | SPI_CPHA_1Edge | SPI_CPOL_Low | SPI_DataSize_16b | - SPI_Mode_Master | SPI_Direction_1Line_Tx | - 3<<3; // Dvisior - - SPI1->CTLR2 = SPI_CTLR2_TXDMAEN; - SPI1->HSCR = 1; - - SPI1->CTLR1 |= CTLR1_SPE_Set; - - memset( bufferset, 0xaa, sizeof( bufferset ) ); - - //DMA1_Channel3 is for SPI1TX - DMA1_Channel3->PADDR = (uint32_t)&SPI1->DATAR; - DMA1_Channel3->MADDR = (uint32_t)bufferset; - DMA1_Channel3->CNTR = sizeof( bufferset )/2; // Number of unique copies. - DMA1_Channel3->CFGR = - DMA_M2M_Disable | - DMA_Priority_VeryHigh | - DMA_MemoryDataSize_HalfWord | - DMA_PeripheralDataSize_HalfWord | - DMA_MemoryInc_Enable | - DMA_Mode_Normal | // OR DMA_Mode_Circular or DMA_Mode_Normal - DMA_DIR_PeripheralDST | - DMA_IT_TC | DMA_IT_HT; // Transmission Complete + Half Empty Interrupts. - -// NVIC_SetPriority( DMA1_Channel3_IRQn, 0<<4 ); // Regular priority. - NVIC_EnableIRQ( DMA1_Channel3_IRQn ); - DMA1_Channel3->CFGR |= DMA_CFGR1_EN; + 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 ice = 0xff | ((rs)<<8) | (rs<<16); + return TweenHexColors( fire, ice, tween ); // Where "tween" is a value from 0 ... 255 } int main() { + int k; - // Enable GPIOD. + // Enable GPIOD (for debugging) RCC->APB2PCENR |= RCC_APB2Periph_GPIOD; GPIOD->CFGLR &= ~(0xf<<(4*0)); GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0); - ConfigSpeedyLED(); + WS2812BDMAInit( ); printf( "CFG: %08x\n", SPI1->CTLR1 ); - uint16_t phases[NR_LEDS]; + frameno = 0; + + for( k = 0; k < NR_LEDS; k++ ) phases[k] = k<<8; -/* -#define NR_LEDS 30 -uint16_t bufferset[4+30*6+1]; -*/ - int frame = 0; while(1) { - int k; - Delay_Ms( 10 ); - frame++; - - // 50 us low pulse. - bufferset[0] = 0x00; - bufferset[1] = 0x00; - bufferset[2] = 0x00; - bufferset[3] = 0x00; - bufferset[4] = 0x00; - bufferset[5] = 0x00; - bufferset[6] = 0x00; - bufferset[7] = 0x00; - bufferset[8] = 0x00; - bufferset[9] = 0x00; - bufferset[10] = 0x00; - bufferset[11] = 0x00; - - uint16_t * ept = bufferset + 12; - - //Pre-compute 'whiteness' + while( !WS2812BLEDDone ); + frameno++; + if( frameno < 1024 ) tween = 0; + else if( frameno < (1024+256) ) tween = frameno - 1024; + else if( frameno < 2048 ) tween = 255; + else if( frameno < 2048+256 ) tween = 255-(frameno - 2048); + else frameno = 0; + for( k = 0; k < NR_LEDS; k++ ) { - int phase = phases[k] += (((rands[k&0xff])+0xf)<<2) + (((rands[k&0xff])+0xf)<<1); - - EncodeLED( ept, k ); - ept += 6; -/* uint8_t rs = ST(index)>>3; - SEND_WS( HUE(rs ) ); - index = (buffer1[l])>>8; - SEND_WS( HUE(rs + 30) ); - SEND_WS( HUE(rs + 190) ); - - SEND_WS( HUE(rs ) ); - SEND_WS( HUE(rs + 30) ); - l++; - SEND_WS( HUE(rs + 190) );*/ + phases[k] += ((((rands[k&0xff])+0xf)<<2) + (((rands[k&0xff])+0xf)<<1))>>1; } - ept[0] = 0;//0xffff; - DMA1_Channel3->CNTR = sizeof( bufferset )/2; // Number of unique copies. + WS2812BDMAStart( NR_LEDS ); } } diff --git a/examples/ws2812demo/Makefile b/examples/ws2812demo/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..816641bef4916ed5ac99f0b17a7a0f60e57bc614 --- /dev/null +++ b/examples/ws2812demo/Makefile @@ -0,0 +1,42 @@ +TARGET:=ws2812bdemo + +all : flash + +PREFIX:=riscv64-unknown-elf + +GPIO_Toggle:=EXAM/GPIO/GPIO_Toggle/User + +EVT:=../../ch32v003evt +MINICHLINK:=../../minichlink + +CFLAGS:= \ + -g -Os -flto -ffunction-sections \ + -static-libgcc -lgcc \ + -march=rv32ec \ + -mabi=ilp32e \ + -I/usr/include/newlib \ + -I$(EVT) \ + -nostdlib \ + -I. + +LDFLAGS:=-T $(EVT)/ch32v003.ld -Wl,--gc-sections + +SYSTEM_C:=$(EVT)/startup_ch32v003.c $(EVT)/embedlibc.c + +$(TARGET).elf : $(SYSTEM_C) $(TARGET).c + $(PREFIX)-gcc -o $@ $^ $(CFLAGS) $(LDFLAGS) + +$(TARGET).bin : $(TARGET).elf + $(PREFIX)-size $^ + $(PREFIX)-objdump -S $^ > $(TARGET).lst + $(PREFIX)-objdump -t $^ > $(TARGET).map + $(PREFIX)-objcopy -O binary $< $(TARGET).bin + $(PREFIX)-objcopy -O ihex $< $(TARGET).hex + +flash : $(TARGET).bin + make -C $(MINICHLINK) all + $(MINICHLINK)/minichlink -w $< -r + +clean : + rm -rf $(TARGET).elf $(TARGET).bin $(TARGET).hex $(TARGET).lst $(TARGET).map $(TARGET).hex + diff --git a/examples/ws2812demo/README.md b/examples/ws2812demo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6894ecdaee82b73a9f3f52c57079f2d32f43e530 --- /dev/null +++ b/examples/ws2812demo/README.md @@ -0,0 +1,17 @@ +# 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. + +This outputs the LED data on the MOSI (PC6) pin of the CH32V003. + +Additionally, this demo only uses 6% CPU while it's outputting LEDs and free while not and it doesn't require precise interrupt timing, increasing or decreasing `DMALEDS` will adjust how lineant the timing is on LED catpures. + +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 + + diff --git a/examples/ws2812demo/color_utilities.h b/examples/ws2812demo/color_utilities.h new file mode 100644 index 0000000000000000000000000000000000000000..3a9d20095c1a2ea8435b5cd19a95c8e949d1f569 --- /dev/null +++ b/examples/ws2812demo/color_utilities.h @@ -0,0 +1,154 @@ +/* + Color utility functions for embedded systems + + Copyright 2023 <>< Charles Lohr, under the MIT-x11 or NewBSD License, you choose! +*/ + +#ifndef _COLOR_UTILITIES_H +#define _COLOR_UTILITIES_H + + + +static uint32_t EHSVtoHEX( uint8_t hue, uint8_t sat, uint8_t val ) +{ + #define SIXTH1 43 + #define SIXTH2 85 + #define SIXTH3 128 + #define SIXTH4 171 + #define SIXTH5 213 + + uint16_t or = 0, og = 0, ob = 0; + + hue -= SIXTH1; //Off by 60 degrees. + + //TODO: There are colors that overlap here, consider + //tweaking this to make the best use of the colorspace. + + if( hue < SIXTH1 ) //Ok: Yellow->Red. + { + or = 255; + og = 255 - ((uint16_t)hue * 255) / (SIXTH1); + } + else if( hue < SIXTH2 ) //Ok: Red->Purple + { + or = 255; + ob = (uint16_t)hue*255 / SIXTH1 - 255; + } + else if( hue < SIXTH3 ) //Ok: Purple->Blue + { + ob = 255; + or = ((SIXTH3-hue) * 255) / (SIXTH1); + } + else if( hue < SIXTH4 ) //Ok: Blue->Cyan + { + ob = 255; + og = (hue - SIXTH3)*255 / SIXTH1; + } + else if( hue < SIXTH5 ) //Ok: Cyan->Green. + { + og = 255; + ob = ((SIXTH5-hue)*255) / SIXTH1; + } + else //Green->Yellow + { + og = 255; + or = (hue - SIXTH5) * 255 / SIXTH1; + } + + uint16_t rv = val; + if( rv > 128 ) rv++; + uint16_t rs = sat; + if( rs > 128 ) rs++; + + //or, og, ob range from 0...255 now. + //Need to apply saturation and value. + + or = (or * val)>>8; + og = (og * val)>>8; + ob = (ob * val)>>8; + + //OR..OB == 0..65025 + or = or * rs + 255 * (256-rs); + og = og * rs + 255 * (256-rs); + ob = ob * rs + 255 * (256-rs); +//printf( "__%d %d %d =-> %d\n", or, og, ob, rs ); + + or >>= 8; + og >>= 8; + ob >>= 8; + + return or | (og<<8) | ((uint32_t)ob<<16); +} + +static const unsigned char huetable[] = { + 0x00, 0x06, 0x0c, 0x12, 0x18, 0x1e, 0x24, 0x2a, 0x30, 0x36, 0x3c, 0x42, 0x48, 0x4e, 0x54, 0x5a, + 0x60, 0x66, 0x6c, 0x72, 0x78, 0x7e, 0x84, 0x8a, 0x90, 0x96, 0x9c, 0xa2, 0xa8, 0xae, 0xb4, 0xba, + 0xc0, 0xc6, 0xcc, 0xd2, 0xd8, 0xde, 0xe4, 0xea, 0xf0, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf9, 0xf3, 0xed, 0xe7, 0xe1, 0xdb, 0xd5, 0xcf, 0xc9, 0xc3, 0xbd, 0xb7, 0xb1, 0xab, 0xa5, + 0x9f, 0x99, 0x93, 0x8d, 0x87, 0x81, 0x7b, 0x75, 0x6f, 0x69, 0x63, 0x5d, 0x57, 0x51, 0x4b, 0x45, + 0x3f, 0x39, 0x33, 0x2d, 0x27, 0x21, 0x1b, 0x15, 0x0f, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + +static const unsigned char rands[] = { + 0x67, 0xc6, 0x69, 0x73, 0x51, 0xff, 0x4a, 0xec, 0x29, 0xcd, 0xba, 0xab, 0xf2, 0xfb, 0xe3, 0x46, + 0x7c, 0xc2, 0x54, 0xf8, 0x1b, 0xe8, 0xe7, 0x8d, 0x76, 0x5a, 0x2e, 0x63, 0x33, 0x9f, 0xc9, 0x9a, + 0x66, 0x32, 0x0d, 0xb7, 0x31, 0x58, 0xa3, 0x5a, 0x25, 0x5d, 0x05, 0x17, 0x58, 0xe9, 0x5e, 0xd4, + 0xab, 0xb2, 0xcd, 0xc6, 0x9b, 0xb4, 0x54, 0x11, 0x0e, 0x82, 0x74, 0x41, 0x21, 0x3d, 0xdc, 0x87, + 0x70, 0xe9, 0x3e, 0xa1, 0x41, 0xe1, 0xfc, 0x67, 0x3e, 0x01, 0x7e, 0x97, 0xea, 0xdc, 0x6b, 0x96, + 0x8f, 0x38, 0x5c, 0x2a, 0xec, 0xb0, 0x3b, 0xfb, 0x32, 0xaf, 0x3c, 0x54, 0xec, 0x18, 0xdb, 0x5c, + 0x02, 0x1a, 0xfe, 0x43, 0xfb, 0xfa, 0xaa, 0x3a, 0xfb, 0x29, 0xd1, 0xe6, 0x05, 0x3c, 0x7c, 0x94, + 0x75, 0xd8, 0xbe, 0x61, 0x89, 0xf9, 0x5c, 0xbb, 0xa8, 0x99, 0x0f, 0x95, 0xb1, 0xeb, 0xf1, 0xb3, + 0x05, 0xef, 0xf7, 0x00, 0xe9, 0xa1, 0x3a, 0xe5, 0xca, 0x0b, 0xcb, 0xd0, 0x48, 0x47, 0x64, 0xbd, + 0x1f, 0x23, 0x1e, 0xa8, 0x1c, 0x7b, 0x64, 0xc5, 0x14, 0x73, 0x5a, 0xc5, 0x5e, 0x4b, 0x79, 0x63, + 0x3b, 0x70, 0x64, 0x24, 0x11, 0x9e, 0x09, 0xdc, 0xaa, 0xd4, 0xac, 0xf2, 0x1b, 0x10, 0xaf, 0x3b, + 0x33, 0xcd, 0xe3, 0x50, 0x48, 0x47, 0x15, 0x5c, 0xbb, 0x6f, 0x22, 0x19, 0xba, 0x9b, 0x7d, 0xf5, + 0x0b, 0xe1, 0x1a, 0x1c, 0x7f, 0x23, 0xf8, 0x29, 0xf8, 0xa4, 0x1b, 0x13, 0xb5, 0xca, 0x4e, 0xe8, + 0x98, 0x32, 0x38, 0xe0, 0x79, 0x4d, 0x3d, 0x34, 0xbc, 0x5f, 0x4e, 0x77, 0xfa, 0xcb, 0x6c, 0x05, + 0xac, 0x86, 0x21, 0x2b, 0xaa, 0x1a, 0x55, 0xa2, 0xbe, 0x70, 0xb5, 0x73, 0x3b, 0x04, 0x5c, 0xd3, + 0x36, 0x94, 0xb3, 0xaf, 0xe2, 0xf0, 0xe4, 0x9e, 0x4f, 0x32, 0x15, 0x49, 0xfd, 0x82, 0x4e, 0xa9, }; + +static const unsigned char sintable[] = { + 0x80, 0x83, 0x86, 0x89, 0x8c, 0x8f, 0x92, 0x95, 0x99, 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xad, + 0xb0, 0xb3, 0xb6, 0xb9, 0xbc, 0xbe, 0xc1, 0xc4, 0xc6, 0xc9, 0xcb, 0xce, 0xd0, 0xd3, 0xd5, 0xd7, + 0xda, 0xdc, 0xde, 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xe9, 0xeb, 0xed, 0xee, 0xf0, 0xf1, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfc, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfd, 0xfc, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, + 0xf5, 0xf4, 0xf3, 0xf1, 0xf0, 0xee, 0xed, 0xeb, 0xe9, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, 0xde, 0xdc, + 0xda, 0xd7, 0xd5, 0xd3, 0xd0, 0xce, 0xcb, 0xc9, 0xc6, 0xc4, 0xc1, 0xbe, 0xbc, 0xb9, 0xb6, 0xb3, + 0xb0, 0xad, 0xab, 0xa8, 0xa5, 0xa2, 0x9f, 0x9c, 0x99, 0x95, 0x92, 0x8f, 0x8c, 0x89, 0x86, 0x83, + 0x80, 0x7d, 0x79, 0x76, 0x73, 0x70, 0x6d, 0x6a, 0x67, 0x64, 0x61, 0x5e, 0x5b, 0x58, 0x55, 0x52, + 0x4f, 0x4c, 0x49, 0x47, 0x44, 0x41, 0x3e, 0x3c, 0x39, 0x36, 0x34, 0x31, 0x2f, 0x2d, 0x2a, 0x28, + 0x26, 0x24, 0x21, 0x1f, 0x1d, 0x1b, 0x1a, 0x18, 0x16, 0x14, 0x13, 0x11, 0x10, 0x0e, 0x0d, 0x0b, + 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14, 0x16, 0x18, 0x1a, 0x1b, 0x1d, 0x1f, 0x21, 0x24, + 0x26, 0x28, 0x2a, 0x2d, 0x2f, 0x31, 0x34, 0x36, 0x39, 0x3c, 0x3e, 0x41, 0x44, 0x47, 0x49, 0x4c, + 0x4f, 0x52, 0x55, 0x58, 0x5b, 0x5e, 0x61, 0x64, 0x67, 0x6a, 0x6d, 0x70, 0x73, 0x76, 0x79, 0x7d, }; + +static uint32_t TweenHexColors( uint32_t hexa, uint32_t hexb, int tween ) +{ + int32_t aamt = 255-tween; + int32_t bamt = tween; + int32_t hab = hexa & 0xff; + int32_t har = (hexa>>8) & 0xff; + int32_t hag = (hexa>>16) & 0xff; + int32_t hbb = hexb & 0xff; + int32_t hbr = (hexb>>8) & 0xff; + int32_t hbg = (hexb>>16) & 0xff; + int32_t b = (hab * aamt + hbb * bamt + 128) >> 8; + int32_t r = (har * aamt + hbr * bamt + 128) >> 8; + int32_t g = (hag * aamt + hbg * bamt + 128) >> 8; + return b | (r<<8) | (g<<16); +} + +#endif + diff --git a/examples/ws2812demo/ws2812b_dma_spi_led_driver.h b/examples/ws2812demo/ws2812b_dma_spi_led_driver.h new file mode 100644 index 0000000000000000000000000000000000000000..9972fee9a0e93fe8d0850d0219a8759a690a1322 --- /dev/null +++ b/examples/ws2812demo/ws2812b_dma_spi_led_driver.h @@ -0,0 +1,197 @@ +/* Single-File-Header for using asynchronous LEDs with the CH32V003 using DMA to the SPI port. + I may write another version of this to use DMA to timer ports, but, the SPI port can be used + to generate outputs very efficiently. So, for now, SPI Port. + + Copyright 2023 <>< Charles Lohr, under the MIT-x11 or NewBSD License, you choose! + + If you are including this in main, simply + #define WS2812DMA_IMPLEMENTATION + + You will need to implement the following two functions, as callbacks from the ISR. + uint32_t CallbackWS2812BLED( int ledno ); + + You willalso need to call + InitWS2812DMA(); + + Then, whenyou want to update the LEDs, call: + WS2812BStart( int num_leds ); +*/ + +#ifndef _WS2812_LED_DRIVER_H +#define _WS2812_LED_DRIVER_H + +#include <stdint.h> + +// Use DMA and SPI to stream out WS2812B LED Data via the MOSI pin. +void WS2812BDMAInit( ); +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" +// Need one extra LED at end to leave line high. +// This must be greater than WS2812B_RESET_PERIOD. +// 1: Divisble by 2. +// 2: +#define DMALEDS 16 +#define WS2812B_RESET_PERIOD 6 +#define DMA_BUFFER_LEN (((DMALEDS+1)/2)*6) + +static uint16_t WS2812dmabuff[DMA_BUFFER_LEN]; +static int WS2812LEDs; +static int WS2812LEDPlace; +volatile int WS2812BLEDDone; + +// 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 ) +{ + const static uint16_t bitquartets[16] = { + 0b1000100010001000, 0b1000100010001110, 0b1000100011101000, 0b1000100011101110, + 0b1000111010001000, 0b1000111010001110, 0b1000111011101000, 0b1000111011101110, + 0b1110100010001000, 0b1110100010001110, 0b1110100011101000, 0b1110100011101110, + 0b1110111010001000, 0b1110111010001110, 0b1110111011101000, 0b1110111011101110, }; + + int i; + uint16_t * end = ptr + numhalfwords; + int ledcount = WS2812LEDs; + int place = WS2812LEDPlace; + + while( place < 0 && ptr != end ) + { + (*ptr++) = 0; + (*ptr++) = 0; + place++; + } + + while( ptr != end ) + { + if( place >= ledcount ) + { + // Optionally, leave line high. + 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( place == ledcount ) + { + // Take the DMA out of circular mode and let it expire. + DMA1_Channel3->CFGR &= ~DMA_Mode_Circular; + } + place++; + } + + + break; + } + + uint32_t ledval24bit = WS2812BLEDCallback( place++ ); + ptr[0] = bitquartets[(ledval24bit>>20)&0xf]; + ptr[1] = bitquartets[(ledval24bit>>16)&0xf]; + ptr[2] = bitquartets[(ledval24bit>>12)&0xf]; + ptr[3] = bitquartets[(ledval24bit>>8)&0xf]; + ptr[4] = bitquartets[(ledval24bit>>4)&0xf]; + ptr[5] = bitquartets[(ledval24bit>>0)&0xf]; + ptr += 6; + i += 6; + } + WS2812LEDPlace = place; +} + +void DMA1_Channel3_IRQHandler( void ) __attribute__((interrupt)); +void DMA1_Channel3_IRQHandler( void ) +{ + GPIOD->BSHR = 1; // Turn on GPIOD0 + + // Backup flags. + int intfr = DMA1->INTFR; + + // Clear all possible flags. + DMA1->INTFCR = DMA1_IT_GL3; + + if( intfr & DMA1_IT_HT3 ) + { + // 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 ); + } + + GPIOD->BSHR = 1<<16; // Turn off GPIOD0 +} + +void WS2812BDMAStart( int leds ) +{ + WS2812BLEDDone = 0; + 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. +} + +void WS2812BDMAInit( ) +{ + WS2812BLEDDone = 1; + + // Enable DMA + Peripherals + RCC->AHBPCENR |= RCC_AHBPeriph_DMA1; + RCC->APB2PCENR |= RCC_APB2Periph_GPIOC | RCC_APB2Periph_SPI1; + + // MOSI, Configure GPIO Pin + GPIOC->CFGLR &= ~(0xf<<(4*6)); + GPIOC->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF)<<(4*6); + + // Configure SPI + SPI1->CTLR1 = + SPI_NSS_Soft | SPI_CPHA_1Edge | SPI_CPOL_Low | SPI_DataSize_16b | + SPI_Mode_Master | SPI_Direction_1Line_Tx | + 3<<3; // Dvisior + + SPI1->CTLR2 = SPI_CTLR2_TXDMAEN; + SPI1->HSCR = 1; + + SPI1->CTLR1 |= CTLR1_SPE_Set; + + SPI1->DATAR = 0; // Set LEDs Low. + + //memset( bufferset, 0xaa, sizeof( bufferset ) ); + + //DMA1_Channel3 is for SPI1TX + DMA1_Channel3->PADDR = (uint32_t)&SPI1->DATAR; + DMA1_Channel3->MADDR = (uint32_t)WS2812dmabuff; + DMA1_Channel3->CNTR = 0;// sizeof( bufferset )/2; // Number of unique copies. (Don't start, yet!) + DMA1_Channel3->CFGR = + DMA_M2M_Disable | + DMA_Priority_VeryHigh | + DMA_MemoryDataSize_HalfWord | + DMA_PeripheralDataSize_HalfWord | + DMA_MemoryInc_Enable | + DMA_Mode_Normal | // OR DMA_Mode_Circular or DMA_Mode_Normal + DMA_DIR_PeripheralDST | + DMA_IT_TC | DMA_IT_HT; // Transmission Complete + Half Empty Interrupts. + +// NVIC_SetPriority( DMA1_Channel3_IRQn, 0<<4 ); // Regular priority. + NVIC_EnableIRQ( DMA1_Channel3_IRQn ); + DMA1_Channel3->CFGR |= DMA_CFGR1_EN; +} + +#endif + +#endif + diff --git a/examples/ws2812demo/ws2812bdemo.c b/examples/ws2812demo/ws2812bdemo.c new file mode 100644 index 0000000000000000000000000000000000000000..a74a420bde1a6aa5cab16e54e90c8869d96aff7b --- /dev/null +++ b/examples/ws2812demo/ws2812bdemo.c @@ -0,0 +1,137 @@ +// Could be defined here, or in the processor defines. +#define SYSTEM_CORE_CLOCK 48000000 + +#include "ch32v00x.h" +#include <stdio.h> +#include <string.h> + +#define WS2812DMA_IMPLEMENTATION + +#include "ws2812b_dma_spi_led_driver.h" + +#define APB_CLOCK SYSTEM_CORE_CLOCK + +#include "color_utilities.h" + +// For debug writing to the UART. +int _write(int fd, char *buf, int size) +{ + for(int i = 0; i < size; i++){ + while( !(USART1->STATR & USART_FLAG_TC)); + USART1->DATAR = *buf++; + } + return size; +} + + +void SystemInit(void) +{ + // Values lifted from the EVT. There is little to no documentation on what this does. + RCC->CTLR |= (uint32_t)0x00000001; + RCC->CFGR0 &= (uint32_t)0xFCFF0000; + RCC->CTLR &= (uint32_t)0xFEF6FFFF; + RCC->CTLR &= (uint32_t)0xFFFBFFFF; + RCC->CFGR0 &= (uint32_t)0xFFFEFFFF; + RCC->INTR = 0x009F0000; + + // From SetSysClockTo_48MHZ_HSI + // This is some dark stuff. But, I copy-pasted it and it seems towork. + FLASH->ACTLR = (FLASH->ACTLR & ((uint32_t)~FLASH_ACTLR_LATENCY)) | FLASH_ACTLR_LATENCY_1; // Flash 0 wait state + RCC->CFGR0 = ( RCC->CFGR0 & ((uint32_t)~(RCC_PLLSRC)) ) | (uint32_t)(RCC_PLLSRC_HSI_Mul2); // PLL configuration: PLLCLK = HSI * 2 = 48 MHz + RCC->CTLR |= RCC_PLLON; // Enable PLL + while((RCC->CTLR & RCC_PLLRDY) == 0); // Wait till PLL is ready + RCC->CFGR0 = ( RCC->CFGR0 & ((uint32_t)~(RCC_SW))) | (uint32_t)RCC_SW_PLL; // Select PLL as system clock source + while ((RCC->CFGR0 & (uint32_t)RCC_SWS) != (uint32_t)0x08); // Wait till PLL is used as system clock source + + // Once clock is up and running, we can enable the UART for other debugging. + + // Enable GPIOD and UART. + RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_USART1; + + // Push-Pull, 10MHz Output, GPIO D5, with AutoFunction + GPIOD->CFGLR &= ~(0xf<<(4*5)); + GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP_AF)<<(4*5); + + // 115200, 8n1. Note if you don't specify a mode, UART remains off even when UE_Set. + USART1->CTLR1 = USART_WordLength_8b | USART_Parity_No | USART_Mode_Tx; + USART1->CTLR2 = USART_StopBits_1; + USART1->CTLR3 = USART_HardwareFlowControl_None; + + #define UART_BAUD_RATE 115200 + #define OVER8DIV 4 + #define INTEGER_DIVIDER (((25 * (APB_CLOCK)) / (OVER8DIV * (UART_BAUD_RATE)))) + #define FRACTIONAL_DIVIDER ((INTEGER_DIVIDER)%100) + USART1->BRR = ((INTEGER_DIVIDER / 100) << 4) | ((((FRACTIONAL_DIVIDER * (OVER8DIV*2)) + 50)/100)&7); + USART1->CTLR1 |= CTLR1_UE_Set; +} + +#define NR_LEDS 197 + +uint16_t phases[NR_LEDS]; +int frameno; +int tween = 0; +int tweendir = 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 ice = 0xff | ((rs)<<8) | (rs<<16); + return ice; + //return TweenHexColors( fire, ice, tween ); // Where "tween" is a value from 0 ... 255 +} + +int main() +{ + int k; + + // Enable GPIOD (for debugging) + RCC->APB2PCENR |= RCC_APB2Periph_GPIOD; + GPIOD->CFGLR &= ~(0xf<<(4*0)); + GPIOD->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*0); + + WS2812BDMAInit( ); + + printf( "CFG: %08x\n", SPI1->CTLR1 ); + + frameno = 0; + + for( k = 0; k < NR_LEDS; k++ ) phases[k] = k<<8; + + + while(1) + { + // Wait for LEDs to totally finish. + if( WS2812BLEDDone ) + { + 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 ); + } + } +} +