Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: FastAnalogIn FastIO FastPWM SimpleDMA USBDevice 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
