Samstag, 25. November 2017

Konfigurierbarkeit des Feuchtigkeitssensor HTU21D


Wenn man im Internet sucht, finden sich viele Beispiele zur Programmierung einiger Sensoren, die ich ebenfalls selbst verwende. Die meisten Codeschnipsel funktionieren auch auf dem Wemos. Dennoch muss noch etwas herum probiert werden, um bestimmte Schwierigkeiten anzugehen, damit auch das erwartete Ergebnis kommt.

HTU21D, HTU21, SHT21
Wird ein Sensor gelesen bekommt man nach diesem Vorgang einen Rohwert, der dann in einen für uns bekannten und lesbaren Wert umgerechnet wird. Wir können dies ohne weiteres nach Datenblatt tun oder einen fertigen Beispiel Code verwenden.
Ich wollte meinen Programmcode mit anderen Beispielen Vergleichen, auf Grund einer Konfigurierbarkeit des Sensors. Leider war dazu auf Anhieb nichts zu finden, womit ein Grund bestand sich damit selbst auseinander zu setzten.
In diesem Fall ist es der Feuchtigkeit Sensor HTU21D und ist auch unter HTU21 oder SHT21 zu finden (Nicht ganz sicher, ob alle dieselben sind). Abgesehen Technischer Unterschiede, können alle drei mit dem gleichen Programmcode angesteuert werden. Vor ein paar Jahren hatte ich bereits ein Beispiel Code mit C# und .NET Micro Framework geschrieben, in dem ich damals keine Implementierung der Einstellmöglichkeiten vornahm.

Für den Sensor sind drei Register verfügbar, mit denen die Einstellungen abgefragt, festgelegt oder zurückgesetzt werden können.
  • Write user register 0xE6
  • Read user register 0xE7
  • Soft Reset 0xFE
Grundeinstellung
Wird der Sensor nur gestartet und gelesen, dann bestehen die Grundeinstellungen. Diese sollten im Normalfall reichen. Bei Speziellen Anwendungen könnten dieses Lesevorgänge zu lange dauern. Mit dem Kompromiss der Genauigkeit, kann der Lesevorgang für die Feuchtigkeit von 18ms auf 3ms gesengt werden. Anstatt der 12bit, werden jedoch nur 8bit verwendet.

Genauigkeit
Zeit
12bit
18ms
11bit
9ms
10bit
5ms
8bit
3ms

Abruf und Zuweisen der Einstellung
Im Grunde ist dies so simpel wie der Abruf der Sensor Daten. Für die Einstellwerte ist jedoch etwas umdenken erforderlich, da diese in einem Byte verpackt sind. Ein Byte besteht aus 8Bit und verfügt damit so acht Schalter für bestimmte Einstellungen. Zum Nachvollziehen habe ich aus dem Datenblatt die Tabelle mit den zugehörigen Bit Schaltern abgebildet.


Einstellen und Auswerten
Mit dem 'Read user register' 0xE7 kann die aktuelle Einstellung abgerufen werden. Das kann hilfreich sein, um die geschrieben Einstellungen gegen zu prüfen.

 void Htu21ReadUserRegister() {  
  Wire.beginTransmission(HTU21D_ADDRESS);  
  Wire.write(command);              
  Wire.endTransmission();  
  Wire.requestFrom(HTU21D_ADDRESS, 1);   
  while(Wire.available() < 1) { }  
  byte regCon = Wire.read();  
  Serial.print("\tRegister Result: ");   
  Serial.println(regCon, BIN);         
 }  

Das folgende Beispiel beschreibt die Einstellung 'RH 11Bit / Temp 11Bit', 'On-Chip Header' aus und 'Disable OTP Reload' aus.

 void Htu21WriteRegisterSetting() {  
  byte command = 0xC0;  // RH 11bit / 11Bit Temp  
  command |= 0x01;     // disable otp reload  
  Wire.beginTransmission(HTU21D_ADDRESS);  
  Wire.write(0xE6);       // command for write register command  
  Wire.write(command);  // write the finish command  
  Wire.endTransmission();  
 }  

War das Schreiben der Einstellung erfolgreich, sollte mit der Methode 'Htu21ReadUserRegister()' das Binäre Ergebnis '1100 0001' ausgegeben werden.

Beispiel Projekt Wetterstation
Auf dem Github Repository stelle ich diesmal das kleine Projekt zur Verfügung, in dem ich bereits den HTU21 und den BMP180 Sensor verwende. Dort sind einige Methoden etwas weiter ausgeführt und zum Teil Kommentiert. Soweit ich immer Zeit habe, erweitere ich das Projekt mit Möglichst überschaubaren Funktionen.

Samstag, 18. November 2017

Servo mit dem Xbox Controller steuern


Das ansteuern eines Servos über einen Xbox One Controller ist simpel umzusetzen. Für dieses Beispiel wird folgendes verwendet:

  • MonoGame
  • Xbox Controller
  • Arduino UNO oder vergleichbar
  • Servo
  • Motor Shield
  • Externe Batterie

Nach der Installation von MonoGame sind in Visual Studio mehre Vorlagen verfügbar. Benötigt wird das Template 'MonoGame Windows Project', das im folgenden Bild als erstes in der Liste erscheint.


Programmcode mit MonoGame
Sobald das Projekt angelegt würde, könnt ihr die Game1.cs Datei öffnen. Für die Verbindung zum Arduino wird die Klasse SerialPort verwendet. Dazu sollte vorher bekannt sein, welcher COM Port bei euch der Arduino verwendet. Die Baudrate von 115200 ist die maximale Geschwindigkeit, die zuverlässig funktioniert. Die restlichen Parameter sind die Default Werte der Seriellen Verbindung zum Arduino (siehe Programmcode).
Die überschriebenen Methoden 'Initialization()', 'LoadContent()' und 'Draw()' werden für das Beispiel nicht verwendet und können entfernt werden.
Mit der Update Methode wird der Xbox Controller eingelesen. Der Input Wert vom Stick kommt dann aus dem GamePadState unter '.ThumbSticks.Left.X'. Der Float Wert geht von -1 bis +1 und wird deshalb zunächst um plus eins dass Offset verschoben. Anschießend wird der Float Wert in ein Byte Wert transformiert, dass dann anschließend an den Serial Port geschrieben wird.
Damit die Anwendung nicht zu viele Bytes versendet, wird am Ende 50 Millisekunden gewartet. Ansonsten läuft der Buffer im Arduino über.

GraphicsDeviceManager _graphics;  
 private SerialPort _serialPort;  
 public Game1()  
 {  
   _graphics = new GraphicsDeviceManager(this);  
   // Verwendete COM Port muss ggf. angepasst werden.  
   this._serialPort = new SerialPort("COM5", 115200, Parity.None, 8, StopBits.One);  
   this._serialPort.Open();  
 }  
 protected override void UnloadContent()  
 {  
   this._serialPort.DiscardOutBuffer();  
   this._serialPort.Close();  
 }  
 protected override void Update(GameTime gameTime)  
 {  
   GamePadState state = GamePad.GetState(PlayerIndex.One);  
   byte stickValue = (byte)((state.ThumbSticks.Left.X + 1) * 90);  
   this._serialPort.Write(new byte[] { stickValue }, 0, 1);  
   Task.Delay(50).Wait();  
 }  

Arduino als Empfänger
Der Arduino muss nun die empfangenden Bytes vom PC entgegen nehmen und daraus ein PWM Signal erzeugen das zur Steuerung des Servos verwendet wird. Weiters habe ich im Programmcode beschrieben.

 #include <Servo.h>  
 Servo servo;  
 int servoPin = 9;  
 int receivedValue;  
 void setup() {  
  // Muss die selbe Baudrate haben,   
  // wie in der MonoGame Anwendung.  
  Serial.begin(115200);  
  // die meisten Servos arbeiten mit einem   
  // HIGH Signal von 1ms bis 2ms.  
  servo.attach(servoPin, 1000, 2000);  
 }  
 void loop() {  
  if( Serial.available() > 0){  
   receivedValue = Serial.read();  
   // Den eingelesenen Wert Abschneiden,  
   // falls der maximale Stellwert überschritten wird.  
   if(receivedValue > 180) {  
    receivedValue = 180;  
   }  
   else if(receivedValue < 0) {  
    receivedValue = 0;  
   }  
   // alle weiteren eingegangenen bytes werden verworfen  
   Serial.flush();  
  }  
  // Aktuellen Einstellwert festlegen.  
  servo.write(receivedValue);  
  delay(20);  
 }  

Was nun
Im Grunde ist die Ausführung simpel und das Schreiben dieses Post hat da deutlich mehr Zeit benötig. :D
Aber was kann man nun mit dem Wissen anstellen? Nun, man kann stattdessen einer  Kabelgebundenen Ausführung, auf eine Bluetooth Funkverbindung wechseln. Damit ließe sich dann ein RC Model ansteuern. Mag etwas Umständlich sein, die Fernsteuerung gegen etwas eigenes gebasteltes zu ersetzen, aber darin liegt der Spaß.

Zur Vollständigkeit wieder ein Link zu dem Github Repository und dem gesamten Beispiel Programmcode.

Nachtrag
Leider funktioniert dieses einfache Beispiel nur in dieser Konstellation. Sobald versucht wird, zwei Bytes zu versenden, treten Probleme auf mit der Seriellen Verbindung.
Wie man das behebt, beschreibe ich mit dem Post "Zeichnen auf dem TFT mit dem XBox Controller"





Sonntag, 5. November 2017

Unterdruck und Höllentemperatur bei falschen Valuetype (Wemos)


Wenn die Ergebnisse nicht den Erwartungen entsprechen, dann ist mit Sicherheit etwas falsch. Das geschah diesmal mit dem Wemos D1 Mini. Einen bereits fertiges Code Beispiel für das Auslesen eines BMP085 Sensors mit einem Arduino, verwendete ich diesmal auf dem Wemos. Nach dem hochladen zeigten sich die nicht erwartenden Ergebnisse. Zumindest war offensichtlich, dass in meiner Wohnung keine 119 Grad Celsius herrschten und bei einem Luftdruck von 4000 Pascal wäre ich sicherlich an Sauerstoffmangel oder kochendem Blut auseinander gegangen. Also musste was an der Berechnung nicht stimmen.


Behoben
Der Fehler ließ sich relativ schnell beheben. Die Verwendeten ValueTypes int und unsigned int wurden ersetzt durch int16_t und unt16_t.

Aber warum
Ein ValueType INT ist immer das gleiche, solange die Variable als INT definiert wird auf einem System. Das eine System ist die Arduino Plattform mit dem 8Bit Mikrocontroller. Der Wemos verwendet wiederum einen 32Bit Mikrocontroller. Das sollte als erstes Auffallen und schnell sollte klar sein, dass etwas mit den Typen etwas nicht stimmt und folglich nicht mit den richtigen Werten rechnet. Eigentlich ist das von Compiler abhängig, welcher Type aus einem INT angelegt wird.
Der Folgende Code macht die Größe eines INT sichtbar, sobald ihr das auf der Ziel Plattform ausführt.

 void setup() {  
  Serial.begin(9600);  
  Serial.print("int: ");   
  Serial.println(sizeof(int), DEC);  
  Serial.print("unsigned int: ");   
  Serial.println(sizeof(unsigned int), DEC);  
  Serial.print("int16_t: ");   
  Serial.println(sizeof(int16_t), DEC);  
  Serial.print("uint16_t: ");   
  Serial.println(sizeof(uint16_t), DEC);  
 }  

Wie man sieht, wird auf dem Wemos kein 16Bit INT, sondern 32Bit INTEGER angelegt

Arduino Nano
Wemos D1 Mini



Aber Moment mal. Damit wäre eine Berechnung auf dem Wemos genauer und der Rechenfehler dürfte erst gar nicht auftreten. Sieht man sich allerdings die Funktionsinhalte an, dann sind mehrere Stellen auffällig, wo die Werte durch Byteshifting hoch oder runter gerechnet werden. Das nur passt wiederum nur mit 16 Bit. Andernfalls muss die Funktion geändert werden.

Zur Vollständigkeit der Beispielcode mit den geänderten ValueTypes. In diesem Fall reichten die Änderungen für INT aus und kann nun für Arduino oder Wemos verwendet werden.

 #include <Wire.h>  
 #define BMP085_ADDRESS 0x77 // I2C address of BMP085  
 const unsigned char OSS = 0; // Oversampling Setting 
// Calibration values
int16_t ac1, ac2, ac3, b1, b2, mb, mc, md;
uint16_t ac4, ac5, ac6;
long b5;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  bmp085Calibration();
}

void loop() {
  uint16_t rawValueUt = bmp085ReadUT();  // MUST be called first
  float temperature = bmp085GetTemperature(rawValueUt);
  uint16_t rawValueUp = bmp085ReadUP();
  float pressure = bmp085GetPressure(rawValueUp);
  float atm = pressure / 101325; // "standard atmosphere"
  float altitude = calcAltitude(pressure); //Uncompensated caculation - in Meters 

  Serial.print("Temperature: "); Serial.print(temperature, 1); Serial.println(" deg C");
  Serial.print("Pressure: "); Serial.print(pressure, 0); Serial.println(" Pa");
  Serial.print("Standard Atmosphere: "); Serial.println(atm, 4);
  Serial.print("Altitude: "); Serial.print(altitude, 2); Serial.println(" M");
  Serial.println();
  delay(2000);
}

void bmp085Calibration(){
  ac1 = bmp085ReadInt(0xAA); Serial.print("ac1"); Serial.print(ac1, DEC);
  ac2 = bmp085ReadInt(0xAC); Serial.print(" ac2"); Serial.print(ac2, DEC);
  ac3 = bmp085ReadInt(0xAE); Serial.print(" ac3"); Serial.print(ac3, DEC);
  ac4 = bmp085ReadInt(0xB0); Serial.print(" ac4"); Serial.print(ac4, DEC);
  ac5 = bmp085ReadInt(0xB2); Serial.print(" ac5"); Serial.print(ac5, DEC);
  ac6 = bmp085ReadInt(0xB4); Serial.print(" ac6"); Serial.print(ac6, DEC);
  b1 = bmp085ReadInt(0xB6); Serial.print(" b1"); Serial.print(b1, DEC);
  b2 = bmp085ReadInt(0xB8); Serial.print(" b2"); Serial.print(b2, DEC);
  mb = bmp085ReadInt(0xBA); Serial.print(" mb"); Serial.print(mb, DEC);
  mc = bmp085ReadInt(0xBC); Serial.print(" mc"); Serial.print(mc, DEC);
  md = bmp085ReadInt(0xBE); Serial.print(" md"); Serial.print(md, DEC); 
  Serial.println();
}
// Calculate temperature in deg C
float bmp085GetTemperature(uint16_t ut){
  long x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  long x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

  return (float)((b5 + 8)>>4) / 10.0;
}
// calculate pressure from raw value
long bmp085GetPressure(unsigned long up){

  long b6 = b5 - 4000;
  // Calculate B3
  long x1 = (b2 * (b6 * b6)>>12)>>11;
  long x2 = (ac2 * b6)>>11;
  long x3 = x1 + x2;
  long b3 = (((((long)ac1)*4 + x3)<>2;

  // Calculate B4
  x1 = (ac3 * b6)>>13;
  x2 = (b1 * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  unsigned long b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
  
  unsigned long b7 = ((unsigned long)(up - b3) * (50000>>OSS));

  long p;
  if (b7 < 0x80000000)
    p = (b7<<1 b4="" else="" p="(b7" x1="(p">>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;

  return p;
}
// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address){
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available()) {}
  return Wire.read();
}
// read two byte 
int16_t bmp085ReadInt(unsigned char address){
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();

  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2 0x34="" 0xf4="" 0xf6="" 0xf7="" 0xf8="" 16="" 4.5ms="" 8="" a="" and="" at="" bmp085readint="" bmp085readup="" bmp085readut="" char="" conversion="" delay="" dependent="" for="" int16_t="" into="" least="" long="" lsb="" msb="" on="" oss="" oversampling="" pressure="" raw="" read="" reading="" register="" request="" return="" setting="" temperature="" the="" time="" uint16_t="" uncompensated="" unsigned="" up="(((unsigned" value="" w="" wait="" wire.begintransmission="" wire.endtransmission="" wire.write="" write="" x2e="" x34="" xf4="" xf6="" xlsb="">> (8-OSS);
  Serial.print("Pressure Raw Value: "); Serial.print(up, DEC); Serial.println();
  return up;
}
// calculate altitude by pressure value
float calcAltitude(float pressure){
  float alti = pow((pressure/101325),(1/5.25588));
  return (1 - alti) /0.0000225577;
}


Die dritte Verfeinerung und mir fällt ein Muster auf

Für das Ausweichen eines Hinderniss, wurde nun dem Robot eine Kamera und zwei Abstandssensoren hinzugefügt. Zusätzlich wird die La...