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:
- 33:d832bcab089e
- Parent:
- 30:6e9902f06f48
- Child:
- 38:091e511ce8a0
diff -r cbff13b98441 -r d832bcab089e TLC5940/TLC5940.h --- a/TLC5940/TLC5940.h Sat Sep 26 02:15:59 2015 +0000 +++ b/TLC5940/TLC5940.h Wed Oct 21 21:53:07 2015 +0000 @@ -20,6 +20,50 @@ #ifndef TLC5940_H #define TLC5940_H +// Should we do the grayscale update within the blanking interval? +// If this is set to 1, we'll send grayscale data during the blanking +// interval; if 0, we'll send grayscale during the PWM cycle. +// Mode 0 is the *intended* way of using these chips, but mode 1 +// produces a more stable signal in my test setup. +// +// In my breadboard testing, using the standard data-during-PWM +// mode causes some amount of signal instability with multiple +// daisy-chained TLC5940's. It appears that there's some signal +// interference (maybe RF or electrical ringing in the wires) that +// can make the bit data and/or clock prone to noise that causes +// random bits to propagate down the daisy chain. This happens +// frequently enough in my breadboard setup to be visible as +// regular flicker. Careful wiring, short wire runs, and decoupling +// capacitors noticeably improve it, but I haven't been able to +// eliminate it entirely in my test setup. Using the data-during- +// blanking mode, however, *does* eliminate it entirely. +// +// It clearly should be possible to eliminate the signal problems +// in a well-designed PCB layout, but for the time being, I'm +// making data-during-blanking the default, since it provides +// such a noticeable improvement in my test setup, and the cost +// is minimal. The cost is that it lengthens the blanking interval +// slightly. With four chips and the SPI clock at 28MHz, the +// full data update takes 27us; with the PWM clock at 500kHz, the +// grayscale cycle is 8192us. This means that the 27us data send +// keeps the BLANK asserted for an additional 0.3% of the cycle +// time, which in term reduces output brightness by the same amount. +// This brightness reduction isn't noticeable on its own, but it +// can be seen as a flicker on data cycles if we send data on +// some blanking cycles but not on others. To eliminate the +// flicker, the code sends a data update on *every* cycle when +// using this mode to ensure that the 0.3% brightness reduction +// is uniform across time. +// +// When using this code with TLC5940 chips on a PCB, I recommend +// doing a test: set this to 0, run the board, turn on all outputs +// (connected to LEDs), and observe the results. If you don't +// see any randomness or flicker in a minute or two of observation, +// you're getting a good clean signal throughout the daisy chain +// and don't need the workaround. If you do see any instability, +// set this back to 1. +#define DATA_UPDATE_INSIDE_BLANKING 1 + #include "mbed.h" #include "FastPWM.h" #include "SimpleDMA.h" @@ -27,10 +71,16 @@ /** * SPI speed used by the mbed to communicate with the TLC5940 * The TLC5940 supports up to 30Mhz. It's best to keep this as - * high as the microcontroller will allow, since a higher SPI - * speed yields a faster grayscale data update. However, if - * you have problems with unreliable signal transmission to the - * TLC5940s, reducing this speed might help. + * high as possible, since a higher SPI speed yields a faster + * grayscale data update. However, I've seen some slight + * instability in the signal in my breadboard setup using the + * full 30MHz, so I've reduced this slightly, which seems to + * yield a solid signal. The limit will vary according to how + * clean the signal path is to the chips; you can probably crank + * this up to full speed if you have a well-designed PCB, good + * decoupling capacitors near the 5940 VCC/GND pins, and short + * wires between the KL25Z and the PCB. A short, clean path to + * KL25Z ground seems especially important. * * The SPI clock must be fast enough that the data transmission * time for a full update is comfortably less than the blanking @@ -49,7 +99,7 @@ * isn't a factor. E.g., at SPI=30MHz and GSCLK=500kHz, * t(blank) is 8192us and t(refresh) is 25us. */ -#define SPI_SPEED 3000000 +#define SPI_SPEED 2800000 /** * The rate at which the GSCLK pin is pulsed. This also controls @@ -116,15 +166,19 @@ gsclk(GSCLK), blank(BLANK), xlat(XLAT), - nchips(nchips), - newGSData(true) + nchips(nchips) { - // Set initial output pin states - XLAT off, BLANK on (BLANK turns off - // all of the outputs while we're setting up) + // set XLAT to initially off xlat = 0; + + // Assert BLANK while starting up, to keep the outputs turned off until + // everything is stable. This helps prevent spurious flashes during startup. + // (That's not particularly important for lights, but it matters more for + // tactile devices. It's a bit alarming to fire a replay knocker on every + // power-on, for example.) blank = 1; - // allocate the grayscale buffer + // allocate the grayscale buffer, and set all outputs to fully off gs = new unsigned short[nchips*16]; memset(gs, 0, nchips*16*sizeof(gs[0])); @@ -137,6 +191,22 @@ // format 0. spi.format(8, 0); spi.frequency(SPI_SPEED); + + // Send out a full data set to the chips, to clear out any random + // startup data from the registers. Include some extra bits - there + // are some cases (such as after sending dot correct commands) where + // an extra bit per chip is required, and the initial state is + // somewhat unpredictable, so send extra just to make sure we cover + // all bases. This does no harm; extra bits just fall off the end of + // the daisy chain, and since we want all registers set to 0, we can + // send arbitrarily many extra 0's. + for (int i = 0 ; i < nchips*25 ; ++i) + spi.write(0); + + // do an initial XLAT to latch all of these "0" values into the + // grayscale registers + xlat = 1; + xlat = 0; // Allocate a DMA buffer. The transfer on each cycle is 192 bits per // chip = 24 bytes per chip. @@ -160,6 +230,10 @@ // Configure the GSCLK output's frequency gsclk.period(1.0/GSCLK_SPEED); + + // mark that we need an initial update + newGSData = true; + needXlat = false; } // Start the clock running @@ -236,55 +310,43 @@ // Has new GS/DC data been loaded? volatile bool newGSData; + + // Do we need an XLAT signal on the next blanking interval? + volatile bool needXlat; // Function to reset the display and send the next chunks of data void reset() { // start the blanking cycle startBlank(); - - // If we have new GS data, send it now - if (true) - { - // Send the new grayscale data. - // - // Note that ideally, we'd do this during the new PWM cycle - // rather than during the blanking interval. The TLC5940 is - // specifically designed to allow this. However, in my testing, - // I found that sending new data during the PWM cycle was - // unreliable - it seemed to cause a fair amount of glitching, - // which as far as I can tell is signal noise coming from - // crosstalk between the grayscale clock signal and the - // SPI signal. This seems to be a common problem with - // daisy-chained TLC5940s. It can in principle be solved with - // careful high-speed circuit design (good ground planes, - // short leads, decoupling capacitors), and indeed I was able - // to improve stability to some extent with circuit tweaks, - // but I wasn't able to eliminate it entirely. Moving the - // data refresh into the blanking interval, on the other - // hand, seems to entirely eliminate any instability. - // - // 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. + +#if DATA_UPDATE_INSIDE_BLANKING + // We're configured to send the new GS data entirely within + // the blanking interval. Start the DMA transfer now, and + // return without ending the blanking interval. The DMA + // completion interrupt handler will do that when the data + // update has completed. + // + // Note that we do the data update/ unconditionally in the + // send-during-blanking case, whether or not we have new GS + // data. This is because the update causes a 0.3% reduction + // in brightness because of the elongated BLANK interval. + // That would be visible as a flicker on each update if we + // did updates on some cycles and not others. By doing an + // update on every cycle, we make the brightness reduction + // uniform across time, which makes it less perceptible. + update(); + +#else // DATA_UPDATE_INSIDE_BLANKING + + // end the blanking interval + endBlank(); + + // if we have pending grayscale data, start sending it + if (newGSData) 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); - } +#endif // DATA_UPDATE_INSIDE_BLANKING } void startBlank() @@ -294,13 +356,16 @@ blank = 1; } - void endBlank(bool needxlat) + void endBlank() { - if (needxlat) + // if we've sent new grayscale data since the last blanking + // interval, latch it by asserting XLAT + if (needXlat) { // latch the new data while we're still blanked xlat = 1; xlat = 0; + needXlat = false; } // end the blanking interval and restart the grayscale clock @@ -350,6 +415,9 @@ // Start the DMA transfer sdma.start(nchips*24); + + // we've now cleared the new GS data + newGSData = false; } // Interrupt handler for DMA completion. The DMA controller calls this @@ -358,8 +426,15 @@ // grayscale cycle. void dmaDone() { - // when the DMA transfer is finished, start the next grayscale cycle - endBlank(true); + // mark that we need to assert XLAT to latch the new + // grayscale data during the next blanking interval + needXlat = true; + +#if DATA_UPDATE_INSIDE_BLANKING + // we're doing the gs update within the blanking cycle, so end + // the blanking cycle now that the transfer has completed + endBlank(); +#endif } };