/* Charliplexing.cpp - Using timer2 with 1ms resolution Alex Wenger http://arduinobuch.wordpress.com/ Matt Mets http://cibomahto.com/ Timer init code from MsTimer2 - Javier Valencia Misc functions from Benjamin Sonnatg History: 2009-12-30 - V0.0 wrote the first version at 26C3/Berlin 2010-01-01 - V0.1 adding misc utility functions (Clear, Vertical, Horizontal) comment are Doxygen complaints now 2010-05-27 - V0.2 add double-buffer mode 2010-08-18 - V0.9 Merge brightness and grayscale This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, 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 #include #include #include "Charliplexing.h" volatile unsigned int LedSign_tcnt2; typedef struct _videoPage { uint8_t pixels[SHADES][48]; // TODO: is 48 right? } videoPage; /* ----------------------------------------------------------------- */ /** Table for the LED multiplexing cycles * Each frame is made of 24 bytes (for the 24 display cycles) * 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]; /// Determines whether the display is in single or double buffer mode uint8_t displayMode = SINGLE_BUFFER; /// Flag indicating that the display page should be flipped as soon as the /// current frame is displayed volatile bool videoFlipPage = false; /// Pointer to the buffer that is currently being displayed videoPage* displayBuffer; /// Pointer to the buffer that should currently be drawn to videoPage* workBuffer; /// Flag indicating that the timer buffer should be flipped as soon as the /// current frame is displayed volatile bool videoFlipTimer = false; // Timer counts to display each page for, plus off time typedef struct timerInfo { uint8_t counts[SHADES]; uint8_t prescaler[SHADES]; } timerInfo; // Double buffer the timing information, of course. timerInfo* frontTimer; timerInfo* backTimer; timerInfo* tempTimer; timerInfo timer[2]; // Record a slow and fast prescaler for later use typedef struct prescalerInfo { uint8_t relativeSpeed; uint8_t TCCR2; } prescalerInfo; // TODO: Generate these based on processor type and clock speed 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 //#ifdef MEASURE_ISR_TIME //uint8_t statusPIN = 19; //#endif typedef struct LEDPosition { uint8_t high; uint8_t low; } LEDPosition; /* ----------------------------------------------------------------- */ /** Table for LED Position in leds[] ram table */ const LEDPosition ledMap[126] = { {13, 5}, {13, 6}, {13, 7}, {13, 8}, {13, 9}, {13,10}, {13,11}, {13,12}, {13, 4}, { 4,13}, {13, 3}, { 3,13}, {13, 2}, { 2,13}, {12, 5}, {12, 6}, {12, 7}, {12, 8}, {12, 9}, {12,10}, {12,11}, {12,13}, {12, 4}, { 4,12}, {12, 3}, { 3,12}, {12, 2}, { 2,12}, {11, 5}, {11, 6}, {11, 7}, {11, 8}, {11, 9}, {11,10}, {11,12}, {11,13}, {11, 4}, { 4,11}, {11, 3}, { 3,11}, {11, 2}, { 2,11}, {10, 5}, {10, 6}, {10, 7}, {10, 8}, {10, 9}, {10,11}, {10,12}, {10,13}, {10, 4}, { 4,10}, {10, 3}, { 3,10}, {10, 2}, { 2,10}, { 9, 5}, { 9, 6}, { 9, 7}, { 9, 8}, { 9,10}, { 9,11}, { 9,12}, { 9,13}, { 9, 4}, { 4, 9}, { 9, 3}, { 3, 9}, { 9, 2}, { 2, 9}, { 8, 5}, { 8, 6}, { 8, 7}, { 8, 9}, { 8,10}, { 8,11}, { 8,12}, { 8,13}, { 8, 4}, { 4, 8}, { 8, 3}, { 3, 8}, { 8, 2}, { 2, 8}, { 7, 5}, { 7, 6}, { 7, 8}, { 7, 9}, { 7,10}, { 7,11}, { 7,12}, { 7,13}, { 7, 4}, { 4, 7}, { 7, 3}, { 3, 7}, { 7, 2}, { 2, 7}, { 6, 5}, { 6, 7}, { 6, 8}, { 6, 9}, { 6,10}, { 6,11}, { 6,12}, { 6,13}, { 6, 4}, { 4, 6}, { 6, 3}, { 3, 6}, { 6, 2}, { 2, 6}, { 5, 6}, { 5, 7}, { 5, 8}, { 5, 9}, { 5,10}, { 5,11}, { 5,12}, { 5,13}, { 5, 4}, { 4, 5}, { 5, 3}, { 3, 5}, { 5, 2}, { 2, 5}, }; /* ----------------------------------------------------------------- */ /** Constructor : Initialize the interrupt code. * should be called in setup(); */ void LedSignInit(uint8_t mode) { //#ifdef MEASURE_ISR_TIME // pinMode(statusPIN, OUTPUT); // digitalWrite(statusPIN, LOW); //#endif float prescaler = 0.0; #if defined (__AVR_ATmega168__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega328P__) || (__AVR_ATmega1280__) TIMSK2 &= ~(1<= 1000000UL) && (F_CPU <= 16000000UL)) { // prescaler set to 64 TCCR2B |= ((1< 16Mhz, prescaler set to 128 TCCR2B |= (1<= 1000000UL) && (F_CPU <= 16000000UL)) { // prescaler set to 64 TCCR2 |= (1< 16Mhz, prescaler set to 128 TCCR2 |= ((1<= 1000000UL) && (F_CPU <= 16000000UL)) { // prescaler set to 64 TCCR2 |= ((1< 16Mhz, prescaler set to 256 TCCR2 |= (1< 7)?24:0); uint8_t work = _BV(pin_high & 0x07); // If we aren't in grayscale mode, just map any pin brightness to max if (c > 0 && !(displayMode & GRAYSCALE)) { c = SHADES-1; } int i; for (i = 0; i < SHADES-1; i++) { if( c > i ) { workBuffer->pixels[i][bufferNum] |= work; // ON } else { workBuffer->pixels[i][bufferNum] &= ~work; // OFF } } } /* Set the overall brightness of the screen * @param brightness LED brightness, from 0 (off) to 127 (full on) */ void LedSignSetBrightness(uint8_t brightness) { // 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; uint8_t i; for (i=1; icounts[i] = pageCounts[i]; // TODO: Generate this dynamically frontTimer->prescaler[i] = slowPrescaler.TCCR2; } } // Wait until the previous brightness request goes through while( videoFlipTimer ) { _delay_ms(1); } // Compute on time for each of the pages // Use the fast timer; slow timer is only useful for < 3 shades. for (i = 0; i < SHADES - 1; i++) { uint8_t interval = 255 - pageCounts[i]; backTimer->counts[i] = 255 - brightnessPercent * interval * fastPrescaler.relativeSpeed; backTimer->prescaler[i] = fastPrescaler.TCCR2; difference += backTimer->counts[i] - pageCounts[i]; } // Compute off time backTimer->counts[SHADES - 1] = 255 - difference; backTimer->prescaler[SHADES - 1] = slowPrescaler.TCCR2; /* ---- End of "This needs review! Please review." -- thilo */ // Have the ISR update the timer registers next run videoFlipTimer = true; } /* ----------------------------------------------------------------- */ /** The Interrupt code goes here ! */ ISR(TIMER2_OVF_vect) { DDRD = 0x0; DDRB = 0x0; //#ifdef MEASURE_ISR_TIME // digitalWrite(statusPIN, HIGH); //#endif // 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; // SHADES pages to display static uint8_t page = 0; TCCR2B = frontTimer->prescaler[page]; TCNT2 = frontTimer->counts[page]; if ( page < SHADES - 1) { if (cycle < 6) { DDRD = _BV(cycle+2) | displayBuffer->pixels[page][cycle*2]; PORTD = displayBuffer->pixels[page][cycle*2]; DDRB = displayBuffer->pixels[page][cycle*2+1]; PORTB = displayBuffer->pixels[page][cycle*2+1]; } else if (cycle < 12) { DDRD = displayBuffer->pixels[page][cycle*2]; PORTD = displayBuffer->pixels[page][cycle*2]; DDRB = _BV(cycle-6) | displayBuffer->pixels[page][cycle*2+1]; PORTB = displayBuffer->pixels[page][cycle*2+1]; } else if (cycle < 18) { DDRD = _BV(cycle+2-12) | displayBuffer->pixels[page][cycle*2]; PORTD = displayBuffer->pixels[page][cycle*2]; DDRB = displayBuffer->pixels[page][cycle*2+1]; PORTB = displayBuffer->pixels[page][cycle*2+1]; } else { DDRD = displayBuffer->pixels[page][cycle*2]; PORTD = displayBuffer->pixels[page][cycle*2]; DDRB = _BV(cycle-6-12) | displayBuffer->pixels[page][cycle*2+1]; PORTB = displayBuffer->pixels[page][cycle*2+1]; } } else { // Turn everything off DDRD = 0x0; DDRB = 0x0; } page++; if (page >= SHADES) { page = 0; cycle++; } if (cycle > 24) { cycle = 0; // If the page should be flipped, do it here. if (videoFlipPage && (displayMode & DOUBLE_BUFFER)) { // TODO: is this an atomic operation? videoFlipPage = false; videoPage* temp = displayBuffer; displayBuffer = workBuffer; workBuffer = temp; } if (videoFlipTimer) { videoFlipTimer = false; tempTimer = frontTimer; frontTimer = backTimer; backTimer = tempTimer; } } //#ifdef MEASURE_ISR_TIME // digitalWrite(statusPIN, LOW); //#endif }