PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : interruptgesteuertes Auslesen aller AD-Wandlerkanäle



_HP_
22.04.2007, 22:44
Hi all,

der in der Zip-Datei vorliegende Code gestattet es, alle 6 AD-Wandler-Kanäle des ASURO interruptgesteuert auszulesen. Bisher (in Lib2.7) war das nur für die Odometriesensoren möglich. Die einzelnen Kanäle (besser, die einzelnen Funktionen) des ASURO lassen sich dabei getrennt ein- und ausschalten. Zugriff auf die Daten hat man über globale Variablen. Mein Wunsch wäre es, wenn diese Funktionalität Einzug in die nächste Version der ASURO-Lib finden würde.

Bei der Entwicklung und den nötigen Tests sind mir darüber hinaus in einigen alten Funktionen ein paar Merkwürdigkeiten aufgefallen. Diese werde ich hier ebenfalls beschreiben.

Als Grundlage habe ich die Software genommen, wie sie sich im Auslieferungszustand auf der CD befindet. Funktionen aus der Lib2.7, die ich benötigt habe, habe ich per Copy&Paste hinzugefügt. Originalfunktionen aus der asuro.c, die ich verändert habe, habe ich kopiert und mit einem Unterstrich versehen.

Soviel zur Einleitung und nun zum Code:

Das Hauptprogramm liest zunächst die einzelnen ADC-Kanäle mit den bekannten Funktionen aus der asuro.c und gibt sie aus. Danach werden die Kanäle dann per ADC-Interrupt gelesen und ebenfalls ausgegeben. Schließlich werden die einzelnen Funktionen wieder ausgeschaltet.

Die Ausgabe habe ich mit den Funktionen SerPrint) und PrintInt() aus der Lib2.7 gemacht. Mit meinem Compiler „avr-gcc.exe (GCC) 4.1.1 (WinAVR 20070122)“, bekomme ich bei der SerPrint()-Funktion jedes Mal eine Vorzeichenwarnung. Darum habe ich den Parameter von „unsigned char“ auf „char“ geändert.

OdometrieData() schaltet die IR-Dioden ein. Wenn diese nicht schon eingeschaltet waren, sind – zumindest bei meinem ASURO – die danach gelesenen Werte (besonders für den linken Sensor) zu niedrig. Das merkt man aber nur, wenn man - bei stehenden Motoren - die Sensoren mehrfach ausliest. Ein Sleep(5) nach dem Einschalten der LEDs war nötig, um die richtigen Werte zu messen. Natürlich könnte man die LEDs auch einschalten, bevor man OdometrieData() aufruft – dann würde die Verzögerung nicht jedes Mal wirksam.

LineData() hat bei mir übrigens den gleichen Effekt hervorgerufen. Werden die Werte direkt nach dem Ein- oder Ausschalten der Front-LED gemessen, sind sie verschieden von den nachfolgenden Messungen. Offensichtlich haben LEDs also auch eine nicht zu vernachlässigenden Trägheit.

Besonders verblüfft hat mich die Batterie-Funktion aus der Lib2.7 (hier GetVoltage()). Der gemessene Wert hatte einfach keine Beziehung zur angelegten Spannung. Das Rätsel ließ sich auch hier über eine Verzögerung (Sleep(5)) lösen! Ursache ist das Umschalten der Referenzspannung von VCC auf die internen 2,56V, die ein wenig Zeit benötigt.

Es würde mich interessieren, ob bei Euren ASUROs die gleichen Effekte auftreten, oder ob es an meinem Exemplar liegt.

Jetzt aber zum interruptgesteuterten Auslesen der Daten:

Die erste Idee, dies im Freeflow-Modus des AD-Wandlers zu machen habe ich fallengelassen, nachdem ich die Dokumentation dazu gelesen hatte. Das Problem ist nämlich, dass man dann nicht mit 100%iger Sicherheit sagen kann, zu welchem Kanal die gerade anstehenden Daten gehören. Bei zwei Kanälen (wie bei dem schon realisierten Auslesen der Odometriesensoren) kann man das noch einigermaßen „erraten“ (obwohl ich erst jetzt verstanden habe, warum die Zuordnung der Daten in der ISR scheinbar „falschrum“ vorgenommen wurde). Bei mehreren Känälen, die auch noch wahlfrei ein- oder ausgeschaltet werden können, war es einfacher, die Messung jedes Mal gezielt zu starten, wenn der neue Kanal gesetzt ist.

Das Ein- bzw. Ausschalten von Funktionsgruppen bzw. Kanälen erfolgt über „Start-„ bzw. „Stop“-Routinen. Diese sind im Wesentlichen nach dem gleichen Prinzip aufgebaut, so dass es einfach ist, solche Routinen auch für Erweiterungsplatinen zu schreiben.

Der Zugriff auf die Daten erfolgt über globale Variablen.

Im Unterschied zur Lösung in der Lib2.7 werden alle Daten (also auch die der Odometer) als 10bit-Werte im Integerformat abgespeichert. Dies hat einmal den Vorteil, dass die Daten aller Kanäle im gleichen Format gespeichert werden und zum anderen kann man die gleichen Schwellwerte sowohl im Polling-Betrieb als auch im Interruptbetrieb nutzen. Man erhält Zugriff auf diese Daten über die globale Variable ADC_DATA[]. Hier werden allerdings tatsächlich nur die „Rohdaten“ des AD-Wandlers abgelegt – also NICHT die Schwarz/Weiß- bzw. Weiß/Schwarz-Übergänge (Ticks) der Odometerscheiben. Diese findet man in der globalen Variable ENCODER[].

Ob neue Daten für einen Kanal vorhanden sind, kann man in der globalen Variablen NEW_ADC_DATA testen. Jedes Mal, wenn neue Daten für einen Kanal in ADC_DATA[] abgelegt werden, wird das entsprechende Bit in NEW_ADC_DATA gesetzt.

Ob sich die Anzahl der Ticks in ENCODER[] geändert hat, kann man in NEW_ENCODER_DATA testen.

Eine Besonderheit Betrifft das Auslesen des ADC-Kanals für die Schalter. Mit ‚StartSwitches()’ kann man diesen Kanal analog zu den anderen einschalten. Wird viermal der gleiche Wert gelesen, kann man über SWITCHES_PRESSED direkt auf das Bitmuster der gedrückten Tasten zugreifen. Ob diese Variable neu geschrieben wurde kann man mit NEW_SWITCH_DATA testen.

Nun macht es natürlich Sinn, das Auslesen der Schalter erst dann zu starten, wenn überhaupt ein Schalter gedrückt wurde. Dies kann ebenfalls interruptgesteuert geschehen. Dazu gibt es in asuro.c die Funktion ‚StartSwitch()’. Erstaunlich fand ich jedoch, dass bei meinem ASURO der Interrupt immer ausgelöst wurde, wenn die Funktion aufgerufen wurde – also unabhängig davon, ob ein Schalter betätigt wurde oder nicht. Ich habe das – in der Funktion ‚WatchSwitches()’ jetzt so geändert, dass der Interrupt nicht mehr bei Low-Potential sonder bei der negativen Flanke ausgelöst wird. Das funktionierte bei meinen Tests zuverlässig. Die Information, dass eine Taste gedrückt wurde, findet man dann in SWITCHED. Um auch zu ermitteln, WELCHE Taste gedrückt wurde, wird in der ISR ‚StartSwitches()’ aufgerufen, so dass man die Information über die gedrückte(n) Taste(n) dann in SWITCHES_PRESSED finden kann, wie oben beschrieben.

Da es sein kann, dass man – egal welche Taste gedrückt wurde – auf jeden Fall erst mal die Motoren stoppen möchte, habe ich diese Möglichkeit mit in die ISR eingebaut. Um sie „scharf zu schalten“ muss die globale Variable ‚STOP_ON_SWITCH’ vorher auf TRUE gesetzt haben.

Bitte schaut Euch die Funktionen an und Testet Sie mit Euren ASUROs aus. Ich freue mich über jedes Feedback – auch über Bugs, die mir entgangen sind. Eine Diskussion darüber, ob und was wir davon in die offizielle Lib übernehmen wollen/sollen, wäre auch willkommen.

Gruß

_HP_

m.a.r.v.i.n
25.04.2007, 22:03
Hallo _HP_,

hab mir die SW heruntergeladen und auch ein wenig damit herumprobiert. Fehler konnte ich bisher noch keine entdecken. Werde am Wochende noch ein paar weitere Tests durchführen.

Gute Arbeit. Weiter so.

_HP_
26.04.2007, 11:32
Hallo m.a.r.v.i.n,

Danke für die netten Worte und vor allem für das Testen. In der Zwischenzeit habe ich ein paar kleinere Änderungen vorgenommen, die Du vielleicht auch gleich mittesten kannst:

1.) Eine kleine kosmetische Änderung, die eine Zeile code spart.



...
// --- find next active channel ---
if (ADC_CHANNELS) // at least one channel must be set
{
do
{
if (++ADC_CHANNEL > 5) ADC_CHANNEL = 0;
} while (!(ADC_CHANNELS & (1<<ADC_CHANNEL)));
...


2.) Ich glaube es ist eine bessere Idee, die ENCODER[] rückwärts zählen zu lassen. Dann kann man bei Erreichen der Null den zugehörigen Motor stoppen. Damit das nun wieder geht, muss man die Richtungen für die Motoren in globale Variablen schreiben.
Das Ganze sieht dann so aus:


...
volatile unsigned char STOP_ON_ENCODER = FALSE; //if this is TRUE and ENCODER[] hits zero the corresponding motor will be stoped
volatile long ENCODER[2] = {0,0}; //Ticks for right [0] and left [1] wheel (They will be decreased)
...
volatile unsigned char DIRECTION_LEFT = BRAKE; //Use this together with STOP_ON_ENCODER in order to stop the left motor if ENCODER reaches null
volatile unsigned char DIRECTION_RIGHT = BRAKE; //Use this together with STOP_ON_ENCODER in order to stop the right motor if ENCODER reaches null


ISR(SIG_ADC)
{
...
if (ADC_DATA[ADC_CHANNEL] < dval)
{
if (last_odo_segment[ADC_CHANNEL] == WHITE)
{
ENCODER[ADC_CHANNEL]--;
last_odo_segment[ADC_CHANNEL] = BLACK;
NEW_ENCODER_DATA |= (1 << ADC_CHANNEL);
}
if (last_odo_segment[ADC_CHANNEL] == UNKNOWN)
{
last_odo_segment[ADC_CHANNEL] = BLACK;
}
}

// --- bright light ---
if (ADC_DATA[ADC_CHANNEL] > lval)
{
if (last_odo_segment[ADC_CHANNEL] == BLACK)
{
ENCODER[ADC_CHANNEL]--;
last_odo_segment[ADC_CHANNEL] = WHITE;
NEW_ENCODER_DATA |= (1 << ADC_CHANNEL);
}
if (last_odo_segment[ADC_CHANNEL] == UNKNOWN)
{
last_odo_segment[ADC_CHANNEL] = WHITE;
}
}

// --- stop motor if ENCODER reaches null ---
if (!ENCODER[ADC_CHANNEL] && STOP_ON_ENCODER)
{
if (ADC_CHANNEL == RIGHT) MotorDir(DIRECTION_LEFT, BRAKE);
if (ADC_CHANNEL == LEFT) MotorDir(BRAKE, DIRECTION_RIGHT);
}
}
...


Natürlich muss man dann am Anfang die Encoder auf die Werte setzten, die der Strecke entsprechen, die die Motoren bewegt werden sollen.
Ich halte diese Änderungen für notwendig, da man sonst gegenüber dem Pollingbetrieb keine Verbesserung erreicht.

Gruß,

_HP_

rossir
09.05.2007, 23:57
Hallo HP,

hatte mich auch damit beschäftigt.


Die erste Idee, dies im Freeflow-Modus des AD-Wandlers zu machen habe ich fallengelassen, nachdem ich die Dokumentation dazu gelesen hatte. Das Problem ist nämlich, dass man dann nicht mit 100%iger Sicherheit sagen kann, zu welchem Kanal die gerade anstehenden Daten gehören.

Das ist kein Problem. Die ADC Pipline braucht genau zwei Schritte (hier Interrupts) bis nach dem Setzen von ADMUX die passenden Daten fertig sind. Mit diesem Wissen konnte ich den nötigen Code vereinfachen.
Dein ADC_DATA[] heißt bei mir state[]. Hier meine zentrale Interruptroutine.


/**
* ADC_vect: handles all ADC and odometry ticking
*/
ISR(ADC_vect) {
static unsigned char flag[2], avg[2], toggle = 0, stable=0;
static unsigned int old;
static unsigned char mux[]={
(1 << ADLAR) | (1 << REFS0) | SWITCH,
(1 << ADLAR) | (1 << REFS0) | IR_LEFT, // AVCC reference with external capacitor
(1 << ADLAR) | (1 << REFS0) | IR_RIGHT, // AVCC reference with external capacitor
(1 << ADLAR) | (1 << REFS0) | (1 << REFS1) | BATTERY, // internal 2.56V reference with external capacitor
(1 << ADLAR) | (1 << REFS0) | WHEEL_LEFT, // AVCC reference with external capacitor
(1 << ADLAR) | (1 << REFS0) | WHEEL_RIGHT // AVCC reference with external capacitor
};

state[toggle]=ADCL + (ADCH << 8);
if(toggle<2) {
if(ADCH>avg[toggle]) avg[toggle]++; else avg[toggle]--;
if ( flag[toggle]? (ADCH < avg[toggle]-5) : (ADCH > avg[toggle]+5)) {
// if ( flag[toggle]? (ADCH < 180) : (ADCH > 190)) {
encoder[toggle]++;
flag[toggle] ^= 1;
}
}

if(toggle==2) {
stable++;
if(state[2][list=1]old) {
stable=0;
old=state[2];
}
if(stable>3) state[6]=old;
}

ADMUX = mux[toggle];
toggle=(toggle+1) % sizeof(mux);
}

Und dann z.B. für LineData()


/**
* reads out the phototransistors
* @param data pointer to the data destination. access: data[LEFT], data[RIGHT]
*/
void LineData(unsigned int *data)
{
data[0] = state[3];
data[1] = state[4];
}