PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Programm wird nur einmal durchlaufen



Karl.Hofmann
23.03.2009, 16: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

vohopri
23.03.2009, 17:48
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

Spanky
23.03.2009, 17:51
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.^^

wkrug
24.03.2009, 07:46
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
}

};

}

Ceos
24.03.2009, 08:17
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"

askazo
24.03.2009, 08:25
@ 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

wkrug
24.03.2009, 10:22
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 ?

askazo
24.03.2009, 12:34
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

Ceos
24.03.2009, 12:36
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, 17: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, 14: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

Ceos
25.03.2009, 15:24
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!

askazo
25.03.2009, 16:20
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, 17: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!

Ceos
26.03.2009, 08:29
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

askazo
26.03.2009, 08:30
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, 19: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

Ceos
26.03.2009, 23:38
UnsignedINTeger16bit_Typedef

also n vorzeichenloser 16bit integer, Typedef steht für benutzerdefinierter datentyp

Hubert.G
27.03.2009, 09: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, 18: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!

wkrug
29.03.2009, 22:14
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, 17: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, 17: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!

wkrug
31.03.2009, 17:35
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.