PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Zeitmessung mit dem Atmega8 - geht das?



Körperkrämer
18.03.2008, 13:52
Hallo Leute,
ich muss erst einmal erwähnen, ich bin absoluter Neuling im AVR-Gebiet.
Was nicht heißen soll, dass das mich davon abhalten soll^^

Mir geht es um Folgendes:
Ich habe 2 Taster, mein Ziel wäre es , je nachdem ob 1 oder beide taster gedrückt sind und auch va. wie lange diese jeweils gedrückt sind, diverse Aktionen zu provozieren.

Mir wäre es am Liebsten das ganze in C++/Basic zu programmieren,
nur wie kann man mit dem AVR 2 Verschiede Zeiten gleichzeitig messen?
was mir wichtig ist, ist dass es dann so funktioniert:
Taster gedrückt -> Zähler startet; Taster losgelassen -> Zähler stoppt.
Und davon halt zwei gleichzeitig.

ist soetwas machbar?
Bitte erklärt es so genau wie möglich, ich bin wirklich absoluter anfänger.
Sollte ich weitere Bauteile benötigen wäre das auch kein Problem, ich bestelle eh erst danach.

Schon mal Danke:)

daniel.weber
18.03.2008, 14:01
Hallo,
schau dir mal diesen Artikel im RN-Wissen Bereich an:

https://www.roboternetz.de/wissen/index.php/Bascom_und_Timer

Sollte das sein, was du suchst :)

Körperkrämer
18.03.2008, 14:30
Ich denke du hast es zumindest mit diesem Artikel ganz gut getroffen.
Allerdings tun sich mir nach dem Lesen der Artikels wieder 2 Fragen auf:

1. All die Sachen die in den Beispielen oben zu Beginn des Programms aufgerufen werden, kann man die auch beliebig im Programm aufrufen?
Wie zB müsste ich den Code umschreiben, um timer0 bei tastendruck zu starten und wieder zu beenden?

2. Heißt das es ist ohne weiteres möglich, beide 8-Bit Timer des mega8
unabhängig voneinander, aber zugleich durchlaufen zu lassen?[/list]

Felix G
18.03.2008, 14:44
Also zunächst mal würde ich einen der Timer so konfigurieren, daß er eine art globalen Takt zur Verfügung stellt, er sollte also im wesentlichen nur eine Variable z.B. 1x pro Millisekunde um eins inkrementieren. Je nach Bedarf muss das natürlich eventuell auf mehrere Variablen erweitert werden, ich verwende eigentlich immer mehrere Variablen (z.B. 1/1000s, 1/10s, 1s, 1min ... je nachdem was gerade gebraucht wird)


Auf diese Art kann man leicht an beliebiger Stelle die Zeit, welche zwischen zwei Ereignissen vergangen ist feststellen, denn man muss ja nur die Startzeit von der Endzeit abziehen.


So, und damit das mit deinen beiden Tastern bequem funktioniert, schließt du sie an die beiden zur Verfügung stehenden externen Interrupts an.

Der Ablauf ist dann ganz simpel:
- Einstellen beider Interrupts auf fallende Flanke (wenn die Taster gegen Masse schalten)

- Wenn ein Interrupt durch eine fallende Flanke ausgelöst wird: die aktuelle Zeit (= Startzeit) merken, und den Interrupt auf steigende Flanke umschalten

- Wenn ein Interrupt durch eine steigende Flanke ausgelöst wurde: Startzeit von aktueller Zeit abziehen (= Dauer des Tastendrucks), und den Interrupt wieder auf fallende Flanke umschalten.

Körperkrämer
18.03.2008, 15:12
Hallo Felix G,
ich finde deine Idee eigentlich klasse, nur will mir mein fehlendes AVR-Wissen mal wieder einen Strich durch die Rechnung ziehen.

Daher hab ich nochmal ein paar Fragen:

Kannst du mir vlt kurz erklären was es mit den Flanken auf sich hat ( Der Begriff "Flanke" sagt mir leider im Zusammenhang mit dem IC nichts)
Ein Link zu einer Erklärung reicht natürlich auch.

Wie würde sich das mit globalen Variable auf Dauer verhalten.
Bei mir wäre es nämlich so, dass der atmega die ganze zeit laufen würde, da ich damit unter anderem auch meinen Computer anschalten will.

Wie verhält sich das mit Pufferüberläufen bzw wie könnte ich das umgehen?

Schon mal Danke für die Mühe

Felix G
18.03.2008, 15:39
Also bei einem Digitalsignal sind die Flanken im Prinzip die Übergänge von einem Pegel auf den anderen.

Eine fallende Flanke ist dabei ein Übergang von High auf Low (wie er z.B. beim Drücken des Tasters entsteht, falls dieser gegen GND schaltet), und eine steigende Flanke dementsprechend ein Übergang von Low auf High.

Die externen Interrupts eines ATmega kann man nun so konfigurieren, daß sie in unterschiedlichen Situationen ausgelöst werden, z.B. eben bei einer steigenden oder einer fallenden Flanke.


Da ein (gegen GND schaltender) Taster bei Betätigung eine fallende Flanke erzeugt, und beim Loslassen eine steigende, kann man mit Hilfe eines Interrupts leicht feststellen wann ein Taster gedrückt, und wann er wieder losgelassen wurde.


edit:
wichtig dabei ist übrigens, daß die Taster hardwaremäßig entprellt wurden.
sie benötigen also jeweils einen nachgeschalteten RC-Tiefpass, und idealerweise zusätzlich noch einen sogenannten "Schmitt-Trigger" (notfalls reicht aber auch der Tiefpass, wenn er etwas großzügiger dimensioniert ist).


Was die Variable betrifft, ist die Lösung eigentlich recht simpel...

Entweder du verwendest mehrere Variablen, und realisierst damit eine Art interne "Uhr". Wenn du also eine 8-Bit Variable für ms hast, und möchtest gerne weiter zählen als bis 256ms, dann nimm noch eine zweite für z.B. 1/100s oder 1/10s, und zähle diese jeweils eins hoch wenn die ms-Variable den entsprechenden Wert erreicht hat (die ms Variable wird dann natürlich wieder auf 0 gesetzt). Nach diesem System kannst du deine "Uhr" bis auf Sekunden, Minuten, Stunden, Tage, Jahre oder gar Jahrhunderte erweitern, je nachdem wieviel du benötigst.

Eine andere Variante wäre es, bei der Subtraktion entsprechend zu berücksichtigen ob in der Zwischenzeit ein Überlauf stattgefunden hat (das merkt man ja leicht, wenn die Endzeit kleiner ist als die Startzeit). Die Einzige Beschränkung liegt dann natürlich darin, daß nur maximal ein Überlauf im gemessenen Zeitraum auftreten darf.

ich denke meist ist eine Kombination aus beiden Varianten am sinnvollsten, also z.B. Zählvariablen die bis in den Sekundenbereich gehen, und dann überprüfen ob in der gemessenen Zeit die Sekundenvariable einen Überlauf hatte (so könntest du dann Zeiten bis 256s messen).

Körperkrämer
18.03.2008, 17:45
Ich hab mir überlegt und denke mir reicht es wenn ich eine "Uhr" erstelle die im 50 ms-takt tickt. Würde dann ja heißen, dass die Variable 20 mal die Sekunde erhöht wird. Ich weiß jetzt nicht genau wo die grenze Liegt (vermute ja mal was im bereich um die 65000 für eine Int Zahl), aber
65000/20 reicht mir vollkommen, wenn man bedenkt, dass so ein Tastendruck maximal 5 sek. dauern wird ^^

bedeutet Interrupt dass dieser Interrupt quasi überwacht, ob sich eine Flanke geändert hat oder müsste ich diese interrupt Funktion in eine iterative/rekursive Schleife packen? (Also wie beim erstellen eines GUIs: Entweder man prüft andauernd ob ein Button geklickt wurde und Ruft entsprechend die Funktion auf oder man verknüpft den Button mit einer Funktion, damit dieser sie beim Klicken aufruft.)
Ich hoffe nur ich fange nicht langsam an dich zu nerven,aber...
Kennst du vielleicht ein gutes Tutorial bezüglich Interrupts?
Ich finde es klingt zwar schon logisch, aber mir ist es immer am liebsten wenn ich es mir iwie bildlich vorstellen kann.

Felix G
18.03.2008, 17:58
Interrupts sind dafür da, daß man eben nicht dauernd nachprüfen muss ob etwas passiert ist...

Du schreibst nur eine spezielle Funktion, die sogenannte ISR (Interrupt Service Routine), diese wird dann vom AVR vollautomatisch ausgeführt, sobald das entsprechende Ereignis eintritt (also z.B. ein Pegelwechsel von Low nach High).


Für jeden Interrupt den du verwendest benötigst du eine ISR, in deinem Fall wären das also insgesamt 3, nämlich eine für den Timer und nochmal je eine für die beiden externen Interrupts.


Bei einer ISR ist darauf zu achten, daß sie möglichst kurz sein sollte, sehr aufwendige Berechnungen haben darin nichts zu suchen.



Ich kann dir, wenn ich später ein bischen Zeit habe, gerne mal ein kleines Beispielprogramm (in C) schreiben, dann lässt sich der ganze Zusammenhang und die Funktionsweise auch besser erklären.

Körperkrämer
18.03.2008, 18:39
Das wäre klasse wenn du das machen könntest.
ich hatte mir zwar den Timer Artikel aus dem RN-Wissen angeschaut, aber erst jetzt als du "ISR" erwähnt hast, ist mir der Zusammenhang aufgefallen.

Felix G
18.03.2008, 20:41
Soooo....


Ich habe jetzt hier ein (ungeprüftes) Beispielprogramm das, wenn es funktioniert, zwei Taster darauf überprüfen soll ob sie kurz (<3s) oder lang (>3s) gedrückt wurden, und jeweils eine von 4 zugehörigen Funktionen aufruft.

Der Code ist etwas lang, und normalerweise würde ich ein derartiges Programm auf mehrere .c und .h Dateien verteilen, aber es ist ja nur ein einfaches Beispiel.

#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>

/* globale Variablen */
volatile unsigned char timer_10ms, timer_1s;
volatile unsigned char flags;

#define FLAG_INT0_SHORT 0
#define FLAG_INT0_LONG 0
#define FLAG_INT1_SHORT 0
#define FLAG_INT1_LONG 0


/* Hardware initialisieren */
void avr_init(void)
{
//Ports

//Port-D
DDRD = 0xF3; //Pins PD2(Int0) und PD3(Int1) als Eingang
PORTD = 0x0C; //Pull-Up Widerstände von PD2 und PD3 aktiviert


//Timer

//Timer0 (10ms Takt)
//ATmega8 Datenblatt, Seite 72
TCCR0 = 0x05; //Prescaler: 1/1024
TCNT0 = 0x64; //Vorgabewert: 100

//Timer Interrupt Register
//ATmega8 Datenblatt, Seite 72-73
TIMSK = 0x01; //Overflow-Interrupt für Timer0
TIFR = 0x00; //Interrupt-Flag für Timer0 löschen


//Externe Interrupts
//ATmega8 Datenblatt, Seite 66-68
MCUCR = 0x0A; //Externe Interrupts für fallende Flanke konfigurieren
GICR = 0xC0; //Externe Interrupts aktivieren
GIFR = 0x00; //Interrupt-Flags löschen


sei(); //Interrupts global aktivieren
}


/* Timer0 Interrupt Service Routine */
SIGNAL(SIG_OVERFLOW0)
{
TCNT0 = 0x64; //Vorgabewert neu einstellen

timer_10ms++; //10ms Variable inkrementieren

//nach einer Sekunde
if(Timer_10ms % 100 == 0)
{
Timer_10ms = 0; //10ms Variable = 0
Timer_1s++; //1s Variable inkrementieren
}
}


/* INT0 Interrupt Service Routine */
SIGNAL(SIG_INTERRUPT0)
{
static char int0_start, int0_duration;
char tmp;

//fallende oder steigende Flanke?
if((MCUCR & 0x03) == 0x02)
{
//fallende Flanke

//Startzeit merken
int0_start = timer_1s;

//Interrupt auf steigende Flanke einstellen
tmp = MCUCR;
tmp &= 0xFC; //Konfigurationsbits für INT0 löschen
tmp |= 0x03; //einstellen auf steigende Flanke
MCUCR = tmp;
}
else if((MCUCR & 0x03) == 0x03)
{
//steigende Flanke

//aktuelle Zeit zwischenspeichern, falls ISR durch Timer-Interrupt unterbrochen wird
tmp = timer_1s;

if(tmp <= int0_start)
int0_duration = tmp + (255 - int0_start);
else
int0_duration = tmp - int0_start;

//kurz oder lang?
if(int0_duration < 3)
flags |= (1 << FLAG_INT0_SHORT);
else
flags |= (1 << FLAG_INT0_LONG);

//Interrupt auf fallende Flanke einstellen
tmp = MCUCR;
tmp &= 0xFC; //Konfigurationsbits für INT0 löschen
tmp |= 0x02; //einstellen auf fallende Flanke
MCUCR = tmp;
}
else
{
//Interrupt falsch eingestellt, rücksetzen auf fallende Flanke
tmp = MCUCR;
tmp &= 0xFC; //Konfigurationsbits für INT0 löschen
tmp |= 0x02; //einstellen auf fallende Flanke
MCUCR = tmp;
}
}


/* INT1 Interrupt Service Routine */
SIGNAL(SIG_INTERRUPT1)
{
static char int1_start, int1_duration;
char tmp;

//fallende oder steigende Flanke?
if((MCUCR & 0x0C) == 0x08)
{
//fallende Flanke

//Startzeit merken
int0_start = timer_1s;

//Interrupt auf steigende Flanke einstellen
tmp = MCUCR;
tmp &= 0xF3; //Konfigurationsbits für INT1 löschen
tmp |= 0x0C; //einstellen auf steigende Flanke
MCUCR = tmp;
}
else if((MCUCR & 0x0C) == 0x0C)
{
//steigende Flanke

//aktuelle Zeit zwischenspeichern, falls ISR durch Timer-Interrupt unterbrochen wird
tmp = timer_1s;

if(tmp <= int1_start)
int1_duration = tmp + (255 - int1_start);
else
int1_duration = tmp - int1_start;

//kurz oder lang?
if(int0_duration < 3)
flags |= (1 << FLAG_INT1_SHORT);
else
flags |= (1 << FLAG_INT1_LONG);

//Interrupt auf fallende Flanke einstellen
tmp = MCUCR;
tmp &= 0xF3; //Konfigurationsbits für INT1 löschen
tmp |= 0x08; //einstellen auf fallende Flanke
MCUCR = tmp;
}
else
{
//Interrupt falsch eingestellt, rücksetzen auf fallende Flanke
tmp = MCUCR;
tmp &= 0xF3; //Konfigurationsbits für INT1 löschen
tmp |= 0x08; //einstellen auf fallende Flanke
MCUCR = tmp;
}
}


/* Hier kommen die Funktionen */
void action_int0_short(void)
{
//eine Funktion muss tun, was eine Funktion tun muss
}

void action_int0_long(void)
{
//eine Funktion muss tun, was eine Funktion tun muss
}

void action_int1_short(void)
{
//eine Funktion muss tun, was eine Funktion tun muss
}

void action_int1_long(void)
{
//eine Funktion muss tun, was eine Funktion tun muss
}


/* last but not least: main */
int main(void)
{
//Variablen initialisieren
timer_10ms = 0;
timer_1s = 0;
flags = 0x00;

//Hardware initialisieren
avr_init();

while(1)
{
//Taster an INT0 wurde kurz betätigt
if(flags & (1 << FLAG_INT0_SHORT))
{
action_int0_short(); //zugehörige Funktion aufrufen
flags &= ~(1 << FLAG_INT0_SHORT); //flag löschen
}

//Taster an INT0 wurde lang betätigt
if(flags & (1 << FLAG_INT0_LONG))
{
action_int0_long(); //zugehörige Funktion aufrufen
flags &= ~(1 << FLAG_INT0_LONG); //flag löschen
}

//Taster an INT1 wurde kurz betätigt
if(flags & (1 << FLAG_INT1_SHORT))
{
action_int1_short(); //zugehörige Funktion aufrufen
flags &= ~(1 << FLAG_INT1_SHORT); //flag löschen
}

//Taster an INT1 wurde lang betätigt
if(flags & (1 << FLAG_INT1_LONG))
{
action_int1_long(); //zugehörige Funktion aufrufen
flags &= ~(1 << FLAG_INT1_LONG); //flag löschen
}
}
}

Es beginnt mit der Definition einiger Variablen, dann kommt eine funktion die die Initialisierung des Mega8 übernimmt, gefolgt von den drei ISRs und den 4 Funktionen die aufgerufen werden sollen. Das eigentliche "Hauptprogramm", also main, kommt gaaanz am Schluss.


Die Funktionen werden übrigens aus einem bestimmten Grund in main() aufgerufen: jede Funktion die man innerhalb einer ISR aufruft, verlängert diese unnötig, daher ist es sinnvoll sich nur zu merken welche Funktion aufgerufen werden soll, und dieses dann außerhalb der ISR zu tun.

Körperkrämer
19.03.2008, 17:36
ich melde mich mal wieder zurück.
Erst mal Danke für die viele Mühe-

ich hab noch ein paar Fragen zu deinem Code:

Ich habe das jetzt so interpretiert, dass die einzige Funktion, die immer
wieder wiederholt wird int main () ist. Und die prüft, ob die flags entsprechend gesetzt wurden und ruft die dazugehörigen Funktionen auf.

Wie ist es jetzt, wenn ich - banal ausgedrückt - die Taste drücke?
Wird dann die INTx ISR aufgerufen , die dann erst einmal feststellt "fallende Flanke -> also intx_start merken" und beim zweiten Aufruf "steigende Flanke -> Dauer berechnen und flangs setzen" ausführt?

Heißt das dann dass der atmega8 also mehrere tasks gleichzeitig (oder so gut wie ?) ausführen kann? Es kann ja sein, dass ich beide tasten gleichzeitig drücken will. Wird int main() während des Abarbeitens einer ISr unterbrochen? Ja oder?.

Ich weis, Fragen über Fragen, aber ich bin halt neugierig und erst zufrieden mit mir, wenn das Projekt steht O:)

Nur noch eine allerletzte für diesen post:
jegliche Zuweisungen...Ports, Pins und flags... geschehen die in c++ hexadezimal? Sry für die vlt dumme Frage, aber ich bin erst dabei C++ zu lernen und hab noch kein brauchbares Tutorial für Einsteiger bezüglich Atmega8 gefunden.
Vlt kennst du ja eins?

Also nochmals Danke von mir^^

Felix G
19.03.2008, 20:45
Ich habe das jetzt so interpretiert, dass die einzige Funktion, die immer
wieder wiederholt wird int main () ist. Und die prüft, ob die flags entsprechend gesetzt wurden und ruft die dazugehörigen Funktionen auf. Genau...
in C ist main() eigentlich immer die "Hauptfunktion", d.h. das Programm beginnt immer mit main().

Die Endlosschleife in main() übernimmt genau die von dir beschriebene Funktion.



Wie ist es jetzt, wenn ich - banal ausgedrückt - die Taste drücke?
Wird dann die INTx ISR aufgerufen , die dann erst einmal feststellt "fallende Flanke -> also intx_start merken" und beim zweiten Aufruf "steigende Flanke -> Dauer berechnen und flangs setzen" ausführt?Ja, ganz genau so ist es.
Der Controller ruft - wenn er richtig konfiguriert wurde - die passende ISR automatisch auf, sobald das zugehörige Ereignis eintritt.


Heißt das dann dass der atmega8 also mehrere tasks gleichzeitig (oder so gut wie ?) ausführen kann? Es kann ja sein, dass ich beide tasten gleichzeitig drücken will. Wird int main() während des Abarbeitens einer ISr unterbrochen? Ja oder?.Naja, er kann natürlich nicht wirklich zwei Dinge gleichzeitig tun, aber rein prinzipiell ist das schon so eine Art Multitasking (allerdings recht primitiv).

Wenn ein Interrupt ausgelöst wird, unterbricht der Controller automatisch das laufende Programm, und springt zur ISR. Sobald er diese abgearbeitet hat, springt er zurück zu der Stelle an der das Programm unterbrochen wurde.

Dabei ist es übrigens auch möglich daß sich Interrupts gegenseitig unterbrechen.

Falls du es tatsächlich schaffen solltest die beiden Taster exakt zum gleichen Zeitpunkt zu betätigen ist das auch nicht weiter schlimm, denn der Controller merkt sich von selbst was er noch zu tun hat, und bearbeitet die beiden ISRs dann halt nacheinander.



Nur noch eine allerletzte für diesen post:
jegliche Zuweisungen...Ports, Pins und flags... geschehen die in c++ hexadezimal?Das nicht, aber eine binäre Zuweisung gibt es in C nicht, und die hexadezimale Schreibweise ist in solchen Fällen sozusagen die nächstbeste Lösung was die Lesbarkeit betrifft (bei dezimalzahlen ist es recht schwer herauszufinden welche Bits denn da nun gesetzt sind und welche nicht, hexadezimal ist das leichter da man die Bits immer schön in 4er Gruppen betrachten kann).


Ein C Tutorial (C++ macht bei Mikrocontrollern nicht viel Sinn) gibt es unter anderem in unserem Wiki: RN-Wissen C-Tutorial (https://www.roboternetz.de/wissen/index.php/C-Tutorial)

Körperkrämer
20.03.2008, 13:51
Ich denke ich bin wohl damit, den Code in C/C++ zu schreiben doch etwas überfordert, was bedeutet, ich werde auf Basic und Bascom zurückgreifen.

Den Sinn, warum die Flags entsprechend zugewiesen werden, hab ich ja mittlerweile raus, nur ist mir immer noch nicht klar, was genau dabei geschieht. (Meine C++ kenntnisse enden leider da, wo ich das erste mal das Wort "Bit" höre :-$ 8-[ )
Würde es nicht reichen das Ergebnis der ISRs "//kurz oder lang? " statt in flags einfach in zwei Variablen (boolsche) jeweils für INT0 und INT1 zu schreiben?

Ich hab leider auch nicht so viel Ahnung von Basic, aber nachdem was ich jetzt gesehen habe, ist es doch einfacher als einen AVR mit C zu proggen.
Gibt es in basic überhaupt funktionen ( In Anlehnung an main())?

Felix G
20.03.2008, 14:52
Also natürlich kannst du anstatt der flags auch einzelne Variablen verwenden, der einzige Nachteil dabei ist der höhere Speicherbedarf, was man bei den ersten kleinen Projekten aber getrost vernachlässigen kann.