/*
Arduino LED Tester
DPIN--39R--+--10R---TESTLED---GND
           |      |         |
          470u    ATOP     ABOT
           |
          GND

 Measures LED characteristics by charging up the cap to deliver target current and find forward voltage
 From target current, we can calculate R to be used with a design supply voltage and a matching part number.
  */
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
//lcd update interval
#define LCDINT 500
//key check interval
#define KEYINT 200
//pin for buttons
#define KEYPIN A0
//button constants
#define btnRIGHT  6
#define btnUP     5
#define btnDOWN   4
#define btnLEFT   3
#define btnSELECT 2
#define btnNONE   (-1)
LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD adress
//Globals for display
long itest=10;    //test current, in mA
long vset =14000;  //display voltage in mV
long vled,vrr,irr,pset;  //LED voltage, Resistor voltage, resistor current, display power
//resistors in Jaycar 1/2 W range, part nos start at RR0524 for 10R
#define RCOUNT 121
long rvals[]={10,11,12,13,15,16,18,20,22,24,27,30,33,36,39,43,47,51,56,62,68,75,82,91,100,110,120,130,150,160,180,200,220,240,270,300,330,360,390,430,470,510,560,620,680,750,820,910,1000,1100,1200,1300,1500,1600,1800,2000,2200,2400,2700,3000,3300,3600,3900,4300,4700,5100,5600,6200,6800,7500,8200,9100,10000,11000,12000,13000,15000,16000,18000,20000,22000,24000,27000,30000,33000,36000,39000,43000,47000,51000,56000,62000,68000,75000,82000,91000,100000,110000,120000,130000,150000,160000,180000,200000,220000,240000,270000,300000,330000,360000,390000,430000,470000,510000,560000,620000,680000,750000,820000,910000,1000000};
long lastlcd=0;  //time of last lcd update
long lastkey=0;  //time of last key check
int lcdflash=0;  //lcd flashing phase variable
long pdes;
long rval;      //calculated resistor value for display
long rindex;    //index of selected resistor in rvals[]
int pwmout=0;    //pwm output of current driver
int rvalid=0;    //flag if resistor value is valid 
//pins for test interface
#define ATOP A2
#define ABOT A3
#define DPIN 3
#define OSAMP 16

void setup() {
  Serial.begin(9600);
// lcd.begin(16, 2);        //lcd 
  lcd.begin();
  lcd.clear();         
  lcd.backlight(); 
  pinMode(DPIN,OUTPUT);    //pwm pin
}

void loop() {
  long atop,abot,arr;      //analog sample values
  rvalid=0;                //set flag to not valid
  atop=analogoversample(ATOP,OSAMP)/OSAMP;
  abot=analogoversample(ABOT,OSAMP)/OSAMP;  
  arr=atop-abot;      //this is the analog value across the 10R sense resistor
  if(arr<0){arr=0;}    //sanity check
  vled=abot*5000/1023;  //5000mV=1023 => voltage across LED
  vrr=arr*5000/1023;    //voltage across sense resistor
  irr=vrr/10;     //led and resistor current in mA (cos it's a 10R resistor)
    
  if(irr<itest){pwmout++;if(pwmout>255){pwmout=255;}}    //ramp up current if too low
  if(irr>itest){pwmout--;if(pwmout<0){pwmout=0;}}        //ramp down if too high
  if(irr>24){pwmout=pwmout-5;if(pwmout<0){pwmout=0;}}    //ramp down quick if too too high
  if(irr>itest*3){pwmout=pwmout-5;if(pwmout<0){pwmout=0;}}    //ramp down quick if too too high
  analogWrite(DPIN,pwmout);                              //output new PWM
  rval=(vset-vled)/itest;                                //mV/mV => ohms resistance of display resistor
  for(int i=0;i<RCOUNT;i++){                             //find next highest E24 value
    if(rvals[i]>=rval){rindex=i;rval=rvals[rindex];i=RCOUNT+1;rvalid=1;}
  }
  if(abs(irr-itest)>(itest/5)+1){rvalid=0;}              //has current settled within 20%?
  if(vled>vset){rvalid=0;}                               //if vled>vset, no valid resistor exists
  pset=0;        //work out dissipation in ballast resistor if valid)
  if(rvalid){pset=itest*itest*rval;}      //this will be in microwatts (milliamps squared)
  
  if(millis()-lastlcd>LCDINT){      //check if display due to be updated
    lastlcd=millis();
    dolcd();        //update display
    lcdflash=1-lcdflash;  //toggle flash variable
    }
  if(millis()-lastkey>KEYINT){    //check if keys due to be checked
    lastkey=millis();
    dobuttons();
    }
    delay(1);
}        //end of loop

void dolcd(){
  lcd.setCursor(0,0);    //first line
  //milliamps
  if(lcdflash||rvalid){        //show if correct if on flashing phase
    if(itest>9){lcd.write(((itest/10)%10)+'0');}else{lcd.write(' ');}    //blank tens if zero
    lcd.write((itest%10)+'0');
  }
  else{      //blank if not
    lcd.write(' ');lcd.write(' ');
  }
    lcd.write('m');lcd.write('A');lcd.write(' ');
  //VLED
  lcd.write(((vled/1000)%10)+'0');
  lcd.write('.');
  lcd.write(((vled/100)%10)+'0');
  lcd.write('V');lcd.write(' ');

  //actual LED current
  if(irr>9){lcd.write(((irr/10)%10)+'0');}else{lcd.write(' ');}    //blank tens if zero
  lcd.write((irr%10)+'0');
  lcd.write('m');lcd.write('A');lcd.write(' ');
  if((pset>499999)&&(lcdflash)){lcd.write('P');}else{lcd.write(' ');} //flash P if power above 1/2 watt
  
  lcd.setCursor(0,1);   //second line
  //
  if(vset>9999){lcd.write(((vset/10000)%10)+'0');}else{lcd.write(' ');}    //tens of vset, blank if zero
  lcd.write(((vset/1000)%10)+'0');      //units of vset
  lcd.write('V');lcd.write(' ');
  
  if(rvalid){
    lcdprintrval(rval);  //resistor value (4 characters)
    lcd.write(' ');
    lcdprintpartno(rindex);    //resistor part no (6 characters)
    if(pset>499999){lcd.write('!');}else{lcd.write(' ');} //show ! if power above 1/2 watt
  }else{
    lcd.write(' ');
    lcd.write('-');
    lcd.write('-');
    lcd.write('-');
    lcd.write(' ');
    lcd.write('-');
    lcd.write('-');
    lcd.write('-');
    lcd.write('-');
    lcd.write('-');
    lcd.write('-');
    lcd.write(' ');
  }    
}

void lcdprintpartno(int index){
  //part number
  lcd.write('R');
  lcd.write('R');
  lcd.write('0');
  lcd.write((((index+524)/100)%10)+'0');      //part no's start at RR0524 for 10R
  lcd.write((((index+524)/10)%10)+'0');
  lcd.write((((index+524))%10)+'0');
}

void lcdprintrval(long rval){        //print a value in 10k0 format, always outputs 4 characters
  	long mult=1;
	long modval;
	if(rval>999){mult=1000;}
	if(rval>999999){mult=1000000;}
	modval=(10*rval)/mult;		//convert to final format, save a decimal place
	if(modval>999){		//nnnM
		lcd.write(((modval/1000)%10)+'0');
		lcd.write(((modval/100)%10)+'0');
		lcd.write(((modval/10)%10)+'0');
		lcdprintmult(mult);
			}else{
	if(modval>99){		//nnMn
		lcd.write(((modval/100)%10)+'0');
		lcd.write(((modval/10)%10)+'0');
		lcdprintmult(mult);
		lcd.write(((modval)%10)+'0');
			}else{	//_nMn
		lcd.write(' ');
		lcd.write(((modval/10)%10)+'0');
		lcdprintmult(mult);
		lcd.write(((modval)%10)+'0');
    }
  }
}
void lcdprintmult(long mult){      //helper function to print multiplier
	switch (mult){
	case 1:	lcd.print('R');break;
	case 1000:	lcd.print('k');break;
	case 1000000:	lcd.print('M');break;
	default:	lcd.print('?');break;
  }
}
int read_LCD_buttons(){
  int adc_key_in    = 0;
  adc_key_in = analogRead(KEYPIN);      // read the value from the sensor 
  delay(5); //switch debounce delay. Increase this delay if incorrect switch selections are returned.
  int k = (analogRead(KEYPIN) - adc_key_in); //gives the button a slight range to allow for a little contact resistance noise
  if (5 < abs(k)) return btnNONE;  // double checks the keypress. If the two readings are not equal +/-k value after debounce delay, it tries again.
  // my buttons when read are centered at these valies: 0, 144, 329, 504, 741
  // we add approx 50 to those values and check to see if we are close
  if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
  if (adc_key_in < 50)   return btnRIGHT;  
  if (adc_key_in < 195)  return btnUP; 
  if (adc_key_in < 380)  return btnDOWN; 
  if (adc_key_in < 555)  return btnLEFT; 
  if (adc_key_in < 790)  return btnSELECT;   
  return btnNONE;  // when all others fail, return this...
 
}
void dobuttons(){      //updates variables. debounces by only sampling at intervals
    int key;
    key = read_LCD_buttons();
    if(key==btnLEFT){itest=itest-1;if(itest<1){itest=1;}}
    if(key==btnRIGHT){itest=itest+1;if(itest>20){itest=20;}}
    if(key==btnUP){vset=vset+1000;if(vset>99000){vset=99000;}}
    if(key==btnDOWN){vset=vset-1000;if(vset<0){vset=0;}}   
}
long analogoversample(int pin,int samples){      //read pin samples times and return sum
  long n=0;
  for(int i=0;i<samples;i++){
    n=n+analogRead(pin);
  }
  return n;
}
