Dienstag, 16. September 2014

Debounce / Entprellen für STM32

Hallo Entwickler,

gestern und heute habe ich mir überlegt, wie man mit dem STM32F10x elegant Tasten entprellen kann. Eine gute Einführung zu diesem Thema liefert http://www.emcu.it/STM32/STM32Discovery-Debounce/STM32Discovery-InputWithDebounce_Output_UART_SPI_SysTick.html . Von dort habe ich auch die grundsätzlich Idee zu meiner Lösung. An drei Stellen habe ich jedoch Verbesserungen vorgenommen:

  • Meine Lösung arbeitet mit bis zu 16 Tastern (also einem gesamten Port)
  • Meine Lösung hat nur halb so viel Speicherplatzverbrauch pro Taster
  • Meine Lösung gibt einen Tastimpuls verzögerungsfrei weiter.
Den letzten Punkt muss ich wohl noch ein wenig detaillierter ausführen. Der emcu-Code stellt einen Pegelwechsel an einem Pin fest und beginnt dann, die entsprechende Pegelvariable hochzuzählen. Erst wenn diese Pegelvariable nach mehreren Durchläufen einen bestimmten Wert erreicht hat, wird der Tastendruck bzw. das Loslassen detektiert. Ich gehe da einen anderen Weg: Sobald ein Pegelwechsel erkannt wird, wird dieser sofort weitergegeben. Danach schließt sich eine "Totzeit" an, in der kein weiterer Pegelwechsel erkannt wird. Jegliches Prellen in dieser Zeit wird also sicher unterdrückt.

So langsam werde ich auch vertraut mit bestimmten Möglichkeiten, dem Compiler und dem Linker in meiner Toolchain Anweisungen im Code zu geben. Sehr interessant für den vorliegenden Anwendungsfall ist beispielsweise das "__attribute__((weak))".

Dieses kommt (per define abgekürzt mit "AW") in dem Modul debounce.c zum Einsatz, um überschreibbare Funktionsdefinitionen für die Ereignishandler meiner Debounce-Routine zu realisieren. Das gleiche Prinzip kommt auch bei den Interrupt-Handlern der Standard Peripherial Libary zum Einsatz. Die Idee ist einfach: Die mit weak attributierte Funktion kann durch eine gleichnamige Funktion in einer beliebigen c-Datei überschrieben werden. Ist keine derartige Überschreibung definiert, wird eben die Weak-Standardimplementierung verwendet. Üblicherweise macht die Standardimplementierung einfach garnichts... Schaut Euch einfach die Funktionen DEBOUNCE_click() und DEBOUNCE_up() in debounce.c und main.c an.


Das Beispiel im SVN ist auf das von mir empfohlene 4€-STM32F103-Board zugeschnitten: Eine LED ist an PA1 angeschlossen; der Taster (in meinem Fall einfach eine Steckbrücke) befindet sich an PB0.

Samstag, 13. September 2014

Microseconds Delay for STM32

Lieber Entwickler,

alle STM32-Derivate bieten für Performance-Messungen einen Cycle-Counter. Nach Aktivierung zählt dieser mit jedem Taktzyklus einfach um Eins nach oben. Damit eignet er sich hervorragend für blockierende Delays im Mikrosekundenbereich. Vorteilhaft an dessen Verwendung ist:

  • Delays mit Timern verbrauchen eine wertvolle und mächtige Timer-Ressource für profanes Zählen
  • Delays mit dem Systick-Timer im Mikrosekundenbereich sorgn dafür, dass der Systick-Interrupt jede Mikrosekunde aufgerufen wird. So kommt der STM32 kaum noch zu seinen eigentlichen Aufgaben ;-)
  • Delays mit __NOP() und Schleifen sind ungenau, weil sie durch Interrupts unterbrochen werden können.
So ist die Verwendung des Cycle-Counters m.E. der absolut zu bevorzugende Weg.


Den Code sowie ein schönes Anwendungsbeispiel findet ihr in meinem SVN-Repository: https://stm32tutor.googlecode.com/svn/trunk/103dht22. Wesentlich dort sind die Dateien timing.c und timing.h. Beachtet, dass das Modul vor seiner erstmaligen Verwendung mit TIMING_Init(); initialisiert werden muss. Diese Routine schaltet intern lediglich den Performance-Couter frei und setzt ihn auf einen Startwert.

Happy Coding

Dienstag, 9. September 2014

DHT22 AM2302 Sensor am STM32F103

Liebe Entwickler,

mein erstes Projekt mit dem STM32F103-Board ist die Ansteuerung eines DHT22-Sensors zur Temperatur- und Luftfeuchtigkeitsmessung. Mein Testaufbau sieht dann so aus:


Die schwarze Box rechts ist ein Logic-Analyzer von Saleae (möglicherweise ein chinesischer Nachbau davon ;-)), der mir enorm beim Debuggen geholfen hat.

Ein halb-englisch-halb-chinesisches Datenblatt vom DHT22 stellt beispielsweise der Händler Adafruit auf der Produktseite zur Verfügung. Ich selbst habe den Sensor nicht dort, sondern wie üblich bei Aliexpress bestellt. Mit Hilfe der ebenfalls auf der Produkseite angebotenen Arduino-Bibliothek ließ sich das Protokoll des DHT22 dann auch entschlüsseln. Insbesondere die Umwandlung der gelesenen Bits in eine Fließkommazahl habe ich von Adafruit geguttenbergt.

Zum Protokoll selbst:
Generell handelt es sich um ein serielles, unidirektionales Verfahren mit LSB-first. Es wird lediglich eine einzige Datenleitung zwischen Host und DHT22 verwendet. Sowohl Host als auch DHT22 können die Datenleitung auf Masse "ziehen" und sie dann wieder "loslassen". Ein PullUp-Widerstand stellt sicher, dass im Ruhezustand die Datenleitung auf dem Niveau der Versorgungsspannung liegt. Empfohlen wird ein 4k7..10k-Widerstand. Der im SMT32 integrierte PullUp hat etwa 40k, weshalb ich mich für einen externen PullUp mit 10k entschieden habe.

Eine Transaktion läuft ausgehend vom Ruhezustand wie folgt ab

  1. Der Host zieht die Datenleitung >500us auf Masse. Der DHT22 interpretiert dies als Startbefehl. Der Host zieht nun bis zum Ende der Übertragung nicht mehr auf Masse, sondern "lauscht" passiv.
  2. Etwa 20-40us nach dem "Loslassen" des Hosts zieht der DHT22 die Datenleitung für 80us auf Masse und zeigt damit seine Präsenz.
  3. Etwa 80us nach dem "Loslassen" beginnt die eigentliche Datenübertragung
  4. Für jedes Bit zieht der DHT22 die Leitung für 50us auf Masse
  5. Die Dauer des darauf folgenden Loslassens bestimmt die Polarität des Bit.
    • Wenn das Loslassen 26-28us dauert, ist dies als 0 zu interpretieren
    • Wenn das Loslassen etwa 70us dauert, ist dies als 1 zu interpretieren
  6. Insgesamt werden so 40bits übertragen

Eine komplette Transaktion sieht im Logic-Analyzer dann so aus:


Mein Ziel in diesem Projekt ist es, bis zu 16 DHT-Module parallel an einen Port des STM32 anzuschließen und diese in einer Interrupt-gesteuerten Schleife immer wieder abzufragen. Ich war der festen überzeugung, dass die Performance eines 72MHz-Prozessors probleeeeeemlos ausreicht, um das notwendige "Bitbanging" und das bisschen Aktualisieren von Datenarrays en passant zu erledigen.
Tja, wie man sich doch täuschen kann!
Tatsächlich wird das Timing in diesem Projekt zur sehr kritischen Komponente. Die Zykluszeiten des DHT22 Signals machen es erforderlich, dass zumindest im 20us-Zyklus abgetastet wird (Shannon-Theorem +  Sicherheitszuschlag ;-) ). In dieser Zeit schafft es der STM32 nicht, über alle 16 Bits des Ports zu iterieren und bei Bedarf ein Datenarray zu aktualisieren. Das Diassembly meines ersten Codeentwurfes zeigt im Abgleich mit der Taktzyklustabelle von ARM, weshalb das so ist (Man beachte den Performance-Counter oben rechts):


So einfache Schleifen oder if-Abfragen verbrauchen letztlich jeweils über 10 Takte. Alleine die 16malige Abfrage, ob für das betreffende Bit etwas zu tun ist, verschlingt über 300 Takte, also grob 5us. Das ist zwar jetzt ein Klagen auf hohem Niveau, aber alleine diese Prüfung macht 25% der mir zur Verfügung stehenden Zeit aus . Im Übrigen half da auch eine testweise aktivierte Compileroptimierung nicht viel.

Ich habe meinen Code weiter optimiert und die Anforderungen reduziert. Wesentliche Punkte waren

  • Der Port-Input-Befehl der SPL ("GPIO_ReadInputDataBits") dauert wegen der im Debug-Mode durchgeführten Assertions schon alleine etwa 3us. Natürlich geht das im Release schneller, aber zur Geschwindigkeitssteigerung dieser spezeillen Funktion beim Debuggen habe ich die SPL-Aufrufe durch direkte Registerzugriffe ersetzt.
  • Reduktion der unterstützten Pins (im Code nur noch 8, kann aber erhöht werden)
  • In einer ersten Version suchte ich sowohl nach der steigenden als auch nach der fallenden Flanke des DHT-Signals. Anhand der Zeitdauer des positiven Signals konnte der Code bestimmen, ob eine 0 oder eine 1 übertragen wurde. Die neue Codeversion wird ausschließlich bei fallenden Flanken aktiv. Die Polarität des Bits wird durch das Intervall zur vorherigen fallenden Flanke bestimmt. Insgesamt wird also weniger Code pro Sensor ausgeführt.
  • Einige Umstellungen im Code brachten einige wenige Taktzyklen.
Der Kerncode "doProtocol" ist so konzipiert, dass er aus Interrupthandlern von Timern und EXTIs aufgerufen werden kann, aber auch für den Aufruf aus einer synchronen pollenden Methode geeignet ist. Im Codebeispiel ist die letzte Möglichkeit umgesetzt.
Die Funktionsweise des Codes ist an sich sehr einfach:
  • Eine Zustandsverwaltung kümmert sich zunächst um das Reset-Signal und das Abwarten der Präsenzmeldungen. Sollte die Präsenzmeldung nicht wie im Datenblatt beschrieben erfolgen, werden Fehler zurückgegeben
  • Sobald die Zustandsverwaltung erkennt, dass die eigentliche Datenübertragung erfolgt, übergibt sie die Kontrolle immer an doBitbang2. Deren Funktionalität ist im Code anhand der Kommentare nachvollziehbar. Besonders hinweisen möchte ich auf zwei Sachverhalte
    • Es findet eine Timeout-Erkennung statt. Wenn der Sensor keine /zu wenige Signale liefert, kommt es nicht zu einem Fehler. Auch zu viele Signale (wie auch immer das passieren kann) werden effizient abgefangen und führen nicht zu einem Überlauf.
    • Die allererste fallende Flanke muss übersprungen werden, weil diese ja noch kein Bit repräsentiert, sondern lediglich den Start des Payloads repräsentiert.
Den kompletten Code findet ihr in Form eines Beispielprojektes unter https://stm32tutor.googlecode.com/svn/trunk/103dht22

Happy Coding - ich freue mich auf Verbesserungsvorschläge




Ein STM32F103 Board für 4€

Liebe Entwickler,

neben meinem STM32F4 Discovery habe ich mir bei Aliexpress.com einige STM32F103C8T6-Boards bestellt und in Betrieb genommen. 5 davon bleiben beim günstigsten Händlerincl. Versandkosten unter der magischen 22€-Zollgrenze. Eine Suche mit "STM32F103C8T6 board" + "Show Price per Piece" + Sortieren nach Preis sollte Euch schnell zum Ziel führen. Ich habe hier bestellt.

Es ist schon genial, was sich mit diesen Boards zum Stückpreis von etwa 4€ anstellen lässt. 32bit, 72MHz, 64kb Flash, 20kB Ram, viele IOs, ADC, RTC, USB und diverse Timer lassen jeden Arduino echt alt aussehen:-) Leider ist der Community-Support noch ungleich schlechter - doch daran möchte ich zusammen mit vielen anderen ja arbeiten.

Traditionell schwierig ist es natürlich, von den Chinesen vernünftige Datenblätter zu bekommen. Einer der unzähligen Händler für dieses Board bietet glücklicherweise einen Link zu einem Datenblatt an. Zur Sicherheit habe ich das Datenblatt hier auf meinen SVN hochgeladen.

Für die STM32F1-Reihe bietet ST noch keine CubeMX-Firmware an. Zu kritisch ist das allerdings nicht - auch die als "Standard Peripherial Library" (SPL) hat genug zu bieten. Die von mir beschriebene Toolchain nutzt das Gnuarmeclipse-Plugin und dieses hat die SPL für den STM32F1 direkt integriert. Die Erstellung eines neuen Projektes ist also komplett menügeführt und ohne händisches Einbindung von Header-Dateien oder das Setzen von DEFINES möglich.

Zum Programmieren und Debuggen dieser Boards bieten die Chinesen Nachbauten des STLinkV2 an. Ich habe diese nicht ausprobiert. Statt dessen verwende ich mein STM32F4-Discovery hierfür. Das klappt in der Regel sehr gut. Manchmal jedoch scheint sich das Konstrukt irgendwie aufzuhängen. In diesen Fällen nutze ich das STM32 ST-Link Utility (siehe meine Toolchain), verbinde mich mit dem Target und führe ein "Erase All" durch. Danach konnte ich immer wieder weiter arbeiten. Die Verkabelung gestaltet sich nicht weiter schwer.

  1. Die beiden Jumper CN3 entfernen ("ST-LINK DISCOVERY")
  2. 4 Leitungen verbinden, nämlich:

STM32F4-Discovery Signal STM32F103-Board
P2.5 oder 6 3V 3V3
CN2.2 SWCLK CLK
CN2.3 GND GND
CN2.4 SWDIO DIO

In Eclipse ist für OpenOCD die folgende Einstellung bei "Config Options" erforderlich:
-f interface/stlink-v2.cfg -f target/stm32f1x_stlink.cfg