Zeitsynchronisation mit BLE

Viele technische Anwendungen erfordern die zeitliche Synchronisation verschiedener Systemkomponenten. So sind auch wir bei MEDtech schon häufig mit diesem Thema in Kontakt gekommen. In diesem Blog möchte ich zunächst generell auf Zeitsynchronisation eingehen und danach eine Methode vorstellen, mit welcher Master-Zeitstempel unter Verwendung von BLE-Advertising Paketen verschickt und von Slaves empfangen sowie ausgewertet werden.Dabei soll speziell auf die Anforderungen eingegangen werden, welche an den verwendeten (Mikro-)Controller sowie die HF-Einheit gestellt werden. Daraus sollen sich dann die auftretenden Probleme ableiten und erklären lassen.

Zeitsynchronisation: Was ist das?

Die Antwort auf diese Frage liegt den (hoffentlich) vielen lesenden Personen meines Beitrags bestimmt auf der Hand. Da ein Jeder erfahrungsgemäß aber eine leicht abweichende („eigene“) Definition für Zeitsynchronisation besitzt, möchte ich meine Definition des Begriffs, zumindest für den Kontext dieses Artikels, einmal niedergeschrieben haben:

Zeitsynchronisation ist die Orientierung mehrerer Systemkomponenten an einem gemeinsamen, systemübergreifenden Taktsignal.

Die Begrifflichkeit „Taktsignal“ ist hierbei von entscheidender Bedeutung: ein technisch verwertbares Taktsignal kann z.B. durch die Winkelbeschleunigung des Sekundenzeigers einer analogen Wanduhr erzeugt werden. Die Voraussetzung ist hier, dass die Uhr das typische „Tick“-Verhalten aufweist: der Zeiger wechselt in jeder Sekunde zwischen Stillstand und Bewegung. Ein Taktsignal ist also ein sich mit der Zeit änderndes, periodisches Signal. Die Taktdauer entspricht der Periodendauer des Signals (in unserem Fall 1s).

Quarze statt Wanduhren!

In der Welt der Mikroelektronik wird für die Erzeugung eines Taktes aber selbstverständlich keine Wanduhr benutzt (das wäre wirklich umständlich, schon allein aus Platzgründen). Wir setzen hier auf kleine, aber sehr komfortabel einsetzbare Helferlein: sogenannte Oszillatoren. Diese generieren aus einer angelegten Gleichspannung ein periodisch schwingendes Signal. Es existieren viele verschiedene Oszillatortypen. Ein sehr gängiger sowie genauer Typ sind sogenannte Kristalloszillatoren (häufiger als Quarz bezeichnet, manchmal mit XTAL abgekürzt). Deren Funktionsweise basiert auf den piezoelektrischen Eigenschaften der verwendeten Kristallstruktur. Die Genauigkeit liegt hier typischerweise im Bereich 10-100ppm (parts per million). Ein wenig ungenauer sind RC-Oszillatoren: bei diesen wird mithilfe mehrerer RC-Glieder, die auf eine Verstärkerschaltung rückgekoppelt werden, eine 180° Phasendrehung und damit ein schwingungsfähiges System erzeugt. Weiterhin existieren in der Praxis: Schwingkreise bestehend aus Kondensatoren und Spulen, Keramikresonatoren, elektromechanische Oszillatoren (MEMS-Resonatoren). Eine große Tücke ist, dass die Genauigkeit eines Oszillators große Abhängigkeiten aufweist: so z.B. die durch die Leiterplatte entstehenden parasitären Kapazitäten. Auch zeigt die Genauigkeit bei den meisten Typen starke Temperatur- und Altersabhängigkeit.

Aus der variierenden Genauigkeit ergibt sich eine Anforderung an die Synchronisation: möchte man die Uhr einer Systemkomponente mit einer übergeordneten Uhr synchronisieren, so genügt es nicht, die zwei Uhren lediglich zum selben Zeitpunkt zu starten. Die Uhren driften nämlich mit der Zeit auseinander. Es ist also ein kontinuierlicher Austausch (Re-Synchronisation) notwendig. Das soll den sogenannten „Clock-Drift“ kompensieren.

BLE als Kommunikationskanal

Nun stellt sich die Frage: worüber kann dieser Austausch erfolgen, wenn die Systeme physisch voneinander getrennt sind? Wie die Überschrift des Artikels verrät, möchte ich an dieser Stelle über die Verwendung von Bluetooth Low Energy sprechen. BLE bietet sich auf den ersten Blick nicht gerade für Synchronisationsanwendungen an: so werden beispielsweie nicht-verbindungsorientierte Pakete (sogenannte Advertising-Pakete) quasi azyklisch verschickt. Auf den Paketzyklus wird nämlich eine Zufallszeit aus dem Intervall 0-10 ms addiert, um Kollisionen mit anderen Paketen vorzubeugen. Auch das Auftreten von Latenzen macht bei der Synchronisation zu schaffen: Nutzdaten müssen nämlich bei BLE einen riesigen Protokoll-Stack durchlaufen. Bei hohen Datenraten entsteht hier schnell Konkurrenz mit anderen Daten, wodurch die Synchronisationsdaten unter Umständen gepuffert werden müssen. Nichtsdestotrotz ist BLE in den vergangenen Jahren aufgrund der gesteigerten Anforderungen an die Batterielaufzeit zu einem der weitverbreitesten Nahbereichs-Funkstandards aufgestiegen. Hierauf begründet sich die Notwendigkeit, auch eine Synchronisation über BLE durchführen zu können.

Synchronisation mit BLE

Ich möchte im Folgenden eine Methode vorstellen, die Advertising Kanäle nutzt, um Synchronisationspakete von einem Master an einen oder mehrere Slaves zu übermitteln. Die in meinen Versuchen erzielte Genauigkeit lag dabei im Rahmen von ca. 250µs, wobei der Optimierungsfokus auf die Stromeinsparung gerichtet war. Als Grundlage für die Arbeit dienten mir die Veröffentlichungen CheepSync und BlueSync.
In einem Blockdaigramm möchte ich zunächst die prinzipielle Architektur vorstellen:

Da Bluetooth-Stacks in der Regel gekapselt vom Rest der Software betrachtet werden müssen, findet die Magie auf der Applikationsebene statt. Nun stellt sich die Frage: worum müssen wir uns hier denn überhaupt kümmern? Die Antwort möchte ich in einer Liste zusammenfassen.

  1. Typ und Inhalt des Synchronisationspakets
  2. Radio-Timing beim Empfang des Synchronisationspakets
  3. Verarbeitung des Synchronisationspakets
    • Identifikation der Pakete
    • Latenzkompensation
    • Plausibilitätscheck
    • RTC-Synchronisation

Im Folgenden möchte ich näher auf die einzelnen Punkte eingehen.

Typ und Inhalt der Pakete

Zunächst einmal etwas Grundsätzliches: der hier beschriebene Algorithmus basiert auf der Verbreitung von BLE-Advertising Paketen vom Typ ADV_IND_NONCONN (durch den BLE-Substandard spezifiziert). Diese Pakete sind von allen Scanning-fähigen BLE-Nodes empfangbar, laden allerdings nicht zum Aufbau einer Verbindung ein. Weiterhin erlauben diese Pakete den Transport von generischen, anwendungsspezifischen Informationen: nämlich im AD-Modus „Manufacturer Specific Data“ (siehe PDF-Dokument AD-Typen). Nachteil der Advertising Pakete ist, dass diese keiner Sicherung unterliegen: geht während der Übertragung einmal ein Paket verloren oder wird beschädigt, kommt es zu keiner Retransmission. Fehlerhafte Pakete werden jedoch durch eine Checksumme durch den BLE-Stack erkannt und eliminiert.
Um den Algorithmus richtig ausführen zu können, benötigen wir mindestens 3 Komponenten. Diese sind:

  1. Eindeutiger Identifikator zur Unterscheidung von anderen Paketen (z.B. 16 Bit)
  2. RTC-Zeitstempel, auf den sich der/die Slaves synchronisieren werden (je nach Registergröße zwischen 8 und 32 Bit)
  3. Paketnummer (zur Erkennung von Paketverlust, wenige Bits mit Überlaufhandling)

Beim benutzten Advertising Typ ist eine Nutzdatengröße von bis zu 31 Byte möglich. Trotzdem sollten die Pakete möglichst klein ausfallen: Da es beim Advertising nicht zu Retransmission kommt, ist ein Verlust der Pakete durch Kollision mit anderen Paketen sowie Konkurrenz mit anderen Funkstandards im unlizenzierten ISM Band möglich. Kleine Pakte verringern die Übertragungsdauer der Pakete und damit die Wahrscheinlichkeit einer Kollision.

Radio Timing beim Empfang des Synchronisationspakets

Ein wichtiger Aspekt beim Empfang der Pakete ist das rechtzeitige Einschalten des Empfängers (im BLE-Jargon Scanner genannt). Zwar ist es denkbar und möglich, ein Scanning durchgehend durchzuführen. Davon würde ich persönlich jedoch abraten, da dies den Stromverbrauch unnötigerweise in die Höhe treibt. Beim Timing sind übergeordnet zwei Komponenten zu beachten:

  • Zykluszeit der Synchronisationspakete (TSync)
  • Vom BLE-Substandard vorgeschriebene Zufallszeit zur Zykluszeit der BLE-Pakete

Auch muss man beachten, dass die Scanning-Intervalle durch die BLE-Spezifikation genau geregelt sind: der Empfänger kann für eine Dauer Tdur (Scan-Duration) innerhalb eines Zeitfensters Twdw (Scan-Window) eingeschaltet werden. Dabei muss Tdur ein ganzzahliges Vielfaches von 2,5 ms sein. Die Länge von Twdw muss ein ganzzahliges Vielfaches von 10ms betragen. Bei Benutzung eines guten BLE-Stacks sollte die Einstellung komfortabel über ein SDK möglich sein.
Soweit, so gut! Aber wie muss jetzt das Timing gestaltet werden? In der Praxis hat sich ergeben, dass der Scanner für geringe Verlust für mindestens Tdur=100ms um den geschätzten Empfangszeitpunkt eingeschaltet werden sollte. Das heißt: möchte man Pakete im Abstand von TSync=10s zueinander übermitteln, so sollte der Empfänger im Zeitraum zwischen im Intervall zwischen i * (10s +- 0,05s) eingeschaltet sein. i beschreibt hierbei die Paketnummer seit Synchronisationsstart. Das folgende Oszillogramm kann dies ein bisschen besser darstellen:

Zwei Advertising Pakete (orangene Flanken) fallen in Scanning Intervalle (blaue Pegel)

Zu beachten ist an dieser Stelle außerdem, dass die Zufallszeit über die Dauer der Synchronisation kompensiert werden muss: das Scan-Window, in dem die Scans zyklisch durchgeführt werden, muss mit dem Mittelwert der Zufallsverteilung (5ms) beaufschlagt werden. Die Dauer des Scan-Windows beträgt also: Twdw = TSync + 5ms.

Verarbeitung der Synchronisationspakete

Identifikation

Der erste Schritt der Verarbeitung im Slave ist die Identifizierung der Synchronisationspakete. Dies muss in der Anwendungsschicht geschehen, da der BLE-Stack die Scanning-Resultate typischerweise im Rahmen eines Callbacks in die Applikationsschicht reicht. Für die Identifizierung kann ein Siebsystem mit nach hinten feiner werdenden Sieben benutzt werden:
Sieb 1: Filter nach Pakettyp (ADV_IND_NONCONN)
Sieb 2: Filter nach AD-Modus (Manufacturer Specific Data)
Sieb 3: Filter nach Identifikator (eigens gewählt)
Die Menge an in Frage kommenden Paketen wird also von oben nach unten geschmälert. Das ist vor Allem wichtig, um den Callback schnell wieder verlassen zu können (gerade im urbanen Umfeld mit vielen BLE-Nodes können dann mehr Pakete empfangen werden).

Latenzkompensation

Prinzipiell lassen sich Latenzen in deterministische („synchronisationsfreundliche“) und nicht-deterministische („weniger synchronisationsfreundliche“) Latenzen einteilen. In unserem Fall, bei der Übertragung von Paketen mit BLE, treten leider auch gehäuft nicht-deterministische Latenzen auf. Diese entstehen z.B. durch die Belegung der CPU mit anderen Prozessen, was ein Abholen von empfangenen Paketen aus dem MAC-Layer verzögern kann. Mit dem Wissen aus [2] lassen sich die nicht-deterministischen Verzögerungen auf Masterseite jedoch nahezu komplett beseitigen: mithilfe eines Interrupts, der z.B. mithilfe eines latenzarmen Off-load Peripheral Interconnect Signals nach dem Versenden eines Pakets ausgelöst wird, wird der Zeitstempel für das nächste Paket gebildet. Die versendeten Zeitstempel weisen dann immer ein Paket Versatz zueinander auf!

Aus Sicht des Slaves verkompliziert sich die Interpretation des Zeitstempels dann allerdings ein wenig: wenn der Zeitstempel immer ein Paket Versatz aufweist, wie kommen wir dann auf den aktuellen Zeitstempel zum Empfang eines Pakets? Die Antwort liefert uns nicht die Glaskugel, sondern, wie so oft im Leben des Ingenieurs, die Mathematik. Mithilfe von ein wenig RAM sowie einfacher Algebra können wir uns nämlich den Offset der Master und Slave Uhren zum Synchronisationszeitpunkt ausrechnen. Es muss sich nur der Slave-Zeitstempel bei Empfang des letzten Pakets (i-1) gemerkt werden. Beim Empfang des Folgepakets (i) kann dann der empfangene Master-Zeitstempel dem mit dem Paket (i-1) gebildeten Slave-Zeitstempel zugeordnet werden. Addiert der Slave nun noch die Zähler-Ticks auf, die zwischen dem Empfang von Paket i-1 und Paket i gezählt wurden, so erhält man eine sehr genaue Schätzung für den Master-Zeitstempel.

Ein Nachteil des Verfahrens liegt jedoch beim großen Einfluss von Paketverlust: geht einmal ein Paket verloren, so wirkt sich das im Slave auch auf das vorhergehende und auf das nachfolgende Paket aus. Zur genauen Schätzung des Master-Zeitstempels sind nämlich immer zwei aufeinanderfolgende Pakete notwendig.

Plausibilitätscheck zur Ausreißererkennung

Diese Komponente hatte ich am Anfang meiner Arbeit an diesem Thema sehr unterschätzt. Ich war es aus der Uni nämlich gewohnt, mit deterministischen Systemen zu arbeiten. Ein Synchronisationssystem ist in der Regel aber kein solches System. Es können an vielen Stellen in dem Prozess unerwartet Verzögerungen (nicht-deterministische Latenzen) auftreten, welche die Übermittlung der Synchronisationspakete stören und dadurch die Zeitstempel verfälschen. Um solche Verfälschungen zu „entlarven“, haben wir uns im Zuge dieser Aufgabe mit einem Median-Filter beholfen. Dieser überprüft die Plausibilität der Zeitstempel durch den Vergleich mit dem zuvor aus N Korrekturwerten gebildeten Median, wobei N die Länge des Filters ist. Bei der Implementierung ist dabei vor Allem darauf zu achten, dass der Grenzwert, der die zulässige Toleranz zum Median bestimmt, weder zu groß noch zu klein ist sowie Korrekturen in beide Richtungen zulässt. Die Funktionsweise des Filters kann man am Besten anhand einer Grafik beschreiben.

Wichtig bei der Implementierung ist, dass der Korrekturwert immer relativ zur Dauer seit der letzten Synchronisation bestimmt und in das Filter abgelegt wird. Das ist wichtig, weil die Re-Synchronisation innerhalb eines Intervalles unter Umständen auch einmal übersprungen werden kann, z.B. bei Paketverlust. Auch wird von einer Kompensation mit dem aktuellen Filterwert abgeraten, da dies die Stabilität des Systems beeinflussen kann. Als fehlerhafte erkannte Zeitstempel sollten verworfen werden. Weiterhin sollte die Länge des Filters nicht zu klein sein. Ein Filter der Länge 7 liefert bei unserer Anwendung gute Ergebnisse.

RTC-Synchronisation

An dieser Stelle möchte ich noch anreißen, wie der letzte Schritt des Kernvorgangs aussieht. Bis jetzt haben wir:

  • Auf dem Master Zeitstempel gebildet und versendet
  • Diese mit dem Slave empfangen
  • Korrekturwerte aus Zeitstempelpaaren gebildet
  • Die Korrekturwerte auf Plausibilität überprüft

Nun soll also das „Nachstellen“ der Uhr stattfinden. Je nach verwendetem Zählwerk sowie Anwendungsfall kann dies verschiedene Formen annehmen. Ich möchte mich hier jedoch auf einen Fall beschränken, in dem eine direkte Manipulation des RTC-Registerwerks möglich ist (was aber nicht selbstverständlich ist): nehmen wir an, dass dies mit einer Funktion void setTicks(uint32_t tickVal) möglich ist! Eine Funktion uint32_t getTicks(void) soll uns den aktuellen Zählwert der RTC als Rückgabewert liefern. Erklärtes Ziel ist an dieser Stelle also, das RTC-Zählwerk des Slaves auf den Wert der Master-RTC zu setzen. Ein Code-Beispiel soll uns den Lösungsweg aufzeigen.

/*
 * from previous processing steps we got the correction value
 * saved in variable correctionValueTicks
 */

// get slave rtc time in ticks
uint32_t slaveTicks = getTicks();

// correct slave rtc time to master's rtc time
uint32_t masterTicks = slaveTicks + correctionValueTicks;

// set new rtc time
setTicks(masterTicks);

Re-Synchronisationsintervalle

Bevor ich einige Ergebnisse unserer Arbeit zeige, möchte ich noch kurz auf das letzte, wichtige Thema eingehen. Das Thema Re-Synchronisationsintervall beeinflusst die Genauigkeit sowie den Stromverbrauch der Synchronisation nämlich stark. Wählt man die Re-Synchronisationsintervalle nämlich zu groß, so leidet die Genauigkeit durch den Clock-Drift. Der Stromverbrauch des Slaves wird jedoch durch die wenigen Empfangsintervalle sinken. Hier handelt es sich also um einen Trade-Off zwischen Genauigkeit und Stromverbrauch. Wie komme ich also auf den „richtigen“ Wert für die Re-Synchronisation? Die Antwort auf diese Frage hängt im Grunde von der Größe des RTC-Drifts ab. Bei einem guten Leiterplattendesign und der Verwendung von hochwertigen Oszillatoren kann die Dauer der Re-Synchronisationsintervalle problemlos groß gewählt werden, ohne mit großen Unannehmlichkeiten rechnen zu müssen. Hier spart man natürlich auch sehr viel Strom! Im Umkehrschluss gilt: je ungenauer meine Uhren „von Natur aus“ sind, desto öfter müssen sie natürlich nachgestellt werden. In unserer Anwendung benutzen wir sehr ungenaue Uhren (keine Quarz-, sondern RC-Oszillatoren). Wir müssen hier alle 10s (siehe Grafik) neue Pakete verschicken, um die gewünschte Genauigkeit beizubehalten.

Und warum verschicken wir 3 Sync-Pakete? Die Antwort ist einfach: sollte durch Störung im Kanal einmal ein Paket verloren gehen, existiert noch ein Paket „Reserve“. Der Algorithmus braucht über das Intervall von 10s jedoch nur ein Paketpaar zur Synchronisation. Das überflüssige Paket kann dann verworfen werden.

Ergebnisse

Zuletzt wollen wir uns den Ergebnissen dieser Arbeit widmen: im Praxisversuch konnte über die Versuchsdauer von 8h eine Genauigkeit von im Mittel ca. 25,2 µs erzielt werden, wobei die Spannweite der resultierenden Verteilung ca. 460 µs beträgt. Die Samples werden durch jeweils einen RTC-Interrupt auf Master bzw. Slave gebildet, deren RTCs mithilfe des hier beschriebenen Algorithmus synchronisiert wurden. In der Interrupt Routine wird auf beiden Controllern ein Pin getoggled. Mithilfe eines dritten Controllers kann der Abstand der Flanken zueinander bestimmt werden.

Und was ist noch wichtig?

Man sollte sich bewusst sein, dass die Genauigkeit der Synchronisation nie genauer als der Wert der RTC-Auflösung sein kann. Liegt die Auflösung der RTC bei 30,5 µs (RTC-Frequenz 32,768 kHz), so wird die Genauigkeit der Synchronisation mit dieser Methode stets schlechter als dieser Wert sein.

Noch Fragen?

Designen Sie ein Produkt, das Synchronisation benötigt oder brauchen Sie Hilfe bei der Umsetzung ihrer BLE-Features? Durch viele erfolgreiche Projekte in diesem Bereich verfügen wir bei MEDtech über geballtes BLE Know-How. Kommen Sie also gerne auf uns zu!

Kontaktieren Sie uns!

Autor

  • Luca ist studierter Elektrotechnik-Ingenieur und sammelte während seines Studiums sowie in der Zeit danach wertvolle berufliche Erfahrungen bei MEDtech. Obwohl er inzwischen für ein anderes Unternehmen tätig ist, bleibt er bei MEDtech als Autor erhalten und verfasst gelegentlich Beiträge, um seine Expertise und Leidenschaft für seinen Beruf zu teilen. Darüber hinaus zählt er weiterhin zu den engagierten Lesern des Blogs.

    Alle Beiträge ansehen
Auch interessant:

LTspice – was tun, wenn die Simulation nicht konvergiert

LTspice als Software zur Schaltungssimulation ist wahrscheinlich jedem Hardware-Entwickler bekannt. Der Kopf hinter LTspice war Mike Engelhardt, was wahrscheinlich auch viele wissen, da er viele Jahre die LTspice World Tour gemacht hat und Seminare zu LTspice gehalten hat. Bei "The Amp Hour Electronics Podcast" gibt es eine Episode aus dem…
Getagged mit: , , , , , ,