Created
December 14, 2012 19:26
-
-
Save aperiodic/4287932 to your computer and use it in GitHub Desktop.
Driving a 5x5 RGB LED grid with 5-bit PWM using only some shift registers, a demuxer, and a timer interrupt.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <avr/interrupt.h> | |
#include <avr/io.h> | |
#include <avr/pgmspace.h> | |
#define __spi_clock 13 // SCK - hardware SPI | |
#define __spi_latch 10 | |
#define __spi_data 11 // MOSI - hardware SPI | |
#define __spi_data_in 12 // MISO - hardware SPI (unused) | |
#define __brightness_levels 32 // 0...15 above 28 is bad for ISR ( move to timer1, lower irq freq ! ) | |
#define __max_brightness __brightness_levels-1 | |
#define __TIMER1_MAX 0xFFFF // 16 bit CTR | |
#define __TIMER1_CNT 0x0130 // 32 levels --> 0x0130; 38 --> 0x0157 (flicker) | |
#define __TIMER2_MAX 0xFF // 8 bit CTR | |
#define __TIMER2_CNT 0xFF // max 28 levels ! | |
/* PHYSICAL DISPLAY ATTRIBUTES */ | |
#define ROWS 5 | |
#define COLUMNS 5 | |
#define LEDS ROWS*COLUMNS | |
/* FRAMEBUFFER MACROS */ | |
#define FLIP_BUFFER() which_buffer=1-which_buffer | |
long count, ms; | |
byte row; | |
// framebuffer arrays | |
byte fb_r[ROWS * COLUMNS * 2], fb_g[ROWS * COLUMNS * 2], fb_b[ROWS * COLUMNS * 2], which_buffer; | |
int outs[COLUMNS]; | |
void setup(void) { | |
Serial.begin(9600); | |
// setup shift register pins | |
pinMode(__spi_clock,OUTPUT); | |
pinMode(__spi_latch,OUTPUT); | |
pinMode(__spi_data,OUTPUT); | |
pinMode(__spi_data_in,INPUT); | |
digitalWrite(__spi_latch,LOW); | |
digitalWrite(__spi_data,LOW); | |
digitalWrite(__spi_clock,LOW); | |
// setup 3:8 selector pins, initialize to row 0 | |
pinMode(2,OUTPUT); | |
pinMode(3,OUTPUT); | |
pinMode(4,OUTPUT); | |
pinMode(5,OUTPUT); | |
digitalWrite(2,HIGH); | |
digitalWrite(3,LOW); | |
digitalWrite(4,LOW); | |
digitalWrite(5,LOW); | |
row = 0; | |
// setup frame buffer | |
which_buffer = 0; | |
for (int i = 0; i < LEDS * 2; i++) { | |
fb_r[i] = 0; | |
fb_g[i] = 0; | |
fb_b[i] = 0; | |
outs[i % ROWS] = 0; | |
} | |
// setup program state | |
count = 0; | |
ms = 0; | |
// setup hardware spi. for some reason, if this is put | |
// at the beginning of setup(), everything breaks. | |
setup_hardware_spi(); | |
delay(10); | |
// setup the timer1 interrupt, which drives the LED display | |
setup_timer1_ovf(); | |
} | |
void loop(void) { | |
count++; | |
int dly = 0; | |
// omitted: filling the framebuffer w/stuff | |
} | |
if (ms < 3000 || durations[program] - ms < 3000) { | |
int diff = min(ms, durations[program] - ms); | |
float t = diff/3000.0; | |
fb_uniform_mul(t); | |
} | |
FLIP_BUFFER(); | |
delay(dly); | |
} | |
byte spi_transfer(byte data) | |
{ | |
SPDR = data; // Start the transmission | |
while (!(SPSR & (1<<SPIF))) // Wait the end of the transmission | |
{ | |
}; | |
return SPDR; // return the received byte, we don't need that | |
} | |
void setup_hardware_spi(void) { | |
byte clr; | |
// spi prescaler: | |
// SPI2X SPR1 SPR0 | |
// 0 0 0 fosc/4 | |
// 0 0 1 fosc/16 | |
// 0 1 0 fosc/64 | |
// 0 1 1 fosc/128 | |
// 1 0 0 fosc/2 | |
// 1 0 1 fosc/8 | |
// 1 1 0 fosc/32 | |
// 1 1 1 fosc/64 | |
SPCR |= ( (1<<SPE) | (1<<MSTR) ); // enable SPI as master | |
SPCR &= ~ ( (1<<SPR1) | (1<<SPR0) ); // clear prescaler bits | |
clr=SPSR; // clear SPI status reg | |
clr=SPDR; // clear SPI data reg | |
SPSR |= (1<<SPI2X); // set prescaler bits | |
} | |
void setup_timer1_ovf(void) { | |
// Arduino runs at 16 Mhz... | |
// Timer1 (16bit) Settings: | |
// prescaler (frequency divider) values: CS12 CS11 CS10 | |
// 0 0 0 stopped | |
// 0 0 1 /1 | |
// 0 1 0 /8 | |
// 0 1 1 /64 | |
// 1 0 0 /256 | |
// 1 0 1 /1024 | |
// 1 1 0 external clock on T1 pin, falling edge | |
// 1 1 1 external clock on T1 pin, rising edge | |
// | |
TCCR1B &= ~ ( (1<<CS12) );// | ( 1<<CS10 )); | |
TCCR1B |= ( (1<<CS11) | (1<<CS10) ); | |
//normal mode | |
TCCR1B &= ~ ( (1<<WGM13) | (1<<WGM12) ); | |
TCCR1A &= ~ ( (1<<WGM11) | (1<<WGM10) ); | |
//Timer1 Overflow Interrupt Enable | |
TIMSK1 |= (1<<TOIE1); | |
TCNT1 = __TIMER1_MAX - __TIMER1_CNT; | |
// enable all interrupts | |
sei(); | |
} | |
ISR(TIMER1_OVF_vect) { | |
TCNT1 = __TIMER1_MAX - __TIMER1_CNT; | |
byte cycle; | |
byte fb_off = (1-which_buffer) * LEDS; | |
for(cycle = __max_brightness; cycle > 0; cycle--) { | |
for (int i = 0; i < COLUMNS; i++) { | |
if (cycle == fb_r[row*COLUMNS + i + fb_off]) { | |
outs[row] |= 1 << (i*3); | |
} | |
if (cycle == fb_g[row*COLUMNS + i + fb_off]) { | |
outs[row] |= 1 << ((i*3) + 1); | |
} | |
if (cycle == fb_b[row*COLUMNS + i + fb_off]) { | |
outs[row] |= 1 << ((i*3) + 2); | |
} | |
} | |
digitalWrite(__spi_latch,LOW); | |
spi_transfer(outs[row] >> 8); | |
spi_transfer(outs[row]); | |
digitalWrite(__spi_latch,HIGH); | |
} | |
digitalWrite(__spi_latch,LOW); | |
spi_transfer(0); | |
spi_transfer(0); | |
digitalWrite(__spi_latch,HIGH); | |
next_row(); | |
} | |
void next_row(void) { | |
row++; | |
if (row == ROWS) { | |
row = 0; | |
for (int i = 0; i < ROWS; i++) { | |
outs[i] = 0; | |
} | |
} | |
digitalWrite(2, LOW); | |
switch(row) { | |
case 0: | |
digitalWrite(3, LOW); | |
digitalWrite(4, LOW); | |
digitalWrite(5, LOW); | |
break; | |
case 1: | |
digitalWrite(3, HIGH); | |
digitalWrite(4, LOW); | |
digitalWrite(5, LOW); | |
break; | |
case 2: | |
digitalWrite(3, LOW); | |
digitalWrite(4, HIGH); | |
digitalWrite(5, LOW); | |
break; | |
case 3: | |
digitalWrite(3, HIGH); | |
digitalWrite(4, HIGH); | |
digitalWrite(5, LOW); | |
break; | |
case 4: | |
digitalWrite(3, LOW); | |
digitalWrite(4, LOW); | |
digitalWrite(5, HIGH); | |
break; | |
} | |
digitalWrite(2, HIGH); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment