forked from Michel2/LoLshield
Grayscale enhancements, PLEASE REVIEW; see issue #20.
This patch enhances the grayscale work by Matt Mets.
-----------------------------------------------------
THESE CHANGES NEED REVIEW. PLEASE REVIEW THE CHANGES.
-----------------------------------------------------
We now support a total of 8 different shades (including black / OFF)
by solely relying on the high resolution timer.
PLEASE REVIEW these changes made to Charlieplexing.cpp.
COLORS has been renamed to SHADES.
BIT_DEPTH has been removed; SHADES can be set directly.
A FADE example based on Matt's has been added and extended
to allow for "performance tests".
BasicTest has been extended to use double buffering. Since the buffer
is redrawn in a loop it uses synchronized buffer flip.
A new mini-example "Breathe" has been added. "Breathe" simply fades
all the LEDs from off to on and back to off again, not unlike the
power LEDs of some laptop brands when the laptop is in suspend.
"Breathe" can be used to fine-tune the brightness of the individual
shades. Currently the transitions feel far from fluent; I think we
could do better.
Signed-off-by: Thilo Fromm <kontakt@thilo-fromm.de>
This commit is contained in:
+58
-82
@@ -35,6 +35,7 @@
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include "Charliplexing.h"
|
||||
|
||||
@@ -42,13 +43,13 @@ volatile unsigned int LedSign::tcnt2;
|
||||
|
||||
|
||||
struct videoPage {
|
||||
uint8_t pixels[COLORS-1][48]; // TODO: is 48 right?
|
||||
uint8_t pixels[SHADES][48]; // TODO: is 48 right?
|
||||
};
|
||||
|
||||
/* ----------------------------------------------------------------- */
|
||||
/** Table for the LED multiplexing cycles
|
||||
* Each frame is made of 24 bytes (for the 24 display cycles)
|
||||
* There are 3 frames per buffer in grayscale mode (one for each brigtness)
|
||||
* There are SHADES frames per buffer in grayscale mode (one for each brigtness)
|
||||
* and twice that many to support double-buffered grayscale.
|
||||
*/
|
||||
videoPage leds[2];
|
||||
@@ -66,21 +67,15 @@ videoPage* displayBuffer;
|
||||
/// Pointer to the buffer that should currently be drawn to
|
||||
videoPage* workBuffer;
|
||||
|
||||
// For grayscale support
|
||||
// TODO: map these based on tcnt2 calculation
|
||||
//uint8_t pageCounts[COLORS] = {250, 205, 88, 0};
|
||||
uint8_t pageCounts[COLORS] = {240, 205, 88, 0};
|
||||
|
||||
|
||||
/// Flag indicating that the pageCounts buffer should be flipped as soon as the
|
||||
/// Flag indicating that the timer buffer should be flipped as soon as the
|
||||
/// current frame is displayed
|
||||
volatile boolean videoFlipTimer = false;
|
||||
|
||||
|
||||
// Timer counts to display each page for, plus off time
|
||||
typedef struct timerInfo {
|
||||
uint8_t counts[COLORS];
|
||||
uint8_t prescaler[COLORS];
|
||||
uint8_t counts[SHADES];
|
||||
uint8_t prescaler[SHADES];
|
||||
};
|
||||
|
||||
// Double buffer the timing information, of course.
|
||||
@@ -102,6 +97,8 @@ prescalerInfo slowPrescaler = {1, 0x03};
|
||||
//prescalerInfo fastPrescaler = {32, 0x01};
|
||||
prescalerInfo fastPrescaler = {4, 0x02};
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
/// Uncomment to set analog pin 5 high during interrupts, so that an
|
||||
/// oscilloscope can be used to measure the processor time taken by it
|
||||
#define MEASURE_ISR_TIME
|
||||
@@ -215,7 +212,6 @@ void LedSign::Init(uint8_t mode)
|
||||
|
||||
tcnt2 = 256 - (int)((float)F_CPU * 0.0005 / prescaler);
|
||||
|
||||
|
||||
// Record whether we are in single or double buffer mode
|
||||
displayMode = mode;
|
||||
videoFlipPage = false;
|
||||
@@ -238,29 +234,17 @@ void LedSign::Init(uint8_t mode)
|
||||
backTimer = &timer[1];
|
||||
|
||||
videoFlipTimer = false;
|
||||
|
||||
// And write some default stuff into the front timer
|
||||
for (int i = 0; i < COLORS; i++) {
|
||||
frontTimer->counts[i] = pageCounts[i];
|
||||
// TODO: Generate this dynamically
|
||||
frontTimer->prescaler[i] = slowPrescaler.TCCR2;
|
||||
}
|
||||
|
||||
LedSign::SetBrightness(127);
|
||||
SetBrightness(127);
|
||||
|
||||
// Clear the buffer and display it
|
||||
LedSign::Clear(0);
|
||||
LedSign::Flip(false);
|
||||
|
||||
// Then start the display
|
||||
TCNT2 = tcnt2;
|
||||
#if defined (__AVR_ATmega168__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega328P__) || (__AVR_ATmega1280__)
|
||||
TCNT2 = tcnt2;
|
||||
TIMSK2 |= (1<<TOIE2);
|
||||
#elif defined (__AVR_ATmega128__)
|
||||
TCNT2 = tcnt2;
|
||||
TIMSK |= (1<<TOIE2);
|
||||
#elif defined (__AVR_ATmega8__)
|
||||
TCNT2 = tcnt2;
|
||||
#elif defined (__AVR_ATmega128__) || defined (__AVR_ATmega8__)
|
||||
TIMSK |= (1<<TOIE2);
|
||||
#endif
|
||||
|
||||
@@ -272,6 +256,8 @@ void LedSign::Init(uint8_t mode)
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -347,10 +333,10 @@ void LedSign::Set(uint8_t x, uint8_t y, uint8_t c)
|
||||
|
||||
// If we aren't in grayscale mode, just map any pin brightness to max
|
||||
if (c > 0 && !(displayMode & GRAYSCALE)) {
|
||||
c = COLORS-1;
|
||||
c = SHADES-1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < COLORS-1; i++) {
|
||||
for (int i = 0; i < SHADES-1; i++) {
|
||||
if( c > i ) {
|
||||
workBuffer->pixels[i][bufferNum] |= work; // ON
|
||||
}
|
||||
@@ -364,64 +350,62 @@ void LedSign::Set(uint8_t x, uint8_t y, uint8_t c)
|
||||
/* Set the overall brightness of the screen
|
||||
* @param brightness LED brightness, from 0 (off) to 127 (full on)
|
||||
*/
|
||||
|
||||
void LedSign::SetBrightness(uint8_t brightness)
|
||||
{
|
||||
Serial.print("starting ");
|
||||
// An exponential fit seems to approximate a (perceived) linear scale
|
||||
float brightnessPercent = ((float)brightness / 127)*((float)brightness / 127);
|
||||
uint8_t difference = 0;
|
||||
|
||||
/* ---- This needs review! Please review. -- thilo */
|
||||
// set up page counts
|
||||
// TODO: make SHADES a function parameter. This would require some refactoring.
|
||||
int start = 15;
|
||||
int max = 255;
|
||||
float scale = 1.5;
|
||||
float delta = pow( max - start , 1.0 / scale) / (SHADES - 1);
|
||||
uint8_t pageCounts[SHADES];
|
||||
|
||||
pageCounts[0] = max - start;
|
||||
for (uint8_t i=1; i<SHADES; i++) {
|
||||
pageCounts[i] = max - ( pow( i * delta, scale ) + start );
|
||||
}
|
||||
Serial.end();
|
||||
|
||||
if (! initialized) {
|
||||
// set front timer defaults
|
||||
for (int i = 0; i < SHADES; i++) {
|
||||
frontTimer->counts[i] = pageCounts[i];
|
||||
// TODO: Generate this dynamically
|
||||
frontTimer->prescaler[i] = slowPrescaler.TCCR2;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until the previous brightness request goes through
|
||||
while( videoFlipTimer )
|
||||
{
|
||||
while( videoFlipTimer ) {
|
||||
delay(1);
|
||||
}
|
||||
|
||||
// An exponential fit seems to approximate a (perceived) linear scale
|
||||
float brightnessPercent = ((float)brightness / 127)*((float)brightness / 127);
|
||||
|
||||
uint8_t difference = 0;
|
||||
|
||||
Serial.print((int)brightness);
|
||||
Serial.print(" ");
|
||||
|
||||
Serial.print((int)(brightnessPercent*100));
|
||||
Serial.print(" ");
|
||||
|
||||
// Compute on time for each of the pages
|
||||
for (uint8_t i = 0; i < COLORS - 1; i++) {
|
||||
// Use the fast timer; slow timer is only useful for < 3 shades.
|
||||
for (uint8_t i = 0; i < SHADES - 1; i++) {
|
||||
uint8_t interval = 255 - pageCounts[i];
|
||||
|
||||
backTimer->counts[i] = 255 - brightnessPercent*interval;
|
||||
|
||||
backTimer->counts[i] = 255 - brightnessPercent
|
||||
* interval
|
||||
* fastPrescaler.relativeSpeed;
|
||||
backTimer->prescaler[i] = fastPrescaler.TCCR2;
|
||||
difference += backTimer->counts[i] - pageCounts[i];
|
||||
|
||||
if (backTimer->counts[i] < 240) {
|
||||
// use low-pres prescaler
|
||||
backTimer->prescaler[i] = slowPrescaler.TCCR2;
|
||||
}
|
||||
else {
|
||||
// use high-res prescaler
|
||||
backTimer->counts[i] = 255 - brightnessPercent*interval*fastPrescaler.relativeSpeed;
|
||||
|
||||
backTimer->prescaler[i] = fastPrescaler.TCCR2;
|
||||
}
|
||||
|
||||
Serial.print((int)backTimer->counts[i]);
|
||||
Serial.print(":");
|
||||
Serial.print((int)backTimer->prescaler[i]);
|
||||
Serial.print(" ");
|
||||
}
|
||||
|
||||
// Compute off time
|
||||
backTimer->counts[COLORS - 1] = 255 - difference;
|
||||
backTimer->prescaler[COLORS - 1] = slowPrescaler.TCCR2;
|
||||
backTimer->counts[SHADES - 1] = 255 - difference;
|
||||
backTimer->prescaler[SHADES - 1] = slowPrescaler.TCCR2;
|
||||
|
||||
Serial.print((int)backTimer->counts[COLORS - 1]);
|
||||
Serial.print(":");
|
||||
Serial.print((int)backTimer->prescaler[COLORS - 1]);
|
||||
Serial.print(" ");
|
||||
/* ---- End of "This needs review! Please review." -- thilo */
|
||||
|
||||
// Then update the registers
|
||||
// Have the ISR update the timer registers next run
|
||||
videoFlipTimer = true;
|
||||
Serial.print("done\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -435,28 +419,20 @@ ISR(TIMER2_OVF_vect) {
|
||||
digitalWrite(statusPIN, HIGH);
|
||||
#endif
|
||||
|
||||
// For each cycle, we have potential COLORS pages to display.
|
||||
// For each cycle, we have potential SHADES pages to display.
|
||||
// Once every page has been displayed, then we move on to the next
|
||||
// cycle.
|
||||
|
||||
// 24 Cycles of Matrix
|
||||
static uint8_t cycle = 0;
|
||||
|
||||
// COLORS pages to display
|
||||
// SHADES pages to display
|
||||
static uint8_t page = 0;
|
||||
|
||||
#if defined (__AVR_ATmega168__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega328P__) || (__AVR_ATmega1280__)
|
||||
TCCR2B = frontTimer->prescaler[page];
|
||||
TCNT2 = frontTimer->counts[page];
|
||||
#elif defined (__AVR_ATmega128__)
|
||||
TCCR2B = frontTimer->prescaler[page];
|
||||
TCNT2 = frontTimer->counts[page];
|
||||
#elif defined (__AVR_ATmega8__)
|
||||
TCCR2B = frontTimer->prescaler[page];
|
||||
TCNT2 = frontTimer->counts[page];
|
||||
#endif
|
||||
|
||||
if ( page < COLORS - 1) {
|
||||
if ( page < SHADES - 1) {
|
||||
|
||||
if (cycle < 6) {
|
||||
DDRD = _BV(cycle+2) | displayBuffer->pixels[page][cycle*2];
|
||||
@@ -492,7 +468,7 @@ ISR(TIMER2_OVF_vect) {
|
||||
|
||||
page++;
|
||||
|
||||
if (page >= COLORS) {
|
||||
if (page >= SHADES) {
|
||||
page = 0;
|
||||
cycle++;
|
||||
}
|
||||
|
||||
+1
-2
@@ -17,8 +17,7 @@
|
||||
|
||||
#define DISPLAY_COLS 14 // Number of columns in the display
|
||||
#define DISPLAY_ROWS 9 // Number of rows in the display
|
||||
#define BIT_DEPTH 2 // Number of bits per pixel
|
||||
#define COLORS (1<<BIT_DEPTH) // Number of unique shades we can display
|
||||
#define SHADES 8 // Number of distinct shades to display, including black, i.e. OFF
|
||||
|
||||
namespace LedSign
|
||||
{
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
#include <Charliplexing.h> //Imports the library, which needs to be
|
||||
//Initialized in setup.
|
||||
|
||||
int blinkdelay = 33; //Sets the time each frame is shown
|
||||
//Sets the time each frame is shown (milliseconds)
|
||||
unsigned int blinkdelay = 1000 / 50;
|
||||
|
||||
/*
|
||||
The BitMap array is what contains the frame data. Each line is one full frame.
|
||||
@@ -127,10 +128,10 @@ uint16_t BitMap[][9] PROGMEM = {
|
||||
};
|
||||
|
||||
void setup() {
|
||||
LedSign::Init(GRAYSCALE); //Initializes the screen
|
||||
LedSign::Init(DOUBLE_BUFFER | GRAYSCALE); //Initializes the screen
|
||||
}
|
||||
void loop() {
|
||||
for (uint8_t gray = 1; gray < COLORS; gray++)
|
||||
for (uint8_t gray = 1; gray < SHADES; gray++)
|
||||
DisplayBitMap(gray); //Displays the bitmap
|
||||
}
|
||||
|
||||
@@ -140,8 +141,10 @@ void DisplayBitMap(uint8_t grayscale)
|
||||
byte frame = 0; //Frame counter
|
||||
byte line = 0; //Row counter
|
||||
unsigned long data; //Temporary storage of the row data
|
||||
unsigned long start = 0;
|
||||
|
||||
while(run == true) {
|
||||
|
||||
for(line = 0; line < 9; line++) {
|
||||
|
||||
//Here we fetch data from program memory with a pointer.
|
||||
@@ -163,11 +166,16 @@ void DisplayBitMap(uint8_t grayscale)
|
||||
LedSign::Set(led, line, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LedSign::Flip(true);
|
||||
|
||||
//Delays the next update
|
||||
delay(blinkdelay);
|
||||
unsigned long end = millis();
|
||||
unsigned long diff = end - start;
|
||||
if ( start && (diff < blinkdelay) )
|
||||
delay( blinkdelay - diff );
|
||||
start = end;
|
||||
|
||||
frame++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Super-simple LoL Shield "breathe" fading test
|
||||
Written by Thilo Fromm <kontakt@thilo-fromm.de>.
|
||||
|
||||
Writen for the LoL Shield, designed by Jimmie Rodgers:
|
||||
http://jimmieprodgers.com/kits/lolshield/
|
||||
|
||||
This is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Version 3 General Public
|
||||
License as published by the Free Software Foundation;
|
||||
or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "Charliplexing.h"
|
||||
|
||||
unsigned int inhale_time_ms = 500;
|
||||
unsigned int hold_breath_ms = 600;
|
||||
unsigned int exhale_time_ms = 800;
|
||||
unsigned int pause_breath_ms = 2000;
|
||||
|
||||
void setup() // run once, when the sketch starts
|
||||
{
|
||||
LedSign::Init(DOUBLE_BUFFER | GRAYSCALE);
|
||||
}
|
||||
|
||||
void loop() // run over and over again
|
||||
{
|
||||
Serial.begin(9600);
|
||||
// inhale
|
||||
for (int8_t i=0; i <= SHADES; i++) {
|
||||
uint8_t sleep = inhale_time_ms / SHADES
|
||||
+ ( SHADES / 2 - i ) * ( inhale_time_ms / (SHADES * 6) );
|
||||
LedSign::Clear(i);
|
||||
LedSign::Flip(true);
|
||||
delay( sleep );
|
||||
}
|
||||
delay( hold_breath_ms );
|
||||
|
||||
// exhale
|
||||
for (int8_t i=SHADES; i >= 0; i--) {
|
||||
uint8_t sleep = exhale_time_ms / SHADES
|
||||
+ ( SHADES / 2 - i ) * ( inhale_time_ms / (SHADES * 6) );
|
||||
LedSign::Clear(i);
|
||||
LedSign::Flip(true);
|
||||
delay( sleep );
|
||||
}
|
||||
|
||||
// pause
|
||||
delay( pause_breath_ms );
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
LoL Shield grayscale / fading test
|
||||
|
||||
Writen for the LoL Shield, designed by Jimmie Rodgers:
|
||||
http://jimmieprodgers.com/kits/lolshield/
|
||||
|
||||
Written by Thilo Fromm <kontakt@thilo-fromm.de>
|
||||
largely based on the work of Matt Mets <Matt.Mets@gmail.com>.
|
||||
|
||||
This is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Version 3 General Public
|
||||
License as published by the Free Software Foundation;
|
||||
or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "Charliplexing.h"
|
||||
|
||||
// Screen "refresh"
|
||||
unsigned int fps = 40;
|
||||
unsigned int fps_ms = 1000 / fps;
|
||||
|
||||
unsigned int frames = 0;
|
||||
|
||||
// number of frames to wait before advance animation
|
||||
unsigned int frames_per_step = 10;
|
||||
|
||||
void setup() // run once, when the sketch starts
|
||||
{
|
||||
LedSign::Init(DOUBLE_BUFFER | GRAYSCALE);
|
||||
}
|
||||
|
||||
uint8_t i = 0;
|
||||
void loop() // run over and over again
|
||||
{
|
||||
static unsigned long start = 0;
|
||||
unsigned long end;
|
||||
|
||||
for (int row = 0; row < 9; row++)
|
||||
for (int col = 0; col < 14; col++)
|
||||
LedSign::Set(col, row, (row+col + i)%SHADES);
|
||||
|
||||
LedSign::Flip(true);
|
||||
|
||||
if (0 == (frames++) % frames_per_step)
|
||||
i++;
|
||||
|
||||
end = millis();
|
||||
if (start) {
|
||||
unsigned long diff = end - start;
|
||||
if ( diff < fps_ms ) {
|
||||
delay( fps_ms - diff );
|
||||
} else {
|
||||
// Signal that we're too slow...
|
||||
LedSign::Clear(0);
|
||||
LedSign::Flip();
|
||||
delay(500);
|
||||
LedSign::Clear(SHADES);
|
||||
LedSign::Flip();
|
||||
delay(500);
|
||||
// re-set timeout
|
||||
end = millis();
|
||||
}
|
||||
}
|
||||
start = end;
|
||||
}
|
||||
Reference in New Issue
Block a user