1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00
arduinolibs/libraries/BlinkLED/Charlieplex.cpp
2013-09-29 09:28:58 +10:00

351 lines
12 KiB
C++

/*
* Copyright (C) 2012 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "Charlieplex.h"
#if defined(ARDUINO) && ARDUINO >= 100
#include <Arduino.h>
#else
#include <WProgram.h>
#endif
#include <stdlib.h>
#include <string.h>
/**
* \class Charlieplex Charlieplex.h <Charlieplex.h>
* \brief Manage an array of LED's in a charlieplexed arrangement.
*
* <a href="http://en.wikipedia.org/wiki/Charlieplexing">Charlieplexing</a>
* is a technique for multiplexing large numbers of LED's on a small
* number of microcontroller output pins. LED's are arranged in
* complementary pairs; the simplest being for two output pins:
*
* \image html charlieplex2pin.png
*
* When Pin1 is 1 and Pin2 is 0, LED1 will be lit. When Pin1 is 0 and
* Pin2 is 1, then LED2 will be lit. The technique extends to 3 pins
* as follows:
*
* \image html charlieplex3pin.png
*
* In this case, LED5 is lit when Pin1 is 1, Pin3 is 0, and Pin2 is set
* to a high-impedance input to "disconnect" it.
*
* Charlieplex presents a simple array of led() values that indicate whether
* each LED is on, off, or in an intermediate PWM state (if setPwmLed()
* is used). The application must call loop() or refresh() on a regular
* basis to ensure that the multiplexed display is kept up to date.
* The following example drives 6 LED's connected to the output pins
* D9, D10, and D11:
*
* \dontinclude BlinkLED/examples/Charlieplex/Charlieplex.ino
* \skip #include
* \until charlie.loop
* \until }
*
* The following diagram extends the circuit for 4 output pins and 12 LED's:
*
* \image html charlieplex4pin.png
*
* The following diagram extends the circuit for 5 output pins and 20 LED's:
*
* \image html charlieplex5pin.png
*
* Circuits for higher numbers of LED's get increasingly complex. For those
* cases it can be easier to use traditional multiplexing matrix arrangements
* and shift registers. The DMD class does this for a specific kind of
* large dot matrix display. Otherwise, use the following pseudocode to
* determine how to connect the LED's for higher numbers of pins:
*
* \code
* n = 1
* for Pass = 1 to NumPins-1:
* for Pin = 1 to NumPins-Pass:
* LED[n] is connected between Pin (anode) and Pin+Pass (cathode)
* LED[n+1] is connected between Pin+Pass (anode) and Pin (cathode)
* n = n + 2
* \endcode
*
* Note: while the above circuit diagrams and psuedocode use 1-based
* numbering for LED's, Charlieplex uses 0-based numbering in the led(),
* setLed(), pwmLed(), and setPwmLed() functions.
*
* It isn't necessary to wire up all LED's. If you only need 10 LED's,
* then use the 4-output circuit and omit LED11 and LED12. Charlieplex
* only drives LED's that are lit; LED's that are unlit or unused will
* be skipped during the refresh scan. The maximum number of LED's that
* that can be driven by a specific number of pins is given by the
* following table:
*
* <table>
* <tr><td>Number of Pins</td><td>Number of LED's</td></tr>
* <tr><td>2</td><td>2</td></tr>
* <tr><td>3</td><td>6</td></tr>
* <tr><td>4</td><td>12</td></tr>
* <tr><td>5</td><td>20</td></tr>
* <tr><td>6</td><td>30</td></tr>
* <tr><td>7</td><td>42</td></tr>
* <tr><td>8</td><td>56</td></tr>
* <tr><td>9</td><td>72</td></tr>
* <tr><td>10</td><td>90</td></tr>
* <tr><td>n</td><td>n * (n - 1)</td></tr>
* </table>
*/
/**
* \brief Constructs a new charliexplexing array where the output pins
* are specified by the \a numPins entries in \a pins.
*
* Note: \a numPins must be 2 or greater for correct operation.
*
* \sa count(), setLed()
*/
Charlieplex::Charlieplex(const uint8_t *pins, uint8_t numPins)
: _count(((int)numPins) * (numPins - 1))
, _lastTime(micros())
, _currentIndex(-1)
, _pwmPhase(0xC0)
{
// Determine the best hold time for 50 Hz refresh when all LED's
// are lit. Divide it again by 4 (to get 200 Hz) to manage the
// simulated PWM in refresh().
_holdTime = 20000 / _count / 4;
// Allocate the pin arrays and populate them. Doing this now makes
// refresh() more efficient later, at the expense of some memory.
_pins1 = (uint8_t *)malloc(_count);
_pins2 = (uint8_t *)malloc(_count);
int n = 0;
for (uint8_t pass = 1; pass < numPins; ++pass) {
for (uint8_t pin = 0; pin < (numPins - pass); ++pin) {
_pins1[n] = _pins2[n + 1] = pins[pin];
_pins2[n] = _pins1[n + 1] = pins[pin + pass];
n += 2;
}
}
// Allocate space for the LED value array and zero it.
_values = (uint8_t *)malloc(_count);
memset(_values, 0, _count);
// Start with all pins configured as floating inputs (all LED's off).
for (uint8_t pin = 0; pin < numPins; ++pin) {
digitalWrite(pins[pin], LOW);
pinMode(pins[pin], INPUT);
}
}
/**
* \brief Destroys this charlieplexed array.
*/
Charlieplex::~Charlieplex()
{
free(_pins1);
free(_pins2);
free(_values);
}
/**
* \fn int Charlieplex::count() const
* \brief Returns the number of LED's in this charlieplexed array based
* on the number of pins.
*
* <table>
* <tr><td>Number of Pins</td><td>Number of LED's</td></tr>
* <tr><td>2</td><td>2</td></tr>
* <tr><td>3</td><td>6</td></tr>
* <tr><td>4</td><td>12</td></tr>
* <tr><td>5</td><td>20</td></tr>
* <tr><td>6</td><td>30</td></tr>
* <tr><td>7</td><td>42</td></tr>
* <tr><td>8</td><td>56</td></tr>
* <tr><td>9</td><td>72</td></tr>
* <tr><td>10</td><td>90</td></tr>
* <tr><td>n</td><td>n * (n - 1)</td></tr>
* </table>
*
* \sa led()
*/
/**
* \fn bool Charlieplex::led(int index) const
* \brief Returns the value of the LED at \a index in the charplexed array;
* true if lit; false if not lit.
*
* If the LED is displaying a PWM value, then this function will return
* true for any non-zero PWM value.
*
* \sa setLed(), pwmLed()
*/
/**
* \fn void Charlieplex::setLed(int index, bool value)
* \brief Sets the \a value of the LED at \a index in the charliplexed array.
*
* The brightness of the LED will be proportional to the number of LED's
* that are currently lit, as the holdTime() refresh rate will cause the
* LED to appear to dim; the more LED's that are lit the less overall time
* each individual LED is held on. For best results, only a single LED should
* be lit at once or higher-brightness LED's should be used.
*
* \sa led(), setPwmLed()
*/
/**
* \fn uint8_t Charlieplex::pwmLed(int index) const
* \brief Returns the PWM value of the LED at \a index in the charplexed array;
* between 0 and 255.
*
* \sa setPwmLed(), led()
*/
/**
* \fn void Charlieplex::setPwmLed(int index, uint8_t value)
* \brief Sets the PWM \a value of the LED at \a index in the charliplexed
* array; between 0 and 255.
*
* If this function is used, then it is assumed that the output pins are
* capable of PWM output.
*
* The PWM-specified brightness of the LED will also be affected to the
* number of LED's that are currently lit, as the holdTime() refresh rate
* will cause the LED to appear to dim; the more LED's that are lit the
* less overall time each individual LED is held on. For best results,
* only a single LED should be lit at once or higher-brightness LED's
* should be used.
*
* \sa pwmLed(), setLed()
*/
/**
* \fn unsigned long Charlieplex::holdTime() const
* \brief Returns the number of microseconds that each LED should be
* held on for before moving onto the next in loop().
*
* The default value is calculated so that all LED's can be refreshed
* with a rate of at least 200 Hz, which is necessary for handling PWM
* output on multiple LED's. The less LED's that are lit at once,
* the faster the display will refresh.
*
* \sa setHoldTime(), loop()
*/
/**
* \fn void Charlieplex::setHoldTime(unsigned long us)
* \brief Sets the number of microseconds that each LED should be
* held on for before moving onto the next in loop() to \a us.
*
* \sa holdTime(), loop()
*/
/**
* \brief Runs the multiplexing loop, to display the LED states on
* the charlieplexed array.
*
* If holdTime() microseconds have elapsed since the last call to loop(),
* then the current LED is turned off and the next LED that needs to be
* lit is turned on.
*
* LED's that do not need to be lit are skipped. The total time for a
* single pass through all lit LED's may be very short if only a few
* LED's are lit at once. If all LED's are lit, then the total time for
* a single pass will be count() * holdTime() microseconds.
*
* If the application is using timer interrupts to drive the multiplexing
* process, then use refresh() instead of loop().
*
* \sa led(), pwmLed(), holdTime(), refresh()
*/
void Charlieplex::loop()
{
unsigned long us = micros();
if ((us - _lastTime) >= _holdTime) {
_lastTime = us;
refresh();
}
}
/**
* \brief Refreshes the charlieplexed array by advancing to the next LED
* that needs to be lit.
*
* This function is intended to be called from a timer interrupt service
* routine to advance the multiplexing state without the main application
* having to explicitly call loop().
*
* \sa loop()
*/
void Charlieplex::refresh()
{
// Find the next LED to be lit.
int prevIndex = _currentIndex;
int limit = _count;
while (limit >= 0) {
_currentIndex = (_currentIndex + 1) % _count;
if (_values[_currentIndex] != 0)
break;
--limit;
}
if (limit < 0) {
// No LED's are lit. Turn off the previous LED and exit.
if (prevIndex != -1) {
digitalWrite(_pins1[prevIndex], LOW);
digitalWrite(_pins2[prevIndex], LOW);
pinMode(_pins1[prevIndex], INPUT);
pinMode(_pins2[prevIndex], INPUT);
}
_currentIndex = -1;
return;
}
// Light the current LED.
uint8_t value = _values[_currentIndex];
uint8_t pin1 = _pins1[_currentIndex];
uint8_t pin2 = _pins2[_currentIndex];
_pwmPhase += 0x40;
if (prevIndex != _currentIndex) {
// Turn off the previous LED.
if (prevIndex != -1) {
digitalWrite(_pins1[prevIndex], LOW);
digitalWrite(_pins2[prevIndex], LOW);
pinMode(_pins1[prevIndex], INPUT);
pinMode(_pins2[prevIndex], INPUT);
}
// We simulate PWM using a phase counter because analogWrite()
// combined with holdTime() causes too much flickering if more
// than one LED is lit. This reduces the PWM resolution to 1 in 4.
pinMode(pin1, OUTPUT);
pinMode(pin2, OUTPUT);
if (value > _pwmPhase)
digitalWrite(pin1, HIGH);
else
digitalWrite(pin1, LOW);
} else {
// Same LED as previous. Since there is only a single LED
// that is lit, we can use analogWrite() to set the PWM state.
if (value == 255)
digitalWrite(pin1, HIGH);
else
analogWrite(pin1, value);
}
}