Low Power (< 1W), variable High Voltage power supply design

Thread Starter

mad33

Joined Apr 23, 2017
3
Hi,
I am working with Photon Counters, which require low current high voltage biasing. And I wish to design a power supply for its operation. My requirements are:
  • VIN = 5 V
  • VOUT = 50 - 80 V ; IOUT = 2 mA
So far I have found this Microchip pdf appnote, and this thread which is very close enough to my requirements. I am trying to build a similar design with an ATmega16 (I had one already) instead of the PIC. I have a couple MOSFETs (IRF7820s), MOSFET-drivers (TC4427), ultra-fast diodes, inductors and the other components as used in this linked thread. I am lost as to which PWM timer (and 8 or 16 bit) I need to use, and how to get the code below running to achieve my required high voltage (I get extreme fluctuations of 3-30V sometimes with my output of 40-80V).

C:
#----------------------------power supply code for atmega16---------------------------------------
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#define F_CPU 4000000UL

unsigned int adc1, adc2;
int fp;
float v_ref = 4.90;                               // Actual output value from the 7805 IC
float supply_ratio = 10.986;             // Supply ratio for supply adc reading
float osc_freq = 4;                             // Tried switching between 4 and 8
float L_Ipeak = 67;                           // Peak current value of Inductor = 0.67 A, but Inductor value = 100 (hence 67)
int fb_value = 130;                          // Calculated value of 130 should give about 80V, 65 gives ~40 V

float supply_multiplier, period_multiplier, v_feedback, v_supply;
float final_duty, final_period, t_rise, SMPS_duty, t_period, SMPS_period;

int read_adc(uint8_t ch)
{
    ADMUX = (1<<REFS0);
    ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);

    // select the corresponding channel 0~7
    // ANDing with '7' will always keep the value
    // of 'ch' between 0 and 7
    ch &= 0b00000111;  // AND operation with 7
    ADMUX = (ADMUX & 0xF8)|ch;     // clears the bottom 3 bits before ORing

    // start single conversion
    // write '1' to ADSC
    ADCSRA |= (1<<ADSC);

    // wait for conversion to complete
    // ADSC becomes '0' again
    // till then, run loop continuously
    while(ADCSRA & (1<<ADSC));

    return (ADC);
}

int upd_smps()
{
   adc1 = read_adc(0);
  
   v_feedback = adc1;
   v_supply = v_feedback/1024;
   supply_multiplier = v_ref*supply_ratio;
   v_supply = v_supply*supply_multiplier;        //v_supply is converted back from ADC count to actual value of ~5V
  
   period_multiplier=(4/osc_freq);
   t_rise = L_Ipeak/v_supply;
   SMPS_duty=t_rise*osc_freq;
   final_duty=SMPS_duty ;
   t_period=(t_rise*1.33);
   SMPS_period=(t_period/period_multiplier);
   final_period=SMPS_period-1.0;
   return final_period;
}

int main(void)
{

      DDRD = 0xff;                  
      TCCR1A = 0;                                            //----- I need -------------------------------
      TCCR1B = 0;               
      TCCR1A |= ( 1<<COM1B1 | 1<<COM1B0 | 1<<WGM11 );        //----- help ---------------------------------
      TCCR1B |= ( 1<<WGM12 |  1<<WGM13 |  1<<CS10);          //----- understanding ------------------------
      ICR1 = 40;
      while(1)
      {
           adc2 = read_adc(1);
           fp = upd_smps();
           if (adc2<fb_value)                                //----- this whole ---------------------------
           {      
               OCR1B = fp;                                   //----- part as I know -----------------------
           }
           else
           {
               OCR1B = ICR1;                                 //----- something is probably wrong ----------
           }
      }
}
I need help understanding the main() function in order to compare the adc read from the output voltage and my set value (fb_value = 130), but I am guessing the adc reading speed, the code running speed do not match and one of it lags, resulting in faulty readings and high fluctuations. I may have understood some PWM part wrong too creating this blunderous output.

Also if there are some easier designs out there using similar PWM with MOSFET based Power supplies using ATmega or other components please help me out.

Thanks!!
 

Attachments

BobaMosfet

Joined Jul 1, 2009
2,113
Your code is not right (you know this). Your ADC routine could be a little cleaner. The first part of main() is configuring a timer-- but you never turn interrupts on, so the timer doesn't run. And you've created no interrupt routine. I recognize your concept- set up a timer and read the ADC at regular intervals.

There are a number of things that make this appear 'cobbled'. Not being mean, just an observation. How much _help_ do you really need? I mean, is this just way out of your depth, or? There is no wrong answer, everybody starts somewhere. I'd just like to get a feel for how much lifting you need someone else to do.

Normally (the following is not entirely complete, but gives you a good conceptual look), when you set up a timer for something like this, you need to use the largest timer possible (like 16-bit in this case), and what you do is you set a prescaler so it uses the least numbers of clock cycles (which are still more than enough for your purpose) and then set your timer up to count at a rate you require. For example, 1,000/second = 1ms. Then every time the interrupt occurs, it calls your code to read the ADC and put that value somewhere you can look at it, and then it exits. Then in your main loop you sit there compare that value against your check value, and make a decision on it.

:)
 

Thread Starter

mad33

Joined Apr 23, 2017
3
Hey thanks for the in depth break up of the code. I started out at extremely basic stuff and now have some progress, with basic understanding about the timers and PWM operation. I have cleaned up the code, removed most of the junk (previously I had to cobble together the code from 3/4 different sources). Thing is I put this code in Proteus simulation and got around 0.2 - 0.4 V of fluctuations (I wish to bring it down to 0.010 V). I am sure there are better ways to implement an interrupt and ADC. (I haven't quite grasped the concept of combining the both in a nice manner, any help would be great)

Since I found out that internal RC oscillators aren't that accurate in AVRs I got a 16 MHz crystal, modified the FUSE bits (CKOPT:0 CKSEL:1111 SUT:11) and finally got the ATMEGA16 to work (I bricked it, the 1 MHz square wave to XTAL1 pin got it working again). So now since I needed a PWM period of 30.00 us using the avrcalc and the OCR calculation equation with prescaler=1, I implemented the solution below. Also since the ADC frequency will be around 125 kHz, I was guessing the code once implemented in the AVR wouldn't crash (but I see no activity). I cannot figure out why the code doesn't work on the AVR but does in simulation (broken MOSFET, drivers or components is a possibility, but haven't quite figured out which. Any design help; pointing towards a relevant appnote would be great help too).

#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

uint16_t adc1;
uint16_t fb_value = 86;

void adc_init()
{
ADMUX = (1<<REFS0);

ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

uint16_t read_adc(uint8_t ch)

{
ADMUX = (ADMUX & 0xF8)|ch;
ADCSRA |= (1<<ADSC);
while(ADCSRA & (1<<ADSC));
return (ADC);
}

int main(void)
{
adc_init();
DDRD = 0xff;

while(1)
{

adc1 = 0;
adc1 = read_adc(1);
if (adc1 > fb_value)
{
TCCR1A = 0;
TCCR1B = 0;
TCCR1A |= (0 << COM1B0);
}
else
{
TCCR1B |= (1 << WGM12);
TCCR1A |= (1 << COM1B0);
TCCR1B |= (1 << CS10);
OCR1A = 239;
}
}

}
 

BobaMosfet

Joined Jul 1, 2009
2,113
Hey thanks for the in depth break up of the code. I started out at extremely basic stuff and now have some progress, with basic understanding about the timers and PWM operation. I have cleaned up the code, removed most of the junk (previously I had to cobble together the code from 3/4 different sources). Thing is I put this code in Proteus simulation and got around 0.2 - 0.4 V of fluctuations (I wish to bring it down to 0.010 V). I am sure there are better ways to implement an interrupt and ADC. (I haven't quite grasped the concept of combining the both in a nice manner, any help would be great)

Since I found out that internal RC oscillators aren't that accurate in AVRs I got a 16 MHz crystal, modified the FUSE bits (CKOPT:0 CKSEL:1111 SUT:11) and finally got the ATMEGA16 to work (I bricked it, the 1 MHz square wave to XTAL1 pin got it working again). So now since I needed a PWM period of 30.00 us using the avrcalc and the OCR calculation equation with prescaler=1, I implemented the solution below. Also since the ADC frequency will be around 125 kHz, I was guessing the code once implemented in the AVR wouldn't crash (but I see no activity). I cannot figure out why the code doesn't work on the AVR but does in simulation (broken MOSFET, drivers or components is a possibility, but haven't quite figured out which. Any design help; pointing towards a relevant appnote would be great help too).

#ifndef F_CPU
#define F_CPU 16000000UL
#endif


#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>


uint16_t adc1;
uint16_t fb_value = 86;


void adc_init()
{
ADMUX = (1<<REFS0);
ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
}

uint16_t read_adc(uint8_t ch)
{
ADMUX = (ADMUX & 0xF8)|ch;
ADCSRA |= (1<<ADSC);
while(ADCSRA & (1<<ADSC));
return (ADC);
}


int main(void)
{
adc_init();
DDRD = 0xff;
while(1)
{
adc1 = 0;
adc1 = read_adc(1);
if (adc1 > fb_value)
{
TCCR1A = 0;
TCCR1B = 0;
TCCR1A |= (0 << COM1B0);
}
else
{
TCCR1B |= (1 << WGM12);
TCCR1A |= (1 << COM1B0);
TCCR1B |= (1 << CS10);
OCR1A = 239;
}
}

}
You're headed in the right direction, but there are a number of things you don't yet have your head around. For one thing, you have to actually tell the MCU whether a pin is for input or output. Including the ADC pin. So read and understand the pin control section in the datasheet. To be honest, there are a few things you want to fiddle with before you try to implement your existing concept, simply so you can work on a single problem at a time, and get each small thing to work before combining them into trying to do something else.

FPU clock: 16MHz. That means it has one clock cycle or heartbeat every 0.0000000625 of a second. If you want a pulse at 30us, you only have 30us / 62.5ns = 480 heartbeats between pulses. Which equates to about 20 or so assembly language instructions - which is likely not enough for your code to execute completely between pulses.

I see now that you're attempting to operate the gate of Q1 at 33kHz. That's why you're looking at 30us. Right?

Show me your equation for how you got this number, so I don't have to derive it. I may have a better solution using far less code, that this control can easily do for you.
 
Last edited:

Thread Starter

mad33

Joined Apr 23, 2017
3
I am calculating the 30 us time period from here. Putting my input voltage at 5 V and 100 uH with 0.67 A (about 1.3 W) I get around 13 us @ 50% duty cycle. For the inductor to operate in discontinuous mode there should be enough time for the current to reach zero from this app note. I gave it more than enough time just to be safe, hence the reason to operate the gate of Q1 at 33kHz.

It would be great to use a better solution for it. Although I hope the ADC instruction frequency can be reduced so that all the instructions cn get executed within the 30 us cycle.
 
Top