Pinscape Controller version 1 fork. This is a fork to allow for ongoing bug fixes to the original controller version, from before the major changes for the expansion board project.
Dependencies: FastIO FastPWM SimpleDMA mbed
Fork of Pinscape_Controller by
Diff: TLC5940/TLC5940.h
- Revision:
- 30:6e9902f06f48
- Parent:
- 29:582472d0bc57
- Child:
- 33:d832bcab089e
--- a/TLC5940/TLC5940.h Fri Sep 25 18:49:53 2015 +0000 +++ b/TLC5940/TLC5940.h Fri Sep 25 21:28:31 2015 +0000 @@ -22,6 +22,7 @@ #include "mbed.h" #include "FastPWM.h" +#include "SimpleDMA.h" /** * SPI speed used by the mbed to communicate with the TLC5940 @@ -48,7 +49,6 @@ * isn't a factor. E.g., at SPI=30MHz and GSCLK=500kHz, * t(blank) is 8192us and t(refresh) is 25us. */ -#define USE_SPI 1 #define SPI_SPEED 3000000 /** @@ -112,22 +112,22 @@ * @param nchips - The number of TLC5940s (if you are daisy chaining) */ TLC5940(PinName SCLK, PinName MOSI, PinName GSCLK, PinName BLANK, PinName XLAT, int nchips) -#if USE_SPI : spi(MOSI, NC, SCLK), -#else - : sin(MOSI), sclk(SCLK), -#endif gsclk(GSCLK), blank(BLANK), xlat(XLAT), nchips(nchips), newGSData(true) { + // Set initial output pin states - XLAT off, BLANK on (BLANK turns off + // all of the outputs while we're setting up) + xlat = 0; + blank = 1; + // allocate the grayscale buffer gs = new unsigned short[nchips*16]; memset(gs, 0, nchips*16*sizeof(gs[0])); -#if USE_SPI // Configure SPI format and speed. Note that KL25Z ONLY supports 8-bit // mode. The TLC5940 nominally requires 12-bit data blocks for the // grayscale levels, but SPI is ultimately just a bit-level serial format, @@ -137,21 +137,32 @@ // format 0. spi.format(8, 0); spi.frequency(SPI_SPEED); -#else - sclk = 1; -#endif - // Set output pin states - xlat = 0; - blank = 1; + // Allocate a DMA buffer. The transfer on each cycle is 192 bits per + // chip = 24 bytes per chip. + dmabuf = new char[nchips*24]; - // Configure PWM output for GSCLK frequency at 50% duty cycle + // Set up the Simple DMA interface object. We use the DMA controller to + // send grayscale data updates to the TLC5940 chips. This lets the CPU + // keep running other tasks while we send gs updates, and importantly + // allows our blanking interrupt handler return almost immediately. + // The DMA transfer is from our internal DMA buffer to SPI0, which is + // the SPI controller physically connected to the TLC5940s. + sdma.source(dmabuf, 1); + sdma.destination(&(SPI0->D), 0, 8); + sdma.trigger(Trigger_SPI0_TX); + sdma.attach(this, &TLC5940::dmaDone); + + // Enable DMA on SPI0. SimpleDMA doesn't do this for us; we have to + // do it explicitly. This is just a matter of setting bit 5 (TXDMAE) + // in the SPI controllers Control Register 2 (C2). + SPI0->C2 |= 0x20; // set bit 5 = 0x20 = TXDMAE in SPI0 control register 2 + + // Configure the GSCLK output's frequency gsclk.period(1.0/GSCLK_SPEED); - gsclk.write(.5); - blank = 0; - } + } - // start the clock running + // Start the clock running void start() { // Set up the first call to the reset function, which asserts BLANK to @@ -178,6 +189,7 @@ ~TLC5940() { delete [] gs; + delete [] dmabuf; } /** @@ -189,20 +201,23 @@ { // store the data, and flag the pending update for the interrupt handler to carry out gs[idx] = data; -// newGSData = true; + newGSData = true; } private: // current level for each output unsigned short *gs; -#if USE_SPI + // Simple DMA interface object + SimpleDMA sdma; + + // DMA transfer buffer. Each time we have data to transmit to the TLC5940 chips, + // we format the data into this buffer exactly as it will go across the wire, then + // hand the buffer to the DMA controller to move through the SPI port. + char *dmabuf; + // SPI port - only MOSI and SCK are used SPI spi; -#else - DigitalOut sin; - DigitalOut sclk; -#endif // use a PWM out for the grayscale clock - this provides a stable // square wave signal without consuming CPU @@ -225,12 +240,11 @@ // Function to reset the display and send the next chunks of data void reset() { - // turn off the grayscale clock, and assert BLANK to end the grayscale cycle - gsclk.write(0); - blank = 1; + // start the blanking cycle + startBlank(); // If we have new GS data, send it now - if (true) // (newGSData) + if (true) { // Send the new grayscale data. // @@ -250,26 +264,40 @@ // data refresh into the blanking interval, on the other // hand, seems to entirely eliminate any instability. // - // Note that there's no CPU performance penalty to this - // approach. The KL25Z SPI implementation isn't capable of - // asynchronous DMA, so the CPU has to wait for the - // transmission no matter when it happens. The only downside - // I see to this approach is that it decreases the duty cycle - // of the PWM during updates - but very slightly. With the - // SPI clock at 30 MHz and the PWM clock at 500 kHz, the full - // PWM cycle is 8192us, and the data refresh time is 25us. - // So by doing the data refersh in the blanking interval, - // we're effectively extending the PWM cycle to 8217us, - // which is 0.3% longer. Since the outputs are all off - // during the blanking cycle, this is equivalent to - // decreasing all of the output brightnesses by 0.3%. That - // should be imperceptible to users. + // update() will format the current grayscale data into our + // DMA transfer buffer and kick off the DMA transfer, then + // return. At that point we can return from the interrupt, + // but WITHOUT ending the blanking cycle - we want to keep + // blanking the outputs until the DMA transfer finishes. When + // the transfer is complete, the DMA controller will fire an + // interrupt that will trigger our dmaDone() callback, at + // which point we'll finally complete the blanking cycle and + // start a new grayscale cycle. update(); // the chips are now in sync with our data, so we have no more // pending update newGSData = false; + } + else + { + // no new grayscale data - just end the blanking cycle without + // a new XLAT + endBlank(false); + } + } + + void startBlank() + { + // turn off the grayscale clock, and assert BLANK to end the grayscale cycle + gsclk.write(0); + blank = 1; + } + void endBlank(bool needxlat) + { + if (needxlat) + { // latch the new data while we're still blanked xlat = 1; xlat = 0; @@ -285,12 +313,20 @@ void update() { -#if USE_SPI - // Send GS data. The serial format orders the outputs from last to first - // (output #15 on the last chip in the daisy-chain to output #0 on the - // first chip). For each output, we send 12 bits containing the grayscale - // level (0 = fully off, 0xFFF = fully on). Bit order is most significant - // bit first. + // Send new grayscale data to the TLC5940 chips. + // + // To do this, we set up our DMA buffer with the bytes formatted exactly + // as they will go across the wire, then kick off the transfer request with + // the DMA controller. We can then return from the interrupt and continue + // with other tasks while the DMA hardware handles the transfer for us. + // When the transfer is completed, the DMA controller will fire an + // interrupt, which will call our interrupt handler, which will finish + // the blanking cycle. + // + // The serial format orders the outputs from last to first (output #15 on + // the last chip in the daisy-chain to output #0 on the first chip). For + // each output, we send 12 bits containing the grayscale level (0 = fully + // off, 0xFFF = fully on). Bit order is most significant bit first. // // The KL25Z SPI can only send in 8-bit increments, so we need to divvy up // the 12-bit outputs into 8-bit bytes. Each pair of 12-bit outputs adds up @@ -300,33 +336,32 @@ // [ element i+1 bits ] [ element i bits ] // 11 10 9 8 7 6 5 4 3 2 1 0 11 10 9 8 7 6 5 4 3 2 1 0 // [ first byte ] [ second byte ] [ third byte ] - for (int i = 61 /* (16 * nchips) - 2 */ ; i >= 0 ; i -= 2) + for (int i = (16 * nchips) - 2, dst = 0 ; i >= 0 ; i -= 2) { // first byte - element i+1 bits 4-11 - spi.write(((gs[i+1] & 0xFF0) >> 4) & 0xff); + dmabuf[dst++] = (((gs[i+1] & 0xFF0) >> 4) & 0xff); // second byte - element i+1 bits 0-3, then element i bits 8-11 - spi.write((((gs[i+1] & 0x00F) << 4) | ((gs[i] & 0xF00) >> 8)) & 0xFF); + dmabuf[dst++] = ((((gs[i+1] & 0x00F) << 4) | ((gs[i] & 0xF00) >> 8)) & 0xFF); // third byte - element i bits 0-7 - spi.write(gs[i] & 0x0FF); + dmabuf[dst++] = (gs[i] & 0x0FF); } -#else - // Send GS data, from last output to first output, 12 bits per output, - // most significant bit first. - for (int i = 16*3 - 1 ; i >= 0 ; --i) - { - unsigned data = gs[i]; - for (unsigned int mask = 1 << 11, bit = 0 ; bit < 12 ; ++bit, mask >>= 1) - { - sclk = 0; - sin = (data & mask) ? 1 : 0; - sclk = 1; - } - } -#endif + + // Start the DMA transfer + sdma.start(nchips*24); } + + // Interrupt handler for DMA completion. The DMA controller calls this + // when it finishes with the transfer request we set up above. When the + // transfer is done, we simply end the blanking cycle and start a new + // grayscale cycle. + void dmaDone() + { + // when the DMA transfer is finished, start the next grayscale cycle + endBlank(true); + } + }; - #endif