For kicks, this is "Dave C" for my last uSupply USB prototype, the entire program:
/* ****************************************
uSuppyUSB Firmware
Version 1.0
Last Edit: 8/9/12
Written in: AVR GCC C under AVR Studio 6
Written by: David L. Jones
Copyright(C)2012 David L. Jones
[url=http://www.eevblog.com]www.eevblog.com[/url]
NOTES:
- Written for an ATmega88A target on Rev 1 uSupplyUSB Hardware
Revision notes
1.0 - First prototype version
******************************************* */
#define F_CPU 8000000 //clock frequency 8MHz
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <string.h>
#include <stdio.h>
//definitions
#define version 1
#define TRUE 1
#define FALSE 0
#define CURRbutton (PINB & 1<<PB7) //CURRENT button
#define SETbutton (PINB & 1<<PB6) //SET button
#define bit_get(p,m) ((p) & (m))
#define bit_set(p,m) ((p) |= (m))
#define bit_clear(p,m) ((p) &= ~(m))
//#define POWPIN_HIGH PORTC.PINC4=1
//#define POWPIN_HIGH bit_set(PORTC, PINC4);
#define POWPIN_HIGH PORTC|=0b00010000
#define POWPIN_LOW bit_clear(PORTC, PINC4);
#define DIG1ON PORTB=0xE //Switch ON the first digit, current display
#define DIG2ON PORTB=0xD //Switch ON the second digit, current display
#define DIG3ON PORTB=0xB //Switch ON the third digit, current display
#define DIGITSOFF PORTB=0xF //Switch OFF all digits, current display
#define DIG1VON PORTB=0xFB //Switch ON the first digit, voltage display
#define DIG2VON PORTB=0xF7 //Switch ON the second digit, voltage display
#define DIG3VON PORTB=0xEF //Switch ON the third digit, voltage display
#define DIGITSVOFF PORTB=0xFF //Switch OFF all digits, current display
#define SEGPORT PORTD // the LED display digits are on PORTD
// define the two rotary encoder input pins
#define PHASE_A (PINC & 1<<PC5)
#define PHASE_B (PINC & 1<<PC6)
#define ADCcomplete (ADCSRA & (1<<ADSC)) //TRUE if ADC is complete
//#define ADCcomplete (ADCSRA & 1<<ADIE) //TRUE if ADC is complete
#define setADCIE (ADCSRA|=1<<ADIE)
//global variables
unsigned char DIGIT_NUM;
int SET_VOLTAGE; //the set output voltage in mV
int SET_CURRENT; //the set output current limit mA
int READ_VOLTAGE; //the measured output voltage in mV
int READ_CURRENT; //the measured current output in mA
int READ_VOLTAGE_ARRAY[40]; //array for averages
int READ_CURRENT_ARRAY[40]; //array for averages
int READ_VOLTAGE_AVERAGE; //the measured output voltage in mV
int READ_CURRENT_AVERAGE; //the measured current output in mA
unsigned char digit; //the number of the currently displayed LED digit
unsigned char CCmode; //TRUE if in Constant Curent mode
int VoltageSETcount; //counter for temp display of the set voltage as the knob is turned.
int SETbuttonCount; //counter for the time the SET button has been pressed
int DPblinkCount; //counter for the constant current mode decimal point blink mode
char blink;
const unsigned char digitdata[11]={0x3F,0x6,0x5B,0x4F,0x66,0x6D,0x7D,0x7,0x7F,0x6F,0}; // 7 segment digit display data
volatile int8_t enc_delta; // -128 ... 127
static int8_t last;
static int8_t knobcount;
//*****************************************************************
//saves the current voltage and current figures to EEPROM
void SaveEEPROM(void)
{
cli(); //disable interrupts so it doesn't corrupt the write process
// eeprom_write_word(0,SET_VOLTAGE);
// eeprom_write_word(2,SET_CURRENT);
sei(); //enable interrupts again
return;
}
//*****************************************************************
//loads the stored voltage and current data from the EEPROM
void LoadEEPROM(void)
{
cli();
// SET_VOLTAGE=eeprom_read_word(0);
// SET_CURRENT=eeprom_read_word(2);
sei();
return;
}
//*****************************************************************
//displays a number on the selected 7seg display
// If DP is TRUE the associated Decimal Point turns ON
void Disp7SEG(unsigned char digit, unsigned char num, unsigned char DP)
{
unsigned char temp;
DIGITSOFF; //disable the entire LED display
DIGITSVOFF;
// if ((CCmode!=0) && (digit<4)) DP=1; //force decimal point on for current display in CCmode
if (num>9) num=10; // if unknown digit then display a blank digit
temp=digitdata[num];
if (DP!=0) temp=temp|128; // turn on decimal point if requested
SEGPORT=temp; //output the display segments
switch(digit)
{
case 1: DIG1ON; break;
case 2: DIG2ON; break;
case 3: DIG3ON; break;
case 4: DIG1VON; break;
case 5: DIG2VON; break;
case 6: DIG3VON; break;
}
//leave the digit displayed until the next call, so we can continue processing and we get persistence of vision.
return;
}
//*****************************************************************
// Updates the voltage and current PWM outputs
void UpdateOutput(void)
{
unsigned long PWMvalue;
PWMvalue=((unsigned long)SET_VOLTAGE*1000)/47695; //(12210mV full scale / 256 bits)
if (PWMvalue>255) PWMvalue=255; //overflow trap, we don't want any undesirable wrap
OCR1A=PWMvalue; //set the output VOLTAGE
PWMvalue=((unsigned long)SET_CURRENT*1000)/1078; //0-255 = 0-275mV/
if (PWMvalue>255) PWMvalue=255; //overflow trap, we don't want any undesirable wrap
OCR1B=PWMvalue; //set the output CURRENT
return;
}
//*****************************************************************
// reads the voltage and current values from the ADC and updates the global variables
void ReadValues(void)
{
unsigned long value;
unsigned long total;
unsigned char c,temp;
if (CURRbutton)
{
//measure VOLTAGE
ADMUX=0b00000001; //select ADC1 input and 1.1V reference, right adjusted
value=ADCL; //get the upper 8 bit result
value=value+(ADCH*256); //get the lower 8 bit result and add it on.
value=value*1074; //convert to microvolts for 1.1V reference
value=value/1000; //convert back to millivolts
READ_VOLTAGE=value*12; //multiply for the resistor divider
for(c=0;c<39;c++) //update the moving average array
READ_VOLTAGE_ARRAY[c]=READ_VOLTAGE_ARRAY[c+1];
READ_VOLTAGE_ARRAY[39]=READ_VOLTAGE;
total=0;
for(c=0;c<40;c++)
total=total+READ_VOLTAGE_ARRAY[c];
READ_VOLTAGE_AVERAGE=total/40;
}
else
{
//measure CURRENT
total=0;
ADMUX=0; //select ADC0 input and 1.1V reference, right adjusted
value=ADCL; //get the upper 2 bit result
value=value+(ADCH*256); //get the lower 8 bit result and add it on.
value=value*10740; //convert to microvolts for 1.1V reference
value=value/10000; //convert back to mV (/1000)
//the final value is now in mA*10
READ_CURRENT=value;
for(c=0;c<39;c++) //update the moving average value array
READ_CURRENT_ARRAY[c]=READ_CURRENT_ARRAY[c+1];
READ_CURRENT_ARRAY[39]=READ_CURRENT;
total=0;
for(c=0;c<40;c++)
total=total+READ_CURRENT_ARRAY[c];
READ_CURRENT_AVERAGE=total/40;
}
return;
}
//*****************************************************************
// Checks to see if the user has turned the rotary encoder knob
void CheckKnob(void)
{
if (enc_delta==1)
{
knobcount++;
VoltageSETcount=0;
}
if (enc_delta==-1)
{
knobcount--;
VoltageSETcount=0;
}
enc_delta=0;
if (knobcount>3)
{
if (CURRbutton) //adjust the VOLTAGE
{
SET_VOLTAGE=SET_VOLTAGE-50;
if (SET_VOLTAGE<0) SET_VOLTAGE=0;
}
else //adjust the CURRENT
{
SET_CURRENT=SET_CURRENT-1;
if (SET_CURRENT<0) SET_CURRENT=0;
}
UpdateOutput(); //update the output voltage/current on the terminals
knobcount=0;
}
if (knobcount<-3)
{
if (CURRbutton) //adjust the VOLTAGE
{
SET_VOLTAGE=SET_VOLTAGE+50;
if (SET_VOLTAGE>12200) SET_VOLTAGE=12200;
}
else //adjust the CURRENT
{
SET_CURRENT=SET_CURRENT+1;
if (SET_CURRENT>300) SET_CURRENT=300;
}
UpdateOutput(); //update the output voltage/current on the terminals
knobcount=0;
}
}
//*****************************************************************
// Interrupt Service Routine
// This ISR has several functions. It is based on TIMER0
// 1) Updates the 7 segment display at regular intervals
// (This display only one digit every interrupt, digits get cycled through each ISR pass)
// 2) Reads and updates the status of the rotary encoder knob
// 3) Ticks over a counter to hold the display in a set state for a certain period
ISR(TIMER0_OVF_vect)
{
unsigned int temp;
unsigned char dig1, dig2, dig3, dig4, dig5, dig6, dp1, dp2, dp3;
int8_t state, diff, CURRset;
cli(); //disable interrupts
VoltageSETcount++;
if (VoltageSETcount>=500) VoltageSETcount=500;
if (!SETbutton) SETbuttonCount++; else SETbuttonCount=0;
if (CCmode)
{
if (blink) DPblinkCount++; else DPblinkCount--;
if (DPblinkCount>100) blink=FALSE;
if (DPblinkCount<=0) blink=TRUE;
}
dp1=0; //turn all decimal points OFF
dp2=0;
dp3=0;
CURRset=0; //assume we are displaying the READ current and not the SET current
if (digit==4||digit==5||digit==6) //display the VOLATGE display digits
{
if (SETbutton) temp=READ_VOLTAGE_AVERAGE; else temp=SET_VOLTAGE;
if (VoltageSETcount<500) //force display of the SET voltage or current if the knob has been turned within the time limit
{
if (CURRbutton) temp=SET_VOLTAGE;
else
{
temp=SET_CURRENT;
CURRset=TRUE;
}
}
if (temp>9999) //check for voltages 10V or greater, we have to switch the decimal point and calcs
{
dig4=temp/10000;
temp=temp-(dig4*10000);
dig5=temp/1000;
temp=temp-(dig5*1000);
dig6=temp/100;
dp2=TRUE; //decimal point for 99.9
}
else
{
dig4=temp/1000;
temp=temp-(dig4*1000);
dig5=temp/100;
temp=temp-(dig5*100);
dig6=temp/10;
dp1=TRUE;
} //decimal point for 9.99
}
else // ELSE display the current display digits
{
if (SETbutton) temp=READ_CURRENT_AVERAGE; else temp=SET_CURRENT;
if (!CURRset && SETbutton) dp2=TRUE; //turn on decimal point for 99.9mA display if we are displaying the READ current
//otherwise display the SET current with 1mA resolution as 999mA
if (temp>=999) //if current is >99.9mA
{
dig1=temp/1000;
temp=temp-(dig1*1000);
dig2=temp/100;
temp=temp-(dig2*100);
dig3=temp/10;
}
else
{
dig1=temp/100;
temp=temp-(dig1*100);
dig2=temp/10;
temp=temp-(dig2*10);
dig3=temp;
}
if (CCmode) //if in constant current mode then blink the decimal point
{
if (blink)
{
dp1=FALSE;
dp2=FALSE;
dp3=FALSE;
}
}
}
//display the currently selected digit
//this digit will stay on until the next ISR loop, giving persistence of vision
if (digit==1) Disp7SEG(1,dig1,dp1);
if (digit==2) Disp7SEG(2,dig2,dp2);
if (digit==3) Disp7SEG(3,dig3,dp3);
if (digit==4) Disp7SEG(4,dig4,dp1);
if (digit==5) Disp7SEG(5,dig5,dp2);
if (digit==6) Disp7SEG(6,dig6,dp3);
digit++; //display the next digit next time through the ISR loop
if (digit>6) digit=1;
//read the rotary encoder knob
state = 0;
if( PHASE_A ) state = 3;
if( PHASE_B ) state ^= 1; // convert gray to binary
diff = last - state; // difference value
if( diff & 1){ // bit 0 = value (1)
last = state; // store new as next last
enc_delta += (diff & 2) - 1; // result will be +1 for positive direction and -1 for negative direction
}
ReadValues(); //read the ADC values
sei(); //enable interrupts again
}
//*****************************************************************
int main(void){
unsigned char c,d,average;
unsigned char keyspeed, keycount;
DDRB =0b00000111; //PB0, PB1, PB2, PB3, PB4 are OUTPUTS, all others INPUTS
PORTB=0b11111000; //enable pull-ups on all PORTB pins except PB1 and PB2 which are outputs
DDRC =0b00111000; //PORTC are all INPUTS, except for 3 LED digit outputs
PORTC=0b11000111; //enable pull-ups on only upper 3 pins PORTC pins
DDRD =0b11111110; //PORTD are all OUTPUTS
PORTD=0;
MCUCR=0; //Enable pin pull-ups and Disable Sleep BOD stuff.
POWPIN_HIGH; //Latch the power ON
//PORTC=0xFF;
// this must be done BEFORE the PWM is turned on to ensure we DON'T OVERSHOOT at power on!
LoadEEPROM(); //restore the saved voltage and current settings
if (SET_VOLTAGE>12200) SET_VOLTAGE=12200; //do some out of bounds checking
if (SET_VOLTAGE<50) SET_VOLTAGE=50;
if (SET_CURRENT>100) SET_CURRENT=100;
if (SET_CURRENT<1) SET_CURRENT=1;
//set up TIMER0 used for the display updating (165Hz rate per digit)
TCCR0A=0x3; //use the /64 prescaler for clock source
TIFR0=0;
TCNT0=0x0;
TIMSK0=1; //enable the TIMER0 interrupt
//set up PWM in TIMER1
TCCR1A=0b10100001; //clear on compare match, SET at TOP for both PWM output pins. 8bit FAST PWM mode.
TCCR1B=0b00001001; //matchig 8bit fast PWM mode setting
TCNT1=0x3FE;
UpdateOutput(); //set the output voltage and current
//set up ADC
PRR=0; //make sure all periphers are turned ON
ADCSRB=0; //free running mode, manual trigger
DIDR0=3; //diable digital input on ADC0 and ADC1 pins
ADCSRA |= (1 << ADATE);
// ADMUX |= (1 << ADLAR); //left align ADC result
ADCSRA |= (1 << ADEN); //enable the ADC
ADCSRA |= (1 << ADSC); //start conversion
ADCSRA |= (1 << ADPS2); //prescaler = 128
ADCSRA |= (1 << ADPS1); //prescaler = 128
// ADCSRA |= (1 << ADPS0); //prescaler = 128
//set up the user control knob interrupt
// PCMSK1=32; //enable knob pin change interrupt
// PCICR=2; //ebale the PCIE1 pin change interrupt
Disp7SEG(3,version,0); //display version number at power on
_delay_ms(2000);
digit=1;
average=0;
keyspeed=0;
keycount=0;
VoltageSETcount=0;
SETbuttonCount=0;
DPblinkCount=0;
ReadValues();
sei(); // enable interrupts (turns on display and functions)
while(TRUE)
{
if ((SET_CURRENT*10) < (READ_CURRENT_AVERAGE+1)) CCmode=TRUE; else CCmode=FALSE; //check to see if we are in Constant Current Mode
CheckKnob(); //check if the user has turned the knob
if (SETbuttonCount>=1500) //check to see if the SET button has been pressed for 3 seconds
{
SETbuttonCount=0;
SaveEEPROM();
cli();
Disp7SEG(1,5,0);
_delay_ms(1000);
sei();
}
}
}