Engduino v1.0
EngduinoLEDs.cpp
Go to the documentation of this file.
00001 /**
00002 * \addtogroup EngduinoLEDs
00003 *
00004 * This is the driver code for LEDs on the Engduino
00005 * These LEDS are not directly connected to pins on the
00006 * AtMega32u4 processor. Instead they are connected through
00007 * LED drivers so should only be accessed through this code.
00008 *
00009 * The Engduino has 16 RGB LEDs on it, each of which
00010 * can be controlled independently. To make the granularity
00011 * of control greater, we implement a software PWM for each
00012 * LED. This allows for 16 levels of brightness in each of
00013 * the RGB channels on each LED with minimal flicker.
00014 * 
00015 * This implementation uses TIMER4 Comparator A to
00016 * implement the PWM. The timer is set to run at ~320Hz. The
00017 * clock is reset on interrupt, so you should be very wary
00018 * about using the other comparators.
00019 *
00020 * @{
00021 */
00022 
00023 /**
00024 * \file 
00025 *               Engduino LED driver
00026 * \author
00027 *               Engduino team: support@engduino.org
00028 */
00029 
00030 #include "pins_arduino.h"
00031 #include "EngduinoLEDs.h"
00032 
00033 /*---------------------------------------------------------------------------*/
00034 /**
00035 * \brief Constructor
00036 * 
00037 * C++ constructor for this class. Empty.
00038 */
00039 EngduinoLEDsClass::EngduinoLEDsClass()
00040 {
00041 }
00042 
00043 /*---------------------------------------------------------------------------*/
00044 /**
00045 * \brief begin function - must be called before using other functions
00046 *
00047 * The connection to the LEDs is not direct. Instead, it happens through
00048 * three LED drivers - one each for the R, G, and B channels. These are
00049 * connected through a daisy-chained SPI connection. There is a single latch
00050 * line connected to all driver chips, which latches the value set by SPI when
00051 * the line is pulsed high. The output from each driver chip can be switched on
00052 * or off with a separate output enable line to each driver - when these are LOW
00053 * the driver output lines go to the LEDs; when HIGH, they do not.
00054 * 
00055 */
00056 void EngduinoLEDsClass::begin() 
00057 {
00058         for (int i = 0; i < 15; i++) {
00059           RSet[i]   = GSet[i]   = BSet[i]   = 0;
00060           RAccum[i] = GAccum[i] = BAccum[i] = 0;
00061           RDisp[i]  = GDisp[i]  = BDisp[i]  = 0;
00062         }
00063         
00064         // RGB LED drivers - output enable lines
00065         pinMode(LED_R_OE, OUTPUT);
00066         pinMode(LED_G_OE, OUTPUT);
00067         pinMode(LED_B_OE, OUTPUT);
00068 
00069         // Disable the output from the LED drivers while we set up
00070         digitalWrite(LED_R_OE, HIGH);
00071         digitalWrite(LED_G_OE, HIGH);
00072         digitalWrite(LED_B_OE, HIGH);
00073         
00074         // Set up SPI data connection to the RGB LEDs
00075         pinMode(LED_MISO,  INPUT);
00076         pinMode(LED_MOSI,  OUTPUT);
00077         pinMode(LED_SCLK,  OUTPUT);
00078         pinMode(LED_LATCH, OUTPUT);
00079 
00080         // Clock line is initially low.
00081         // We must pulse high to tick
00082         digitalWrite(LED_SCLK, LOW);
00083         
00084         // Latch line is initially low
00085         // We must pulse high to latch
00086         digitalWrite(LED_LATCH, LOW);
00087         
00088         // The buffers might have something latched from the
00089         // last time they were programmed, so remove it - nothing
00090         // will be shown, because the LED driver OE lines are pulled
00091         // high at this point in time.
00092         setAll(OFF);
00093         
00094         // Set up timer 4. We use a software PWM to drive the LEDs to control both
00095         // brightness and colour. This requires a tick, which we set at 320Hz - this
00096         // is determined by the fact that we have 16 brightness levels per channel on
00097         // our PWM. Brightness 1 corresponds to 1 on period and 15 off periods. To
00098         // avoid flicker, we need this to happen at >15-18Hz, so we choose to run
00099         // through the 16 on/off periods 20 times per second, meaning that we need
00100         // a timer tick of 320Hz.
00101         //
00102         // Timer 4 is a free running 8 or 10 bit timer; NOTE: there is no CTC
00103         // (Clear Timer on Compare match) mode as for timers 1 and 3, so we must
00104         // manually reset the clock when an interrupt occurs.
00105     //
00106         // Note, as with other timers, if we set the count to, n, then we will
00107         // interrupt after n+1 clock cycles
00108         //
00109         // For a 10 bit write we must write the high byte before the low byte;
00110         // in this case there is a single shared register for the high bits, TC4H
00111         // and this is used for writing to ANY 10 bit register, so we need to be
00112         // aware of what is in there when we write what we might think is an
00113         // 8 bit value.
00114         //
00115         // At 8MHz with a /128 prescaler, one count is equal to 16 us
00116         // At 194 counts, we will interrupt every 195 periods:
00117         // at 3.12ms intervals, or 320.51Hz  
00118         //
00119         
00120         cli();          // Disable interrupts
00121 
00122         TIMSK4  = 0x00;         // Disable interrupts for all timer 4 comparisons, plus overflow
00123 
00124     TC4H    = 0x00;             // Reset the timer to zero, high byte first
00125         TCNT4   = 0x00;         // Reset the timer to zero
00126                                                 // This must be done first because the TC4H is used to provide
00127                                                 // high bits for the 10 bit registers accessed below
00128 
00129         TCCR4A  = 0x00;         // Turn off OC4Ax/OC4Bx connections, FfOC and PWM
00130         TCCR4B  = 0x08;         // No PWM inversion, don't reset the prescaler, don't set dead time, clock/128
00131     TCCR4C  = 0x00;             // Comparator B and D to normal mode
00132     TCCR4D  = 0x00;             // Fault protection off
00133     TCCR4E  = 0x00;             // Don't lock and switch off all output compare pins
00134         OCR4A   = 0x4F;         // Set the counter to 194 (0xC2), giving ~320Hz
00135 
00136         TIMSK4 |= 0x40;     // And enable interrupts for a compare A match only
00137 
00138         // Finally, enable the output from the LED drivers
00139         digitalWrite(LED_R_OE, LOW);
00140         digitalWrite(LED_G_OE, LOW);
00141         digitalWrite(LED_B_OE, LOW);
00142         
00143         sei();          // Enable interrupts    
00144 }
00145 
00146 
00147 /*---------------------------------------------------------------------------*/
00148 /**
00149 * \brief end function - switch off the LEDs
00150 *
00151 * We drive the output enables for all three drivers high. This disconnects
00152 * the driver from the LEDs and so all subsequent changes will have no effect.
00153 * We also stop the timer interrupt.
00154 * 
00155 */
00156 void EngduinoLEDsClass::end() 
00157 {
00158         // Disable output from LED drivers
00159         digitalWrite(LED_R_OE, HIGH);
00160         digitalWrite(LED_G_OE, HIGH);
00161         digitalWrite(LED_B_OE, HIGH);
00162 
00163         TIMSK4  = 0x00;         // Disable interrupts for all timer 4 comparisons, plus overflow
00164 }
00165 
00166 
00167 /*---------------------------------------------------------------------------*/
00168 /**
00169 * \brief Internal function to set an LED to a given colour/brightness.
00170 * \param LEDidx     LED index, ranging from 0 (for LED1) to 15 (LED16)
00171 * \param c          Colour, as chosen from the colour enum
00172 * \param brightness A 0-MAX_BRIGHTNESS(=15) value
00173 *
00174 * Internal function to set the colour/brightness of an LED. The colour value
00175 * here is chosen from an enum and corresponds to the primary and secondary
00176 * colours of light, plus white and off. Brightness ranges from 0 to
00177 * MAX_BRIGHTNESS (currently 15), and is shifted here to an internal 8 bit
00178 * representation (0-255) to make some of the maths slightly quicker later.
00179 * Choosing a brightness of zero will turn an LED off. 
00180 * 
00181 * Note: in the Engduino 1.0, we cannot use the full brightness for the LEDs
00182 * when the colour white is requested, simply because this causes too big a
00183 * drain on current whilst the device is attached to the USB. This
00184 * causes a system reset, which is both irritating and makes reprogramming
00185 * rather tedious.
00186 *
00187 */
00188 void EngduinoLEDsClass::_setLED(uint8_t LEDidx, colour c, uint8_t brightness)
00189 {       
00190         LEDidx &= 0x0F;                                 // We only have 16 LEDS
00191         brightness = brightness << 4;   // Translate brightness to a 0-255 scale
00192         
00193         switch (c) {
00194                 case RED:
00195                         RSet[LEDidx]=brightness; GSet[LEDidx]=0x00;       BSet[LEDidx]=0x00;
00196                         break;
00197                 case GREEN:
00198                         RSet[LEDidx]=0x00;       GSet[LEDidx]=brightness; BSet[LEDidx]=0x00;
00199                         break;
00200                 case BLUE:
00201                         RSet[LEDidx]=0x00;       GSet[LEDidx]=0x00;       BSet[LEDidx]=brightness;
00202                         break;
00203                 case YELLOW:
00204                         RSet[LEDidx]=brightness; GSet[LEDidx]=brightness; BSet[LEDidx]=0x00;
00205                         break;
00206                 case MAGENTA:
00207                         RSet[LEDidx]=brightness; GSet[LEDidx]=0x00;       BSet[LEDidx]=brightness;
00208                         break;
00209                 case CYAN:
00210                         RSet[LEDidx]=0x00;       GSet[LEDidx]=brightness; BSet[LEDidx]=brightness;
00211                         break;
00212                 case WHITE:
00213                         brightness = (brightness > 0xB0) ? 0xB0 : brightness;   // Can't use full scale for white
00214                         RSet[LEDidx]=brightness; GSet[LEDidx]=brightness; BSet[LEDidx]=brightness;              
00215                         break;
00216                 case OFF:
00217                         RSet[LEDidx]=0x00;       GSet[LEDidx]=0x00;       BSet[LEDidx]=0x00;
00218                         break;
00219         }
00220 }
00221 
00222 /*---------------------------------------------------------------------------*/
00223 /**
00224 * \brief Internal function to set an LED to a given point in the rgb space.
00225 * \param LEDidx     LED index, ranging from 0 (for LED1) to 15 (LED16)
00226 * \param r          Brightness of the red channel from 0-MAX_BRIGHTNESS (15)
00227 * \param g          Brightness of the green channel from 0-MAX_BRIGHTNESS (15)
00228 * \param b          Brightness of the blue channel from 0-MAX_BRIGHTNESS (15)
00229 *
00230 * Internal function to set the colour/brightness of an LED to a point in rgb
00231 * space. The brightness in each channel may range from 0 to MAX_BRIGHTNESS
00232 * (currently 15), and is shifted here to an internal 8 bit representation
00233 * (0-255) to make some of the maths slightly quicker later.
00234 * 
00235 * Note: in the Engduino 1.0, we cannot use the full brightness for the LEDs
00236 * when the colour white, or colours close to white are requested, simply
00237 * because this causes too big a drain on current whilst the device is attached
00238 * to the USB. This causes a system reset, which is both irritating and makes
00239 * reprogramming rather tedious. So we limit the sum of the rgb values to
00240 * a number we chose empirically, and we reduce the chosen rgb values until they
00241 * sum to less than that chosen.
00242 * 
00243 */
00244 void EngduinoLEDsClass::_setLED(uint8_t LEDidx, uint8_t r, uint8_t g, uint8_t b)
00245 {       uint16_t sum;
00246 
00247         LEDidx &= 0x0F;         // We only have 16 LEDS
00248         r = r << 4;                     // We only use 16 brightness levels to avoid flickering
00249         g = g << 4;                     // Turn the 0-15 scale into a 0-255 scale; makes maths easier
00250         b = b << 4;
00251 
00252         // The brightest colours cause a reset on the engduino v1.0
00253         // when running on USB. So we limit what we allow someone to choose.
00254         // This subtraction avoids division, which is slow.
00255         //
00256         sum = r + g + b;
00257         while (sum > 0x240) {   // Empirically determined: (0x24 << 4)
00258                 r -= 1;
00259                 g -= 1;
00260                 b -= 1;
00261                 sum = r + g + b;
00262         }
00263         
00264         RSet[LEDidx] = r;
00265         GSet[LEDidx] = g;
00266         BSet[LEDidx] = b;
00267 }
00268 
00269 /*---------------------------------------------------------------------------*/
00270 /**
00271 * \brief Set the colour of a single LED at maximum brightness
00272 * \param LEDNumber  LED number, ranging from 1 (for LED1) to 16 (LED16)
00273 * \param c          Colour, as chosen from the colour enum
00274 *
00275 * Set the colour of an LED. The colour value here is chosen from an enum and
00276 * corresponds to the primary and secondary colours of light, plus white and
00277 * off.
00278 * 
00279 */
00280 void EngduinoLEDsClass::setLED(uint8_t LEDNumber, colour c)
00281 {       
00282         _setLED(LEDNumber-1, c, MAX_BRIGHTNESS);
00283 }
00284 
00285 
00286 /*---------------------------------------------------------------------------*/
00287 /**
00288 * \brief Set an LED to a given colour/brightness.
00289 * \param LEDNumber  LED number, ranging from 1 (for LED1) to 16 (LED16)
00290 * \param c          Colour, as chosen from the colour enum
00291 * \param brightness A 0-MAX_BRIGHTNESS(=15) value
00292 *
00293 * Set the colour of an LED. The colour value here is chosen from an enum and
00294 * corresponds to the primary and secondary colours of light, plus white and
00295 * off. Brightness ranges from 0 to MAX_BRIGHTNESS (currently 15). Choosing a
00296 * brightness of zero will turn an LED off. 
00297 * 
00298 */
00299 void EngduinoLEDsClass::setLED(uint8_t LEDNumber, colour c, uint8_t brightness)
00300 {       
00301         _setLED(LEDNumber-1, c, brightness);
00302 }
00303 
00304 /*---------------------------------------------------------------------------*/
00305 /**
00306 * \brief Set an LED to a given point in the rgb space.
00307 * \param LEDNumber  LED number, ranging from 1 (for LED1) to 16 (LED16)
00308 * \param r          Brightness of the red channel from 0-MAX_BRIGHTNESS (15)
00309 * \param g          Brightness of the green channel from 0-MAX_BRIGHTNESS (15)
00310 * \param b          Brightness of the blue channel from 0-MAX_BRIGHTNESS (15)
00311 *
00312 * Set the colour/brightness of an LED to a point in rgb space. The brightness
00313 * in each channel may range from 0 to MAX_BRIGHTNESS (currently 15).
00314 * 
00315 * Note: in the Engduino 1.0, we cannot use the full brightness for the LEDs
00316 * when the colour white, or colours close to white are requested, simply
00317 * because this causes too big a drain on current whilst the device is attached
00318 * to the USB. This causes a system reset, which is both irritating and makes
00319 * reprogramming rather tedious. So we limit the sum of the rgb values to
00320 * a number we chose empirically, and we reduce the chosen rgb values until they
00321 * sum to less than that chosen.
00322 *
00323 */
00324 void EngduinoLEDsClass::setLED(uint8_t LEDNumber, uint8_t r, uint8_t g, uint8_t b)
00325 {       
00326         _setLED(LEDNumber-1, r, g, b);
00327 }
00328 
00329 /*---------------------------------------------------------------------------*/
00330 /**
00331 * \brief Set the colour of all LEDs at maximum brightness
00332 * \param c          Colour, as chosen from the colour enum
00333 *
00334 * Set the colour of all LEDs. The colour value here is chosen from an enum and
00335 * corresponds to the primary and secondary colours of light, plus white and
00336 * off.
00337 *
00338 */
00339 void EngduinoLEDsClass::setAll(colour c)
00340 {       
00341         for (int i = 0; i < 16; i++)
00342                 _setLED(i, c, MAX_BRIGHTNESS);
00343 }
00344 
00345 
00346 /*---------------------------------------------------------------------------*/
00347 /**
00348 * \brief Set all LEDs to a given colour/brightness.
00349 * \param c          Colour, as chosen from the colour enum
00350 * \param brightness A 0-MAX_BRIGHTNESS(=15) value
00351 *
00352 * Set the colour of all LEDs. The colour value here is chosen from an enum and
00353 * corresponds to the primary and secondary colours of light, plus white and
00354 * off. Brightness ranges from 0 to MAX_BRIGHTNESS (currently 15). Choosing a
00355 * brightness of zero will turn all LEDs off. 
00356 *
00357 */
00358 void EngduinoLEDsClass::setAll(colour c, uint8_t brightness)
00359 {       
00360         for (int i = 0; i < 16; i++) {
00361                 _setLED(i, c, brightness);
00362         }
00363 }
00364 
00365 /*---------------------------------------------------------------------------*/
00366 /**
00367 * \brief Set all LEDs to a given point in the rgb space.
00368 * \param r          Brightness of the red channel from 0-MAX_BRIGHTNESS (15)
00369 * \param g          Brightness of the green channel from 0-MAX_BRIGHTNESS (15)
00370 * \param b          Brightness of the blue channel from 0-MAX_BRIGHTNESS (15)
00371 *
00372 * Set the colour/brightness of all LEDs to a point in rgb space. The brightness
00373 * in each channel may range from 0 to MAX_BRIGHTNESS (currently 15).
00374 * 
00375 * Note: in the Engduino 1.0, we cannot use the full brightness for the LEDs
00376 * when the colour white, or colours close to white are requested, simply
00377 * because this causes too big a drain on current whilst the device is attached
00378 * to the USB. This causes a system reset, which is both irritating and makes
00379 * reprogramming rather tedious. So we limit the sum of the rgb values to
00380 * a number we chose empirically, and we reduce the chosen rgb values until they
00381 * sum to less than that chosen.
00382 *
00383 */
00384 void EngduinoLEDsClass::setAll(uint8_t r, uint8_t g, uint8_t b)
00385 {       
00386         for (int i = 0; i < 16; i++)
00387                 _setLED(i, r, g, b);
00388 }
00389 
00390 /*---------------------------------------------------------------------------*/
00391 /**
00392 * \brief Set the colour of all LEDs at maximum brightness from an array of
00393 *        individual values
00394 * \param c          Array of colour values, as chosen from the colour enum
00395 *
00396 * Set the colour of all LEDs from an array of colour values - i.e. each LED
00397 * can be set to a different colour, though all are at maximum brightness. The
00398 * colour value here is chosen from an enum and corresponds to the primary and
00399 * secondary colours of light, plus white and off.
00400 *
00401 */
00402 void EngduinoLEDsClass::setLEDs(colour c[16])
00403 {       
00404         for (int i = 0; i < 16; i++)
00405                 _setLED(i, c[i], MAX_BRIGHTNESS);
00406 }
00407 
00408 
00409 /*---------------------------------------------------------------------------*/
00410 /**
00411 * \brief Set all LEDs to a given colour/brightness from arrays of
00412 *        individual values
00413 * \param c          Array of colour values, as chosen from the colour enum
00414 * \param brightness Array of brightness values, from 0-MAX_BRIGHTNESS(=15)
00415 *
00416 * Set the colour/brightness of all LEDs from two arrays of values - i.e.
00417 * each LED can be set to a different colour/brightness value. The colour
00418 * value here is chosen from an enum and corresponds to the primary and
00419 * secondary colours of light, plus white and off. Brightness ranges from 0
00420 * to MAX_BRIGHTNESS (currently 15). Choosing a brightness of zero will turn
00421 * an LED off. 
00422 *
00423 */
00424 void EngduinoLEDsClass::setLEDs(colour c[16], uint8_t brightness[16])
00425 {       
00426         for (int i = 0; i < 16; i++) {
00427                 _setLED(i, c[i], brightness[i]);
00428         }
00429 }
00430 
00431 /*---------------------------------------------------------------------------*/
00432 /**
00433 * \brief Set all LEDs to a given point in the rgb space from arrays of
00434 *        individual values.
00435 * \param r          Array of red channel brightness, from 0-MAX_BRIGHTNESS (15)
00436 * \param g          Array of green channel brightness, from 0-MAX_BRIGHTNESS (15)
00437 * \param b          Array of blue channel brightness, from 0-MAX_BRIGHTNESS (15)
00438 *
00439 * Set the colour/brightness of all LEDs to a point in rgb space from three
00440 * arrays of values - i.e. each LED can be set to a different rgb point. The
00441 * brightness in each channel may range from 0 to MAX_BRIGHTNESS (currently 15).
00442 * 
00443 * Note: in the Engduino 1.0, we cannot use the full brightness for the LEDs
00444 * when the colour white, or colours close to white are requested, simply
00445 * because this causes too big a drain on current whilst the device is attached
00446 * to the USB. This causes a system reset, which is both irritating and makes
00447 * reprogramming rather tedious. So we limit the sum of the rgb values to
00448 * a number we chose empirically, and we reduce the chosen rgb values until they
00449 * sum to less than that chosen.
00450 *
00451 */
00452 void EngduinoLEDsClass::setLEDs(uint8_t r[16], uint8_t g[16], uint8_t b[16])
00453 {       
00454         for (int i = 0; i < 16; i++)
00455                 _setLED(i, r[i], g[i], b[i]);
00456 }
00457 
00458 /*---------------------------------------------------------------------------*/
00459 /**
00460 * \brief Set all LEDs to a given point in the rgb space a 2D array of
00461 *        individual values. The array is of form rgb[3][16]
00462 * \param rgb        2D array of rgb brightnesses, from 0-MAX_BRIGHTNESS (15)
00463 *
00464 * Set the colour/brightness of all LEDs to a point in rgb space from a 2D
00465 * array values - i.e. each LED can be set to a different rgb point. The array
00466 * is of form rgb[3][16] - i.e. the first index is the red/green/blue channel
00467 * and the second is the LED (ranging from 0-15). The brightness in each
00468 * channel may range from 0 to MAX_BRIGHTNESS (currently 15).
00469 * 
00470 * Note: in the Engduino 1.0, we cannot use the full brightness for the LEDs
00471 * when the colour white, or colours close to white are requested, simply
00472 * because this causes too big a drain on current whilst the device is attached
00473 * to the USB. This causes a system reset, which is both irritating and makes
00474 * reprogramming rather tedious. So we limit the sum of the rgb values to
00475 * a number we chose empirically, and we reduce the chosen rgb values until they
00476 * sum to less than that chosen.
00477 *
00478 */
00479 void EngduinoLEDsClass::setLEDs(uint8_t rgb[3][16])
00480 {       
00481         for (int i = 0; i < 16; i++)
00482                 _setLED(i, rgb[0][i], rgb[1][i], rgb[2][i]);
00483 }
00484 
00485 
00486 /*---------------------------------------------------------------------------*/
00487 /**
00488 * \brief Set all LEDs to a given point in the rgb space a 2D array of
00489 *        individual values. The array is of form rgb[16][3]
00490 * \param rgb        2D array of rgb brightnesses, from 0-MAX_BRIGHTNESS (15)
00491 *
00492 * Set the colour/brightness of all LEDs to a point in rgb space from a 2D
00493 * array values - i.e. each LED can be set to a different rgb point. The array
00494 * is of form rgb[16][3] - i.e. the first index is the LED (ranging from 0-15).
00495 * and the second is the red/green/blue channel. The brightness in each
00496 * channel may range from 0 to MAX_BRIGHTNESS (currently 15).
00497 * 
00498 * Note: in the Engduino 1.0, we cannot use the full brightness for the LEDs
00499 * when the colour white, or colours close to white are requested, simply
00500 * because this causes too big a drain on current whilst the device is attached
00501 * to the USB. This causes a system reset, which is both irritating and makes
00502 * reprogramming rather tedious. So we limit the sum of the rgb values to
00503 * a number we chose empirically, and we reduce the chosen rgb values until they
00504 * sum to less than that chosen.
00505 *
00506 */
00507 void EngduinoLEDsClass::setLEDs(uint8_t rgb[16][3])
00508 {       
00509         for (int i = 0; i < 16; i++)
00510                 _setLED(i, rgb[i][0], rgb[i][1], rgb[i][2]);
00511 }
00512 
00513 
00514 /*---------------------------------------------------------------------------*/
00515 /**
00516 * \brief This is an internal routine to set the LED latches appropriately
00517 * \param value      The on/off bits for each LED on a given channel
00518 *
00519 * This function is written using low level C programming primitives for
00520 * changing the voltages on ATMega32U4 pins rather than the Arduino
00521 * digitalWrite equivalents. This has to be done in this way for speed - it is
00522 * around 18 times faster than the alternative and, because we update the
00523 * values rather frequently to avoid flickering, speed is important. This code
00524 * manually emulates SPI functionality (mode 0), both for simplicity and speed
00525 * 
00526 * Notes:
00527 * PB1 is pin 9  - SPI SCLK
00528 * PB2 is pin 10 - SPI MOSI
00529 * PB3 is pin 11 - SPI MISO
00530 *
00531 */
00532 inline void writeLEDs(volatile uint8_t *value)
00533 {
00534         // This is code to emulate SPI mode 0 = we set the
00535         // data value first and then tick the clock with a rising
00536         // edge to latch the data into the slave.
00537         //
00538         for (int i = 15; i >= 0; i--) {
00539                 if (value[i] != 0)                      // Set the data line...
00540                         PORTB |= _BV(PORTB2);   // Drive MOSI high
00541                 else
00542                         PORTB &= ~_BV(PORTB2);  // Drive MOSI low
00543                 PORTB |= _BV(PORTB1);           // ...and tick the SPI clock
00544                 PORTB &= ~_BV(PORTB1);
00545         }
00546 }
00547 
00548 /*---------------------------------------------------------------------------*/
00549 /**
00550 * \brief ISR routine for comparator A of TIMER 4
00551 *
00552 * TIMER4 interrupt code  ticks at ~320Hz, which means we can do 16 brightness
00553 * levels at 20Hz, so flickering is invisible to the naked eye. This ISR
00554 * implements the software PWM and calls the LED display function. 
00555 * 
00556 * Because we must update the LEDs very frequently, and because it still takes
00557 * a significant period to write all 3*16 values to the LED drivers, we need
00558 * this code to run as fast as possible. We also need to ensure that we do not
00559 * prevent other interrupts from occurring whilst we process this. To that end
00560 * we stop interrupts for this ISR to prevent any nesting, reset the counter
00561 * and re-enable interrupts generally before calling setting the LED values.
00562 *
00563 * The PWM implementation relies on counting in units of the brightness on a
00564 * channel and setting the apropriate colour channel on for an LED when the
00565 * accumulated value overflows. This is slightly faster if the rgb values are
00566 * in the range 0-255 than 0-15, because we can do it with a simple comparison.
00567 * 
00568 * This function then calls writeLEDs for each of the RGB values, in reverse
00569 * order. The drivers are daisy chained and we need to shift the blue values
00570 * to the far end of that chain before pulsing the latch. This written using
00571 * low level C programming primitives of changing the voltages on ATMega32U4
00572 * pins rather than the Arduino digitalWrite equivalents. This has to be done
00573 * in this way for speed - it is around 18 times faster than the alternative
00574 * and, because we update the values rather frequently to avoid flickering,
00575 * speed is important.
00576 * 
00577 * Notes:
00578 * The PWM implementation will occasionally put one more dark period
00579 *   in than it should, but we can live with this for simplicity.
00580 * PB7 is pin 12 on the ATMega32U4 and is attached to the LED LATCH
00581 *
00582 */
00583 ISR(TIMER4_COMPA_vect)
00584 {  
00585         // Temporarily disable interrupts on this ISR to avoid nesting.
00586         //
00587         TIMSK4  = 0x00;
00588 
00589         // Because there is no CTC mode, we need to reset the timer
00590         // count manually.
00591         //
00592         TC4H    = 0x00;         // Reset the timer to zero, high byte first
00593         TCNT4   = 0x00;         // Reset the timer to zero
00594 
00595         // And re-enable interrupts for all other ISRs whilst we set the LEDs 
00596         sei();
00597         
00598 
00599         // Accumulate RGB values for each LED into their respective arrays
00600         // and only set the LED on when we overflow the count; it is otherwise
00601         // set off.
00602         //
00603         for (int i = 0; i < 16; i++) {
00604                 uint8_t ra = EngduinoLEDs.RAccum[i];
00605                 uint8_t ga = EngduinoLEDs.GAccum[i];
00606                 uint8_t ba = EngduinoLEDs.BAccum[i];
00607                 
00608                 EngduinoLEDs.RAccum[i] += EngduinoLEDs.RSet[i];
00609                 EngduinoLEDs.GAccum[i] += EngduinoLEDs.GSet[i];
00610                 EngduinoLEDs.BAccum[i] += EngduinoLEDs.BSet[i];
00611                 
00612                 EngduinoLEDs.RDisp[i]   = (EngduinoLEDs.RAccum[i] < ra);
00613                 EngduinoLEDs.GDisp[i]   = (EngduinoLEDs.GAccum[i] < ga);
00614                 EngduinoLEDs.BDisp[i]   = (EngduinoLEDs.BAccum[i] < ba);
00615         }
00616         
00617         // Now display the values we calculated.
00618         //
00619         PORTB &= ~_BV(PORTB7);                  // Drive LATCH low - disable 
00620         
00621         writeLEDs(EngduinoLEDs.BDisp);  // Write the RGB values
00622         writeLEDs(EngduinoLEDs.GDisp);
00623         writeLEDs(EngduinoLEDs.RDisp);
00624         
00625         PORTB |= _BV(PORTB7);                   // Pulse LATCH to switch
00626         PORTB &= ~_BV(PORTB7);
00627         
00628         // And we're done with this call of the ISR, so re-enable interrupts
00629         //
00630         TIMSK4 |= 0x40;     // And enable interrupts for a compare A match only
00631 }
00632 
00633 
00634 /*---------------------------------------------------------------------------*/
00635 /*
00636  * Preinstantiate Objects
00637  */ 
00638 EngduinoLEDsClass EngduinoLEDs = EngduinoLEDsClass();
00639 
00640 /** @} */