PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : der ASURO als Wecker



M1.R
17.12.2007, 21:39
Hallo,

hier (Anhang) nun das Gegenstück zum Einschlafprogramm: (https://www.roboternetz.de/phpBB2/viewtopic.php?p=334425#334425)
der ASURO als Wecker.
Leider eignet er sich nur für ein kurzes Mittagsschläfchen, da die Akkus nicht sehr lange durchhalten.
Oder gibt es irgendwelche programmiertechnischen Tricks zur Energieeinsparung?

Gruss
M.

Sternthaler
17.12.2007, 23:33
Hallo M1.R.
da hast du dir ja wieder was lustiges, und sinnvolles ausgedacht.

Zu Stromsparen kannst die Kiste mit:
void set_sleep_mode (uint8 t mode)
void sleep_mode (void)

in einen Sleep-Mode setzen.

Die mode-Möglichkeiten sind folgende:

#define SLEEP_MODE_ADC BV(SM0)
ADC Noise Reduction Mode.
#define SLEEP_MODE_EXT_STANDBY ( BV(SM0) | BV(SM1) | -
Extended Standby Mode.
#define SLEEP_MODE_IDLE 0
Idle mode.
#define SLEEP_MODE_PWR_DOWN BV(SM1)
Power Down Mode.
#define SLEEP_MODE_PWR_SAVE ( BV(SM0) | BV(SM1))
Power Save Mode.
#define SLEEP_MODE_STANDBY ( BV(SM1) | BV(SM2))
Standby Mode.

Am sinnvollsten erscheint mir der Mode "Power Save Mode", da hier alles außer Timer 2 abgeschaltet wird. Mit hoher Wahrscheinlichkeit muss noch das Bit AS2 im ASSR-Register gesetzt werden. Sonst ist auch Timer 2 ausgeschaltete!

Dann sollte folgendes Prinzip funktionieren:

// ISR muss nichts tun. Soll nur vorhanden sein.
ISR (TIMER-2)
{
;
}

// Initialisiere Timer 2 möglichst auf 1 ms
// Hier die Bits setzen, die den Timer 2 veranlassen, nach einer ms einen
// Interrupt auszulösen.
Tu dies;
Setze das;
schalte den Interrupt scharf;

// Initialisiere einen eigenen ms-Zähler
ms_zeit = 0;

// Sleepmode setzen
set_sleep_mode (SLEEP_MODE_PWR_SAVE);

while (ms_zeit < zeit)
{
ms_zeit++;
sleep_mode ();
}


Das Ganze ist in den Dokumenten "AVRInstructionSet.pdf" und "ATmega8.pdf" auf der Asuro-CD zu finden.

Im ATmega8.pdf sind auf Seite 33 die Sleep-Modes
Im AVRInstructionSet.pdf in Kapitel "5.5 Power Management and Sleep Modes" geht es um die Sleep-Befehle.

So, und jetzt werfe ich das Einschlafprogramm an. ;-)

Gruß Sterntahler
P.S.: Das Ganze ist rein theoretisch, da ich das selber noch nie gemacht habe.

M1.R
18.12.2007, 13:22
Hallo Sternthaler,


// Initialisiere Timer 2 möglichst auf 1 ms
// Hier die Bits setzen, die den Timer 2 veranlassen, nach einer ms einen
// Interrupt auszulösen.
Tu dies;
Setze das;
schalte den Interrupt scharf;

o je, o je, da hab ich mir mal wieder was angetan - erst diese seltsamen Bitschiebereien, und dann auch noch die mir völlig suspekten Interrupts.
Und ich habe gedacht, so was brauche ich nie.

Fragen dazu folgen bestimmt demnächst ...

Ist aber schon mal gut zu wissen, dass es Stromsparmöglichkeiten gibt, vielen Dank!

Gruss
M.

M1.R
19.12.2007, 19:05
so, jetzt habe ich rumprobiert.

Mit IDLE scheints mit einem normalen Msleep zu gehen:


#include "asuro.h"
#include <avr/interrupt.h>
#include <avr/sleep.h>
//------------------------------------------------------------------
int main(void)
{
Init();

StatusLED(OFF);
BackLED(ON,ON);
MotorDir(FWD,FWD);
MotorSpeed(200,0);
Msleep(5000);

set_sleep_mode ( SLEEP_MODE_IDLE );

for (int16_t i = 1; i <= 10; ++i)
{
BackLED(OFF,OFF);
StatusLED(YELLOW);
sleep_enable();
sleep_mode();
sleep_disable();
Msleep(500);
}

MotorSpeed(0,0);
StatusLED(RED);
while (1);
return 0;
}


das gleiche mit PWR_SAVE wie zu erwarten leider nicht: die BackLEDs gehen brav aus, die StatusLED wird gelb, der Motor dreht sich schneller und so bleibts.

...
set_sleep_mode ( SLEEP_MODE_PWR_SAVE );
ASSR = (1<<AS2);
...

Mit Interrupts gehts wahrscheinlich - hilft mir bitte jemand dabei? (Ich kapiers nicht!)

da gibt es noch eine Anleitung: ...WinAVR-20070525/doc/avr-libc/avr-libc-user-manual/group__avr__sleep.html
und da:http://www.mikrocontroller.net/articles/Sleep_Mode

Gruss
M.

Sternthaler
20.12.2007, 00:44
Hallo M1.R,
in dem von dir angegeben Wiki steht ja direkt zu Anfang warum das nicht geht:
... Das heißt aber auch, daß vor dem Eintritt in den Sleep-Mode die jeweiligen Interrupts freigegeben werden müssen und auch die entsprechenden Interruptroutinen bereitgestellt werden müssen. Sonst gibt es im wahrsten Sinne des Wortes ein böses Erwachen oder einen endlosen Dornröschenschlaf, ...
Der "Power-Save-Mode" schaltet nämlich auch alle laufenden Timer ab.

Im "ATmega8.pdf" auf Seite 33 ist unter dem Kapitel "Power Management
and Sleep Modes" eine Tabelle, die angibt was im AVR noch mit Taktsignalen in welchem Mode versorgt wird. Nur wo Takt ist, spielt die Musik. Sollte in deinem Haushalt doch bekannt sein ;-)

Da du ja in deinem Beispiel beim "Power-Save-Mode" keine Interrupt-Funktion hast, kann auch kein Wecksignal erzeugt werden.
Und somit bleibt der AVR im oben angegeben Dornröschenschlaf.

Du solltest dir Tabelle (Tabelle 14) mal ansehen. Dann wird klar, warum ich den Timer-2-Interrupt vorgeschlagen hatte, und du auf das AS2-Bit achten musst.
Jetzt finde ich aber auch folgendes zum "Pover-Save-Mode":
Hier werden fast alle Oszillatoren gestoppt. Die einzige Ausnahme ist der Timer 2, welcher asynchron mit einem 32,768 kHz Uhrenquarz betrieben werden kann. Ist er entsprechend konfiguriert, dann bleibt er beim Einschalten des Power Save Mode aktiv. Dieser Modus ist einer der wichtigsten! Da alle Oszillatoren gestoppt sind, funktionieren nur noch die asynchronen Module.
Hier scheint somit ein externer Takt für den Timer 2 notwendig zu sein. Also ist es nicht sicher, ob das dann überhaupt mit dem Quarz am Asuro funktionieren wird. Obwohl das ja ein externer Taktgeber ist. Keine Ahnung.

Gruß Sternthaler

M1.R
20.12.2007, 17:17
Hallo Sternthaler,
erstmal eine Erfolgsmeldung:
mit "IDLE" haben die Akkus die ganze Nacht durchgehalten und mein ASURO hat mich heute morgen geweckt! :)
Vielen Dank!!!


in dem von dir angegeben Wiki steht ja direkt zu Anfang warum das nicht geht ...Hab ich ja auch eigentlich nicht gedacht, habs nur ausprobiert in der Hoffnung, dass im Msleep ein Interrupt "drin" ist.


Du solltest dir Tabelle (Tabelle 14) mal ansehen. Dann wird klar, warum ich den Timer-2-Interrupt vorgeschlagen hatte, und du auf das AS2-Bit achten musst.Hab ich, ist auch klar. Ich hätte es ja auch so versucht, wie du vorgeschlagen hast, wenn ich die Interrupt-Programmierung verstehen würde.:(
Gruss
M.

inka
20.12.2007, 17:56
hi M.1.R,
und wie hast du es nun gemacht? große abweichungen zum code oben?

M1.R
20.12.2007, 20:03
hi M.1.R,
und wie hast du es nun gemacht? große abweichungen zum code oben?meinst du die Änderungen im Wecker-Programm?

Das muss rein:
#include <avr/sleep.h>
die Warteschleife (kurz vorm Ende des Programms) sieht jetzt so aus:

set_sleep_mode ( SLEEP_MODE_IDLE );
while ( Gettime() < zeit )
{
sleep_enable();
sleep_mode();
sleep_disable();
Gettime();
}
Das ist jetzt aber nur der Idle Mode, mit dem Power Save Mode könnte man noch viel mehr Strom sparen.
Gruss
M.

recycle
20.12.2007, 20:27
Leider eignet er sich nur für ein kurzes Mittagsschläfchen, da die Akkus nicht sehr lange durchhalten.
Oder gibt es irgendwelche programmiertechnischen Tricks zur Energieeinsparung?


Ich bin irgendwie auf Akkuverschwendung programmiert, wache morgens manchmal schon vor dem Weckerklinglen auf, mach ihn dann im Halbschlauf aus und verpenne.

Mit ein bischen Selbstdisziplin kannst du dich ja vielleicht so prigrammieren, dass du auch vorher wach wirst und den Asuro erst kurz vor dem Wecken einschaltest.
Das spart nicht nur Strom - dann kannst du auch die Vorfreude auf den Asuro-Weckdienst besser geniessen.
Ausserdem braucht dein Asuro dann auch keine Angst haben beim Wecken Schläge zu kriegen, was meinem Wecker wenn er mich morgens wirklich kalt im Schlaf erwischt öfter mal passiert ;-)

Sternthaler
20.12.2007, 22:02
Hallo recycle,
die Idee ist nicht schlecht. Bei mir geht es eher so, dass ich KEINEN Wecker stelle. Meine Frau versucht mich bis zu 10 mal zu wecken. Danach kommt sie mit einem nassen, kalten Waschlappen und quält mich.
Hat bis jetzt immer funktioniert. ;-)

Gruß Sternthaler

@M1.R
Super, hätte nie gedacht, dass der Stromverbrauch dann so weit runter geht.

[EDIT]Rechtschreibfehler reduziert.

M1.R
20.12.2007, 22:11
Hallo Sternthaler,

würdest du mir vielleicht trotzdem bei Gelegenheit die Interrupt-Geschichte erklären?

Gruss
M.

Sternthaler
06.01.2008, 03:01
Hallo M1.R,
oh je mi ne.

Ich bin mir relativ sicher, dass du schon alle möglichen Doku's gelesen hast. Ich grüble deshalb schon lange darüber nach, was ich dir da noch wie schreiben kann. Na gut, wollen wir das mal angehen.

Vorweg:
Es gibt unterschiedliche Arten von Interrupts.
1) Durch externe Dinge ausgelöst: Ein Pin am Prozessor ändert die Spannung.
2) Durch interne Dinge ausgelöst: Z.B. ein Timer läuft ab, oder der ADC ist fertig.
3) Durch dein Programm ausgelöst: Nennt sich Softwareinterrupt und lassen wir hier weg.

Alle Interrupts haben folgendes gemeinsam:
A) Ein Programmcode in der Interruptfunktion muss von dir geschrieben werden.
B) Der entsprechende Interrupt muss über das dazugehörende Bit im entsprechenden Steuerbyte zugelassen werden.
C) Der CPU muss generell erlaubt werden Interrupts auszuführen.
D) Die Verarbeitung im Hauptprogramm muss sich auf Interrupts einstellen.

Warum aber so ein Aufwand?
Dein Hauptprogramm im Asuro hat ja folgenden Aufbau:

int main (void)
{
Init ();
while (1)
{
tu_was ();
}
return 0;
}An der Stelle tu_was(); arbeitet dein Programm deine Hauptaufgabe ab und der Asuro ist die ganze Zeit damit beschäftigt endlos etwas zu tun.
Wenn die Aufgabe nun darin besteht, dass der Asuro einfach geradeaus fahren soll, bis ein Taster gedrückt wird um zu wenden, kannst du in der While()-Schleife ja "immer die Tasten abfragen" und dann entsprechend reagieren.
Dieses "immer die Tasten abfragen", nennt man 'pollenden' Betrieb, da du dich in der Schleife selbst drum kümmerst, auf die Tasten zu achten.
Somit sieht dein Programm nun ungefähr so aus:
int main (void)
{
Init ();
while (1)
{
if (PollSwitch () > 0)
fahre_ein_stueck_zurueck_und_drehe ();
else
tu_was ();
}
return 0;
}

Nun kommt der Interrupt ins Spiel,
damit du dich eben nicht um solche Dinge kümmern musst.
Hier bietet es sich natürlich an, das man einen externen Interrupt nutzt, um den durch den Taster verursachten Spannungswechsel zu registrieren.

In der Lib gibt es dazu folgende Funktionen:
A) Die Interruptfunktion:
SIGNAL (SIG_INTERRUPT1)
{
switched = 1;
GICR &= ~(1 << INT1); // Externen Interrupt 1 sperren
}
B) Die Vorbereitung, das der Interrupt am Pin INT1 zugelassen wird:
void StartSwitch (void)
{
SWITCH_OFF; // Port-Bit auf LOW
DDRD &= ~SWITCHES; // Port-Bit SWITCHES als INPUT
MCUCR &= ~((1 << ISC11) | (1 << ISC10)); // Low level erzeugt Interrupt
GICR |= (1 << INT1); // Externen Interrupt 1 zulassen
}
C) Die allgemeine Erlaubnis, dass Interrupts ausgeführt werden dürfen:
void Init (void)
{
...
...
sei ();
}

Bis dahin haben wir nur Arbeit gehabt und noch keinen Nutzen.
Jetzt aber wird es Spannend.

Dein Hauptprogramm ändern wir nun ein kleines bisschen ab:
int main (void)
{
Init ();
switched = 0;
StartSwitch ();
while (1)
{
if (switched == 1)
{
fahre_ein_stueck_zurueck_und_drehe ();
switched = 0;
StartSwitch ();
}
else
tu_was ();
}
return 0;
}Bäh. da kommt ja immer mehr Programmcode zusammen.
Das ist zwar nicht schön, aber es bringt einen entscheidenden Vorteil. Du nutzt jetzt eine Interruptfunktion, die darauf aufpasst, ob ein Taster gedrückt wurde.
Und was ist daran so toll?
Du sparst enorm viel Rechenzeit in deiner while()-Schleife, da du dich dort nicht mehr darum kümmern musst die Information, ob eine Taste gedrückt wurde, zu ermitteln. Du kannst dich in deinem Programm auf die Auswertung dieser Information konzentrieren und hast dann dafür mehr Rechenzeit für die tu_was()-Funktion zur Verfügung.

Was aber, wenn man genau wissen will welche Taste gedrückt wurde?
Hier reicht es nun, dass im Hauptprogramm nur genau einmal die PollSwitch()-Funktion aufgerufen wird. Und zwar dann natürlich, wenn feststeht, dass eine Taste gedrückt wurde.
Also ändern wir wieder ein bisschen:
int main (void)
{
unsigned char taste;

Init ();
switched = 0;
StartSwitch ();
while (1)
{
if (switched == 1)
{
taste = PollSwitch ();
fahre_ein_stueck_zurueck_und_drehe (taste);
switched = 0;
StartSwitch ();
}
else
tu_was ();
}
return 0;
}Hier haben wir nun die Interruptfunktion dazu benutzt, dass uns angezeigt wird, dass irgendeine Taste gedrückt wurde. Nun holen wir wie gewohnt mit der PollSwitch()-Funktion die Info, welche Taste(n) das waren und geben diese Info einfach an unsere fahre_ein_stueck_zurueck_und_drehe()-Funktion weiter. Die kann dann entscheiden, ob nach rechts oder links gedreht wird.
Aber entscheidend ist hier, dass die PollSwitch()-Funktion nicht permanent aufgerufen werden muss.
Das heißt nun einfach 'Interrupt'-Betrieb. (im Gegensatz zum 'pollenden' Betrieb)

Was uns bis jetzt noch fehlt, ist eine Möglichkeit, dass wir in unserem Hauptprogramm die Interruptfunktionalität abschalten können. Denn ist sie einmal aktiv, dann wird man sie so schnell auch nicht wieder los.

Dafür gibt es in der Lib die folgende Funktion:
void StopSwitch (void)
{
GICR &= ~(1 << INT1); // Externen Interrupt 1 sperren
}


Und wo bitte wurde die Variable switched definiert?
- In der Lib gibt es die Datei globals.c
-- Darin wird mit:
volatile int switched;
diese Variable definiert. Hier ist es ganz wichtig, dass dieses 'volatile' angegeben ist. Damit verbieten wir dem Compiler, dass er diese Variable in einem CPU-Register speichert. Sie muss vom Compiler im RAM gespeichert werden.
Das ist immer dann notwendig, wenn der Inhalt sowohl in der Interruptfunktion als auch außerhalb davon benutzt/geändert werden soll. Und hier wollen wir ja den Inhalt dazu benutzten, dass dein Hauptprogramm mitgeteilt bekommt das etwas passiert ist.

Und was passiert nun eigentlich?
- Dein Hauptprogramm macht alle technische Vorbereitungen:
-- sei(); <== Interrupts grundsätzlich erlauben
-- GICR |= (1 << INT1); <== Steuerbit INT1 im Steuerregister GICR setzen

- Dein Hauptprogramm macht logische Vorbereitungen:
-- switched = 0; <== Information initialisieren

- Dein Hauptprogramm dreht sich im Kreis und arbeitet:
-- tu_was();

- Nun kommt die Wand und drückt unerwartet eine Taste
-- INTERRUPT-FUNKTION setzt nur switched = 1;

- Dein Hauptprogramm berücksichtigt diese Information und wertet sie aus:
-- if (switched == 1)

- Und zu guter letzt, initialisiert dein Hauptprogramm eine weitere Interrupt-Behandlung:
-- if (switched == 1)
-- {
---- switched = 0;
---- StartSwitch ();
Dies ist hier notwendig, da die Interruptfunktion nicht nur die Variable switched auf 1 setzt, sondern hier auch noch den Punkt B) rückgängig macht und somit einen weiteren Tasteninterrupt nicht mehr zulässt.
Dies ist hier sinnvoll, da der Interrupt sonst so lange permanent aufgerufen wird, bis alle Tasten losgelassen wurden. Das aber wiederum dauert aus CPU-Sicht sehr, sehr lange bis die Motoren den Asuro endlich von der Wand wegbewegt haben. Und das wiederum würde uns nur kostbare Rechenzeit stehlen.


So, da ich nicht weiß, ob dir diese Beschreibung überhaupt weitergeholfen hat, höre ich hier erst einmal auf. (Ist doch etwas aufwändig)
Wenn dir das aber helfen konnte, dann melde dich mal, und ich werde auch noch zu internen Interrupts (ADC, Timer) etwas schreiben.

Gruß, und natürlich ein 'Schönes neues Jahr' wünscht dir
Sternthaler

inka
06.01.2008, 11:21
hi sternthaler,
auch ich habe auf deine antwort zu den interrupts gespannt gewartet...wie immer sehr lehrreich, kurz & prägnant, mit praktischen - asuro - beispielen ergänzt...danke...

Sternthaler
06.01.2008, 12:25
Guten Morgen zusammen.


.. kurz & prägnant, ..
Ich dachte eher, das dies ein Buch wird. :P
Wenn ihr noch mehr haben wollt, sagt Bescheid. Ich hätte da z.B. die Verschachtelung von Interrupts auf Lager. So á la Timer-Interrupt startet ADC nachdem Taster-Interrupt ausgelöst wurde um ADC-Interrupt zu provozieren. :cheesy:

Gruß Sternthaler

damaltor
06.01.2008, 13:49
deine geduldist bewundernswert. ohne diskussion. wir sollten mal beginnen, die lehrreichsten beitrage z uprämieren :D

M1.R
06.01.2008, 21:04
Hallo Sternthaler,
dir auch ein Gutes Neues!
Vielen , vielen Dank für deine ausführliche Erläuterung!
Meine Hoffnung steigt, dass ich die Interrupts doch noch eines Tages kapieren werde.
Da im Moment mein PC krank ist, kann ich mich vorläufig leider noch nicht näher damit befassen.:(
(Ohne Ausprobieren hilft mir alle Theorie nicht wirklich weiter)
Dafür bin ich zur Zeit am Zusammenbasteln meines Dosen-ASUROs "DORO M2.R". Vor ein paar Tagen hat er seinen Mechanik-Test bestanden:
http://de.youtube.com/watch?v=qkaaG6ElcVE
Jetzt ist auch die Platine fertig gelötet und der Selbsttest scheint zu funktionieren, soweit ich das ohne IR-Kommunikation beurteilen kann.:)
Gruss
M.

@damaltor

deine geduldist bewundernswert. ohne diskussion. wir sollten mal beginnen, die lehrreichsten beitrage z uprämieren :Ddu bist auch sehr geduldig mit uns Doofies - von dir habe ich schon sehr viel gelernt!
Gruss
M.

damaltor
07.01.2008, 02:43
:D danke... aber irgendwann haben wir alle mal klein angefangen...

M1.R
25.01.2008, 20:39
Hallo Sternthaler,

kurzer Zwischenbericht zu den Interrupts:
das Prinzip habe ich jetzt (glaube ich) verstanden, zumindest bei der Tastergeschichte :)

Nach unserem Roboter-Wettbewerb (http://roboterclub-freiburg.pbwiki.com/) werde ich mich weiter damit beschäftigen und versuchen mit den internen Interrupts klarzukommen. Dann melde ich mich wieder.

Also nochmal vielen Dank!

Gruss
M.

oberallgeier
25.01.2008, 22:36
Hallo Sternthaler,

... hätte da z.B. die Verschachtelung von Interrupts auf Lager... Jetzt schlag mich bitte nicht, wenn ich als Newbie mit Besserwisserischer i-Tüpfel-Reiterei daherkomme. Interrupts sind ja beim AVR hardwaremässig so implementiert, dass sie immer sequentiell abgearbeitet werden. Schachteln dagegen erinnern mich an diese Babutschkas - die russischen Holzpuppen. In einer ist dann wieder eine, die enthält eine, die enthält eine - - - so sehe ich "verschachtelt".

AVR-Interrupts sind ja sehr brav und geradeaus - da wird IMMER erst eine Arbeit fertig gemacht. Das ist doch ein wichtiger, kleiner und wirklich feiner (im Sinne von prächtig, nicht von "dünn") Unterschied.

Daher muß ja Dein Beispiel
... So á la Timer-Interrupt startet ADC nachdem Taster-Interrupt ausgelöst wurde um ADC-Interrupt zu provozieren ... auch sequentiell abgearbeitet werden, oder irre ich mich?

Verschachtelte Interrupts stelle ich mir so vor - mit wirklich verschachteltem Ablauf - nicht nur mit verschachteltem Request:
[Interrupt 1 fängt an und ..
[Interrupt 2 fängt an und ...
[Interrupt 3 fängt an ... fertig
]
.. I2 macht weiter ... fertig
]
.. I1 ... fertig
] . . und ich glaube ziemlich sicher, ich hätte das beim seligen Z80 so gehabt. Ich glaube, bayerische Programmierer nennen so eine Interrupt-Organisation statt verschachtelt "verschandelt".

damaltor
26.01.2008, 00:46
Hallo Sternthaler,

... hätte da z.B. die Verschachtelung von Interrupts auf Lager... Jetzt schlag mich bitte nicht, wenn ich als Newbie mit Besserwisserischer i-Tüpfel-Reiterei daherkomme. Interrupts sind ja beim AVR hardwaremässig so implementiert, dass sie immer sequentiell abgearbeitet werden. Schachteln dagegen erinnern mich an diese Babutschkas - die russischen Holzpuppen. In einer ist dann wieder eine, die enthält eine, die enthält eine - - - so sehe ich "verschachtelt".

AVR-Interrupts sind ja sehr brav und geradeaus - da wird IMMER erst eine Arbeit fertig gemacht. Das ist doch ein wichtiger, kleiner und wirklich feiner (im Sinne von prächtig, nicht von "dünn") Unterschied.


das stimmt nicht ganz - wenn man das interrupt enable bit manuell wieder setzt bzw sei ausführt in der interruptroutine, können interrupts durchaus geschachtelt werden. allerdings erst ab diesem moment.

oberallgeier
26.01.2008, 01:00
Ach Du liebes Bisschen - klar, wenn ich mich recht erinnere, bin ich schon mal drüber gestolpert - aber jetzt bleibt es hoffentlich in meinem Kopf :
The user software can write logic one to the I-bit to enable nested interrupts. All enabled interrupts can then interrupt the current interrupt routine.. . . also doch vorlaut oder -eilig.

damaltor
27.01.2008, 01:27
aber du hast in sofern recht, dass die interrupts normalerweise reihenweise abgearbeitet werden. :)

Sternthaler
28.01.2008, 19:11
Hallo ihr Verschachtelten ;-)

Ich muss euch leider enttäuschen. Das es beim AVR keine Schachteln gibt, liegt nur daran, dass wir die Interrupt-Funktion immer mit:
- SIGNAL (Interrupt-Name)
bzw.:
- ISR (Interrupt-Name) in neueren Compilerversionen
angeben.

Es liegt genau an diesem Namen, bzw. bei ISR an der Parameterform, dass die Interrupts im AVR eben nicht geschachtelt werden.
Es gibt nämlich noch die Form:
- ISR(XXX_vect, ISR_NOBLOCK)
und auch eine Schreibweise beim alten Compilern. (Im Moment ist mir der Name abhanden gekommen, da ich da selber auch noch nichts mit gemacht habe.)

Hier wird erzwungen, dass Interrupts eben doch geschachtelt aufgerufen werden können. Natürlich unter Einhaltung der Interrupt-Prioritäten. Nur diese sind Hardwareabhängig.

Insofern ist also alle möglich!
- Schachteln vermeiden ist Default.
- Schachteln erzwingen ist Kunst.
- Schachteln per Bit-Fummelei ist Wissen.


Aber das ist es gar nicht was ich euch noch mitteilen möchte. :cheesy:

Ich möchte euch davon berichten, dass die Softwarestruktur sich um die Aufrufe zum Starten von Funktionen kümmert, so dass der Timer-Interrupt z.B. etwas macht, dass ein AD-Wandler angestoßen wird. Der Timer ist also schon lange fertig, aber die AD-Wandler-Hardware ackert und wird in Zukunft einen Interrupt auslösen. Und dass dann noch in Kombination mit einem Tasten-Interrupt.
Aber da muss ich erst noch die Zeit zu finden um euch mit weiterem Input zu 'beglücken'.

In diesem Sinne warten wir auf den nächsten Interrupt. Ich gehe jetzt erst einmal nach Hause. (schon wieder Arbeitszeit, pfui)
Gruß Sternthaler

damaltor
30.01.2008, 14:04
schachtel erzwingen ist einfach... einfach das I-bit setzen in der interruptroutine. =)

oberallgeier
30.01.2008, 15:20
Das ist ja auch die Empfehlung von Atmel:

https://www.roboternetz.de/phpBB2/zeigebeitrag.php?p=346944#346944

ts230
31.01.2008, 16:29
Kann mir jmd sagen wo ich das void set_sleep_mode (uint8 t mode)
void sleep_mode (void)
einsetzen muss??Ich möchte das genze über ein 4x16 lcd und 8 tastern am I²C steuern.

inka
31.01.2008, 16:50
ein bitte und ein danke wären schon mal nicht schlecht, vom gruß mal ganz abgesehen...

ts230
31.01.2008, 17:59
@inka
sorry,habe ich ganz vergessen.Ach:danke für das tolle Projekt.
Kann mir bitte jmd. sagen wo ich das void set_sleep_mode (uint8 t mode)
void sleep_mode (void)
einsetzen muss??Ich möchte das genze über ein 4x16 lcd und 8 tastern am I²C steuern.Ich habe vor 10 min. draufgeflasht und ich habe probleme bei der einstellung.Knn mir bitte jemand helfen?

ts230
02.03.2008, 11:36
Problem gelöst!zwei Taster waren Schrott!

M1.R
01.04.2008, 23:03
So, da ich nicht weiß, ob dir diese Beschreibung überhaupt weitergeholfen hat, höre ich hier erst einmal auf. (Ist doch etwas aufwändig)
Wenn dir das aber helfen konnte, dann melde dich mal, und ich werde auch noch zu internen Interrupts (ADC, Timer) etwas schreiben.

Hallo Sternthaler,
deine Taster-Interrupt-Erklärung ist super!:)
gilt dein Angebot noch? Ich wäre dir sehr dankbar, wenn du mir die anderen Interrupts auch noch erklären würdest.

Ich habe jetzt mal zu Übungszwecken, um die Zusammenhänge zu begreifen, versucht ein kleines Programm zu schreiben, in das ich alle nötigen Funktionen etc. direkt eingebunden habe. Damit lässt sich die StatusLED schalten und der Tasterinterrupt geht auch.
Mein nächstes Ziel ist nun, nach der Tastergeschichte mit Hilfe von Timer2-Interrupt eine Wartezeit einzubauen, nach der die LED gelb leuchtet.
Kann es sein, das ich hierfür die "SIGNAL (SIG_OVERFLOW2)" ändern müsste?
Leider ist mir diese Funktion ziemlich unverständlich.

//----------------------------------------------------------------
//bitschiebereien und interrupts

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

//----------------------------------------------------------------
// variablen in global.c definiert:
volatile int switched; //für tasterinterrupt

volatile unsigned char count36kHz; //für timer2 interrupt
volatile unsigned long timebase; //für timer2 interrupt

//----------------------------------------------------------------
// eigene variablen
volatile int zeit; //für timer2 interrupt
long i; //fürs warten
//----------------------------------------------------------------
// Funktionen
//----------------------------------------------------------------
// warten
void warten(void)
{
for(i=0;i<500000;i++);
}
//----------------------------------------------------------------
//Interruptfunktion taster in asuro.c
SIGNAL (SIG_INTERRUPT1)
{
switched = 1;
GICR &= ~(1 << INT1); // Externen Interrupt 1 sperren
}
//----------------------------------------------------------------
//taster interrupt einschalten
void StartSwitch (void)
{
DDRD &= ~(1 << PD3); // PD3 als Eingang
PORTD &= ~(1 << PD3); // PD3 auf LOW

MCUCR &= ~((1 << ISC11) | (1 << ISC10)); // Low level erzeugt Interrupt
GICR |= (1 << INT1); // Externen Interrupt 1 zulassen
}
//----------------------------------------------------------------
//taster interrupt ausschalten
void StopSwitch (void)
{
GICR &= ~(1 << INT1); // Externen Interrupt 1 sperren
}
//----------------------------------------------------------------
//timer2 interrupt einschalten
void TimerInit (void) // teil der init in asuro.c
/*
Timer2, zum Betrieb mit der seriellen Schnittstelle, fuer die
IR-Kommunikation auf 36 kHz eingestellt.
*/
{
TCCR2 = (1 << WGM20) | (1 << WGM21) | (1 << COM20) | (1 << COM21) | (1 << CS20);
OCR2 = 0x91; // duty cycle fuer 36kHz
TIMSK |= (1 << TOIE2); // 36kHz counter
}
//----------------------------------------------------------------
//Interruptfunktion timer2 in asuro.c
SIGNAL (SIG_OVERFLOW2)
{
TCNT2 += 0x25;
count36kHz ++;
if (!count36kHz)
timebase ++;
#ifdef RC5_AVAILABLE
if (enableRC5 && !(count36kHz % 8))
IsrRC5(); // wird alle 222.2us aufgerufen
#endif
}

//----------------------------------------------------------------
//----------------------------------------------------------------

int main(void)
{
sei (); //Interrupts erlauben
//cli (); //Interrupts verbieten

//led vorbereiten
DDRB |= (1<<PB0); // Datenrichtungsregister für LED-grün auf Ausgang
DDRD |= (1<<PD2); // für rot auf ausgang

//----------------------------------------------------------------
// Ampel
// grün
PORTB |= (1<<PB0); // Ausgang für grün schalten
warten();
// gelb
PORTD |= (1<<PD2); // rot dazu einschalten
warten();
// rot
PORTB &= ~(1<<PB0); // grün ausschalten
warten();
// aus
PORTD &= ~(1<<PD2); // rot auschalten
// Ampel Ende
//----------------------------------------------------------------

warten();
warten();

//----------------------------------------------------------------
// Taster Interrupt
switched = 0;
StartSwitch ();
while (switched == 0) // wenn kein taster gedrückt ist rot leuchten
{
PORTB &= ~(1<<PB0); // grün ausschalten
PORTD |= (1<<PD2); // rot einschalten
}

PORTB |= (1<<PB0); // grün einschalten
PORTD &= ~(1<<PD2); // rot auschalten

//switched = 0;
//StartSwitch ();

StopSwitch ();
// Taster Interrupt Ende
//----------------------------------------------------------------

// und jetzt nach 1sek led gelb mit timer2 interrupt ?????????
//----------------------------------------------------------------
// timer2 Interrupt
// zeit = 1;
TimerInit ();
if ( zeit == 1)
{
PORTB |= (1<<PB0); // grün einschalten
PORTD |= (1<<PD2); // rot einschalten
}
//----------------------------------------------------------------

while(1);
return(0);
}

Schöne Grüsse
M.

Sternthaler
04.04.2008, 02:11
Hallo M1R,
schön, dass dir die Beschreibung gefallen hat.
Ja, das Angebot steht noch, und es kann sich nur noch um Monate ;-) handeln, bis ich dann da weiter mache.

Hier muss ich erst mal deine sehr übersichtliche und einheitliche Schreibweise vom Code loben. Ich bin da immer extrem pingelig, aber bei deinem main()-Teil gab es (fast) nichts, was ich während des Lesens umformatiert habe. Echt Klasse.


2 Anmerkungen zu deinem main():

1:
Folgenden Code kannst du noch ändern, um besser zu sehen, dass das Hauptprogramm nur etwas tun muss, wenn der Interrupt etwas ausgelöst hat:

StartSwitch ();
while (switched == 0) // wenn kein taster gedrückt ist rot leuchten
{
PORTB &= ~(1<<PB0); // grün ausschalten
PORTD |= (1<<PD2); // rot einschalten
}

umbauen zu:

// rot hier nur einmal einschalten
PORTB &= ~(1<<PB0); // grün ausschalten
PORTD |= (1<<PD2); // rot einschalten

StartSwitch ();
while (switched == 0) // wenn kein taster gedrückt ...
{
// ... wird hier normalerweise dein Hauptprogramm ackern und
// heftigste Berechnungen und Motorsteuerungen machen.
// Allerdings darf hier auch nicht zu viel Zeit zur Bearbeitung
// benötigt werden, da diese Code-Konstruktion ja sonst nicht
// schnell genug wieder zum while() kommt um zu reagieren.
}
// ... und jetzt erst muss der Motor gestoppt
// der Rückwärtsgang eingelegt,
// und die getroffene Wand umfahren werden.

Das ist jetzt für deinen Programmablauf zwar nur Haarspalterei, aber genau darum geht es ja bei den Interrupts. Es soll eben nichts 'überflüssiges' während der while()-Arbeitsschleife gemacht werden, da ja der Interrupt bzw. die Information in der Variablen switched nur dann eine Reaktion in deinem Hauptprogramm erzeugen soll, wenn der Interrupt etwas zu sagen hatte. (switched == 1)


2:
Erst einmal liegst du vollkommen richtig, dass es mit der Interruptfunktion "SIGNAL (SIG_OVERFLOW2)" zu tun hat.
Eigentlich ist es nicht unbedingt notwendig dort etwas zu ändern, da 'im Prinzip' schon alles vorhanden ist, aber zum Verständnis mal folgendes:

Annahme von mir: Deine Variable zeit sieht mir danach aus, dass du sie als Sekundenzähler nutzen willst.
==> if (zeit == 1) // und jetzt nach 1 sek ...
Also muss sich der Timer2-Interrupt um diese Variable kümmern. (Du hast sie mit "volatile int zeit;" schon richtig angelegt.)

So ist es bei dir angegeben:
//----------------------------------------------------------------
//Interruptfunktion timer2 in asuro.c
SIGNAL (SIG_OVERFLOW2)
{
TCNT2 += 0x25;
count36kHz ++;
if (!count36kHz)
timebase ++;
#ifdef RC5_AVAILABLE
if (enableRC5 && !(count36kHz % 8))
IsrRC5(); // wird alle 222.2us aufgerufen
#endif
}Den Teil zwischen "#ifdef RC5_AVAILABLE" und "#endif" ignorieren wir hier komplett. m.a.r.v.i.n hat hierzu eine tolle Beschreibung in der HTML-Doku zur Lib direkt auf der Startseite unter dem Punkt "Neue Projekte" geschrieben.

Es bleibt übrig:
//----------------------------------------------------------------
//Interruptfunktion timer2 in asuro.c
SIGNAL (SIG_OVERFLOW2)
{
TCNT2 += 0x25;
count36kHz ++;
if (!count36kHz)
timebase ++;
}
Nun noch mal überlegen. Wann wird diese Funktion überhaupt aufgerufen?
Ja genau. Immer dann wenn der Timer2 eine bestimmte Zeit gearbeitet hat.
Die Vorgabe zur Initialisierung vom Timer2 hast du ja schon gefunden und als eigene Funktion in TimerInit() eingebaut.
Und wann ist "eine bestimmte Zeit" nun genau?

Achtung: Es wird nun kompliziert, aber die Initialisierung ist hier das A und O.
Also mal die Bit genauer betrachten, die in den Registern gesetzt werden:

- Register TCCR2
-- WGM20 und WGM21 setzen den Timer in den "Fast PWM"-Mode
-- COM20 und COM21 setzen im "Fast PWM"-Mode einen 'Output Compare Mode'
-- auf 'Set OC2 on Compare Match, clear OC2 at TOP
-- CS20 setzt einen Takt-Vorteiler auf 1 (8MHz / 1 = 8MHz-Takt)

Für unsere Zeitfunktion ist es nun wichtig zu 'wissen' (Doku zur CPU), dass der 8-Bit-Zähler TCNT2 so initialisiert ist, dass er mit dem Takt von 8MHz immer von 0 bis 255 hochzählt und dann einfach wieder neu bei 0 anfängt.
Außerdem wird noch, jetzt aufpassen, unser gewünschte Interrupt immer 'vorbereitet', wenn der Zähler bei 255 ankommt.
Um diesen 'vorbereiteten' Interrupt auch tatsächlich zu erhalten, muss noch im:
- Register TIMSK
-- TOIE2 gesetzt werden.


Jetzt können wir mal rechnen, wie lang unsere "bestimmte Zeit" nun eigentlich ist. (Teil 1, da noch nicht alles richtig sein wird)
-> 8MHz vom Quarz geteilt durch Takt-Vorteiler 1 geteilt durch 256 (8-Bit-Zähler) sind nun
-> 8000000Hz / 1 / 256 = 31250Hz <== Noch nicht 32kHz.
-> Die "bestimmte Zeit" ist dann 1 / 31250Hz = 0,000032Sekunden

Nach (ungefähr) dieser Zeit bekommen wir somit den ersten Interrupt.

Im Interrupt aber passiert nun als aller erstes folgendes:
-- TCNT2 += 0x25; // 0x25 sind dezimal 37
Hier wird der Zähler TCNT2 'einfach mal zum Spaß' um 37 Werte größer gemacht.
Da der Interrupt ja immer dann kommt, wenn der Zähler TCNT2 aber gerade bei 255 ist, und schon wieder zur 0 umschlagen möchte, heißt dies, dass der Zähler eigentlich nur von 37 bis 255 läuft.
Tut er aber auch nicht, da das "TCNT2 += 0x25;" schon 3 Takte kostet, und somit von 37 – diese 3 Takte bis 255 läuft.
Und warum 3 Takte und warum -?
- Auslesen, addieren und wieder schreiben vom Register TCNT2 kosten jeweils einen Takt.
- In Wirklichkeit läuft die Zeit ja weiter bevor wir mit der Registermanipulation fertig sind.

Jetzt ein neues 2.tes rechnen:
-> 8000000Hz / (256 - (37-3)) = 8000000Hz / 222 = 36036,...Hz <== Da sind unsere guten 36kHz


Uff, jetzt geht es einfach weiter:
36036Hz sind in Sekunden: 1 / 36036Hz = 0,00002775... Sekunden
-- count36kHz ++; zählt also in dieser Zeit um 1 weiter.


Nun müssen wir nur noch deine ursprüngliche Frage beantworten.
Was muss im Interrupt gemacht werden, damit wir dort einen Zähler bekommen, der genau jede Sekunde um 1 weiterzählt?

Wir müssen nur eine bestimmte Anzahl von Interrupts abwarten, bis wir deine Variablen zeit um 1 hochzählen können.
Diese Anzahl ist jetzt leicht auszurechnen:
1 Sekunde (wollen wir haben) durch 0,00002775... Sekunden (Unsere "bestimmte Zeit" vom Interrupt)
-> 1 Sekunde / 0,00002775... Sekunden = 36036
Upps, ist ja die Frequenz von 36kHz. Soll ja auch so sein, da Hz nun mal "Takte pro Sekunde" sind ;-)

Also im Interrupt nun:

//----------------------------------------------------------------
//Interruptfunktion timer2 in asuro.c
SIGNAL (SIG_OVERFLOW2)
{
static int teiler = 0; // static ist dafuer da, dass der Variablen-
// inhalt nicht verloren geht, wenn die
// Funktion beendet wird.

TCNT2 += 0x25;
count36kHz ++;
if (!count36kHz)
timebase ++;

teiler ++;
if (teiler == 36036)
{
teiler = 0;
zeit ++;
}

#ifdef RC5_AVAILABLE
if (enableRC5 && !(count36kHz % 8))
IsrRC5(); // wird alle 222.2us aufgerufen
#endif
}


So, damit glaube ich, dass auch der Timer-Interrupt nicht mehr so im dunkeln liegt.
Auch hier hat es sich ja nun heraus gestellt, dass das wie bei den Tastern erklärte Prinzip vorhanden ist:
A) Ein Programmcode in der Interruptfunktion muss von dir geschrieben werden.
B) Der entsprechende Interrupt muss über das dazugehörende Bit im entsprechenden Steuerbyte zugelassen werden.
C) Der CPU muss generell erlaubt werden Interrupts auszuführen.
D) Die Verarbeitung im Hauptprogramm muss sich auf Interrupts einstellen.

A) ist nun die Bearbeitung der Variablen teiler und zeit.
B) war hier natürlich der einfachste Teil mit dem "TIMSK |= (1 << TOIE2);"
Kompliziert war es ja nur an der Stelle zum Initialisieren des Timer 2 und der von waste erfundenen Manipulation am TCNT2-Register im Interrupt selbst ;-).
Ist schon stark, was 4 Zeilen Code alles anrichten können!
C) hast du in deinem main() zu Anfang erledigt.
D) musst du allerdings noch etwas umbauen.
Hinter deinem Aufruf von TimerInit() fragst du ja sofort die Interrupt-Information zeit ab.
Natürlich ist da dann noch keine Sekunde vergangen. Somit wirst du dann die LED nicht auf gelb setzen sondern sofort zum Ende deines Programms kommen.
Hier hilft zum testen auch erst einmal ein schnuckeliges while():

// und jetzt nach 1sek led gelb mit timer2 interrupt ?????????
//----------------------------------------------------------------
// timer2 Interrupt
zeit = 0;
TimerInit ();
while (zeit != 1) // warten, bis die zeit bei 1 angekommen ist
;
PORTB |= (1<<PB0); // grün einschalten
PORTD |= (1<<PD2); // rot einschalten
//----------------------------------------------------------------


Ich hoffe, du konntest meiner Schwafelei folgen, und das dein 2-Zeiler
Kann es sein, das ich hierfür die "SIGNAL (SIG_OVERFLOW2)" ändern müsste?
Leider ist mir diese Funktion ziemlich unverständlich. nun nie wieder von dir ausgesprochen werden muss ;-)
Klar, wenn noch (oder noch mehr?) Fragen sind, nur zu.

Gruß Sternthaler

M1.R
04.04.2008, 20:04
Hallo Sternthaler,

vielen, vielen Dank schonmal! O:)

Wie immer gefällt mir deine "Schwafelei" (= anschauliche und unterhaltsame Art, komplizierte Dinge zu erklären) sehr gut.

Es kann ein paar Tage dauern, bis ich dazukomme mich weiter damit zu befassen. Dann melde ich mich wieder.

Schöne Grüsse
M.

Sternthaler
06.04.2008, 03:09
Hallo Biene Maja (hier bekannt als M1.R),
"Honig, Honig, Honig" deine Übersetzung von 'Schwafelei'. \:D/

Das ist gut wenn du dir Zeit nimmst, denn es ist tatsächlich nicht so einfach der Initialisierung und dieser 'undurchsichtigen' Zeile mit dem += 0x25 im Interrupt zu folgen.

Hier noch ein Hinweis zu dem von mir nicht beschriebenem:
'Output Compare Mode' setzen auf 'Set OC2 on Compare Match, clear OC2 at TOP'
Dies ist nicht relevant für unsere Zeitbetrachtung. Es ist aber sehr wichtig für die Funktionsweise der IR-Kommunikation.

Die eine Seite der Sende-IR-LED hängt ja am Port-Pin PB3. Dieser ist durch die Initialisierung in Init() mit dem Register-Bit OC2 verbunden.
Wenn also dieser ominöse 'Output Compare Mode' ein 'Set OC2' und 'Clear OC2' macht, heisst dies, dass an diesem Port-Pin mal +5V (Set) und mal 0V (Clear) anliegen. Somit wird das Licht an der Sende-LED genau mit den 36kHz ein- und ausgeschaltet. Das passiert aber auch nur dann, wenn überhaupt Strom durch die LED fliesst. Dies kann nur bei 'Set OC2' und 0-Datenbit am Sendeausgang Tx passieren.
Somit werden 0-Datenbits als Licht mit 36kHz getaktet gesendet.
1-Datenbits hingegen bleiben im Dunkeln verborgen.
--> Und genau so möchte der Empfänger die Daten haben. <-- 'Tolle Kiste' ;-)

Des weiteren möchte der Empfänger am liebsten haben, dass dieses ein- und ausschalten so passiert, das 'ein' und 'aus' (möglichst) gleich lang sind.
Dafür zuständig ist noch das
--- OCR2 = 0x91; // duty cycle fuer 36kHz <-- Schlechter Kommentar, sollte eher 'duty cycle 50% bei 36kHz' lauten.

- clear OC2 at TOP <-- TOP bedeutet hier, dass TCNT2 bei 255 angekommen ist.
- Set OC2 on Compare Match <-- bedeutet hier, dass TCNT2 bei OCR2 angekommen ist.

0x91 sind ja 145
'Set OC2' also bei TCNT2 von 145 bis 255. Sind (grob gerechnet) 110 Takte
Von 255 über 0; plus die 37 im Interrupt und dann wieder bis 145 sind nun auch (grob gerechnet) 110 Takte.
Also 110 Takte Licht an und auch 110 Takte Licht aus. <-- Schon wieder eine 'Tolle Kiste' ;-), da dies die 50% sind.


Auch hier: 'Eile mit Weile' beim lesen und verstehen.
Gruß Sternthaler