Projektarbeit ASURO

Der ASURO war zentraler Bestandteil der letzten Projektarbeit. Das Ergebnis ist eine Betrachtung der Hardware und eine Umsetzung in Software.

Zusammenfassung
In dieser Studienarbeit wurde die Firmware des ASURO Roboters erweitert. Ziel war es, die Sensorwerte der sechs Sensoren zyklisch abzufragen, um die Werte für den Programmierer direkt zur Verfügung zu stellen. Der Controller des ASUROs besitzt einen AD-Wandler mit sechs Messkanälen, deren Aktivierung in der Software koordiniert und deren Messwerte in die realen Werte umgerechnet werden.

Bei den Sensoren handelt es sich um Reflexionslichtschranken, optoelektronische Sensoren, Taster und Batteriespannung. Von den Reflexionslichtschranken kann auf die Umdrehungen der Räder geschlossen werden; von den optoelektronischen Sensoren kann auf die Helligkeit des Untergrunds geschlossen werden. Die Taster dienen zur Kollisionsabfrage an der Roboterstirnseite und die Batteriespannung dient zur Überwachung der Stromversorgung.

Die Abfrage der Sensoren wurde durch zyklische Auswahl der Messkanäle innerhalb der AD-Wandler Interrupt Service Routine realisiert. Die Werte werden in einem öffentlichen struct dem Rest der Roboter Software zur Verfügung gestellt.

Zusätzlich beschreibt diese Arbeit noch die Verwendung der Softwaresimulation in Atmel Studio und eine Methode für Unittests mit AVR Mikrocontrollern.

Unit Tests für Mikrocontroller [Installation und Konfiguration für AVR]

Unit Tests sind im PC Bereich mittlerweile Standard. Allerdings ist es schwierig sie auch für Software im Bereich der Mikrocontroller zu verwenden. Die wenigen Ressourcen, die der Mikrocontroller zur Verfügung stellt sollten idealerweise für die Software verwendet werden, die später dann auf dem System laufen soll, denn alles andere wäre Geldverschwendung. Es gibt dennoch die Bemühungen mit so wenig Wasserkopf wie möglich Unit Tests auch auf Mikrocontrollern zu implementieren.
Ich habe das in meinen Projekten bisher so gelöst, dass ich zusätzlich zum normalen Programmcode Testfunktionen implementiert habe. Alles was nur zu Testzwecken im Code eingefügt wurde habe ich mit Präprozessor-Anweisungen eingekapselt, sodass sie bei einem Release Built nicht beachtet wurden. Dadurch habe ich während der Entwicklungszeit die Testfunktionen zur Verfügung und später im ‚fertigen‘ Projekt wurden die Testfunktionen nicht mehr übersetzt. Das führte neben kleinerem Programmcode auch zu schnellerer Abarbeitung von z.B. Interrupt Service Funktionen im fertigen Projekt. In dieser Artikelserie werde ich die Verwendung von µCUnit anhand des ASURO Projekts erklären.
Das µCUnit Framework liegt als Open Source Projekt auf GitHub und kann von dort als zip-Archiv heruntergeladen werden. Die verschiedenen Beispielprojekte für i386, avr und arm zeigen, wie das Framework verwendet werden kann.
Da jeder Mikrocontroller und jedes Projekt unterschiedlich sein kann, muss das Framework mit Macros angepasst werden. So muss zum Beispiel die Text Ausgabe so implementiert werden, dass Textzeichen über die Serielle Schnittstelle, Netzwerk, oder Dateien ausgegeben werden können. Sollte auf der Projektplattform die printf() Funktion zur Verfügung stehen, kann sie verwendet werden, aber gerade bei kleinen Controllern ist selten genügend Platz um diese Funktionen mit in dem Programmcode aufzunehmen. Es wird zusätzlich noch eine Funktion benötigt, um das System in einen sicheren Zustand zu setzten und es komplett herunter zu fahren. Jeder Ausgangspin der Hardware sollte in einen sicheren Zustand gesetzt werden, um zum Beispiel hohe Ströme durch den Controller zu verhindern. Die Beispieldateien System.c und System.h enthalten Code, der diese Funktionen beschreibt.

Die System.c für den AVR zeigt welche Funktionen vom Framework erwartet werden. Vor allem die Abwesenheit der printf() Funktion muss behoben werden. Da der ASURO schon eine schlanke Funktion zum Ausgeben von Zeichen besitzt, muss hier nur die Funktion zum Schreiben von Strings implementiert werden:

void System_WriteString(char * s) {
while(*s)
{
UARTbyte(*s);
s++;
}
}

Alle weitern Funktionen habe ich direkt aus der Beispieldatei übernommen.

Nachdem die Funktion implementiert ist, könne wir mit dem Aufbau eines Testcases beginnen. Dazu können wir uns an der mitgelieferten Testsuit.c orientieren. Eine Test Suite ist aufgebaut aus dem Haupt Test und den einzelnen Testcases. Der Haupttest initialisiert den Prozessor und führt die einzelnen Tests durch.
Hier wird auch die int main(void) implementiert; die Funktion, die als Hauptfunktion aufgerufen wird.
Das Beispiel zeigt und diese Funktion

int main(void)
{
UCUNIT_Init();
// [...]
Testsuite_RunTests();
UCUNIT_WriteSummary();
UCUNIT_Shutdown();

return 0;
}

Die Funktionen UCUINT_Init() und UCUNIT_Shutdown() sind in der System.c schon implementiert und tun zur Zeit nichts. Die Funktion Testsuite_RunTests() ruft die einzelnen Tests auf:

void Testsuite_RunTests(void)
{
Test_BasicChecksDemo();
Test_PointersDemo();
Test_ChecklistDemo();
Test_BitChecksDemo();
Test_CheckTracepointsDemo();
}

Es werden alle Tests in der Testsuite aufgerufen. Diese Herangehensweise erlaubt es die Tests einzelner Funktionesteile (Testcases) in verschiedene Hauptkategorien (Testsuits) zu unterteilen. Somit erhält man leichter einen Überblick über die einzelnen Funktionstests.
Schauen wir uns zunächst die einzelnen Testcases an. Der BasicCheckDemo() Test führt ein paar grundlegende arithmetische Operationen aus. Alle diese Tests sollten bestanden werden. Zu Beginn eines jeden Testcases werde die darin verwendeten Variablen deklaiert und eventuell initaialisiert. Der nächste Schritt ist den Testcases zu beginnen, mit Hilfe der Funktion

UCUNIT_TestcaseBegin("Name des Testcases");

Danach kommt dann eine eventuelle Generierung von Testdaten oder sonstige Funktionalität. Danach werden die Ergebnisse der Funktionen evaluiert. Hier sieht man die Verwendung der CheckIsEqual Funktion, die wie ihr Name schon sagt, zwei Werte miteinander vergleicht. Am Ende jedes Testcases wird die Funktion

UCUNIT_TestcaseEnd();

aufgerufen um den Testcase zu beenden und das Ergebnis zu speichern.
Weiter Testfunktionen werden in den anderen Testcases vorgestellt. Ich möchte an dieser Stelle noch einmal auf die Evaluierungsfunktion CheckIsBitSet eingehen, da hier direkt die Ausgangspins des Controllers getestet werden können.

PORTB = (PB1 << 1); 
UCUNIT_CheckIsBitSet(PORTB, 1); /* Pass */

Im der Nächstem Artikel betrachten wir dann die einzelnen Funktionen des ASURO und wie wir eine Testsuite für die einzelnen Hauptgruppen und Testcases für alle Unterfunktionen entwickeln.

[WIP] ASURO mit korruptem Speicher?

Seit letztem mal schlägt das Programmieren des Mikrocontrollers auf dem ASURO bei Page 39 fehl. Die Page liefert beim Auslesen eines zuvor hineingeschriebenen Wertes einen anderen Wert zurück. Die Folge davon ist, dass ein Programm, welches mit dem Bootloader auf den Chip kopiert wird nicht korrekt übertragen wird. Nach einigen Versuchen und etwas googeln habe ich versucht eine andere Programmdatei zu übertragene und das hat ohne Probleme funktioniert. Der ASURO hat also Probleme beim Übertragen von Programmdaten, wenn diese eine Bestimmte Zeichenkette beinhalten. genauer gesagt, viele 0x00 Bytes hintereinander. Zum Glück funktioniert der Chip mit kleinen Änderungen im Quelltext wieder problemlos. Als nächstes werde ich mir die Eingangssignale der IR-Transistoren anschauen und die Signale des ADC auswerten um die Raddrehungen zählen zu können.

Nachtrag: Der Fehler mit den korrupten Pages ist wieder aufgetreten und sogar unabhängig vom verwendeten Chip und der Page. Ein Programm mit 40 Pages bricht bei Page 39 ab, eins mit 12 bei Page 11. Es ist zum Haare raufen.

[WIP] Ein neuer Versuch mit ASURO

Ich bearbeite für das Studium eine Projektarbeit für die Roboterplattform ASURO. Den Verlauf der Entwicklung werde ich hier dokumentieren. Ziel ist es ein Betriebssystem zu erstellen, dass die Werte der sechs Sensoren kontinuierlich ermittelt und sie für den Programmierer aufbereitet. Dazu gehört neben der reinen Speicherung der Messwerte eine, abhängig vom Sensor, kontinuierliche Verarbeitung. Deshalb habe ich mich in letzter Zeit intensiv mit der Wandlung von analogen Spannungen zu digitalen Werten beschäftigt; konkret mit der Wandlung von Spannungen eines IR-Transistors an einem ATmega8.
Der ADC des ATmega8 hat 6 Eingänge in der PDIP Konfiguration und zwei Ausleseverfahren. Einmal die einmalige Wandlung des aktuellen Wertes, und dann die kontinuierliche Wandlung. Dabei wird nach jeder vollständigen Konvertierung direkt eine neue angestoßen. Wenn ich kontinuierlich alle verfügbaren ADC Eingänge auslesen möchte, muss dazu eine interne Logik entwickelt werden, die die Messwerte und die Sensorauswahl steuert. Der normale Verlauf ist, dass der ADC Multiplexer (MUX) eingestellt wird, die Spannung gemessen und das Ergebnis als 10 bit Wert in einem 16 bit Register abgelegt wird. Wenn man aber so schnell wie möglich den Wert eines beliebigen Eingangs verwenden will, muss die Wandlung schon im Voraus durchgeführt und die Ergebnisse vorgehalten werden. Für diesen Fall sind 6 Eingangsgrößen zu bestimmen. Jede der Größen wird nacheinander gewandelt und soll in einem Array zur Verfügung stehen. Nach jeder Konvertierung des ADC wird eine Interrupt Service Routine (ISR) aufgerufen. Diese sorgt für die Verarbeitung des aktuell ermittelten Messwertes. Dabei ist darauf zu achten, dass der Wert, der im Register zur Verfügung steht, der ist, der vor zwei ISR Aufrufen beantragt wurde. Das kommt daher, dass Der MUX beim Aufruf der ISR umgeschaltet wird. Dann wird die noch laufende Konvertierung beendet bevor der neu eingestellte Eingang des MUX an den ADC weitergegeben wird. Dann wandelt der ADC den Wert und ruft die ISR nach Beendigung dessen auf. Jetzt liegt als Ergebnis im Register der Wert für den vor zwei Aufrufen geschalteten MUX Eingang. Dies muss bei der Verwendung der Daten unbedingt beachtet werden, denn sonst kann nicht garantiert werden, dass die Werte an der richtigen Stelle im Array landen. Dazu habe ich eine einfache Funktion geschrieben, die als inline direkt in der ISR eingefügt werden kann. V wird immer um eins erhöht und bei erreichen von max wieder auf 0 gesetzt.

static inline uint8_t cycleValue(uint8_t v, uint8_t max) {
if(v == max) v = 0;
else v++;
return v;
}

Im ASURO sind zwei Refelktionslichtschranken verbaut. Diese zeigen auf die Schwarz-Weißen Encoderscheiben am Getriebe der Räder. Für die Ermittlung der Drehtakte für die Räder wird so von den IR-Transistoren ein sinusförmiges Signal erzeugt. Dieses Signal ist einerseits einfach zu analysieren, andererseits stören Faktoren wie Streulicht, oder axiale Bewegungen der Zahnräder im Getriebe. Dieser Umstand wird mit einem Median-Filter umgangen. Dieser filtert alle Ausreißer in den Messwerten heraus und erzeugt dadurch eine ganz anschauliche Sinusform. Abbildung 1 zeigt den Verlauf von je 300 Messwerten der beiden Radsensoren vor und nach der Filterung.

Zwei sinusförmige Schwingungen, die mit einem Medianfilter geglättet wurden
Abb. 1 Sensorwerte Kanal 0 und 1 ohne und mit Medianfilter

Die Taktung der Radgeschwindigkeit geschieht einmal beim Übergang von hohem Messwert auf niedrigen und andersherum. Mit einem unteren und oberen Schwellwert, wie in Abbildung 2, rote Linie erhält man vier Zustände, die das Auswertesystem annehmen kann: Wert kleiner als die Maximumschwelle steigende Flanke, Wert kleiner als die Maximumsschwelle flanke fallend und des gleiche für die Minimalschwelle. Dabei bietet es sich an die Übergänge  in einem Zustandsautomaten zu berechnen.

Abb. 2 Grenzwert (rote Linie) und Markierungen für Ticks

Der Automat benötigt lediglich die Werte des ADC und kann dann dementsprechend zwischen den Zuständen durchschalten. Das Zählen der Schritte wird ebenfalls in dem Automaten erledigt. Es bietet sich an die Schritte in den unten markierten Zuständen zu zählen, aber es können natürlich auch die beiden anderen Zustände zum Zählen der Schritte verwendet werden.

extern inline void detectEdgeFlip() {
switch (edge) {
case RISING:
if(adc_median_value < threshold[MIN]){
edge = RISING_WAIT;
}
break;
case RISING_WAIT: // Hier kann ein Schritt gezählt werden
if(adc_median_value > threshold[MIN]){
edge = FALLING;
}
break;
case FALLING:
if(adc_median_value > threshold[MAX]){
edge = FALLING_WAIT;
}
break;
case FALLING_WAIT: // Hier kann ein schritt gezählt werden
if(adc_median_value < threshold[MAX]){
edge = RISING;
}
break;
}

Da die Sensoren auf dem Board des ASURO dem Umgebungslicht ausgesetzt sind, ist es wichtg, dass der Minimal- und Maximalwert ständig betrachtet wird und die Schaltgrenzen gegebenenfalls angepasst werden. Dazu werden die letzten 64 Messwerte für jeden Kanal abgespeichert und bei vollem Puffer der Minimal und Maximalwert gesucht.

Sehr wichtig ist es, dass die ISR nicht länger dauern darf als die Wandlung des ADC, denn sonst überholt der ADC die CPU und es kommt zur Blockade der Software, da nur noch die ISR verarbeitet werden kann. Die aktuelle ISR des ADC sieht sehr voll aus und ich werde demnächst die Laufzeitdauer messen. Eventuell müssen einige Funktionen ausgelagert und so sporadisch wie möglich aufgerufen werden.