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

Files at this revision

API Documentation at this revision

Comitter:
mjr
Date:
Fri Sep 25 21:28:31 2015 +0000
Parent:
29:582472d0bc57
Child:
31:1075f5b1c6a7
Commit message:
Use DMA for TLC5940 SPI transfer to reduce time interrupt handler (fixes problem with MMA8415Q freezing up). All LedWiz flashing modes now fully supported.

Changed in this revision

SimpleDMA.lib Show annotated file Show diff for this revision Revisions of this file
TLC5940/TLC5940.h Show annotated file Show diff for this revision Revisions of this file
config.h Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SimpleDMA.lib	Fri Sep 25 21:28:31 2015 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/Sissors/code/SimpleDMA/#876f3b55e6f5
--- 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
--- a/config.h	Fri Sep 25 18:49:53 2015 +0000
+++ b/config.h	Fri Sep 25 21:28:31 2015 +0000
@@ -101,7 +101,7 @@
 // here.
 const uint8_t DEFAULT_LEDWIZ_UNIT_NUMBER = 
 #ifdef ENABLE_JOYSTICK
-   0x01;   // joystick enabled - assume we're the primary KL25Z, so use unit #8
+   0x08;   // joystick enabled - assume we're the primary KL25Z, so use unit #8
 #else
    0x09;   // joystick disabled - assume we're a secondary, output-only KL25Z, so use #9
 #endif
--- a/main.cpp	Fri Sep 25 18:49:53 2015 +0000
+++ b/main.cpp	Fri Sep 25 21:28:31 2015 +0000
@@ -610,25 +610,24 @@
     else if (val == 129)
     {
         //   129 = ramp up / ramp down
-        if (wizFlashCounter < 128)
-            return wizFlashCounter/127.0;
-        else
-            return (255 - wizFlashCounter)/127.0;
+        return wizFlashCounter < 128 
+            ? wizFlashCounter/128.0 
+            : (256 - wizFlashCounter)/128.0;
     }
     else if (val == 130)
     {
         //   130 = flash on / off
-        return (wizFlashCounter < 128 ? 1.0 : 0.0);
+        return wizFlashCounter < 128 ? 1.0 : 0.0;
     }
     else if (val == 131)
     {
         //   131 = on / ramp down
-        return (255 - wizFlashCounter)/255.0;
+        return wizFlashCounter < 128 ? 1.0 : (255 - wizFlashCounter)/128.0;
     }
     else if (val == 132)
     {
         //   132 = ramp up / on
-        return wizFlashCounter/255.0;
+        return wizFlashCounter < 128 ? wizFlashCounter/128.0 : 1.0;
     }
     else
     {