From fb77929126dccae7158dee7a23057b9f3c46eb3b Mon Sep 17 00:00:00 2001 From: Scott Allen Date: Thu, 22 Feb 2018 14:36:55 -0500 Subject: [PATCH] Refactor setRGBled() and add freeRGBled() - setRGBled() has been rewritten to directly control the hardware instead of using the Arduino analogWrite() function. - Added a two parameter version of setRGBled() that sets the brightness of one LED without affecting the others. - Added function freeRGBled() for freeing the PWM control of the LEDs so they can be used digitally. - Added example sketch RGBled. --- LICENSE.txt | 3 + examples/RGBled/RGBled.ino | 354 +++++++++++++++++++++++++++++++++++++ keywords.txt | 1 + src/Arduboy2Core.cpp | 48 ++++- src/Arduboy2Core.h | 50 +++++- 5 files changed, 449 insertions(+), 7 deletions(-) create mode 100644 examples/RGBled/RGBled.ino diff --git a/LICENSE.txt b/LICENSE.txt index 43f68ea..b838e2b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -140,6 +140,9 @@ Placed in the public domain: BeepDemo example sketch: By Scott Allen +RGBled example sketch: +By Scott Allen + =============================================================================== \endverbatim */ diff --git a/examples/RGBled/RGBled.ino b/examples/RGBled/RGBled.ino new file mode 100644 index 0000000..9ff3fde --- /dev/null +++ b/examples/RGBled/RGBled.ino @@ -0,0 +1,354 @@ +/* +This sketch demonstrates controlling the Arduboy's RGB LED, +in both analog and digital modes. +*/ + +/* +To the extent possible under law, Scott Allen has waived all copyright and +related or neighboring rights to this BeepDemo program. +*/ + +#include + +// The frame rate determines the button auto-repeat rate +#define FRAME_RATE 25 + +// The increment/decrement amount when auto-repeating +#define REPEAT_AMOUNT 3 + +// Delay time before button auto-repeat starts, in milliseconds +#define REPEAT_DELAY 700 + +// Calculation of the number of frames to wait before button auto-repeat starts +#define DELAY_FRAMES (REPEAT_DELAY / (1000 / FRAME_RATE)) + +#define ANALOG false +#define DIGITAL true + +#define ANALOG_MAX 255 + +// Color array index +enum class Color { + RED, + GREEN, + BLUE, + COUNT +}; + +// Map LED color index to LED name +const byte LEDpin[(byte)(Color::COUNT)] = { + RED_LED, + GREEN_LED, + BLUE_LED +}; + +Arduboy2 arduboy; + +// Analog LED values +byte analogValue[3] = { 0, 0, 0}; +// Digital LED states +byte digitalState[3] = { RGB_OFF, RGB_OFF, RGB_OFF }; + +byte analogSelected = (byte)(Color::RED); +byte digitalSelected = (byte)(Color::RED); + +boolean controlMode = ANALOG; + +// Button repeat handling +unsigned int delayCount = 0; +boolean repeating = false; + +// ============================= SETUP =================================== +void setup() { + arduboy.begin(); + arduboy.setFrameRate(FRAME_RATE); + analogSet(); +} +// ======================================================================= + + +// =========================== MAIN LOOP ================================= +void loop() { + if (!arduboy.nextFrame()) { + return; + } + + arduboy.pollButtons(); + + // Toggle analog/digital control mode + if (arduboy.justPressed(A_BUTTON)) { + if ((controlMode = !controlMode) == DIGITAL) { + arduboy.freeRGBled(); + digitalSet(); + } + else { + analogSet(); + } + } + + // Reset to Analog mode and all LEDs off + if (arduboy.justPressed(B_BUTTON)) { + reset(); + } + + // Handle D-pad buttons for current mode + if (controlMode == ANALOG) { + modeAnalog(); + } + else { + modeDigital(); + } + + // Handle delay before button auto-repeat starts + if ((delayCount != 0) && (--delayCount == 0)) { + repeating = true; + } + + renderScreen(); // Render and display the entire screen +} +// ======================================================================= + + +// Analog control +void modeAnalog() { + if (arduboy.justPressed(RIGHT_BUTTON)) { + valueInc(1); + startButtonDelay(); + } + else if (arduboy.justPressed(LEFT_BUTTON)) { + valueDec(1); + startButtonDelay(); + } + else if (repeating && arduboy.pressed(RIGHT_BUTTON)) { + valueInc(REPEAT_AMOUNT); + } + else if (repeating && arduboy.pressed(LEFT_BUTTON)) { + valueDec(REPEAT_AMOUNT); + } + else if (arduboy.justPressed(DOWN_BUTTON)) { + analogSelectInc(); + } + else if (arduboy.justPressed(UP_BUTTON)) { + analogSelectDec(); + } + else if (repeating) { + stopButtonRepeat(); + } +} + +// Digital control +void modeDigital() { + if (arduboy.justPressed(RIGHT_BUTTON) || arduboy.justPressed(LEFT_BUTTON)) { + digitalState[digitalSelected] = (digitalState[digitalSelected] == RGB_ON) ? + RGB_OFF : RGB_ON; + arduboy.digitalWriteRGB(LEDpin[digitalSelected], + digitalState[digitalSelected]); + } + else if (arduboy.justPressed(DOWN_BUTTON)) { + digitalSelectInc(); + } + else if (arduboy.justPressed(UP_BUTTON)) { + digitalSelectDec(); + } +} + +// Reset to analog mode and turn all LEDs off +void reset() { + digitalState[(byte)(Color::RED)] = RGB_OFF; + digitalState[(byte)(Color::GREEN)] = RGB_OFF; + digitalState[(byte)(Color::BLUE)] = RGB_OFF; + digitalSet(); + + analogValue[(byte)(Color::RED)] = 0; + analogValue[(byte)(Color::GREEN)] = 0; + analogValue[(byte)(Color::BLUE)] = 0; + analogSet(); + + digitalSelected = (byte)(Color::RED); + analogSelected = (byte)(Color::RED); + + controlMode = ANALOG; +} + +// Increment the selected analog LED value by the specified amount +// and update the LED +void valueInc(byte amount) { + if ((ANALOG_MAX - analogValue[analogSelected]) <= amount) { + analogValue[analogSelected] = ANALOG_MAX; + } + else { + analogValue[analogSelected] += amount; + } + + arduboy.setRGBled(LEDpin[analogSelected], analogValue[analogSelected]); +} + +// Decrement the selected analog LED value by the specified amount +// and update the LED +void valueDec(byte amount) { + if (analogValue[analogSelected] <= amount) { + analogValue[analogSelected] = 0; + } + else { + analogValue[analogSelected] -= amount; + } + + arduboy.setRGBled(LEDpin[analogSelected], analogValue[analogSelected]); +} + +// Select the next analog color index with wrap +void analogSelectInc() { + selectInc(analogSelected); +} + +// Select the previous analog color index with wrap +void analogSelectDec() { + selectDec(analogSelected); +} + +// Select the next digital color index with wrap +void digitalSelectInc() { + selectInc(digitalSelected); +} + +// Select the previous digital color index with wrap +void digitalSelectDec() { + selectDec(digitalSelected); +} + +// Select the next color index with wrap +void selectInc(byte &index) { + if (++index == (byte)(Color::COUNT)) { + index = 0; + } +} + +// Select the previous color index with wrap +void selectDec(byte &index) { + if (index == 0) { + index = ((byte)(Color::COUNT) - 1); + } + else { + index--; + } +} + +// Update all LEDs in analog mode +void analogSet() { + arduboy.setRGBled(analogValue[(byte)(Color::RED)], + analogValue[(byte)(Color::GREEN)], + analogValue[(byte)(Color::BLUE)]); +} + +// Update all LEDs in digital mode +void digitalSet() { + arduboy.digitalWriteRGB(digitalState[(byte)(Color::RED)], + digitalState[(byte)(Color::GREEN)], + digitalState[(byte)(Color::BLUE)]); +} + +// Start the button auto-repeat delay +void startButtonDelay() { + delayCount = DELAY_FRAMES; + repeating = false; +} + +// Stop the button auto-repeat or delay +void stopButtonRepeat() { + delayCount = 0; + repeating = false; +} + +// Render and display the screen +void renderScreen() { + arduboy.setCursor(12, 0); + arduboy.print(F("RGB LED")); + arduboy.setCursor(15, 56); + arduboy.print(F("A:Mode B:Reset")); + arduboy.setCursor(74, 0); + + if (controlMode == ANALOG) { + arduboy.print(F(" Analog")); + drawAnalog(9, Color::RED, "Red:"); + drawAnalog(25, Color::GREEN, "Green:"); + drawAnalog(41, Color::BLUE, "Blue:"); + } + else { // Digital + arduboy.print(F("Digital")); + drawDigital(9, Color::RED, "Red:"); + drawDigital(25, Color::GREEN, "Green:"); + drawDigital(41, Color::BLUE, "Blue:"); + } + + arduboy.display(CLEAR_BUFFER); +} + +// Draw the information for one analog color +void drawAnalog(int y, Color color, const char* name) { + byte value = analogValue[(byte)color]; + + arduboy.setCursor(0, y); + arduboy.print(name); + arduboy.setCursor(42, y); + printValue(value); + if (analogSelected == (byte)color) { + arduboy.print(F(" <--")); + } + drawBar(y + 8, color, value); +} + +// Draw the value bar for an analog color +void drawBar(int y, Color color, byte value) { + byte barLength = value / 2; + + if (barLength == 0) { + return; + } + + if (analogSelected == (byte)color) { + arduboy.fillRect(0, y, barLength, 5); + } + else { + arduboy.drawRect(0, y, barLength, 5); + } +} + +// Draw the informaton for one digital color +void drawDigital(int y, Color color, const char* name) { + byte state = digitalState[(byte)color]; + + arduboy.setCursor(34, y + 3); + arduboy.print(name); + arduboy.setCursor(76, y + 3); + if (state == RGB_ON) { + arduboy.print(F("ON ")); + arduboy.fillCircle(22, y + 6, 4); + } + else { + arduboy.print(F("OFF")); + arduboy.drawCircle(22, y + 6, 4); + } + + if (digitalSelected == (byte)color) { + arduboy.print(F(" <--")); + arduboy.drawRect(16, y, 13, 13); + } +} + +// Print a byte in decimal and hex +void printValue(byte val) { + if (val < 100) { + arduboy.print(' '); + } + if (val < 10) { + arduboy.print(' '); + } + arduboy.print(val); + + arduboy.print(F(" 0x")); + if (val < 0x10) { + arduboy.print('0'); + } + arduboy.print(val, HEX); +} + diff --git a/keywords.txt b/keywords.txt index 62b347b..3571172 100644 --- a/keywords.txt +++ b/keywords.txt @@ -57,6 +57,7 @@ fillTriangle KEYWORD2 flashlight KEYWORD2 flipVertical KEYWORD2 flipHorizontal KEYWORD2 +freeRGBled KEYWORD2 getBuffer KEYWORD2 getCursorX KEYWORD2 getCursorY KEYWORD2 diff --git a/src/Arduboy2Core.cpp b/src/Arduboy2Core.cpp index 69139aa..69b3416 100644 --- a/src/Arduboy2Core.cpp +++ b/src/Arduboy2Core.cpp @@ -429,10 +429,17 @@ void Arduboy2Core::flipHorizontal(bool flipped) void Arduboy2Core::setRGBled(uint8_t red, uint8_t green, uint8_t blue) { #ifdef ARDUBOY_10 // RGB, all the pretty colors - // inversion is necessary because these are common annode LEDs - analogWrite(RED_LED, 255 - red); - analogWrite(GREEN_LED, 255 - green); - analogWrite(BLUE_LED, 255 - blue); + // timer 0: Fast PWM, OC0A clear on compare / set at top + // We must stay in Fast PWM mode because timer 0 is used for system timing. + // We can't use "inverted" mode because it won't allow full shut off. + TCCR0A = _BV(COM0A1) | _BV(WGM01) | _BV(WGM00); + OCR0A = 255 - green; + // timer 1: Phase correct PWM 8 bit + // OC1A and OC1B set on up-counting / clear on down-counting (inverted). This + // allows the value to be directly loaded into the OCR with common anode LED. + TCCR1A = _BV(COM1A1) | _BV(COM1A0) | _BV(COM1B1) | _BV(COM1B0) | _BV(WGM10); + OCR1AL = blue; + OCR1BL = red; #elif defined(AB_DEVKIT) // only blue on DevKit, which is not PWM capable (void)red; // parameter unused @@ -441,6 +448,39 @@ void Arduboy2Core::setRGBled(uint8_t red, uint8_t green, uint8_t blue) #endif } +void Arduboy2Core::setRGBled(uint8_t color, uint8_t val) +{ +#ifdef ARDUBOY_10 + if (color == RED_LED) + { + OCR1BL = val; + } + else if (color == GREEN_LED) + { + OCR0A = 255 - val; + } + else if (color == BLUE_LED) + { + OCR1AL = val; + } +#elif defined(AB_DEVKIT) + // only blue on DevKit, which is not PWM capable + if (color == BLUE_LED) + { + bitWrite(BLUE_LED_PORT, BLUE_LED_BIT, val ? RGB_ON : RGB_OFF); + } +#endif +} + +void Arduboy2Core::freeRGBled() +{ +#ifdef ARDUBOY_10 + // clear the COM bits to return the pins to normal I/O mode + TCCR0A = _BV(WGM01) | _BV(WGM00); + TCCR1A = _BV(WGM10); +#endif +} + void Arduboy2Core::digitalWriteRGB(uint8_t red, uint8_t green, uint8_t blue) { #ifdef ARDUBOY_10 diff --git a/src/Arduboy2Core.h b/src/Arduboy2Core.h index de2328e..9883d7b 100644 --- a/src/Arduboy2Core.h +++ b/src/Arduboy2Core.h @@ -609,10 +609,45 @@ class Arduboy2Core * LEDs will light. * \endparblock * - * \see digitalWriteRGB() + * \see setRGBled(uint8_t, uint8_t) digitalWriteRGB() freeRGBled() */ void static setRGBled(uint8_t red, uint8_t green, uint8_t blue); + /** \brief + * Set the brightness of one of the RGB LEDs without affecting the others. + * + * \param color The name of the LED to set. The value given should be one + * of RED_LED, GREEN_LED or BLUE_LED. + * + * \param val The brightness value for the LED, from 0 to 255. + * + * \note + * In order to use this function, the 3 parameter version must first be + * called at least once, in order to initialize the hardware. + * + * \details + * This 2 parameter version of the function will set the brightness of a + * single LED within the RGB LED without affecting the current brightness + * of the other two. See the description of the 3 parameter version of this + * function for more details on the RGB LED. + * + * \see setRGBled(uint8_t, uint8_t, uint8_t) digitalWriteRGB() freeRGBled() + */ + void static setRGBled(uint8_t color, uint8_t val); + + + /** \brief + * Relinquish analog control of the RGB LED. + * + * \details + * Using the RGB LED in analog mode prevents further use of the LED in + * digital mode. This function will restore the pins used for the LED, so + * it can be used in digital mode. + * + * \see digitalWriteRGB() setRGBled() + */ + void static freeRGBled(); + /** \brief * Set the RGB LEDs digitally, to either fully on or fully off. * @@ -638,14 +673,23 @@ class Arduboy2Core * RGB_ON RGB_ON RGB_ON White * * \note + * \parblock + * Using the RGB LED in analog mode will prevent digital control of the + * LED. To restore the ability to control the LED digitally, use the + * `freeRGBled()` function. + * \endparblock + * + * \note + * \parblock * Many of the Kickstarter Arduboys were accidentally shipped with the * RGB LED installed incorrectly. For these units, the green LED cannot be * lit. As long as the green led is set to off, turning on the red LED will * actually light the blue LED and turning on the blue LED will actually * light the red LED. If the green LED is turned on, none of the LEDs * will light. + * \endparblock * - * \see digitalWriteRGB(uint8_t, uint8_t) setRGBled() + * \see digitalWriteRGB(uint8_t, uint8_t) setRGBled() freeRGBled() */ void static digitalWriteRGB(uint8_t red, uint8_t green, uint8_t blue); @@ -663,7 +707,7 @@ class Arduboy2Core * the RGB LED either fully on or fully off. See the description of the * 3 parameter version of this function for more details on the RGB LED. * - * \see digitalWriteRGB(uint8_t, uint8_t, uint8_t) setRGBled() + * \see digitalWriteRGB(uint8_t, uint8_t, uint8_t) setRGBled() freeRGBled() */ void static digitalWriteRGB(uint8_t color, uint8_t val);