Samstag, 17. Januar 2015

DHT22 an langem Anschlusskabel

Lieber Entwickler,

im unserem zukünftigen EFH sollen Temperaturen und Luftfeuchtigkeit mit preiswerten DHT22-Sensoren gemessen werden. Ich möchte dabei auf dezentrale Intelligenz verzichten und die Sensoren möglichst direkt an die zentrale Steuerung anschließen. Fraglich war für mich, ob die Datenübertragung über ein langes Kabel möglich ist. Dazu habe ich heute einen Test mit 50m CAT5-Kabel gemacht.

Bereits vor einiger Zeit habe die die Sensoren in meinem Blog vorgestellt. Das Bild unten zeigt meinen Testaufbau.


Die "Zentrale" wird vom STM32F4-Discovery gemimt. Zwischen den PullDown-Widerständen rechts (2x10k parallel, um die überall empfohlenen 4,7k näherungsweise zu erreichen) und de DHT22 links hängen ca. 50m CAT5-Kabel. Mein Testcode prüft nach jedem Auslesen, ob der CRC passt. Selbst nach 30 Minuten "Dauerbetrieb" incl. Störung durch einen direkt daneben laufenden ollen Heizlüfter und Handy-Downloads waren keine Probleme zu beobachten.

Happy Coding!


Montag, 5. Januar 2015

CAN-Bus mit dem STM32

Lieber Entwickler,

zunächst wünsche ich Direin gutes und gesundes neues Jahr 2015!

Bereits seit geraumer Zeit muss man beim Lesen dieses Blogs den Eindruck gewinnen, ich sei untätig um Kontext vom STM32. Doch das Gegenteil ist der Fall. Nachdem meine Frau und ich mittlerweile einem Architekten den Auftrag zur Planung unseres EFH gegeben haben, entwickele ich häufiger als zuvor. Der Grund: Unser EFH soll "automatisiert" werden und der STM32 wird eine zentrale Rolle spielen...
 Ich habe mich für eine semi-zentrale Architektur des Automationssystems entschieden. Das bedeutet, es gibt einen singulären Master, über den (fast) alle Informationen fließen. Bestimmte Funktionen sind jedoch primär aus Verdrahtungsgründen ausgelagert. Diese Funktionsbausteine müssen über eine geeignete Kommunikationsschnittstelle mit dem Master verfügen. Ich habe mich hier für CAN (http://de.wikipedia.org/wiki/Controller_Area_Network) entschieden aus den folgenden Gründen:

  • Kann Daten über ausreichend große Distanzen transportieren.
  • Multimaster-fähig
  • Nachrichtenorientiert
  • Adressierung im Protokoll integriert
  • Wählbare Bitrate (je niedriger die Bitrate, desto länger darf das Kabel werden)
  • Verfügbar sogar auf dem billigen STM32F103
  • Günstiger PHY (Sample-Bestellung den SN65HVD230 bei TI :-))
  • Sehr schönes Programmiermodell auf dem STM32
  • Viele andere Hausbus-Ansätze nutzen auch den CAN-Bus als Basis

Als einziger Nachteil könnte angesehen werden, dass maximal 64bit Daten pro Nachricht übertragen werden können. Nun ja, 8 Byte sind tatsächlich nicht viel, aber für den hier besprochenen Anwendungsfall genügt das vollauf. Im Bedarfsfalle müssen höhere Protokollschichten eben eine Aufteilung von Daten vornehmen.

Zum Testen habe ich mir gleich einen einfachen Anwendungsfall ausgedacht. Die Hausautomations-Zentrale sendet an einen dezentralen Aktor Befehle zum Ein- oder Ausschalten von Relais. Die Funktion derZentrale wird vom STM32F4-discovery-Board übernommen; der dezentrale Aktor ist ein STM32F103-Board. In einem ersten Versuch - vor der Ankunft der SN65HVD230  - habe ich die hier (http://www.keil.com/download/files/canprimer_v2.pdf) auf Seite 4 veröffentlichte Schaltung verwendet, um die beiden Boards zu koppeln. Funktionierte!

Nun kamen heute die SN65HVD230 an. Ich lötete diese zusammen mit einem Slope-Control-Widerstand (10k) und dem Busterminator (120R) auf ein DIL-Adapter und verkabelte die Boards mit Hilfe von Steckbrettern Ein Foto meines Aufbaus siehst Du unten.

Auf dem Steckbrett ich von anderen Experimenten noch etwas "Hühnerfutter" zu sehen, das uns jetzt aber nicht interessieren sollte. Spannender ist, womit ich die beiden SN65HVD230 miteinander verbunden habe: Tatsächlich sind das 305m Kabel (CAT5), die zwischen den beiden Transceivern liegen. Bei 125kBit/s funktioniert die Übertragung problemlos! Wahnsinn!

Den Code für den 103canrelayswitch und den 407homeautomation findest Du im SVN unterhalb von https://code.google.com/p/stm32tutor/ Insbesondere im 407homeautomation ist schon einiges anderes implementiert, das aber durch die Endlosschleife im CAN-Experiment erstmal ausgeblendet ist. Die entscheidende Codestelle im 407homeautomation ist in der main.c-Datei die Funktion CAN_Polling(void). Diese bereitet im 500ms-Rhythmus eine Nachricht an den 103canrelayswitch vor und versendet diese. Im 103canrelayswitch spielt die Musik in der Datei stm32f10x_it.c und zwar in der Funktion USB_LP_CAN1_RX0_IRQHandler(void). Es handelt sich hierbei um den Interrupts-Handler des CAN-Busses. Beim 103 greifen CAN und USB chipintern auf die gleichen Ressourcen zu. Am Namen des Interrupt-Handlers ist erkennbar, dass dies auch für Interrupts gilt.  Insbesondere können USB und CAN nicht gleichzeitig benutzt werden. Die seltsamen Operationen vor dem Setzen der GPIOx->ODR-Register dienen dazu, dass die Anschlüsse des Boards in der physischen Reihenfolge angesprochen werden können.



Happy coding!

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



Mittwoch, 21. Mai 2014

printf(...) ganz einfach per Semihosting auf dem STM32F4

Lieber Entwickler,

bei einer Konsolenanwendung auf dem PC ist es (schlechte) gängige Praxis, permanent Debug-Ausgaben per System.out.println("") oder Console.Writeln("") oder printf("") auszugeben. Bei einfachen oder experimentellen Anwendungen entwickelt man insbesondere auch in Verbindung mit einem Debugger schnell ein Gefühl dafür, wo im Falle eines Fehlers nochmals Hand anzulegen ist.

Bei Entwicklungen mit dem Microcontroller ist man meist auf LEDs oder einfache LCD-Displays angewiesen, um Text auszugeben. Profis schreiben über über die serielle Schnittstelle raus und hängen sich mit einem Terminal-Programm an den Prozessor. Alle Lösungen haben jedoch Nachteile:

  • LEDs können nur seeehr einfache Informationen ausgeben. Einen komplexen Morsecode will wohl keiner entwickeln...
  • Ein LCD verlangt nach einigem Code, verbraucht rare IOs und kostet Geld
  • Die serielle Schnittstelle verbraucht einen UART und macht eine zusätzliche Kabelverbindung
Mit der Technik "Semihosting" bietet die STM32-Familie glücklicherweise eine sehr einfache Möglichkeit, Informationen vom Microcontroller über die Debug-Verbindung an den Host zu senden. Meine Toolchain ist bereits für Semihosting vorbereitet. Ein Beispielprojekt habe ich in hier abgelegt

Zur Aktivierung sind die folgenden Schritte notwendig.
  • Hole Dir mein Semihosting-Testprojekt
  • Du kannst dieses Projekt 1:1 verwenden. Es misst einfach nur die Spannung am PA1-Eingang und gibt diese jeder Sekunde aus. Bei mir ist dort ein Sharp-Infrarot-Entfernungsmesser angeschlossen. Wenn bei Dir nichts dort angschlossen ist, funktioniert die Anwendung trotzdem. Wesentlich sind die Dateien:
    • Inc/printf.h
    • Inc/semihosting.h
    • Src/printf.c
    • Src/semihosting.c
    • Src/sh_cmd.asm
  • Füge die asm- und die c-Dateien Deinem Src-Verzeichnis hinzu
  • Füge die h- Datei Deinem Inc-Verzeichnis hinzu
  • Aktiviere Semihosting in den Debugger-Settings (siehe Screenshot)
  • Füge #include "printf.h" in den User-Code-Bereich 0 der main.c hinzu
  • Füge in der while-Schleife jetzt einen printf-Befehl und eine Verzögerung hinzu, beispielsweise: "printf("Der Prozessor ist jetzt %u msec gelaufen, HAL_GetTick()); HAL_Delay(1000);"

Ein wenig seltsam mag erscheinen, dass ich hier eine eigene printf-Funktion verwende und nicht auf die integrierte Funktionalität der STM-Firmware-Library ("newlib") zurück greife. Grund hierfür ist, dass die newlib wirklich jedes Feature von printf unterstützt und sowohl viel Speicher im Flash als auch auf dem Heap verbraucht. Meine Implementierung (die ich natürlich 1:1 abkekupfert habe) realisiert eine mehr als ausreichende Teilmenge der Gesamtfunktionalität und bleibt herrlich kompakt.

Happy Coding