Schlagwort-Archive: Microcontroller

Der Wetterfrosch 2.0 oder Umweltdatenlogger

Loading

Vor ein paar Jahren habe ich ein Projekt vorgestellt, in dem ein Raspberry Pi als Datenlogger arbeitete. An diesen Raspberry waren ein paar Sensoren angeschlossen, die Umweltdaten, wie Lufttemperatur, relative Luftfeuchte, den Luftdruck und auch die aktuelle GPS Position aufzeichneten. Die Sensoren bestanden größtenteils aus fertigen Breakoutboards, die über die diversen Busse (I²C, Serial, SPI…) an den RaspberryPi angeschlossen waren. Am PI selber liefen Python Skripten, die das Auslesen der Sensoren übernahmen, die Daten zusammenfassten und auf einen USB-Flashspeicher ablegten. Dieses Sammelsurium an Komponenten hatte ich dann in eine Kunststoffbox mit einer Größe von 150x80x50mm eingebaut.

Doch es geht auch um einiges kleiner. Im Rahmen eines kleinen Projektes war es die Aufgabe, diesen Sensor/Datenlogger zu verkleinern. Mein Ansatz, das zu realisieren, war ganz einfach: „Alles neu“. So habe ich das Konzept folgendermaßen geändert:

  • der RaspberryPi wird durch einen Microcontroller ersetzt
  • es wird eine Platine erstellt, auf der sämtliche Komponenten untergebracht sind
  • die erfassten Daten werden auf einer MicroSD Karte gespeichert
  • das Board ist auf die wesentlichsten Komponenten reduziert. Die Sensorelektronik und der SD-Card Reader wird direkt auf dem Board platziert
  • ein GPS-Empfänger (in Form eines Breakoutboards) soll optional aufgesteckt werden können
  • die Programmierung des Controllers wird durch eine ISP Schnittstelle durchgeführt
  • die Spannungsversorgung beträgt 5V DC

Daraus habe ich folgendes Blockschaltbild erstellt:

Blockschaltbild

Das zentrale Element ist, wie so oft, der Microcontroller Atmega328. Er benötigt als externe Beschaltung lediglich einen Quarz als Taktstabilisierung. (genauer gesagt bietet er aber auch die Optionen interne Oszillatoren zu benutzen…) Der Microcontroller kommuniziert über den I²C Bus mit den Sensoren HYT939 und BME280. Über die ausgeklügelte bidirektionale Levelshifter Schaltung mittels BSS138 Mosfet mit integrierter Body Diode wird die Anpassung der Pegel von 5V auf der Controller Seite zu den 3,3V auf der Sensorseite realisiert. Diese Schaltung wird sowohl für die SCL- (Serial Clock), als auch für die SDA-Leitung (Serial Data) angewendet.

Die Datenspeicherung findet auf einer MicroSD-Karte statt. Dafür wird ein Card Slot verbaut, der per SPI (Serial Peripheral Interface) mit dem Controller kommuniziert. Auch hier ist eine Anpassung der Signalamplituden notwendig. Das übernimmt dieses Mal jedoch der Chip TXB0108 von Texas Instruments. Das ist ein 8Bit Bidirektionaler Levelshifter.

Ein Taster wird die Datenaufzeichnung starten und stoppen und eine LED soll diverse Statusmeldungen durch Blinkfolgen darstellen.

Das optional aufsteckbare GPS Modul arbeitet mit 5V Spannungsversorgung und die Pegel der seriellen Datenkommunikation (RS232) sind ebenfalls 5V kompatibel.

Zu guter letzt ist natürlich auch die Spannungsversorgung zu planen. Hier soll lediglich eine externe, stabilisierte 5VDC Quelle angeschlossen werden, um den Logger zu versorgen. Die für die Sensoren und SD-Card benötigten 3,3VDC werden am Board mittels einem LDO (Low Drop Out) Regler erzeugt.

Sind alle Komponenten und deren Zusammenspiel definiert, dann wird daraus der Schaltplan gezeichnet. Für meine Bastelprojekte verwende ich hauptsächlich den Schaltplan- und Layout Editor „Eagle“.  Aus dem Blockschaltbild ergibt sich die unten abgebildete Schaltung.

Aus dem Schaltplan habe ich ein Layout mit zwei Layern erstellt, dessen Grundriss die Abmessungen 55x25mm hat. Bis auf die Steckverbinder befinden ausschließlich SMD Komponenten auf dem Board.

Im Layout Tool gibt es die Funktion, eine optische Vorschau der gefertigten Platine zu betrachten. So kann man vorab überprüfen, ob die Platine den Vorstellungen entspricht und gegebenenfalls die Lage der Bauteile optimieren. Ist das erledigt, wird aus dem Design ein Paket mit Produktionsfiles (Gerberdateien) erzeugt und das dann dem Platinen Hersteller seines Vertrauens gesendet. Da der auch sehr, sehr weit weg angesiedelt ist, dauert die Produktion auch ein paar Tage. Aber schlussendlich kommen die Platinen an und können sich auch sehen lassen.

Die beiden Bilder oben zeigen die Platine von der TOP und der BOTTOM Seite. Der nächste Schritt besteht darin, die Komponenten entsprechend der Planung zu bestellen und danach zu bestücken.

Die Bestückung erledige ich per Hand mit einem, für die SMD-Komponenten geeigneten Lötkolben mit entsprechend kleiner Spitze. Für die ganz kleinen Teile, wie den BME280 Sensor, kommt auch noch ein Mikroskop, bzw. eine Mikroskop Kamera zum Einsatz.

Wie die Platine nach der Bestückung aussieht, zeigen die beiden Bilder oben. Das folgende Foto zeigt den Größenunterschied des fertigen Loggers mit dem aufgesteckten GPS Modul im Vergleich zum alten „Wetterfrosch“Nach der Fertigstellung der Hardware, geht es nun an die Software. Die habe ich praktischer Weise mit dem Arduino IDE Tool gebastelt und per AVRISP mk2 über ISP auf den Controller geflashed. Um den AVRISP auf einem Windows 10 Rechner zum Laufen zu bekommen, muss ein geeigneter Treiber installiert sein. (hier hilft libusb-win32-1.2.6.0)

Programmcode mit der ArduinoIDE erstellt
Controller mit AVRISPmkII geflashed

Auf der SD-Karte wird nach Anlegen der Versorgungsspannung und nach Betätigen des Tasters die Datenaufzeichnung gestartet. Die Messwerte werden im Sekundentakt geschrieben. Wenn, wie in diesem Beispiel der GPS-Sensor gesteckt ist, so werden auch die GPS Daten mit aufgezeichnet. Die Software zeichnet auch auf, wenn der GPS Sensor noch keinen „fix“ hat. (Da in dem Beispiellog unten noch kein GPS-Fix vorhanden war, sind auch keine gültigen GPS Daten enthalten.)

Beispiel des Datenlogs:

Luftdruck962.41
Luftfeuchte37.05
Temperatur26.96
-----------------------------
$PGACK,103*40
$PGACK,105*46
$PMTK011,MTKGPS*08
$PMTK010,001*$GPGGA,235947.799,,,,,0,00,,,M,,M,,*71
$GPGLL,,,,,235947.799,V,N*73
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79
$GPRMC,235947.799,V,,,,,0.00,0.00,050180,,,N*48
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPGGA,235948.799,,,,,0,00,,,M,,M

-----------------------------
Luftdruck962.39
Luftfeuchte36.72
Temperatur26.95
-----------------------------
Luftdruck962.43
Luftfeuchte36.66
Temperatur26.97
-----------------------------

Datenknoten mit Arduino

Loading

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.

Joy-It Uno Boards

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“

Alle Arduinos sind mit I²C Datenbus und Spannungsversorgung verbunden

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…