Inhalt
Einleitung
Soweit sind wir also: der PITS läuft und sendet seine Daten, allenfalls Bilder, auf UHF mit RTTY aus und diese können wir empfangen. Nachdem somit die Übertragung funktioniert, kann man sich um die effektiven Daten kümmern. Es stellen sich die Fragen:
- welche Sensoren sind beim PITS onboard mit dabei? Was wurde schon ausprogrammiert?
Antwort gibt: http://www.pi-in-the-sky.com/index.php?id=advanced - welche Schnittstellen können noch genutzt werden: I2C, SPI, UART, Software Serial…
- wie können völlig andere Sensoren angebunden werden?
- wie versteht man die neuen Daten bei der Empfangsseite?
Schritt für Schritt gehe ich in den nächsten Blogs diesen Themen nach. Als erstes will ich ein paar der häufigsten Sensoren einbinden.
Updates und Tools
Bevor man selber Erweiterungen programmiert, könnte eine Update der Tracker Software nützlich sein:
sudo apt-get -update sudo apt-get -upgrade sudo shutdown -r now
und dann
cd ~/pits git pull origin master cd tracker make
Anbindung der Temparatursensoren DS18B20
Das PITS Board hat einen eingebauten DS18B20 Temperatursensor. Ein zweiter kann mit 3 Drähten (TO-92, 1-Wire Interface) angelötet werden. Software-seitig ist dazu höchstens in /boot/skipy.txt eine Zeile external_temperature=0 oder external_temperature=1 anzufügen, wenn die Sensordaten vertauscht sein sollten.
Anbindung eines externen Sensors BMP180
Das PITS Board bringt bereits alles nötige mit, um einen Temperatur/Drucksensor des Typs BMP085 (Bosch, I2C Interface) anzubinden. Mein Vorhaben ist die Anbindung eines BMP180 per I2C; er BMP180 ist kompatibel mit dem BMP085. Sobald dies gelungen und verstanden ist, steht der Erweiterung mit anderen I2C Sensoren nichts mehr im Wege.
Die elektronische Anbindung mit I2C ist klar (siehe auch PITS Homepage). Die Software Anpassung für den PITS besteht aus der Zeile „enable_bmp085=Y“, die in /boot/pisky.txt stehen muss.
Es lohnt sich, schon von Anfang ein paar I2C Werkzeuge zu installieren, z.B. die i2c Tools:
sudo apt-get install -y python-smbus
(die i2c-Tools sind darin enthalten)
Damit entdecken wir dann den BMP180 Sensor wie ewartet unter seiner Adresse 0x77:
Der Telemetriestring wird automatisch um Temperatur und Druck ergänzt:
ohne Drucksensor:
$$PI9TSS,7,00:00:00,0.00000,0.00000,00000,0,0,0,30.8,4.1,0.175*232D
mit Drucksensor:
$$PI9TSS,22,00:00:00,0.00000,0.00000,00000,0,0,0,30.4,4.1,0.175,21.7,954*E2D8
Anbindung eines neuen Sensors
Wenn wir einen neuen, dem PITS noch nicht bekannten, Sensor anbinden wollen, gehen wir in 3 Schritten vor:
- Den Sensor verstehen und ohne PITS in C/C++ beherrschen.
Adafruit, Sparkfun oder die Arduino Foren helfen hier häufig mit tollen Anleitungen. - PITS Source Code mit dem Code aus 1) erweitern: neuen Code richtig in die vorhandene Filestruktur und Telemetrie einbinden
- Anpassungen an der Empfangsseite vornehmen
Das generelle Vorgehen ist beim PITS unter „Extra Functionality“ beschrieben. Meine PITS Boards sind alle relativ neu und schon mit I2C Schnittstelle ausgerüstet.
Der dritte Punkt ist weniger klar: wie kommen wir an die neuen Messwerte heran? Beim Ballon-Habitat System ist das beschrieben im payload Dokument, aber wie geht das z.B. bei APRS und bei anderen Internet-Diensten? Welches Format ist einzuhalten? Für APRS gibt es im direwolf Paket die Anleitung APRS Telemetry Toolkit.pdf und weitere. Dies ist ein Thema für einen kommenden Blog.
Anbindung eines Sensors am Beispiel eines BME280
Der BME280 ist ein verbessertes Nachfolgeprodukt der erwähnten Sensoren. Wichtigster Unterschied ist die zusätzliche SPI Schnittstelle und der zusätzliche Feuchtesensor (Beschreibung bei Adafruit). Es gibt verschiedene Module mit diesem Sensor: bei Adafruit mit Stromversorgung, andere nur mit dem nackten Sensor. So eines habe ich von Sparkfun – Gewicht vielleicht 5 Gramm…
Technische Daten:
- Temp: –40C to 85C
- Humidity: 0 – 100% RH, =-3% from 20-80%
- Pressure: 30,000Pa to 110,000Pa, relative accuracy of 12Pa, absolute accuracy of 100Pa
- Altitude: 0 to 30,000 ft (9.2 km), relative accuracy of 3.3 ft (1 m) at sea level, 6.6 (2 m) at 30,000 ft.
Alle Informationen zum Modul und Programmierung, Hintergundwissen dazu: SparkFun BME280 Breakout Hookup Guide
Wir müssten also die Software des PITS anpassen, dass auch die Feuchtigkeit übertragen wird (zusätzlich die Feuchtedaten auslesen und in den Telemetriestring einbauen). Es stellt sich heraus, dass dieser Sensor bereits vom Tracker vollständig verstanden wird. In der Dokumentation ist das noch nicht erwähnt. Im Code tracker.c sieht man aber
if (Config.EnableBME280) { sprintf(ExtraFields2, ",%.1f,%.0f,%0.1f", GPS->BMP180Temperature, GPS->Pressure, GPS->Humidity); }
Die struct Config erhält in tracker.c die Einstellungen aus der pisky.txt; mit der Option enable_bme280=Y kann man den BME280 einschalten:
ReadBoolean(fp, "enable_bme280", -1, 0, &(Config->EnableBME280)); if (Config->EnableBME280) { printf("BME280 Enabled\n"); }
Im Hauptprogramm wird der Thread eingebunden:
pthread_t PredictionThread, LoRaThread, APRSThread, GPSThread, DS18B20Thread, ADCThread, CameraThread, BMP085Thread, BME280Thread, LEDThread, LogThread;
Es gibt dementsprechend eine bme280.c Datei mit einer Procedure
void *BME280Loop(void *some_void_ptr)
….
if (BMEAddress) { printf("BME280 Found At Address %02xh\n", BMEAddress); } else { printf("BME280 Not Found (nothing at addresses 76/77h)\n"); } while (BMEAddress) { if ((bme.fd = open_i2c(BMEAddress)) >= 0) { bme280StartMeasurement(&bme); sleep(1); // Wait (ample time) for measurement bme280ReadDataRegisters(&bme); bme280GetRawValues(&bme); GPS->BMP180Temperature = bme280Temperature(&bme); GPS->Pressure = bme280Pressure(&bme); GPS->Humidity = bme280Humidity(&bme); // printf("Temperature is %5.2lf\n", GPS->BMP180Temperature); // printf("Pressure is %5.2lf\n", GPS->Pressure); // printf("Humidity is %5.2lf\n", GPS->Humidity); close(bme.fd); } sleep(10);
Der Sensor sollte also automatisch ausgelesen werden, sobald er am I2C Bus erkannt wird.
Es gibt noch ein Problem mit der I2C Adresse: der Code geht von Adresse 0x76h aus, mein Modul ist auf 0x77h. Inkonsequenterweise werden beide Adressen abgecheckt, aber dann stillschweigend 0x76h verwendet. Der Tracker gibt dann natürlich laufend Fehlermeldungen aus, dass er nicht auf den i2c_slave schreiben könne und in der Telemetrie stehen Nullen:
BME280 Found At Address 76h
$$PI9TSS,15,00:00:00,0.00000,0.00000,00000,0,0,0,27.7,4.1,0.175,0.0,0,0.0*992B
Dies kann man entweder im Code beheben oder mit einer Lötbrücke auf dem Modul (siehe Bild oben). Ich werde den Code in Zeile 90 von bme280.c anpassen auf Adresse 0x77h. Ein BMP180 und ein BME280 dürfen deswegen nicht gleichzeitig am I2C angeschlossen sein.
#define BME280_ADDRESS 0x76 // Possible I2C address of BME280 pressure sensor (could also be on ox77)
neu:
#define BME280_ADDRESS 0x77 // I2C address of BME280 pressure sensor, Sparkfun breakout board
Danach ist eine Compilation nötig (sudo make, sudo ./startup) und der Sensor wird gelesen:
SDA/SCL = 2/3 Opened I2C GPS Port T1=28054, T2=26448, T3=50 Registers 19 3, Value 307 Registers 0 3, Value 0 H6 = 30 BME280 Found At Address 77h
Wir sehen hier auch gleich ein Problem in der Programmstruktur: die Messung wird alle 10 Sekunden gemacht; dies wird durch ein blockierendes sleep(10) erreicht. Bei anderen Sensoren, die schneller ausgelesen werden sollen, wird dies zum Problem. Man wird sich das das Zeit-Budget anschauen müssen und evtl. eine Interrupt (Timer-) gesteuerte Abfrage bauen. Natürlich stellt dann der Stromverbrauch ein weiteres Problem dar.
RTTY Telemetrie – String für BME280 definieren
Aus den Messdaten des BME280 ist nun eine Telemetrie-Zeile zu bauen. Eine Codestelle haben wir schon gesehen:
if (Config.EnableBMP085) { sprintf(ExtraFields2, ",%.1f,%.0f", GPS->BMP180Temperature, GPS->Pressure); } if (Config.EnableBME280) { sprintf(ExtraFields2, ",%.1f,%.0f,%0.1f", GPS->BMP180Temperature, GPS->Pressure, GPS->Humidity); }
Der Rest macht der Code in tracker.c (inkl. Prüfsumme anhängen). Der Standard Code geht von 3 „Extrafields“ aus, Stringvariablen zu je 20 Zeichen, in die Werte geschrieben werden können. Anzahl und Länge dieser Felder ist allenfalls hier passend zu erweitern (tracker.c, ca. ab Zeile 70):
void BuildSentence(char *TxLine, int SentenceCounter, struct TGPS *GPS) { char TimeBuffer[12], ExtraFields1[20], ExtraFields2[20], ExtraFields3[20]; sprintf(TimeBuffer, "%02d:%02d:%02d", GPS->Hours, GPS->Minutes, GPS->Seconds); ExtraFields1[0] = '\0'; ExtraFields2[0] = '\0'; ExtraFields3[0] = '\0'; if ((Config.BoardType == 3) || (Config.DisableADC)) { // Pi Zero - no ADC on the PITS Zero, or manually disabled ADC } else if (Config.BoardType == 0) { // Pi A or B. Only Battery Voltage on the PITS sprintf(ExtraFields1, ",%.3f", GPS->BatteryVoltage); } else
Schliesslich wird aus allen Feldern die Zeile komplett gebaut:
sprintf(TxLine, "$$%s,%d,%s,%7.5lf,%7.5lf,%5.5" PRId32 ",%d,%d,%d,%3.1f%s%s%s", Config.Channels[RTTY_CHANNEL].PayloadID, SentenceCounter, TimeBuffer, GPS->Latitude, GPS->Longitude, GPS->Altitude, (GPS->Speed * 13) / 7, GPS->Direction, GPS->Satellites, GPS->DS18B20Temperature[(GPS->DS18B20Count > 1) ? (1-Config.ExternalDS18B20) : 0], ExtraFields1, ExtraFields2, ExtraFields3); AppendCRC(TxLine);
Dann ist mir aufgefallen, dass die Telemetrie Zeile am Bildschirm nicht stimmt. Also am Ende von bme208.c die Zeilen
// printf("Temperature is %5.2lf\n", GPS->BMP180Temperature); // printf("Pressure is %5.2lf\n", GPS->Pressure); // printf("Humidity is %5.2lf\n", GPS->Humidity);
wieder aktiviert. Die Messwerte sind vorhanden in der struct GPS:
$GNGGA,,,,,,0,00,99.99,,,,,,*56 Temperature is 22.44 Pressure is 962.83 Humidity is 52.72 $GNRMC,,V,,,,,,,,,,N*4D
Also schauen wir dort, wo die Telemetrie zusammengebaut wird und ins telemetry.txt File – alles in Ordnung, eventuell wird nur die Anzeige am Bildschirm gekürzt?
Beim Empfänger stimmt alles! Viel Sorge um nichts, dafür kennt man sich jetzt schon besser im Code aus.
APRS Telemetrie – String für BME280 definieren
Der PITS Code ist nicht darauf vorbereitet, die Daten des BME 280 über APRS auszuschicken. Wir holen dies nach – dazu sind 3 Schritte nötig:
- Einfügen der eigentlichen Daten in den APRS Telemetrie String
- Einfügen der Metadaten
- Einfügen von Angaben zum Wertebereich
Das APRS Protokoll sieht maximal 5 analoge Werte im Telemetrie String vor. Koordinaten und Sequence Number zählen nicht dazu. Der PITS Code definiert bereits 3 Werte. Wir ergänzen Druck und Feuchte:
- Sats – Anzahl Satelliten
- Temperature – Temperatur (die interne Temperatur oder externe vom DS18 Sensor)
- Battery – Batteriespannung vom internen ADC
- neu: Pressure – Druck vom BME280 Sensor in hPA
- neu: Humidty – Feuchte vom BME280 Sensor in Prozent
Bei der Temperatur habe ich den Code angeändert, dass die Temperatur des BME280 verwendet wird. Man könnte noch Dezimalgrad verwenden. Die Batteriespannung wird im original Code nicht verwendet, dort steht ein statischer Wert von 4.321V drin (Bug).
Ausser den Werten sind also noch die „Titel“ und die Einheiten anzugeben. Ausserdem können die Werten transformiert werden (typischerweise skaliert). Details dazu stehen in der APRS Spezifikation und in APRS Telemetry Toolkit.pdf (bei Direwolf dabei).
Alle diese Anpassungen sind wie unten folgt in aprs.c vorzunehmen und der tracker neu zu kompilieren.
void SendAPRS(struct TGPS *GPS) { unsigned char frames[4][200]; int lengths[4]; int message_count, total_length; // hb9tss added 4 to 9 char stlm[13]; char slat[5]; char slng[5]; double aprs_lat, aprs_lon; int32_t aprs_alt; static uint16_t seq = 0; uint32_t aprs_temperature, aprs_voltage; // hb9tss new variables uint32_t aprs_pressure, aprs_humidity; ...
// Construct the compressed telemetry format ax25_base91enc(stlm + 0, 2, seq); ax25_base91enc(stlm + 2, 2, GPS->Satellites); // hb9tss //aprs_temperature = GPS->DS18B20Temperature[0] + 100; aprs_temperature = GPS->BMP180Temperature + 100; // hb9tss //GPS->BatteryVoltage = 4.321; aprs_voltage = GPS->BatteryVoltage * 1000; ax25_base91enc(stlm + 4, 2, aprs_temperature); ax25_base91enc(stlm + 6, 2, aprs_voltage); // hb9tss aprs_pressure = GPS->Pressure; aprs_humidity = GPS->Humidity * 10; ax25_base91enc(stlm + 8, 2, aprs_pressure); ax25_base91enc(stlm + 10, 2, aprs_humidity); ...
if (Config.APRS_Telemetry) { char s[10]; sprintf(s, strncpy(s, Config.APRS_Callsign, 7)); if(Config.APRS_ID) snprintf(s + strlen(s), 4, "-%i", Config.APRS_ID); // Transmit telemetry definitions ax25_frame(frames[1], &lengths[1], Config.APRS_Callsign, Config.APRS_ID, APRS_DEVID, 0, 0, 0, // hb9tss added Pressure and Humidity ":%-9s:PARM.Satellites,Temperature,Battery,Pressure,Humidity", s); total_length += lengths[1];
ax25_frame(frames[2], &lengths[2], Config.APRS_Callsign, Config.APRS_ID, APRS_DEVID, 0, 0, 0, // hb9tss added hPa,Percent ":%-9s:UNIT.Sats,deg.C,Volts,hPA,Percent", s); total_length += lengths[2]; ax25_frame(frames[3], &lengths[3], Config.APRS_Callsign, Config.APRS_ID, APRS_DEVID, 0, 0, 0, // hb9tss changed last triplett ":%-9s:EQNS.0,1,0, 0,1,-100, 0,0.001,0, 0,1,0, 0,0.1,0", s); total_length += lengths[3]; message_count += 3; }
Das Ergebnis sieht gut aus:
Ausblick
In einem kommenden Blog beschreibe ich die Anpassungen an der payload Definition für den Habitat Service (nötig, damit die Messdaten auch korrekt interpretiert werden).