QVINTVS · SCRIBET

Sensor BMP280 in purem C ohne Drittbibliotheken auf RaspberryPi auslesen

Der Beitrag erklärt, wie man den Temperatur- und Luftdrucksensor BMP280 mit einem RaspberryPi und einem C-Programm ohne Zuhilfenahme externer Programmbibliotheken ausliest.

Der BMP280 ist ein Temperatur- und Luftdrucksensor der Bosch Sensortec GmbH. Dieser Beitrag erklärt, wie der Sensor mit einem RaspberryPi ausgelesen werden, wobei – anders als bei den meisten im Internet verfügbaren Anleitungen – reines C ohne Einsatz von Drittbibliotheken abseits der Linux-Kernels zum Einsatz kommt.

I. Vorbereitung

Zum BMP280 gehört ein umfangreiches, etwa bei Adafruit herunterladbares Datenblatt, dessen wenigstens kursorische Kenntnis für ein Verständnis dieses Beitrags erforderlich ist. Das Datenblatt enthält alle erforderlichen Informationen, um den Sensor anzusprechen, zu konfigurieren und schließlich die gemessenen Daten auszulesen. Etwas knapp fallen die Ausführungen zur korrekten Verkabelung des Sensors aus; eine Lücke, die dieser Beitrag schließen wird.

An Hardware werden benötigt:

Der BMP280 wird im Einzelhandel nur zusammen mit einer fertigen Anschlußschaltung verkauft, deren Darstellung nicht Gegenstand dieses Beitrags ist. Die vorliegend gegebenen Anweisungen wurden am Modell SEN-KY052temp von JOY-IT getestet.

II. Verkabelung

Der BMP280 ist anders zu verkabeln als das Vorgängermodell BMP180. Die erforderlichen Informationen zum richtigen Anschluß finden sich im Datenblatt, allerdings nicht an einer zentralen Stelle, sondern über verschiedene Kapitel verteilt. Sie seien daher hier zusammengefaßt:

  1. VDD und VDDIO sind an die Spannungsversorgung (3,3V) anzuschließen (Datenblatt § 3.2). Die Platine von JOY-IT weist die beiden Anschlußpunkte nicht einmal mehr separat aus, sondern bietet nur einen gemeinschaftlichen Anschlußpunkt VCC.
  2. GND ist mit dem Masseanschluß des RaspberryPi zu verbinden (setzt das Datenblatt als selbstverständlich voraus).
  3. CSB ist mit VDDIO zu verbinden (Datenblatt § 5.1), d.h. mit der Spannungsversorgung von 3,3 V, die bei der JOY-IT-Platine auch bei VCC anliegt. Dies zeigt dem BMP280 an, daß er per I²C kommunizieren soll.
  4. SDO ist im Regelfalle mit dem Masseanschluß zu verbinden (Datenblatt § 5.2). Dadurch erhält der BMP280 die I²C-Chip-Adresse 0x76. Sollte diese Adresse schon von einem anderen Chip belegt sein, kann man SDO stattdessen mit VDDIO, d.h. mit der Spannungsversorgung, verbinden. Dann erhielte er die Adresse 0x77.
  5. SCK (auf der JOY-IT-Platine als SCL bezeichnet) ist mit dem SCL-Pin des RaspberryPi zu verbinden (Datenblatt § 5.2). Das ist Pin 5 (siehe unten).
  6. SDI (auf der JOY-IT-Platine als SDA bezeichnet) ist mit dem SDA-Pin des RaspberryPi zu verbinden (Datenblatt § 5.2). Das ist Pin 3 (siehe unten).

Die Pin-Nummern können der folgenden Tabelle entnommen werden. Der RaspberryPi wird dabei von oben betrachtet, der USB-Strom-Anschluß befindet sich oben-links, die GPIO-Pins oben-rechts. Die für die Zwecke dieses Beitrages genutzten Pins wurden hervorgehoben.

1 (+3,3V) 2 (+5V)
3 (SDA) 4 (+5V)
5 (SCL) 6 (Masse)
7 (frei) 8 (frei)
9 (Masse) 10 (frei)
11 (frei) 12 (frei)
13 (frei) 14 (Masse)
15 (frei) 16 (frei)
17 (+3,3V) 18 (frei)
19 (frei) 20 (frei)
21 (frei) 22 (frei)
23 (frei) 24 (frei)
25 (Masse) 26 (frei)

Es ergibt sich insgesamt die folgende Schaltung, bei welcher die Anschluß-Pins des RaspberryPi auf der linken, diejenigen des BMP280 (bzw. der JOY-IT-Platine) sich auf der rechten Seite befinden.

Schaltung BMP280

III. Nutzung von I²C

1. Hintergrund zu I²C

Der BMP280 kommuniziert per I²C1. Eine korrekte Implementation auf C-Ebene setzt ein gewisses Grundverständnis dieses Protokolls voraus, für welches man etwa diese gut verständliche Anleitung heranziehen kann. Wichtig für den erfolgreichen Umgang mit dem BMP280 sind allerdings nur ein paar Aspekte, die hier besonders hervorgehoben seien.

I²C ist ein serielles Datenbussystem, d.h. eine Technik, mit der Informationen von einer Komponente zu einer anderen in Form von Bytes über einen elektrischen Leiter übertragen werden können. Die tatsächliche Schaltung in Hardware bezeichnet man als den I²C-Bus, über welchen dann in einer in der I²C-Spezifikation geregelten Art und Weise die einzelnen Bytes durch Anlegen oder Entfernen einer elektrischen Spannung übertragen werden. Man spricht dann davon, die Datenübertragung erfolge „nach dem I²C-Protokoll“. I²C ist damit ein Datenprotokoll auf der untersten OSI-Schicht (Schicht 1: Bitübertragungsschicht). Wer es implementiert, muß sich mit den elektrischen Details auseinandersetzen. Damit unterscheidet sich I²C ganz maßgeblich von den normalerweise eingesetzten Übertragungsprotokollen, die alle letztlich von der Bitübertragungsschicht abstrahieren. Niemand, der typische Endnutzeranwendungen schreibt, würde etwa Ethernet selbst implementieren. Soetwas überläßt man dem Betriebssystem.

An einem I²C-Bus können mehrere Geräte (Chips) angeschlossen werden. Es gibt dabei ein Hauptgerät, den sog. Master, der die Daten von den einzelnen angeschlossenen Geräten, den sog. Slaves, ausliest oder Daten an sie versendet. I²C hat damit in Maßen ein gutes Skalierungspotenzial. Speziell für die Anwendung in privaten Projekten hat es den Vorteil, daß ohne größeren Aufwand verschiedene Sensoren an dieselbe Leitung angeschlossen werden können. Jeder Chip an dem Bus erhält dabei eine eindeutige Adresse, über die er angesprochen wird. Diese Adresse muß im Datenblatt des entsprechenden Chips dokumentiert sein (für den BMP280 in § 5.2 des Datenblatts). Viele Chips – der BMP280 macht hier keine Ausnahme – stellen allerdings mehr als ein Datum gleichzeitig zur Verfügung. Deshalb gibt es auf solchen Chips jeweils mehrere Register, die vom I²C-Master angesprochen werden können. Ein Lese- bzw. Schreibvorgang setzt sich deshalb stets aus drei Komponenten zusammen:

  1. Dem Identifikator des I²C-Bus’ selbst. An einem System können theoretisch mehrere I²C-Busse anliegen.
  2. Der Adresse des Chips am I²C-Bus.
  3. Der Nummer des Registers auf dem Chip.

Ein I²C-Bus besteht dabei aus zwei Leitungen, einer Taktleitung (SCL; das Datenblatt des BMP280 nennt sie SCK), die den Übertragungstakt vorgibt, und der eigentlichen Datenleitung (SDL; das Datenblatt des BMP280 nennt sie SDI), über die in diesem Takt die einzelnen Bits übertragen werden. Die eigentliche Kommunikation erfolgt dann mithilfe von einzelnen, jeweils ein Byte großen Datenpaketen, wobei die I²C-Spezifikation festlegt, daß das erste gesendete Bit das höherwertigste (MSB) und das letzte gesendete Bit das niederwertigste ist (diese Information wird sich deshalb in aller Regel nicht im Datenblatt eines Sensors finden). Eine Lesezugriff erfolgt dann normalerweise wie folgt.

[START] [Chip-Adresse+Register+Lesebit] [ACK] [Datenbyte] [STOP]

Im Einzelnen:

  1. Der Master sendet einen START-Befehl. Dieser blockiert den I²C-Bus, d.h. jetzt darf kein anderer mehr über den Bus kommunizieren (relevant namentlich, wenn mehrere Master-Geräte angeschlossen sind).
  2. Der Master schreibt die Chip-Adresse und die Registernummer auf den Bus, nebst dem Lesebit, das kennzeichnet, daß er Daten empfangen will. Bei richtiger Verdrahtung wird sich nun nur ein einzelner Chip angesprochen fühlen. Diese Daten passen tatsächlich alle in ein einzelnes Byte.
  3. Der angesprochene Chip (Slave) sendet ein ACK. Sodann schickt er in der Regel ein einzelnes Byte Daten auf den Bus.
  4. Der Master schreibt ein STOP auf den Bus und gibt ihn damit wieder frei.

Dieser Prozeß variiert je nach Chip. Der BMP280 etwa schickt auf einen Lesebefehl hin mehrere Byte hintereinander, bis der Master mit NACK zu verstehen gibt, daß er keine mehr möchte (Datenblatt § 5.2.2). Maßgeblich ist das Datenblatt des jeweiligen Chips.

Ein Schreibzugriff läuft ähnlich. Statt des Lesebits wird das Schreibbit gesendet und es ist nunmehr der Master, der ein Byte an den Slave überträgt.

2. I²C auf dem RaspberryPi

Der RaspberryPi verfügt über eine I²C-Leitung, die über die zwei Pins 3 (SDA, d.h. Datenleitung) und 5 (SCL, d.h. Taktleitung) nach außen geführt werden. Die I²C-Leitung muß in der Firmware des RaspberryPi ausdrücklich freigeschaltet werden, sonst erkennt der Linux-Kernel sie nicht. Dies erfolgt entweder mithilfe des entsprechenden Eintrags von raspi-config, oder, wenn man dieses nicht einsetzt, direkt mithilfe des folgenden Parameters in die Datei config.txt auf der Boot-Partition:

dtparam=i2c1=on

Damit die Änderung wirksam wird, ist ein Neustart durchzuführen.

Außerdem muß man zwei Kernel-Module laden, i2c-bcm2708 und i2c-dev. Dies erfolgt mit folgenden Befehlen als root:

# modprobe i2c-bcm2708
# modprobe i2c-dev

Diese Module können in /etc/modules eingetragen werden (eines pro Zeile), um sie in Zukunft automatisch bei jedem Start des RaspberryPi zu laden.

Läuft alles erfolgreich, steht nunmehr eine Gerätedatei /dev/i2c-1 zur Verfügung, die den I²C-Bus repräsentiert. Diese wird später noch von Bedeutung sein.

Mithilfe des Werkzeugs i2cdetect(1) (unter Debian Teil des Pakets i2c-tools, das erforderlichenfalls zu installieren ist) kann man nun den I²C-Bus auf angeschlossene Chips überprüfen. Der I²C-Bus auf dem RaspberryPi 2B hat stets die Kennummer 12:

# i2cdetect 1
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-1.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

An der Adresse 0x76 befindet sich demnach ein Chip. Dies entspricht den Erwartungen, denn laut § 5.2 des Datenblatts ist das die Adresse des BMP280, wenn SDO mit dem Masseanschluß verbunden ist.

IV. Auslesen des BMP280

Nunmehr ist es an der Zeit, sich dem eigentlichen C-Programm zum Auslesen des Sensors zu widmen. Es ist glücklicherweise nicht erforderlich, die elektrischen Ströme auf der Takt- und Datenleitung manuell zu erzeugen, denn der Linux-Kernel enthält ein I²C-Subsystem, dessen man sich für diesen Zweck bedienen kann. Bei der Arbeit damit muß man sich aber vergegenwärtigen, daß die erzeugten Programme nicht portabel sind, sondern strikt vom eingesetzten Kernel abhängen. Deshalb müssen die genutzten Kernel-Header zum laufenden Kernel passen. Fremd-Header können nicht genutzt werden. Darüber hinaus ist es erforderlich, das Programm nach jedem Kernel-Update neu zu kompilieren (sofern es binäre Inkompatibilitäten gegeben hat). Die entsprechenden Kernel-Header sind offenbar exotisch genug, um auf typischen Debian-Systemen nicht standardmäßig installiert zu sein, namentlich sind sie nicht im gewöhnlichen linux-headers-Paket enthalten. Es ist notwendig, das Paket libi2c-dev zu installieren. Auf anderen Distributionen kann das anders sein.

Die Kernel-Dokumentation weist ausdrücklich darauf hin, daß aufgrund der Verwendung von Inline-Funktionen es zwingend nötig ist, GCCs Optimierungen (-O) einzuschalten. Daneben nutzt das nachfolgend vorgestellte C-Programm die Möglichkeit, Variablen erst im Laufe der Funktion zu deklarieren, weshalb die Angabe des C99-Standards nötig ist. Es ergibt sich damit insgesamt zur Kompilation das folgende Kommando:

$ gcc -o messprogramm -std=c99 -O2 messprogramm.c

Das vollständige C-Programm wird unten unter VI. wiedergegeben. Nachfolgend geht es nur um die für das Verständnis besonders wesentlichen Aspekte.

1. Registernummern und vordefinierte Werte

Im Laufe des Programms werden diverse Register des BMP280 ausgelesen und beschrieben. Diese Registernummern sind sämtlich im Datenblatt des BMP280 dokumentiert (§ 4.2). Aus didaktischen Gründen verwendet dieser Beitrag diese Registernummern direkt als magische Zahlen im Code. Wie sonst auch empfiehlt sich im praktischen Einsatz selbstverständlich die Definition von symbolischen Konstanten.

2. calib_params

Der BMP280 enthält in den Registern 0xA1 bis 0x88 diverse 16-Bit-Kalibrierungsparameter. Diese faßt der Code der Übersichtlichkeit halber in einem Struct calib_params zusammen. Sie werden gebraucht, um den vom BMP280 ausgelesenen „rohen“ Temperatur- und Luftdruckwert auf die korrekten Werte umzurechnen.

struct calib_params
{
  uint16_t dig_t1;
  int16_t  dig_t2;
  int16_t  dig_t3;
  uint16_t dig_p1;
  int16_t  dig_p2;
  int16_t  dig_p3;
  int16_t  dig_p4;
  int16_t  dig_p5;
  int16_t  dig_p6;
  int16_t  dig_p7;
  int16_t  dig_p8;
  int16_t  dig_p9;
};

Die Namen der einzelnen Felder folgen aus der Bezeichnung im Datenblatt (§ 3.11.2). Um die richtige Größe des Datentyps in Bit sicherzustellen, kommen die Typen aus stdint.h zum Einsatz.

3. Vorbereitung des I²C-Gerätzugriffs

Den Anfang der Funktion main() bildet die Vorbereitung der Gerätedatei für den I²C-Zugriff und ist angelehnt an die entsprechenden Instruktionen der Kernel-Dokumentation. Auf dem RaspberryPi 2B wird der I²C-Bus immer über die Gerätedatei /dev/i2c-1 repräsentiert. Mithilfe eines besonderen IOCTL-Befehls wird dann der anzusprechende Chip am Bus festgelegt. 0x76 ist bei der hier vorliegenden Verbindung von SDO mit dem Masseanschluß die Adresse des BMP280 (Datenblatt § 5.2).

int i2cbus = open("/dev/i2c-1", O_RDWR);
if (i2cbus < 0) {
  printf("Problem beim Oeffnen von /dev/i2c-1.\n");
  return 1;
}

if (ioctl(i2cbus, I2C_SLAVE, 0x76) < 0) {
  printf("Problem beim Oeffnen der Chip-Adresse 0x76.");
  return 1;
}

Die I²C-Bibliothek des Kernels arbeitet demnach mit Dateideskiptoren. Durch die Kombination von open(2) mit ioctl(2) enthält dieser Dateideskriptor letztlich die für die Adressierung notwendigen zwei Informationen der Bus-Kennummer sowie der Chip-Adresse. Alle weiteren Funktionen der I²C-Bibliothek des Kernels arbeiten mit diesem Dateideskriptor. Wie für normale Dateideskriptoren auch ist dieser natürlich spätestens bei Programmende mithilfe von close(2) zu schließen.

4. Prüfung des Chips

Nun kommt es vor, daß man die an einem I²C-Bus angeschlossenen Chips austauscht. Es kann vorkommen, daß dann ein anderer Chip die Adresse 0x76 besitzt. Um wirklich sicherzugehen, daß ein BMP280 angeschlossen ist, wird nun das Identifikationsregister 0xD0 ausgelesen. Es muß den Wert 0x58 enthalten, widrigenfalls kein BMP280 angeschlossen ist (Datenblatt § 4.3.1).

if (i2c_smbus_read_byte_data(i2cbus, 0xD0) != 0x58) {
  printf("Der Chip an der Adresse 0x76 ist kein BMP280.");
  return 1;
}
else {
  printf("An Adresse 0x76 ist ein BMP280 angeschlossen.\n");
}

Dieser Aufruf stellt die erste Kommunikation per I²C dar. Sie ist die Probe, ob die Hardware richtig angeschlossen worden ist. Die vom Kernel bereitgestellte Funktion i2c_smbus_read_byte_data() nimmt den vorbereiteten Gerätedateideskriptor (dieser enthält ja Buskennummer plus Chip-Adresse) entgegen sowie die Nummer des auszulesenden Registers. Sie gibt genau ein Byte zurück: den Inhalt des Registers.

5. Auslesen der Kalibrierungsparameter

§ 3.11.2 des Datenblatts listet auf, in welchen Registern welche Kalibrierungsparameter enthalten sind. Diese werden nunmehr nacheinander ausgelesen. Dabei ist die Besonderheit zu beachten, daß die Kalibrierungsparameter jeweils als 16-Bit-Zahl vorliegen. Ein Register kann aber per I²C-Spezifikation nur ein Byte, also 8 Bit, enthalten. Der BMP280 löst das Problem, indem die 16-Bit-Zahl auf zwei Register aufgeteilt wird, die jeweils die Hälfte der binären Repräsentation der 16-Bit-Zahl enthalten. Eigentlich müßte man die beiden Register (LSB-Register für die niederwertigen, MSB-Register für die höherwertigen acht Bit des jeweiligen Kalibrierungsparameters) nun einzeln auslesen und aus den beiden Bytes die 16-Bit-Zahl zusammensetzen (was nicht durch einfache Addition erfolgen kann, vielmehr müssen die LSB-Bits an die MSB-Bits angehängt werden). Weil das Problem aber offenbar häufiger auftritt, bietet die I²C-Bibliothek des Kernels aber mit i2c_smbus_read_word_data() eine Funktion an, die das Zusammensetzen der 16-Bit-Zahl übernimmt3. Das funktioniert freilich nur, wenn auf eine Register-Auslese-Aufforderung hin statt einem auch zwei Register ausgelesen werden, mit anderen Worten: wenn der BMP280 trotz Anforderung etwa des Registers 0x88 nach dem Inhalt dieses Registers auch gleich den Inhalt des Registers 0x89 hinterherschickt, ohne daß eine neue Leseaufforderung notwendig wäre. Dies ist jedoch glücklicherweise der Fall: der BMP280 sendet solange den Inhalt des jeweils nächsten Registers über den Bus, bis eine Datenübermittlung statt mit ACK mit NACK quittiert wurde (Datenblatt § 5.2.2). Genau das macht i2c_smbus_read_word_data(), wie die Kernel-Dokumentation an anderer Stelle erklärt.

struct calib_params calib = {0};
calib.dig_t1 = (uint16_t) i2c_smbus_read_word_data(i2cbus, 0x88);
calib.dig_t2 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x8A);
calib.dig_t3 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x8C);
calib.dig_p1 = (uint16_t) i2c_smbus_read_word_data(i2cbus, 0x8E);
calib.dig_p2 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x90);
calib.dig_p3 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x92);
calib.dig_p4 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x94);
calib.dig_p5 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x96);
calib.dig_p6 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x98);
calib.dig_p7 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x9A);
calib.dig_p8 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x9C);
calib.dig_p9 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x9E);

6. Konfiguration des BMP280

Nunmehr wird der BMP280 für die Wetterbeobachtung konfiguriert. Das Datenblatt enthält in § 3.4, Tabelle 7, sogar einen konkreten Konfigurationsvorschlag, den man nur in entsprechende Schreibbefehle für die Register umsetzen muß. 0xF5 ist das allgemeine Konfigurationsregister, 0xF4 das besondere für die Messungen. Das Schreiben in das Register 0xF4 versetzt den BMP280 in einen neuen Operationsmodus (Datenblatt § 3.6).

i2c_smbus_write_byte_data(i2cbus, 0xF5, 0);
i2c_smbus_write_byte_data(i2cbus, 0xF4, 37);

Die möglicherweise willkürlich erscheinen Zahlenwerte beruhen auf den Angaben im Datenblatt. Im Einzelnen:

Die Aktivierung des Forced Mode führt dazu, daß der BMP280 sogleich eine Messung durchführt, die einen Moment dauern kann. Danach kehrt er in den Schlafmodus zurück (Datenblatt § 3.6.4). Es empfiehlt sich daher, an dieser Stelle kurz zu warten, bevor man die gemessenen Daten ausliest.

sleep(1);

7. Auslesen der Werte

Nunmehr können die Werte ausgelesen werden. § 3.9 des Datenblatts empfiehlt, mithilfe der schon bekannten Auto-Inkrement-Funktion des BMP280 alle Meßregister auf einmal auszulesen. Für eine solche Streckenauslesung bietet die I²C-Bibliothek des Kernels die Funktion i2c_smbus_read_block_data() an, die etwas versteckt dokumentiert ist. Da das erste auszulesende Register 0xF7 ist und ausweislich der Tabelle in § 4.2 des Datenblatts sechs Register auszulesen sind, muß der Aufruf lauten:

uint8_t result[6] = {0};
i2c_smbus_read_i2c_block_data(i2cbus, 0xF7, 6, result);

Das Array result enthält nun für jedes ausgelesene Register das in diesem Register ausgelesene Byte (= die 8 Bit des Registers). Die Meßwerte liegen allerdings insgesamt als positive vorzeichenbehaftete 20-Bit-Ganzzahl vor (Datenblatt § 3.11.3). Diese 20 Bit sind für jeden Meßwert über drei Register verteilt, wobei das letzte Register (XLSB) nur zum Teil ausgewertet werden darf (Datenblatt §§ 4.3.6, 4.3.7). Die vier Bits 4-6 sind zu ignorieren, sodaß nur die vier Bits 0-3 ausgewertet werden dürfen. Dieses Mal steht eine bequeme Funktion wie i2c_smbus_read_word_data() leider nicht zur Verfügung; es muß selbst Hand angelegt werden. Die einzelnen Bitketten sind hintereinander zu konkatenieren (zusammenzufügen). Dies erreicht man, indem man die aus dem MSB-Register gelesene Zahl entsprechend um 8 + 4 = 12 Bitstellen nach links verschiebt (Bitshift). Die aus dem LSB-Register gelesene Zahl verschiebt man um 4 Bitstellen nach links. Anschließend nutzt man ein bitweises ODER, um die Bits in den rechts von den MSBs freigewordenen Raum einzufügen. Den Inhalt des XLSB-Registers ignoriert der hier aufgezeigte Code, da er ohnehin die Präzision nur minimal beeinflußt und nach meinen Messungen praktisch immer 0 (Null) ist. Das Extrahieren der einzig erlaubten vier Bits 0-3 zwecks Einspeisung in das Meßergebnis wird dem Leser zur Selbsterarbeitung überlassen. Es ergibt sich (mit | 0 als Platzhalter für die Bits 0-3 des XLSB-Registers):

int32_t adc_t = ((result[3] << 12) | result[4] << 4) | 0;
int32_t adc_p = ((result[0] << 12) | result[1] << 4) | 0;

8. Umrechnung in die richtigen Werte

Die rohen Meßwerte sind nicht aus sich heraus verständlich. Sie müssen nunmehr mithilfe der oben unter IV.5. ausgelesenen Kalibrierungsparameter in die richtigen Temperatur- (°C) und Luftdruckwerte (Pa) umgerechnet werden. Das geschieht mithilfe der beiden Funktionen bmp280_compensate_T() und bmp280_compensate_P(). Der Inhalt dieser Funktionen bestimmt sich im Wesentlichen unmittelbar aus der im Datenblatt in § 3.11.3 beschriebenen Formel. Das Datenblatt schweigt sich über die Bedeutung der einzelnen Schritte aus, sodaß eine Erklärung an dieser Stelle nicht möglich ist. Es bleibt allein, die dort genannten Schritte eins zu eins umzusetzen. Beim Lesen des Datenblatts sollte man sich über die Präzedenz von Operatoren in C im Klaren sein, da ansonsten bei der Umsetzung Irrtümer auftreten können.

9. Ergebnis

# gcc -o messprogramm -std=c99 -O2 messprogramm.c
# ./messprogramm
An Adresse 0x76 ist ein BMP280 angeschlossen.
--- Kalibrierung ---
t1=27525 t2=26561 t3=-1000 p1=36565 p2=-10685 p3=3024 p4=-3092 p5=275 p6=-7 p7=15500 p8=-14600 p9=6000
--- Ende ---
--- Rohdaten ---
temp_msb=0x80 temp_lsb=0x62 temp_xlsb=0x0
temp_msb << 12: 524288
temp_lsb << 4: 1568
adc_T: 525856
adc_P: 513408
--- End Rohdaten ---
Temperatur: 2697
Luftdruck: 100148.7148

Gemessen wurden also eine Temperatur von 26,97 °C und ein Luftdruck von 1001,487148 hPa.

V. Fazit

Damit ist der Grundbaustein für die Messung von Temperatur und Luftdruck mit dem BMP280 in C gelegt. Hierauf aufbauend können nun Projekte wie eine eigene Wetterstation oder Heimautomation verfolgt werden. Weder bedarf es einer externen Programmbibliothek, noch ist man auf Python als Programmiersprache festgelegt. Darüber hinaus erlaubt die hier vorgestellte Vorgehensweise, sich mit den Feinheiten von I²C und seinem Einsatz auf Linux vertraut zu machen.

VI. Vollständiger Code

Nachfolgend stelle ich den gesamten Code zur Kommunikation mit dem BMP280 unter der 2-Klausel-BSD-Lizenz zur Verfügung. Die Umrechnung mithilfe der Kalibrierungsparameter wurde aus urheberrechtlichen Gründen ausgespart. Sie wird im Datenblatt, § 3.11.3, anhand von C-Code erklärt. Darauf wird verwiesen.

Weiterhin wird darauf aufmerksam gemacht, daß der I²C-Code des Linux-Kernels unter der GNU GPL (ausschließlich Version 2) steht.

/*
Copyright (C) 2019 Marvin Guelker

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdio.h>
#include <stdint.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>

struct calib_params
{
  uint16_t dig_t1;
  int16_t  dig_t2;
  int16_t  dig_t3;
  uint16_t dig_p1;
  int16_t  dig_p2;
  int16_t  dig_p3;
  int16_t  dig_p4;
  int16_t  dig_p5;
  int16_t  dig_p6;
  int16_t  dig_p7;
  int16_t  dig_p8;
  int16_t  dig_p9;
};

static int32_t bmp280_compensate_T(const struct calib_params* p_calib, int32_t* p_t_fine, int32_t adc_t);
static uint32_t bmp280_compensate_P(const struct calib_params* p_calib, int32_t t_fine, int32_t adc_p);

int main()
{
  /* I2C-Interface vorbereiten */
  int i2cbus = open("/dev/i2c-1", O_RDWR);
  if (i2cbus < 0) {
    printf("Problem beim Oeffnen von /dev/i2c-1.\n");
    return 1;
  }

  if (ioctl(i2cbus, I2C_SLAVE, 0x76) < 0) {
    printf("Problem beim Oeffnen der Chip-Adresse 0x76.");
    return 1;
  }

  /* Sicherstellen, dass ein BMP280 angeschlossen ist */
  if (i2c_smbus_read_byte_data(i2cbus, 0xD0) != 0x58) {
    printf("Der Chip an der Adresse 0x76 ist kein BMP280.");
    return 1;
  }
  else {
    printf("An Adresse 0x76 ist ein BMP280 angeschlossen.\n");
  }

  /* 1. Kalibrierungsparameter auslesen. Dies nutzt die Auto-Inkrement-Funktion
   * des BMP280 aus, sodass die MSB-Register ohne separaten Lesebfehl mit
   * ausgelesen werden. */
  struct calib_params calib = {0};
  calib.dig_t1 = (uint16_t) i2c_smbus_read_word_data(i2cbus, 0x88);
  calib.dig_t2 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x8A);
  calib.dig_t3 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x8C);
  calib.dig_p1 = (uint16_t) i2c_smbus_read_word_data(i2cbus, 0x8E);
  calib.dig_p2 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x90);
  calib.dig_p3 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x92);
  calib.dig_p4 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x94);
  calib.dig_p5 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x96);
  calib.dig_p6 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x98);
  calib.dig_p7 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x9A);
  calib.dig_p8 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x9C);
  calib.dig_p9 = (int16_t) i2c_smbus_read_word_data(i2cbus, 0x9E);

  printf("--- Kalibrierung ---\n");
  printf("t1=%d t2=%d t3=%d p1=%d p2=%d p3=%d p4=%d p5=%d p6=%d p7=%d p8=%d p9=%d\n",
         calib.dig_t1, calib.dig_t2, calib.dig_t3, calib.dig_p1, calib.dig_p2,
         calib.dig_p3, calib.dig_p4, calib.dig_p5, calib.dig_p6, calib.dig_p7,
         calib.dig_p8, calib.dig_p9);
  printf("--- Ende ---\n");

  /* 2. Konfiguration fuer Wetterbeobachtung. Siehe die Empfehlungen
   * im Datenblatt S. 14, § 3.4 Tabelle 7. Dies versetzt den BMP280
   * in den "Forced Mode", sodass genau eine Messung durchgeführt wird. */
  i2c_smbus_write_byte_data(i2cbus, 0xF5, 0);
  i2c_smbus_write_byte_data(i2cbus, 0xF4, 37);
  /* 37 = 001_001_01 in binaer. Von hinten nach vorn:
   * 01: Forced Mode
   * 001: Oversampling Druck x1
   * 001: Oversampling Temperatur x1
   */

  sleep(1); /* Warte auf Abschluss der Messung */

  /* 3. Ergebnisse einlesen. Streckenlesung gemaess Datenblatt § 3.9 S. 19. */
  uint8_t result[6] = {0};
  i2c_smbus_read_i2c_block_data(i2cbus, 0xF7, 6, result);

  int32_t adc_t = ((result[3] << 12) | result[4] << 4) | 0 /* oder Bits 0-3 des XLSB-Registers */;
  int32_t adc_p = ((result[0] << 12) | result[1] << 4) | 0 /* oder Bits 0-3 des XLSB-Registers */;
  
  printf("--- Rohdaten ---\n");
  printf("temp_msb=0x%x temp_lsb=0x%x temp_xlsb=0x%x\n", result[3], result[4], result[5]);
  printf("temp_msb << 12: %d\n", result[3] << 12);
  printf("temp_lsb << 4: %d\n", result[4] << 4);
  printf("adc_T: %d\n", adc_t);
  printf("adc_P: %d\n", adc_p);
  printf("--- Ende Rohdaten ---\n");

  /* 4. Umrechnung in die richtigen Werte. */
  int32_t t_fine = 0;
  int32_t temperature = bmp280_compensate_T(&calib, &t_fine, adc_t);
  uint32_t pressure = bmp280_compensate_P(&calib, t_fine, adc_p);

  printf("Temperatur: %d\n", temperature);
  printf("Luftdruck: %.04f\n", (double) pressure / 256.0);

  return 0;
}

/* Rechnet den Rohmesswert fuer die Temperatur um in 1/100 °C, d.h. der
 * Rueckgabewert dieser Funktion ist durch 100 zu dividieren, um °C zu
 * erhalten (dieser Trick erlaubt es, dass die Funktion ohne Gleitkommazahlen
 * auskommt). */
int32_t bmp280_compensate_T(const struct calib_params* p_calib, int32_t* p_t_fine, int32_t adc_t)
{
  /*************************************************
   * Einfuegen: Umrechnungsformel gemaess Datenblatt
   * § 3.11.3, S. 22.
   *************************************************/
}

/* Entsprechende Umrechnung fuer den Luftdruck. Das Ergebnis muss durch 256
 * dividiert werden, um den Wert in Pascal zu erhalten. */
uint32_t bmp280_compensate_P(const struct calib_params* p_calib, int32_t t_fine, int32_t adc_p)
{
  /*************************************************
   * Einfuegen: Umrechnungsformel gemaess Datenblatt
   * § 3.11.3, S. 22.
   *************************************************/
}
  1. Es ist auch möglich, den BMP280 per SPI anzusprechen. Diese Möglichkeit wird hier nicht weiter diskutiert. 

  2. Die sehr frühen Modelle des RaspberryPis (Revision 1) weisen dem I²C-Bus offenbar die Kennummer 0 zu. Das hat aber Seltenheitswert. 

  3. Der Begriff „Datenwort“ (engl. eben word) ist historisch bedingt. Er bezeichnet eine Größe, die aus zwei Bytes besteht.