Hallo,
ich hätte da mal eine Verständnisfrage:
wenn ich meinen Controller mit 4 MHz getaktet habe, könnte ich doch
normalerweise eine Funktion "sekunde" aufstellen, in der ich eine for
Schleife einfach hochzählen lasse, und bei einem beliebigen Wert
abbreche.
Nachdem ein ADD Befehl einen Takt benötigt, müsste ich dann mal schnell
bis 4 Millionen hochzählen lassen, um eine Sekunde zu generieren.
Ich habe das ausprobiert und gegen meinen Erwartungen muss ich
allerdings bis 28,5 Millionen hochzählen lassen. Nachdem ich da mit C
mache, hätte ich erwartet, dass ich eher mehr als 4 Millionen Takte für
eine Sekunde brauche, da die for Schleife da siche auch was "raubt".
Wäre echt dankbar für antworten.
void sekunde()
{
uint16_t j;
uint16_t i;
for (j=0;j<500;j++){
for (i=0;i<57000;i++){
}}}
Was mich eher wundert: Dass du da überhaupt einen Zeitverzug messen kannst :-) Wahrscheinlich hast du den Optimizer nicht eingeschaltet. Der optimiert dir die komplette Schleifenstruktur weg, da sie ja offensichtlich keinen Nutzen hat ausser Zeit zu verbraten. Und Compiler sind darauf gedrillt möglichst Rechenzeit einzusparen. Aber im Grundsatz ist dein Gedankengang schon richtig. Um rauszufinden warum du bis 28.5 Millionen zählen musst, müsste man sich jetzt mal das Assembler Listing des vom Compiler generierten Codes ansehen. Wahrscheinlich hat da irgendeine Teiloptimierung zugeschlagen.
Sowas in der Art habe ich mir gedacht. Wenn ich z.B den Code ein wenig
abändere, dann überspringt er mir gleich die 2. for Schleife:
void sekunde()
{
uint16_t j;
uint16_t i;
for (j=0;j<500;j++){
for (i=0;i<57000;i++);
}}
Sowie Karl Heinz meinte wird sicherlich eine Teiloptimierung
vorgenommen. Deine beiden Variablen solltest du mit einem volatile
versehen um eine Optimierung auszuschliessen.
Ich wuerde die Makrofunktion delay_ms benutzen z.B. so:
#include delay.h
void waits(u8 waittime)
{
volatile u8 waittime=0;
for(waittime=0;waitime<1000;waittime++){
delay_ms(1);
}
}
Die Funktion macht nix anderes als die Rechenzeit zuverschwenden.
Hm, die Uhrzeit macht einen Betriebsblind eher so:
1 | #include delay.h
|
2 | |
3 | void waits(u8 waittime) |
4 | {
|
5 | volatile u8 i=0; |
6 | for(i=0;i<waittime;i++){ |
7 | delay_ms(1); |
8 | }
|
9 | }
|
Ok, danke...so funktioniert die Uhr ganz gut, eine Frage hätte ich
allerdings noch bezüglich der Anzeige:
Immer wenn die Zahlen für std,min oder sec nur einzahlig sind, dann
stellt das Display mir keine 0 dar, obwohl ich bei sprintf den Parameter
"%2d" reingeschtieben habe.
Hier mal der ganze code:
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <lcd.h>
#include <lcd.c>
#include <util/delay.h>
uint8_t a=23;
uint8_t b=19;
uint8_t c=50;
int sec=0;
int min;
int hr;
char s[4];
char m[4];
char h[4];
void waits(uint16_t waittime)
{
volatile uint16_t i=0;
for(i=0;i<waittime;i++){
_delay_ms(1);
}
}
int main(void)
{
lcd_init(LCD_DISP_ON);
while (1){
for (hr=a;hr<24;hr++){
for (min=b;min<60;min++){
for (sec=c;sec<60;sec++)
{
a=0;b=0;c=0;
waits(1000);
sprintf(h,"%2d",hr);sprintf(m,"%2d",min);sprintf(s,"%2d",sec);
lcd_home();
lcd_puts(h);lcd_puts(":");lcd_puts(m);lcd_puts(":");lcd_puts(s);
}}}
}}
Es ist schon eine Schande, dass die Controller keine internen Timer haben, die nur darauf warten würden, Interrupts auslösen zu dürfen, in denen man taktgenau die Zeit hochzählen könnte... ;-) ...
Mir geht es hierbei mehr um das Verständnis und nicht um die optimale Lösung eine eigene Uhr zu bauen.
> obwohl ich bei sprintf den Parameter "%2d"
%2d heist nur, dass du die Zahl in einem Feld der
Breite 2 dargestellt haben willst. Wenn du noch
führende Nullen haben willst, musst du die anfordern
%02d
> sprintf(h,"%2d",hr);sprintf(m,"%2d",min);sprintf(s,"%2d",sec);
Warum so kompliziert mit 3 mal sprintf.
Einer tuts auch:
sprintf( buffer, "%02d:%02d:%02d", hr, min, sec );
lcd_home();
lcd_puts( buffer );
(buffer natürlich entsprechend gross dimensioniern)
Die Sache mit dem Timer ignoriere ich mal und nehme
das einfach mal als Übungsaufgabe:
Deine Uhr hat einen Nachteil:
Ist es mit diesem Code nur schwer möglich, sie von aussen
zu stellen.
Kannst du das Ganze auch so machen, dass zu einer
jetzigen Uhrzeit (ausgedrückt in 3 Variablen: Stunden, Minuten,
Sekunden) genau 1 Sekunde addiert wird und sich dabei Stunden
und Minuten richtig verhalten?
Anstatt 3 ineinandergeschachtelter Schleifen hast du nur mehr eine
while( 1 ) {
1 Sekunde zu Stunden/Minuten/Sekunden addieren
Uhrzeit ausgeben
1 Sekunde warten
}
Joachim wrote: > Mir geht es hierbei mehr um das Verständnis und nicht um die optimale > Lösung eine eigene Uhr zu bauen. Das ist gut. Dann fang mal damit an, zu verstehen, dass Dein restlicher Code, besonders die LCD-Ausgabe auch Rechenzeit braucht, also viele Takte dauert. Diese musst Du ermitteln und von Deiner Warteschleife abziehen. Und bei jeder Programmänderung müsstest Du das wieder neu durchrechnen. Da halte ich es für einfacher, zu verstehen, dass ein Timer (evtl. mit Vorteiler) die Takte bis zum nächsten "Termin" im Hintergrund zählen kann, während sich das Hauptprogramm um andere Dinge wie LCD kümmern kann. "Verstehen" kannst Du den Mikrocontroller (den kleinen Mikrocontroller, der ohne Betriebssystem läuft) sowiso nur, wenn Du Dich auf sie ASM-Ebene herab lässt. Denn der Controller kann kein C, auch kein BASIC, er kann nur Maschinencode, der 1:1 in ASM ausgedrückt und beschrieben werden kann. Um nicht wieder darauf festgenagelt zu werden, diese Aussage gilt nicht für PCs oder Hochleistungscontroller mit Betriebssystemen. ...
> Die Sache mit dem Timer ignoriere ich mal und nehme > das einfach mal als Übungsaufgabe: Sorry, ich hatte nicht mitbekommen, dass es hier um des Verständnis von elementarem C geht, ich nahm fälschlicherweise an, es ginge um das Verständnis eines 8-Bit-Mikrocontrollers und vermutete AVR. Daher müsste ich jetzt meinen vorherigen Beitrag zurücknehmen und das Gegenteil behaupten. Mir ist aber eigentlich nicht danach... ;-) ...
Ok, ich verstehe. Jetzt kann ich über externe Interrupts (Taster) die
Uhrzeit vorgeben. Mit 2 Tastern wäre das nicht schwer. Da müsste ich nur
jeweils den Minuten und Stunden den Befehl ++ verpassen. Irgend einen
Vorschlag, wie ich mit nur einem Taster auskommen könnte ?
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <lcd.h>
#include <lcd.c>
#include <util/delay.h>
int sec;
int min=46;
int hr=0;
char buffer[16];
void waits(uint16_t waittime)
{
volatile uint16_t i=0;
for(i=0;i<waittime;i++){
_delay_ms(1);
}
}
int main(void)
{
lcd_init(LCD_DISP_ON);
while (1){
if(sec==60) {sec=0;min++;}
if(min==60) {min=0;hr++;}
if(hr==24) {hr=0;}
sprintf(buffer,"%02d:%02d:%02d",hr,min,sec);
lcd_home();
lcd_puts(buffer);
waits(1000);
sec++;
}
}
> Jetzt kann ich über externe Interrupts (Taster) die > Uhrzeit vorgeben. Es gibt gute Gründe, Taster nicht mit externem Interrupt abzufragen. Mit einem Timer-Interrupt (der nebenbei noch andere Dinge erledigen kann) geht das besser, da mechanische Taster noch entprellt werden müssen. Beitrag "Tasten entprellen - Bulletproof" ...
Hallo, ich beschäftige mich jetzt mit den Interrupts. Im Tutorial habe ich gelesen, dass jeder Interrupt eine Routine besitzt, die dann nacheinander abgearbeitet werden. Wo finde ich diese Routine beim AVR mega8 ? Bei folgendem Programmcode habe ich das Problem, dass sich die Uhr nach einigen Minuten aufhängt. Woran könnte das liegen ? Der Code ist einigermaßen dokumentiert, allerdings sicher nicht schwer verständlich. #include <inttypes.h> #include <avr/io.h> #include <avr/pgmspace.h> #include <lcd.h> #include <lcd.c> #include <avr/interrupt.h> #include <util/delay.h> #include <entprell.c> int sec; int min=11; int hr=23; char buffer[16]; ISR (TIMER0_OVF_vect){ //Einstellmöglichkeit der Minuten über PD2 //PD2 wird 15 mal pro sec abgefragt if(debounce(&PIND, PD2)){ min++; if(sec>59) {sec=0;min++;} if(min>59) {min=0;hr++;} if(hr>23) {hr=0;} sprintf(buffer,"%02d:%02d:%02d",hr,min,sec); lcd_home(); lcd_puts(buffer); } sei(); } ISR(TIMER1_COMPA_vect) //hier wird der Zahlenwert der abgearbeitet { TCNT1=0; //Compare Register löschen if(sec>59) {sec=0;min++;} if(min>59) {min=0;hr++;} if(hr>23) {hr=0;} sprintf(buffer,"%02d:%02d:%02d",hr,min,sec); lcd_home(); lcd_puts(buffer); sec++; } int main(void) //Initialisierung der Timer0(clk/1024) //und Timer1(clk/256) { DDRD=0x00; TCCR1B|=(1<<CS12);TIMSK|=(1<<OCIE1A); TCCR0|=(1<<CS00)|(1<<CS02); TIMSK|=(1<<TOIE0); lcd_init(LCD_DISP_ON); OCR1A=15625; //Compare, damit Timer 1 im Sekundentakt sei(); //aufgerufen wird while(1); }
Warum die Uhr konkret hängt, kann ich im Moment nicht sagen.
Ich denke aber es hängt damit zusammen, dass die eine
Grundregel bei der Arbeit mit Interrupts ausser acht
gelassen hast:
Interrupt Routinen sollen kurz sein! D.h. du sollst in
einer Interrupt Routine nur das notwendigste machen. Nicht
mehr. Grundsätzlich möchte man so schnell wie möglich aus
dem Interrupt wieder raus. Während ein Interrupt abgearbeitet
wird, sind alle weiteren Interrupts gesperrt. Wenn da also
in der Zwischenzeit andere Interrupts auflaufen, kann es sein
dass einer verloren geht. Und das möchte man auf keinen Fall.
Was ist in deinen Interrupt Routinen absolut nicht notwendig?
Da ist ganz sicher das Ausgeben auf LCD. Das muss nicht im
Interrupt passieren.
Die Grundstruktur, die sich bewährt hat, sieht so aus:
Im Hauptprogramm main() läuft die Hauptschleife. In der
Hauptschleife werden die Aktionen angeordnet und mit
einer zusätzlichen Variable, einem 'Flag' (engl. für Flagge,
Fahne) wird ausgewählt, ob der Programmteil betreten werden
soll oder nicht.
Die Interrupt Funktion braucht dann nur noch das Flag setzen,
um die Hauptschleife dazu zu veranlassen, diesen Programmteil
auszuführen.
Das würde zb. so aussehen:
volatile uint8_t UpdateDisplay;
volatile uint8_t sec;
volatile uint8_t min;
volatile uint8_t hr;
ISR(TIMER1_COMPA_vect) //hier wird der Zahlenwert der abgearbeitet
{
TCNT1 = 0; //Compare Register löschen
sec++;
if( sec > 59 ) {
sec = 0;
min++;
}
if( min > 59 ) {
min = 0;
hr++;
}
if( hr > 23 ) {
hr = 0;
}
//
// von der Hauptschleife eine Neuausgabe der Variablen auf
// das Display anfordern. Dazu einfach das Flag UpdateDisplay
// auf 1 setzen. Wenn die Hauptschleife im nächsten Durchlauf
// dann das Flag abfragt, erkennt sie die 1. führt die
// Ausgabe aus und setzt das Flag wieder auf 0 zurück.
//
UpdateDisplay = 1;
}
int main()
{
char buffer[20];
...
UpdateDisplay = 0;
while( 1 ) {
//
// wurde von einer ISR ein Update für das Display
// angefordert?
if( UpdateDisplay == 1 ) {
// Ja. Wurde es. Also machen wir das mal
sprintf( buffer, "%02d:%02d:%02d", hr, min, sec );
lcd_home();
lcd_puts( buffer );
// Auftrag ausgeführt. Das Flag wieder zurücksetzen
UpdateDisplay = 0;
}
}
}
Achte darauf, wie ich die Variable UpdateDisplay einsetze.
Noch was.
Deine Programme werden jetzt schon immer umfangreicher.
Umso wichtiger ist es, dass du anfängst einen leserlichen
Stil zu entwickeln. Alles in einer Wurscht herunterzuschreiben
ist kein Stil. Einrückungen und die Verwendung von Leerzeichen
können den Unterschied zwischen einem unleserlichem und einem
gut lesbarem, wartbarem Programm ausmachen. Ich sage nicht,
dass mein Stil der einzig selig machende ist, aber schau
dir trotzdem an, wo ich Leerzeichen setzte, wie ich einrücke.
Die Regel: Eine Zeile, eine Anweisung hat sich in der Praxis
als recht brauchbar herausgestellt.
> Im Tutorial habe ich gelesen, dass jeder Interrupt eine Routine
> besitzt, die dann nacheinander abgearbeitet werden. Wo finde ich
> diese Routine beim AVR mega8 ?
Das hast du misverstanden. Du bist es der diese Routine schreibt.
Wenn du wissen willst welche Interrupts und damit welche
Interrupt Handler möglich sind und unter welchen Umständen sie
aufgerufen werden, dann lautet die Antwort wie so oft: Im Datenblatt
des Prozessors steht das alles drinnen.
In deinem Pgm sind noch ein paar andere Dinge über die man
reden müsste. Ich belasse es im Moment aber dabei.
Baue jetzt erst mal die andere ISR soweit um, dass sie ebenfalls
einen Update über das Flag von der Hauptschleife anfordert.
Mal sehen, ob sich damit dein Problem soweit lösen läst.
Erst mal danke für deine Bemühungen ! > Im Tutorial habe ich gelesen, dass jeder Interrupt eine Routine > besitzt, die dann nacheinander abgearbeitet werden. Wo finde ich > diese Routine beim AVR mega8 ? Ich habe das falsch ausgedückt: Statt Routine habe ich Priorität gemeint, also Rangordnung. Ganz einfach. Es gibt keine. Wenn mehrere Interrupts wirklich gleichzeitig auftreten gilt die Reihenfolge in der Interrupt Vektor Tabelle.
So...habe jetzt mal versucht, nicht alles nur blind zu übernehmen (auch
wenns vielleicht so aussieht). Bis jetzt läuft die Uhr und hängt sich
nicht auf.
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <lcd.h>
#include <lcd.c>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <entprell.c>
volatile uint8_t UpdateDisplay;
volatile uint8_t sec;
volatile uint8_t min;
volatile uint8_t hr;
void init(void)
{
DDRD=0x00;
TCCR1B|=(1<<CS12);TIMSK|=(1<<OCIE1A);
TCCR0|=(1<<CS00)|(1<<CS02);TIMSK|=(1<<TOIE0);
lcd_init(LCD_DISP_ON);
OCR1A=15625;
}
ISR (TIMER0_OVF_vect)
{
if(debounce(&PIND, PD2)){
min++;
UpdateDisplay = 1;
}
}
ISR (TIMER1_COMPA_vect)
{
TCNT1 = 0; //Compare Register löschen
sec++;
if( sec > 59 ) {
sec = 0;
min++;
}
if( min > 59 ) {
min = 0;
hr++;
}
if( hr > 23 ) {
hr = 0;
}
UpdateDisplay = 1;
}
int main(void)
{
char buffer[16];
init();
sei();
while(1){
if (UpdateDisplay==1){
sprintf(buffer,"%02d:%02d:%02d",hr,min,sec);
lcd_home();
lcd_puts(buffer);
UpdateDisplay = 0;
}
}
}
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.