voltage divider program - helps find best ratio

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
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:

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

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
Wow, there's something about posting things publicly that makes me immediately realize the mistakes I've made... I just discovered a flaw in my system. It will work well and deliver accurate results in *most* scenarios, but I've just found a set of edge cases where it can miss good combos. I'll update when I've got an updated version.

Oh, well. In the meantime, all the results it currently outputs are legitimate - it's just that in some scenarios there might be better results which aren't reported.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
Ok, here's an update which should do much better. Still room for improvement, but I've eliminated at least one gaping hole in the original plan.

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)
    int iScaler = 1;                                //  used to scale resistor values for 2nd pass high/low
    int jScaler = 1;                                //  used to scale resistor values for 2nd pass high/low

    //  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
            }
        }
    }

    //  if target ratio is too close to bottom or top of range, we need to check adjacent ranges of
    //  resistor values above or below the range we've already checked. This code uses iScaler or jScaler
    //  to shift the available ratios up or down and recheck adjacent resistor ranges.

    //  choose a scaler to expand our range in the right direction
    if(ratioTarg<5){
        iScaler=10;
    } else {
        jScaler=10;
    }

    //  recheck resistor values with new scaling factor
    for(i=0; i<numResistors[series]; i++){
        for(j=0; j<numResistors[series]; j++){
            tempResult.rBottom=resAll[series][i]*iScaler;                       //  get bottom resistor value * scaler
            tempResult.rTop=resAll[series][j]*jScaler;                          //  get top resistor value * scaler
            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

crutschow

Joined Mar 14, 2008
34,408
I had the same problem an number of years ago, so I wrote a program in Visual Basic for Windows to perform a similar function.
A screenshot of the window is below:
It can calculate the optimum resistor pair for a given Ratio, Attenuation, or Non-Inverting Gain for an op amp.
It only works with 1% standard resistor values.
In the "Display Best" mode it only displays values that are better than the previous best calculation during the iteration.
Unfortunately I no longer have the source code for it.

upload_2019-2-4_21-20-31.png
 

WBahn

Joined Mar 31, 2012
30,045
Good problem to give you something real to write a program for -- always the best way to learn programming.

One of the first programs I wrote for my HP-41CX was a similar program where I would enter a desired ratio and in would walk (okay, crawl) through the standard values and report the best match.

The alternative I took a few years after that while in grad school was to set up a spreadsheet table that calculated the ratio of all the possible combinations. Later, I used conditional formatting to take the value I had entered into a cell and shade the cells green if they were less than half the indicated tolerance and yellow if they were between half the tolerance and the tolerance, and red if they were between the tolerance and twice the tolerance. Everything else was left white.

That made it very handy to spot good combinations real fast. It was also very versatile because all I needed to do was determine the desired ratio of two resistors, which you can often do for many problems far more complex than a simple voltage divider (which was my original motivation, too).
 

AlbertHall

Joined Jun 4, 2014
12,346
There is also the technique of making the desired resistor value by using a series or parallel combination of standard values. There's another program waiting to be written.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
There is also the technique of making the desired resistor value by using a series or parallel combination of standard values. There's another program waiting to be written.
I'll probably write those too. It'll be good practice for my nascent programming skills!
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
Good problem to give you something real to write a program for -- always the best way to learn programming.

One of the first programs I wrote for my HP-41CX was a similar program where I would enter a desired ratio and in would walk (okay, crawl) through the standard values and report the best match.

The alternative I took a few years after that while in grad school was to set up a spreadsheet table that calculated the ratio of all the possible combinations. Later, I used conditional formatting to take the value I had entered into a cell and shade the cells green if they were less than half the indicated tolerance and yellow if they were between half the tolerance and the tolerance, and red if they were between the tolerance and twice the tolerance. Everything else was left white.

That made it very handy to spot good combinations real fast. It was also very versatile because all I needed to do was determine the desired ratio of two resistors, which you can often do for many problems far more complex than a simple voltage divider (which was my original motivation, too).
I played around with a spreadsheet version before writing this program, but found it was getting obnoxiously large due to all the orders of magnitude in resistor values required for more extreme ratios. Of course, I ended up having to tackle that same problem in the C version, so I could probably go back and apply the same solution in my spreadsheet now...
 

Tonyr1084

Joined Sep 24, 2015
7,894
In Excel, I use "Solver" to go through all the iterations. At first go-through I'll assign the primary voltage and the desired output and let solver do its thing. I get some pretty weird numbers but then I can choose a standard resistor close to one of the solver values and run it again, using the one hard value as a fixed reference point. Solver then looks at the final matching resistor and chooses which resistors (from a table) best match the need. If no reasonable values come up I can use a combination of resistors to give me a hard value to work from and then let solver again calculate an answer. It doesn't give me a solution the first time out, usually it may take about five attempts to come up with a good answer, but it only takes a few minutes. And given that I don't do much with dividers I seldom use the program. In fact, right now I don't know which computer it's on or what file it's in - or even what I named it.

I worked with a woman who used to make fun of my lack of hair on top of my head. I told her 'grass doesn't grow on a busy street.' She replied "It doesn't grow on concrete either!" I can be a block head.
 

AnalogKid

Joined Aug 1, 2013
11,036
I had the same problem a number of years ago,
So did I, a larger number of years ago. I started with QB (QuickBasic) because VB hadn't been invented yet, and stuck with it because it was so fast to modify for a new design. I expanded the core code to have all standard resistor, capacitor, and inductor values, and dropped that core into programs for 555's, 317's, window comparators, filters, thermal problems, power supplies, etc. Fast and stupid, a typical compiled EXE was 30-40 ***K***b. Luv me some QB.

ak
 

MrChips

Joined Oct 2, 2009
30,794
And going one step further, you can take three resistors and come up with an infinite number of combinations for a voltage divider with any amount of tolerance desired.
 

bogosort

Joined Sep 24, 2011
696
Wow, there's something about posting things publicly that makes me immediately realize the mistakes I've made.
Funny how that works. A lot of programmers use it as a productive mental hack: whether you're in the design phase or writing code, imagine that you will be soon showing your results to a group of experts. It's surprising how that little mindset change can improve your work.

Some notes on your code: you're over-commenting. Code should be as self-commenting as possible, which is accomplished by using good variable names, clear logic flow, and standard idioms. Your program does this well, so there's no need to provide a running commentary for each line. Comments should point out non-obvious things; otherwise they are noise. Consider that a developer reading unfamiliar code will always stop to read any comments, as the expectation is that the original programmer thought something was important enough to comment on. But reading comments requires a mental context-switch. A comment like "int i; // for loop variable" doesn't add any new information, but it does break the reader's flow.

Also, you're under-commenting your functions. Someone not familiar with your program (e.g., you in a year) won't remember what each function argument represents. If each function is preceded by a comment explaining what it does, what arguments it takes, and what it returns, then this is not a problem. But as it stands, an unfamiliar reader has to go back to the function call to figure out what's actually happening.

Finally, best practice in C is to affix the pointer '*' character to the variable to which it applies, e.g., "struct result *ptr" instead of "struct result * ptr" (notice how the latter case can look like a multiplication at first glance). The goal should always be to reduce the mental effort required for a human to parse your code. Affixing the '*' to the variable can also prevent subtle bugs, such as writing "int * ptr1, ptr2" intending to declare two pointers to int.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
Funny how that works. A lot of programmers use it as a productive mental hack: whether you're in the design phase or writing code, imagine that you will be soon showing your results to a group of experts. It's surprising how that little mindset change can improve your work.

Some notes on your code: you're over-commenting. Code should be as self-commenting as possible, which is accomplished by using good variable names, clear logic flow, and standard idioms. Your program does this well, so there's no need to provide a running commentary for each line. Comments should point out non-obvious things; otherwise they are noise. Consider that a developer reading unfamiliar code will always stop to read any comments, as the expectation is that the original programmer thought something was important enough to comment on. But reading comments requires a mental context-switch. A comment like "int i; // for loop variable" doesn't add any new information, but it does break the reader's flow.

Also, you're under-commenting your functions. Someone not familiar with your program (e.g., you in a year) won't remember what each function argument represents. If each function is preceded by a comment explaining what it does, what arguments it takes, and what it returns, then this is not a problem. But as it stands, an unfamiliar reader has to go back to the function call to figure out what's actually happening.

Finally, best practice in C is to affix the pointer '*' character to the variable to which it applies, e.g., "struct result *ptr" instead of "struct result * ptr" (notice how the latter case can look like a multiplication at first glance). The goal should always be to reduce the mental effort required for a human to parse your code. Affixing the '*' to the variable can also prevent subtle bugs, such as writing "int * ptr1, ptr2" intending to declare two pointers to int.
Thanks for all the tips. Stuff like this is super-helpful, and it'll be much easier to develop good habits early than break bad habits later!

I know I'm heavy on the commenting, but as a total beginner, you'd be surprised how many things I benefit from little reminders on (although I'll admit that describing i or j as loop iteration variables is overkill by any standard.) I'll try to be more thoughtful about over-commenting. Hadn't really considered the downsides of that until you brought it up.

Cheers!
 

WBahn

Joined Mar 31, 2012
30,045
I played around with a spreadsheet version before writing this program, but found it was getting obnoxiously large due to all the orders of magnitude in resistor values required for more extreme ratios. Of course, I ended up having to tackle that same problem in the C version, so I could probably go back and apply the same solution in my spreadsheet now...
You want to work with just the standard values over one decade (say 10 to 99).

You then normalize the ratios to something between 10 and 99 (or between 1 and 9.9 or something similar).

If I want a ratio of 143.9, you normalize that to 14.39. You then find the closest value to that and then adjust the order of magnitude accordingly.

The normalization and denormalization are easy enough to do by hand, but they can be built automatically into either a spreadsheet or a program.
 

Thread Starter

ebeowulf17

Joined Aug 12, 2014
3,307
You want to work with just the standard values over one decade (say 10 to 99).

You then normalize the ratios to something between 10 and 99 (or between 1 and 9.9 or something similar).

If I want a ratio of 143.9, you normalize that to 14.39. You then find the closest value to that and then adjust the order of magnitude accordingly.

The normalization and denormalization are easy enough to do by hand, but they can be built automatically into either a spreadsheet or a program.
Exactly the approach I took in the C program - just hadn't thought of it when I was working on the spreadsheet!

It's reassuring to know I'm on the right track, and also funny realizing that this thing I'm doing has been done so many times before!
 

WBahn

Joined Mar 31, 2012
30,045
And going one step further, you can take three resistors and come up with an infinite number of combinations for a voltage divider with any amount of tolerance desired.
Since there are a finite number of resistor values and a finite number of ways to built a voltage divider with three resistors, there are only a finite number of ways to do a voltage divider and there will always be resulting gabs where you can specify a target value and tolerance that can't be satisfied.
 

joeyd999

Joined Jun 6, 2011
5,283
The normalization and denormalization are easy enough to do by hand, but they can be built automatically into either a spreadsheet or a program.
I did it by hand-generating a library of values (see attached text file).

The nice thing about that is I could tailor my library to parts that were available to me -- as opposed to all possible parts.
 

Attachments

WBahn

Joined Mar 31, 2012
30,045
Funny how that works. A lot of programmers use it as a productive mental hack: whether you're in the design phase or writing code, imagine that you will be soon showing your results to a group of experts. It's surprising how that little mindset change can improve your work.
I've often used my dog as an audience for working out problems (even if she doesn't happen to be in the house or even when I didn't happen to have a dog at the time). Explaining things to someone else, even a stupid, invisible, non-existent someone else, forces you to organize your thoughts and pay attention to details that you unconsciously glossed over a bit to cavalierly on your own.
I did it by hand-generating a library of values (see attached text file).

The nice thing about that is I could tailor my library to parts that were available to me -- as opposed to all possible parts.
I did something similar even with my HP-41CX program. Instead of putting the standard values in numerical order, I put them in the order that I preferred to stock them. Then I could enter a parameter that told the program to only go that far into the table and report the best two choices. After I wrote those down and pressed a key, it would search the rest of the table and find the best pair overall and report that. Then I could decide if it represented enough of an improvement to justify getting values I didn't stock. I don't recall it ever doing so.
 

wayneh

Joined Sep 9, 2010
17,498
I've often used my dog as an audience for working out problems ...
That has a place in the design phase, but later you need testing by a fool before you can address making something foolproof. The dog isn't foolish enough. A human fool is a very tricky thing to anticipate.
 
Top