Arduino Nano Oven-controller

Keramiek-oven controller met de Arduino nano
Concept-schema (uiteindelijke schema is hier en daar wat anders)

Zoals al gemeld op mijn algemene Arduino-pagina, mijn regelaar voor mijn keramiek-oven mocht wel eens een update gebruiken. Het was een schakeling met discrete logica en een draaiknop voor het energieniveau. In plaats daarvan is het nu meer als in mijn plaatje hiernaast, met een 4-cijferig display voor temperatuur (tot 1250 graden) en wat drukknopjes voor de invoer. De Arduino Nano zorgt voor de tijd, programma-stap en zo voort. Op die manier hoef ik de oven niet meer zelf in de gaten te houden, maar neemt de Nano het stappenplan (zoals in de grafiek onderaan de pagina over de oude controller) over. Dat was het idee; nu de uitvoering...

De eerste stap is het opzetten van voorlopige hardware om de software te kunnen maken. Een prototype-bordje, plus het display, een bordje met knopjes dat ik nog had liggen, en een LED in plaats van de uiteindelijke SSR (Solid State Relais) zodat ik kan zien wanneer en hoe hard de oven aan gaat staat. Dit is nog een versie zonder temperatuurmeting: eigenlijk de oude regeling maar dan met een automatisch afspelende programma's voor verschillende temperatuur-profielen. Zo moet de eerste keer bakken altijd heel geleidelijk gaan, de tweede bak met glazuur mag sneller (zie de oven-pagina).

De opbouw

Oven controller prototype
Prototype board met de eerste opzet
Display: "P=01", programma 1.

Display (€3 bij dx.com) is blijkbaar toch niet echt I2C-compatible... Dus toch maar op twee andere pinnen aansluiten. Gelukkig heeft 'avishorp' al een goede Arduino library voor de TM1637 geschreven: werkt in een keer, en in de zip download zit ook de English datasheet (niet alleen de Chinese versie)! Dankzij het 'character designer' spreadsheet van JosePino kan ik ook tekst makkelijk weergeven, niet alleen getallen.

Knopjes uitlezen is makkelijker, zitten direct op de I/O pinnen. Waar je wel op moet letten is de 'contact-dender'; schakelaars en drukknoppen kunnen met indrukken een paar keer 'stuiteren' en dus in korte termijn meerdere pulsjes geven. Ik los dit op door ze meerdere keren te lezen (met een kleine pauze er tussen), en pas een signaal te geven als de waarde stabiel is.

Eindresultaat van de ovencontroller
En zo ziet het ingebouwde resultaat er uit
(teruggeplaatst in het oude kastje).
Display: "E=10", energieniveau 10 van de 16.

Voor de veiligheid heb ik ook de watchdog-timer gebruikt: als de processor crasht (niet vanwege een softwarefout, maar door bijvoorbeeld spanningspieken tijdens het schakelen) schakelt de oven automatisch na twee seconden uit... Safety first!

Het geheel is vrij compact (eigenlijk alleen een nieuw frontje met 2 cm elektronica er achter, het prototype bordje zelf is uiteraard niet ingebouwd), en ik heb het weer in mijn oude kastje terug kunnen plaatsen, de voeding en het SSR is hergebruikt. Moest wel een nieuw frontje maken uit een stukje plexiglas dat ik nog had.

De 50 Hz timing

De 50Hz op INT0 is op het proto-board nog niet aangesloten, in plaats daarvan gebruik ik de TimerOne library om daarmee 50Hz te maken (maakt het testen makkelijker). In de uiteindelijke software test ik tijdens het opstarten of er van buitenaf 50 Hz binnenkomt (want gaat de interrupt-teller omhoog), zo niet dan maak ik zelf de 50 Hz met de timer; zie de voorbeeldcode hieronder. Onderstaande code komt uit mijn Arduino setup() functie, die bij het opstarten wordt aangeroepen.

// Externe interrupt op de INT0PIN met de 50Hz: de routine verhoogt 'clkToProcess'
  attachInterrupt(digitalPinToInterrupt(INT0PIN), myIntHandler, RISING);

  display.setSegments(str2seg(segBuf1, "boot"));
  delay(1000);                 // 1 seconde wachten (op display de tekst 'boot')

// Is er 50Hz gevonden (is de teller opgehoogt)? Zo niet: maak zelf 50 Hertz
  if(clkToProcess == 0)        // Geen interrupts gezien op INT0?
  {                            // Dan omschakelen naar de interne timer
    detachInterrupt(digitalPinToInterrupt(INT0PIN));
    display.setSegments(str2seg(segBuf1, "no50"));
    delay(1000);
    Timer1.initialize(20000);  // Zet de timer op 20000 microseconds = 50 Hz
    Timer1.attachInterrupt( myIntHandler ); // interrupt routine op deze timer
    no50Hz = true;             // Even onthouden dat we niet echt 50 Hz hebben
  }

In de interrupt routine zelf gebeurt overigens meer dan alleen het ophogen van de teller (hier niet weergegeven), zo worden ook de toetsen hier gelezen en verwerkt, en wordt het Solid State Relais aan- en uit-gezet. Maar in een interrupt-routine wil je in het algemeen zo min mogelijk doen, al is het hier bij de oven niet kritisch. Het 'grove' werk heb ik daarom nooit in de interrupt-routine, maar gewoon in het hoofdprogramma zitten.

De interrrupt-routine wordt elke 20 milliseconde (50 Hz) aangeroepen. Ik deel dat door 5 (met behulp van de clkTicks variabele), en elke 100 milliseconde verhoog ik de variabele clkToProcess. Die wordt in het hoofdprogramma gebruikt om te kijken of er weer wat moet gebeuren, en om de tijd bij te houden. Vooral clkToProcess moet als 'volatile' gedefinieerd worden, zodat de compiler weet dat deze terwijl het hoofdprogramma loopt zomaar in de interrupt-routine gewijzigd kan worden!

volatile uint8_t clkTicks = 0;
volatile uint8_t clkToProcess = 0;// openstaande process requests

void myIntHandler(void)        // in setup() aan de interrupt gekoppeld
{
    clkTicks++;                // at 10 Hz (every 5 ticks) update clkToProcess
    if(clkTicks == 5) { clkTicks = 0; clkToProcess++; }

    ... doe de SSR aan/uit-regeling ... // voor het voorbeeld verwijderd
    ... doe de toetsenbord-scan ...
}

void loop()                    // Arduino 'main()' functie
{
    if(clkToProcess)           // is er een 100 ms clock-tick geweest?
    {
      --clkToProcess;          // verlaag de teller (is er een afgehandeld)
      ... en verder ...

Eenvoudige setup: sequences

De eerste versie is heel eenvoudig: enkele van-te-voren ingeprogrammeerde reeksen zoals ik die eerst handmatig doorliep (zie de stookcurve op de oven-pagina). Heb er nu 5 in zitten:

  1. Rustig naar 300 °C (drogen): 2 uur op stand 5 om rustig op te warmen, 2 uur op 2 (op temperatuur houden).
  2. Biscuit stoken (950 °C): eerst rustig naar de 100 °C (vocht verdrijven: uur op stand 3), dan naar de 600 °C (kristalwater verdrijven), dan door naar de 950 °C.
  3. Normaal aardewerk stoken (1050 °C)
  4. ??? (1150 °C)
  5. Steengoed stoken (1250) °C

Hiervoor heb ik twee arrays, een met de schakeltijden (per kwartier) en een met de energie-waardes (0..16). Mogelijkheden genoeg om preciesere curves te maken, maar op het moment schakel ik per uur.

Gebruikers-interactie

De zaak draait met een state-machine, waarbij iedere state bepaalde acties kent. Er zijn in totaal 6 states zoals in de tabel weergegeven. Het display is maar 4 tekens, en dus beperkt... Bij keuze is het de huidige keuze; in de run-modes wisselt de programma- of energie-waarde af met de tijd.

State Beschrijving Display +/- toetsen OK/Next toetsen Back toets
stateIdle Rusttoestand, geen verwarming ---- Kies programma (prog/manual) Ga naar gekozen instellingen --
stateSetProg Keuze programma  300, 950.. Kies temperatuur-reeks Start en ga naar stateRunProg naar stateIdle
stateRunProg Draaien gekozen programma P=xx / time Energieniveau op/neer Naar handmatige bediening (stateRunManual) naar stateIdle
stateSetManual Instellen handbediening E=xx Energieniveau op/neer Start en ga naar stateRunManual naar stateIdle
stateRunManual Draaien onder handbediening E=xx / time Energieniveau op/neer ?? (nog geen aktie)
naar stateIdle
stateRunDone Programma klaar done Naar stateIdle Naar stateIdle naar stateIdle

Simpel, maar als eerste stap voldoende. Wel misschien de up/down in programma-run mode nog verbeteren, verandert nu de energiewaarde alleen tot de volgende programmastap. Moet iets slimmers mee kunnen (denk energieniveau op/neer, om de hele curve op en neer te verschuiven gedurende het gehele programma?).

Veiligheid eerst!

Een aantal veiligheidsmaatregelen:

  • Watchdog timer op 2 seconde om in geval van crash de oven uit te zetten (reset);
  • SSR zwaarder genomen dan nodig is (25 Ampère in plaats van 10 Ampère): Chinees spul haalt niet altijd de specs die vermeld zijn...;
  • En mechanisch natuurlijk zorgvuldig bouwen, komt in een soms vochtige omgeving: aarding, isolatie (scheiding 220V gedeelte van het laagspanningsgedeelte), en zo voort;
  • Nog te doen: NTC om de temperatuur van met name de SSR in de gaten te houden (al wel in schema en in de SW, nog niet in de hardware); en misschien een piëzo buzzertje, zoals in mijn airsense project

Zou voldoende moeten zijn.

En dan...

De volgende grote stap wordt (denk ik) het aansluiten van mijn thermo-koppel. Ik kan hier natuurlijk zelf een versterkertje voor maken (thermokoppel-signalen zijn te zwak om gelijk op de Arduino aan te sluiten, 41 µV per °C), maar ik denk dat ik maar ga voor de kant-en-klare Adafruit module met de MAX31856. Zit een voorversterker in, maar ook linearisatie, 50-Hertz filtering, koude-las compensatie, A/D converter en alles wat er voor een thermokoppel nodig is. Aansluiting via SPI. Ook in Nederland in diverse webshops verkrijgbaar, voor zo'n €20.

Nuttige Libraries etc