neon

In de vorige blogpost liet ik een belletje rinkelen, in deze ga ik een lampje laten branden. Een neonlampje, want ik houd van het oranje licht van neonlampjes en nixiebuizen. In veel nixieklokken zie ik neonlampjes als scheidingsteken tussen de uren en minuten, waarvan maar 1 elektroden licht geeft omdat het lampje op DC wordt gebruikt. Dat kan voor het lampje geen kwaad, maar ik vind het mooier wanneer beide elektroden oplichten. Dus in deze blogpost ga ik beide elektroden van een neonindicatorlampje laten oplichten.

(Chicago Miniature A1C neon indicator lampje)
(Chicago Miniature A1C neon indicator lampje)

De ingewikkelde manier

Met dit schema, gebaseerd op de smps uit vorige blogposts kun je AC maken en op die manier beide electroden van een neonindicatorlampje licht laten geven. (Eigenlijk hetzelfde trucje als het laten rinkelen van de telefoon, maar op een hogere frequentie zodat je het neonlampje niet ziet knipperen en de koppelcondensator kleiner kan.)

Maar dat is een beetje Rube Goldberg. Het werkt wel:

Het kan een stukje simpeler:

De eenvoudiger manier

Door beide elektroden van het neonlampje een weerstand naar + en een transistor naar – te geven, kan vrij eenvoudig in elke richting stroom door het lampje gestuurd worden:

Een volledige H-brug zou energiezuiniger zijn, maar dit is een eenvoudiger en compacter schakeling.

Dit maakt het mogelijk beide elektroden van een neonlampje afzonderlijk te sturen.

Hier een demofimpje van een looplicht / binaire teller. 4 bits, op 2 neonlampjes:

(Als de hieronder ingebedde video niet werkt kun je dezelfde video op youtube kijken)

De sourcecode staat hier, puur voor de compleetheid.

De achtergrond

Beide elektroden van een neon-indicatorlampje afzonderlijk aansturen geeft de mogelijkheid bijvoorbeeld een heel nerdy (binair) neonlampjes-horloge te maken. Er bestaan nixie-horloges, maar een neonlampjeshorloge heb ik nog niet gezien. Of een neonlampjesklok met 30 of 60 lampjes in een cirkel.

In plaats van een neonlampjes-looplicht of -klok zou je ook een wat exotisch type stappenmotor (inchworm piëzo motor) aan kunnen sturen. (Mogelijk wil je dan wel een volledige H-Brug. En een instelbare spanning voor precisie-positionering: iets met een dedicated SMPS-IC en een DAC om het setpoint in te stellen, niet een microcontroller die het naast andere taken doet).

En natuurlijk om beide elektroden licht te laten geven in de scheidingstekens van luxe nixieklokken. Er zijn een aantal makers van hele mooie luxe nixieklokken. Vaak voor bedragen waarvoor ik eerder een oscilloscoop zou kopen (en normale mensen een tweedehands auto). Maar kijken kan.

Dalibor Farny maakt zelfs de nixiebuizen zelf (Hij verteld er van alles over op youtube en op zijn site). Ik weet niet of de seperators in zijn klokken ook zelfgemaakt zijn, maar ik vind ze mooier dan een ‘standaard neonlampje’.

Andere makers, zoals nixieshop gebruiken een IN-3 (of dergelijke), net als mijn nixieklok.

Maar bijvoorbeeld elektor en retrotimes gebruiken ‘gewone’ (NE-2 achtige) neonindicatorlampjes. Omdat je bij deze klokken tegen de bovenkant van de neonindicator op kijkt zie je eigenlijk niet dat er maar 1 electrode licht geeft. Bij Millclock, toch hele mooie luxe klokken, zie je het wel.

(Geen van deze links is gesponsord, en ik heb ook met geen van deze bedrijven ervaring, ik vind het gewoon mooie klokken.)

Door ‘speciale’ neonlampjes (IN-3 en whatever Dalibor Farny gebruikt) als uren/minuten/seconden scheidingstekens te gebruiken, heb je niet dat er maar 1 electrode van een ‘standaard’ neonindicatorlampje oplicht.

Maar, je zou dat effect ook juist kunnen gebruiken: met 2 bits per neonlampje zou je extra informatie kunnen weergeven op de scheidingstekens van een nixieklok. Bijvoorbeeld de (buiten)temperatuur, binair weergegeven in 6 bits op 3 lampjes. Of, met wat meer lampjes, als lineair (bargraph) display van bijvoorbeeld de luchtvochtigheid.

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.