Saintummers Projekte

Control Demo

Für einen Kollegen habe ich diese Steuerung entworfen, welche auf Tastendruck eine Pumpe startet. Der Durchfluss wird per Geber gezählt. Die Pumpe wird per Schwimmerschalter gestoppt, wenn der gewünschte Pegel erreicht ist. Während die Pumpe läuft wird Zeit und Durchfluss angezeigt. Überwachung des Programmes erfolgt via Watchdog. Weitere Überwachungen gegen Fehler durch falsche Sensordaten sind in dieser Demo nicht vorgesehen.

Demo einer einfachen Prozesssteuerung erstellt mit Arduino IDE Version 1.8.15 auf einem Arduino UNO
Interaktionen, von Sensoren und Schaltern werden ohne Verzögerung durchgeführt und am Display sofort angezeigt.
Verwendung von Hardware Interrupt und völliger Verzicht auf die Delay() Funktion.

Display 4x20
Zeile1: Programm Version
Zeile2: Gezählte Impulse
Zeile3: abgelaufene Zeit
Zeile4: Stati wie run, START und STOP


Controldemo.ino - Sketch oder auch Sourcecode dieses Projektes.

File listing not supported by your software, please use an up-to-date browser

Download Sketch Controldemo
Danke an die Arduinio Community welche ihr Wissen im Internet zugänglich machte und an dem ich partizipieren durfte.

Die Software in die Arduino IDE laden und compilieren, hochladen auf die Hardware.
Ausgabe beim Compilieren über den verbrauchten Speicher für Variablen.
 Sketch uses 5730 bytes (17%) of program storage space. Maximum is 32256 bytes. 
 Global variables use 538 bytes (26%) of dynamic memory, leaving 1510 bytes for local variables. Maximum is 2048 bytes. 

Notwendige Komponenten:
• PC zur Software Entwicklung mit USB
• Arduino UNO oder Clone
• Arduino UNO Pinout
• Arduino IDE für Installation am PC, nicht den Web Editor
• Arduino IDE Language Reference

Einsatz der millis() Funktion, damit keine delays verwendet werden müssen. Impuslzählung per Interrupt INT0.
Der UNO mit Atmega328 ist die ideale Hardware für die Entwicklung solcher Projekte.
USB Typ B und ein Hohlbuchse zur Stromversorgung sind hier verwendet. Die USB Buchse wird ja nur zur SW Entwicklung mit der Arduino IDE gebraucht.
Überlegungen zum Echtbetrieb.
Ich lehne Hardware mit USB Interface in eigenen Projekten ab, da hier ein Ablaufdatum - Treiberdproblematik - immer mit geliefert wird. Solche Projekte habe auch eine Einsatzdauer von mehr als 10 Jahren, da will man sich nicht mit Windows Befindlichkeiten herumschlagen. Vernünftige Schnittstellen I2C, RS232, LAN, WLAN ist sinnvoll, aber nicht USB. Zuverlässige Stromversorgung per Industrie Hutschienen Netzteil aber nicht per USB Ladegerät oder USB Steckernetzteil. Für den Echtbetrieb kann man dann ja eine eigene Platine machen lassen, wenn gewünscht. Für Atmega328 sehr leicht, bei moderneren CPUs, welche nur in SMD vorliegen, wird man eher die fertigen Platinen, in eigene Entwürfen integrieren.
Siehe auch dieses Projekt Wake on Lan mit IR Fernbedienung hier auf meiner Seite

Funktion:
• Als Sicherheitsmerkmal wird der AVR Watchdog verwendet.
• Anzeige auf LCD Display 4 Zeilen zu 20 Zeichen via I2C
• Heartbeat - Blinken per LED und blinkenden Punkt am Display
• Es wird ein Pumpenmotor mit Relais angesteuert.
• Die Durchflussmenge (Impulse von einem Flügelrad) werden per Interrupt Eingang gezählt.
• Die Zeit der Motor Einschaltdauer wird in Sekunden angezeigt.
• Der Motor wird mit START und STOP Taste kontrolliert. Auch Schwimmerkontakte oder Ultraschall Niveau Geber sind möglich.
• Debounce - Entprellung der Tasten

Arduino UNO Pinout:
Den Ausdruck in Folie einschweißen, das sollte immer zur Hand sein.
Hier sieht man die Beziehung zu Atmega328 Pin# - Arduino Pin# - Arduino Pin Name.
Ein Pin kann, je nach Funktion, mehrere Arduino Namen haben.
zB: Die Blink LED hängt hier beim UNO an Arduino Pin# 13, und das ist hier der Atmega Pin# 19
Der INT0 Eingang ist am UNO Pin# 2
Für eigene Print Designs ist der Atmega Pin relevant. Im Sketch (Sourcecode) ist die Arduino Pin und der Name relevant.
Arduino Uno Pinout
Danke an /commons.wikimedia.org/wiki/File:Arduino-uno-pinout.png für die Nutzung des unveränderten Bildes hier auf meiner Seite.


Schaltung:

Controldemo Schaltung
Die Schaltung ist einfach. Ich verwende grundsätzlich keine bunten Fritzing Bildchen, welches einen Arduino mit Breadboard und Bauteilen abbildet. Eine Schaltung kann man auch mit einem Bleistift auf Papier verfassen, und das wesentliche ist ausgedrückt. Was das Fritzing Zeug soll? Keine Ahnung - Schade um die Zeit.
Wichtig sind die Pin Nummern, welche mit den Deklarationen im Source Code übereinstimmen sollen;-)
Im Sketch (Source Code) sind nur die Arduino Pin Nummern relevant, wie sie im Arduino Pinout verwendet werden.

Software Beschreibung:
Zur Verwendung von Peripherie wie Display, Ein- und Ausgängen, etc. müssen diese entsprechend definiert, initialisiert und aufgerufen oder abgefragt werden.
Dafür sind drei Abschnitte vorgesehen. Deklaration, Setup und Loop.
In der Ardunio Programmier- Umgebung sind Setup und Loop vorgesehen. Als Deklarationsblock sind alle Zeilen oberhalb von Setup zu sehen.
Setup wird einmal bei Programmstart ausgeführt und beinhaltet im allgemeinen Initialisierungen.
Die Loop Schleife wird permanent durchlaufen und verwendet die Peripherie anhand der Logik im Programm Ablauf. Die eigentliche Ablauf ist auf verschiedene Funktionen aufgeteilt, damit der Loop (Hauptprogramm) übersichtlich bleibt. Es ist auch sinnvoll keine Delays im Programm einzubauen, da hier die CPU nur wartet, komplette Verarbeitung ist gestoppt. Für Programme, welche immer auf äußere Ereignisse reagieren sollen, ist die Verwendung der Funktion Delay() nicht vorteilhaft. Test-halber ein Delay(5000) mit 5 Sekunden im Loop und das Programm wird von Watchdog neu gestartet - Der Watchdog verhindert ein stehenbleiben des Programms


Millis():
Alle zeitlichen Bezüge in diesem Programm werden mit der millis()
Funktion gesteuert. Diese Funktion gibt die vergangenen Millisekunden seit Programmstart zurück. Der Datentype ist unsigned long (32bit ist Zahlenbereich 0 - 4,294,967,295) Alle 50 Tage in etwa erfolgt hier ein Überlauf dieses Zählers, was aber bei richtiger Verwendung dieser Funktion kein Problem darstellt.

Blinken
LED Blinken und blinkender Punkt am Display, zeigt an dass die Steuerung lauft.
in https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay
ist der Blinkvorgang ohne Delay beschrieben.
Hier wird eine Funktion blinker verwendet, welcher drei Argumente übergeben werden. Dauer in 100ms, Zeile und Postion.
Der Aufruf  blinker(10,0,19);    
ergibt eine Blinkdauer von 10*100ms (1Sekunde) und der Punkt auf Zeile 0, Position 19 Blinkt wie die LED.

Display:
Es wird ein LCD Display mit I2C Interface verwendet. Display mit Interface
Einbindung Display Library im Programm
Deklaration:
Alle Zeilen welche mit // beginnen sind nur Kommentar und können weggelassen werden.
//LCD                             
#include <LiquidCrystal_I2C.h>    
LiquidCrystal_I2C lcd(0x27,20,4); 

Setup:
lcd.init();     
lcd.backlight();
Setup: oder Loop: je nach Programmlogik
lcd.setCursor(0,3);
lcd.print("hello world");

Der Library für das LCD wird als Information die I2C Adresse, Anzahl Zeichen und Anzahl Zeilen übergeben.
Lt. Datenblatt des Interfaces sind alle Lötbrücken für die Adressen im Anlieferungszustand offen - ergibt Adresse 27 lt. Datenblatt.
Mit dem blauen Poti wird der Kontrast am LCD eingestellt - sehr wichtig.
Hier wird auf Zeile 3 Position 0 ein Text als Beispiel ausgegeben.

Watchdog:
Deklaration:
//Watchdog           
#include <avr/wdt.h> 

Setup:
wdt_enable(WDTO_4S);
Loop:
wdt_reset();  
Der Timer wird hier auf 4 Sekunden eingestellt. Gültige Parameter der Arduino Doku entnehmen.
Wenn der wdt_reset nicht innerhalb in der eingestellten Zeit erfolgt, wird das Programm neugestartet.

Ausgänge:
Hier die Pin Nummern als Konstante für den Motor (Relais), Cycle (Oszi Messung) und der LED definieren.
Diese Pins werden dann als OUTPUT klassifiziert und verwendet.
Deklaration:
//LED   
const byte ledrun = 13;   ;
//MOTOR Relais für Motor
const byte rel1 = 11;   
// Cycle Messung Periodendauer
const byte cycle = 8;

Setup:
pinMode(ledrun, OUTPUT);
pinMode(rel1, OUTPUT);   
pinMode(cycle, OUTPUT);

Loop:
digitalWrite(rel1,HIGH)   
digitalWrite(ledrun, ledstatus);  

digitalWrite werden in der Klammer zwei Argumente übergeben: Pin Nummer und Wert HIGH/LOW oder 1/0
Für die Messung der Periodendauer wir ein Rechtecksignal am Ausgang cycle ausgegeben.
cyclecheck(cycle);

Eingänge:
Deklaration:
const int pinstart = 7; // Start Button pin 7
const int pinstop = 6; // Stop Button pin 6   

Setup:
pinMode(pinstart,INPUT_PULLUP);
pinMode(pinstop,INPUT_PULLUP);  

Loop:
xy = digitalRead(pinstart);
xz = digitalRead(pinstop);  

Die Input Pins sind mit internen Pullup Widerständen deklariert. Einfache Zuweisung des gelesenen Wert von digitalRead ohne Debounce in eine Variable als Beispiel.
Hier in dieser Demo wird das aber durch die Unterprogramme T_start() und T_stop(), welche auch die Entprellung durchführen, gemacht.
Das Thema Entprellen oder Debounce wird im Internet ausreichend besprochen.

Interrupt Eingang:
Deklaration:
Der UNO (Atmega328) hat zwei Hardware Interrupt Eingänge INT0 und INT1; Nicht zu verwechseln mit den PCINTxx (PinChangesInterrupts) Eingängen, welche etwas komplizierter zu verwenden sind.
Eingang vom Flügelrad ist hier der INT0.
// Counter   
const byte intPin = 2; // Eingang vom Pumenrad PIN2 = int0 beim Arduio UNO.
volatile unsigned long wheel = 0; // Impulse vom Pumpenrad
Die Impulse werden in der Variable wheel gespeichert. Das Schlüsselword volatile zeigt dem Compiler, dass die Variable aus dem Ram gelesen wird und sollte bei Verwendung in Interrupt Service Routinen so deklariert werden.
Setup:
pinMode(intPin, INPUT_PULLUP); // definition Eingang mit internen Pullup Widerstand
attachInterrupt(digitalPinToInterrupt(intPin),flowcount,FALLING);
detachInterrupt(digitalPinToInterrupt(intPin));

attachInterrupt bekommt als drei Argumente übergeben, Pim Nummer Eingang, ISR Name die bei Interrupt gestartet wird, Definition der Impulsform (Hier fallende Flanke)
detachInterrupt schaltet den Interrupt wieder aus.
Loop:
Im Loop Teil wird über die Ereignisse T_start() und T_stop(), in Subroutine Jobcontrol, der Interrupt Ein- und ausgeschaltet.
attachInterrupt(digitalPinToInterrupt(intPin),flowcount,FALLING);
oder
detachInterrupt(digitalPinToInterrupt(intPin));
Die Interrupt Service Routine (ISR) wird nur von einem Interrupt ausgelöst.
Getestet habe ich mit einem 1kHz Signal, welches einwandfrei verarbeitet wird.
Testreihen, welche Maximale Frequenz vom 16MHz Arduino UNO ohne Impulsverlust verarbeitet wird habe ich nicht gemacht.

  void flowcount(){
  wheel++;
  }    


Debounce oder Entprellen
Mechanische Tasten neigen zum Prellen. Das Signal ist bei Tastenbetätigung für eine kurze Zeit nicht eindeutig, bevor es den erwarteten Zustand des Schalters annimmt.
Die Debounce Routine von https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce
habe ich hier verwendet.

Mess- Ausgang für Oszilloskop
Am cycle Pin kann die Durchlaufzeit der Hauptschleife gemessen werden.
Bei jedem Durchlauf wird das Signal invertiert (Funktion cyclecheck). Hier in dieser Konfiguration liegt am Cycle Ausgang ein Rechteck Signal von ca. 19Hz an.
Die Periodendauer ist damit 52mS. Ein Loop Durchlauf ist eine Halbwelle von diesem Signal und hat damit eine Dauer von 26mS.
Es ist interessant zu sehen, wie die Zeit sich etwas verlängert , wenn viele Interrupts auftreten, oder mehr am Display ausgegeben wird.
Auch könnte man hier die Watchdog Zeit kürzer einstellen, welche ja mit 4 Sekunden sehr lang ist.

Terminal Ausgabe
Manche Implementationen von Steuerungen haben kein Display. Ausgabe erfolgen nur über Schnittstelle. RS232 ist am UNO vorhanden und wird auch in der Programmierumgebung als Terminalfenster angeboten.
Hier in dieser Demo wird nur die Programmversion ausgegeben.
Deklaration:
const char vers[20] = "ControlDemo V 1.01";
Setup:
Serial.begin(9600);
Serial.println(vers);

Programmversionen sind für Maintenance und Identifikation von Geräten unverzichtbar.
Da will ich keine MD5 Summe ziehen müssen..

Loop:
Das Hauptprogramm, der Loop ist hier sehr übersichtlich, da hier nur Funktionen aufgerufen werden.
Meterlange Hauptprogramme, Komplexe If Verzweigungen vermischt mit allen Textausgaben, kann man nicht vernünftig pflegen, darum die Aufteilung.
Die Funktionen millis() und wdt_reset sind Standard Arduino, alle anderen Funktionen sind nach dem Loop im Sketch aus-programmiert. Sämtliche Logik mit Verzweigungen und Textausgaben etc. ist alles in Funktionen ausgelagert. Das hat auch den Vorteil, dass Teile des Programms hier für Tests leicht Aus- und wieder Eingehängt werden können.

void loop() myMillis = millis(); cyclecheck(cycle); blinker(10,0,19); T_start(); T_stop(); buttonanzeige(); jobcontrol(); runmotor(); zeitanzeige(); flowanzeige();> isrunning(); wdt_reset(); }


Fazit:
Dieses kleine Demo Programm zeigt, dass mit einfachen Mitteln ein schnelles Antwortverhalten, Responsivität von Programmen, erreicht werden kann.
Displays mit I2C Ansteuerung sind immer vorzuziehen, weil damit Leitungen eingespart werden, außerdem benötigt man ja auch öfter andere I2C Hardware wie Sensoren usw.
Interaktionen, von Sensoren und Schaltern werden durchgeführt und am Display sofort angezeigt.
Es ist auch klar, dass das Programm hier nicht die Ultima Ratio darstellt, man kann einiges optimieren.
Hier ist auch noch genügend Raum für funktionelle Erweiterungen vorhanden.
Es lohnt sich die angebotenen Ressourcen von Arduino zu nutzen.

Haftungs- Ausschluss
Lehne natürlich jegliche Verantwortung / Haftung für den Nachbau dieser Lösung ab.
Wenn Geräte mit Netzspannung gesteuert werden, ist besondere Vorsicht geboten. Die Vorschriften für Elektroinstallation sind einzuhalten. Nur für erfahrene Elektroniker gedacht.

H.Stummer Dateidatum: 25.09.2022


Zum Seitenanfang


Besucherzaehler