Software

Github: Alle source-files van het PC-programma zijn vanaf nu beschikbaar op Github, zie ebrew_PC

Op de hardware / elektronica pagina heb je kunnen zien, dat er een behoorlijke hoeveelheid elektronica ontwikkeld is om de brouwinstallatie te automatiseren. Maar deze hardware is nutteloos zonder bijbehorende software, die een en ander controleert en bestuurt. Toen ik startte met het ontwerp van mijn systeem, was flexibiliteit een belangrijke voorwaarde. Ik kon een ontwerp maken op basis van een microcontroller, waarin ik embedded software plaatste, zodat je geen PC nodig had. Maar omdat ik toch verschillende PCs in mijn huis heb, is het wel net zo makkelijk om een PC te gebruiken voor het besturen van je brouwerij. Op deze manier is het makkelijk om nieuwe functionaliteit toe te voegen. Als ik een nieuwe functie wil toevoegen aan de besturing, dan maak ik een update van het bestaande programma, ik test het en daarna installeer ik het op de PC in de brouwerij (via het netwerk).

Deze opzet heeft vele jaren succesvol gewerkt. De aangesloten hardware was via de parallelle poort aangesloten. Met de overgang naar nieuwe hardware werd ook dit bijgewerkt naar een (inmiddels gebruikelijke) USB verbinding. En dan wordt toch een ontwerp m.b.v. een microcontroller weer interessant. In de nieuwe opzet, die hier beschreven is, is dit dan ook gerealiseerd. Veel oude functionaliteit, bijvoorbeeld het opwekken van een PWM signaal, is standaard aanwezig in zo'n microcontroller. Dus kon ook de hoeveelheid hardware componenten verminderd worden. Aan de ene kant heb je dus het PC-programma dat via een USB kabel communiceert met een Arduino Nano met een ATmega328 microcontroller, waar ook weer software op draait. Die laatste noemen we echter firmware, om een onderscheid te maken met de PC-software.

Welke programmeertaal moet er gebruikt worden voor beide systemen? Voor de firmware is dat duidelijk: ik maak gebruik van een Arduino Nano, die ik via Atmel Studio in de programmeertaal C aanstuur. In Embedded land is C de standaard en zijn zelfs vrijwel geen andere talen voorhanden. Voor het PC-programma is dat minder duidelijk door het grote aanbod op PC gebied. Ik ben niet echt een voorstander van moderne programmeertalen zoals Java en C#/.Net (ik heb nog nooit MS programmeer omgevingen gebruikt en dat was ik nu ook maar niet van plan!). Normaliter schrijf ik mijn programma's in the programmeertaal C, maar voor een programma onder Windows heb ik een programmeertaal nodig waarmee ik ook redelijk makkelijk een front-end GUI (windows, menus, muis etcetera) kan maken. Op zich is de taal C daar minder goed geschikt voor. De oplossing lag echter voor de hand: gebruik de programmeertaal C++ (object georiënteerd). Hiermee kun je ook je bestaande routines uit de taal C gebruiken en tegelijkertijd toch een mooi Windows programma maken. En als je nu ook nog eens Borland's meer dan uitstekende C++ Builder gebruikt, dan is het programmeren hiervan echt een feest om te doen!

Oke, de configuratie is gekozen, de programmeertaal en omgeving zijn ook geselecteerd. Wat kunnen we nog meer doen? Welnu, ik zou wat kunnen vertellen over de interne structuur van mijn brouwprogramma (die ik e-brew! gedoopt heb, hetgeen staat voor elektronisch brouwen). Deze pagina bestaat dan ook uit de volgende paragrafen:

1. Overview / Software-Architectuur

Het PC-programma bevat een aantal belangrijke componenten die gezamenlijk een geheel volgen. Omdat de taal C++ object georiënteerd is, is het logisch om een aantal objecten tegen te komen. Daarnaast zijn er echter ook een aantal componenten die in de taal C geschreven zijn. Maar deze kunnen relatief gemakkelijk vanuit een C++ object worden aangeroepen. De belangrijkste componenten zijn:

Het PC-programma verzorgt met name allerlei mogelijke instellingen van het brouwprogramma (tijden, temperaturen, parameters) en verstuurt op vaste tijdstippen commando's naar de hardware via een standaard communicatieprotocol. De reacties van de hardware worden weer op het scherm zichtbaar gemaakt. Een overzicht van alle bestanden en schermen ziet er als volgt uit:

Alleen al het hoofdobject (heet TMAinForm()) bevat al ongeveer 1600 Lines of Code (LOC). De totale PC-applicatie omvat ongeveer 4400 regels C++ code.

Naast het PC-programma is er uiteraard nog een tweede belangrijke deelcomponent in het totale systeem en dat is de firmware die op de ATmega328 van de Arduino Nano draait. Ook dit softwaresysteem omvat weer de nodige regels code: bij R1.28 waren dit ongeveer 3400 regels C code. Alles bij elkaar dus ruim 7700 regels code!

Een volledig functionele versie van het PC-programma kun je hier downloaden (.zip file, v1.83). Voor een goede werking heb je uiteraard ook nog de hardware met de juiste firmware nodig.

Terug naar boven

2. Scheduler

De e-brew! applicatie vraagt strakke timingseisen en er moet veel gebeuren op vaste tijdstippen. Er moeten commando's naar de hardware gestuurd worden, er worden temperaturen en volumes ingelezen, het scherm moet bijgewerkt worden, etcetera. Het zou heel mooi zijn om de beschikking te hebben over een real-time embedded operating systeem (RTOS genoemd), maar Windows komt bij zoiets nog niet eens in de buurt. Om de 100 milliseconden iets uitvoeren, wordt al erg lastig. Gelukkig zijn temperaturen geen snel veranderende grootheden en hoeft er niet zo snel geregeld en gestuurd te gaan worden. Maar er is wel iets nodig om ervoor te zorgen dat alle verschillende taken ook aan bod komen. Hiervoor is een aparte scheduler geschreven. Een scheduler is software die ervoor zorgt dat de verschillende taken in een software programma op het juiste tijdstip en met de juiste frequentie uitgevoerd worden. De gebruikte scheduler is non pre-emptive, hetgeen wil zeggen dat als een taak gestart is, deze niet meer onderbroken kan worden door een andere taak. Een volgende taak kan pas starten als de huidige taak is afgelopen. Voor ons brouwsysteem is dit voldoende en met wat aandacht voor de duur van een taak levert dit geen problemen op.

Taken zijn op deze manier makkelijk toe te voegen, om dit te illustreren onderstaande stukje C code (zoals dit ook in het PC-programma aanwezig is):


	//-----------------------------------------
	// Now add all the tasks for the scheduler
	//-----------------------------------------
	add_task(task_alive_led     , "alive_led"    ,   0,  500); /* 0, 500, 1000, 1500 */
    add_task(task_read_temps    , "read_temps"   , 100, 2000);
	add_task(task_pid_ctrl      , "pid_control"  , 300, (uint16_t)(pid_pars_hlt.ts * 1000));
    add_task(task_update_std    , "update_std"   , 400, 1000); /* 400, 1400 */
    add_task(task_hw_debug      , "hw_debug"     , 600, 2000);
    add_task(task_read_flows    , "read_flows"   ,1100, 2000);
    add_task(task_log_file      , "wr_log_file"  ,1600, 5000);
    add_task(task_write_pars    , "write_pars"   ,1700, 5000);
	

Het toevoegen van taken is op deze manier eenvoudig en overzichtelijk. De taak task_update_std() voert bijvoorbeeld iedere seconde (1000 msec.) het toestandsdiagram uit. Je kunt ook nog een offset meegeven: in het geval van task_update_std() start deze op tijdstip 0.4 (400 msec.) en daarna weer op tijdstip 1.4, 2.4, 3.4 etcetera. Bij de PID regelaar, task_pid_ctrl(), is iets bijzonders aan de hand: de aanroepfrequentie is afhankelijk van de parameter Ts, die in seconden weergegeven is. Als in het PC-programma deze tijd bijvoorbeeld op 20 seconden ingesteld staat, dan zorgt deze aanroep ervoor dat de PID regelaar iedere 20 seconden aangeroepen wordt. Het belangrijkste is nu nog om ervoor te zorgen dat de taken niet teveel tijd in beslag nemen. De scheduler meet daarom ook de tijdsduur en maximale tijdsduur van iedere taak, dat kan er als volgt uitzien (in een log-file):


	Task-Name      T(ms) Stat T(ms) M(ms)
-------------------------------------
alive_led 500 0x02 0.4 0.5 read_temps 2000 0x02 51.3 51.6 pid_control 20000 0x02 0.0 0.0 update_std 1000 0x02 1.2 1.3 hw_debug 2000 0x03 0.0 0.0 read_flows 2000 0x02 49.9 49.9 wr_log_file 5000 0x02 0.9 1.0 write_pars 5000 0x02 0.0 0.0

Uit dit overzicht blijkt dat task_update_std() maximaal 1.3 msec. geduurd heeft. Samen met het hierboven gedefinieerde overzicht levert dit een goed inzicht op in de diverse taken op. Foutzoeken is op deze manier eenvoudig te doen. De scheduler zelf wordt vanuit een OnTimer() routine van een timer aangeroepen, dit gebeurt iedere 100 milliseconden. Belangrijkste functie van deze routine is het bijhouden van alle timers en het inschakelen en uitschakelen van taken. De feitelijke aanroep van de taken gebeurt hier niet in!

Terug naar boven

3. Toestandsdiagram

Volgens de free online dictionary of Computing is een toestandsdiagram (state transition diagram, of STD) "een diagram die uit cirkels bestaat, die toestanden voorstellen. Ook bestaat zo'n diagram uit gerichte pijlen tussen die toestanden. Deze stellen de overgangen voor. Een of meerdere acties (outputs) kunnen gekoppeld worden aan zo'n overgang.". Kijkend naar de e-brew! applicatie, dan is een STD een centrale besturingsfunctie, die de verschillende fasen van het brouwproces coördineert. Een toestand kan zoiets zijn als 'maisch rust' of 'voorverwarmen'. Een overgang van de ene toestand naar de andere toestand wordt uitgevoerd wanneer de bijbehorende conditie WAAR is (bijv. de 'temperatuur is ok'). Condities zijn in het toestandsdiagram (zie plaatje hieronder) met ROOD aangegeven. Wanneer een overgang naar een andere toestand uitgevoerd wordt, dan kan er ook een bijbehorende actie uitgevoerd worden. (bijv. 'zet pomp aan'). Acties zijn met GROEN aangegeven. Samengevat: er zijn toestanden, overgangen tussen die toestanden, condities en acties. Het toestandsdiagram behorende bij het totale brouwproces bestaat uit 19 unieke toestanden, waarvan een aantal toestanden cyclisch doorlopen kunnen worden. Het ziet er als volgt uit:

Al met al een complex toestandsdiagram, die de totale besturing voor zijn rekening neemt. Een korte uitleg per toestand is dan ook wel handig om hier te geven:

De tabel (rechter bovenhoek) laat zien welke kleppen in welke toestand open (1) of gesloten (0) zijn, maar ook of de pomp aan (1) of uit (1) is. Door op deze manier je ontwerp te maken kun je het gedrag van de verschillende kleppen goed definiëren. Het is altijd duidelijk in welke toestand een bepaalde klep open of gesloten is. Er zijn nog heel wat meer details te vertellen over dit toestandsdiagram, maar het bovenstaande verhaal is hopelijk voldoende om een beeld te geven van de werking. Voor de volledigheid wordt nog opgemerkt dat het toestandsdiagram 1 keer per seconde uitgevoerd wordt.

Terug naar boven

4. Grafische User Interface (GUI)

De scheduler met alle taken en het toestandsdiagram vormen het hart en ziel van het brouwprogramma. Maar om dit flexibel aan te kunnen sturen en te controleren, is het van belang om een goede gebruikersinterface (Graphical User Interface of GUI) te realiseren. Dat is inmiddels gebeurd en deze paragraaf laat een aantal zaken daarvan zien. Zo zie je hier een screenshot van het hoofdscherm van het PC programma:

Er is het nodige op te zien, wat ideaal is wanneer je aan het brouwen bent en je snel even bepaalde waarden wilt zien of controleren:

Als een van de ingelezen waarden vanuit de brouw-hardware niet goed is, of er is geen communicatie, dan kleuren de bijbehorende waarden rood. In het screenshot hierboven wordt de MLT temperatuur en de waarden van de beide flowsensoren correct ingelezen.

4.1 De menu-balk van het e-brew! programma

De menu-balk van het brouwprogramma bevat de volgende menu items:

Terug naar boven

5. Firmware voor de Arduino Nano / ATmega328

De firmware omvat de C code voor de ATmega328 microcontroller en staat dus qua opzet geheel los van het PC-programma dat hiervoor beschreven is. Beide programma's communiceren echter wel met elkaar, via de virtuele COM poort van de USB verbinding. Het grote voordeel van het gebruik van een microcontroller is dat allerlei hardware gerelateerde en vaak tijdkritische zaken niet meer door PC-Windows afgehandeld hoeven te worden. Een microcontroller is hier veel beter voor geschikt. Aan de microcontroller kant wordt dankbaar gebruik gemaakt van de Arduino hardware opzet. Deze Arduino omgeving zal dan ook als eerste geïnstalleerd moeten worden. Hiermee worden de juiste drivers aan de PC kant geïnstalleerd. Aan de microcontroller kan is het een ander verhaal. Gelukkig hoeven we hier niet direct met USB routines aan de slag (complex!), maar kunnen we ook hier gebruik maken van standaard routines voor seriële communicatie. De microcontroller bevat een component die USART heet (Universal Synchronous/Asynchronous Receiver/Transmitter) en hiermee kan, met de juiste routines, seriële communicatie gerealiseerd worden. De gebruikte instellingen zijn 38400,N,8,1, dat wil zeggen: 38400 Baud,geen pariteitbit, 8 databits en 1 stopbit. Dit is uiteraard identiek aan de instellingen in het PC-programma.

Terug naar de overall opzet van de software voor de microcontroller. Ik maak geen gebruik van Arduino sketches, omdat dit te beperkt is voor mijn doeleinden. Van sommige sketches heb ik C libraries gemaakt, anderen heb ik zelf geschreven. Zoals gebruikelijk bij een C project, wordt van bijna iedere component een C file gemaakt met een bijbehorende header file (.h). Tezamen omvatten deze alle routines voor zo'n component. Voorbeeld: de I2C module (met files i2c.c en i2c.h) omvat alle routines voor de I2C communicatie, inclusief specifieke routines voor de gebruikte ICs, zoals de PCA9544, de MCP23017 en de DS2482. De totale opzet (architectuur) ziet er hiermee als volgt uit:

Low-level routines:Zoals in het plaatje te zien is, zijn er libraries voor I2C, SPI, ADC, PWM, One-Wire en Usart. In feite dus voor alle low-level hardware van de microcontroller zelf. Om de werking hiervan goed te begrijpen, zul je toch echt in de datasheet van de ATmega328P microcontroller moeten duiken. Zeker als je aanpassingen hierop wilt maken. Gelukkig zijn er veel goede voorbeelden op het Internet te vinden, het is namelijk ook een van de meest gebruikte microcontrollers.

Interrupt routines:De interrupt routines zijn tijd-kritische stukken code die niet kunnen wachten totdat er tijd voor ze is. Het is dus functionaliteit die direct uitgevoerd moet worden. Voor de ontwerper is het dus van belang om deze routines zo klein mogelijk te houden: je kunt niet even gaan wachten totdat er iets gaat gebeuren. Een mooi voorbeeld hiervan zijn de flowsensoren die aangesloten zitten op poortpinnen van de microcontroller. Als er een puls langs komt van zo'n flowsensor, dan wordt er nu een interrupt gegenereerd die niet veel meer doet dan een teller met 1 verhogen. Alle pulsen van de vier flowsensoren worden in zo'n interrupt afgehandeld. Hiernaast is nog een interrupt gedefinieerd voor de scheduler die iedere milliseconde aangeroepen moet worden. Als laatste zijn er nog twee interrupts actief voor de seriële communicatie. Als de USART hardware van de microcontroller een byte ontvangen heeft, dan wordt een interrupt gegenereerd. In deze interrupt wordt dat byte opgeslagen in een ringbuffer. Iets vergelijkbaars gebeurt er wanneer er data in een ringbuffer aanwezig is om verstuurd te worden.

Naast deze low-level libraries is er ook nog een gemengde library, waar enkele functies inzitten die te maken hebben met het filteren van de ingelezen signalen. Zo is er een moving-average filter (een laagdoorlaatfilter) gedefinieerd en een hellingsbegrenzer functie. Verder een ringbuffer routine voor de seriële communicatie en een RS232 command handler. Ieder commando van de seriële poort wordt door deze routine afgehandeld.

Ethernet/Wiz550io: de brouw-hardware is al voorbereid op de toevoeging van een WIZ550io Ethernet module. De C-routines hiervoor zijn afgeleid van de Arduino sketches (die erg rommelig in elkaar zaten). Met deze toevoeging wordt het voor de brouw-hardware mogelijk om via UDP te werken, in plaats van via de USB verbinding. Dit is echter nog geen standaard onderdeel van de huidige firmware, maar work-in-progress. Het toevoegen van zo'n library legt namelijk een flinke claim op de resources van de microcontroller (met name Flash geheugen en RAM geheugen).

De scheduler van het PC-programma zien we ook hier weer terug. Mooi staaltje van hergebruik van code op verschillende platforms.

Terug naar boven