PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Atmega Timer problem



Technik_Amateur
19.11.2013, 10:22
Hallo liebes Forum,

Ich habe ein Verständnisproblem bezüglich der Timer (8 u. 16 bit).
Ich habe versucht ein PWM-Signal zu generieren, jedoch zeigt das oszi keins an.

Was ich erwartet habe: alle 20ms wird eine entsprechende Pulsbreite von den Timern erzeugt. 0,6ms bis 2,6ms sollte die range der Breite sein.
Die pulsbreite wird über LDRs realisiert, die einen Spannungsteiler bilden. So liegen am ADC1 werte von 0V bis 5V an. Wenn beide LDRs gleich stark beleuchtet sind, liegen ca. 2,5V am ADC1 an. Der Teil des Speicherns in eine Variable funktioniert soweit (0...1024 -> 2,5V = 512).

Ich habe es versucht mit einem Timer1 (16-Bit) zu realisieren aber ich bekomme es einfach nicht hin.
Ich benutze einen Atmega1284p-pu der mit dem internen Clock von 8Mhz arbeitet.

Hier mein Code:




#define F_CPU 8000000UL
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "ADC.h"

ISR (TIMER1_COMPA_vect) //Interrupt sub routine
{
OCR1A = 10000 - OCR1A; // hier werden die 20ms erzeugt die der servo benötigt
}

int main(void)
{
DDRD = (1<<PIND5); //Der Ausgang

ADC_Init(); //ADC initialisieren

double adcwert_1_LDRs; //speicherort für den ADCwert

TCCR1A = (1<<COM1A1) | (1<<COM1B1); //CTC Modus (Clear OC1A on compare match), set OC1A at bottom non-inverting
TCCR1B = (1<<WGM13) | (1<<CS11); //(1<<WGM12) vllt raus, (ICRn aktivieren), OCR1A auf direkt, TOV1 Flag auf MAX, prescaler=8
OCR1A = 799; //(8Mhz/(2*8*(1+799)))=625Hz=1,6ms Mittelstellung, (8Mhz/(2*8*(1+750)))=665,7Hz=1,5ms Mittelstellung,
TIMSK1 = (1<<OCIE1A);//(1<<TOIE1); //Timer-compare-Interrupt an

_delay_ms(2000);

while(1)
{
adcwert_1_LDRs = ADC_Read(1);
if (adcwert_1_LDRs <= 490 && OCR1A > 301) //servo dreht links bis max. 0,6ms
{ //(8MHz/(2*8*(1+9699)))=51,54Hz;20ms-0,6ms=19,4ms->1/19,4ms=51,54Hz->(8MHz/(2*8*51,54Hz))-1=9699->10000-9699=301!
//(8MHz/(2*8*(1+301)))=1655,6Hz=0,6ms
cli(); //interrupts global deaktivieren
OCR1A -= 5; //OCR1A so lange um 5 verringern, bis panel direkt zur sonne gerichtet ist
sei(); //interrupts globl aktiviren
_delay_ms(50); //50ms warten
}

if (adcwert_1_LDRs >= 530 && OCR1A < 1301) //servo dreht rechts
{
cli();
OCR1A += 5; //1301;
sei();
_delay_ms(50);
}
}
}


Ich hoffe Ihr könnt mir helfen.

Mit freundlichen Grüßen

Technik_Amateur

Searcher
19.11.2013, 12:45
Hi,
ich blick nicht durch, wie Dein Programm funktioniert/funktionieren soll. Deshalb mal ein Alternativvorschlag für die Timereinstellung:

OCR1A mit 1500 initialisieren (Mittelstellung für Servo)
Für Timer1 im Fast PWM Modus mit ICR1 als Top (Mode 14)
Im TCCR1A WGM11 setzen
Im TCCR1B WGM12 und WGM13 setzen

ICR1 auf 19999 setzen UND Prescaler auf 8 (im TCCR1B zusätzlich noch das CS11 setzen)
Mit CS11 läuft der Timer1 los und benötigt für eine "Runde" 20ms und eine Einheit im OCR1A entspricht einer µs.

Für PWM Modus "non inverting" in TCCR1A noch zusätzlich das COM1A1 setzten (clear OC1A on compare match, set at bottom)

Nun brauchst Du nur noch OCR1A entsprechend zu setzen und an PD5 erscheint das benötigte PWM Signal für den Servo wenn der, wie in Deinem Programm, auf OUTPUT geschaltet ist.

zB OCR1A auf 500 = Servopulsweite von 0,5ms
zB OCR1A auf 1500 = Servopulsweite von 1,5ms

Dann wird keine ISR benötigt und in der Haupschleife dann nur ADC auswerten und das OCR1A entsprechend setzten.

Alles nach Datenblatt für ATmega1284P_doc8059 und nicht getestet, soll heißen ohne Gewehr :-)


Gruß
Searcher

sternst
19.11.2013, 15:31
Ich habe versucht ein PWM-Signal zu generieren, jedoch zeigt das oszi keins an.Du hast einen PWM-Modus mit variablem TOP-Wert ausgewählt, lässt diesen TOP-Wert dann aber auf 0. Wie soll da dann auch irgendein PWM-Signal raus kommen.

oberallgeier
19.11.2013, 16:18
... Ich habe ein Verständnisproblem bezüglich der Timer (8 u. 16 bit) ...Mir gings ne Zeitlang nicht besser *grins*.


... Ich habe versucht ein PWM-Signal zu generieren, jedoch zeigt das oszi keins an ...Hier (klick) (https://www.roboternetz.de/community/threads/61379-Kopfsache-und-ein-m1284-etliche-Servos-viel-Alu?p=577715&viewfull=1#post577715) steht meine Servo-Timer-Initialisierung für nen 1284P/20 MHz (zwanzig, mit Quarz!!). Damit fahre ich die Servosteuerung vollständig über die beiden 16bittigen Timer. ALLERDINGS für acht, aktuell für zehn Servos, d.h. um auf die 20 ms zu kommen, muss der Timer1 mehrfach (sollte mindestens 8 mal) aufgerufen werden. ACHTUNG - der Hintergrund der Sache ist mein Wunsch die Servod von minimal möglicher GEschwindigkeit in SlowMotion bis zu full speed fahren zu könnnen. Die Initialisierung des Timer/Counter 1 ist für ne CTC, das wäre gerade für Dich vielleicht als Codebeispiel von Interesse. Entsprechend den 20 MHz sind natürlich die Werte für Prescaler, OCR1A und OCR1B.

PS: Mein C ist nicht das Beste, insbes. nicht für alle Compiler wasserdicht :-/

Technik_Amateur
19.11.2013, 18:25
Vielen Dank für die schnellen und hilfreichen Antworten!

@Searcher:
Ich habe deine vorgeschlagenen Werte selber nochmal mit Hilfe des Datenblattes nachvollziehen können.
Wenn ich alles richtig verstanden habe, ist ICR1 also mein TOP-Wert für den Timer der die ganze Zeit durchläuft und mit dem OCR1A-Register kann ich festlegen wann er das HIGH auf LOW schalten soll (HIGH auf LOW durch setzen von COM1A1) ? Sprich der Timer gibt von Anfang an ein HIGH-Signal und wenn er auf den OCR1A-Wert trifft geht das Signal auf LOW, so lange bis der TOP-Wert von ICR1 erreicht ist, dann wieder automatisch auf HIGH usw.?

Mit freundlichen Grüßen

Technik_Amateur

Searcher
19.11.2013, 19:57
Wenn ich alles richtig verstanden habe, ist ICR1 also mein TOP-Wert für den Timer der die ganze Zeit durchläuft

Ja, im Timer1 Mode 14 ist ICR1 der TOP-Wert nach Datenblatt "Table 14-5. Waveform Generation Mode Bit Description".


und mit dem OCR1A-Register kann ich festlegen wann er das HIGH auf LOW schalten soll (HIGH auf LOW durch setzen von COM1A1) ?
Ja, wenn das PWM-Signal an OC1A (PD5) ausgegeben werden soll -> nach DB "Table 14-3. Compare Output Mode, Fast PWM".
Für PWM an OC1B (PD4) müßte man die COM1Bx Bits verwenden und das OCR1B Register.


Sprich der Timer gibt von Anfang an ein HIGH-Signal und wenn er auf den OCR1A-Wert trifft geht das Signal auf LOW, so lange bis der TOP-Wert von ICR1 erreicht ist, dann wieder automatisch auf HIGH usw.?
Ja, solange der Timer (TCNT1) unterhalb von OCR1A ist, ist der Output (OC1A) HIGH. Mit Erreichen von OCR1A (Compare Match) wird OC1A LOW. Allerdings wird OC1A nicht bei Erreichen von ICR1 wieder HIGH, sondern erst einen Timertick später, wenn TCNT1 0 ist.

Gruß
Searcher

Technik_Amateur
19.11.2013, 20:08
Ok vielen Dank! Jetzt weiß ich schon ein bisschen besser bescheid, wie ich mit den Registern umzugehen habe. EIGENTLICH gar nicht so schwer:-k
Dann werde ich mal das Programm schreiben und nochmal hier posten. Testen kann ich dieses leider erst am Wochenende mit oszi.

Gruß

Technik_Amateur

Searcher
19.11.2013, 20:28
:-) ist auch EIGENTLICH nicht schwer. Man muß nur einmal den Dreh raus haben und beachten, daß es für die verschiedenen Timer Betriebsmodi (non PWM, Fast PWM, Phase Correct PWM) verschiedene Tabellen gibt und verschiedene TOP Werte, die auch noch variabel sein können und die man nicht vergessen darf zu initialisieren :-) Außerdem die Qual der Wahl, welcher Modus denn nun am besten für das Vorhaben ist.

Hatte am Anfang auch schon viele Fehlversuche hinter mir und vertue mich immer noch.

Viel Erfolg und Gruß
Searcher

Technik_Amateur
19.11.2013, 22:18
Soo ich habe jetzt den Code geschrieben und hoffe, dass es so funktionieren könnte.

Da ich ein Panel zur Sonne ausrichten lassen will (programmtechnisch), hoffe ich das meine Idee auch so funktionieren kann.



#define F_CPU 8000000UL
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
#include <stdio.h>
#include "ADC.h"

DDRD = (1<<PIND5); //PORTD pin 5 auf ausgang

uint16_t Servo_soll = 512, Servo_ist;

ADC_Init(); //ADC initialisieren
TCCR1A = (1<<COM1A1) | (1<<WGM11); //clear OC1A on compare match, set at bottom (non-inverting)
TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS11); //WGM12,WGM13,WGM11 -> Mode14 -> Fast PWM, ICRn, set OCR1A at Bottom | prescaler = 8
ICR1 = 19999; // ICR1 = 19999 als TOP-Wert, hier werden die 20ms gebildet (8MHz / 8 * (1 + 19999)) = 50Hz
OCR1A = 1499; // OCR1A = 1499, Servomittelstellung, (8MHz / 8 * (1 + 1499)) = 666,6Hz = 1,5ms
_delay_ms(2000); // kurz warten bis der Servo sich ausgerichtet hat



Da ich auch noch nicht so sehr mit C++ vertraut bin, Bitte ich euch mir zu sagen, welche Variante besser wäre, also nicht so rechenlastig für den µC.
Diese mit if?:



while(1)
{
//########################################### Nachführung des Panels #######################################

Servo_ist = ADC_Read(1); //ADC kanal 1 auslesen und in die variable adcwert_1_LDRs speichern
if (Servo_ist != Servo_soll) //Wenn der ADC-Wert der LDRs nicht mit dem Servo_soll Wert von 512 überein stimmt, dann...
{
if (Servo_ist < Servo_soll && OCR1A > 600) //Wenn der ADC-Wert kleiner ist als 512 und 0,6ms noch nicht erreicht sind...
{
OCR1A = OCR1A--; //verringere OCR1A um 1
}
if (Servo_ist > Servo_soll && OCR1A < 2600) //Wenn der ADC-Wert größer ist als 512 und 2,6ms noch nicht erreicht sind...
{
OCR1A = OCR1A++; //erhöhe OCR1A um 1
}
}
}


Oder Diese mit while?:



while(1)
{
//########################################### Nachführung des Panels #######################################

Servo_ist = ADC_Read(1); //ADC kanal 1 auslesen und in die variable adcwert_1_LDRs speichern
if (Servo_ist != Servo_soll) //Wenn der ADC-Wert der LDRs nicht mit dem Servo_soll Wert von 512 überein stimmt, dann...
{
if (Servo_ist < 500 || Servo_ist > 524) //Tolleranzbereich, sodass der Servo nich bei jeder kleinen Änderung dreht
{
while(Servo_ist < Servo_soll && OCR1A > 600) //Solange der ADC-Wert kleiner ist als 512 und 0,6ms noch nicht erreicht sind...
{
OCR1A = OCR1A--; //verringere OCR1A um 1
}
while(Servo_ist > Servo_soll && OCR1A < 2600) //Solange der ADC-Wert größer ist als 512 und 2,6ms noch nicht erreicht sind...
{
OCR1A = OCR1A++; //erhöhe OCR1A um 1
}
}
}
}


Gruß

Technik_Amateur

wkrug
20.11.2013, 09:57
Im Prinzip dürfte es egal sein, welche Variante Du benutzt.
Ich würde die if Variante bevorzugen.
Begründung:
Ich vermute diese Programmschleife wird so schnell durchlaufen, das dein Servo ohnehin mit der Stellgeschwindigkeit nicht mitkommt.
Das bedeutet Du wirst immer A/D Wandler Werte kriegen die nicht den aktuellen Stand der PWM widerspiegeln, da das Servo hinterherhängt.
Bei der if Variante kannst Du Verzögerungsglieder ( Zähler der im Timer hochgezählt wird ) reinbauen.

Was soll das Ganze denn werden? Ein Lichtsucher? Eine Solarzellen Nachführung? Eine Laser Kanone?

Searcher
20.11.2013, 11:33
Hallo Technik_Amateur,
den Ablauf der Timerkonfiguration sehe ich folgendermaßen:

TCCR1A = (1<<COM1A1) | (1<<WGM11); //clear OC1A on compare match, set at bottom (non-inverting)
TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS11); //WGM12,WGM13,WGM11 -> Mode14 -> Fast PWM, ICRn, set OCR1A at Bottom | prescaler = 8

Der Timer läuft durch das CS11 los, ICR1 ist aber noch auf seinem Initialwert 0.

ICR1 = 19999; // ICR1 = 19999 als TOP-Wert, hier werden die 20ms gebildet (8MHz / 8 * (1 + 19999)) = 50Hz
Erst hier wird der richtige TOP-Wert definiert. Keine Ahnung was passiert, wenn der Timer versucht mit TOP Wert 0 loszulaufen. Wird vermutlich keine spürbaren Auswirkungen haben. Logischer wäre, das ICR1 vor Start des Timers zu setzten.

OCR1A = 1499; // OCR1A = 1499, Servomittelstellung, (8MHz / 8 * (1 + 1499)) = 666,6Hz = 1,5ms
1499 für 1500µs Pulsweite stimmt. Hab ich mal mit einem Mega88 getestet; ist zwar ein anderer µC aber die Timerbeschreibung ist sehr ähnlich, wenn nicht gleich. Ist in meinem ersten Post falsch aufgeführt :(

EDIT: Wenn der Timer läuft und OCR1A auf 0 ist und bleibt, gibt es in diesem Fall 1µs spikes bei jedem Durchlauf am OC1A Pin (alle 20ms).

Man sollte auch noch beachten, wann das OCR1A Register upgedatet wird. Im Mode 14 ist es bei TCNT1=0 (BOTTOM). Wenn der Timer schon läuft, wird beim Schreiben auf OCR1A der wert gepuffert und erst bei Erreichen von TCNT1=BOTTOM dann übernommen und aktiv.

Logischer wäre auch hier, das OCR1A vor Starten des Timers zu initialisieren.

Auch könnte man sich überlegen, ob man das COM1A1 Bit erst zum Schluß setzt und damit PD5 auf die Compare Unit/waveform Generation Unit durchschaltet, wenn der Timer komplett initialisiert ist und läuft.

Das meiste ist für den Betrieb nicht so wichtig, da es nur den ersten Timerzyklus betrifft und man hier Unsauberkeiten am Servo wahrscheinlich nicht erkennen kann. Könnte aber hilfreich bei der Bugsuche sein.

Möchte noch sagen, daß ich auch noch Amateur bei der µC Programmierung bin und nicht alles richtig sein muß, was ich hier geschrieben habe. C verstehe ich auch nur rudimentär und enthalte mich auch erstmal einem Kommentar zu Deinem restlichen Programm.

Gruß
Searcher

Technik_Amateur
20.11.2013, 15:59
Ich habe jetzt den Code mit der "if-Variante" benutzt und konnte heute schon testen ob alles ok ist. laut oszi werden jetzt PWM-Signale zwischen 0,6 und 2,6ms erzeugt. Darauf hin habe ich den Servo angeschlosse nund siehe da, alles funktioniert super. Eine
Sache stört mich aber immernoch. Der Servo dreht sich etwas zu schnell. Kann ich die geschwindigkeit denn jetzt noch irgendwie beeinflussen, ohne das ich die 20ms-Periode und die pulsbreite zerstöre? Ich habe es mit delays versucht, aber dann beeinflusse ich doch die Periode und somit die Signale oder? Gibt es da eine möglichkeit dies zu umgehen?

@wkrug: Was meinst du genau mit Verzögerungsglieder? Die Drehgeschwindigkeit verändern?
Ja das ganze wir eine Solarzellen Nachführung. Der Sonnenstand wird mithilfe von 2 LDRs als Spannungsteiler ermittelt und diese Spannung (0...5V) geht an den ADC.

@Searcher: Hallo Searcher!
Ja das klingt logisch, dass beim ersten initialisieren die TOP-Werte warscheinlich unsauber sind. Auch wenn ich diese heute beim Test nicht bemerken konnte. Ich werde sie trotzdem anpassen!


Gruß

Technik_Amateur

oberallgeier
20.11.2013, 16:20
... Hintergrund ... Servos von minimal möglicher Geschwindigkeit ... bis zu full speed fahren zu können ...
... Der Servo dreht sich etwas zu schnell. Kann ich die geschwindigkeit denn jetzt noch irgendwie beeinflussen ...Siehe oben.

Searcher
20.11.2013, 16:44
Das Servo versucht möglichst schnell die Position zu erreichen, die ihm die Pulsweite vorgibt. Man müße also die Sollpulsweite, die durch die ADC Messung vorgegeben wird, von der Istpulsweite aus langsam erreichen. Deine If Bedingung, in der OCR1A um eins verändert wird, steht vermutlich in einer Schleife, die "irre schnell" immer wieder durchlaufen wird. Schneller, als das Servo der durch OCR1A veränderten Pulsweite folgen kann und deshalb versucht es also so schnell es ihm möglich ist, ihr zu folgen.

Gibt sicher viele Möglichkeiten, die Pulsweite von Ist nach Soll langsam zu verändern. Eine simple wäre, die Schleife, in der die IF Abfragen stehen einfach durch ein delay zu verzögern. Welches delay passend ist müßte man ausprobieren. Schätze mal so mit 5ms bis 20ms könnte man anfangen. Dadurch würde auch die ADC Abfrage verlangsamt, sollte aber bei dem Sonnenfolger und den langsamen LDR keine Rolle spielen.

Eine ausgefeilte Möglichkeit bietet oberallgeier an, bedeutet aber auch schon einiges an Einarbeitungszeit.


@Searcher: Hallo Searcher!
Ja das klingt logisch, dass beim ersten initialisieren die TOP-Werte warscheinlich unsauber sind. Auch wenn ich diese heute beim Test nicht bemerken konnte. Ich werde sie trotzdem anpassen!

Kann man auch meistens vernachlässigen. Nur wenn unerklärbare Problem auftauchen, sollte man auch an so etwas denken.


Gruß
Searcher

wkrug
20.11.2013, 23:01
Was meinst du genau mit Verzögerungsglieder? Die Drehgeschwindigkeit verändern?
Die Impulsaufbereitung findet komplett in der Hardware statt, soweit ich das kapiert habe.
Die verzögerung kann also nur auf der Eingabeseite für die Servoimpulslänge erfolgen.
Am einfachsten wäre es einfach vor die if Abfrage ein delay_ms einzufügen und zusätzlich die geänderten Werte nur um wenige Werte zu erhöhen bzw. zu erniedrigen.
Über die Anzahl der maximalen änderbaren Schrittweite und der delayzeit kann man die maximale Geschwindigkeit des Servos dann einstellen.

delay_ms hat aber leider den fiesen Nachteil, das dadurch das komplette Hauptprogramm verzögert wird.
Ich benutze dazu dann lieber einen zusätzlichen Timer, der in seinen Overflow Interrupts eine Variable runterzählt.
Wenn die Variable den Wert 0 hat wird die if abfrage durchlaufen, die Variable wieder auf den Initialwert gesetzt und die Impulsweite eingestellt.
Den Grad der Servoverzögerung kann man dabei über den Initialwert der Variable verändern.

Dann wird die if abfrage nicht mehr durchlaufen, bis die Variable wieder den Wert 0 hat.

Will man weitere zeitabhängige Vorgänge Triggern kann man das mit zusätzlichen Variablen in diesem Timer Overflow Interrupt realisieren.
Die Variablen sollten aber als volatile definiert werden, da sie sich ja in einem Interrupt ändern können ( und sollen ).


Diese Methode hat den Vorteil, das das restliche Hauptprogramm nicht ausgebremst wird, wie das bei delay_ms der Fall wäre.

// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
if (uc_sectimer>0)
{
uc_sectimer--;
}
}
...


// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 7,813 kHz
TCCR0=0x05;
TCNT0=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x01;

while (1)
{
if (uc_sectimer==0)
{
uc_sectimer=10;
if(....//Dein Code
}....


Die konkreten Werte für die Timer Register müsstest Du selber mal imDatenblatt nachschauen.
Die Aufrufe für die Interrupts sind bei AVR GCC anders, das Beispiel ist für CodeVision AVR.

Für das Hauptprogramm "sieht" es so aus, als ob sich diese Variablen wie von Geisterhand in festen Zeitabständen verringern würden.


Ich würde in deinem System auch ne Realtime Clock ( als externer Chip ) einsetzten um bei ungünstigen Witterungsverhältnissen ( bewölkter Himmel ) eine Position aus einer Tabelle ansteuern zu können.
Die Tabelle kann auch bei gutem Wetter dafür genutzt werden die Werte der Lichtsensoren zu verifizieren und das System am Morgen in eine definierte Startposition zu bringen.

Das hört sich vieleicht ein wenig "Oversized" an aber zumindest im Platinenlayout würd ich diese Option mal vorsehen.

Zudem würde ich mir überlegen einen Getriebemotor mit Spindelantrieb vorzusehen.
Dieser braucht im Ruhezustand keinen Strom und hat dann auch ein relativ gutes Haltemoment.
Als Ansteuerung könnte dann eine H-Brücke dienen.
Servos brauchen auch wenn sie sich nicht bewegen einen Strom für das Haltemoment und die interne Elektronik.

Technik_Amateur
21.11.2013, 13:22
Also als erstes habe ich das das langsamer drehen des Servos mit delay_ms ausprobiert mit erfolg.
Des weiteren hab ich das Programm nach den Tipps von Searcher angepasst.
Als nächstes habe ich jetzt mal versucht einen einen Timer für dieses delay_ms zu programmieren.
Wenn in die variable us_counter eine 1 geschrieben wird, soll das 1µs entsprechen (d.h. us_counter =1000 = 1000µs).
Kann das so funktionieren?

Eine Frage habe ich noch: Warum wird erst in der if-Abfrage (if (us_counter == 0)) der Wert von us_counter mit einem Wert beschrieben (us_counter = 1000; ) und nicht schon davor?




#define F_CPU 8000000UL
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdio.h>
#include "ADC.h"

ISR TIMER0_OVF_vect //interrupt für timer 0
{
if (us_counter > 0) //wenn us_counter größer als 0 ist, dann...
{
us_counter--; //us_counter-1
}
}
int main(void)
{
uint16_t Servo_soll = 512, Servo_ist;
volatile uint16_t us_counter;

ADC_Init(); //ADC initialisieren

//############################ TIMER1 Einstellen für PWM der Nachführung #######################################

ICR1 = 19999; // ICR1 = 19999 als TOP-Wert, hier werden die 20ms gebildet (8MHz / 8 * (1 + 19999)) = 50Hz
OCR1A = 1499; // OCR1A = 1499, Servomittelstellung, (8MHz / 8 * (1 + 1499)) = 666,6Hz = 1,5ms
TCCR1A = (1<<COM1A1) | (1<<WGM11); //clear OC1A on compare match, set at bottom (non-inverting)
TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS11); //WGM12,WGM13,WGM11 -> Mode14 -> Fast PWM, ICRn, set OCR1A at Bottom | prescaler = 8
DDRD = (1<<PIND5); //PORTD pin 5 auf ausgang

//############################ TIMER0 Einstellen für Millisekunden #######################################

OCR0A = 3; //OCR0A = 3 als TOP-Wert festlegen
TCCR0A = (1<<WGM01) | (1<<WGM00); //Topwert = 3, CTC-Modus,bei overflow den Timer0 auf 0 setzen
TCCR0B = (1<<CS00); //prescaler = 1 -> (8MHz/(2*1*(1+3))=1MHz = 1µs
TCNT0 = 0;
TIMSK0 = (1<<TOIE0); //interrupts freischalten

_delay_ms(2000); // kurz warten bis der Servo sich ausgerichtet hat

while(1)
{
//########################################### Nachführung des Panels #######################################
if (us_counter == 0)
{
us_counter = 1000;
//_delay_ms(1); //der Motor brauch 2s um sich um 180° zu drehen -> (2000 schritte und je schritt 1ms)
Servo_ist = ADC_Read(1); //ADC kanal 1 auslesen und in die variable adcwert_1_LDRs speichern
if (Servo_ist != Servo_soll) //Wenn der ADC-Wert der LDRs nicht mit dem Servo_soll Wert von 512 überein stimmt, dann...
{
if (Servo_ist < Servo_soll && OCR1A > 600) //Wenn der ADC-Wert kleiner ist als 512 und 0,6ms noch nicht erreicht sind...
{
OCR1A = OCR1A--; //verringere OCR1A um 1
}
if (Servo_ist > Servo_soll && OCR1A < 2600) //Wenn der ADC-Wert größer ist als 512 und 2,6ms noch nicht erreicht sind...
{
OCR1A = OCR1A++; //erhöhe OCR1A um 1
}
}
}
}
}


Gruß

Technik_Amateur

Searcher
21.11.2013, 16:41
Hallo,
ich kann erstmal nur auf die Timer0 Konfiguration eingehen. Leider paßt die Timer0 Konfiguration nicht mit Deinen Kommentaren zusammen.

Nach DB "Table 13-8. Waveform Generation Mode Bit Description" hast Du nicht CTC sondern Fast PWM eingestellt.

Wenn Mode 2 (CTC mit OCR1A als TOP) eingestellt wäre, wäre OCR0A mit 3 für 1µs Interruptfrequenz falsch belegt.
Zur Berechnung hast Du die Formel der Frequenz an OC0A genommen, wenn Du diesen über OCR0A mit COM0A0=1 toggeln läßt.
Für den 1µs Interruptabstand braucht der Timer nur halb so schnell laufen.

Auch ist das falsche Interrupt Enable Bit gesetzt. In DB "Table 13-8. Waveform Generation Mode Bit Description" letzte Spalte, wird das TOV bei MAX gesetzt. MAX wäre beim 8Bit Timer0=0xFF. Du müßtest den Timer/Counter0 Compare Match A interrupt verwenden.

;)
Gruß
Searcher

Technik_Amateur
21.11.2013, 18:16
oh#-o bei dem Modus bin ich wohl in der Zeile verrutscht.

Aber wie kommst du darauf, dass der Timer nur halb so schnell laufen muss?
Ich habe im CTC Modus nur eine Formel gefunden. Im Datenblatt steht nur, dass wenn keine prescaler oder ein niedriger verwendet wird, der CTC Modus keine double buffering funktion mehr hat. Heisst das das die 2 in der Formel raus muss und man nur noch den fclk/(N*(1+OCR0A)) benutzt? Ich kann es mir nicht so richtig erklären.

Ok das mit dem Interrupt leutet mir ein, allerdings muss ich dann zusätzlich noch das OCF0A im TIFR0-Register setzen?

Und die ISR heißt dann so?: ISR TIMER0_COMPA_vect

Gruß

Technik_Amateur

Searcher
21.11.2013, 22:00
Aber wie kommst du darauf, dass der Timer nur halb so schnell laufen muss?
Im Mode 2 läuft der Timer immer von 0 nach OCR0A. Bei OCR0A setzt er das OCF0A Flag und toggelt, falls eingestellt, den OC0A Pin. Für die Frequenz an OC0A beginnt eine Periode. Timer0 springt nach 0 und läuft wieder nach OCR0A. Bei Erreichen OCR0A wird OCF0A Flag wieder neu gesetzt, wenn es zwischendurch gelöscht wurde und OC0A getoggelt. Frequenz: 1/2 Priode ist um und zweite Halbperiode beginnt. Timer -> 0 -> OCR0A. Nun ist eine Periode der Frequenz um, das OCF0A Flag wurde zweimal gesetzt. Also für eine Periodendauer von 1µs durch Toggeln gibt es zwei Compare A Interrupts. -> Läuft Timer halb so schnell, dann Compare A Interrupt Abstand 1µs.


Ich habe im CTC Modus nur eine Formel gefunden. Im Datenblatt steht nur, dass wenn keine prescaler oder ein niedriger verwendet wird, der CTC Modus keine double buffering funktion mehr hat. Heisst das das die 2 in der Formel raus muss und man nur noch den fclk/(N*(1+OCR0A)) benutzt? Ich kann es mir nicht so richtig erklären.
Ja, die 2 muß raus. Siehe Erklärung oben. Für eine Frequenzperiode durch Toggeln gibt es zwei Compare A Interrupts. (Ist anders als im Fast PWM Modus, wenn dort die Frequenz nicht durch Toggeln erzeugt wird. Ist aber weiteres Thema)
Sacken lassen :) ...


Ok das mit dem Interrupt leutet mir ein, allerdings muss ich dann zusätzlich noch das OCF0A im TIFR0-Register setzen?
Na ja, vermutlich meinst Du das Richtige. Statt des TIMSK0 = (1<<TOIE0); muß es TIMSK0 = (1<<OCIE0A); sein. Das OCF0A Flag wird ja von der HW gesetzt. Man kann es durch setzen nur löschen.


Und die ISR heißt dann so?: ISR TIMER0_COMPA_vect
Ja. (Wenn das der korrekte Name in C für den Timer0 Compare Interrupt A ist - ich kann nicht wirklich C)

PS. ...und das "sei;" zum globalen Interrupt Enable nicht vergessen ...

Gruß
Searcher

oberallgeier
21.11.2013, 22:33
... die ISR heißt dann so?: ISR TIMER0_COMPA_vect ...Heißt schon so, wird aber im AVR-GCC so geschrieben :


ISR(TIMER0_COMPA_vect)

wkrug
21.11.2013, 23:28
Eine Frage habe ich noch: Warum wird erst in der if-Abfrage (if (us_counter == 0)) der Wert von us_counter mit einem Wert beschrieben (us_counter = 1000; ) und nicht schon davor?
Eigentlich ganz einfach.
Wenn die Variable us_couter den Wert 0 hat, dann wird die darunter stehende Routine aufgerufen.
Als ersten wird dann innerhalb dieser Routine der Variable us_couter der Wert 1000 zugewiesen und dann der Rest der da stattfinden soll ausgeführt.
Nun wird diese Routine so lange nicht mehr aufgerufen, bis die Variable us_couter wieder den Wert 0 erreicht.
us_couter wird in einem Timer Overflow Interrupt heruntergezählt, somit hat man eine Zeitverzögerung erreicht.

Würde man die Variable us_couter ausserhalb der if Abfrage auf 1000 setzen, wäre sie praktisch immer grösser als 0, da sie ja ständig neu gesetzt würde.

Übrigens teste ich meine Programme gerne mit der Simulator Funktion von AVR Studio 4.
Damit kann man das Timing wunderbar mit dem Timer und Cycle Conter überprüfen und gucken ob in der Simulation die Ausgänge auch das tun was man erwartet.
Das Prog hat da zwar ein paar kleinere Bugs, funktioniert aber ansonsten super.

Der Aufruf bzw. das Einbinden von Interrupts ist Compilerabhängig und sollte sich im Tutorial des jeweiligen Compilers finden lassen. Bei ACR GCC hätt ich jetzt nachschauen müssen.

Technik_Amateur
22.11.2013, 17:07
So, also es scheint alles soweit zu Klappen. Beim ersten Versuch hab ich die Freigabe der Interrupts via sei() ; nicht ganz an den richtigen Stellen geschrieben. Aber das ist mir sofort aufgefallen und habe sie richtig setzen können.
Viele Dank für eure Hilfe! Habe wieder einiges dazu lernen können:cool:

Ganz besonderer Dank geht an Searcher und wkrug!

Gruß

Technik_Amateur