//
// Title        : Single led pwm 
// Author       : Lars Pontoppidan Larsen
// Date         : February 15, 2006
// Version      : 1.00
// Target MCU   : Atmel AVR Series, 8 MHz intended
//
// DESCRIPTION:
// This module implements controlling a single led using high res 16-bit PWM. 
// Two behaviours are implemented. A really lazy filament lamp turnon and decay.
// "Below" that a really slow exp. sinusoid creates an alternating backgound
// light.
//
// RESOURCES:
// 16-bit timer and PWM output. This module is a bit wastefull with flash memory
// The two lookup tables take 260+316 bytes alone.
//
// DISCLAIMER:
// The author is in no way responsible for any problems or damage caused by
// using this code. Use at your own risk.
//
// LICENSE:
// This code is distributed under the GNU Public License
// which can be found at http://www.gnu.org/licenses/gpl.txt
//

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

/******************************************************************************
*                                                                             *
*                           HARDWARE ABSTRACTION                              *
*                                                                             *
*  In this section it is defined what hardware resources the module uses.     *
*                                                                             *
******************************************************************************/


// Using timer1
// Set fast pwm mode, counting to 65535, clear on compare match, prescaler 1
#define TIMER_INIT()        TCCR1A = (1<<COM1A1)|(1<<COM1A0)|(1<<WGM11); \
                            TCCR1B = (1<<WGM12)|(1<<WGM13)|(1<<CS10); TIMSK1 = (1<<TOIE1); \
                            ICR1 = 65535; TCNT1 = 0;
#define TIMER_OCR           OCR1A
#define TIMER_OVERFLOW_ISR  TIMER1_OVF_vect

#define SET_DDR()           DDRB |= (1<<1)


/******************************************************************************
*                                                                             *
*                               IMPLEMENTATION                                *
*                                                                             *
******************************************************************************/


#define PWM_VALUE_PRESCALE 1 // Controls speed of lamp envelope


#define PWM_BG_PRESCALE 2  // Controls speed of backlight sinusoid


// matlab to make ramp down: 
// x = exp([0:125]/11.5); y=round(x * 65535 / max(x))
//
#define PWM_VALUES 130
#define PWM_VALUE_ON 6
#define PWM_VALUE_OFF 129
const unsigned short pwm_table[PWM_VALUES] PROGMEM = {
8192,24576,40960,57344,
65535,60077,55074,50487,46282,42428,38894,35655,32686,29963,27468,25180,23083,
21161,19398,17783,16302,14944,13700,12559,11513,10554,9675,8869,8131,7453,6833,
6264,5742,5264,4825,4424,4055,3717,3408,3124,2864,2625,2407,2206,2022,1854,
1700,1558,1428,1309,1200,1100,1009,925,848,777,712,653,599,549,503,461,423,388,
355,326,299,274,251,230,211,193,177,162,149,137,125,115,105,96,88,81,74,68,62,
57,52,48,44,40,37,34,31,29,26,24,22,20,18,17,16,14,13,12,11,10,9,8,8,7,7,6,5,5,
5,4,4,4,3,3,3,3,2,2,2,2,2,1,1,1};

unsigned short pwm_value;
unsigned char pwm_target;


// Quarter exp(sinusoid) for horny background light
// x=exp(sin(0:0.02:pi/2)*6); y=round(x*1024/max(x))
//
#define PWM_BG_VALUES 158
const unsigned short pwm_bg_table[PWM_BG_VALUES] PROGMEM = {
3,3,3,3,3,3,4,4,4,4,5,5,5,6,6,6,7,7,7,8,8,9,9,10,11,11,12,13,13,14,15,16,17,18,
19,20,21,22,23,25,26,28,29,31,33,35,36,38,41,43,45,47,50,53,55,58,61,65,68,71,
75,79,83,87,91,96,101,105,110,116,121,127,133,139,145,152,158,165,173,180,188,
196,204,213,221,230,239,249,259,269,279,290,300,311,323,334,346,358,370,383,
396,408,422,435,448,462,476,490,504,519,533,548,562,577,592,607,622,636,651,
666,681,696,711,725,740,754,768,782,796,810,823,836,849,861,873,885,897,908,
918,929,938,948,957,965,973,980,987,993,999,1004,1009,1013,1016,1019,1021,1023,
1024,1024};


signed short pwm_bg;
unsigned char pwm_bg_up;


ISR(TIMER_OVERFLOW_ISR)
{
  unsigned char c;
  unsigned short pwm1;
  unsigned short pwm2;
  
  // Prepare pwm from led flashing
  c = pwm_value >> PWM_VALUE_PRESCALE;
  
  if (c<pwm_target)
    pwm_value++;
 
  pwm1 = pgm_read_word_near(&(pwm_table[c]));
    
    
  // Prepare pwm from background light
  c = pwm_bg >> PWM_BG_PRESCALE;

  if (pwm_bg_up) {
    if (c==(PWM_BG_VALUES-1))
      pwm_bg_up = 0;
    else
      pwm_bg++;
  }
  else {
    if (c==0)
      pwm_bg_up = 1;
    else
      pwm_bg--;
  }
  
  pwm2 = pgm_read_word_near(&(pwm_bg_table[c]));
  
  // Take the brightest one
  if (pwm2 < pwm1)
    TIMER_OCR = pwm1;
  else
    TIMER_OCR = pwm2;
    
}


void pwmled_on_hold(void)
{
  pwm_target = PWM_VALUE_ON;
  pwm_value = 0;
}

void pwmled_off(void)
{
  pwm_target = PWM_VALUE_OFF;
}

void pwmled_flash(void)
{
  pwm_target = PWM_VALUE_OFF;
  pwm_value = 0;  
}


void pwmled_init(void)
{
  pwm_target = PWM_VALUE_OFF;
  pwm_value = PWM_VALUE_OFF<<PWM_VALUE_PRESCALE;
  pwm_bg = 0;
  pwm_bg_up = 1;
  TIMER_OCR = 0;
  SET_DDR();
  TIMER_INIT();
  
}



