mikrocontroller.net

Forum: Compiler & IDEs AVR-GCC Programmstruktur (Tastenabfrage+ADC)


Autor: Jimmy (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

Ich programmiere gerade eine Display-ansteuerung. Auf der platine sind 4 
Tasten vorhanden, mit denen ich verschiede Werte in das Programm 
eingebe.

Im Endeffekt, möchte ich ADC auslesen und auf dem Display anzeigen 
(+andere Daten aktualisieren), aber auch auf Tasten reagieren. Im 
Prinzip blockiert ADC die Tastenabfrage, weil der C-code ja ziemlich 
linear ist:
while(1) {
1.Buttonabfrage + damit verbundene Funktionen ausführen
2.Adc lesen + Wert anzeigen
}
Ich frage mich, welche Möglichkeiten es da gibt, auf Tastendruck immer 
möglichst fix zu reagieren? Interrupts bringt irgendwie nicht viel, weil 
ich ja nicht in die Funktion, die mit der Taste verbunden ist, springen 
kann.

Oder gibts doch eine Möglichkeit Interrupts für diesen Code-Verlauf 
einzusetzen?

Gruß Jimmy

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jimmy wrote:

> Oder gibts doch eine Möglichkeit Interrupts für diesen Code-Verlauf
> einzusetzen?

Doch, Interrupts sind die Lösung.

Das Prinzip ist einfach:
Jede Interruptfunktion benutzt eine jeweils eine globale
Variable um anzuzeigen, dass etwas passiert ist.
Dein Hauptprogramm sieht dann so aus:
volatile uint8_t Ereignis_1;
volatile uint8_t Ereignis_2;

int main()
{
  // 
  // alles aufsetzen, Ports initialisieren
  // alle möglichen Datenquellen so konfigurieren
  // das bei vorliegen eines Wertes ein Interrupt
  // ausgelöst wird (zb. Wenn eine Taste gedrückt wird
  // oder wenn der ADC mit einer Wandlung fertig ist

  while( 1 ) {

    if( Ereignis_1 == 1 ) {
      // behandle Ereignis_1.
      // dies tritt zb. ein, wenn der ADC mit einer
      // Messung fertig ist und die zugehörige ISR
      // den Wert geholt hat und Ereignis_1 auf 1
      // gesetzt hat

      // die zugehörige Aktion könnte daher sein:
      // ADC Wert am Display ausgeben

      Ereignis_1 = 0;
    }

    if( Ereignis_2 == 1 ) {
      // behandle Ereignis_2
      // Ereignis2 könnte zb. sein, dass eine bestimmte Taste
      // gedrückt wurde.

      Ereignis_2 = 0;
    }
  }
}

Jede Interrupt Funktion ist also nur dafür zuständig die
entsprechenden Werte bereit zu stellen (mglw. in globalen
Variablen) und das zugehörige Ereignis Flag zu setzen.
Wenn die while Schleife in der main() dann wieder zur
Abfrage kommt, sieht sie das gesetzte Flag und bearbeitet
dieses Ereignis.
Auf diese Art muss nirgends auf irgendetwas gewartet werden
und du kriegst maximale Programmdurchlaufzeit bzw.
minimale Programmantwortzeit auf alle Ereignisse. Die zur
verfügung stehende Rechenzeit teilt sich dynamisch auf die
einzelnen Aufgaben auf, je nachdem wie sie gerade anfallen.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Interrupts müssen doch im Moment des Flag-zurücksetzens gesperrt 
werden, oder nicht?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Simon Küppers wrote:
> Die Interrupts müssen doch im Moment des Flag-zurücksetzens gesperrt
> werden, oder nicht?

Eigentlich sollten sie schon gesperrt werden, wenn die
entsprechende Bearbeitung anfängt. Also

   if( Ereignis_1 ) {
     cli();

     .....

     Ereignis_1 = 0;
     sei();
   }

Eigentlich auch nicht ganz richtig. Eigentlich muesste nur
der eine Interrupt gesperrt werden, der dieses spezielle
Ereignis auslösen kann.

Edit: Stimmt eigentlich auch nicht ganz. Die Interrupts müssen
gesperrt werden, solange auf die globalen Variablen die vom
ISR geschrieben werden, zugegriffen wird.

Wenn eine längere Bearbeitung ansteht, kann es also sinnvoll sein,
sich die Werte zunächst mal in Hifsvariable zu kopieren, damit
der ISR möglichst schnell wieder aktiv werden kann

   if( Ereignis_1 ) {
     char ReceivedString[IRGENDWAS];
     cli();
     strcpy( ReceivedString, StringVonUART_ISR );
     Ereignis_1 = 0;
     sei();

     // und jetzt gibt es viel Zeit um den String auszuwerten, zb.
     Parse( ReceivedString );
     EvaluateFormula();
     WasAuchImmer();
   }

Autor: Jimmy (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mhh, dass man mit Interrupts bestimmte Vars setzen kann und dann in der 
Haupschleife abfragen ist klar, aber wenn z.B. nun das Ereignis 1 
(z.B.ADC) bearbeitet wird, dann kann man doch keine Tasten abfragen bis 
der code durchlaufen ist? Das ist das Problem. Das sieht doch so aus:
///taste=get_key();
switch(taste) {
  Taste1: code (display-anzeige manipulation usw.)
  Taste2: code
}

adc_val=get_ADC();
zeige Wert

Wenn ich nun Flags setze, dann muss ich ja trotzdem auf die Abarbeitung 
warten?

Gruß
Jimmy

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jimmy wrote:
> Mhh, dass man mit Interrupts bestimmte Vars setzen kann und dann in der
> Haupschleife abfragen ist klar, aber wenn z.B. nun das Ereignis 1
> (z.B.ADC) bearbeitet wird, dann kann man doch keine Tasten abfragen bis
> der code durchlaufen ist?

Das ist richtig.

>
> ///taste=get_key();
> switch(taste) {
>   Taste1: code (display-anzeige manipulation usw.)
>   Taste2: code
> }
> 
> adc_val=get_ADC();
> 

Genau hier liegt der Knackpunkt.
Du kannst den ADC so einstellen, dass er einen Interrupt
auslöst, wenn er fertig ist. D.h. dein Programm muss
nicht aktiv auf das fertigwerden des ADC warten.

> Wenn ich nun Flags setze, dann muss ich ja trotzdem auf die Abarbeitung
> warten?

eben nicht.
Du startest den ADC. Dazu gibt es 2 Möglichkeiten:
entweder du machst das 'free running' (d.h. der ADC arbeitet
ständig) oder aber indem du selbst regelmässig den ADC loslegen
lässt. Wenn der ADC ein Ergebnis fertig hat, meldet er sich
mit einem Interrupt.

D.h. die Programmstruktur sieht zb. so aus
volatile uint8_t ADCfinished;

ISR( ADC_fertig )   // weiss grad nicht wie der ISR richtig heist
{
  ADCfinished = 1;
}

int main()
{
  ...
  // ADC konfigurieren (MUX und sonstiges einstellen und 
  // ganz wichtig: Dem ADC mitteilen, dass er einen interrupt
  // auslösen soll.
  ...

  sei();

  // den ADC erst mal loslegen lassen. StartADC wartet aber nicht
  // bis der ADC fertig ist!
  StartADC();
  // der ADC beginnt jetzt mit einer Wandlung, während das Programm
  // hier erst mal weitermacht.

  while( 1 ) {

     ...

     if( ADCfinished ) {
       // hole Wert vom ADC. Nur holen! Die Wandlung ist zu
       // diesem Zeitpunkt bereits erledigt
       // zeige ihn an
       
       // und dem ADC erneut Arbeit zuweisen
       ADCfinished = 0;
       StartADC();
     }

     ...
   }
}

und damit hast du eine Programmstruktur, in der du nicht
auf das Fertigwerden des ADC warten musst. Der ADC meldet
sich mit einem Interrupt wenn er fertig ist und in weiterer
Folge wird in der while der Abschnitt für das ADC Ereignis
kurz betreten um das Ergebnis abzuholen und eine neue
Wandlung anzustossen.

Autor: Jimmy (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klingt sehr gut, vielen Dank schon mal. Die Grundidee hab ich jetzt 
verstanden, versuche das jetzt umzusetzen.

Gruß
Jimmy

Autor: Andreas Paulin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der ADC braucht aber nur ein paar Mikrosekunden für eine Wandlung. In 
der Praxis ists meistens egal, ob das Programm ein paar µs schneller 
oder langsamer reagiert, besonders bei Tastendrücken.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nicht vergessen, dass die Tasten ohnehin entprellt werden müssen,
das braucht vernünftigerweise einen Timerinterrupt, der so alle
ca. 10 ms triggert.  Den braucht man auch für andere Dinge, und
der fragt die Tasten gleich mit ab.

Den größten Teil seines Lebens darf der AVR dann getrost im Schlaf
verbringen (der hat's gut :).

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Andreas Paulin wrote:
> Der ADC braucht aber nur ein paar Mikrosekunden für eine Wandlung. In
> der Praxis ists meistens egal, ob das Programm ein paar µs schneller
> oder langsamer reagiert, besonders bei Tastendrücken.

Naja, einige Mikrosekunden? Einige Hundert-Mikrosekunden wohl eher :-)

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Simon Küppers wrote:

> Naja, einige Mikrosekunden? Einige Hundert-Mikrosekunden wohl eher :-)

Habe gestern gerade einen Strommesser mit einem AVR gebaut, damit ich
mir den Stromverbrauch einer Schaltung parallel zu den anderen Daten
auf dem logic analyzer mit aufzeichnen lassen konnte.  OK, es waren
effektiv (Differenzmessung, die aber nur mit einer Polarität benutzt
wird, meine Schaltung liefert ja keinen Strom :) nur 7 Bit Auflösung,
aber dafür hab' ich ihn bis 28 µs Wandlungszeit (und dank free running
mode auch Abtastrate) bekommen.  Das ist immerhin dreimal so schnell
wie der kommerzielle Strommesser, den wir am Anfang nehmen wollten
(der hat aber weniger Speichertiefe als der LA, und man hätte seine
Messung nicht einfach mit den LA-Daten korrelieren können).  OK, der
hätte sicher eine deutlich bessere Dynamik gehabt, war aber hier nicht
wichtig.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Interessant. Aber wenn man vom worst-case ausgeht sind das 13 Takte 
(oder etwa das doppelte bei der ersten Messung) bei 50kHz Wandler-Takt. 
Macht 260µs.

Bei 16MHz ist eine Periode 62,5ns lang. Das heißt, dass etwa 4000 
Prozessortakte verstreichen, ehe der Prozessor weiterrechnet.*

*Wenn ich jetzt keinen Rechenfehler gemacht habe ;)

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Simon Küppers wrote:

> Interessant. Aber wenn man vom worst-case ausgeht sind das 13 Takte
> (oder etwa das doppelte bei der ersten Messung) bei 50kHz Wandler-Takt.
> Macht 260µs.

Naja, was heißt ``worst case''?  Die (50...200) kHz sind ja eine
reine Empfehlung für den optimalen Betriebsbereich.  ``worst case''
kannst du ihn auch noch langsamer machen. ;-)  Dann wird aber
vermutlich die Haltezeit der S&H-Schaltung die Genauigkeit
reduzieren.  Nach oben gibt Atmel mittlerweile offiziell zu, dass
für reduzierte Genauigkeitsansprüche bis zu 1 MHz Takt möglich
sind, das wären 13 µs.  In meinem Fall waren aber bei 14 µs (die
Differenzmessung dauert einen Takt länger) die Messergebnisse für
ein konstantes Eingangssignal schon sehr wackelig geworden, daher
bin ich nur bis 500 kHz gegangen.

In der Praxis kommt man auf Grund des 2**N-Vorteilers wohl eher
selten an die Obergrenze 200 kHz des Optimalbereichs.

> Bei 16MHz ist eine Periode 62,5ns lang. Das heißt, dass etwa 4000
> Prozessortakte verstreichen, ehe der Prozessor weiterrechnet.*

Das ist ein wenig eine Milchmädchenrechnung, auf Grund des besagten
Vorteilerproblems.  16 MHz / 50 kHz = 320, der größtmögliche Vorteiler
ist aber 128.  Generell kannst du ganz einfach sagen, dass bei 13
ADC-Takten irgendwas zwischen 26 und 1664 CPU-Takten zwischen den
(minimal möglichen) Messungen liegt, in 1:2-Abstufungen gestaffelt.
Da ich im Interruptbetrieb (mit SLEEP_MODE_ADC) gearbeitet habe, sind
die 26 Takte (28 bei mir) schon ein wenig eine Herausforderung, damit
man überhaupt noch das Ergebnis ausgegeben bekommt, bevor das nächste
Ergebnis bereit gestellt wird. ;-)  Da die main loop bei mir aber nur
aus einer Endlosschleife von sleep_cpu()s bestand, habe ich mir den
Luxus einer `naked' ISR geleistet und damit jeglichen Prolog/Epilog
gespart.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.