Forum: Mikrocontroller und Digitale Elektronik Atmega8A - PWM-Einstellung über Spannungsteiler mit AD Wandlung


von Peter A. (elkopeter)


Lesenswert?

Hallo in die Runde.
Ich habe einen C-Code erstellt um eine PWM über einen Spannungsteiler 
mit Poti in der Frequenz zu regulieren.
Folgendes soll das Programm ausführen:
ADC Wandlung des Spannungsteilers.
ADCL-Register(max 255) schreibt seinen Wert in das Timer1-Register(TOP = 
511), somit soll der Startwert des Timers beeinflusst werden.
Die Ausgangsfrequenz soll in 256 Stufen von 50 auf annähernd 100Hz 
gestellt werden können
Kann das so klappen?
1
#define F_CPU 3686400L                                      // Externes Quarz -> 3,6864 MHz
2
3
#include <avr/io.h>                                        // Einbindung der Bibliotheken 
4
#include <avr/interrupt.h>
5
                                                // 50Hz Sinus-Ausgangssignal (Grundschwingung)
6
                                                // -> T/2 = 10ms
7
uint16_t array [36];                                      // 9-Bit Timer1-Auflösung -> f_PWM = 7,2 kHz
8
uint8_t Schritt = (512-BOTTOM)/(sizeof(array));                               // 10ms/(1/7,2kHz)= 72 -> 72 Änderungen
9
                                                // des Comparewertes je Halbwelle
10
volatile uint8_t n1 = 0;                                    // Comparewert wird hoch- und runergezählt 
11
volatile uint8_t n2 = 0;                                    // -> 36 Arraywerte (512/36 = 14,2 ->14)    
12
volatile uint8_t x = 0;                                      // -> Duty_max = 490/512 = 95,7%
13
volatile uint8_t BOTTOM;
14
15
16
17
void InitPWM()                                          // Initialisierung der PWM-Modi/Prescaler
18
{
19
  TCCR1A |= (1<<WGM12) | (1<<WGM11) | (1<<COM1A1) | (0<<COM1A0) |(1<<COM1B1) | (0<<COM1B0);  // Aktivieren des Fast-PMW Modus des Timer1 9-Bit-Breite(512).
20
  TCCR1B |= (1<<CS10);                                    // Abschalten der Ausgänge beim Erreichen des Compare-
21
  DDRB   |= (1<<PORTB1) | (1<<PORTB2);                            // Wertes. Prescaler = 1. PortB1 u. PortB2 als Ausgänge festlegen.                                            
22
  DDRD   |= (1<<PORTD2);                                    // PORTD2 als Ausgang für 5V an Spannungsteiler  
23
  DDRC   |= (1<<PORTC0);                                    // PORTC als Eingang von Poti
24
  ADCSRA |= (1<<ADEN) | (1<<ADIE) | (1<<ADFRA) | (0<<ADPS2) | (0<<ADPS1) | (1<<ADPS0);    // AD-Wandlung aktivieren + Interruptfreigabe + Free Run + Presacle = 2; 
25
  ADMUX  |= (0<<REFS1) | (0<<REFS0) | (0<<ADLAR) | (0<<MUX3) | (0<<MUX2) | (0<<MUX1) | (0<<MUX0); 
26
}                                                // externe Referenzspannung (High also 5V) + ADC0 als Eingang definieren
27
28
int main()
29
{
30
  InitPWM();
31
  PORTD  |= 0x04;
32
  for(uint8_t i=0; i<sizeof(array); i++)                            // Schleife füllt array;
33
  {
34
    array[i] = array[i-1]+Schritt;
35
  }
36
                                                
37
  TIMSK |= (1<<TOIE1);                                    // Freischalten des OverflowInterrupts                                        // für beide Compare-Register
38
  sei();                                            // globale Interruptfreigabe
39
  for(;;)
40
  {
41
  }
42
  return 0;
43
}
44
45
46
ISR(TIMER1_OVF_vect)                                      // ISR für Overflowinterrupt
47
{
48
  //static uint16_t cnt;                                    // cnt verlangsamt die Comparewertumschaltung
49
  OCR1A = array[n1];                                      // Comparewert1 wird überschrieben
50
  OCR1B = array[n2];                                      // Comparewert2 wird überschrieben                                            // x als Zählvariable für die Einteilung in T/4
51
  //cnt++;                                          
52
  //if(cnt==19)                                        // Vorteiler für die Comparewert Weiterschaltung
53
  //  {
54
      x++;
55
  //    cnt=0;
56
      if(x<sizeof(array))                                  // Hochzählen des Comparewertes für Q1/Q4
57
        {
58
          n2 = 0;
59
          n1++;
60
        }
61
      if((x>=sizeof(array))&&(x<(2*sizeof(array))))                    // Runterzählen des Comparewertes für Q1/Q4
62
        {
63
          n2=0;
64
          n1--;
65
        }
66
      if((x>=(2*sizeof(array)))&&(x<(3*sizeof(array))))                  // Hochzählen des Comparewertes für Q2/Q3
67
        {
68
          n1=0;
69
          n2++;
70
        }
71
      if((x>=(3*sizeof(array)))&&(x<(4*sizeof(array))))                  // Runterzählen des Comparewertes für Q2/Q3
72
        {
73
          n1=0;
74
          n2--;
75
        }
76
      if(x==(4*sizeof(array)))                              // Rücksetzen von x nach dem letzten Comparewert
77
        {
78
          x=0;
79
          n1=0; 
80
          n2=0;
81
        }
82
    //}
83
    ISR(ADC)
84
    {
85
             BOTTOM = ADC;
86
             TCNT1 = BOTTOM;
87
    }
88
}

von Karl H. (kbuchegg)


Lesenswert?

Ich versteh deinen Code zwar nicht wirklich, aber spätestens hier
1
    ISR(ADC)
2
    {
3
             BOTTOM = ADC;
4
             TCNT1 = BOTTOM;
5
    }

macht sich meine Alarmklingel höllisch bemerkbar.

Lass
den
Timer
in
Ruhe
arbeiten
!

Wenn du dauernd am TCNT1 rumfingerst, wirst du nie auch nur irgendeine 
vernünftige PWM kriegen.

> Die Ausgangsfrequenz soll in 256 Stufen von 50 auf
> annähernd 100Hz gestellt werden können

Dazu benutzt man üblicherweise eine DDS


> (1<<WGM12) | (1<<WGM11)

Welcher Timer MOdus ist das? (bin zu faul im Datenblatt nachzusehen)

Auch bin ich mir nicht sicher, ob da überhaupt ein Overflow Interrupt 
ausgelöst wird. Denn streng genommen gibt es ja keinen Overflow des 
Timers.

Aber alles in allem.
Nein, ich denke nicht das das so funktioniert.
Wenn du die Frequenz beeinflussen willst, dann musst du den TOP Wert des 
Timers manipulieren. Was dann allerdings auch wieder bedeutet, dass die 
Compare-Werte entsprechend skaliert werden müssen, damit sich der 
Duty-Cycle jedes deiner Kurven-Stützpunkte aus dem Array nicht 
verändert.

In Summ dürfte eine klassische DDS deutlich einfacher sein, auch wenn 
bei deiner Lösung die Kurvenform theoretisch besser reproduziert wird, 
weil bei allen Frequenzen alle Samples aus dem Array benutzt werden. Bei 
einer Sample-Anzahl die groß genug ist, und deinen niedrigen Freuqenzen, 
fällt das kaum ins Gewicht, wenn durch das DDS ein paar Sample-Points 
pro Wellenzug unter den Tisch fallen.

von Peter A. (elkopeter)


Lesenswert?

> Lass
> den
> Timer
> in
> Ruhe
> arbeiten
> !
>
> Wenn du dauernd am TCNT1 rumfingerst, wirst du nie auch nur irgendeine
> vernünftige PWM kriegen.
>
>> Die Ausgangsfrequenz soll in 256 Stufen von 50 auf
>> annähernd 100Hz gestellt werden können
>
> Dazu benutzt man üblicherweise eine DDS
>
>
>> (1<<WGM12) | (1<<WGM11)
>
> Welcher Timer MOdus ist das? (bin zu faul im Datenblatt nachzusehen)
>
> Auch bin ich mir nicht sicher, ob da überhaupt ein Overflow Interrupt
> ausgelöst wird. Denn streng genommen gibt es ja keinen Overflow des
> Timers.


Ok also keine Timer1 Manipulation außer dem TOP Wert -> d.h. aber auch, 
dass dann nur ein PWM_Ausgang verwendet werden kann.
Ich verwende den Fast PWM Modus (Kommentierung ist irgendwo ins Nirvana 
verschwunden).
Ich hab den Code aus einem vorher angefertigtem funktionierenden Code 
für 50Hz Sinus Ausgansspannung abgeändert (zumindest aufm Oszi sah das 
Ganze recht gut aus).
Der Overflow kommt auf jedenfall, sonst würde bei der Ausgabe an 2 LEDs 
nicht abwechselnd die Helligkeit verändert werden.
Was meinst du mit DDS?

von Thomas E. (thomase)


Lesenswert?

Peter A. schrieb:
> Ok also keine Timer1 Manipulation außer dem TOP Wert -> d.h. aber auch,
> dass dann nur ein PWM_Ausgang verwendet werden kann.
Wieso? Timermode 8, 10 und 14.

mfg.

von Karl H. (kbuchegg)


Lesenswert?

Peter A. schrieb:

> Was meinst du mit DDS?

Im Prinzip sowas
1
ISR( ... )
2
{
3
  x += Schritt;
4
  OCR1xx = Samples[ x / 256 ];
5
}

und die Frequenz wird durch Verändern des Wertes 'Schritt' eingestellt. 
(Die PWM wird nicht angetastet sondern läuft mit konstanter Frequenz 
durch. Lediglich die Zeiten, in denen man der PWM einen jeweils anderen 
zu realisierenden Duty-Cycle vorgibt, verändertn sich).
Wobei die Division durch 256 im Grunde nichts anderes als eine Variante 
von Fixedpoint Arithmetik darstellt. Schritt hat 8-Bits für die 
Nachkommastellen. Was wiederrum notwendig ist, damit man die Frequenz 
fein genug einstellen kann, weil sich ja Schritt aus der gewünschten 
Frequenz und der Anzahl der Samples durch eine Division errechnet.

Google mal nach DDS.
Dazu sollte sich hier im Forum einiges finden lassen.

von Hubert G. (hubertg)


Lesenswert?

Du solltest auch im ADMUX ADLAR aktivieren und nur ADCH abfragen. Damit 
hast du den richtigen 8bit-Wert für deinen Timer.

von Peter A. (elkopeter)


Lesenswert?

Was meinst du mit Timermode 8,10,14 ?
Ich kann unterschiedliche Auflösungen einstellen also von 8-10 Bit für 
den Timer1.
Im Datenblatt steht: "When using OCR1A as TOP value in PWM mode, the 
OCR1A Register can not be used for generating PWM output."
-> Also kann hier keine PWM Ausgabe an den beiden Ausgängen erfolgen.

von Karl H. (kbuchegg)


Lesenswert?

Hubert G. schrieb:
> Du solltest auch im ADMUX ADLAR aktivieren und nur ADCH abfragen. Damit
> hast du den richtigen 8bit-Wert für deinen Timer.

Und ich seh noch nicht, inwiefern die ADC Abfrage mittels Interrupt 
irgendwas bringt.
Ganz im Gegenteil, würde ich die ADC Abfrage und Auswertung in die 
Hauptschleife legen. Der ADC arbeitet schnell genug, wenn da ein Mensch 
am Poti dreht.
Aber: wird die ADC Auswertung in einer ISR gemacht, dann muss der 
'Overflow-Code' seinerseits warten, wenn der ADC gerade am Zug ist.
Ist die ADC Auswertung aber in der Hauptschleife, dann kann die 
Kurvengenerierung diese unterbrechen und den nächsten Sample ausgeben.
Und das erscheint mir wichtiger zu sein, als ob die Frequenz erst ein 
paar µs später vom Poti abgegriffen und aktiviert wird.

von Spess53 (Gast)


Lesenswert?

Hi

>Ich verwende den Fast PWM Modus (Kommentierung ist irgendwo ins Nirvana
>verschwunden).

Warum Fast PWM? Wenn du eine variable Frequenz erzeugen willst ist CTC 
der geeignete Mode. PWM ist für ein variables Tastverhältnis bei 
konstanter Frequenz.

MfG Spess

von Thomas E. (thomase)


Lesenswert?

Peter A. schrieb:
> Was meinst du mit Timermode 8,10,14 ?
> Ich kann unterschiedliche Auflösungen einstellen also von 8-10 Bit für
> den Timer1.
> Im Datenblatt steht: "When using OCR1A as TOP value in PWM mode, the
> OCR1A Register can not be used for generating PWM output."
> -> Also kann hier keine PWM Ausgabe an den beiden Ausgängen erfolgen.

Dann guck doch in die Tabelle im Datenblatt. Das sind die Modes mit ICR 
als Top.

mfg.

von Peter A. (elkopeter)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Peter A. schrieb:
> und die Frequenz wird durch Verändern des Wertes 'Schritt' eingestellt.
> (Die PWM wird nicht angetastet sondern läuft mit konstanter Frequenz
> durch. Lediglich die Zeiten, in denen man der PWM einen jeweils anderen
> zu realisierenden Duty-Cycle vorgibt, verändertn sich).
> Wobei die Division durch 256 im Grunde nichts anderes als eine Variante
> von Fixedpoint Arithmetik darstellt. Schritt hat 8-Bits für die
> Nachkommastellen. Was wiederrum notwendig ist, damit man die Frequenz
> fein genug einstellen kann, weil sich ja Schritt aus der gewünschten
> Frequenz und der Anzahl der Samples durch eine Division errechnet.
>
> Google mal nach DDS.
> Dazu sollte sich hier im Forum einiges finden lassen.

OK also im prinzipiell kann ich auch einfach meine Array Größe 
verändern, was den gleichen Effekt haben sollte.

von Peter A. (elkopeter)


Lesenswert?

Spess53 schrieb:
> Hi
>
>>Ich verwende den Fast PWM Modus (Kommentierung ist irgendwo ins Nirvana
>>verschwunden).
>
> Warum Fast PWM? Wenn du eine variable Frequenz erzeugen willst ist CTC
> der geeignete Mode. PWM ist für ein variables Tastverhältnis bei
> konstanter Frequenz.
>
> MfG Spess

Hab ich das richtig verstanden, dass im CTC-Modus der Zählerhochlauf 
beim eingestellten TOP-Wert abgebrochen wird? Wenn ja, wird mein 
Overflow Interrupt dann überhaupt ausgelöst?

Zum ICR1 Register:
Kann ich das dann einfach mit dem eingelesenen ADC-Wert beschreiben?

Zu der ADC-ISR:
Karl Heinz Buchegger hat schon erwähnt, dass es hier Probleme mit der 
Overflow-ISR geben wird.
Schreibe ich dann einfach in der Dauerschleife der main meinen ADC-Wert 
ins ICR1 Register?
[C]
for(;;)
{
   ICR1 = ADC;
}

von Thomas E. (thomase)


Lesenswert?

Peter A. schrieb:
> Hab ich das richtig verstanden, dass im CTC-Modus der Zählerhochlauf
> beim eingestellten TOP-Wert abgebrochen wird?
>
Der Zähler fängt beim Erreichen von Top wieder von vorne an oder zählt 
bei einigen PWM-Modes rückwärts.
> Zum ICR1 Register:
> Kann ich das dann einfach mit dem eingelesenen ADC-Wert beschreiben?
Ja.
> Zu der ADC-ISR:
> Karl Heinz Buchegger hat schon erwähnt, dass es hier Probleme mit der
> Overflow-ISR geben wird.
Nein. Beimm Erreichen von Top wird das Overflow-Flag gesetzt und ggf. 
der Interrupt ausgelöst.

> Schreibe ich dann einfach in der Dauerschleife der main meinen ADC-Wert
> ins ICR1 Register?
> [C]
> for(;;)
> {
>    ICR1 = ADC;
> }
Nein.
Du liest den ADC in der Overfow-ISR vom Timer aus und startest den ADC 
neu.
Beim nächsten Overflow hast du dann wieder den zuletzt gemessenen Wert.
Die ADC-ISR brauchst du gar nicht. Das Poti muss nicht mit maximal 
möglicher Geschwindigkeit eingelesen werden. Genauso wenig wie das ICR 
ständig neu beschrieben werden muss.
Ausserdem stellst du damit sicher, daß du ICR beschreibst, wenn der 
Counter nahezu 0 ist. Sonst könnte der neue ICR auch mal <TCNT sein und 
der Timer dreht eine grosse Runde über 65535 bis TCNT = ICR ist. In 
jedem Fall solltest du den Minimalwert begrenzen.

mfg.

von Peter A. (elkopeter)


Lesenswert?

Ah ok danke. Das hilft mir auf jedenfall weiter.
Jetzt muss ich doch nochmal blöd nachfragen, weil ich heute erst mit der 
ADC-Thema angefangen habe:

Heißt das, dass ich bei jedem Overflow Interrupt meine Register
ADCSRA |= (1<<ADEN) | (1<<ADSC) | .... setzten muss?

Also ADSC startet meine AD Conversion, so stehts zumindest im Datenblatt 
und setzt sich nach Abschluss wieder auf 0(vorausgesetzt ich hab den 
free running Modus nicht an).

von Thomas E. (thomase)


Lesenswert?

Peter A. schrieb:
> Ah ok danke. Das hilft mir auf jedenfall weiter.
> Jetzt muss ich doch nochmal blöd nachfragen, weil ich heute erst mit der
> ADC-Thema angefangen habe:
>
> Heißt das, dass ich bei jedem Overflow Interrupt meine Register
> ADCSRA |= (1<<ADEN) | (1<<ADSC) | .... setzten muss?
>
> Also ADSC startet meine AD Conversion, so stehts zumindest im Datenblatt
> und setzt sich nach Abschluss wieder auf 0(vorausgesetzt ich hab den
> free running Modus nicht an).

Nur starten:

ICR = ADCH;
if(ICR < MINIMUM) ICR = MINIMUM;
ADCSRA |= (1 << ADSC);

Der Rest ändert sich ja nicht.

Und in der Initialisierung das ADLAR-Bit setzen!


mfg.

von Spess53 (Gast)


Lesenswert?

Hi

>Nein. Beim Erreichen von Top wird das Overflow-Flag gesetzt und ggf.
>der Interrupt ausgelöst.

Im CTC-Mode wird je nach Top-Register das OCR- bzw. IC-Interrupt Flag 
gesetzt aber kein Overflow IR Flag. Bei Fast-PWM das Overflow-Interrupt 
Flag und bei variablen Top zusätzlich das entsprechende Interrupt Flag 
des Registers gesetzt.

MfG Spess

von Peter A. (elkopeter)


Lesenswert?

Thomas Eckmann schrieb:

> Der Rest ändert sich ja nicht.
>
> Und in der Initialisierung das ADLAR-Bit setzen!


Was hat es mit dem ADLAR-Bit auf sich? Im Datenblatt steht, dass es die 
Werte bei gesetztem Zustand vom MSB abwärts in das High-Byte schreibt 
und bei 0 vom LSB aufwärts ins Low-Byte des ADC-Registers.
Werden die beiden Bytes falsch zusammengesetzt beim überschreiben auf 
das ICR1 Register, oder warum muss dieses Bit gesetzt sein?

von Peter A. (elkopeter)


Lesenswert?

Spess53 schrieb:
> Im CTC-Mode wird je nach Top-Register das OCR- bzw. IC-Interrupt Flag
> gesetzt aber kein Overflow IR Flag. Bei Fast-PWM das Overflow-Interrupt
> Flag und bei variablen Top zusätzlich das entsprechende Interrupt Flag
> des Registers gesetzt.

Heißt das, dass ich statt dem Overflow Interrupt-Vektor den Capture 
Event Vektor verwenden muss?

von Spess53 (Gast)


Lesenswert?

Hi

>Heißt das, dass ich statt dem Overflow Interrupt-Vektor den Capture
>Event Vektor verwenden muss?

Bei CTC, ja.

MfG Spess

von Thomas E. (thomase)


Lesenswert?

Peter A. schrieb:
> Thomas Eckmann schrieb:
>
>> Der Rest ändert sich ja nicht.
>>
>> Und in der Initialisierung das ADLAR-Bit setzen!
>
>
> Was hat es mit dem ADLAR-Bit auf sich? Im Datenblatt steht, dass es die
> Werte bei gesetztem Zustand vom MSB abwärts in das High-Byte schreibt
> und bei 0 vom LSB aufwärts ins Low-Byte des ADC-Registers.
> Werden die beiden Bytes falsch zusammengesetzt beim überschreiben auf
> das ICR1 Register, oder warum muss dieses Bit gesetzt sein?

Mit ADLAR = 1 wird der ADC-Wert um 6 Bit nach links geschoben. Damit 
stehen die Bits 2 - 9 im ADCH. Die nimmst du als ADC-Ergebnis.
Wenn du mit ADLAR = 0 die unteren 8 Bits verwendest(BOTTOM = ADC;), hast 
du 1. Wackler auf den unteren beiden Bits und wenn du dein Poti drehst, 
bist du nach 1/4 Umdrehung wieder auf 0. Und der Wert wackelt dann auch 
mindestens zwischen 1 und 255 hin umd her.
Mit ADLAR hast du praktisch einen sauberen 8-Bit-Wandler und drehst dein 
Poti von 0 - 255 über den vollen Bereich.

> Im CTC-Mode wird je nach Top-Register das OCR- bzw. IC-Interrupt Flag
> gesetzt aber kein Overflow IR Flag.
Stimmt. Der OVF wird zwar bei Timer-Overflow gesetzt. Wenn der Timer 
allerdings richtig läuft, wird dieser Wert nie erreicht.


mfg.

von Karl H. (kbuchegg)


Lesenswert?

> Was hat es mit dem ADLAR-Bit auf sich?

Kurz gesagt (und ein wenig vereinfacht)

* ADLAR nicht gesetzt  ->  du hast einen 10 Bit ADC und musst aus ADCH 
und ADCL den 10 Bit Wert zusammensetzen
* ADLAR gesetzt -> du hast einen 8 Bit ADC und in ADCH steht bereits das 
komplette 8 Bit Ergebnis


Für dich heißt das:
Du willst deine Spannung in 256 Stufen aufteilen (also 8 Bit).
Wozu soll der Code dann zuerst ein 10 Bit Ergebnis aus 2 ADC Registern 
zusammensetzen, nur um dann dieses Ergebnis durch rechtsverschieben um 2 
Bit wieder zu einem 8 Bit Ergebnis zu machen, wenn du dieses 8 Bit 
Ergebnis dir auch direkt vom ADC abholen kannst?

von Peter A. (elkopeter)


Lesenswert?

Kann ich mein ICR1 Register nicht mit dem 10Bit Wert von ADC 
überschreiben?

ICR1=ADC; // ohne das Tauschen mit ADLAR=1

von Thomas E. (thomase)


Lesenswert?

Peter A. schrieb:
> Kann ich mein ICR1 Register nicht mit dem 10Bit Wert von ADC
> überschreiben?
>
> ICR1=ADC; // ohne das Tauschen mit ADLAR=1
Ja sicher.
Dann hast du bis zu 1024 Schritte. Also die vierfache Auflösung.
In deinem Ausgangspost hattest du aber den ADC-Wert (unsauber) auf 8 Bit 
heruntergebrochen. Deswegen der Hinweis auf ADLAR, um das ohne Risiken 
und Nebenwirkungen zu machen.

mfg.

von Peter A. (elkopeter)


Lesenswert?

Also Thema hat sich erledigt. Habs hingekriegt mit dem Fast PWM Modus 
mit ICR1 als TOP.

Hier der Code:
1
#define F_CPU 3686400L                                      // Externes Quarz -> 3,6864 MHz
2
3
#include <avr/io.h>                                        // Einbindung der Bibliotheken
4
#include <avr/interrupt.h>
5
                                                // 50Hz Sinus-Ausgangssignal (Grundschwingung)
6
                                                // -> T/2 = 10ms
7
uint16_t array [36];                                      // max.10-Bit Timer1-Auflösung -> f_PWM_min = 3,6 kHz
8
                                                 // des Comparewertes je Halbwelle
9
volatile uint8_t n1 = 0;                                    // Comparewert wird hoch- und runergezählt
10
volatile uint8_t n2 = 0;                                    // -> 72 Arraywerte 
11
volatile uint8_t x = 0;                                      // Zähler für Einteilung des Ausgangssignals in 1/4*T 
12
uint8_t Schritt = 1024/(sizeof(array));                              // Duty-Cycle bei max. Auflösung = 95,7% 
13
14
15
void InitPWM()                                          // Initialisierung der PWM-Modi/Prescaler
16
{
17
  TCCR1A |= (1<<WGM11)  | (1<<COM1A1) | (0<<COM1A0) |(1<<COM1B1) | (0<<COM1B0);        // Aktivieren des Fast PWM Modus des Timer1 mit ICR1 als TOP.
18
  TCCR1B |= (1<<WGM13)  | (1<<WGM12)  |(1<<CS10);                        // Abschalten der Ausgänge beim Erreichen des Compare-
19
  DDRB   |= (1<<PORTB1) | (1<<PORTB2);                            // Wertes. Prescaler = 1. PortB1 u. PortB2 als Ausgänge
20
                                                // festlegen.
21
  DDRC   |= (0<<PORTC0);                                    // PORTC0 als AD-Eingang
22
  ADCSRA |= (1<<ADEN) | (1<<ADSC);                              // AD-Wandlung aktivieren        
23
}
24
25
int main()
26
{
27
  InitPWM();
28
  for(uint8_t i=0; i<sizeof(array); i++)                            // Schleife füllt array;
29
  {
30
    array[i] = array[i-1]+Schritt;
31
  }
32
  TIMSK |= (1<<TICIE1);                                    // Freischalten des CaptureEventInterrupts                                        // für beide Compare-Register
33
  sei();                                            // globale Interruptfreigabe
34
  for(;;)
35
  {
36
  }
37
  return 0;
38
}
39
40
41
ISR(TIMER1_CAPT_vect)                                      // ISR für cCaptureEventinterrupt
42
{  
43
  
44
  //static uint16_t cnt;                                    // cnt verlangsamt die Comparewertumschaltung
45
  ADCSRA |= (1<<ADSC);                                    // AD-Wandlung starten                            
46
  ICR1 = ADC;                                          // ADC auf ICR1 schreiben  
47
  OCR1A = array[n1];                                      // Comparewert1 wird überschrieben
48
  OCR1B = array[n2];                                      // Comparewert2 wird überschrieben                                            // x als Zählvariable für die Einteilung in T/4
49
  //cnt++;
50
  //if(cnt==19)                                        // Vorteiler für die Comparewert Weiterschaltung
51
  //  {
52
  x++;
53
  //    cnt=0;
54
  if(x<sizeof(array))                                      // Hochzählen des Comparewertes für Q1/Q4
55
  {
56
    n2 = 0;
57
    n1++;
58
  }
59
  if((x>=sizeof(array))&&(x<(2*sizeof(array))))                        // Runterzählen des Comparewertes für Q1/Q4
60
  {
61
    n2=0;
62
    n1--;
63
  }
64
  if((x>=(2*sizeof(array)))&&(x<(3*sizeof(array))))                      // Hochzählen des Comparewertes für Q2/Q3
65
  {
66
    n1=0;
67
    n2++;
68
  }
69
  if((x>=(3*sizeof(array)))&&(x<(4*sizeof(array))))                      // Runterzählen des Comparewertes für Q2/Q3
70
  {
71
    n1=0;
72
    n2--;
73
  }
74
  if(x==(4*sizeof(array)))                                  // Rücksetzen von x nach dem letzten Comparewert
75
  {
76
    x=0;
77
    n1=0;
78
    n2=0;
79
  }
80
  //}
81
  
82
}

von Karl H. (kbuchegg)


Lesenswert?

1
  ...
2
  ADCSRA |= (1<<ADSC);                                    // AD-Wandlung starten                            
3
  ICR1 = ADC;   
4
  ...

falsch.
Du kannst nicht den ADC starten und dir gleich darauf sofort das 
Ergebnis abholen.
Der ADC braucht ein bischen Zeit, bis er das Ergebnis fertig hat!

So gehts normalerweise
1
  ADCSRA |= (1<<ADSC);                                    // AD-Wandlung starten                            
2
  while( ADCSRA & ( 1 << ADSC ) )                         // Warten bis das Ergebnis vorliegt.
3
    ;
4
5
  ICR1 = ADC;                                             // Ergebnis holen

aber das willst du hier nicht machen, weil es in einer ISR passiert.
Jetzt ist es aber so, dass wir wissen, dass er ADC nicht ewig für sein 
Ding braucht. D.h. man kann das ganze ein wenig umdrehen. Macht man es 
so
1
   ...
2
3
   ICR1 = ADC;      // den letzten Wert holen
4
5
   ADCSRA |= ( 1 << ADSC );    // und den ADC gleich wieder starten
6
                               // Das Ergebnis dieser WAndlung wird
7
                               // abgeholt, wenn der Code das nächste
8
                               // mal aufgerufen wird
9
10
  ...

dann arbeitet der ADC, während der Rest des Programms abläuft. Und wir 
wissen: dieser Rest benötigt mehr Zeit als der ADC für eine Wandlung 
braucht. Wird die ISR das nächste mal aufgerufen, dann ist der hier 
gestartete Wandlungsdurchgang schon längst fertig und das Ergebnis liegt 
bereits zur Abholung bereit.

von Karl H. (kbuchegg)


Lesenswert?

Ausserdem denke ich nicht, dass deine ganzen sizeof Ausdrücke richtig 
sind.

sizeof liefert die gesuchte Größe in Bytes!

d.h. bei
1
uint16_t array [36];

liefert sizeof(array) den Wert 72.
Und ich denke nicht, dass du das berücksichtigt hast.

Willst du mittels sizeof die Arraygröße feststellen, dann geht das so
1
#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(*x))
2
3
....
4
5
   if( x < ARRAY_SIZE(array) )
6
     ....


(und nenn die Variable nicht 'array'. Das ist recht nichtssagend. Das 
das ein Array ist, das sehe ich auch so.
Welche Funktion hat den dieses Array? Welche WErte sind denn in ihm 
gespeichert?
Das sind ja wohl die Sample-Werte zur Kurvenerzeugung. Also könnte man 
das zb samples nennen. Oder stuetzpunkte. Oder sinus. Oder ....
Egal wie - aber benenne Variablen nach ihrer Funktion und nicht nach 
ihrem Datentyp.

von Peter A. (elkopeter)


Lesenswert?

Danke für die Hinweise. Ich werde das ganze zukünftig berücksichtigen.

von Karl H. (kbuchegg)


Lesenswert?

Vorsicht!

So was
1
uint8_t Schritt = 1024/(sizeof(array));
2
3
....
4
5
  for(uint8_t i=0; i<sizeof(array); i++)
6
  {
7
    array[i] = array[i-1]+Schritt;
8
  }
9
....

kann zu numerischen Problemen führen! Schritt ist eine Ganzzahlvariable. 
D.h. was auch immer theoretisch an Kommastellen bei der Division 
entstehen würden, sie sind nicht in Schritt enthalten. Wenn du daher 
dann in weiterer Folge aufsummierst, dann summierst du auch den dadurch 
eingeführten Fehler mit auf, der dann immer größer wird.

Mal ein Beispiel mit etwas anderen Zahlen, du willst 11 Zahlen, die den 
Bereich 0 bis 19 abdecken. 0 (die erste Zahl), soll sich auf 0 abbilden, 
der Funktionswert für 10 soll 19 sein.
Also diese Zuordnung ist zu realisieren
1
  Nr    0    1    2    3    4    5    6    7    8    9   10
2
 ----------------------------------------------------------
3
  Wert  0  1.9  3.8  5.7  7.6  9.5 11.4 13.3 15.2 17.1 19.0

wie errechnen sich die Werte, wenn man Nr hat? Ganz einfach
1
  Wert = Nr * 1.9;
warum 1.9?
Das folgt aus dem gewünschten Wertebereich und der gewünschten Anzahl 
der Zahlen.
1
   Schrittweite = ( 19.0 - 0.0 ) / ( 10 - 0 )
2
   Schrittweite ist hier also 19.0 / 10.
3
   Und das ergibt 1.9

Rechne die Werte nach. stimmt alles

Aber:
Deine variable schritt ist aber nicht in der Lage einen Werte von 1.9 zu 
speichern. Die kann nur die 1 Speichern.

Welche Tabelle ergibt sich daraus jetzt bei dir mit diesem Code?
1
  for(uint8_t i=0; i<sizeof(array); i++)
2
  {
3
    array[i] = array[i-1]+Schritt;
4
  }

Nun. Da entsteht mit den hier gewählten Beispielzahlen diese Zuordnung
1
  Nr    0    1    2    3    4    5    6    7    8    9   10
2
 ----------------------------------------------------------
3
  Wert  0  1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0

und das ist noch nicht mal annähernd korrekt. Die Funktionswerte laufen 
nicht wie gefordert  von 0 bis 19.0 sondern von 0 bis 10.0
Warum ist das so?
Weil die korrekte Schrittweite eigentlich 1.9 gewesen wäre, der Code 
aber nur mit 1 gerechnet hat. d.h. bei jedem Schritt der aufaddiert 
wird, fehlen 0.9. Und: bei jeder jeweils nächsten zu ermittelnden Zahl 
wird dieser Fehler mitgeschleppt und es kommen weitere 0.9 Fehler gleich 
noch mit dazu!

von Karl H. (kbuchegg)


Lesenswert?

Peter A. schrieb:
> Danke für die Hinweise. Ich werde das ganze zukünftig berücksichtigen.

Nicht zukünftig.

Jetzt!

von Karl H. (kbuchegg)


Lesenswert?

Mit der ganzen PWM Sache gibt es auch noch ein Problem.

Du kannst nicht einfach die OCR Werte so mir nichts dir nichts zuweisen!
Wenn du ICR1 veränderst, müssen sich auch die OCR Werte verändern, damit 
der angestrebte Duty Cycle erreicht wird!

von Peter A. (elkopeter)


Lesenswert?

Also ich änder das Ganze jetzt ab. Das mit dem numerischen Fehler und 
die fehlende Duty_cycle Anpassung waren mir schon klar. Aber trotzdem 
danke für den Hinweis.

von Karl H. (kbuchegg)


Lesenswert?

Dann würde ich vorschlagen, du präsentierst dein Programm das nächste 
mal erst dann, wenn du denkst dass es korrekt ist. Denn dann spare ich 
mir in der Zwischenzeit die Analyse, was in deinem Code noch alles 
fehlerhaft ist.

von Peter A. (elkopeter)


Lesenswert?

Werde ich tun. Danke nochmal für die Hilfe.

von Peter A. (elkopeter)


Lesenswert?

Noch eine Frage:
Zur Anpassung meiner Duty Cycle Werte müsste ich meine Werte in der 
Dauerschleife der main-Funktion beschreiben oder gibt´s da eine 
sinnvollere Lösung? Wie würde ich den numerischen Fehler meiner 
Schrittwerte beikommen?
Könnte ich mein array mit float-Zahlen beschreiben und vor der 
Index-Auswahl die Zahlen auf eine int Variable überschreiben? Oder 
lassen sich float Zahlen auch mit dem Timerwert vergleichen?

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
Noch kein Account? Hier anmelden.