Software - Interessante sofTware und harTware.

RC Summensignal per Microcontroller einlesen

Einige Modellbauempfänger bieten die Möglichkeit, auf einer Signalleitung alle Servosignale als Summensignal auszugeben. Dieses Summensignal soll per Microcontroller eingelesen werden, um selbst Servos, Motoren, Lichter und Spezialfunktionen zu steuern.

Die Übertragung des Lehrer-Schüler-Signals zwischen 2 Sendern findet oft ebenfalls in Form eines Summensignals statt.

Analyse des Summensignals per Oszi

[1] Signalverhalten
[2] Periodendauer ca. 22.5ms

[3A] CH1=Min
[3B] CH1=Min

[4A] CH1=Max
[4B] CH1=Max

[5] Pause: ca. 9ms
[6] Einstellung am Sender

Im Video [1] sieht man, dass bei der Bewegung eines Knüppels/Schalters jeweils ein "Balken" im Signal seine Pulsdauer ändert.
Ein kompletter Durchgang hat eine Periodendauer von ca. 22.5ms - das ist an diesem Sender einstellbar [6] und passt mit der Messung überein.

Bilder [3A] und [4A]: Wenn Kanal1 auf Minimum steht, ist die Dauer des HIGH-Pulses bei 0.68ms. Bei Knüppelstellung "Maximum" sind es 1.7msec.
Die Messung inklusive nachfolgendem LOW-Pegel in [3B], [4B] stimmt mit 1ms bzw. 2ms dann wiederum exakt mit den PPM Minimal- bzw. Maximal-Pulsdauern der Modellbauservos überein.
Die Low-Pegel sind somit 300µs lang, ebenfalls in Übereinstimmung mit der Einstellung am Sender [6].

Die Messung der Pause [5] muss mit Vorsicht betrachtet werden: da die Periodendauer des Gesamtsignals konstant ist, schwankt die Pause mit den Werten der einzelnen Kanäle.
Wären alle 8 Kanäle auf Maximum, sind 8*2ms = 16ms belegt. Bei einer Periodendauer von 22.5ms sind von der Pause dann nur noch 6.5ms übrig.

Die Amplitude beträgt 3.3V. Das sollte ein 5V Atmega/Arduino-Microcontroller noch als HIGH-Pegel interpretieren können.

Umsetzung in Software

Softwaredesign

Um das Singal mit möglichst wenig Rechenpower verarbeiten zu können, soll der Input Capture Mode des Atmega verwendet werden.
Damit kann die Dauer einzelner HIGH/LOW-Pegel gemessen werden, es wird jeweils ein Interrupt getriggert. Diese gemessene Signaldauer (des HIGH-Pegels) kann dann mittels State Machine ausgewertet und den einzelnen Kanälen zugeordnet werden.

State Diagram Version 1

Zuerst wird im Zusand "wait for initial pulse" auf einen langen Impuls ("Pause") gewartet. Danach folgen die einzelnen Kanäle: falls die Dauer gültig ist (1..2ms) wird zum nächsten Kanal gesprungen. Andernfalls geht es zurück zum Anfang, da das Signal ungültig war.

Dieser Ansatz kann noch vereinfacht werden: da alle States zum Einlesen eines Kanals bis auf die Kanalsnummer identisch sind, kann auch eine Zählervariable für die Kanalnummer verwendet werden. Dann lassen sich die Zustände zu einem zusammenfassen:

State Diagram Version 2

Das liest sich einfacher, und lässt sich auch deutlich eleganter ohne copy-paste-code programmieren.

Atmega: Input Capture

Beim Interrupt Capture Mode schreibt die Hardware beim Ändern des Pin-Levels den aktuellen Timerwert in ein Register, und löst einen Interrupt aus.
Dadurch kann mit wenig Rechenleistung eine sehr präzise Zeitmessung vorgenommen werden.
Theoretisch ist mit einem Atmega bei 16MHz und einem Timer-Vorteiler von 1 eine Auflösung von t = 1/f = 1/16MHz = 62.5nsec möglich.
Für den ersten Test mit Vorteiler 256 ergibt sich ein Timer-Takt von 62.5kHz und somit 16µs pro Tick.

Beim Atmega328 kann nicht jeder Pin für Input Capture verwendet werden. Es gibt sogar nur genau einen Pin: PB0.
Im Register TIMSK muss das Bit ICIE1 gesetzt werden, und beim Timer1 über TCCR1B der Vorteiler gewählt werden. Damit ist Input Capture bereits aktiv und der Interrupt wird getriggert.
Besonderheit am Arduino: Der Arduino-eigene Init-Code setzt TCCR1A (wird für Analogmessung benötigt). Damit der Timer1 volle 16bit zählt, muss TCCR1A zurück auf 0 geschrieben werden.

Die Testsoftware für Input Capture per Interrupt:

Die Intervalle zwischen steigenden Flanken an PB0 wird eingelesen, und die letzten 20 Messwerte als Tabelle über die Serielle Konsole (Arduino USB) ausgegeben.
Download Sourcecode: Input Capture Test

Speicherbedarf laut Compiler:

FLASH: 3528 Byte von 30720 Byte 11%
RAM: 332 Byte von 2048 Byte 16%

Screenshot Konsolenausgabe. 10 Ticks -> 160µs
Funktionsgenerator: 160µs Signal

Summensignal Testprogramm:

Auf einem 16bit-Microcontroller ist das längste ohne Überlauf messbare Intervall (2^16 - 1) = 65535 Ticks.
Die Taktfrequenz des Counters muss so gewählt werden, dass die lange 9ms-Pause sicher ohne Überlauf messbar bleibt.
Je höher die Taktfrequenz des Counters, desto höher die Auflösung des erfassten Signals. Das bedeutet, mit höherem Takt wird die Auflösung der Servos "feiner".

Nehmen wir die Pause mit 10ms an. Das sind 10.000µs.
Mit einem Clock-Vorteiler von 256 ergeben sich wie oben erwähnt 16µs pro Tick. Um 10ms zu messen verstreichen 625 Ticks -> die Frequenz kann noch erhöht werden.

Vorteiler Zeit pro Tick Ticks für 10ms Maximaler Messbereich mit 65535 Ticks
1024 64µs 156,25 4,2 Sekunden
256 16µs 625 1 Sekunde
64 4µs 2500 262 msec
8 0,5µs 20000 32.7 msec
1 0,0625µs 160000 4.1msec

Mit einem Clock-Vorteiler von 8 wird der Messbereich somit optimal genutzt.
Das Servosignal von 1..2ms hat eine Breite von 1ms. Diese kann mit den 0.5µs-Schritten in Form von 2000 unterscheidbaren Werten gemessen werden. Das wären beinahe 11 Bit Messauflösung, absolut akzeptabel.

Das Summensignal wird am Pin PB0 des Arduino Micro eingelesen, und auf der Konsole (USB-Terminal) ausgegeben.
Download Sourcecode: Summensignal

Speicherbedarf laut Compiler:
FLASH: 3772 Byte von 30720 Byte 12%
RAM: 279 Byte von 2048 Byte 13%

Screenshot Konsolenausgabe: Einlesen des Summensignals
Konsolenausgabe in Aktion

Die Konfiguration erfolgt über die Konstanten in SumSig_Cfg.h. Es können die Timings des Signals sowie die Anzahl Kanäle angepasst werden.
Wenn der Empfänger es unterstützt, können also auch mehr als 8 Kanäle per Summensignal übertragen werden!

Da das Einlesen der Werte mittels Interrupt stattfindet, muss berücksichtigt werden, dass auch während der Verarbeitung der Werte ein Interrupt stattfinden kann.
Parallele Zugriffe können inkonsistente Daten erzeugen - in unserem Fall würde sich das durch springende Servopositionen bemerkbar machen.
Abhilfe schafft das "Double Buffer"-Konzept: im RAM werden 2 Datensätze abgelegt. Einer wird beim Lesen verwendet, der andere mit frisch eingelesenen Daten befüllt.
Sobald der Datensatz komplett ist, werden die Zeiger auf die Datensätze vertauscht: Beim nächten Durchgang wird dann der jeweils andere gelesen/beschrieben.