Archiv verlassen und diese Seite im Standarddesign anzeigen : Programm wird nur einmal durchlaufen
Karl.Hofmann
23.03.2009, 17:47
Hallo Liebe Robotergemeinde.
Ich hab von nem Freund ein RN-CONTROL geschenkt bekommen. Ich mich deshalb einbisschen mit der Programmierung von Mikrocontroller auseinandergesetzt. Hab auch ein paar Progrämchen geschrieben. Leider bleib ich an einem Ort hängen und zwar bei if und else...
Mein Ziel wäre es das sich bei Tastendurck auf einer der 5 Tasten etwas verändert und zwar die LEDs. Leider muss ich jedes mal wieder auf die Taste Reset drücken damit sich was ändert, was eigentlich nicht sein sollte. Ich schätze mal das liegt daran, dass das Programm nur einmal durchlaufen wird.
Das Programm sieht so aus:
#include <avr/io.h>
int main (void)
{
DDRA = 0x00;
if (PINA & (1<<PINA7)) //Bedingung1
{
DDRC = 0xff; //Port c als Ausgang
PORTC = (1<<PC0); //Alle LEDs ausser LED1 sollten leuchten
}
else
{
DDRC = 0xff; //Erneut als Ausgang definieren, weil
//es nicht mehr im selben Blck ist wie voher
PORTC = (1<<PC1); //Alle LEDs ausser LED2 sollten leuchten
}
for (;;) //Endlosschleife
return 0; //Wieder an den Anfang
}
Vielleicht entdeckt jemand einen Fehler?
[-o<
Karl
Hallo Karl,
du hast auch alle Abfragen und Aktionen vor die For Schleife gestellt, anstatt hinein in die Schleife.
Dafür gehört das return nicht in in die Schleife. return 0 hat nichts mit "wieder an den Anfang" zu tun. In der Hilfe steht, was es bedeutet.
grüsse,
Hannes
Hi,
Ich verwende als Endlos Schleife immer while(1)
#include <avr/io.h>
int main (void)
{
while(1) {
DDRA = 0x00;
if (PINA & (1<<PINA7)) //Bedingung1
{
DDRC = 0xff; //Port c als Ausgang
PORTC = (1<<PC0); //Alle LEDs ausser LED1 sollten leuchten
}
else
{
DDRC = 0xff; //Erneut als Ausgang definieren, weil
//es nicht mehr im selben Blck ist wie voher
PORTC = (1<<PC1); //Alle LEDs ausser LED2 sollten leuchten
}
} // Ende der While Schleife
return 0; //Wieder an den Anfang
}
Jetzt wird einfach die komplette int main(void) unendlich oft wiederholt.
Ich glaube du kannst auch die Definition PortC als Ausgang außerhalb der Schleife setzen, dann musst du sie nicht immer neu Definieren.
Ich bin in C auch noch nicht ganz so weit. Kann durchaus sein, dass die while schleife falsch gesetzt ist.
Gruß
Spanky
EDIT: Hmm eben war wieder Critical Error. Hätte nicht gedacht das mein Beitrag ankommt.^^
Probier mal den Code:
#include <avr/io.h>
int main (void)
{
while(1)
{
DDRA = 0x00;
if (PINA & (1<<PINA7)) //Bedingung1
{
DDRC = 0xff; //Port c als Ausgang
PORTC = (1<<PC0); //Alle LEDs ausser LED1 sollten leuchten
}
else
{
DDRC = 0xff; //Erneut als Ausgang definieren, weil
//es nicht mehr im selben Blck ist wie voher
PORTC = (1<<PC1); //Alle LEDs ausser LED2 sollten leuchten
}
};
}
DDRC = 0xff; //Erneut als Ausgang definieren, weil
//es nicht mehr im selben Blck ist wie voher
das hat nichts mit dem "block" zu tun, wenn du etwas in den registern des controller veränderst ist es statisch, soll heissen verändert sich nur, wenn du etwas veränderst! das gilt auch für variablen, jede variable die du in deinem programm einmal verwendest (ausser natürlich rekursivaufrufe) wird immer wiedern an der selben speicherstelle liegen
das ständige "neu einstellen" kostet dich 125nS @ 16Mhz
also das doppelte DDRC raus, udn alle DDRs falls du sie zur programmlaufzeit nicht ändern willst, VOR die while(1)
PS: das war nicht gegen dich WKrug, eher ein zeichen an den "code-kommentator"
@ spanky & wkrug: Wenn Ihr Euch selber nicht sicher seid, solltet Ihr nicht antworten. Das verunsichert doch nur...
1. Initialisierungen gehören nicht in eine Schleife.
2. Hinter eine geschweifte Klammer kommt niemals ein Semikolon
3. Das return 0; gehört immer ans Ende der main-Funktion hat aber nichts mit "wieder an den Anfang" springen zu tun.
Ein korrektes Programm sähe so aus:
#include <avr/io.h>
int main (void)
{
DDRA = 0x00; //PORTA als Eingang definieren (kann man sich aber auch schenken, da Standart-Einstellung)
DDRB = 0xFF; //PORTB als Ausgang definieren
for (;;) //Beginn der Endlosschleife
{
if (PINA & (1<<PINA7)) //Bedingung1
PORTC = (1<<PC0); //Alle LEDs ausser LED1 sollten leuchten
else
PORTC = (1<<PC1); //Alle LEDs ausser LED2 sollten leuchten
} //Ende der Endlosschleife
return 0;
}
PS: Ob man while(1) {} oder for(;;){} für die Endlosschleife verwendet, ist eigentlich egal. Laut einer Application Note von Atmel ist die for-Variante aber effektiver (warum auch immer).
Gruß,
askazo
Das mit der Initialisierung hab ich glatt übersehen.
Die Variante mit der for( ; ; ) war mir unbekannt - wieder was dazu gelernt.
Vermutlich kommt es auch auf den Compiler an, was der draus macht.
Bei einer If Anweisung sind bei den Code Templates nach der geschweiften Klammer immer ";" dran - Jedenfalls bei meinem Compiler.
Wenn die Entwickler von Compiler Software sowas machen, dürfte es nicht ganz verkehrt sein ?!
CodeVision verwendet für die Haupschleife nur die while(1) Variante.
Das return 0 hab ich rausgenommen, wenn Dir das aufgefallen ist, weil es ja keinen Sinn macht, ohne eine Funktion aufgerufen zu haben.
Anscheinend ist das aber wieder Compiler abhängig.
Die Anweisung int main(void) kommt mir auch etwas seltsam vor.
Sollte es nicht void main(void) heissen ?
Hab's gerade mal mit dem GCC ausprobiert - ein Semikolon hinter der geschweiften Klammer lässt er tatsächlich ohne zu meckern zu.
Gängig ist das aber nicht, ich hab's zumindest bis jetzt noch nie in einem C-Code gesehen.
Wie die main-Funktion auszusehen hat, scheint wirklich Compiler-abhängig zu sein. Der GCC möchte auf jeden Fall einen Rückgabewert haben, auch wenn's eigentlich sinnlos ist. Aber ansonsten gibt's ne Warnung.
Und wenn man einen Rückgabewert hat, muss man die Funktion natürlich auch dementsprechend deklarieren, daher int main(void)....
Das mit der for(;;)-Schleife scheint allerdings Compilerunabhängig zu sein. Zumindest bezieht sich die entsprechende Application-Note nicht auf einen bestimmten Compiler. Hier der Link dazu:
http://www.atmel.com/dyn/resources/prod_documents/doc1497.pdf
siehe S.19
Die Application-Note scheint doch für einen bestimmten Compiler zu gelten, und zwar für den IAR. Naja, ich lass' es trotzdem mal so stehen...
Gruß,
askazo
der winavr compiler muckt bei void main rum, und wenns int main ist verlangt er auch nach return 0; auch wenns nie erreicht wird
Karl.Hofmann
24.03.2009, 18:51
Hallo.
Die Antworten sind ja massenhaft gekommen. Danke euch!
Jetzt ists auch gegangen, das Problem war, dass ich while (1) oder for (;;) eben für return genutzt und nicht am Anfang gesetzt.
Lg
Karl
Karl.Hofmann
25.03.2009, 15:01
Hallo
Ich hab jetzt gleich noch eine weitere Frage, mache dafür aber nicht extra einen weiteren Thread auf. In diesem Artikel von RN-Wissen wird gezeigt wie man eine LED blinken lassen kann ohne Timer:
https://www.roboternetz.de/wissen/index.php/LED-Blinken_ohne_Timer
Dort wird die Funktion sleep_ms () benuzt. Und die drei Bibliotheken:
<avr/io.h>
<util/delay.h>
<stdint.h>
Ist diese Funktion in einer diesen Bibliotheken bereits definiert? Also in avr/io soweit cih weiss nicht... aber vielleicht in einer anderen? Und kann man diese Funktion, wenn man die oberen Bibliotheken "inkludiert" hat, einfach benutzen?
Jedoch wird sie im Programm am Anfang nochmal dekliniert mit:
void sleep_ms(uint16_t ms);
Ist es notwendig, des zu machen?
Karl
in der delay.h ist sowohl eine
delay_loop(X)
die X*3 prozessorzyklen wartet
als auch eine sleep_us, die in mikrosekunden wartet, dafür muss man die frequenz des controllers mit in den projekteinstellungen angeben!
Das ist nicht ganz richtig...
In der delay.h gibt es keine Funktion sleep_ms(), dafür allerdings eine Funktion _delay_ms().
Nachteil dieser _delay_ms() ist, dass man damit maximal
262.14 ms / F_CPU in MHz
warten kann. Bei einem Takt von 8 MHz also gerade mal 32,768 ms.
In dem Beispielprogramm wurde deswegen die Funktion sleep_ms() eingebaut, die in einer Schleife die Funktion _delay_ms(1) aufruft und somit das Problem behebt.
Die Funktion findest Du im Beispiel unter der main()
Gruß,
askazo
Karl.Hofmann
25.03.2009, 18:55
Hallo
Aber warum wird das Problem mit der Schleife behebt? Das ganze wartet dann einfach eine ms länger.
Und warum ist die Funktion mit delay ms soweit unten beim Programm? Müsste das nicht vor dem LEDblinken sein?
Grüsse!
In der delay.h gibt es keine Funktion sleep_ms(), dafür allerdings eine Funktion _delay_ms().
ja das war ein wenig voreilig und falsch erinnert
Aber warum wird das Problem mit der Schleife behebt? Das ganze wartet dann einfach eine ms länger.
/**
\ingroup util_delay
Perform a delay of \c __us microseconds, using _delay_loop_1().
The macro F_CPU is supposed to be defined to a
constant defining the CPU clock frequency (in Hertz).
The maximal possible delay is 768 us / F_CPU in MHz.
If the user requests a delay greater than the maximal possible one,
_delay_us() will automatically call _delay_ms() instead. The user
will not be informed about this case.
*/
/**
\ingroup util_delay
Perform a delay of \c __ms milliseconds, using _delay_loop_2().
The macro F_CPU is supposed to be defined to a
constant defining the CPU clock frequency (in Hertz).
The maximal possible delay is 262.14 ms / F_CPU in MHz.
When the user request delay which exceed the maximum possible one,
_delay_ms() provides a decreased resolution functionality. In this
mode _delay_ms() will work with a resolution of 1/10 ms, providing
delays up to 6.5535 seconds (independent from CPU frequency). The
user will not be informed about decreased resolution.
*/
das limit liegt also bei 6.5 sekunden
mit verringerter auflösung
Gut, das ganze mal ausführlicher:
Mal angenommen, wir haben einen Takt von 8 MHz. Dann könnte mit der Funktion _delay_ms() bei einem Aufruf eine maximale Wartezeit von ~32ms erreicht werden. Wenn wir nun eine ganze Sekunde warten wollen, müssten wir _delay_ms() mehrmals hintereinander aufrufen.
Genau das macht die Funktion sleep_ms() mit einer Schleife. Damit das ganze verständlich bleibt, wird in der Schleife genau 1ms gewartet und das ganze n-mal widerholt. Somit erreichen wir durch den Befehl sleep_ms(1000) eine Wartezeit von ziemlich genau 1000ms, also eine Sekunde.
An welcher Stelle im Programm die eigentliche Funktion steht, ist egal und bleibt dem Stil des Programmierers überlassen. Sie kann also vor oder hinter der Stelle stehen, an der sie aufgerufen wird.
Wichtig ist nur, dass die Deklaration, also der Prototyp der Funktion vor dem Aufruf steht.
When the user request delay which exceed the maximum possible one, _delay_ms() provides a decreased resolution functionality. In this mode _delay_ms() will work with a resolution of 1/10 ms, providing delays up to 6.5535 seconds (independent from CPU frequency)
Das war mir auch neu...interessant!
Danke für die Info.,
Gruß,
askazo
Karl.Hofmann
26.03.2009, 20:03
Hallo.
Danke für die ausführliche Antwort. Jetzt verstehe ich das mit der Funktion sleep_ms...
sleep_ms wird ja als Funktion definiert mit:
sleep_ms (uint16_t ms)
Für was steht das uint16_t? Ich konnte diese Frage leider nicht übers Netz beantworten und hab auch schon im C-Tutorial danach gesucht, aber es wird oft benutzt aber leider nicht gesagt für was es ist, ich schätze mal das ist so weil es eine ziemlich simple Bedeutung hat...
Karl
UnsignedINTeger16bit_Typedef
also n vorzeichenloser 16bit integer, Typedef steht für benutzerdefinierter datentyp
Hubert.G
27.03.2009, 10:32
Ich würde dir nur raten dich trotzdem mit Timer zu beschäftigen.
In der Zeit in der das sleep_ms() oder _delay_ms() durchlaufen wird, kann das Programm nichts anderes machen. Du kannst in der Zeit keine Taste abfragen und nichts ansteuern. Unterbrechen kannst du das nur mit Interrupts.
Karl.Hofmann
29.03.2009, 19:21
Hallo.
Ich bin mich zurzeit auch sehr stark mit Timern am beschäftigen, versteh aber irgendwie nicht alles... ich kenn auch nur das Mikrocontroller.net Tut. von den Timern und das auf RN-Wiki. Gibts dazu vielleicht ein Buch oder ein sehr ausführliches anderes Tutorial?
Mein Ziel wäre es eben schlussendlich ein Servo ansteuern zu können.
Grüsse!
Mein Ziel wäre es eben schlussendlich ein Servo ansteuern zu können
Dazu wäre der Timer 1 der ATMEGA's meiner Meinung nach optimal geeignet.
Du lässt der Controller mit 8MHz Taktfrequenz laufen und stellst den Prescaler des Timers 1 auf 8.
Somit entspricht ein inkrement des Timers 1 genau 1µs.
Du gibst nun die beiden Comparematch Interrupts des Timers frei.
Der Comparematch Interrupt A löst einen Interrupt aus.
Die Routine in diesem Interrupt zählt zum augenblicklichen Zählerstand des Timers 1 die gewünschte Impulslänge, also z.B. 1500 für 1,5ms dazu und speichert diesen Wert im Comparematch B Register ab.
Gleichzeitig wird ein Port auf High geschaltet.
Der Controller läuft nun weiter bis der Comparematch B erreicht ist.
Die Interruptroutine hier drin zählt zum aktuellen Zählerstand des Timers 1 die gewünschte Pausenzeit hinzu z.B. 19000 für 19ms.
Dieser Wert wird dann im Comparematch A Register abgespeichert.
Gleichzeitig wird der in der Comparematch A gesetzte Ausgang wieder auf 0 gesetzt.
Somit läuft die komplette Impulserzeugung im Interrupt des Controllers ab.
Die gewünschten Servoimpulslängen können direkt im Comparematch A aus Hilfsregistern übernommen werden.
Eine Erweiterung der möglichen Servokanäle auf ca 8...9 sollte problemlos möglich sein. Man braucht ja nur in der Comparematch A Routine eine Hilfsvariable einsetzen, die bei jedem Durchlauf hochgezählt wird und jeweils einen anderen Servoausgang aktiviert.
Im Comparematch B muss bei mehreren Kanälen die Pausenzeit verkürzt werden, damit man wieder auf eine Refreshrate von ca. 20ms pro Servoausgang kommt. Praktischerweise würde ich im Comparematch B immer alle benutzten Servoausgänge auf 0 setzen.
Nimmt man dafür einen komplatten Port, geht das mit einem Kommando.
Da der Zählerstand von TCNT1 bei dieser Methode nicht beinflusst wird, kann man den Timer 1 auch noch zusätzlich für Zeitmessungen o.Ä. verwenden.
Karl.Hofmann
31.03.2009, 18:24
Hallo.
Danke für die ausführliche Antwort. Also:
Den Prescaler müsste ich einstellen mit:
TCCR1B = (1<<CS11)
?
Und damit die Comparematch Interrupts des Teimers frei werden und OC1 auf high gesetzt wird müsste ich das ungefähr so machen:
TCCR1A = (1<<COM1A1) | (1<<COM1A1)
?
Und jetzt sollte das eigentlich mit dem Vergleichswert verglichen werden und sobald diese dann gleich sind ein Interrupt ausgelöst werden? Stimmt das?
Karl
Karl.Hofmann
31.03.2009, 18:24
Achja zur Servoansteuerung:
Ich hab mich da mal im wiki umgesehen und dieses Programm gefunden:
#define SERVOPIN 7
#define SERVOPORT PORTD
#define DDRSERVO DDRD
volatile unsigned char servopos;
void servo_init()
{
TIMSK|=(1<<OCIE2);
TCCR2 |= (1<<WGM21) | (1<<CS20); //Prescale=1, CTC mode
OCR2 = F_CPU/100000; //alle 10µS ein IRQ
DDRSERVO|=(1<<SERVOPIN);
};
ISR(TIMER2_COMP_vect)
{
static int count;
if(count>servopos)SERVOPORT&=~(1<<SERVOPIN);
else SERVOPORT|=(1<<SERVOPIN);
if(count<2000+servopos)count++;
else count=0;
};
Ist mit diesem Programm die Funktion ISR () definiert?
Mit Servo_init () wird zwar hier der Timer initialisiert.
Und könnte ich mit diesem Programm anschliessend an ISR ()
int main (void) schreiben
und nun die Funktion ISR benutzten?
Oder hab ich da was falsch verstanden?
Grüsse!
Hier die Einstellungen für einen ATMEGA 16 bei 8MHz
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: On
TCCR1A=0x00;
TCCR1B=0x02;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x18;
Und hier die dazugehörigen Interruptroutinen für 4 Kanäle
// Timer 1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
PORTC=0; // Port C abschalten = alle Ausgänge aus
}
// Timer 1 output compare B interrupt service routine
interrupt [TIM1_COMPB] void timer1_compb_isr(void)
{
switch(uc_channel)
{
case 0:
PORTC.0=1; // Kanal 1 ein
OCR1A=ui_pwm[0];
break;
case 1:
PORTC.1=1; // Kanal 2 ein
OCR1A=ui_pwm[1];
break;
case 2:
PORTC.2=1; // Kanal 3 ein
OCR1A=ui_pwm[2];
break;
case 3:
PORTC.3=1; // Kanal 4 ein
OCR1A=ui_pwm[3];
break;
};
TCNT1=0;
uc_channel++;
if ( uc_channel>3){uc_channel=0;};
}
Ich hab die Routinen noch nicht mit Servos getestet, man möge mir also Fehler verzeihen, wenn was nicht stimmt.
Die Codes sind für Codevision AVR, sollten sich aber leicht für andere Compiler umschreiben lassen.
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.