Leider sind die Abstände, in denen ich ein wenig Zeit finde, einen neuen Beitrag für den Blog zu schreiben, nicht kürzer geworden. Aber einen Beitrag pro Monat zu posten, halte ich ein… 🙂
Dieses Mal ist es kein Retro Bastelprojekt aus den heimischen Gefilden oder eine Restauration eines alten Gerätes, sondern wieder etwas zum Thema Arduino. Die Idee – es soll ein Sensor gebaut werden, der wie immer, eine physikalische Größe in ein elektrisches Signal umwandelt. Das ist jetzt nichts Besonderes und um welche Art von Sensor es sich handeln wird, werde ich vorerst noch nicht beschreiben. Aber es soll nicht ein Sensorboard geben, sondern viele. Und diese Sensorboard kurz „Sensoren“ sollen in einer zweidimensionalen Matrix miteinander vernetzt werden. Man kann sich das in etwa vorstellen wie ein Schachbrett, wobei jedes der Schachbrettfelder einen Sensor darstellt. Dieses Netzwerk an Sensoren – also Sensorknoten – soll dann über eine Übergabestelle mit einem Rechner verbunden sein und die Sensordaten des jeweiligen Feldes ausgeben. Es soll dann auch möglich sein, einzelne Felder aus dem Netzwerk zu entfernen ohne dass das verbleibende Netzwerk seine Funktion verliert.
Das ganze System soll möglichst einfach und günstig aufgebaut werden. Und so ist schnell ein Systemkonzept entstanden, in dem die Knoten über den I²C Bus kommunizieren und ihre Daten zu einem Master senden. Das folgende Diagramm soll das verdeutlichen.
Dieses Konzept, so dachte ich mir, lässt sich am einfachsten mit einem Atmega Microcontroller realisieren. Der hat genügend IO´s, einen I²C Bus und UART onboard, ebenso auch analoge Eingänge und benötigt wenig Bauteilperipherie, um ihn in einem eigenen Layout zum Leben zu erwecken. Und es gibt nichts schnelleres, so einen Testaufbau eines solchen Knotennetzwerks zu realisieren, als die gut bekannten Arduino Development Boards zu benutzen. Ich habe die günstigste Variante für einen Testaufbau gewählt -> den Chinanachbau vom Arduino Uno (Joy-IT UNO) mit dem Atmga328 im gesockelten DIL Gehäuse.
Im Bild sind zehn Stück dieser Microcontrollerboards zu sehen. Von denen soll einer als Bus-Master und neun Stück als Slaves eingesetzt werden. Jeder dieser Slaves hat natürlich eine eindeutige Bus-Adresse, die im System nur einmal vorkommt. Im Testaufbau wird diese Busadresse über den Programmcode fest vergeben, da ohnehin jeder Arduino einmal mit dem Rechner verbunden werden muss, um den Programm-Upload durchzuführen. Das soll natürlich später anders aussehen. Denn der Arduino wird auf den Atmega328 Chip, seinen Quarz und die paar Widerstände reduziert auf dem Sensorboard mit gelayoutet. Programmiert soll der Chip dann über die ISP Pins werden. Da bei vielen Boards natürlich nicht jedes Mal der Programmcode individuell angepasst wird und alle das gleiche Flashfile erhalten sollen, will ich die Sensoradresse mit einem 7Bit Dipschalter einstellen. Ein 4021 Cmos Static Shift Register soll die Bits nach dem Einschalten des Controllers auslesen und seriell in den Controller schieben. Der daraus resultierende Wert steht dann in einer Variablen als Busadresse zu Verfügung.
Jeder dieser Slaves mit seiner individuellen Busadresse wird nun vom Masterknoten der Reihe nach abgefragt, welchen Zustand er hat und ob er einen Ausgang schalten soll, oder nicht. Das bedeutet, der Knoten hat lediglich einen DO (Digitalen Ausgang) mit dem er beispielsweise eine LED aus- und einschalten kann und einen oder mehrere DI (Digitalen Eingang) der einen Zustand, zum Beispiel eines einfachen Schalters abfragt. Diese Funktionen werden in 2 Bits eines Bytes gespeichert. Ein weiteres Byte dient zur Übertragung der Busadresse. Es werden also zwei Bytes über den Bus geschickt. Das untenstehende Bild zeigt den Testaufbau mit den „UNO-Boards“
Der Ablauf läuft wie folgt:
MASTER:
Der Masterknoten sendet nach der Reihe an alle Slave-Adressen eine Anfrage und einen Schaltbefehl (der kommt für alle Knoten von einem TEST-Taster Eingang am Master) für den LED-Ausgang des Knotens und sieht ob eine Antwort zurückkommt oder nicht. Wenn keine Antwort kommt, ist der Knoten nicht im Netz oder defekt. Kommt eine Antwort, so besteht diese aus der Adresse des Knotens und seinem Statusbyte. Diese Informationen werden über ein RS232 Terminal an den, am Master angeschlossenen Rechner übertragen. So kann dort beispielsweise über eine Visualisierung mittels (NI LabVIEW, oder Matlab o.ä.) der Schaltzustand jedes einzelnen Knotens am Bildschirm angezeigt werden. Mit einer Anpassung des Master Programmes ist es auch möglich, die LED-Ausgänge der Slaves über den Rechner zu schalten.
SLAVE:
Wenn der Masterknoten vom Slave Daten anfordert, so sendet der Slave zwei Bytes zurück. Wobei Byte0 wieder die Slave ID (also Busadresse ist) und Byte1 die Daten. Byte1 besteht eigentlich aus nur zwei Bit, die wie folgt kodiert sind (in dezimaler Darstellung):
0 = LED aus | Sensor nicht ausgelöst
1 = LED ein | Sensor nicht ausgelöst
2 = LED aus | Sensor ausgelöst
3 = LED ein | Sensor ausgelöst
Der Programmcode als Beispiel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
// I2C Slave Code // 16.05.2018 // ver 1.3 #include <Wire.h> #define ADDRESS 2 // adresse des slave knotens #define PAYLOAD_SIZE 2 // anzahl der bytes die vom masterknoten zu erwarten sind int LED=12; // indicator led an pin D12 int SENSOR = 8; // sensor input an pin D8 bool actionState=0; // sensor zustand int busstatus; // statusvariable // 0 = LED aus | sensor nicht belegt // 1 = LED ein | sensor nicht belegt // 2 = LED aus | sensor belegt // 3 = LED ein | sensor belegt bool sensled=0; // sensor LED byte nodePayload[PAYLOAD_SIZE]; void setup() { pinMode(LED, OUTPUT); //sensorLED pinMode(SENSOR, INPUT); //Sensor Wire.begin(ADDRESS); // Activate I2C network Wire.onReceive(receiveEvent); Wire.onRequest(requestEvent); // auf master anforderung warten // // debug interface // Serial.begin(9600); } // *********************************mainloop**************************************************** void loop() { delay(5); if(sensled){digitalWrite(LED, HIGH);} else{digitalWrite(LED, LOW);} actionState = digitalRead(SENSOR); //Sensoreingang abfragen if((actionState==1)&&(sensled==1)) {busstatus=3;} else if ((actionState==0)&&(sensled==1)) {busstatus=1;} else if ((actionState==1)&&(sensled==0)) {busstatus=2;} else if ((actionState==0)&&(sensled==0)) {busstatus=0;} // Serial.println("######################"); // Serial.print("busstatus neu setzen "); // Serial.println(busstatus); // Serial.print("sensled LED "); // Serial.println(sensled); // Serial.print("actionState "); // Serial.println(actionState); // Serial.println("######################"); nodePayload[0] = ADDRESS; // Adresse in byte0 zurücksenden. nodePayload[1] = busstatus; //byte 1 ist die statusinfo der LED } // ********************************************************************************************* void requestEvent() { Wire.write(nodePayload,PAYLOAD_SIZE); Serial.println("bytes status schreiben"); Serial.println(nodePayload[0]); Serial.println(nodePayload[1]); delay(5); } // ********************************************************************************************* void receiveEvent(int bytes) //einen wert vom I2C lesen { busstatus = Wire.read(); //If the value received was true turn the led on, otherwise turn it off // Serial.println("status empfangen"); // Serial.println(busstatus); if((busstatus==1)||(busstatus==3)){sensled = 1;} else{sensled = 0;} } |
Die Busadresse ist in diesem Slave-Code noch individuell einzugeben. In der nächsten Version ist dann der vorherbeschriebene „Serializer“ der parallelen Dip-Schaltervariante implementiert. Das folgende Codebeispiel ist von Masterknoten, der die Slaves ausliest und mittel Prüftaster ein LED Muster an die Sensor Slaves sendet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
// I2C masterknoten // 16.05.2018 // ver 1.2 // changes abfrage wenn kein knoten am bus dann 255 ausgeben #include <Wire.h> #define busbytes 2 // wievele byte vom I2C knoten zu erwarten sind #define maxKNOTEN 10 // anzahl der zu scannenden slaves #define startKNOTEN 2 // startadresse der slaves #define DELAY 5 // einfach ein delay .... int i; int j=0; int buttonPin = 12; int testbut = 0; int anim = 0; int buttonState = 0; int DATEN[busbytes]; int adresse; int busstatus; byte sensorbelegt; byte ledsensoron; // 0 = LED aus | Sensor nicht belegt // 1 = LED ein | Sensor nicht belegt // 2 = LED aus | Sensor belegt // 3 = LED ein | Sensor belegt int leddat1[] = {1,1,1,1,0,1,1,1,1}; // - int leddat2[] = {0,0,0,0,1,0,0,0,0}; // | void setup() { Serial.begin(9600); Serial.println("MASTER-KNOTEN"); Serial.print("Maximum Slaveknoten: "); Serial.println(maxKNOTEN); Serial.print("Datengroesse in byte: "); Serial.println(busbytes); Serial.println("***********************"); Wire.begin(); // Activate I2C link pinMode(buttonPin, INPUT); // test-tastereingang festlegen } //##################################################################################################### void loop() { for (int Knotenadresse = startKNOTEN; //alle knoten scannen Knotenadresse <= maxKNOTEN; Knotenadresse++) //################################################################################################ { // testbut = 0; anim = 0; Wire.requestFrom(Knotenadresse, busbytes); // daten vom jeweiligen knoten anfordern DATEN[0]=255; DATEN[1]=255; // wenn kein knoten dann auf 255 setzen if(Wire.available() == busbytes) { // wenn knoten und daten dann for (i = 0; i < busbytes; i++) DATEN[i] = Wire.read(); // daten holen (zuerst busID, dann daten) // for (j = 0; j < busbytes; j++) Serial.println(DATEN[j]); // daten an rs232 ausgeben } // Serial.println(Knotenadresse); // Serial.println(DATEN[0]); // Serial.println(DATEN[1]); // Serial.println(" "); adresse=DATEN[0]; busstatus=DATEN[1]; if(busstatus == 0) {sensorbelegt=false; ledsensoron=false;} else if (busstatus == 1) {sensorbelegt=false; ledsensoron=true;} else if (busstatus == 2) {sensorbelegt=true; ledsensoron=false;} else if (busstatus == 3) {sensorbelegt=true; ledsensoron=true;} //################################################################################################ //Testbutton Status lesen und variable testbut entsprechend setzen buttonState = digitalRead(buttonPin); //tastereingang einlesen if(buttonState == HIGH){ //wenn taster aktiv dann variable anim setzen anim = 1; //delay(5); } // //debug debuginfo tasterstatus auf rs232 ausgeben // Serial.println("#######################"); // Serial.print("Knoten Adresse :"); // Serial.println(adresse); // Serial.print("Busstatus :"); // Serial.println(busstatus); // // Serial.println("----------------"); // Serial.print("Fliese belegt :"); // Serial.println(sensorbelegt); // Serial.print("LED Fliese :"); // Serial.println(ledsensoron); // Serial.print("#######################"); // Serial.println(" "); //################################################################################################ //Testbutton Status an jeweiligen knoten senden Wire.beginTransmission(Knotenadresse); // transmit to device actual in for loop //anim schreiben if (anim==0) {testbut=leddat1[j]; j++;} else {testbut=leddat2[j]; j++;} if (j>8){j=0;} Wire.write(testbut); // senden des tasterstatus Wire.endTransmission(); // ende gelände mit uerbertragung delay(DELAY); } } |
Mit dieser Anordnung ist es jetzt möglich alle Arduinos und deren Eingang auszulesen bzw. die LED über den Bus zu steuern. Im nächsten Schritt wird ein „Sensor“ gebaut, eine Platine gelayoutet und der Arduino Uno auf seinen Microcontroller reduziert. Darüber werde ich in einem der nächsten Posts berichten…