Lerneinheit Pulsoxymeter¶
Wir möchten ein Pulsoxymeter an der Platine anschließen, mit welchem am Ende der Blutsauerstoff, der Puls und die Herzfrequenzvariabilität gemessen werden kann.
Aufgabe
Schließe zuerst die Platine am Computer und den Fingerclip an der Platine an. Öffne danach mithilfe des Mu Editors die sensor.py Datei auf deinem Raspberry, indem du auf Laden klickst und die Datei auswählst. Wähle den Modus RP2040 aus.
Jetzt hast du den Quelltext (der Code für das Programm) geöffnet. Damit du den Puls messen kannst, müssen noch ein paar Lücken ausgefüllt werden. Der Quelltext ist im folgenden in kleine Einheiten eingeteilt. Zu jeder Einheit gibt es einen Text, welcher den Quellcode erklärt. Manchmal ist im Quellcode ein rotes Fragezeichen eingebaut, dieses soll mit deiner Antwort auf die Frage ersetzt werden. Wenn du mal nicht weiter weist kannst du dir einen Tipp anschauen. Außerdem gibt es Abschnitte, welche für Experten sind und mit einer Glühbirne gekennzeichnet sind. Diese kannst du dir bei Interesse anschauen. Viel Spaß!
Funktion des Pulsoxymeters¶

Damit du dir vorstellen kannst, wie ein Pulsoxymeter funktioniert, erstmal eine kurze Erklärung dazu. Schaue dir dazu die Abbildung an. Ein Pulsoxymeter hat eine rote und infrarote LED. Es wird also rotes und infrarotes Licht ausgesendet. Infrarotes Licht ist für das menschliche Auge nicht sichtbar, das rote Licht kannst du sehen. Die beiden LEDs werden abwechselnd ganz schnell an und ausgeschaltet. Dabei reflektiert ein Teil des Lichts in deinem Finger. Genau dieses Licht wird mit einem Photodetektor gemessen. Ein Photodetekor wandelt das ankommende Licht in ein elektrisches Signal um. Das Signal verändert sich durch den Pulsschlag in deinem Finger. Das infrarote und rote Licht wird für die Berechnung der Blutsauerstoffsättigung benötigt. Dort machen wir uns zunutze, dass sich das Licht bei den roten Blutkörperchen mit Sauerstoff anders als ohne verhält.
Der Raspberry Pi Pico¶
Vielleicht kennst du Computer als Geräte, auf denen du YouTube Videos
schauen kannst, Spiele spielen kannst oder mit anderen Menschen
kommunizeren kannst. Die allermeisten Computer sind aber ein wenig
kleiner. Es handelt sich dabei um Mikro-Computer (Oder auch
Mikro-Controller). Diese verleihen all den Maschinen, die wir im Alltag
benutzen ihre Intelligenz. Dazu zählen beispielsweise Waschmaschinen,
Kühlschränke und sogar die Kaffemaschine! Im Grunde genommen sind
Mikro-Controller überall verbaut, allein in einem modernen Auto stecken
Hunderte drin.
Üblicherweise sind diese Mikro-Controller versteckt und
nicht vom Anwender programmierbar. Der Raspberry-Pi Pico hingegen lässt
sich leicht über eine USB-Schnittstelle von einem Computer
programmieren. Auch verfügt der Pico über jede Menge Anschlüsse, mit
denen man mit der Welt kommunizieren kann.
CircuitPython¶
CircuitPython ist eine auf Python aufbauende Programmiersprache, die man nutzen kann, um den Raspberry Pi Pico zu programmieren. Micro-Python unterscheided sich von Python hauptsächlich darin, dass es über verschiedene Bibliotheken verfügt, mit deren Hilfe sich die elektronische Hardware des Raspberry Pi-Picos ansteuern lässt.
Importieren der Module und Bibliotheken¶
Zuerst werden die für das Pulsoxymeter benötigten Module und Bibliotheken importiert. Bibliotheken und Module gestalten das verfasste Programm übersichtlicher. Mit diesen können Funktionen und Klassen direkt aufgerufen werden, welche bestimmte Aufgaben in einem Programm erfüllen. Ansonsten würde das Programm sehr lange und unübersichtlich werden, wenn in diesem alle Funktionen in einem Skript definiert werden müssten.
Experte
Was ist der Unterschied zwischen Bibliotheken und Modulen? Module sind eher kleinere Funktionseinheiten im Gegensatz zu Bibliotheken. Dabei sind in einem Modul vorallem Funktionen enthalten, welche einem bestimmten Zweck dienen, während in Bibliotheken eine Sammlung von Klassen und Funktionen vorhanden sein können, um eine ganze Aufgabe zu lösen. Eine Bibliothek ist also meistens umfangreicher, während Module kleinere Funktionen enthalten 1.
Zunächst werden die Module importiert, welche für CircuitPython
entwickelt wurden. Das Modul displayio
stellt eine Verbindung
zum Display her und wird verwendet, um Inhalte auf dem Display
anzuzeigen und busio
stellt eine Verbindung mit dem SPI für
das Display her. SPI bedeutet Serial Peripheral Interface und ist ein
Bus-System. Dies wird später genauer erklärt. Mithilfe des
board
Moduls kann auf die Pins des Board zugegriffen werden.
Das time
Modul wird benötigt um später die Zeit zu messen,
wann ein Herzschlag stattfindet. Durch terminalio
kann ein
Text auf einem Display angezeigt werden. Aus dem math
Modul
wird sqrt
, also die Wurzel für die
Herzfrequenzvariabilitätsmessung benötigt 2.
# Circuitpython Module
import displayio
import busio
import board
import time
import terminalio
from math import sqrt
Als nächstes werden noch ein paar weitere Bibliotheken importiert.
Adafruit_ticks
wird für die Berechnung der zeitlichen
Differenz von zwei Herzschlägen benötigt und für einen timer in
Millisekunden. Die Bibliothek adafruit_st7789
wird für das
Display gebraucht. Die adafruit_display_text
Bibliothek wird
für die Anzeige eines Textes auf dem Display benötigt. Damit später das
Signal des Pulses auf dem Bildschirm angezeigt wird, wird
adafruit_display_shapes.sparkline
verwendet 34.
# Circuitpython Bibliotheken
from adafruit_ticks import ticks_diff, ticks_ms
from adafruit_st7789 import ST7789
from adafruit_display_text import label
from adafruit_display_shapes.sparkline import Sparkline
Des Weiteren wird die Bibliothek MAX30103
verwendet, damit
direkt auf den Sensor des Pulsoxymeters zugegriffen werden kann. Dadurch
können direkt die Werte des Signals, also die Werte des roten und
infraroten Lichts ausgegeben werden.
# Bibliothek des Sensors MAX30102
from pcb.MAX30102 import MAX30102, MAX30105_PULSE_AMP_MEDIUM, FIR_FILTER_COEFFS
Die Bibliothek heartrate ist für die Erkennung eines Herzschlages
zuständig. Damit die gemessenen reflektierten Werte des infraroten und
roten Lichts gespeichert werden, wird die CircularBuffer
Bibliothek verwendet. Diese erstellt eine Liste mit einer bestimmten
Länge. In einer Liste werden die Werte gespeichert. Wenn die Liste die
maximale Länge erreicht hat, wird das älteste Element gelöscht und das
Neue hinzugefügt. Aus der Utilities
Bibliothek wird die
Funktion remove_dc
, welche den Gleichanteil eines Signals
entfernt verwendet und der fir_lowpass
glättet das Signal.
Das wird später nochmal genauer erklärt 5.
# Bibliothek für die Erkennung eines Herzschlags
from lib.heartrate import HeartRate
# Bibliothek zum speichern von Werten in einer Liste
import lib.CircularBuffer as CircularBuffer
# Bibliothek zum entfernen des Gleichanteils im Signal und ein Tiefpassfilter
from lib.Utilities import remove_dc, fir_lowpass
Die Länge der Liste wird hier auf 100 festgelegt.
# Die Länge der Liste
CIRCULAR_BUFFER_SIZE=100
Initialisierung des Pulsoxymeters¶
Zuerst wird die Klasse Pulsoxymeter erstellt, in welcher einige Methoden definiert werden. Methoden erfüllen bestimmte Aufgaben (Funktionen) in einem Programm, welche das Verhalten von Objekten beschreiben 6. Damit du dir in etwa vorstellen kannst, was überhaupt Klassen und Objekte sind, gibt es eine kurze Erklärung dazu.
Was sind Klassen und Objekte?¶
Klassen und Objekte werden verwendet, um ein Programm übersichtlicher zu gestalten. Eine Klasse repräsentiert alle Objekte einer Art und definiert diese mithilfe von Parametern und Methoden. Ein Beispiel für eine Klasse kann zum Beispiel eine Person sein. Objekte dazu wären bestimmte Personen, wobei jede Person Eigenschaften besitzt. Diese Variablen, wie zum Beispiel der Name und das Gewicht der Person werden Instanzvariablen genannt 7.
In der Klasse Pulsoxymeter, werden als erstes einige Variablen und
Startwerte initialisiert. Das self
wird in Funktionen
innerhalb von Klassen benötigt. Dadurch kann auf Methoden und
Instanzvariablen der Klasse zugegriffen werden 8. Zuerst wird ein
Objekt der Klasse MAX30102 erzeugt, mit der Bezeichnung
sensor
. Dieses Objekt stellt eine Verbindung mit der I2C
Schnittstelle des Sensors MAX30102 her.
Experte
Was ist eine I2C Schnittstelle? Dies ist eine Abkürzung für Inter-Integrated Circuits. Es ist ein Datenbus, mit welchem die Daten unseres Sensors für die Blutsauerstoffsättigung und den Puls übertragen werden. Dabei basiert dieser auf zwei Leitungen. Der SCL und SDA Leitung. Die SCL (serial clock) Leitung ist für das Taktsignal verantwortlich. SDA (serial data) steht für die serielle Datenübertragung. Die Datenübertragung funktioniert nach einem Master Slave Prinzip. Dabei ist in unserem Versuch der Mikrocontroller der Master und der Sensor MAX30102 der Slave. Wenn Daten von dem Sensor verlangt werden teilt dies der Master dem Slave durch eine Startsequenz mit. Wie in der Abbildung zu sehen gibt es einen Low und einen High Zustand, dies entspricht einer 1 oder 0. Zu Beginn, wenn noch keine Daten verlangt wurden, sind SDA und SCL beide auf High.
Bei der Startsequenz wird SDA auf Low gesetzt. Danach sendet der Master die Adresse des Slaves, damit dieser mit dem richtigen Sensor kommuniziert. Danach wird durch ein Bit festgelegt, in welche Richtung die Daten übertragen werden sollen. Daraufhin werden die Daten auf die SDA Leitung gelegt, während der SCL den Takt vorgibt. Dabei handelt es sich um Datenpakete von 8 Bit. Es werden solange weitere Datenpakete versendet, bis eine stopp Bedingung erfolgt. Dabei sind SCL und SDA beide wieder auf High gesetzt 9.
Mit der Methode setup_sensor()
werden dem Sensor bestimmte
Werte für Parameter übergeben. Dies ist sozusagen die Grundeinstellung
des Sensor. Die Werte der Parameter sind in der MAX30102 Bibliothek
festgelegt.
Einstellungen des Sensors MAX30102¶
Ein kleiner Einblick, was alles an dem Sensor in der MAX30102
eingestellt wird. Die LED_Power
ist die Helligkeit der LED.
Diese ist auf mittel eingestellt, da dort die besten Ergebnisse erzielt
wurden. Da zum Beispiel bei einer geringen Helligkeit zu wenig Licht am
Photodetektor gelangen könnte und somit zu wenige Daten gesammelt
werden. Die SAMPLE_RATE
wird auf 1600 eingestellt. Das
bedeutet, dass in einer Sekunde 1600 mal die LEDs an- und ausgeschaltet
werden. Je höher die Abtastrate ist, desto mehr Daten können gesammelt
werden und umso genauer ist der Signalverlauf am Ende. Dabei sollte auch
beachtet werden, dass dies mehr Rechenleistung benötigt. Das
SAMPLE_AVG
ist auf 4 eingestellt 10. Dazu müssen wir
uns erstmal anschauen was ein FIFO ist.
Was ist ein FIFO?¶
FIFO (First In First Out) ist eine Datenstruktur, bei welcher Daten gespeichert werden. Dabei ist zu beachten, dass die Daten, die zuerst hinzugefügt wurden, zuerst gelöscht werden. Dies siehst du in der Abbildung. Man kann sich dies wie ein Speicher vorstellen, welcher solange mit Daten gefüllt wird bis dieser voll ist. Daraufhin wird immer das älteste Element gelöscht, wenn ein Neues hinzugefügt wird 11.

Das SAMPLE_AVG
bedeutet sample averaging. Es werden immer
vier aufeinanderfolgende Werte gemittelt. Da es immer kleine
Abweichungen bei Messungen gibt, soll diese Mittelung zu große
Messabweichungen verringern. Als nächstes werden noch zwei Listen
erstellt, in welcher die Werte von dem roten und infraroten AC-Signal
gespeichert werden. Mit red_w
und ir_w
werden die
beiden Parameter initialisiert, welche später den vorherigen Wert des
roten und infraroten Signlas speichern. Dieser wird für die Berechnung
des AC- und DC-Wertes benötigt. Was das überhaupt ist erfährst du im
nächsten Abschnitt. Als letztes wird ein Objekt der Klasse
HeartRate()
erstellt, welches später für die Detektion der
Herzschläge verwendet wird.
# Es wird eine Klasse Pulsoxymeter erstellt die einige Funktionen definiert
class Pulsoxymeter:
# Variablen werden initialisiert
def __init__(self):
# Verbindet den Sensor mit der I2C Schnittstelle
self.sensor = MAX30102()
# Sensor konfigurieren
self.sensor.setup_sensor(LED_POWER=MAX30105_PULSE_AMP_MEDIUM)
# Stellt die Abtastrate auf 1600 ein
self.sensor.set_sample_rate(1600)
# Im Fifo werden jeweils 4 Werte gemittelt
self.sensor.set_fifo_average(4)
# Initialisiert 2 Listen mit einer Länge von 100
self.red_buffer = CircularBuffer.CircularBuffer(CIRCULAR_BUFFER_SIZE)
self.ir_buffer = CircularBuffer.CircularBuffer(CIRCULAR_BUFFER_SIZE)
# Initialisiert die beiden Parameter, diese speichern den vorherigen roten/infraroten Wert, wird für die Berechnung des DC/AC Wertes benötigt
self.red_w = 0
self.ir_w = 0
# Ruft die Klasse HeartRate auf, welche die Peaks eines Signals detektiert
self.heart_rate = HeartRate()
AC- und DC-Wert¶
Was bedeutet AC und DC?¶
Wie zu Beginn beschrieben, wird ein Teil des Lichts am Finger reflektiert. In dem Finger sind Arterien vorhanden, die sich bei einem Pulsschlag weiten und danach wieder verengen. Dadurch ist die Stärke der Reflektion an den Arterien immer unterschiedlich. Ein Teil des Lichts wird aber auch schon zum Beispiel am Gewebe, an den Knochen und den Muskeln reflektiert. Dieser Teil bleibt während den Pulsschlägen immer konstant, da diese sich nicht mit dem Herzschlag ändern. Das bedeutet, wir haben einen reflektierten Teil der sich während der Messung ändert und einen Teil der konstant bleibt 12. In der Elektrotechnik wird ein Strom der sich nicht ändert, also konstant bleibt, mit Direct Current (DC) bezeichnet. Ein Strom der sich während der Zeit ändert wird Alternating Current (AC) bezeichnet.
Da der Photodetektor das gesamte Signal misst, müssen wir das Signal in
einen AC- und DC- Anteil aufteilen. Dazu wird die Methode
red_value
und ir_value
definiert. Zuerst wird das
sensor
Objekt aufgerufen und mit get_red()
wird
die Methode aufgerufen, welche einen Wert des roten Lichts zurück gibt.
Dadurch wird der Wert auf unserer Variable red
gespeichert.
Der Wert entspricht in diesem Fall dem reflektierten roten Licht an
unserem Finger, welches der Photodetektor misst. Der nächste Schritt
ist, dass auf der Variablen red
der Wert in ein AC- und DC-
Anteil aufgeteilt wird. Dazu verwenden wir die Funktion
remove_dc()
. Als nächstes soll der DC-Wert berechnet werden.
Aufgabe
Wie kann der DC-Wert berechnet werden? Schreibe in die red_value()
und ir_value()
Methode bei red_dc
und ir_dc
anstelle des Fragezeichens die passenden Variablen. Dabei benötigst du nur Variablen die innerhalb dieser Methode erstellt wurden. Schaue dir dazu die Abbildung an.
Damit die Methode einen Wert zurück gibt, wird als letztes der
return
Befehl gegeben. Diese gibt den AC- und den DC-Wert
aus.
# Diese Methode gibt den Wert des roten Lichts als AC- und DC-Wert zurück
def red_value(self):
red = self.sensor.get_red()
red_ac, self.red_w = remove_dc(red, self.red_w, alpha=0.95)
red_dc = ? - ?
return red_ac, red_dc
# Diese Methode gibt den Wert des infraroten Lichts als AC- und DC-Wert zurück
def ir_value(self):
ir = self.sensor.get_ir()
ir_ac, self.ir_w = remove_dc(ir, self.ir_w, alpha=0.95)
ir_dc = ? - ?
return ir_ac, ir_dc
1. Tipp
Du musst den Wert des gesamten Signals mit dem AC-Anteil subtrahieren.
2. Tipp
Verwende die Variable red
und red_ac
bzw. ir
und ir_ac
.
Initialisierung der Startsequenz¶
Bevor irgendwelche Werte für die Blutsauerstoffsättigung und den Puls
berechnet werden, sollen erstmal 100 Werte des roten und infraroten
Lichts gesammelt werden. Dadurch können direkt durchschnittliche Werte
berechnet werden, mit einer guten Näherung des aktuellen Pulses und der
Blutsauerstoffsättigung. Wenn erst wenige Werte vorhanden sind, wären
dies sehr ungenaue Werte, da es immer Messabweichungen gibt. In der
Methode setup
wird als erstes eine Variable i
erstellt, die als ein Zähler fungiert. Die while-Schleife läuft also
solange bis i
so groß wie die Länge der red_buffer
und ir_buffer
Listen ist. Innerhalb der Schleife werden die
zuvor definierten Methoden aufgerufen. Dadurch erhalten wir den
aktuellen Wert von dem roten und infraroten Licht. Der AC-Wert des von
dem Photodetektor gemessenen Lichts wird in der jeweils dazugehörigen
Liste hinzugefügt. Danach wird der Wert des Zählers i
um eins
erhöht. Dieses Vorgehen wird solange wiederholt bis die Bedingung der
while-Schleife nicht mehr erfüllt ist. Dann gibt die Methode mit dem
print
Befehl aus, dass alle Werte in der Liste gespeichert
sind.
# In dieser Methode werden die ersten 100 AC-Werte des roten/infraroten Lichts gespeichert
def setup(self):
print('Initialsierung der Startsequenz:')
i = 0
# Die while Schleife wird bis zum erreichen der Listenlänge wiederholt
while i < CIRCULAR_BUFFER_SIZE:
red_ac, red_dc = self.red_value()
ir_ac, ir_dc = self.ir_value()
# Der AC-Wert wird der Liste hinzugefügt
self.red_buffer.append(red_ac)
self.ir_buffer.append(ir_ac)
i += 1
print('Die ersten {} Werte sind in der Liste gespeichert'.format(CIRCULAR_BUFFER_SIZE))
Initialisierung der Variablen und des Displays¶
Als erstes werden einige Variablen initialisiert, welche in dieser
Methode benötigt werden. Dabei steht bpm
für beats per
minute, diese zeigt somit später den aktuellen Puls an. Die Variable
time_last_beat
ist für die Speicherung des vorherigen Pulses
zuständig und delta
ist die zeitliche Differenz zwischen dem
aktuellen Herzschlag und dem vorherigen. Dann wird noch eine neue Liste
erstellt, in welcher die Puls Werte gespeichert werden. Unter
spo2
speichern wir die Blutsauerstoffsättigungswerte. Die
Herzfrequenzvariabilität wird unter hfv
gespeichert. Als
nächstes wird wieder eine neue Liste erstellt, in welcher die
Herzfrequenzvariabilitätswerte gespeichert werden. Der Parameter N ist
auf 10 eingestellt, welches später die Listen Länge begrenzen wird.
Bevor wir mit den gemessenen Werten des Sensors Berechnungen vornehmen,
rufen wir unsere Methode setup()
auf, damit die ersten Daten
gesammelt werden.
# Mit dieser Methode wird der Puls, Blutsauerstoff, Herzfrequenzvariabilität berechnet und das Display konfiguriert
def run_sensor(self):
# Initialisierung der Variablen
# Beats per minute
bpm = 0
# Für die Speicherung des Zeitpunktes des letzten Herzschlags
time_last_beat = 0
# Zeitdifferenz zwischen 2 Herzschlägen
delta = 0
# Neue Liste, speichert die bpm Werte
bpms = []
# Blutsauerstoff Wert
spo2 = 0
# Herzfrequenzvariabilität
hfv = 0
# Neue Liste, speichert die hfv Werte
HFVs = []
# Listen länge
N = 10
# Paramter für die Display Aktualisierung
c = 0
# Ruft die Methode setup auf
self.setup()
Display Einstellungen¶
Die Werte von Puls, Blutsauerstoffsättigung und Herzfrequenzvariabilität
möchten wir am Ende auf dem Display ausgeben. Dazu muss erstmal eine
Verbindung zum Display hergestellt werden. Mit
release_displays()
wird das Display freigegeben. Wenn zum
Beispiel das Display kurz vorher schon mal anderweitig benutzt wurde und
die Pins belegt sind, werden mit diesem Befehl die Pins wieder
freigegeben. Danach stellen wir mit SPI eine Verbindung zum Display
her.
Experte
Was ist SPI? SPI bedeutet Serial Peripheral Interface. Es ist genauso wie bei I2C ein Master Slave Prinzip. Der Unterschied ist, dass es eine Dateneingangs- und eine Datenausgangsleitung gibt. Bei I2C gibt es nur eine Datenleitung. Die Taktleitung (clock) gibt vor in welcher Geschwindigkeit die Daten gesendet beziehungsweise empfangen werden. MOSI bedeutet Master Output Slave In. Die Daten werden also von unserem Mikrocontroller an das Display gesendet. MISO bedeutet genau das Umgekehrte, Master Input Slave Output. Dies ist auf None eingestellt, da keine Daten vom Display zum Mikrocontroller gesendet werden müssen 13.
Aufgabe
Schau auf die Rückseite der Platine. Dort ist ein Spickzettel für die Pinbelegung. Suche nach den beiden passenden Pins. Die Taktleitung wird mit SCK abgekürzt und beachte, dass die Verbindung für das OLED Display ist. Schreibe die gefundenen Zahlen jeweils direkt hinter GP, anstelle des Fragezeichens.
1. Tipp
Schaue welche Zahl bei Display_SCK steht und schreibe diese bei clock hin.
2. Tipp
Die Zahl für MOSI steht direkt unter Display_SCK.
# Display Konfiguration
displayio.release_displays()
spi = busio.SPI(clock=board.GP?, MOSI=board.GP?, MISO=None)
Damit das SPI überhaupt verwendet werden kann muss es mit dem
unlock()
Befehl entsperrt werden. Als nächstes wird das
Display mit allen Pins verbunden. Die baudrate
gibt an, wie
viele Signaländerungen pro Sekunde an das Display übergeben werden
können. Für das Display benötigen wir eine Initialisierungssequenz,
welche in der Bibliothek ST7789
enthalten ist. Des Weiteren
muss dort die Anzahl der Pixel in Breite und Höhe eingegeben werden.
Danach wird ein Index der ersten möglichen Zeile und die Rotation des
Displays festgelegt. Dabei kann in 90 Grad Schritten das Display gedreht
werden. Die blacklight_pin
ist für die Hintergrundbeleuchtung
des Displays zuständig 4. Damit später die Werte auf dem
Display angezeigt werden, erstellen wir erstmal eine Gruppe. In der
Gruppe werden sozusagen alle Texte gesammelt und mit dem Befehl
show()
angezeigt 14.
Aufgabe
Trage in die Lücken anstelle des Fragezeichens, die Höhe und Breite des Displays in Pixel ein. Schaue dir dazu genau das Display auf der Platine an. Dort steht die Anzahl der Pixel.
spi.unlock()
display_bus = displayio.FourWire(spi, reset=board.GP13, command=board.GP12, chip_select=board.GP11, baudrate=62500000)
display = ST7789(display_bus, width=?, height=?, rowstart=80, rotation=180, backlight_pin=board.GP10)
# Display
splash = displayio.Group()
display.root_group=splash
Um Bilder auf dem Display anzuzeigen werden diese als Bitmap
gespeichert. Eine Bitmap_datei
unterteilt das Bild in Pixel,
wobei jedes Pixel eine Farbe repräsentiert. Das Herzsymbol wird unter
bitmap
gespeichert. Als nächstes wird das Bild als Tile Grid
gespeichert, damit es im nächsten Schritt auf dem Bildschirm angezeigt
werden kann. Danach wird die Position mit x und y festgelegt 15.
# Herz Symbol
# Symbol wird als Bitmap gespeichert
bitmap = displayio.OnDiskBitmap("/heart.bmp")
# Tile grid wird erstellt, damit Symbol angezeigt wird
tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
splash.append(tile_grid)
# Position des Symbols
tile_grid.x=150
tile_grid.y=140
Als nächstes wird mit Label
der Text für das SpO2 erstellt.
Mit terminalio.FONT
wird die Schriftart festgelegt. Danach
wird der Text festgelegt und die Farbe. Die Farbe wird als
Hexadezimalsystem angegeben.
Was ist das Hexadezimalsystem?¶
Dezimalzahl | Dualzahl | Hexadezimalzahl |
---|---|---|
0 | 0000 | 0 |
1 | 0001 | 1 |
2 | 0010 | 2 |
3 | 0011 | 3 |
4 | 0100 | 4 |
5 | 0101 | 5 |
6 | 0110 | 6 |
7 | 0111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | A |
11 | 1011 | B |
12 | 1100 | C |
13 | 1101 | D |
14 | 1110 | E |
15 | 1111 | F |
Es ist ein Zahlensystem, welches auf der Basis 16 beruht. In der Tabelle sind im Vergleich die Dual-/Binär-, Dezimal- und Hexadezimalzahlen abgebildet. Dort siehst du den Längenunterschied zwischen dem Zahlensystem nochmal genauer. Das Hexadezimalsystem ist viel kompakter. Wie in dem Beispiel zu sehen, stellt man dem Hexadezimalsystem ein 0x vorne weg. Dadurch kann das System von anderen Zahlensystemen unterschieden werden. Da danach zwei Hexadezimalzahlen folgen, besitzt die erste Zahl den Exponenten 1 und die letzte 0. Die Basis ist dabei 16. In der kannst du nachschauen, welche Dezimalzahl F bedeutet. Nach diesem Prinzip kann die Hexadezimalzahl in eine Dezimalzahl übergeführt werden.
\(\begin{aligned} 0xFF &= 15 \cdot 16^1 + 15 \cdot 16^0\\ &= 255 \label{HexaBeispiel}\end{aligned}\)
Jetzt kommen wir zurück zu der Darstellung der Farben. Dabei folgen nach den 0x sechs Hexadezimalzahlen in der Form 0xRRGGBB. Dabei steht R für rot, G für grün und B für blau. Dies erfolgt also nach dem RGB Schema.
RGB steht für rot, grün und blau. Diese drei Farben werden in unterschiedlichen Verhältnissen gemischt, um eine beliebige Farbe zu erhalten. Dabei besitzt jeder der drei Grundfarben ein Wert zwischen 0 und 255 16.

In der Tabelle sind in der ersten Spalte die verschiedenen Hexadezimalen Werte angegeben. In der zweiten Spalte steht welchem RGB-Anteil dies entspricht und in der letzten, welche Leuchtkraft.
Aufgabe
Suche dir eine Farbe für den Text aus, welche später auf dem Display der Platine zu sehen ist. Schaue dir dafür die Tabelle an und verwende den Hex-Wert. Ergänze dazu nach 0x die Farbe. Zum Beispiel steht 0xFF0000 für rot.
Hex-Wert | Dezimalwert | Leuchtkraft() |
---|---|---|
00 | 0 | 0 |
40 | 64 | 25 |
80 | 128 | 50 |
C0 | 192 | 75 |
FF | 255 | 100 |
# Display SpO2
spo2_label=label.Label(terminalio.FONT, text="SpO2 ", color=0x??????)
Nachdem die Farbe für SpO2 festgelegt ist, wird nun die Position für den Text angegeben. Dabei haben wir ein Display mit 240x240 Pixel. Das Display ist wie ein Koordinatensystem aufgebaut 17. Wie in der Abbildung zu sehen, ist oben links im Display die Koordinate 0,0. Nach rechts reicht die x-Achse von dem Wert 0-239. Die y-Achse geht von der Koordinate 0,0 senkrecht nach unten mit den Werten 0-239.

Als nächstes erfolgt mit spo2_label.scale
die Skalierung der
Schrift, sozusagen die Festlegung der Schriftgröße. Danach muss zum
anzeigen der Schrift das label unserer Display Gruppe hinzugefügt
werden. Dies wird mit append()
vorgenommen.
spo2_label.x = 10
spo2_label.y = 20
spo2_label.scale = 2
# Label auf dem Display anzeigen
splash.append(spo2_label)
Als nächstes soll der aktuelle Wert der Sauerstoffsättigung auf dem
Display ausgegeben werden. Der Wert der spo2
Variablen wird
weiter unten im Quellcode berechnet. In die geschweifte Klammer
schreiben wir unsere Variable spo2
damit der aktuelle Wert
der Sauerstoffsättigung angezeigt wird. Nach der Variablen folgt ein
Prozentzeichen, da die Blutsauerstoffsättigung in Prozent angegeben
wird. Danach folgt das gleiche Vorgehen wie oben, bei der Anzeige von
SpO2.
Aufgabe
Welche Farbe möchtest du für den Wert der Blutsauerstoffsättigung? Schreibe diese anstelle der Fragezeichen, in Form des Hex-codes rein.
# Display SpO2 Wert
spo2_string = f"{spo2}%"
spo2_value_label = label.Label(terminalio.FONT, text = spo2_string, color = 0x??????)
Aufgabe
Was ist die y-Position des spo2_value_label
, wenn wir dieses in der gleichen Zeile wie den String SpO2 stehen haben wollen? Trage diesen Wert ein. Tipp: Wie vorher ist die Position 20.
# Position des labels
spo2_value_label.x = 100
spo2_value_label.y = ?
Aufgabe
Welchen Wert hat der Skalierungsfaktor, wenn wir möchten das der Text die gleiche Größe, wie unser vorheriger Text hat? Trage diesen ein.
spo2_value_label.scale = ?
Tipp
Wie vorher ist dieser 2.
Aufgabe
Was muss in der Klammer stehen? Wenn du es nicht mehr genau weißt schau dir den vorherigen Abschnitt an.
# Label auf dem Display anzeigen
splash.append(?)
Tipp
Schreibe in die Klammer spo2_value_label.
Das gleiche Vorgehen folgt nun für die Anzeige des Pulses und für die Herzfrequenzvariabilität.
# Display BPM
bpm_label=label.Label(terminalio.FONT, text="BPM ", color=0xFF0000)
# Position des labels
bpm_label.x = 10
bpm_label.y = 60
bpm_label.scale = 2
# Label auf dem Display anzeigen
splash.append(bpm_label)
bpm_string = f"{bpm}"
bpm_value_label = label.Label(terminalio.FONT, text = bpm_string, color = 0xFF0000)
# Position des labels
bpm_value_label.x = 100
bpm_value_label.y = 60
bpm_value_label.scale = 2
# Label auf dem Display anzeigen
splash.append(bpm_value_label)
# Display Herzfrequenzvariabilität
hfv_label=label.Label(terminalio.FONT, text="HFV ", color=0x00FF00)
# Position des labels
hfv_label.x = 10
hfv_label.y = 100
hfv_label.scale = 2
# Label auf dem Display anzeigen
splash.append(hfv_label)
hfv_string = f"{hfv}ms"
hfv_value_label = label.Label(terminalio.FONT, text = hfv_string, color = 0x00FF00)
hfv_value_label.x = 100
hfv_value_label.y = 100
hfv_value_label.scale = 2
# Label auf dem Display anzeigen
splash.append(hfv_value_label)
While-Schleife¶
Als nächstes folgt eine While-Schleife. Die Schleife wird, wenn das Programm läuft und kein Fehler auftritt, ständig wiederholt. Als erstes möchten wir immer den aktuellen Wert des roten und infraroten Lichts erhalten.
Aufgabe
Rufe die passende Methode auf. Einmal für das rote und einmal für das infrarote Licht.
while True:
# Gibt die aktuellen Werte des roten/infraroten Lichts an
red_ac, red_dc = self.?
ir_ac, ir_dc = self.?
1. Tipp
Wenn du es nicht mehr weißt schau dir die Methode setup()
nochmal an.
2. Tipp
Füge in das erste Fragezeichen red_value()
ein.
3. Tipp
Füge in das zweite Fragezeichen ir_value()
ein.
Da wir für die spätere Berechnung auch vergangene Werte des roten und
infraroten Lichts benötigen, werden mit append()
die Werte in
einer Liste gespeichert.
# Der neue Wert wird in der Liste gespeichert
self.red_buffer.append(red_ac)
self.ir_buffer.append(ir_ac)
Damit das Pulsoxymeter nicht Werte berechnet, obwohl gar kein Finger im Pulsoxymeter ist, soll dies erkannt werden. Dafür verwenden wir eine if-Bedingung. Wenn der DC-Wert des roten Lichts über 15000 liegt, ist mit großer Wahrscheinlichkeit ein Finger erkannt worden.
# Wenn der Wert von 15000 überschritten ist, dann ist mit großer Wahrscheinlichkeit ein Finger erkannt worden.
# Die folgenden Funktionen werden dann ausgeführt.
if red_dc>15000:
Da es immer kleine Messfehler und Ungenauigkeiten im Signalverlauf gibt, wird ein FIR-Tiefpassfilter verwendet. Einfach gesagt glättet dieser unser Signal.
# Fir Tiefpassfilter
red_fir = fir_lowpass(self.red_buffer.get_buffer(), FIR_FILTER_COEFFS)
Wir rufen nun die Methode check_beat()
auf. Diese soll in
unserem roten AC-Signal schauen, ob es ein beat, also ein Pulsschlag
gibt. Mithilfe der if-Bedingung soll dies untersucht werden.
Aufgabe
Was muss dafür in der Klammer von check_beat
stehen?
# Wenn ein Herzschlag erkannt wurde, wird der Puls berechnet.
if self.heart_rate.check_beat(?):
1. Tipp
Wir wollen das rote AC-Signal untersuchen, welches zuvor mit einem Filter geglättet wurde.
2. Tipp
Schreibe red_fir
in die Klammer.
Der Teil in der if-Bedingung wird nur ausgeführt, wenn ein Puls erkannt wurde. Dabei sucht der Algorithmus nach den Spitzen in einem Signal.
Danach wird mit ticks_ms()
ein timer gestartet. Unter
time_last_beat
wird der Zeitpunkt des vorherigen beats
gespeichert. Bei delta
wird also die Zeitdifferenz zwischen
zwei detektierten beats berechnet, wie in zu sehen. Der Puls wird immer
auf die Herzschläge pro Minute berechnet. Da eine Minute 60000
Millisekunde hat wird dies durch die Zeitdifferenz zwischen einem beat
dividiert. Daraus ergibt sich ein ungefährer Puls.

# Ein timer wird in ms gestartet
t = ticks_ms()
# Die zeitliche Differenz zwischen den letzten 2 Herzschlägen wird berechnet.
delta = ticks_diff(t, time_last_beat)
# Der Zeitpunkt des Herzschlags wird gespeichert, damit beim nächsten Durchlauf wieder die Zeit zwischen den letzten beiden Herzschlägen berechnet werden kann.
time_last_beat = t
# Der Puls wird berechnet.
bpm = 60000 / delta
Damit die peak Detektion etwas genauer wird, wird nochmals eine if-Bedingung eingefügt. Diese wird nur ausgeführt, wenn der Puls unter 250 liegt.
Experte
Der maximal Puls eines Menschen, welcher bei maximaler körperlicher Anstrengung erreicht werden kann, kann mit der Sally-Edwards-Formel berechnet werden 19.
\(\begin{aligned} M = 214 - 0,5 \cdot Lebensalter - 0,11 \cdot Gewicht (in\ Kg) \label{maxpulsM}\end{aligned}\)
\(\begin{aligned} F = 210 - 0,5 \cdot Lebensalter - 0,11 \cdot Gewicht (in\ Kg) \label{maxpulsF}\end{aligned}\)
Dabei steht M für die Berechnung des Maximalpulses für Männer und F für Frauen.
Da die beiden Gleichungen oben nur einer Näherung entsprechen, ist der maximale Puls auf 250 für den Pulsoxymeter festgelegt. Alles was darüber hinaus geht sind mit hoher Wahrscheinlichkeit Messfehler. Wenn der Puls über 250 ist, entspricht dies Kammerflattern und bei über 400 Schlägen pro Minute spricht man von Kammerflimmern 20. Da das Pulsoxymeter in unserem Versuch nicht darauf ausgelegt ist Kammerflattern und -flimmern erkennen zu müssen schließen wird dies aus.
Wenn also ein Puls von unter 250 gemessen wurde, wird dieser Wert der
Liste bpms
angehängt. Danach werden die letzten zehn Elemente
in unserer Liste gespeichert. Der älteste Wert wird somit gelöscht.
Für die Berechnung der Herzfrequenzvariabilität, werden in der Liste
HFVs
die delta
Werte gespeichert. Die Variable
delta
speichert den zeitlichen Abstand zwischen zwei
Herzschlägen.
# Es werden die letzten 10 berechneten Pulse, gespeichert, wenn der Puls unter 250 liegt.
if (bpm < 250):
bpms.append(bpm)
bpms = bpms[-10:]
HFVs.append(delta)
Was ist die Herzfrequenzvariabilität?🫀¶
Wie du in der Abbildung sehen kannst, schwankt die Zeit zwischen zwei Herzschlägen. Genau diese zeitliche Abweichung stellt die Herzfrequenzvariabilität dar.

Die nächste if-Bedingung wird ausgeführt, wenn die Länge der
HFVs
Liste größer als N ist. Dadurch werden erstmal die Werte
gesammelt, damit danach ein Mittelwert gebildet werden kann. Innerhalb
der if-Bedingung werden die letzten N Elemente in der Liste gespeichert
und das älteste Element gelöscht. Als nächstes wird die
Herzfrequenzvariabilität berechnet. Dafür verwenden wir die folgende Formel 22.
\(HRV = \sqrt{\frac{1}{N-1}\sum_{i=1}^{N-1}(RR_{i+1}-RR_{i})^2} \label{HRV2}\)
Dabei benötigen wir eine Summenformel, welche mithilfe der for-Schleife
umgesetzt wird. Dabei stellt die for-Schleife sozusagen die Grenzen der
Summenformel dar. Zuerst wird für i
Null eingesetzt, danach
eins und so weiter. Das range()
erstellt eine Liste von
Zahlen von 0 bis einschließlich N-1. In unserem Fall ist N-1 = 9. Es
wird somit eine Liste mit 9 Elementen erstellt. Dies entspricht 0, 1, 2,
3, 4, 5, 6, 7, 8. Mithilfe der Summenformel wird ein Mittelwert über die
letzten 10 HFVs
Werte gebildet.
# Die Herzfrequenzvariabilität wird berechnet, wenn N Werte vorhanden sind.
if (len(HFVs))>N:
HFVs = HFVs[-N:]
hfv = round(sqrt(1/(N-1) * sum((HFVs[1+i]-HFVs[i])**2 for i in range(N-1))))
Für die berechneten Pulswerte wird ebenso ein Mittelwert gebildet. Da wir zehn Pulswerte gespeichert haben, addieren wir diese zehn Elemente auf und dividieren durch zehn.
# Es wir der aktuelle Puls mit der Bildung eines Mittelwertes berechnet.
bpm = int(sum(bpms)/10)
Jetzt kann als nächstes der Blutsauerstoff berechnet werden. Dort werden
zuerst die 100 Werte des roten Lichts, welche in der Liste gespeichert
sind mit sich selbst multipliziert, dann addiert und die Wurzel gezogen.
Am Ende wird dies durch 100 dividiert, um wieder einen Mittelwert zu
erhalten. Dadurch wird ein quadratisches Mittel berechnet, welche etwas
größere Werte stärker gewichtet als kleine. In ist die allgemeine Formel
für die Berechnung des quadratischen Mittels gegeben 23. Das Gleiche Verfahren wird danach für das
infrarote Licht durchgeführt. Als nächstes wird mit der Variablen
r_ratio
das Modulationsverhältnis ermittelt. Dies ist ein
Verhältnis zwischen dem infraroten AC- und DC-Wert und dem roten AC- und
DC-Wert. Das Verhältnis wird durch die Blutsauerstoffsättigung
beeinflusst.
\(QM = \sqrt{\frac{1}{N}\sum_{i=1}^{N}(x_{i})^2} \label{QuadratischesMittel}\)
# Der Blutsauerstoffsättigungswert wird berechnet.
red_rms = sqrt(sum(i * i for i in self.red_buffer.get_buffer()) / CIRCULAR_BUFFER_SIZE)
ir_rms = sqrt(sum(i * i for i in self.ir_buffer.get_buffer()) / CIRCULAR_BUFFER_SIZE)
r_ratio = (ir_rms / ir_dc) / (red_rms / red_dc)
Eine Näherung für die Berechnung des Blutsauerstoffgehalts ist die Formel 24.
spo2 = round(110 - 25 * r_ratio, 1)
Da es kleine Messfehler gibt durch zum Beispiel die Bewegung des Finger, kann es passieren, dass der Blutsauerstoffgehalt angeblich größer als 100% ist.
Aufgabe
Erstelle eine if-Bedingung, welche Werte über 99,9% als einen spo2 Wert von 99,9% anzeigt.
# Wenn der spo2 Wert über 100% liegen sollte, wird dieser als 99,9% angegeben.
?
Tipp
if spo2 > 99.9:
spo2 = 99.9
Danach möchten wir noch, dass unser Pulsoxymeter keine Werte ausgibt, wenn kein Finger vorhanden ist. Dabei wird der DC-Wert betrachtet, da dieser Wert durch die Reflexion an Haut, Gewebe und weiteren nicht pulsierenden Komponenten des Fingers einen relativ konstanten Wert besitzt.
Aufgabe
Welche Werte sollen die folgenden Variablen annehmen, wenn kein Finger erkannt wurde, also der DC-Wert kleiner ist als dein festgelegter Schwellenwert.
# Wenn der DC-Wert des roten Lichts unter 15000 beträgt, werden die Variablen auf 0 gesetzt.
if red_dc < 15000:
bpm = ?
spo2 = ?
red_fir = ?
hfv = ?
Tipp
Die Werte sollen 0 sein.
Der letzte Schritt in unserer while-Schleife ist die Aktualisierung der
Werte auf dem Display. Damit nicht bei jedem while-Schleifen Durchlauf
die Werte aktualisiert werden und Ressourcen gespart werden, wird eine
weitere if-Bedingung eingeführt. Dabei wird bei jedem 50. Durchlauf der
Wert auf dem Display aktualisiert. Dafür wird ein formatierter String
verwendet. Das .1f
bedeutet eine Nachkommastelle. Das f
leitet sich von float ab, dies ist ein Datentyp, welcher Kommazahlen
speichern kann. Mit der nächsten Zeile kann nun der aktuelle String
angezeigt werden. Das gleiche Vorgehen folgt nun für den Puls und die
Herzfrequenzvariabilität.
Aufgabe
Auf wie viel Kommastellen sollte der Puls gespeichert werden? Trage den Wert in das Fragezeichen ein.
# Jeden 50. Durchlauf der while-Schleife werden die Werte auf dem Display aktualisiert.
if c==50:
c=0
# Display SpO2 Wert
spo2_string = f"{spo2:.1f}%"
spo2_value_label.text = spo2_string
# Display BPM Wert
bpm_string = f"{bpm:.?f}"
bpm_value_label.text = bpm_string
# Display HFV Wert
hfv_string = f"{hfv:.0f}ms"
hfv_value_label.text = hfv_string
c+=1
1. Tipp
Sollte ein Puls eine Kommastelle haben?
2. Tipp
Es ist eine 0.
Programm starten¶
Jetzt haben wir unser Programm fertig gestellt. Das Programm wird mit der Klasse und der Methode aufgerufen.
Aufgabe
Schreibe dies nun unten in den Quelltext, damit das Programm fertig gestellt wird.
Pulsoxymeter().run_sensor()
Jetzt sollte alles funktionieren und deine Parameter sollten auf dem Display der Platine angezeigt werden. Wenn etwas nicht funktioniert, wird eine Fehlermeldung ausgegeben, in der steht wo der Fehler ist. Gehe zu der Aufgabe zurück und schaue sie dir nochmal an.
Achte bei der Messung darauf deinen Finger möglichst ruhig zu halten. Durch Bewegung werden die Messwerte verfälscht.
Zusatzaufgaben¶
Puls plotten¶
Wenn du schon den Puls, die Blutsauerstoffsättigung und die Herzfrequenzvariabilität auf dem Display siehst, kannst du dir als nächstes das AC-Signal anschauen. Damit du einen Plot auf dem PC-Bildschirm sehen kannst, wird der print-Befehl verwendet 25. Füge also
print((red_fir,))
deinem Quelltext hinzu. Dabei sollte dies am besten am Ende der if-Bedingung
if red_dc>15000:
stehen. Dadurch wird der Plot nur angezeigt, wenn ein Finger detektiert wird. Jetzt kannst du das Programm ausführen. Damit der Plot zu sehen ist, drücke oben bei dem Mu-Editor auf Plotter. Jetzt sollte sich neben dem REPL eine Plotter öffnen. Wenn du jetzt mit deinem Finger eine Messung durchführst ist das AC-Signal zu sehen. Dort siehst du eine Wellenform, welches dein Puls darstellt. Achte darauf deinen Finger ruhig zu halten. Danach kannst du dir anschauen, was passiert wenn du deinen Finger bewegst. Was hat dies für Folgen für deine Messung?
Plot auf dem Display¶
Als nächstes kannst du den Plot auch auf dem Display anzeigen lassen.
Dabei wird die Höhe und die Breite in Pixel des zusehenden Graphen
eingestellt. Mithilfe der Sparkline()
Funktion wird ein Graph
erstellt. Bei max_items
, wird angegeben wie viele Werte in
einem Graph angezeigt werden. Mit x und y wird die Position des Graphen
auf dem Display festgelegt. Der folgende Quelltext wird vor die
while-Schleife geschrieben.
Aufgabe
Damit der Graph angezeigt wird, muss etwas in der Klammer ergänzt werden. Ergänze diese Lücke.
# BPM Plot
chart_width = 50
chart_height = 40
sparkline1 = Sparkline(width=chart_width, height=chart_height, max_items=15, x=30, y=150)
splash.append(?)
1. Tipp
Trage in die Klammer sparkline1
ein.
Damit der Plot von dem roten Licht erst angezeigt wird wenn es in einem
relevanten Wertebereich liegt, wird der Bereich mithilfe der
if-Bedingung begrenzt. Innerhalb der Schleife wird der Wert des roten
Lichts auf eine Nachkommastelle gerundet. Mithilfe von
auto_refresh = False
wird das Display kurz ausgeschaltet und
dann der neue Wert dem Plot hinzugefügt. Danach wird das Display wieder
angeschaltet und der neue Wert wird mit auf dem Plot angezeigt 26.
# Das Signal des roten Lichts wird geplottet, wenn der Wert zwischen -500 und +500 liegt. Damit nur relevante Daten angezeigt werden.
if (-500<red_fir<500):
red_plot = round(red_fir,1)
display.auto_refresh = False
sparkline1.add_value(red_plot)
display.auto_refresh = True
Diesen Teil schreibst du innerhalb der folgenden if-Bedingung.
if red_dc>15000:
Wenn alles funktioniert kannst du noch weitere Sachen mit dem Pulsoxymeter ausprobieren. Du kannst zum Beispiel verschiedene Farben ausprobieren, die Position des Textes ändern oder dir selbst etwas ausdenken.
-
Sebastian Dörn. Python lernen in abgeschlossenen Lerneinheiten. Springer Vieweg, Wiesbaden, 2020. ↩
-
Core modules. URL: https://docs.circuitpython.org/en/latest/shared-bindings/index.html (visited on 2023-09-28). ↩
-
Circuitpython libraries. URL: https://circuitpython.org/libraries (visited on 2023-10-01). ↩
-
Gut gesättigt! URL: https://become-an.engineer/2023/ies/ (visited on 2023-11-01). ↩↩
-
Sam Koblenski. Everyday dsp for programmers: dc and impulsive noise removal. URL: http://sam-koblenski.blogspot.com/2015/11/everyday-dsp-for-programmers-dc-and.html (visited on 2023-08-29). ↩
-
Funktionen und methoden bei variablen/strings. URL: https://www.python-lernen.de/unterschied-methoden-funktionen.htm (visited on 2023-10-25). ↩
-
- objekte und klassen. URL: https://pythonbuch.com/objekte.html (visited on 2023-10-18).
-
Python self. URL: https://www.w3schools.com/python/gloss_python_self.asp (visited on 2023-11-02). ↩
-
Enrique Fernandez. I2c-bus. URL: http://fmh-studios.de/theorie/informationstechnik/i2c-bus/ (visited on 2023-09-30). ↩
-
Max30102. 2018. URL: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX30102.pdf (visited on 2023-10-02). ↩
-
First in - first out (fifo). URL: https://www.datenbanken-verstehen.de/lexikon/first-in-first-out/ (visited on 2023-10-18). ↩
-
Eberhard Koch. Anästhesievorbereitung und perioperatives Monitoring. Thieme, München und Frankfurt am Main, 2015. ↩
-
Spi protocol. URL: https://learn.adafruit.com/circuitpython-basics-i2c-and-spi/spi-devices (visited on 2023-10-01). ↩
-
Group. URL: https://learn.adafruit.com/circuitpython-display-support-using-displayio/group (visited on 2023-10-15). ↩
-
Display a bitmap. URL: https://learn.adafruit.com/circuitpython-display-support-using-displayio/display-a-bitmap (visited on 2023-09-25). ↩
-
Über die farbmodelle rgb, cmyk und hex, ihre anwendung und umwandlung. URL: https://www.techsmith.de/blog/rgb-und-cmyk-unterschied/ (visited on 2023-10-20). ↩
-
Grouplibrary overview. URL: https://learn.adafruit.com/circuitpython-display-support-using-displayio/library-overview (visited on 2023-10-15). ↩
-
Elektrokardiografie (ekg). URL: https://www.kardionet.de/elektrokardiographie-ekg/ (visited on 2023-10-19). ↩
-
Trainingspuls. URL: https://www.kardiologie-gamm.de/sportmedizin/trainingspuls/ (visited on 2023-10-12). ↩
-
Nicole Menche. Biologie Anatomie Physiologie. Elsevier, München, 2016. ↩
-
Alles, was du über die herzfrequenzvariabilität (hfv) wissen musst. URL: https://www.whoop.com/ae/de/thelocker/alles-was-du-uber-die-herzfrequenzvariabilitat-hfv-wissen-musst/ (visited on 2023-10-22). ↩
-
Nicole Franke-Gricksch. Berechnung des hrv-werts rmssd. URL: https://hrv-herzratenvariabilität.de/2017/10/berechnung-des-hrv-werts-rmssd/ (visited on 2023-09-12). ↩
-
Quadratisches mittel. URL: https://de.wikipedia.org/wiki/Quadratisches_Mittel (visited on 2023-10-21). ↩
-
How to design peripheral oxygen saturation (spo2) and optical heart rate monitoring (ohrm) systems using the afe4403. URL: https://www.ti.com/lit/an/slaa655/slaa655.pdf (visited on 2023-09-09). ↩
-
Plotting data with mu. URL: https://codewith.mu/en/tutorials/1.2/plotter (visited on 2023-09-04). ↩
-
Sparkline simple test. URL: https://docs.circuitpython.org/projects/display-shapes/en/latest/examples.html#sparkline-simple-test (visited on 2023-09-20). ↩