Step-Up converter in software

Wie hier eerder iets gelezen heeft zal niet verrast zijn als ik zeg dat ik iets heb met nixiebuisjes, neonlampjes en switched-mode step-up converters (boostconverters). Naast andere dingen, maar dit wordt een technische post.

In mijn (Xantus kit) nixieklok zit een boostconverter rondom een NE555. Maar, er zit ook een microcontroller in die de klok bestuurt. Een boostconverter kan ook gemaakt worden met de ADC en 1 van de PWM kanalen van een microcontroller. Behalve voor nixieklokken en andere toepassingen met nixiebuisjes of neonlampjes is zo’n boost converter voor tal van andere dingen bruikbaar. Er is in software controle over de uitgangsspanning. Bijvoorbeeld in mijn jongleerkubussen pas ik een dergelijke step-up toe als leddriver, daarbij wordt de uitgangsstroom gereguleerd en gestuurd om verschillende LED kleuren te mengen. (ipv een spanningsdeler een shunt in de terugkoppeling, zie schema).

Een schema van zo’n step-up voor een nixieklok ziet er ongeveer zo uit:

Even in verband met die 250V: Ik ga er van uit dat wanneer je dit kunt nabouwen, je ook het benul hebt om van spanningvoerende delen af te blijven. (Uiteraard kun je ook hogere of lagere spanningen maken, waar zo nodig ook afgebleven moet worden).

De microcontroller is uit dit schema weggelaten, elk type met een ADC ingang en een PWM uitgang is geschikt. Ik gebruik een totempole driver om de gate van de IRF740 aan te sturen, omdat de microcontroller waar ik mee test op 3,3 V loopt. Om de FET goed open te sturen is een hogere gatespanning nodig. (De gate Threshold van een IRF740 is minimaal 2 V en maximaal 4 V.)

Ik heb er mee gespeeld in STM32CubeMX met een STM32F030, maar dat geeft zo’n berg autogegenereerde code en commentaar, dat het idee beter over te brengen is met een stukje pseudocode. Hieronder eerst een omschrijving hoe het in CubeMX werkt en verderop pseudocode waarmee het idee beter uit te leggen is. Daarna nog een keertje op een ATmega328. Dat is hopelijk wel genoeg.

STM32F030 en STM32CubeMX

In de CubeMX omgeving kan TIM1 worden ingesteld op ‘output compare no output’ (screenshot) en de ADC worden ingesteld om te triggeren op ‘timer 1 trigger out event’ (screenshot). Zo triggert TIM1 steeds een ADC conversie zodat de ADC op een vaste samplerate sampled. Timer3 wordt gebruikt voor PWM output (en eveneens ingesteld in de configuratortool).

De timers worden geklokt uit 24 MHz, TIM3 telt tot 240 zodat 100 kHz PWM gedaan wordt. TIM1 telt tot 2400 zodat de ADC op 10 kHz triggert. Vervolgens worden in main de ADC en de timers gestart met:

  HAL_ADC_Start_IT(&hadc);    // start ADC so it can be triggered by timer
  HAL_TIM_Base_Start(&htim1); // start the timer that triggers the ADC to convert
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // start the timer for the PWM output

Vervolgens kan in de interruptcode de ADC interrupt worden ingesteld om de PWM-timer aan te sturen: (En te clampen op een maximale dutycycle van ergens tussen de 50% en de 75% om te voorkomen dat de spoel verzadigd)

void ADC1_IRQHandler(void)
{
  /* USER CODE BEGIN ADC1_IRQn 0 */
    uint32_t raw_adc_input;
    int errorvalue;
    if(LL_ADC_IsActiveFlag_EOC(ADC1)){
        raw_adc_input = HAL_ADC_GetValue(&hadc);
	errorvalue = 1241 - raw_adc_input; // err = soll - ist, using 1V reference voltage
	if(errorvalue>0){
	    //TIM3_OC1A = ... via hal
	    if(errorvalue<168){ // clamp to 168/240= 70% ducy
	        __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,errorvalue);
	    } else
                __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,168);
	}else __HAL_TIM_SET_COMPARE(&htim3,TIM_CHANNEL_1,0);
        //HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // for debug, output half of ADC trigger frequency.
    }
  /* USER CODE END ADC1_IRQn 0 */
  HAL_ADC_IRQHandler(&hadc);
  /* USER CODE BEGIN ADC1_IRQn 1 */
  /* USER CODE END ADC1_IRQn 1 */
}

Een spanningsdeler deelt de gewenste uitgangsspanning af naar 1 V referentiespanning, aangeboden aan de ADC.

Platformonafhankelijke Pseudocode met uitleg

Om te beginnen heb je een PWM-uitgang en een ADC ingang nodig op een microcontroller naar smaak. De ADC moet op een vaste samplerate samplen, althans, het reguleren van de uitgang wordt onvoorspelbaar als de samplerate niet vast is. Op een STM32F0 kun je een timer rechtstreeks (hardwarematig) een ADC conversie(sessie) laten starten, op een andere MCU zit er mogelijk wat software tussen maar kan dat ook.

De pseudocode hieronder toont hoe de step-up werkt: Op een vaste samplerate neemt de ADC samples. Deze worden vergeleken met een ingesteld setpoint. Als de gewenste uitgangsspanning nog niet bereikt is, wordt er een PWM signaal uitgestuurd naar de FET welke de spoel schakelt. Als de uitgangsspanning wel bereikt is, wordt het PWM signaal 0 en de FET niet meer aangestuurd. Daartussen varieert het PWM signaal naar rato. Het PWM signaal wordt beperkt tot maximaal bijvoorbeeld 70% dutycycle, omdat bij 100% dutycycle de FET constant aan staat en dus de voeding kortsluit, en omdat de spoel kan verzadigen op een ‘te hoge’ dutycycle.

/* pseudo-code voor softwarematige step-up SMPS */
#define SETPOINT 512  // overeenkomend met b.v. 1 volt in ADC ticks

// ADC sampled op 10 kHz, getriggert door een timer (TIMER 1)
// steeds als een conversie gedaan is, loop onderstaande routine
// TIMER 2 is ingesteld om PWM uit te sturen
ADC_ISR(){
  int rawsample, dutycycle;
  rawsample = ADC_RESULT;            // lees conversie-resultaat
  dutycycle = SETPOINT - rawsample; 
  // naarmate het setpoint bereikt wordt, neemt dutycycle af
  
  if(dutycycle<0) dutycycle = 0;     // clamping tegen 0
  if(dutycycle>MAX) dutycycle = MAX; // clamping tegen maximum (b.v. 70%)
  TIMER2_PWM = dutycycle;            // stel dutycycle in
}

Er is dan een spanningsdeler die de uitgangsspanning afdeelt naar het ingestelde SETPOINT.

Met een Atmega328

Omdat MPLAB X toch fijner werkt dan STM32CubeMX ook nog maar een keertje in een Atmega328. Het setpoint van 128 komt hier overeen met 0,55 V: de interne 1.1 V ADC referentie wordt gebruikt.

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8e6
#include <util/delay.h>

int16_t setpoint = 128; //With 1.1V internal ADC reference and a 8 bit result, 128 means 0.55 V approximately for the output divider

void setup()
{
    /*GPIO*/
    DDRC = 0xBE;                  // PC0 input, PC6=reset input, rest ouput
    DDRB = 0x3F;                  // all output, except crystal input PB6,7
    DDRD = 0xF7;                  // all output, except PD3 (switch?)
 
    /* Timer 1, for PWM output and ADC samplerate (PWM /8) 
     * Has to be set to inverted output, because otherwise OCRA1 == 0 results in a small spike
     * on inverted, OCR1A == TOP == ICR1 means a constantly low signal and OCR1A = 0 a nearly constantly high signal
     */
    ICR1 = 80;                    // use 80 as top, so 8M/80=100 kHz PWM
    TCCR1A = 0b11000010;          // use OC1A output (inverted), fast PWM, ICR1 as top
    TCCR1B = 0b00011001;          // WGM mode fast PWM ICR1 as top, clock timer from clkIO unprescaled, start
    TIMSK1 = (1<<TOIE0);          // enable interrupt on overflow (at ICR1 value)
  
    /*ADC*/
    ADMUX = 0b11100000;           // set reference voltage to 1V1 internal, use ADC0 as input, left adjust result (so only higher 8 bits have to be read)
    ADCSRA = (1<<ADEN)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS0); // ADC enable, interrupt enable, clock source clkio/32 (8M/32 < 200kHz) 
    DIDR0 = 0x01;                 // disable digital input buffer on ADC0 input   
    sei();                        // enable interrupts
}

int main(void) {
    setup();
    while (1) {

    }
}

ISR(TIMER1_OVF_vect){
    static uint8_t i=0;
    i++;
    if(i>=7){ 
     ADCSRA |= (1<<ADSC); // start a new adc conversion every 8th OVF. OVF at 100 kHz, ADC max is 15ksps at max resolution
     i=0;
    }
    PIND = 0x01; // toggle PD0 for debug
}

ISR(ADC_vect){
    /* 
     * read adc result, and set new PWM value
     * because PWM is inverted to prevent the 1 cycle high output
     * 80 means output LOW, 0 means 100% dutycycle high.
     * 
     * If PWM were not inverted an OCR1A 0 would mean 0 output instead of
     *  a thin needle of 1 clk cycle, then this would be a bit simpler:
     * if error > 0 {if error<56 OCR=error }else ocr=56 else ocr = 0;
     */
    uint8_t adcresult = ADCH; 
    int16_t error = (setpoint - adcresult)*8; // P factor
    if(error>0){
        if(error < 57) OCR1A = 80-error; else OCR1A = 40;  // limit to 50% dutycycle (40) max
    }else OCR1A = 80;
}

En Wat Is Daar Het Praktisch Nut Van?

Ik hoop dat dit voor een mede-electronicus misschien iets zinnigs / nuttigs bevat, bijvoorbeeld als je een project met een microcontroller hebt waar ook een paar hogere spanningen nodig zijn, bijvoorbeeld iets met nixies of VFD-buisjes of iets om EEPROMS te wissen ofzo. (Of je zet er nog een lineaire regelaar achter als de regulatie wat preciezer moet zijn omdat je iets wilt met een e-ink display, bijvoorbeeld)

Maar ook: in een volgende blogpost pas ik een dergelijke boost converter toe, en wil ik graag dat er een belletje gaat rinkelen of een lampje gaat branden.


by

Tags:

Comments

2 responses to “Step-Up converter in software”

  1. […] de step-up uit de vorige post en wat verdere microcontroller-magie (sinus PWM-en) is het mogelijk een T65 telefoon te laten […]

  2. […] dit schema, gebaseerd op de smps uit vorige blogposts kun je AC maken en op die manier beide electroden van een neonindicatorlampje licht laten […]

Leave a Reply

Your email address will not be published. Required fields are marked *