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:
thilo.alexander
2012-01-07 22:37:32 +00:00
parent af145d3e13
commit db05a29ac7
5 changed files with 207 additions and 91 deletions
+58 -82
View File
@@ -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
View File
@@ -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;
}