AdSense

Posts mit dem Label Arduino werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Arduino werden angezeigt. Alle Posts anzeigen

Sonntag, 30. August 2020

Home Automation mit Raspberry PI und ESP8266, Teil 2: Ein einfacher Sensor (ESP8266)

(English version) Als ersten Teil möchte ich auf meine Temperatur- und Luftfeuchte-Sensoren eingehen. Ich nutze hierfür einen ESP8266, welcher einen Si7021-Sensor ausliest. Den Si7021 habe ich mit einem 4-poligen Kabel mit dem ESP8266 an den Pins D1-D4 verbunden, wie man es hier im Bild sehen kann:

Zusätzlich muss noch der Pin D0 mit RST verbunden werden, damit das Deep Sleep funktioniert, welches wir noch brauchen werden. Da ich außerdem relativ instabile Handyladegeräte für die Sensoren benutzt habe, habe ich noch einen Kondensator parallel zu G und 3V geschalten. 

Das Ganze kommt dann in ein 3D-gedrucktes Gehäuse, welches man hier auf Thingiverse finden kann: https://www.thingiverse.com/thing:4583759. Mit dem kleinen Bügel wird der Sensor im Gehäuse fixiert, danach kommt der ESP oben drauf und der Deckel fixiert dann alles. An der offenen Stelle ist nun genau genug Platz für den Anschluss des Handyladegeräts.



Der Code ist vergleichbar mit meinem anderen Post zu dem Thema, https://physudo.blogspot.com/2019/07/wlan-thermometer-mit-dem-dht22-und.html. Zuerst wird im Setup-Teil das WLan verbunden, sollte das nicht klappen resettet sich der ESP automatisch nach 100 Sekunden. Außerdem wird der Si7021-Sensor initialisiert.

In Loop wird dann, falls das WLan verbunden ist (ansonsten wird der ESP auch resettet), die Temperatur und Luftfeuchte gemessen. Sollte hier etwas schief gehen wird auch der ESP neu gestartet. Anschließend wird alles per http Request an den Raspberry gesendet, wie im vorigen Post beschrieben (https://physudo.blogspot.com/2020/08/home-automation-mit-raspberry-pi-und.html).

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
const char* ssid     = "WIFI SSID";
const char* password = "WIFI PASSWORD";

#include "Adafruit_Si7021.h"

Adafruit_Si7021 sensor = Adafruit_Si7021();

void setup()
{
  Serial.begin(9600);

  Serial.print("Your are connecting to;");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  int resetCtr = 0;
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    resetCtr ++;
    Serial.print(".");
    if (resetCtr > 200)
    {
      ESP.restart();
    }
  }

  pinMode(D0, WAKEUP_PULLUP);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  digitalWrite(D3, HIGH);
  digitalWrite(D4, LOW);
  delay(500);

  while (!sensor.begin())
  {
    Serial.println("Did not find Si7021 sensor!");
  }

  digitalWrite(D3, LOW);
}



void loop() {
  if (WiFi.status() != WL_CONNECTED)
  {
    ESP.restart();
  }

  if (WiFi.status() == WL_CONNECTED)
  {
    Serial.println("");
    Serial.println("Your ESP is connected!");
    Serial.println("Your IP address is: ");
    Serial.println(WiFi.localIP());

    digitalWrite(D3, HIGH);
    delay(500);
    float t = sensor.readTemperature();
    Serial.print("Temperature: ");
    Serial.println(t);
    float h = sensor.readHumidity();
    Serial.print("Humidity: ");
    Serial.println(h);
    if (isnan(t))
    {
      t = sensor.readTemperature();
    }
    if (isnan(h))
    {
      h = sensor.readHumidity();
    }
    digitalWrite(D3, LOW);

    if (isnan(t))
    {
      ESP.restart();
    }
    if (isnan(h))
    {
      ESP.restart();
    }
    

        
    HTTPClient http;
    char requestString[255] = "";
    sprintf(requestString, "http://raspberrypi/SendValues.php?name=Temperatursensor_1&split=1&data=%f,%f", t, h);    
    http.begin(requestString);
    int httpCode = http.GET();
    Serial.println(httpCode);

    if (httpCode > 0)
    {
      String payload = http.getString();
      Serial.println(payload);
    }

    http.end();

    ESP.deepSleep(300e6); 
    delay(300000);
    ESP.reset();
  }
  else
  {
    Serial.println("");
    Serial.println("WiFi not connected");
    delay(1000);
  }
}

Dienstag, 2. Juli 2019

WLAN-Thermometer mit dem DHT22 und EPS8266-12E

(English version) Eigentlich sollte ich ja an den Posts zum Drucker weitermachen, aber ich habe gerade an einem ESP8266-12E gearbeitet und dabei super einfach ein WLAN-Thermometer (mit dem ESP8266-12E als Web-Client, welcher an einen Apache-Server auf meinem Raspberry PI sendet) realisiert, sodass ich das erstmal hier erklären möchte.


1. Der ESP8266-12E
Der ESP8266-12E ist ein Arduino-ähnlicher Baustein, welcher einen WLAN-Baustein mit integriert hat. Sucht man auf Google danach, findet man diesen bei vielen Quellen zu kaufen. Das Tolle ist, dass er mit der Arduino-Umgebung programmiert werden kann, sodass alle Libraries auch funktionieren!

2. Einrichtung Arduino für den ESP8266-12E
Zuerst sollte der CH340-Treiber installiert werden. Da es dazu genug im Internet gibt werde ich darauf nicht eingehen. Als nächstes muss in der Arduino-Umgebung auf die Voreinstellungen gehen:

Hier trägt man nun die zusätzliche Boardverwalter-URL ein:
http://arduino.esp8266.com/stable/package_esp8266com_index.json

Danach kann man dann auch schon im Boardverwalter die Einstellungen für die Boards herunterladen:

Hier muss nun nach "ESP8266" gesucht werden und das sollte dann auch installiert werden:

Nun ist eigentlich alles vorbereitet und wir können das Board auswählen, ich habe dabei "NodeMCU 1.2 (ESP8266-12E Module)" ausgewählt:


3. Der Code
Zu dem Code gibt es eigentlich nicht viel zu erklären. Einerseits muss man seine WLAN-Daten eingeben, darunter wird der DHT22-Sensor initialisiert. In "Loop" wird getestet ob man mit dem WLAN verbunden ist, sollte dies der Fall sein, so wird Temperatur und Luftfeuchte gemessen (float t und float h), diese Werte werden in einen String (requestString) geschrieben und dann als HTTP Get-Request an einen Webserver gesendet. Den Code für den Web-Server habe ich unten angefügt.

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
const char* ssid     = "***";
const char* password = "***";   
int wifiStatus;
    
#include "DHT.h"
#define DHTPIN D2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);
  // put your setup code here, to run once:

  Serial.print("Your are connecting to;");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  dht.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  wifiStatus = WiFi.status();
  
  if(wifiStatus == WL_CONNECTED){
    Serial.println("");
    Serial.println("Your ESP is connected!");  
    Serial.println("Your IP address is: ");
    Serial.println(WiFi.localIP()); 
    float t = dht.readTemperature(); 
    float h = dht.readHumidity(); 
    HTTPClient http;
    char requestString[255] = "";
    sprintf(requestString, "http://webServer.de/Send.php?name=TemperatureSensor&data=%f,%f", t, h);
    http.begin(requestString);  //Specify request destination
    int httpCode = http.GET();                                                                  //Send the request
    
    if (httpCode > 0) 
    { //Check the returning code
      String payload = http.getString();   //Get the request response payload
      Serial.println(payload);                     //Print the response payload
    }
    
    http.end();   //Close connection
    
    delay(300000);
  }
  else{
    Serial.println("");
    Serial.println("WiFi not connected");
    delay(1000);
  }
}

4. Der Server-Code
Auf meinem Raspberry Pi läuft ein Apache-Webserver. In /var/www liegt die Datei "Send.php", welche einfach Sensordaten annimmt und in .csv-Dateien schreibt. Dabei übergibt der Sensor im Get-Befehl seinen Namen, das wird dann auch der Dateiname, hier wird dann mit aktuellem Zeitstempel der Inhalt von "data" angehängt, was in diesem Fall die Temperatur in °C und die Luftfeuchte in % ist.

<html>
 <head>
  <title>Send Sensor Values</title>
 </head>
 <body>
  <?php

$name = htmlspecialchars($_GET["name"]);
$data = htmlspecialchars($_GET["data"]);

$date = date("Y-m-d") . "T" . date("H:i:s.u") . "Z";

$file = "data/".$name.".csv";
$Saved_File = fopen($file, 'a');
fwrite($Saved_File, $date . "," . $data . "\r\n");
fclose($Saved_File);

  ?>
 </body>
</html>

Sonntag, 11. September 2016

DHT11 Sensor mit Arduino auslesen

(English version) Vor einizger Zeit habe ich einen Temperatur- und Luftfeuchte-Sensor gekauft, die Bezeichnung lautet DHT11. Der Sensor ist einfach zu benutzen und verträgt sowohl 3,3V als auch 5V. Der Sensor hat 4 Pins, für den normalen Betrieb werden aber nur 3 Pins benötigt.
Der linke Pin (wie im Bild erkennbar) wird mit 5V verbunden, der rechte Pin mit GND. Der linke der beiden mittleren Pins ist der Signal-Pin, dieser wird mit einen beliebigen Digital-Pin am Arduino verbunden (in diesem Fall ist das Kanal 2). Außerdem wird noch ein 10k Widerstand zwischen dem Daten-Pin und 5V benötigt.

Als nächstes benötigt man die entsprechende Library, diese gibt es hier: Download. In der Arduino Software wird dann der Sensor wie folgt ausgelesen. Der Quellcode sollte selbsterklärend sein. Falls ein Sensor mit höherer Genauigkeit benötigt wird kann auch ein DHT22 benutzt werden, das muss dann entsprechend in DHTTYPE geändert werden.

#include "DHT.h"
#define DHTPIN 2

#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);

  dht.begin();
}

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  
  Serial.print("Humidity: ");
  Serial.print(h);
  Serial.print(" %\t");
  Serial.print("Temperature: ");
  Serial.print(t);
  Serial.println(" *C ");

  delay(2000);
}

Mittwoch, 23. September 2015

nrf24l01+ mit dem ATtiny84

(English version) Ich habe bereits erklärt, wie man das Funkmodul nrf24l01+ am Arduino bzw. ATmega betreibt. Die Details können in diesem Post nachgelesen werden. Möchte man das Funkmodul jedoch an einem ATtinyx4, wie zum Beispiel dem ATtiny84 betreiben, sind einige Anpassungen nötig.

In diesem Post möchte ich erklären, wie man eine Funkstrecke zwischen einem Arduino und dem ATtiny aufbaut. Ich verwende dazu das ping-Bespiel der Mirf-Library. Der Arduino sendet ein Ping-Signal aus, der ATtiny empfängt es und sendet es zurück.

Voraussetzung ist, dass ihr die ATtiny-Cores für die Arduino IDE installiert habt, wie ich es HIER beschrieben habe.

Nötige Libraries

Das Modul nrf24l01+ kommuniziert über SPI mit dem Mikrocontroller. Der ATtiny hat anders als der ATmega keine hardwareseitige Unterstützung für SPI, SPI muss also softwareseitig emuliert werden. Dies geschieht über das sogenannte Universal Serial Interface (USI). Dafür gibt es zum Glück wie immer eine fertige Library, wir müssen uns also um nix kümmern. Außerdem sind Änderungen an der Mirf-Library notwendig, doch auch hier hat sich schon jemand die Arbeit gemacht und eine passend Lib erstellt. Beide Libs können HIER herunter geladen werden. Nachdem ihr die beiden Ordner "Mirf" und "SPI85" in den entsprechenden "libraries"-Ordner der Arduino IDE kopiert habt, stehen euch die Libs wie gewohnt zur Verfügung.

Hardware

Die Verkabelung erfolgt ähnlich wie im Beispiel mit dem ATmega. Es müssen CSN, CE, SCK, MOSI und MISO sowie VCC und GND angeschlossen werden. Ich weise nochmal darauf hin, dass das Modul mit 3,3V betrieben werden muss! Folgende Grafik zeigt eine mögliche Verkabelung mit dem ATtiny84:
  
Wichtiger Hinweis: im Datenblatt des ATtiny gibt es zwar Pins, die mit MISO, MOSI und SCK bezeichnet sind, diese sind aber NUR zum Anschluss des Programmers (z.B. USBasp geeignet). Das Funkmodul muss mit den Pins USI-DI (entspricht MISO), USI-DO (entspricht MOSI) und USCK (entspricht SCK) verbunden werden. Standardmäßig sind dies die Pins D4 bis D6 (siehe Grafik oben). Prinzipiell sind die Pins aber frei wählbar. Wichtig ist, dass USI-DI nicht auf den MISO-Pin des Programmers gelegt werden darf. Dieses gilt ebenso für USI-DO und den MOSI-Pin. 

Software

Auf dem Arduino läuft unverändert das Beispiel "ping_client" der Mirf-Library. Auf dem ATtiny läuft eine angepasst Version des Beispiels "ping_server":

#include <SPI85.h>
#include <Mirf.h>
#include <MirfHardwareSpi85Driver.h>

// This USI was defined in SPI85.cpp
// Not to be confused with SPI (MOSI/MISO) used by ICSP pins
// Refer to page 61 of attiny84 datahseet
// USI pins could be redefined here
//#define USI-DO  5
//#define USI-DI  4
//#define USCK   6

#define CE    7    
#define CSN   3 

void setup(){

  
  /*   * Set the SPI Driver.   */

  Mirf.spi = &MirfHardwareSpi85;
    
  /*   * Setup pins / SPI.   */
  
  Mirf.cePin = CE;
  Mirf.csnPin = CSN;
  Mirf.init();
  
  /*   * Configure reciving address.   */
   
  Mirf.setRADDR((byte *)"serv1");
  
  /*   * Set the payload length to sizeof(unsigned long) the   * return type of millis().   *   * NB: payload on client and server must be the same.   */
   
  Mirf.payload = sizeof(unsigned long);
  
  /*   * Write channel and payload config then power up reciver.   */
   
  Mirf.config();

}

void loop(){
  /*   * A buffer to store the data.   */
   
  byte data[Mirf.payload];
  
  /*   * If a packet has been recived.   *   * isSending also restores listening mode when it    * transitions from true to false.   */
   
  if(!Mirf.isSending() && Mirf.dataReady()){

    /*     * Get load the packet into the buffer.     */
     
    Mirf.getData(data);
    
    /*     * Set the send address.     */
     
     
    Mirf.setTADDR((byte *)"clie1");
    
    /*     * Send the data back to the client.     */
     
    Mirf.send(data);
    
    /*     * Wait untill sending has finished     *     * NB: isSending returns the chip to receving after returning true.     */
      
    
  }
}

Der einzige Unterschied im Vergleich zur Version für den ATmega ist, dass SPI85.h sowie MirfHardwareSpi85Driver.h eingebunden werden und im setup() MirfHardwareSpi85 als SPI-Treiber ausgewählt wird:
Mirf.spi = &MirfHardwareSpi85;

Wenn ihr den Code mit eurem Programmer auf den ATtiny übertragt, sollte der pingende Arduino nun eine Antwort vom ATtiny bekommen.

Ein kleiner Hinweis zum Schluss: bei mir gab es Probleme mit dem Upload der Software über den USBasp, sobald das Funkmodul am ATtiny angeschlossen war. Das liegt vermutlich daran, dass sich der USBasp sowie das Modul die Pins am ATtiny teilen. Ich muss daher während dem flashen des ATtiny die Spannungsversorgung des Funkmoduls unterbrechen, dann klappt alles problemlos.

Dienstag, 22. September 2015

ATtiny mit der Arduino IDE programmieren - RELOADED

(English version) Vor einiger Zeit habe ich schon mal einen Beitrag zum Thema "ATtiny und Arduino" verfasst. In diesem habe ich erklärt, wie man die sogenannten "ATtiny-Cores" für Arduino installiert, damit man mit der Arduino IDE auch ATtinys programmieren kann.

Da ich für ein anderes Projekt einen anderen Core verwendet habe, möchte ich diesen hier auch nochmals kurz vorstellen. Es handelt sich dabei um Core-Dateien für ATtiny25, ATtiny45, ATtiny85, ATtiny24, ATtiny44 und ATtiny84.

Die nötigen Dateien können HIER heruntergeladen werden. Das entpackte Verzeichnis wird dann ebenfalls in den Ordner "hardware" kopiert. Die Ordnerstruktur sähe dann beispielsweise so aus: "C:\Programme\Arduino\hardware\tiny". Im Ordner "tiny" öffnet ihr den Unterordner "avr" und bennent dort die Datei "Prospective Boards.txt" um in "boards.txt" - sonst wird der Core von der IDE nicht erkannt.

Nach einem Neustart der Arduino IDE könnt ihr dann unter "Werkzeuge" - "Platine" den gewünschten ATtiny auswählen und ihn mit einem Programmer wie z.B. dem USBasp programmieren ("Sketch" - "Hochladen mit Programmer"). Wichtig ist, dass die Fuses im ATtiny richtig gesetzt sind und zur ausgewählten Platine passen.

Die Pinbelegung kann der Datei "pins_arduino.c" im Ordner "tiny\avr\cores\tiny" entnommen werden. Für den ATtinyx4 lautet sie beispielsweise:
//
//                                        +-\/-+
//                               VCC  1|    |14  GND
//                     (D  0)  PB0  2|    |13  AREF (D 10)
//                     (D  1)  PB1  3|    |12  PA1  (D  9)
//                               PB3  4|    |11  PA2  (D  8)
//  PWM  INT0  (D  2)  PB2  5|    |10  PA3  (D  7)
//    PWM        (D  3)  PA7  6|    |9   PA4  (D  6)
//    PWM        (D  4)  PA6  7|    |8   PA5  (D  5)        PWM
//                                       +----+

Dienstag, 10. Februar 2015

Serielle Kommunikation mit Matlab oder C#

(English version) Im vorigen Post (Herzschlag-Sensor ansteuern) habe ich erklärt, wie man mit einem Arduino über die serielle Schnittstelle den Wert des Herzschlag-Sensors sendet. In diesem Post will ich nun erklären, wie man diese Daten mit Matlab oder C# ausliest. Anfangen werde ich mit Matlab. Zuerst muss ein serieller Port geöffnet werden:

s = serial('COM3');
fopen(s);


Hier muss man einfach den entsprechenden COM-Port eintragen. Danach kann man auch schon alle verfügbaren Zeichen einlesen:

str = fscanf(s);

Das Problem daran ist allerdings, dass es auch sein kann, das eine Nachricht vom Arduino erst halb gesendet wurde und alle bisher gesendeten Zeichen eingelesen werden. Das kann man umgehen indem man z.B. alle gelesenen Zeichen in einen String schreibt und diesen dann ausliest. Am Ende sollte man nicht vergessen, den Port wieder zu schließen:

fclose(s);

Als nächstes möchte ich nun erklären wie man mit C# kommuniziert. Prinzipiell muss hier auch zuerst ein Port geöffnet werden:

port = new SerialPort("COM3", 9600);
port.Open();

Danach kann man direkt alle existierenden Zeichen lesen (analog zum Beispiel mit Matlab):

string indata = sp.ReadExisting();

In C# muss man den Port nicht unbedingt schließen, da dies beim Beenden des Programmes automatisch passiert.

Samstag, 24. Januar 2015

Herzschlag-Sensor ansteuern

(English version) Wenn man bei ebay nach "heartbeat sensor" sucht, findet man eine kleine Platine, welche mit LED und Phototransistor bestückt ist, welche angeblich den Herzschlag messen kann. Ich habe mir so ein Teil gekauft und es ausprobiert. Leider gibt es sehr wenig (ok, gar keine) Dokumentation dazu und nichtmal eine Seriennummer, alles was ich weiß ist das auf meinem Sensor xinda und Lcup drauf steht.
Der Herzschlag-Sensor
Der Sensor hat 3 Anschlüsse welche auch sehr toll beschriftet sind. Einer mit -, das ist der Anschluss für GND. Der nächste Anschluss hat ein S, das steht für Sensor, hier wird der Phototransistor ausgelesen. Dann gibt es noch den Pin in der Mitte ohne Beschriftung, das ist der Pin für die LED. Beschaltet wird der Chip ganz einfach: +5V an die LED und den Sensor-Ausgang an einen Analog-Eingang des Arduinos.

Das Programm liest nun praktisch den Sensor-Wert und sendet diesen über die serielle Schnittstelle an den Computer wo die Daten weiterverarbeitet werden können (z.B. mit Matlab, darüber werde ich vielleicht im nächsten Post etwas schreiben). Hier ist ein Bild von den geplotteten Daten mit Matlab und darunter der Code für den Arduino.
//Define pins for LED and sensor.
int ledPin = 13;
int sensorPin = 0;
//alpha is used in the original proposed code by the company (see below).
double alpha = 0.75;
//lasttime is used to have a very precise measurement of the time so the calculated pulse is correct.
unsigned long lasttime;

void setup ()
{
  //Switch on the LED.
  pinMode (ledPin, OUTPUT);
  digitalWrite(ledPin,HIGH);
  Serial.begin (9600);
  lasttime = micros();
}

void loop ()
{
  //Also used for smoothening the signal.
  static double oldValue = 0;
  
  //Wait 10 ms between each measurement.
  while(micros() - lasttime < 10000)
  {
    delayMicroseconds(100);
  }
  
  //Read the signal.
  int rawValue = analogRead (sensorPin);
  lasttime += 10000;
  
  //In the "original" code example, "value" was sent to the computer. This calculation basically smoothens "rawValue".
  //double value = alpha * oldValue + (1 - alpha) * rawValue;
  
  //Send back the measured value.
  Serial.println (rawValue);
  oldValue = value;
}

Dienstag, 13. Januar 2015

Arduino - Keypad auslesen

(English version) Heute möchte ich kurz vorstellen wie man ein simples Keypad, wie im folgenden Bild zu sehen, mit dem Arduino auslesen kann. Man könnte dazu z.B. dieses hier von ebay nehmen: Keypad.
Das Keypad hat 8 Anschlüsse. Jeweils 4 sind dabei für die Zeilen bzw. Spalten. Ein Tastendruck verbindet nun zwei dieser Anschlüsse. 4 Anschlüsse werden am Arduino als Output genutzt, die anderen 4 als Input. Ich habe Pin 22, 24, 26 und 28 als Output benutzt und 30, 32, 34, 36 als Input, man kann das Display dadurch sehr einfach anschließen, indem man eine Stiftleiste benutzt.
Nun legt man nacheinander an jeden Output eine Spannung an und misst dabei, welcher Input diese Spannung abbekommt. Dadurch weiß man sofort welche Tasten gedrückt wurden. Mein Code liest die Tasten aus und sendet, sobald sich der Status einer Taste geändert hat, dies über die serielle Schnittstelle an den Computer.


//For different sizes of Keypads, you can adjust the numbers here. Important: Also change the keyValues array!
const int numOuts = 4;
const int numIns = 4;
//These are the 4 output pins, adjust it if you use a different pin mapping
int outs[numOuts] = {22, 24, 26, 28};
//These are the 4 input pins, adjust it if you use a different pin mapping
int ins[numIns] = {30, 32, 34, 36};
//This array contains the values printed to the different keys
char keyValues[numOuts][numIns] = {{'1','2','3','A'},{'4','5','6','B'},{'7','8','9','C'},{'*','0','#','D'}};
//This array contains whether a pin is pressed or not
boolean pressed[numOuts][numIns];

void setup() {  
  Serial.begin(9600);
  //Define all outputs and set them to high
  for (int i = 0; i < numOuts; i++)
  {
    pinMode(outs[i], OUTPUT);
    digitalWrite(outs[i], HIGH);
  }
  //Define all inputs and activate the internal pullup resistor
  for (int i = 0; i < numIns; i++)
  {
    pinMode(ins[i], INPUT);
    digitalWrite(ins[i], HIGH);
  }
}

//Read whether a key is pressed
void KeyPressed()
{
  for (int i = 0; i < numOuts; i++)
  {
    //Activate (set to LOW) one output after another
    digitalWrite(outs[i], LOW);
    //Wait a short time
    delay(10);
    for (int j = 0; j < numIns; j++)
    {
      //Now read every input and invert it (HIGH = key not pressed (internal pullup), LOW = key pressed (because the output was set to LOW)
      boolean val = !digitalRead(ins[j]);
      //If the value changed, send via serial to the computer and save the value in the "pressed" array
      if (pressed[i][j] != val)
      {
        char str[255];
        sprintf(str, "%c pressed: %d", keyValues[i][j], val);
        Serial.println(str);
        pressed[i][j] = val;
      }
      
    }
    //Deactivate (set back to HIGH) the output
    digitalWrite(outs[i], HIGH);
  }
}

//Yeah, do this forever...
void loop()
{
  KeyPressed();
}

Sonntag, 14. Dezember 2014

Arduino - Barometrischen Höhensensor GY-65 auslesen

(English version) Eine weit verbreitete Möglichkeit, relative Höhen zu messen ist das Benutzen eines barometrischen Höhensensors. Dieser misst einfach den Druck und aus der barometrischen Höhenformel (Wikipedia) lässt sich daraus eine Höhe bestimmen. Da der Luftdruck sehr stark vom Tag abhängt wird normalerweise an einer bekannten Höhe der Sensor kalibriert und dann die restlichen Höhen relativ zu dieser Höhe und dem dort gemessenen Druck berechnet. Das liefert Höhen mit einer Genauigkeit von bis zu 1 Meter.

Wenn man bei ebay nach GY-65 sucht, findet man den entsprechenden Sensor für ca. 5 Euro. Der Sensor hat ein normales I2C-Interface, sodass nur 2 Kabel nötig sind. Für den Arduino braucht man dann die I2Cdev Library, die gibt es hier zum runterladen. Der Quellcode an sich ist maximal simpel, zuerst wird der Sensor initialisiert, dann wird eingestellt, dass der Druck gemessen werden soll und anschließen der Druck ausgelesen und daraus die Höhe berechnet. Ein komplettes, simples Beispielprogramm sieht so aus:


#include <I2Cdev.h>
#include "BMP085.h"
#include <Wire.h>

BMP085 barometer;

double pressure;
double altitude;


void setup() {
  Wire.begin();
  
  Serial.begin(9600);
  Serial.println("starting...");
    
  //initialize the barometer
  barometer.initialize();
}

void loop()
{
  // request pressure (3x oversampling mode, high detail, 23.5ms delay).
  // Let's just wait a bit more to be sure...
  barometer.setControl(BMP085_MODE_PRESSURE_3);
  delay(30);
  // read calibrated pressure value in Pascals (Pa)
  pressure = barometer.getPressure();
  // calculate absolute altitude in meters based on known pressure
  // (may pass a second "sea level pressure" parameter here,
  // otherwise uses the standard value of 101325 Pa)
  altitude = barometer.getAltitude(pressure); 
  
  // print back the calculated altitude
  Serial.println(altitude);
  // do this every second
  delay(1000);
}

I2C OLED Display

(english version) Wer bei Ebay nach "Arduino OLED Display" sucht, stößt auf ein OLED-Display mit einer Bildschirmdiagonale von ca. 1 Zoll und einer Auflösung von 128x84 Pixel. Das Display ist für weniger als 4 Euro zu haben. Angesteuert wird es über I2C. 1 Zoll ist natürlich nicht viel, aus Interesse habe ich mir das Display aber dennoch bestellt. Damit man sich vorstellen kann, wie klein es wirklich ist, habe ich eine Münze zum Vergleich daneben gelegt:

Dank Ansteuerung über I2C ist die Verkabelung denkbar übersichtlich: neben der Spannungsversorgung sind nur zwei Kabel für den I2C-Bus nötig, das wars. Nun stellt sich natürlich die Frage: wie steuere ich das Ding an? Nach kurzer Recherche habe ich heraus gefunden, dass es sich bei dem Display um einen Nachbau des OLED-Displays mit der Bezeichnung SSD1306 von Adafruit handelt. Dafür bietet Adafruit eine fertige Arduino-Library an, welche HIER erhältlich ist. Außerdem muss die Adafruit-GFX-Library herunter geladen und installiert werden. In der Arduino IDE findet sich dann unter "Adafruit_SSD1306" das Beispiel "ssd1306_128x64_i2c". Wer dieses Beispiel nun einfach so 1:1 auf den Arduino überträgt sieht erstmal: gar nichts. Das China-Display hat eine andere I2C-Adresse wie das Original von Adafruit, daher muss in der setup()-Funktion eine kleine Änderung durchgeführt werden: die Adresse muss von 0x3D in 0x3C geändert werden. Aus
  display.begin(SSD1306_SWITCHCAPVCC, 0x3D);  // initialize with the I2C addr 0x3D (for the 128x64)
wird
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x64).
Dann dann kann das Beispiel auf den Arduino übertragen werden. Ihr solltet jetzt eine kleine Demonstration der verschiedenen Funktionen der GFX-Library auf dem Display sehen. Da die Funktionen und somit auch das Beispiel sehr umfangreich sind, werde ich sie nicht im Detail erläutern. Im Prinzip sind die Funktionen im Beispiel alle selbsterklärend. Bei Unklarheiten schaut in die Header-Datei der GFX-Library, dort ist auch erläutert, welche Parameter die einzelnen Funktionen erwarten. Es gibt die Möglichkeit Text auszugeben, außerdem lassen sich Linien und diverse Formen (Rechteck, Dreieck, ...) mit und ohne Füllung zeichnen. Auch das Invertieren des Bildschirms sowie das Scrollen sind möglich.

Außerdem kann ein vordefiniertes Bitmap ausgegeben werden. Dies möchte ich kurz näher erklären:

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define XPOS 50
#define YPOS 25
#define BITMAP_HEIGHT 16 
#define BITMAP_WIDTH  16 
static const unsigned char PROGMEM bitmap[] =
{ B00000001, B10000000,
  B00000010, B01000000,
  B00000100, B00100000,
  B00001000, B00010000,
  B00010000, B00001000,
  B00111111, B11111100,
  B00101000, B00010100,
  B00100100, B00100100,
  B00100010, B01000100,
  B00100001, B10000100,
  B00100010, B01000100,
  B00100100, B00100100,
  B00101000, B00010100,
  B00110000, B00001100,
  B00111111, B11111100,
  B00000000, B00000000 };

void setup()   {                

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x64)
  
  //clear display buffer
  display.clearDisplay();
  //draw a bitmap stored in variable bitmap with given size at XPOS, YPOS in color white
  display.drawBitmap(XPOS, YPOS, bitmap, BITMAP_WIDTH, BITMAP_HEIGHT, WHITE);
  display.display();
 
}


void loop() { 
}

In diesem Beispiel zeichnen wir ein Bitmap mit einer Größe von 16x16 Pixeln. Die Größe des Bitmaps kann natürlich angepasst werden. Das Bitmap muss in einem Character-Array hinterlegt sein. Besonders wichtig ist hier bei der Deklaration das Schlüsselwort "PROGMEM". Es sorgt dafür, dass die Variable im Flash-Speicher des Arduinos abgelegt wird. Dies wird von der Library so erwartet. Im Beispiel speichern immer jeweils zwei Character in insgesamt 16 Bit den Inhalt einer Zeile. Ist ein Bit gesetzt, wird beim später Zeichnen ein Pixel gesetzt. Der Übersicht halber habe ich im Code Zeilenumbrüche eingefügt. Eine Zeile im Code entspricht nachher eine Zeile des Bitmaps. Zur Darstellung des Bitmaps wird dann der Befehl drawBitmap() aufgerufen. Als Parameter werden übergeben: die X- und Y-Position des Bitmaps, die Variable, in der das Bitmap gespeichert ist, die Breite und die Höhe des Bitmaps sowie die Farbe (WHITE oder BLACK), in der ein gesetzter Pixel dargestellt werden soll.
Das Ergebnis:

Freitag, 31. Oktober 2014

Arduino Nano V3.0 Klon mit CH340G in Betrieb nehmen

Bei Ebay gibt es einen Klon des Arduino Nano schon für unter 3 Euro zu kaufen. Meist unter dem Namen "Nano V3.0 ATmega328 CH340G" mit dem Zusatz "for Arduino" oder "Arduino compatible", wie zum Beispiel hier. Bei diesen Klonen kommt als USB-Seriell-Wandler nicht wie beim Original ein FT232R sondern (wie der Name schon erkennen lässt) ein CH340G zum Einsatz. Daher sind auch die Treiber der Arduino IDE ungeeignet, stattdessen muss ein extra Treiber von Hand installiert werden. Dieser kann hier beim Hersteller herunter geladen werden. Sollte der Link direkte nicht mehr funktionieren, einfach nach "ch341ser.zip" suchen. Es sind auch Treiber für MAC oder Linux erhältlich. Ich werde hier nur auf die Installation unter Windows eingehen. Zuerst muss das ZIP-Archiv entpackt werden. Falls die Version für 64bit-Windows benötigt wird, muss dann die Datei "DRVSETUP64.exe" aus dem Ordner "DRVSETUP64" in den darüberliegenden Ordner (der unter anderem die Datei "CH341SER.INF" enthält) kopiert werden. Je nach System muss dann die "SETUP.EXE" (32bit) oder die "DRVSETUP64.exe" (64bit) ausgeführt werden. Dann wird auf "INSTALL" geklickt und der Treiber damit installiert.  Damit die Installation des Treibers reibungslos abläuft, darf das Board dabei NICHT eingesteckt sein. Erst NACH der Installation wird der Arduino Nano eingestöpselt. Er sollte dann von Windows erkannt werden.

Sonntag, 31. August 2014

Spannung überwachen am Arduino, ATmega oder ATtiny

(English version)
Wer seinen Arduino oder einen ATmega bzw. ATtiny über eine Batterie mit Spannung versorgt muss immer damit rechnen, dass die irgendwann einmal leer ist und dann der Mikrocontroller ohne Vorwarnung den Dienst einstellt. Hier ist es hilfreich, die Versorgungsspannung zu überwachen. Sollte sie unter einen bestimmten kritischen Wert fallen, kann man sich zum Beispiel durch anschalten einer LED warnen lassen.
Die Überwachung der Spannung kann ohne zusätzliche Hardware erfolgen. Möglich macht es der AD-Wandler. Dieser arbeitet ja bekannterweise nach folgender Formel:
WERT_ADC = V_EINGANG * 1023/V_REFERENZ
Nun könnte man natürlich meinen: "Ich lege einfach meine Versorgungsspannung an den Eingang des AD-Wandlers und kann diese so messen". Das funktioniert leider nicht, da die Referenzspannung des AD-Wandler ebenfalls die Versorgungsspannung ist. Der Wert des AD-Wandlers wäre als immer 1023.
Doch es gibt einen Trick: jeder Mikrocontroller erzeugt eine interne Referenzspannung, die so genannte "Bandgap Voltage" V_BG. Diese beträgt beim ATmega328, der im Arduino Uno zum Einsatz kommt, 1.1V.  Diese Spannung lässt sich auf den Eingang des AD-Wandlers legen. mit V_EINGANG = V_BG und V_REFERENZ = V_CC ergibt sich dann:
WERT_ADC = V_BG * 1023/V_CC
oder umgeformt:
V_CC = V_BG * 1023/WERT_ADC
Setzt man nun ein V_BG = 1100 (in mV) ergibt sich:
V_CC = 1100 * 1023/WERT_ADC
V_CC ist nun der Wert der Spannungsversorgung in mV

Umgesetzt in Code sieht das dann so aus:

int led = 13;  //Pin der LED
int adc_low, adc_high;  //Zwischenspeicher für die Ergebnisse des ADC
long adc_result;  //Gesamtergebnis der Messung des ADC
long vcc;  //Versorgungsspannung

void setup() {    
  pinMode(led, OUTPUT);  

  ADMUX |= (1<<REFS0); //VCC als Referenzspannung für den AD-Wandler
  ADMUX |= (1<<MUX3) | (1<<MUX2) | (1<<MUX1);  //1.1V Referenzspannung als Eingang für ADC 
  delay(10);  //warten bis sich die Referenzspannung eingestellt hat
  ADCSRA |= (1<<ADEN);   //ADC aktivieren
}

// the loop routine runs over and over again forever:
void loop() {    
  ADCSRA |= (1<<ADSC);  //Messung starten

  while (bitRead(ADCSRA, ADSC));  //warten bis Messung beendet ist
  //Ergebnisse des ADC zwischenspeichern. Wichtig: zuerst ADCL auslesen, dann ADCH
  adc_low = ADCL;
  adc_high = ADCH;

  adc_result = (adc_high<<8) | adc_low; //Gesamtergebniss der ADC-Messung 
  vcc = 1125300L / adc_result;  //Versorgungsspannung in mV berechnen (1100mV * 1023 = 1125300)

  //wenn Spannung kleiner als 5V 
  if (vcc < 5000)
  {
    digitalWrite(led, HIGH);  //schalte LED an
  }
  //wenn größer oder gleich 5V
  else 
  {
    digitalWrite(led, LOW);  //schalte LED aus
  }

  delay(500);
}


In der setup()-Funktion wird zuerst der AD-Wandler so eingestellt, dass zum einen die Versorgungsspannung V_CC als Referenz und zum anderen die Bandgap-Spannung V_BG als Eingang für den AD-Wandler dienen. Nach einer kurzen Wartezeit wird der AD-Wandler dann aktiviert.
In der loop() wird dann die Messung durch setzen des Bits ADSC gestartet. Ist die Messung beendet (ADSC ist wieder 0) wird das Ergebnis der Wandlung aus den Registern ADCL (die unteren 8 Bit) und ADCH (die oberen 2 Bit) ausgelesen und zu einem 10-Bit-Ergebnis zusammengesetzt. Aus diesem wird dann nach der obigen Formel die Versorgungsspannung bestimmt. Liegt sie unter 5V, wird eine LED eingeschaltet.

Diese Methode funktioniert prinzipiell mit allen ATmegas oder ATtinys. Je nach verwendetem µC müssen allerdings in den Registern des AD-Wandlers andere Bits gesetzt werden. Welche das genau sind, kann dem Datenblatt entnommen werden.

Mittwoch, 20. August 2014

ATmega - Bodenfeuchte von Zimmerpflanzen messen

(English version) Heute möchte ich vorstellen, wie man einen Feuchtigkeitssensor baut und ansteuert. Die Grundidee dahinter ist, zu messen wie feucht die Erde einer Pflanze ist und dann dementsprechend zu wissen wann man gießen muss.

Für den Bau des Sensors wird nicht viel benötigt. Ich habe ein kleines Stück Streifenrasterplatine, 2 Nägel und Stiftleisten benutzt. Der Sensor sieht dann so aus:
Nun misst man den Widerstand zwischen den zwei Nägeln (wenn der Sensor in der Erde steckt) und schon kann man Rückschlüsse auf die Bodenfeuchte ziehen. In der Schaltung sieht das dann folgendermaßen aus:
Bei feuchter Erde leiter der Sensor gut, also misst der ATmega eine hohe Spannung (zu interpretieren als Erde ist feucht). Bei trockener Erde steigt der Widerstand, daher sinkt die gemessene Spannung. Natürlich kann man jetzt noch überlegen welche Einheiten die Achsen bekommen und so weiter, ich denke jedoch es ist sinniger, einfach 0 bis 5 V aufzutragen (oder 0 bis 100%) und dann selbst zu überprüfen wann die Pflanze gegossen werden muss - dies ist dann der Wert bei dessen Unterschreitung eine rote Lampe an geht oder eine automatische E-Mail gesendet wird.