Electronics > Projects, Designs, and Technical Stuff
uSupply Firmware Released
EEVblog:
--- Quote from: ataradov on May 26, 2020, 09:52:07 pm ---And yes, this C++ heavy code is very hard to read, and the whole functionality could be implemented in a much-much smaller code.
--- End quote ---
Not a comparison because the features aren't even remotely the same, but for those interested, previous versions of my uSupply written by me are 389 lines and 479 lines for two of the design variants.
That's everything, including comments, UI code, control code etc.
Not sure I'd like to guess how many lines this version of the uSupply would have taken me in "Dave C", but yes, it would been vastly different to the way David writes his code. I just let him do it the way he wanted, we have vastly different styles. But he writes games engines in his spare time for fun, and I wouldn't know C++ if it bit me on the arse.
EEVblog:
For kicks, this is "Dave C" for my last uSupply USB prototype, the entire program:
--- Code: ---/* ****************************************
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();
}
}
}
--- End code ---
uer166:
Even in the tiny forum window I can figure out what's going on.. This must be hardware vs. software engineering thinking ways.
Dave:
As soon as software becomes slightly more complex, you need to start splitting it in logical units, which you put into separate files. A well written software should not have files that stretch for more than a couple of hundred lines. There are of course always exceptions to this rule, but in general, one shouldn't have to scroll around and search for a particular part of code.
Next thing is abstraction. Just tossing register names around all over the place is very poor form. At the very least they should be macroed away with descriptive names.
Preferably, hardware abstraction should be done in a separate module altogether, so you never actually fiddle with registers directly in the application portion. By making code modular, porting code to a new platform becomes very easy. Want to use a new microcontroller? No problem, just replace/modify the hardware abstraction module and you're set.
Good code usually doesn't even need comments, because the names of the functions and variables themselves are descriptive enough to make it clear what something is doing.
nuclearcat:
--- Quote from: NivagSwerdna on May 26, 2020, 08:59:47 pm ---
--- Quote from: nuclearcat on May 26, 2020, 06:46:31 pm ---I think when device is released and if it will be affordable, we might see competition who can (IF CAN!) write better code :) (and hopefully giving away to Dave)
--- End quote ---
Probably but I've never really understood working for free, seems like a poor business model. ;)
--- End quote ---
You can gain a lot from writing free code.
)Community recognition. When you contribute to opensource, your potential employer will probably get an positive impression about you even before he looks at your CV.
In some cases, if you are contractor - you may be hired, since such portfolio is the best portfolio.
)Honing your skills and you might get free beta-testing and free review by professionals, including (probably) Dave. It is unlikely that you will have enough money to make Dave interested to touch and look to your proprietary code otherwise.
)Competition. Its FUN to be best!
And not only about gaining something. Altruism is good for humanity. Starting from the fact that the forum(software running it, OS, etc) we are talking in - exists, because many programmers once gifted the world pieces of their code.
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version