diff options
author | David Crocker <dcrocker@eschertech.com> | 2020-07-16 16:07:49 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2020-07-16 16:07:49 +0300 |
commit | b66bd9e3d8aafb83fb137909d61fa14cb8ad40ac (patch) | |
tree | 34e567ce13b5b61599c2bd76d020f94c819272f9 /src/Fans | |
parent | 3bc14dd7f859c1b12776a5b44ffd21c452996a5b (diff) |
Added support for bit-banging Neopixels on Mini 12864 display
Also enabled cache to get more accurate timing
Also added an untested 16-bit SPI mode for DotStar/Neopixel DMA
Diffstat (limited to 'src/Fans')
-rw-r--r-- | src/Fans/DotStarLed.cpp | 224 |
1 files changed, 188 insertions, 36 deletions
diff --git a/src/Fans/DotStarLed.cpp b/src/Fans/DotStarLed.cpp index 0bebc954..e3aaac02 100644 --- a/src/Fans/DotStarLed.cpp +++ b/src/Fans/DotStarLed.cpp @@ -17,11 +17,19 @@ # include <sam/drivers/usart/usart.h> #else # include <DmacManager.h> -# if SAME70 +# if SAME5x +# include <Hardware/IoPorts.h> +# elif SAME70 # include <sam/drivers/xdmac/xdmac.h> # endif #endif +#define USE_16BIT_SPI 1 // set to use 16-bit SPI transfers instead of 8-bit + +#if USE_16BIT_SPI && DOTSTAR_USES_USART +# error Invalid combination +#endif + // Duet 5 Mini only supports NeoPixel, not DotStar. So the DotStar code is dead in the Duet 3 Mini build. namespace DotStarLed @@ -35,7 +43,7 @@ namespace DotStarLed constexpr unsigned int MaxDotStarChunkSize = ChunkBufferSize/4; // maximum number of DotStarLEDs we DMA to in one go. Most strips have 30 LEDs/metre. constexpr unsigned int MaxNeoPixelChunkSize = ChunkBufferSize/12; // maximum number of NeoPixels we can support. A full ring contains 60. - static uint32_t ledType = 1; // 0 = DotStar (not supported on Duet 3 Mini), 1 = NeoPixel + static uint32_t ledType = 1; // 0 = DotStar (not supported on Duet 3 Mini), 1 = NeoPixel, 2 = NeoPixel on Mini 12864 display (Duet 3 Mini only) static uint32_t whenDmaFinished = 0; // the time in step clocks when we determined that the DMA had finished static unsigned int numRemaining = 0; // how much of the current request remains after the current transfer (DotStar only) static unsigned int totalSent = 0; // total amount of data sent since the start frame (DotStar only) @@ -59,7 +67,11 @@ namespace DotStarLed //TODO use 16-bit mode to make the DMA more efficient, but that probably requires us to swap alternate bytes in the buffer DmacManager::DisableChannel(DmacChanDotStarTx); DmacManager::SetTriggerSource(DmacChanDotStarTx, DmaTrigSource::qspi_tx); +# if USE_16BIT_SPI + DmacManager::SetBtctrl(DmacChanDotStarTx, DMAC_BTCTRL_STEPSIZE_X2 | DMAC_BTCTRL_STEPSEL_SRC | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_BEATSIZE_HWORD | DMAC_BTCTRL_BLOCKACT_NOACT); +# else DmacManager::SetBtctrl(DmacChanDotStarTx, DMAC_BTCTRL_STEPSIZE_X1 | DMAC_BTCTRL_STEPSEL_SRC | DMAC_BTCTRL_SRCINC | DMAC_BTCTRL_BEATSIZE_BYTE | DMAC_BTCTRL_BLOCKACT_NOACT); +# endif DmacManager::SetSourceAddress(DmacChanDotStarTx, chunkBuffer); DmacManager::SetDestinationAddress(DmacChanDotStarTx, &QSPI->TXDATA.reg); DmacManager::SetDataLength(DmacChanDotStarTx, numBytes); // must do this last! @@ -71,13 +83,21 @@ namespace DotStarLed | XDMAC_CC_MBSIZE_SINGLE | XDMAC_CC_DSYNC_MEM2PER | XDMAC_CC_CSIZE_CHK_1 +# if USE_16BIT_SPI + | XDMAC_CC_DWIDTH_HALFWORD +# else | XDMAC_CC_DWIDTH_BYTE +# endif | XDMAC_CC_SIF_AHB_IF0 | XDMAC_CC_DIF_AHB_IF1 | XDMAC_CC_SAM_INCREMENTED_AM | XDMAC_CC_DAM_FIXED_AM | XDMAC_CC_PERID((uint32_t)DmaTrigSource::qspitx); +# if USE_16BIT_SPI + p_cfg.mbr_ubc = numBytes/2; //TODO is this correct? The datasheet isn't clear. +#else p_cfg.mbr_ubc = numBytes; +#endif p_cfg.mbr_sa = reinterpret_cast<uint32_t>(chunkBuffer); p_cfg.mbr_da = reinterpret_cast<uint32_t>(&(QSPI->QSPI_TDR)); xdmac_configure_transfer(XDMAC, DmacChanDotStarTx, &p_cfg); @@ -126,13 +146,21 @@ namespace DotStarLed #elif SAME5x // DotStar on Duet 3 Mini uses the QSPI peripheral QSPI->CTRLA.reg = QSPI_CTRLA_SWRST; // software reset - QSPI->CTRLB.reg = 0; // SPI mode, 8 bits per transfer +# if USE_16BIT_SPI + QSPI->CTRLB.reg = QSPI_CTRLB_DATALEN_16BITS; // SPI mode, 8 bits per transfer +# else + QSPI->CTRLB.reg = QSPI_CTRLB_DATALEN_8BITS; // SPI mode, 8 bits per transfer +# endif QSPI->BAUD.reg = QSPI_BAUD_CPOL | QSPI_BAUD_CPHA | QSPI_BAUD_BAUD(SystemCoreClockFreq/frequency - 1); QSPI->CTRLA.reg = QSPI_CTRLA_ENABLE; #elif SAME70 // DotStar on Duet 3 uses the QSPI peripheral QSPI->QSPI_CR = QSPI_CR_SWRST; - QSPI->QSPI_MR = 0; // SPI mode, 8 bits per transfer +# if USE_16BIT_SPI + QSPI->QSPI_MR = QSPI_MR_NBBITS_16_BIT; // SPI mode, 16 bits per transfer +# else + QSPI->QSPI_MR = QSPI_MR_NBBITS_8_BIT; // SPI mode, 8 bits per transfer +# endif QSPI->QSPI_SCR = QSPI_SCR_CPOL | QSPI_SCR_CPHA | QSPI_SCR_SCBR(SystemPeripheralClock()/frequency - 1); QSPI->QSPI_CR = QSPI_CR_QSPIEN; #endif @@ -199,14 +227,27 @@ namespace DotStarLed { static constexpr uint8_t EncodedByte[4] = { 0b10001000, 0b10001110, 0b11101000, 0b11101110 }; - *p++ = EncodedByte[val >> 6]; - *p++ = EncodedByte[(val >> 4) & 3]; - *p++ = EncodedByte[(val >> 2) & 3]; - *p++ = EncodedByte[val & 3]; +#if USE_16BIT_SPI + if (ledType == 1) + { + // Swap bytes for 16-bit DMA + *p++ = EncodedByte[(val >> 4) & 3]; + *p++ = EncodedByte[val >> 6]; + *p++ = EncodedByte[val & 3]; + *p++ = EncodedByte[(val >> 2) & 3]; + } + else +#endif + { + *p++ = EncodedByte[val >> 6]; + *p++ = EncodedByte[(val >> 4) & 3]; + *p++ = EncodedByte[(val >> 2) & 3]; + *p++ = EncodedByte[val & 3]; + } } - // Send data to NeoPixel LEDs - static GCodeResult SendNeoPixelData(uint8_t red, uint8_t green, uint8_t blue, uint32_t numLeds, bool following) noexcept + // Send data to NeoPixel LEDs by DMA to SPI + static GCodeResult SpiSendNeoPixelData(uint8_t red, uint8_t green, uint8_t blue, uint32_t numLeds, bool following) noexcept { uint8_t *p = chunkBuffer + (12 * numAlreadyInBuffer); while (numLeds != 0 && p <= chunkBuffer + ARRAY_SIZE(chunkBuffer) - 12) @@ -220,13 +261,102 @@ namespace DotStarLed --numLeds; ++numAlreadyInBuffer; } + + if (!following) + { + DmaSendChunkBuffer(12 * numAlreadyInBuffer); // send data by DMA to SPI + numAlreadyInBuffer = 0; + } + return GCodeResult::ok; + } + +#ifdef DUET_5LC + constexpr uint32_t NanosecondsToCycles(uint32_t ns) noexcept + { + return (ns * (uint64_t)SystemCoreClockFreq)/1000000000u; + } + + constexpr uint32_t T0H = NanosecondsToCycles(350); + constexpr uint32_t T0L = NanosecondsToCycles(850); + constexpr uint32_t T1H = NanosecondsToCycles(800); + constexpr uint32_t T1L = NanosecondsToCycles(475); + + // Send data to NeoPixel LEDs by bit banging + static GCodeResult BitBangNeoPixelData(uint8_t red, uint8_t green, uint8_t blue, uint32_t numLeds, bool following) noexcept + { + uint8_t *p = chunkBuffer + (3 * numAlreadyInBuffer); + while (numLeds != 0 && p <= chunkBuffer + ARRAY_SIZE(chunkBuffer) - 3) + { + *p++ = green; + *p++ = red; + *p++ = blue; + --numLeds; + ++numAlreadyInBuffer; + } + if (!following) { - DmaSendChunkBuffer(12 * numAlreadyInBuffer); + const uint8_t *q = chunkBuffer; + cpu_irq_disable(); +#if 1 + uint32_t lastTransitionTime = SysTick->VAL & 0x00FFFFFF; + uint32_t nextDelay = SystemCoreClockFreq/1000000; + while (q < p) + { + uint8_t c = *q++; + for (unsigned int i = 0; i < 8; ++i) + { + if (c & 0x80) + { + lastTransitionTime = DelayCycles(lastTransitionTime, nextDelay); + fastDigitalWriteHigh(LcdNeopixelOutPin); + lastTransitionTime = DelayCycles(lastTransitionTime, T1H); + fastDigitalWriteLow(LcdNeopixelOutPin); + nextDelay = T1L; + } + else + { + lastTransitionTime = DelayCycles(lastTransitionTime, nextDelay); + fastDigitalWriteHigh(LcdNeopixelOutPin); + lastTransitionTime = DelayCycles(lastTransitionTime, T0H); + fastDigitalWriteLow(LcdNeopixelOutPin); + nextDelay = T0L; + } + c <<= 1; + } + } +#else + while (q < p) + { + uint8_t c = *q++; + for (unsigned int i = 0; i < 8; ++i) + { + // The delays in the following have been adjusted to work on the SAMD51 with cache disabled + if (c & 0x80) + { + fastDigitalWriteHigh(LcdNeopixelOutPin); + delayCycles3(10); // about 800ns total high time + fastDigitalWriteLow(LcdNeopixelOutPin); + delayCycles3(7); // about 475ns total low time + } + else + { + fastDigitalWriteHigh(LcdNeopixelOutPin); + delayCycles3(7); // about 350ns total high time + fastDigitalWriteLow(LcdNeopixelOutPin); + delayCycles3(11); // about 850ns total low time + } + c <<= 1; + } + } +#endif + cpu_irq_enable(); numAlreadyInBuffer = 0; + whenDmaFinished = StepTimer::GetTimerTicks(); } return GCodeResult::ok; } +#endif } void DotStarLed::Init() noexcept @@ -269,7 +399,7 @@ GCodeResult DotStarLed::SetColours(GCodeBuffer& gb, const StringRef& reply) THRO return GCodeResult::notFinished; } - if (needStartFrame && ledType == 1 && StepTimer::GetTimerTicks() - whenDmaFinished < MinNeoPixelResetTicks) + if (needStartFrame && (ledType == 1 || ledType == 2) && StepTimer::GetTimerTicks() - whenDmaFinished < MinNeoPixelResetTicks) { return GCodeResult::notFinished; // give the NeoPixels time to reset } @@ -280,21 +410,25 @@ GCodeResult DotStarLed::SetColours(GCodeBuffer& gb, const StringRef& reply) THRO if (gb.Seen('X')) { seen = true; - const uint32_t newType = gb.GetLimitedUIValue('X', 2); // only types 0 and 1 are supported + const uint32_t newType = gb.GetLimitedUIValue('X', #ifdef DUET_5LC - if (newType == 0) - { - throw GCodeException(gb.MachineState().lineNumber, -1, "DotStar not supported on this platform. Use NeoPixel."); - } + 3, 1 // types 1 and 2 are supported +#else + 2, 0 // types 0 and 1 are supported #endif + ); const bool typeChanged = (newType != ledType); - bool setFrequency = typeChanged; - uint32_t frequency = DefaultSpiFrequencies[newType]; - gb.TryGetUIValue('Q', frequency, setFrequency); - if (setFrequency) + if (newType != 2) { - SetupSpi(frequency); + bool setFrequency = typeChanged; + + uint32_t frequency = DefaultSpiFrequencies[newType]; + gb.TryGetUIValue('Q', frequency, setFrequency); + if (setFrequency) + { + SetupSpi(frequency); + } } if (typeChanged) @@ -310,6 +444,15 @@ GCodeResult DotStarLed::SetColours(GCodeBuffer& gb, const StringRef& reply) THRO DmaSendChunkBuffer(1); return GCodeResult::notFinished; } +#ifdef DUET_5LC + else if (ledType == 2) + { + // Set the data output low to start a WS2812 reset sequence + IoPort::SetPinMode(LcdNeopixelOutPin, PinMode::OUTPUT_LOW); + whenDmaFinished = StepTimer::GetTimerTicks(); + return GCodeResult::notFinished; + } +#endif } } @@ -342,27 +485,36 @@ GCodeResult DotStarLed::SetColours(GCodeBuffer& gb, const StringRef& reply) THRO switch (ledType) { +#ifndef DUET_5LC case 0: // DotStar - -#ifdef DUET_5LC - // DotStar not supported, so avoid dragging the code for SendDotStarData in - break; -#else { - const uint32_t data = (brightness >> 3) | 0xE0 | ((blue & 255) << 8) | ((green & 255) << 16) | ((red & 255) << 24); +#if USE_16BIT_SPI + // Swap bytes for 16-bit SPI + const uint32_t data = ((brightness >> 11) | (0xE0 << 8)) | ((blue & 255)) | ((green & 255) << 24) | ((red & 255) << 16); +#else + const uint32_t data = ((brightness >> 3) | 0xE0) | ((blue & 255) << 8) | ((green & 255) << 16) | ((red & 255) << 24); +#endif return SendDotStarData(data, numLeds, following); } #endif case 1: // NeoPixel - { - // Scale RGB by the brightness - return SendNeoPixelData( (uint8_t)((red * brightness + 255) >> 8), - (uint8_t)((green * brightness + 255) >> 8), - (uint8_t)((blue * brightness + 255) >> 8), - numLeds, following - ); - } + // Scale RGB by the brightness + return SpiSendNeoPixelData( (uint8_t)((red * brightness + 255) >> 8), + (uint8_t)((green * brightness + 255) >> 8), + (uint8_t)((blue * brightness + 255) >> 8), + numLeds, following + ); + +#ifdef DUET_5LC + case 2: + // Scale RGB by the brightness + return BitBangNeoPixelData( (uint8_t)((red * brightness + 255) >> 8), + (uint8_t)((green * brightness + 255) >> 8), + (uint8_t)((blue * brightness + 255) >> 8), + numLeds, following + ); +#endif } return GCodeResult::ok; // should never get here } |