Hey everybody, I wrote myself a little program in C that I thought someone here might find useful (I've just taken some online courses and this is my first program from scratch in C.)
I've been annoyed several times when I wanted to use a voltage divider to deliver a reference voltage, but struggled to find the best resistor combo from standard values. Of course, for any given resistor choice, I can easily calculate what the other resistor would have to be in order to get the desired output... but often that second resistor isn't a standard value. So, depending on how accurate you want the output voltage to be, you try another first resistor choice and calculate its mate once again. Theoretically, this search can go on for a while.
So, my program simply tries every resistor combination and provides you a list of resistor combinations, sorted by accuracy of the resulting output. It asks for the input voltages at top and bottom of the voltage divider (and assumes zero output impedance from those sources, so it's rather crude in that sense,) as well as the desired output voltage. It also asks how many results you want to see (top 5 most accurate? top 15? top 99?) and whether you want to limit your search to E24 series values, E48 series, or a combination of both.
I've attached a screenshot below. The C code and an executable file are both in the attached ZIP file. Also, for those of you who don't trust unknown files from the internet, here's the code in a form you can simply copy and paste without downloading anything:
I've been annoyed several times when I wanted to use a voltage divider to deliver a reference voltage, but struggled to find the best resistor combo from standard values. Of course, for any given resistor choice, I can easily calculate what the other resistor would have to be in order to get the desired output... but often that second resistor isn't a standard value. So, depending on how accurate you want the output voltage to be, you try another first resistor choice and calculate its mate once again. Theoretically, this search can go on for a while.
So, my program simply tries every resistor combination and provides you a list of resistor combinations, sorted by accuracy of the resulting output. It asks for the input voltages at top and bottom of the voltage divider (and assumes zero output impedance from those sources, so it's rather crude in that sense,) as well as the desired output voltage. It also asks how many results you want to see (top 5 most accurate? top 15? top 99?) and whether you want to limit your search to E24 series values, E48 series, or a combination of both.
I've attached a screenshot below. The C code and an executable file are both in the attached ZIP file. Also, for those of you who don't trust unknown files from the internet, here's the code in a form you can simply copy and paste without downloading anything:
C:
// ***** Voltage Divider Resistor Selector *****
// ********** Eric Schaefer Jan. 2019 **********
// This program will provide a list of the best resistor
// combinations to achieve any given voltage divider ratio.
// This program is meant to help in one aspect of circuit design. Sometimes you need a voltage
// divider to deliver a very specific output voltage. Numerous resistor combinations will get
// close to the right result, but there's no easy, systematic way to quickly determine which
// combinations of resistors will provide the best possible output with standard value resistors.
// This program prompts the user for input and output voltage requirements, a number of list
// results to be printed (top 5 best combinations, top 10, etc,) and a series of resistors to
// work with (readily available E24, more expensive E48, or a combination of both.)
// Once it has the basic data it needs up front, it converts the voltage requirements into an
// ideal ratio between the two resistors. Any pair of resistors with that ideal ratio will
// provide the desired output. A perfect match is unlikely, but the program will seek resistor
// combos that get as close as possible to the ideal ratio. In order to avoid having to iterate
// through even more standard values, the ratio is then scaled up or down by factors of ten
// until it can be achieved with resistor values from 1-9.99 (once the list is complete, this
// multiples-of-ten correction will be applied to the output list as needed.
// In addition to being a potentially useful program I'd like to have, it was also an excuse to
// try out most of the new concepts I learned in my first C programming class through edX.
// Some of the new concepts I used are:
// - pointers
// - array of pointers (works like multi-dimensional array)
// - struct
// - bubble sort
// - malloc
// - ternary operator
#include <stdio.h>
#include <stdlib.h> // required for malloc, among other things
#include <math.h> // required for powers
struct result{ // this stuct represents potential solution sets: two resistor values and an accuracy/error level
int rTop; // root value of top resistor in hundreths of ohm (2.43k would be 243)
int rBottom; // root value of bottom resistor in hundreths
double errorAmt; // represents deviation from perfect result
};
const struct result defaultResult = {1, 1, 999}; // this instance is used to initialize other new instances
// These arrays hold the resistor values for E24 series, E48 series, and a combo of both with 3 duplicates removed:
const int resE24[24]={100,110,120,130,150,160,180,200,220,240,270,300,330,360,390,430,470,510,560,620,680,750,820,910};
const int resE48[48]={100,105,110,115,121,127,133,140,147,154,162,169,178,187,196,205,215,226,237,249,261,274,287,301,
316,332,348,365,383,402,422,442,464,487,511,536,562,590,619,649,681,715,750,787,825,866,909,953};
const int resCombo[69]={100,105,110,115,120,121,127,130,133,140,147,150,154,160,162,169,178,180,187,196,200,205,215,220,
226,237,240,249,261,270,274,287,300,301,316,330,332,348,360,365,383,390,402,422,430,442,464,470,
487,510,511,536,560,562,590,619,620,649,680,681,715,750,787,820,825,866,909,910,953};
const int * resAll[3]= {resE24, resE48, resCombo}; // array of pointers, effectively equals multi-dimensional array
const int numResistors[3]={24,48,69}; // number of resistors in each series option
// function prototypes:
void getInputs(int * numResults, int * series, double * vTop, double * vBot, double * vTarg);
void findResistorPairs(double ratioTarg, int series, int num, struct result * ptr);
double vCalc(double vTop, double vBot, double rTop, double rBot);
void printResults(int num, struct result * ptr, double vTop, double vBot, double vTarg, int tenPower);
struct result * initResults(int num);
void listResistors(int series, int num);
void getRatio(double top, double bottom, double target, double *ratio, int *power);
void printGreeting(void);
void printSchematic(void);
// ********************************************************************************************************************
// *************************************** Start Main Loop ************************************************************
// ********************************************************************************************************************
int main(void){
double voltageTop = 0; // input voltage at top of voltage divider
double voltageBottom = 0; // input voltage at bottom of voltage divider
double voltageTarget = 0; // desired output voltage
int numResults=10; // number of results to return (top 10, top 15, etc.)
int seriesSelector=-1; // used to choose E24 series resistors, E48 series, etc.
double ratioTarget=1; // required ratio between top & bottom resistors to deliver target voltage
int tenPower=0; // scaling factor (powers of 10) applied to ratio
struct result * bestResults; // pointer which will be base of array variable containing result structs
char prompt[10]="z"; // user input at end of main: quit or continue
while(prompt[0]!='q' && prompt[0]!='Q'){
printGreeting(); // displays greeting/header output
printSchematic(); // displays ASCII schematic representing voltage divider parts
printf("\n"); // add blank space
// Gather user inputs:
getInputs(&numResults, &seriesSelector, &voltageTop, &voltageBottom, &voltageTarget);
// Calculate required ratio and scaling factor between resistors
getRatio(voltageTop, voltageBottom, voltageTarget, &ratioTarget, &tenPower);
// allocate memory for results array, starting at address bestResults
bestResults = initResults(numResults);
// iterates through all resistor combos, saving the best ones in a list
findResistorPairs(ratioTarget, seriesSelector, numResults, bestResults);
// print out list of best resistor values, along with resulting output and accuracy scores
printResults(numResults, bestResults, voltageTop, voltageBottom, voltageTarget, tenPower);
free(bestResults); // free memory from earlier malloc function
// prompt user to either repeat or quit
printf("Enter 'q' to quit, or any other character to run another calculation: ");
scanf("%s",&prompt);
}
return 0;
}
// ********************************************************************************************************************
// *************************************** End Main Loop **************************************************************
// ********************************************************************************************************************
void getInputs(int * numResults, int * series, double * vTop, double * vBot, double * vTarg){
// this function prompts user for required inputs and includes some data validation
printf("Enter the number of results to return (1-99): ");
scanf("%d",numResults);
while(*numResults<1 || *numResults>99){
// creates a loop to keep prompting until a valid selection is made
printf("Please enter a value from 1 to 99 for number of results: ");
scanf("%d",numResults);
}
printf("Enter the resistor range to search:\n");
printf(" 0 for E24 series resistors only.\n");
printf(" 1 for E48 series resistors only.\n");
printf(" 2 for E24 and E48 series resistors. ");
scanf("%d",series);
while(*series<0 || *series>2){
// creates a loop to keep prompting until a valid selection is made
printf("Please enter a value from 0 to 2 for resistor series choice: ");
scanf("%d",series);
}
printf("Enter the \"top\" voltage: ");
scanf("%lf",vTop);
printf("Enter the \"bottom\" voltage: ");
scanf("%lf",vBot);
while(*vBot>=*vTop){
// creates a loop to keep prompting until a valid selection is made
printf("Please enter a bottom voltage *below* the top voltage: ");
scanf("%lf",vBot);
}
printf("Enter the \"target\" output voltage: ");
scanf("%lf",vTarg);
while(*vTarg<=*vBot || *vTarg>=*vTop){
// creates a loop to keep prompting until a valid selection is made
printf("Please enter target voltage *between* top and bottom voltages: ");
scanf("%lf",vTarg);
}
}
void addItem(struct result newResult, struct result * ptr, int num){
// this sub replaces last item in list with newResult then re-sorts list (one-pass bubble sort)
int i; // for loop iteration
struct result swapResult; // struct variable used for swapping during sort
ptr[num-1]=newResult; // copy newResult into last position in list
for(i=num-1; i>0; i--){ // loop through list once, swapping bottom up until new result finds its place
if(ptr[i].errorAmt<ptr[i-1].errorAmt){
swapResult = ptr[i-1];
ptr[i-1]=ptr[i];
ptr[i]=swapResult;
}
}
}
void findResistorPairs(double ratioTarg, int series, int num, struct result * ptr){
// loop through all resistor pairs and save copies of the best combination
int i, j; // for loop iteration
int loopCounter=0; // used for debugging (could probably lose this)
double ratioCurrent=1; // temporarily stores ratio of current resistor combo
struct result tempResult; // temporary storage for current resistors and accuracy
tempResult = defaultResult; // initialize tempResult with default values (helps sorting)
// these nested loops iterate through every resistor value combination in the chosen series
for(i=0; i<numResistors[series]; i++){
for(j=0; j<numResistors[series]; j++){
tempResult.rBottom=resAll[series][i]; // get bottom resistor value
tempResult.rTop=resAll[series][j]; // get top resistor value
ratioCurrent=(double)tempResult.rTop/(double)tempResult.rBottom; // calc ratio between resistors
tempResult.errorAmt=ratioCurrent-ratioTarg; // calc error between ratio and target ratio
if(tempResult.errorAmt<0) {tempResult.errorAmt*=-1;} // find absolute value of error (no negatives)
// if current error amount is lower (better) than last item in existing list add this to list:
if(tempResult.errorAmt<ptr[num-1].errorAmt){
addItem(tempResult, ptr, num);
loopCounter++; // counter used for debugging, counts how many times addItem() is called
}
}
}
}
double vCalc(double vTop, double vBot, double rTop, double rBot){
// calculates what the output voltage would be based on input voltage and resistor values
double vOut = vBot + (vTop-vBot)*rBot/(rTop+rBot);
return vOut;
}
void printResults(int num, struct result * ptr, double vTop, double vBot, double vTarg, int tenPower){
// prints out list of best resistor combinations
int i; // for loop iteration
double vOut; // calculated output voltage for any given resistor pair
double vErr; // calculated voltage error
double percError; // error as percentage of target voltage
double rTopFloat; // resistor value, top of divider
double rBotFloat; // resistor value, bottom of divider
printf("\n");
// loop through all items in list
for(i=0; i<num; i++){
rTopFloat = (double)ptr[i].rTop/100.0; // convert resistor value from int of hundreths to float
rBotFloat = (double)ptr[i].rBottom/100.0; // convert resistor value from int of hundreths to float
// applies previously determined scaling factor to one side of voltage divider
// chooses side such that it makes one side larger by factors of ten (never makes values smaller)
if(tenPower>0){
rTopFloat = rTopFloat * pow(10,tenPower);
} else {
rBotFloat = rBotFloat * pow(10,-tenPower);
}
vOut = vCalc(vTop, vBot, rTopFloat,rBotFloat); // calculate output voltage for this resistor combo
vErr = vOut-vTarg; // calc voltage error as a percentage of target voltage
percError = vTarg==0?999:vErr/vTarg*100; // calc percentage error; return 999 for division by 0
// print one-line output per result
printf("Result %s%d: R-top = %0.2lfk; R-bottom = %0.2lfk;",i<10?" ":"",i,rTopFloat,rBotFloat);
printf(" V-Out = %s%0.5lf; V-Error = %s%0.5lf",vOut<0?"":" ",vOut,vErr<0?"":" ",vErr);
// only include error percentage if there's a valid result (no div. by zero error)
if(percError<998) printf("; \%%Err = %s%0.5lf",percError<0?"":" ",percError);
printf("\n");
}
printf("\n");
}
struct result * initResults(int num){
// allocates memory for results list (array of structs) and returns pointer to its address
int i; // for loop iteration
struct result * ptr; // pointer to be returned at end of function
ptr = (struct result *)malloc(num * sizeof(struct result)); // allocate memory for array at address bestResults
for(i=0; i<num; i++){
ptr[i]=defaultResult;
// printf("Result %d: R1=%0.2lf, R2=%0.2lf, Error=%0.2lf\n",i,ptr[i].rTop/100.0,ptr[i].rBottom/100.0,ptr[i].errorAmt);
}
// printResults(num, ptr);
return ptr;
}
void listResistors(int series, int num){
// this was used in debugging to confirm list of resistors (incl. series choice) behaved properly
int i; // for loop iteration
for(i=0;i<num;i++){
printf("%0.2lf, ",(float)resAll[series][i]/100);
}
printf("\n");
}
void getRatio(double top, double bottom, double target, double *ratio, int *power){
// This first finds the necessary ratio between top and bottom resistors to achieve target voltage
// It then also multiplies/divides by powers of 10 until ratio is between 1 & 9.99
// This scaling of the ratio makes it possible to test "base" values of all resistors one
// time through without worrying about larger or smaller versions of them.
// For example, in E24 series, you check values from 1.00ohm to 9.10ohm, but you don't
// need to check 10 or 100 ohm, nor 0.91 or 91 ohm.
// calculate actual ratio:
*ratio = (top-bottom)/(target-bottom)-1; // initial ratio calculation
// now find the power of ten you would divide ratio by to get in range of 1-9.99
*power=0; // power value must start at zero
if(*ratio>=10){
while(*ratio>=10){
*ratio/=10;
(*power)++;
// printf("%lf, %d\n",*ratio,*power);
}
} else {
while(*ratio<1){
*ratio*=10;
(*power)--;
// printf("%lf, %d\n",*ratio,*power);
}
}
// Here is the basic formula and its (clumsy derivation)
// target = bottom + (top-bottom)*rBottom/(rTop+rBottom)
// define temporary variables to facilitate transformations:
// adjTop = top-bottom
// adjTarget = target-bottom
// basic voltage divider formula:
// adjTarget = adjTop*rBottom/(rTop+rBottom)
// solve for ratio of top/bottom resistors:
// adjTarget(rTop+rBottom) = adjTop*rBottom
// adjTarget*rTop + adjTarget*rBottom = adjTop*rBottom
// adjTarget*rTop/rBottom + adjTarget = adjTop
// adjTarget*rTop/rBottom = adjTop - adjTarget
// rTop/rBottom = (adjTop - adjTarget)/adjTarget
// rTop/rBottom = adjTop/adjTarget - 1
// substitute original terms back in:
// rTop/rBottom = (top-bottom)/(target-bottom)-1
}
void printGreeting(void){
printf("***** Voltage Divider Resistor Selector *****\n");
printf("********** Eric Schaefer Jan. 2019 **********\n\n");
printf("This program will provide a list of the best resistor\n");
printf("combinations to achieve any given voltage divider ratio.\n\n");
}
void printSchematic(void){
printf("Voltage Top -----+\n");
printf(" /\n");
printf(" \\ R-top\n");
printf(" /\n");
printf(" \\\n");
printf(" |\n");
printf(" +------ Voltage Out\n");
printf(" |\n");
printf(" /\n");
printf(" \\ R-bottom\n");
printf(" /\n");
printf(" \\\n");
printf("Voltage Bottom --+\n\n");
}
/*
Voltage Top -----+
/
\ R-top
/
\
|
+------ Voltage Out
|
/
\ R-bottom
/
\
Voltage Bottom --+
*/
Attachments
-
14.5 KB Views: 13
-
17.8 KB Views: 4
