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 Mike R

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