// ledheart.c
// for NerdKits with ATmega168
// hevans@nerdkits.com
// modified from ledarray.c

#define F_CPU 14745600

#include <stdio.h>
#include <stdlib.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/uart.h"
#include "../libnerdkits/lcd.h"

// PIN DEFINITIONS:
//
// PC5 ROW DRIVER
// PB1-PB5, PC0-PC4 : Column drivers 0 - 10
// We will use one row, and 10 column drivers. Switching the 
// direction of every other LED, this allows us to drive 20 LEDs
#define ROWS 1
#define COLS 20

volatile uint8_t la_row;
volatile uint8_t duty[COLS]; //keeps the duty cycle of every LED: 0 - never on, 64 - all on
volatile uint8_t incrementor;


//duty 0 - 64
void set_duty(uint8_t j, uint8_t the_duty) {
  if(j < COLS){
    duty[j] = (the_duty >  64) ? 64 : the_duty;
  }  
}

//set all the LEDs to full duty
void full_duty(){
  uint8_t j;
  for(j=0;j<COLS;j++){
    duty[j] = 64;
  }
}

//retrieve duty
inline uint8_t duty_get(uint8_t j) {
  if(j < COLS) {
    return duty[j];
  } else {
    return 0;
  }
}


//shortcut to set duty full on or full off
inline void ledarray_set(uint8_t j, uint8_t onoff) {
  if(j < COLS) {
    if(onoff) {
      duty[j] = 64;
    } else {
      duty[j] = 0;
    }
  }
}

inline void ledarray_set_columndriver(uint8_t j, uint8_t onoff, uint8_t sense) {
  // cols 0-4: PB1-5
  // cols 5-9: PC0-4
  if(j < 5) {
    if(onoff) {
      PORTB |= (1 << (PB1 + j));
    } else {
      PORTB &= ~(1<< (PB1 + j));
    }
    if(sense == onoff) {
      DDRB |= (1 << (PB1 + j));
    } else {
      DDRB &= ~(1 << (PB1 + j));
      PORTB &= ~(1 << (PB1 + j));
    }
  } else {
    if(onoff) {
      PORTC |= (1 << (PC0 + (j-5)));
    } else {
      PORTC &= ~(1<< (PC0 + (j-5)));
    }  
    if(sense == onoff) {
      DDRC |= (1 << (PC0 + (j-5)));
    } else {
      DDRC &= ~(1 << (PC0 + (j-5)));
      PORTC &= ~(1 << (PC0 + (j-5)));
    }
  }
}

inline void ledarray_all_off() {
  // turn off all row drivers
  DDRC &= ~(1<<PC5);
  PORTC &= ~(1<<PC5);
    
  // turn off all column drivers
  DDRC &= ~( (1<<PC0) | (1<<PC1) | (1<<PC2) | (1<<PC3) | (1<<PC4) );
  PORTC &= ~( (1<<PC0) | (1<<PC1) | (1<<PC2) | (1<<PC3) | (1<<PC4) );
  DDRB &= ~( (1<<PB1) | (1<<PB2) | (1<<PB3) | (1<<PB4) | (1<<PB5) );
  PORTB &= ~( (1<<PB1) | (1<<PB2) | (1<<PB3) | (1<<PB4) | (1<<PB5) );
}

SIGNAL(SIG_OVERFLOW0) {
  
  //keep a counter to provide duty cycle
  incrementor++;
  if(incrementor == 64) 
	incrementor = 0;

  // turn off old row driver
  ledarray_all_off();

  // increment row number
  if(++la_row == 2*ROWS)
    la_row = 0;

  // set column drivers appropriately
  uint8_t j;
  if(la_row%2 == 0) {
    // even la_row number: fill even columns
    for(j=0; j<COLS/2; j++) {
      //check duty for each LED, do not turn on if duty is less than incrementor
      if(duty[2*j] > incrementor)
        ledarray_set_columndriver(j, 1, 1);
      else
        ledarray_set_columndriver(j, 0, 1);
    }
    // activate row driver SINK
    PORTC &= ~(1 << (PC5));
    DDRC |= (1 << (PC5));
  } else {
    // odd la_row number: fill odd columns
    for(j=0; j<COLS/2; j++) {
      //check duty for each LED, do not turn on if duty is less than incrementor
      if(duty[2*j + 1] > incrementor)
        ledarray_set_columndriver(j, 0, 0);
      else
        ledarray_set_columndriver(j, 1, 0);
    }
    // activate row driver SOURCE
    PORTC |= (1 << (PC5));
    DDRC |= (1 << (PC5));
  }
}

void ledarray_init() {
  incrementor = 0;

  // Timer0 CK/8 (7000Hz)
  TCCR0B = (1<<CS01) | (0<<CS00);
  TIMSK0 = (1<<TOIE0);
  
  // outputs (set row drivers high for off)
  DDRC &= ~( (1<<PC0) | (1<<PC1) | (1<<PC2) | (1<<PC3) | (1<<PC4) | (1<<PC5) );
  DDRB &= ~( (1<<PB1)|(1<<PB2)|(1<<PB3)|(1<<PB4)|(1<<PB5) );
}


void ledarray_blank() {
    uint8_t j;
    for(j=0; j<COLS; j++) {
      ledarray_set(j,0);
    }
}

//turn on all LEDs
void all(){
  uint8_t j;
    for(j=0; j<COLS; j++) {
      ledarray_set(j,1);
    }
}


//blink all LEDs slowly
void blink_all(uint16_t delay, uint8_t times){

  uint8_t i,j,z;
  for(i=0;i<times;i++){ 
     

    for(j=0;j<40;j++){ //light up
      for(z=0; z<COLS; z++) {
        duty[z] = j;
      }
      delay_ms(delay);
    }

    for(j=40;j>2;j--){ //dim out
      for(z=0; z<COLS; z++) {
        duty[z] = j;
      }
      delay_ms(delay);
    }

  }
}

void set_all_duty(uint8_t set){
  uint8_t i;
  for(i=0;i<COLS;i++){
    duty[i] = set;
  }

}


//random twinking effect
void twinkle(){
   FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);



   //set all LEDs to small brightness
   set_all_duty(2);

   //one tenth of the max rand() will return
   int limit = RAND_MAX/10;

   //keep the current state of each LED 
   //0 - off
   //1 - brightening
   //2 - dimming
   uint8_t state[COLS];
   uint8_t max_bright[COLS];
   uint8_t a;
   for(a=0; a<COLS; a++) {
      state[a] = 0;
      max_bright[a] = 0;
   }

   uint8_t run = 0;
   uint32_t brightCalc;
   while(1){
     
     if(run == COLS) 
	run = 0;

     //lightIt will be 1, 1 in 10 times
     uint16_t rand = abs(random());
     uint8_t lightIt = (rand < limit) ? 1 : 0;

     //chanche state off one LED to 1, if lightIt is on.
     //1 in 10 LEDs will begin a twinkle every time the the whie loop goes around
     if(((lightIt == 1) && (state[run] == 0))){
       state[run] = 1;
       brightCalc = (((uint32_t)rand * 32) / limit ) ;
       max_bright[run] = (uint8_t)brightCalc + 10;
          //fprintf_P(&uart_stream, PSTR("max %d, rand %d, bright %d \n\r"),RAND_MAX,rand,brightCalc);  
     }
     
     uint8_t i;
     for(i=0; i<COLS; i++) {
        //increment duty of all LEDs in state 1 by 1 (make it brighter)
        if(state[i] == 1){
	  duty[i] = duty[i] + 1;
          //switch to state 2 once we reach max brightness
  	  if(duty[i] == max_bright[i]){
		state[i] = 2;
	  }
         //decrement duty of all LEDs in state 2
        } else if(state[i] == 2) {
	   duty[i] = duty[i] - 1;
           //return to state 0 once we reach bottom of brightness
           if(duty[i] == 2) 
		state[i] = 0;
        }
     }

     delay_ms(15);
     run++;

   }

}





int main() {
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

  ledarray_init();
  lcd_init();
  
  lcd_home();                //012345678901234567890123  
  fprintf_P(&lcd_stream, PSTR("  Happy Valentines Day  "));
  lcd_line_two();
  fprintf_P(&lcd_stream, PSTR("     From NerdKits      "));  
  // activate interrupts
  sei();

  // init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;
  
  //blink_all(10,10);
  //ledarray_blank();
  twinkle();
  return 0;
}

