Monatsarchiv für März 2012

Erster Kontakt mit dem GPS Receiver => NMEA Daten mit dem µC einlesen (NMEADll)

19. März 2012

GPS Receiver stellen ihre aktuellen Daten normalerweise mit Hilfe des NMEA-0183 Protokolls über eine einfache UART Schnittstelle der Außenwelt zur Verfügung. Manche Receiver bieten oft auch noch proprietäre Protokolle mit erweiterten Funktionen zur Verfügung. Auf diese möchte ich aber hier nicht eingehen. Für mich reicht der Standard völlig aus. In diesem Artikel beschreibe ich also jetzt wie man auf unterster Ebene NMEA Datensätze mit einem Mikrocontroller, in meinem Fall ein STM32, einlesen und auf Gültigkeit prüfen kann. In meinem Projekt, dass es auch hier zum Download gibt, heißt das Software Modul „nmeadll.c„.

Struktursicht

Um erst mal einen groben Überblick über die Software zu bekommen ist eine Struktursicht immer ein guter Startpunkt, da man hier sieht wie sich ein Modul in den Rest der Software einfügt. Die Struktursicht zum nmeadll-Modul sieht wie folgt aus:

Wie zu erkennen ist, wird der GPS-Receiver direkt an einen der USARTs des Controllers angeschlossen. Darüber liegt dann noch eine Softwareschicht namens „huart.c“ die eine Abstraktion der Hardware (HAL = hardware abstraction layer), bzw. des USARTs, darstellt. Damit wird die Software leichter portierbar, d.h. man kann einfach auf einen anderen Controller wechseln und muss nur das Modul huart austauschen. Danach sollte alles wieder funktionieren (soweit die Theorie ;-)). Über dem HAL liegt dann auch schon das nmeadll-Modul welches die herinkommenden NMEA Datensätze einliest und auf Gültigkeit prüft. An der Stelle ist wichtig, dass hier nur der Datensatz an sich geprüft wird und nicht dessen Inhalt. Die Überprüfung des Dateninhalts liegt in der Verantwortung des übergeordneten SW-Layers der hier mit „gps“ gekennzeichnet ist und in der Software als Task implementiert wurde. Zu guterletzt ist noch erkennbar, dass der NMEA Data Link Layer noch Services des RTOS und verschiedene Utilities verwendet. Dazu aber später mehr.

Das NMEA-0183 Protokoll

Empfangene NMEA Daten haben immer das folgende Format:

D.h. ein Frame beginnt immer mit einem $-Zeichen gefolgt von den beiden Buchstaben „GP“ was für GPS-Receiver steht. Grundsätzlich können hier auch andere zwei Buchstaben stehen aber bei GPS-Receivern sind es immer die Buchstaben GP. Im Anschluss an diese beiden Buchstaben folgen dann drei weitere Buchstaben die den Datensatz kennzeichnen. Im Beispiel oben wäre das z.B. „GSV“. Hierüber lässt sich der Aufbau des empfangenen Datensatzes ermitteln. Daran anschließend folgen dann jew. durch ein Komma getrennt die einzelnen Daten bis zum nächsten ‚*‘-Zeichen was das Ende des Datenfeldes markiert und das Checksummenfeld abgrenzt. Die Checksumme befindet sich in den folgenden beiden Zeichen. Sie wird durch eine einfache XOR-Verknüpfung über alle Datenbytes inkl. des Identifiers gebildet (s.o.). Das Ergebnis wird abschließend noch in einen String konvertiert, d.h. jedes Nibble des Bytes wird in einen Character umgewandelt sodass das Ergebnis wieder direkt lesbar ist. Abgeschlossen wird jeder NMEA Frame mit den beiden nicht lesbaren Zeichen 0x0D und 0x0A, d.h. <CR>+<LF>. Ein NMEA Datensatz kann so inkl. aller Zeichen maximal 82 Zeichen lang sein.

Wie am obigen Beispiel zu sehen ist, lässt sich ein Datensatz auch sehr gut mit Hilfe eines Terminal-Programms auswerten. Das hat mir oft auch sehr bei der Implementierung geholfen.

Gundfunktionen

Der NMEA Data Link Layer stell die folgenden Funktionen zur Verfügung:

  • Einlesen der NMEA Daten in den internen Speicher
  • Prüfen der NMEA Daten auf Gültigkeit
    • Synchronisation auf einen laufenden Datenstrom
    • Formatprüfung
    • Checksumme
  • Verwerfen von ungültigen Datensätzen
  • Speichern von mehreren gültigen Datensätzen bis zur Verarbeitung
  • Bereitstellen der Datensätze für die höher liegende Softwareschicht

Interface

Das Interface des Data Link Layers ist in der folgenden Abbildung dargestellt:

Wie zu erkennen ist, besteht die Schnittstelle zur höheren SW-Schicht aus nur vier Funktionen. Zunächst muss der Data Link Layer mit der Funktion NMEAD_vInitNMEADll() intialisiert werden. Hier werden dann alle internen Datenstruktureninitialisiert, der USART konfiguriert und ein Interrupt Handler installiert (USART Receive Interrupt) in dem das Protokoll verarbeitet wird. Diese Funktion heißt NMEAD_vNMEAProtcolRxEvent(). Auf Applkationsebene spielt sich dann alles mit Hilfe der Funktionen NMEAD_pucGetNMEAFrame() und NMEAD_vReleaseFrame() ab. Über GetFrame kann ein neuer NMEA Datensatz angefordert werden. Hier ist besonders, dass diese Funktion die aufrufende Funktion so lange blockiert bis ein neuer Datensatz empfangen wurde (siehe auch hier). Mit ReleaseFrame wird der Speicher eines Datensatzes wieder freigegeben und kann somit wieder mit neuen Daten überschrieben werden.

Einbindung in das RTOS

Die Kommunikation mit der höheren Softwareschicht ist in der folgenden Abbildung dargestellt. Hier ist auch erkennbar, wie das Betriebssystem in die Kommunikation eingebunden ist.

In obiger Abbildung ist erkennbar, dass jedes empfangene Byte vom GPS-Receiver einen Receive-Interrupt des USARTs auslöst. In diesem Interrupt (Funktion NMEAD_vNMEAProtcolRxEvent()) wird das NMEA Protokoll verarbeitet und die Daten gespeichert. Sollte ein kompletter Frame empfangen worden sein, so posted der Interrupt Handler eine Semaphore die also mit jedem vollständig empfangenen Frame um eins inkrementiert wird. Der GPS Task kann nun auf Task-Ebene die Funktion NMEAD_pucGetNMEAFrame() aufrufen. Diese Funktion fragt wiederum die Semaphore ab und blockiert so lange bis der Interrupt Handler einen neuen Datensatz eingelesen hat (Semaphore Post). Wurde ein Datensatz empfangen, so wird der Task fortgesetzt. Mit diesem Konzept ist somit sichergestellt, dass nur dann Rechenleistung verwendet wird, wenn auch Daten zu verarbeiten sind.

Pufferkonzept

Die folgende Abbildung zeigt das Pufferkonzept des Data Link Layers:

Die NMEA Daten werden im internen Speicher in Form eines Ringpuffers gespeichert. Der Ringpuffer ist dabei Datensatzweise organisiert, d.h. jedes Ringelement kann einen kompletten, bis zu 82 Zeichen langen, NMEA Datensatz speichern. Dieses Konzept ist zwar nicht ganz speicherschonend aber es ermöglicht eine einfache Verarbeitung auf der Protokollebene als auch auf der höher liegenden Verarbeitungsebene, da hier die Daten einfach in einem linearen Array liegen. Es muss also nicht speziell auf Speicherumbrüche geachtet werden.

Protokoll Statemachine

Die Statemachine des Protokollhandlers ist wie folgt organisiert:

Die Statemachine besitzt als Entry-State den Zustand Synch. Dieser Zustand synchronisiert den Protokoll-Handler also zunächst auf einen laufenden Datenstrom indem er auf das Ende eines NMEA Datensatzes wartet und bis dahin alle empfangenen Zeichen verwirft. Nach der Synchronisation wird in den Idle-State gewechselt wo der Automat auf einen neuen Datensatz wartet der immer mit dem ‚$‘-Zeichen beginnen muss. Wurde ein Datensatz erkannt, so wird dieser im State ReadFrame eingelesen und gespeichert. Das Datenfeld endet mit dem ‚*‘-Zeichen. Wird dieses Zeichen empfangen, so wechselt der Automat in den State WaitEOF wo die Checksumme ausgewertet wird und noch die letzten Zeichen (<CR> + <LF>) eingelesen werden. Weiterhin gibt es immer noch den direkten Wechsel zum Synch-Zustand, der immer dann durchgeführt wird wenn die maximale Frame-Länge überschritten wird.

Fehlerbehandlung

Grundsätzlich muss mit den folgenden Fehlern gerechnet werden:

  • Falsches Frame-Format, d.h.
    • Ein Frame beginnt nicht mit dem ‚$‘-Zeichen
    • Das Datenfeld eines Frames endet nicht mit dem ‚*‘-Zeichen
    • Die Checksumme stimmt nicht
    • Der Frame endet nicht mit <CR> + <LF>
  • Es wird auf einen laufenden Datenstrom geschaltet
  • Ein laufender Datenstrom wird unterbrochen

Das Frameformat wird von Data Link Layer komplett auf Interrupt-Ebene geprüft. Sollte das Format eines Frames nicht passen, so synchronisiert sich der Protokoll-Handler wieder neu auf den Datenstrom indem er auf das <LF>-Zeichen wartet. Auch das Aufschalten auf einen laufenden Datenstrom wird über die Synchronisation erschlagen. Ein abreißender Datenstrom wird von Protokoll Handler nicht direkt abgefangen. Sollte dieser Fall jedoch eintreten, so sollte generell die Formatprüfung, speziell die Checksummenprüfung, fehlschlagen und somit eine Neusynchronisation erzwungen werden. Alle Fehler die bei der Formatprüfung erkannt werden führen dazu, dass ein eingelesener Datensatz verworfen wird.

Erstes Release meines SportTrackers

14. März 2012

Heute habe ich mich entschieden schon mal ein wenig von meinem Source Code für den SportTracker zu veröffentlichen. Das ganze ist zwar derzeit noch nicht wirklich fertig, d.h. es läuft alles nur im Debugger ohne eine richtige Benutzerschnittstelle, aber es sind schon einige Funktionen realisiert. Das sind z.B.:

  • Parsen der NMEA Frames eines GPS Receivers
    • Extrahieren der aktuellen Position (Lat/Lon)
    • Extrahieren der aktuellen Zeit sowie des Datums
    • Extrahieren der Höhe
  • Ansteuerung einer SD Card über das SDIO Interface
  • Integration eines FAT File Systems (ElmChans FatFS)
  • Einfache Tracker Funktion mit Speicherung eines Tracks auf einer SD Karte im GPX-Format
  • Tastenentprellung

Wie gesagt läuft das Ganze noch nicht zusammen und ist derzeit auch noch sehr schlecht dokumentiert. Das Konzept des NMEA Parsers habe ich dabei an meinem alten Code für den AVR angelehnt.

Zu finden ist der Code hier:  SportTracker Code

Viel Spaß damit…

RCC Modul: Ganz schön taktlos…

11. März 2012

Ich weiß nicht wie oft ich schon in die Falle getappt bin. Es ist eigentlich immer das Gleiche. Ich versuche gerade mal wieder eine Peripherie in Betrieb zu nehmen und konfiguriere alle Register so wie ich es denke, starte den Debugger und nichts geht! Dann geht wie immer die Sucherei nach dem Fehler los. Mmhh… ist doch eigentlich alles richtig. In der Std Peripheral Library ist eigentlich auch alles so gemacht. Eigentlich sollte das blöde Ding also tun, tut es aber nicht :-(. Aber halt, da war ja noch das sog. RCC Modul des Controllers. Ja klar! Ohne Takt geht natürlich nichts! Also, schnell mal den Takt der besagten Peripherie eingeschaltet und siehe da, es geht also doch :-).  Man muss sich wirklich erst mal an dieses neue Ding gewöhnen. Besonders wenn man wie ich von einer anderen Controllerfamilie auf den STM32 wechselt…

So, hier nun das „kurze“

RCC Peripherie Tutorial

Beim STM32 ist es so, dass zunächst mal die meisten Peripheriemodule nach einem Reset nicht mit einem Takt versorgt werden. D.h. diese Module verbrauchen damit auch nur sehr wenig Strom. Was aus Standby Sicht eigentlich eine schöne Sache ist führt aber auf der anderen Seite immer wieder zu oben geschilderten Suchorgien am Debugger. Damit eine Peripherie wie z.B. ein GPIO verwendet werden kann muss dessen Takt also erst mal im RCC Modul eingeschaltet werden. Hierfür gibt es im wesentlichen drei Register:

  1. RCC_AHBENR
  2. RCC_APB2ENR
  3. RCC_APB1ENR

Wie die Namen schon andeuten kann man über das RCC_AHBENR Register Peripherieeinheiten einschalten die sich am AHB Bus des Controllers befinden. Die beiden anderen Register sind dementsprechend für Peripherieeinheiten am APB2 bzw. APB1 Bus zuständig. Jede Peripherieeinheit hat in einem dieser Register ein Bit mit dem man den Takt der jew. Einheit ein- bzw. ausschalten kann. Ist das Bit gesetzt, ist der Takt eingeschaltet, ist es gelöscht, so ist der Takt ausgeschaltet. Peripherieeinheiten können grundsätzlich nur dann konfiguriert werden wenn der Takt der Einheit eingeschaltet ist. Ist eigentlich ganz einfach, oder?

Damit ich mich immer an diese Register erinnere habe ich immer zwei Seiten aus den Datenblättern der Controller ausgedruckt auf meinem Schreibtisch liegen. Das sind:

  1. Den Clock Tree des Controllers den man im RCC Kapitel des Reference Manuals findet und
  2. Das Blockdiagramm des Controllers welches man im Datasheet des eingesetzten Controllers findet.

Im Clock Tree sieht man sehr gut die ganzen Zusammenhänge der verschiedenen Controllertakte, und es gibt einige davon, und im Blockdiagramm findet man schnell an welchem Bus eine Peripherie angebunden ist. Damit findet man also auch schnell in welchem RCC Register eine Peripherie aktiviert werden muss.

Neben den ganzen Clock Enable Registern findet man im RCC Modul auch noch weitere Register welche z.B. auch das Resetten einer Peripherieeingeit ermöglichen. Das Modul ist also generell mal einen Blick Wert da hier alle Fäden bzgl. der Taktkonfiguration zusammenlaufen.

Was ich noch verschwiegen habe ist das RTC Modul des Controllers. Da dieses Modul in der Backup Domain des Controllers angesiedelt ist verhält es sich ein wenig anders (auch in Bezug auf die Taktfreischaltung). Hierauf möchte ich aber hier nicht näher eingehen.

Hier noch abschließend ein kurzes Beispiel:

Wenn man z.B. den GPIO Port B verwenden möchte schaut man zunächst im Blockdiagramm nach auf welchem Bus dieser Port liegt. Hier findet man, dass alle GPIOs auf dem APB2 Bus liegen. Damit kann der Port B über das Register RCC_APB2ENR aktiviert werden und das geht ganz einfach so:

RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;