Projekt "W-Bus GSM", Version 1
Ziel
Ziel dieser ersten Programmversion ist es, basierend auf dem Hardware-Setup aus Arduino UNO, W-Bus Interface und GSM-Interface eine einfache Startfunktion für die Standheizung umzusetzen.
Dabei geht es zunächst nicht um saubere Codierung, Programmablauf oder Störfestigkeit, sondern rein um die Funktion.
Programmablauf
- Beim Start des Arduino (Reset bzw. anlegen der Spannungsversorgung) wird zunächst die W-Bus Schnittstelle initialisiert und geprüft. Tritt hier eine Störung auf, wird das durch schnelles blinken der roten LED auf dem Arduino Board signalisiert und die Codeausführung beendet. Ein Neustart ist erforderlich.
- Anschließend wird das GSM-Board initialisiert und ins Handy-Netz eingebucht. Auch hier wird ein Fehlschlag durch die schnell blinkende LED des Arduino angezeigt und das Programm abgebrochen. Ein Neustart ist erforderlich.
- Bei einem eingehenden Anruf soll die Standheizung für eine festeingestellte Zeit (30 Minuten) laufen. Während des Heizvorganges soll die rote LED konstant leuchten um den Betriebszustand anzuzeigen.
- Um die Heizung in Betrieb zu halten, ist eine periodische Abfrage über den W-Bus notwendig. Hierbei wird die Antwort der Heizung ausgewertet um eine Startverhinderung bzw. das „natürliche Ende“ des Heizvorganges mitzubekommen.
- Am Ende des Heizvorganges (egal ob erfolgreich oder nicht) soll die rote LED am Arduino langsam blinken um das Programmende anzuzeigen. Aus diesem Zustand kommt der Arduino auch nur noch die Reset wieder heraus.
Codebesprechung
Nachfolgend erkläre ich den bislang generierten Programmcode.
Einstellparameter
Als erstes definieren wir einige wichtige (konfigurations) Parameter:
#define WBUS_RX_PIN 0 #define WBUS_TX_PIN 1 #define GSM_TX_PIN 2 #define GSM_RX_PIN 3 #define GSM_PWRKEY_PIN 4 // Power-On input to GSM #define GSM_RI_PIN 5 // Ring-Indicator output from GSM #define GSM_STATUS_PIN 6 #define ERROR -1 #define OK 0
Die "BREAK" Funktion
Eine Funktion die wir wohl häufiger brauchen werden ist die zum erzeugen des BREAK-Signals auf dem W-Bus:
/** * Generate BREAK signal on W-Bus (LOW/HIGH pulse on TX line) */ void wbus_break() { Serial.end(); pinMode(WBUS_TX_PIN, OUTPUT); digitalWrite(WBUS_TX_PIN, LOW); delay(25); digitalWrite(WBUS_TX_PIN, HIGH); delay(25); Serial.begin(2400, SERIAL_8E1); }
Serial.end()
sorgt dafür, das der Eingangs- und Ausgangspuffer des Hardware-UART vom Arduino geleert und seine internen Sende- und Empfangsleitung von den Pins 0 und 1 getrennt werden. Diese können dann als ganz normale GPIOs genutzt werden. In der nächsten Zeile wird der TX-Pin, welcher als TX-Signal für den W-Bus dient, als Ausgang eingestellt wird.
Im Ruhezustand hat eine serielle Sendeleitung (TX) HIGH-Pegel, also 12V am W-Bus. Mit dem digitalWrite()
setzen wir den Pegel nun auf LOW (0V am W-Bus), warten 25 Millisekunden und setzen ihn dann wieder auf HIGH. Auch danach warten wir 25 ms bevor wir mit Serial.begin()
die RX/TX Pins wieder mit dem Hardware-UART verbinden.
FATAL ERROR Funktion
Sollte unser Programm in eine Zustand geraten in dem wir nicht weiter machen können, empfiehlt es sich diese Routine anzuspringen:
/** * Signal FATAL ERROR. Stop executing code and blink onboard LED fast. */ void halt() { while(1) { digitalWrite(LED_BUILTIN, HIGH); delay(250); digitalWrite(LED_BUILTIN, LOW); delay(250); } }
Hier wird nichts anderes gemacht als in einer Endlosschleife die rote On-Board LED des Arduino schnell blinken zu lassen. Das Makro LED_BUILTIN
ist vom Arduino vorgegeben und löst intern auf Pin 13 auf. Dieser ist auf dem Board mit einer LED verbunden.
Aus dieser Routine gibt es kein zurück mehr, es hilft nur noch ein Hardware-Reset per Button oder Stromabschaltung.
Die Initialisierungsroutine
Die wichtigsten Initialisierungen machen wir in der Arduino setup()
-Routine. Diese spezielle Funktion wird vom Arduino Bootloader einmalig als erstes beim Start durchlaufen.
void setup() { // Init Pins pinMode(LED_BUILTIN, OUTPUT); pinMode(GSM_PWRKEY_PIN, OUTPUT); digitalWrite(GSM_PWRKEY_PIN, LOW); // will be inverted by levelshifter or transistor pinMode(GSM_RI_PIN, INPUT); pinMode(GSM_STATUS_PIN, INPUT); // Init W-Bus if (wbus_init() != OK) { halt(); } // Init GSM-Modem if (gsm_init() != OK) { halt(); } }
W-Bus init
int wbus_init() { Serial.begin(2400, SERIAL_8E1); ...
Mit Serial.begin()
wird zum einen die Baudrate (2400 Baud) und Serialparameter (8 Datenbits, Gerade Parität, 1 Stopp-Bit) angegeben und zum anderen werden die Arduino Pins 0 und 1 mit der RX und TX-Leitung des Hardware-UART verbunden.
... // W-Bus: ECHO-TEST Serial.write("TEST"); Serial.flush(); if (Serial.readString() != "TEST") { return(-1); } ...
Beim „ECHO-Test“ senden wir eine kurze Bytefolge auf den W-Bus. Auf der gemeinsamen RX/TX-Leitung des W-Bus müssten diese Bytes praktisch sofort nach dem senden wieder im Eingangspuffer des UART befinden. Ist das nicht der Fall ist der W-Bus entweder nicht angeschlossen, nicht terminiert oder es gibt sonst irgendwelche Störungen darauf.
... // W-Bus: COMM-TEST wbus_break(); byte tx[] = { 0xF4, 0x03, 0x51, 0x0A, 0xAC }; Serial.write(tx); delay(50); if (Serial.available() < 6) { halt(); } byte rx[6]; Serial.readBytes(rx, 6); if ((rx[0] != 0x4F) && (rx[2] != 0xD1)) { halt(); } ...
Der Kommunikationstest fragt einen trivialen Standheizungsparameter ab um die Kommunikation mit der Heizung zu überprüfen. Hierbei geht es vielmehr um eine Antwort der Heizung als deren Inhalt. Bevor wir etwas zur Heizung senden, kommt erstmal ein BREAK um diese ggf. aufzuwecken. Einer laufenden Standheizung tut das nicht weg. Dann senden wir eine einfache Abfrage (W-Bus Protokollversion der Heizung), welche in der Bytefolge von tx[]
kodiert ist.
Anschließend warten wir 50 ms (maximalzeit laut Protokoll bis die Heizung antworten muss) und prüfen ob wenigstens die erwartete 6 Byte Antwort zurückgesendet wurde. Ist dies nicht der Fall, gibts ein Problem und wir stoppen wieder den Code mit der halt()
Funktion.
Kam etwas zurück (erwarten würden wir sowas wie 4F 03 D1 0A 33 A4
) überprüfen wir das erste Byte der Antwort (rx[0]
) auf die korrekte Adresse (hier muss 0x4F drin stehen, also Standheizung → Uns
) und das gesendete Kommandobyte mit gesetztem ACK
-Bit (=0xD1
).
... return( OK ); }
Sollten wir es bis hierher geschafft haben, geben wir als Ergebnis den per define hinterlegten Zahlenwert für OK
zurück.
GSM init
int gsm_init() { // "push" powerkey of GSM to startup module digitalWrite(GSM_PWRKEY_PIN, HIGH); delay(1000); digitalWrite(GSM_PWRKEY_PIN, LOW);
Nun warten 3 weitere Sekunden bis das STATUS-Signal kommt und anzeigt das sich das Modem ins GSM-Netz eingebucht hat. Geht das schief, dann ist hier erstmal ende:
delay(3000); if (digitalRead(GSM_STATUS_PIN) != HIGH) { return( ERROR ); }
Wenn das GSM-Modem vorab richtig eingerichtet wurde (SIM-Sperre raus, Netz vorgewählt, etc.) dann brauchen wir hier nun nichts weiter tun:
return( OK ); }
Das Hauptprogramm
Zunächst warten wir auf einen eingehenden Anruf in einer Endlosschleife. Dieser wird durch einen LOW-Pegel am RI-Pin (Ring-Indicator) angezeigt. Die LOW-Phase ist abhängig vom Anruftyp und Dauer, daher habe ich die Wartezeit zwischen den Abfragen recht klein (50 ms) gestellt:
void loop() { while(digitalRead(GSM_RI_PIN) == LOW) { delay(50); }
Nun, da wir ein Startsignal von außen erhalten haben, signalisieren wir das durch anzeigen der roten LED auf dem Arduino Board:
digitalWrite(LED_BUILTIN, HIGH);
Weiterhin senden wir ein BREAK auf den W-Bus und die Startsequenz für die Heizung hinterher:
wbus_break(); //byte tx[5] = { 0xF4, 0x03, 0x21, 0x1E, 0xC8 }; // uncomment to start parking heater (CMD 0x21) e.g. Ford Mondeo byte tx[5] = { 0xF4, 0x03, 0x23, 0x1E, 0xCA }; // uncomment to start supplemental heater (CMD 0x23) e.g. VW Touran Serial.write(tx);
Nun geben wir der Heizung ein bischen Zeit darauf zu reagieren. Sie wird uns den Befehl bestätigen oder ablehnen. Für diese Antwort interessieren wir uns aber im Moment noch nicht, daher „überlesen“ wir das einfach:
delay(1000); Serial.flush();
Nehmen wir an, die Heizung wurde erfolgreich gestartet. Nun müssen wir alle 4 Sekunden eine Statusabfrage als Erhaltungsbefehl senden, sonst schaltet sich die Heizung wieder aus:
byte rxbuf[16]; while(1) { //byte tx[6] = { 0xF4, 0x04, 0x44, 0x21, 0x00, 0x95 }; // uncomment this for Ford Mondeo byte tx[6] = { 0xF4, 0x03, 0x44, 0x23, 0x00, 0x90 }; // uncomment this for VW Touran Serial.write(tx);
Nun müssen wir die Antwort der Heizung auswerten, da wir sonst nicht mitbekommen, ob sich diese aufgrund einer Störung oder ganz regulär nach Ablauf der Heizzeit ausgestellt hat. In der Antwort der Heizung auf unsere Statusabfrage wird im Normalfall hinter dem Kommandobyte (mit ACK-Bit 7 gesetzt) ein Statuswert 0x00 folgen. Ist dieser anders, also ungleich 0x00, dann liegt ein Fehler vor oder die Heizzeit ist abgelaufen und wir verlassen die Schleife mit break;
:
delay(50); // give heater time to answer. Must return at least 5 Byte answer. if(Serial.available() < 5) { halt(); } Serial.read(rxbuf, 5); // heater should answer code 0x00 for OK ("4F 03 C4 00 88") or code 0xFF for ERROR ("4F 03 C4 FF 77") if ( (rxbuf[0] != 0x4F) || ((rxbuf[2] & 0x80) == 0) || (rxbuf[3] != 0x00) ) { // heater not running (anymore). exit loop break; }
Sieht alles soweit ok aus, dann warten wir jetzt 4 Sekunden bis zur nächsten Erhaltungsabfrage:
delay(4000); }
Nachdem unsere Heizfunktion einmal durchlaufen wurde, beenden wir unser Programm und signalisieren das durch langsames blinken der roten LED auf dem Arduino Board:
while(1) { digitalWrite(LED_BUILTIN, LOW); delay(800); digitalWrite(LED_BUILTIN, HIGH); delay(800); }