Forum: Mikrocontroller und Digitale Elektronik Atmega8A - C-Code PWM Erzeugung was mache ich falsch?


von Peter A. (elkopeter)


Angehängte Dateien:

Lesenswert?

Hallo in die Runde.
Im Anhang findet sich das gewünschte Ausgangssignal (Schema schnell 
hingeschmiert mit der Hand) einer H-Brücke, dass mit dem Atmega8A über 
entsprechende Halbbrückentreiber erzeugt werden soll.(50Hz Sinus)
Ich benutze das myAVR board MK2 und AVR Studio 6.1.
Die Taktfrequenz wird über ein externes Quarz mit 3,6864Mhz beziffert.

Ich hab jetzt mal einen C-Code generiert der den Timer1 und Fast Pwm 
Mode nutzt, um an Port B1 und B2 über 2 LEDs die beiden Signale 
auszugeben.

Ich habe jetzt auf ziemlich umständliche Art probiert meine beiden 
Ausgänge über Interrupts und diverse Zähler nacheinander auszugeben, 
aber irgendwie klappt das nicht so recht.

Ich werde jetzt einfach mal den Code nachfolgend reinkopieren.
Vielleicht kann mir ja einer von euch weiterhelfen.
1
#define F_CPU 3686400L
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <util/delay.h>
6
7
uint8_t  count1= 0;                          // Zähler für 1. Halbbrücke
8
uint8_t  count2= 0;                          // Zähler für 2. Halbbrücke
9
uint16_t array [18] = {0, 28, 56, 84, 112, 140, 168, 196, 224, 252, 280, 308, 336, 364, 392, 420, 448, 476} ;         // 18 array Comparewerte 
10
uint8_t n1 = 1;                              // array Zähler1
11
uint8_t n2 = 1;                              // array Zähler2
12
13
14
15
16
void InitPWM()                                          // Initialisierung der PWM-Modi/Prescaler
17
{
18
  TCCR1A |= (1<<WGM12) | (1<<WGM11) | (1<<COM1A1) | (0<<COM1A0) |(1<<COM1B1) | (0<<COM1B0);  // Aktivieren des Phase-Correct-PMW Modus des Timer1 9-Bit-Breite(512).
19
  TCCR1B |= (1<<CS10);                                    // Abschalten der Ausgänge beim Erreichen des Compare-
20
  DDRB   |= (1<<PORTB1) | (1<<PORTB2);                            // Wertes. Prescaler = 1. PortB1 u. PortB2 als Ausgänge 
21
                                                // festlegen.
22
}
23
24
int main()
25
{
26
  InitPWM();
27
  TIMSK |= (1<<OCIE1A) | (1<<OCIE1B) | (1<<TOIE1);                      // Freischalten der Compare- und OverflowInterrupts 
28
                                                // für beide Compare-Register
29
  
30
  sei();                                            // globale Interruptfreigabe
31
  for(;;)
32
  {
33
    OCR1A = array[n1];
34
    _delay_ms(50);
35
    OCR1B = array[n2];
36
    _delay_ms(50); 
37
  }
38
39
return 0;  
40
}
41
42
43
ISR(TIMER1_OVF_vect)                                      // ISR für Compareinterrupt A/B
44
{
45
  if(count1<=8)                                        
46
  {
47
    n1++;
48
  }
49
  if((count1<=17)&&(count1>8))
50
  {
51
    n1--;
52
  }
53
  if((count2<=26)&&(count2>17))
54
  {
55
    n2++;
56
  }
57
  if((count2<=35)&&(count2>26))
58
  {
59
    n2--;
60
  }
61
  
62
  if(count1>35)
63
  {
64
    count1=0;
65
    count2=0;
66
    n1=1;
67
    n2=1;
68
  }
69
  
70
}
71
ISR(TIMER1_COMPA_vect)
72
{
73
  count1++;
74
}
75
ISR(TIMER1_COMPB_vect)
76
{
77
  count2++;
78
}

von Peter II (Gast)


Lesenswert?

n1 und n2 müssen volatile sein.

von Peter A. (elkopeter)


Lesenswert?

Ah ok hab grad nachgelesen, dass Variablen in ISRen als volatile 
deklariert werden müssen danke.
Das Blinken der LEDs sieht jetzt annähernd abwechselnd aus. Aber vom 
gewünschten Ausgangssignal bin ich wohl noch kilometer weit entfernt 
oder?

von Falk B. (falk)


Lesenswert?

@ Peter A. (elkopeter)

>    WP_20130910_001.jpg
>    1 MB, 25 Downloads

Naja, das könnte man auch ein wenig kleiner hinkriegen ,zumal es 
unscharf ist.

>Ich hab jetzt mal einen C-Code generiert der den Timer1 und Fast Pwm
>Mode nutzt, um an Port B1 und B2 über 2 LEDs die beiden Signale
>auszugeben.

>Ich habe jetzt auf ziemlich umständliche Art probiert meine beiden
>Ausgänge über Interrupts und diverse Zähler nacheinander auszugeben,

In der Tat.

>aber irgendwie klappt das nicht so recht.

Vieleicht alles mal einfach versuchen?

Wie funktioniert sowas denn normalerweise? Die Samples/PWM-Werte werden 
in einem festen Zeitraster ausgegeben, je nach Anwendung ist das 
Zeitraster sogar ein ganzzahliges Vielfaches der PWM-Periodendauer.
Man braucht also nur einfach einen Timer, indem bei jedem Durchlauf ein 
neuer Wert aus einem Array in die PWM geschrieben wird und der Index auf 
das Array hochgezählt wird.

Ich hab einfach mal den Overflow-Interrupt der PWM genutzt, hier erfolgt 
also der Update nach jedem PWM-Durchlauf. Will man das langsamer haben, 
muss man dort noch einen Zähler in Software reinbauen, und nur bei jedem 
Nten Durchlauf einen Update machen.
1
#define F_CPU 3686400L
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <util/delay.h>
6
7
uint8_t  count1= 0;                          // Zähler für 1. Halbbrücke
8
uint8_t  count2= 0;                          // Zähler für 2. Halbbrücke
9
uint16_t array [18] = {0, 28, 56, 84, 112, 140, 168, 196, 224, 252, 280, 308, 336, 364, 392, 420, 448, 476} ;         // 18 array Comparewerte 
10
uint8_t n1 = 1;                              // array Zähler1
11
uint8_t n2 = 1;                              // array Zähler2
12
13
14
15
16
void InitPWM()                                          // Initialisierung der PWM-Modi/Prescaler
17
{
18
  TCCR1A |= (1<<WGM12) | (1<<WGM11) | (1<<COM1A1) | (0<<COM1A0) |(1<<COM1B1) | (0<<COM1B0);  // Aktivieren des Phase-Correct-PMW Modus des Timer1 9-Bit-Breite(512).
19
  TCCR1B |= (1<<CS10);                                    // Abschalten der Ausgänge beim Erreichen des Compare-
20
  DDRB   |= (1<<PORTB1) | (1<<PORTB2);                            // Wertes. Prescaler = 1. PortB1 u. PortB2 als Ausgänge 
21
                                                // festlegen.
22
}
23
24
int main()
25
{
26
  InitPWM();
27
  TIMSK |= (1<<OCIE1A) | (1<<OCIE1B) | (1<<TOIE1);                      // Freischalten der Compare- und OverflowInterrupts 
28
                                                // für beide Compare-Register
29
  
30
  sei();                                            // globale Interruptfreigabe
31
  for(;;)
32
  {
33
  }
34
35
return 0;  
36
}
37
38
ISR(TIMER1_OVF_vect)                                      // ISR für Compareinterrupt A/B
39
{
40
    static uint8_t cnt;
41
42
    cnt++;
43
    if (cnt == 20) {
44
      cnt = 0;
45
      OCR1A = array[n1];
46
      OCR1B = array[n2];
47
      n1++;
48
      n2++;
49
      if (n1 == sizeof(array)) then n1=0;
50
      if (n2 == sizeof(array)) then n2=0;
51
    }
52
}

von Peter A. (elkopeter)


Lesenswert?

Danke für die Antwort. Allerdings war der Zweck meines umständlichen 
Codes die beiden Ausgänge nacheinander zu schalten und der Duty-Cycle 
sollte von 0% bis x% und wieder zurück auf 0% Prozent während einer 
Halbwelle laufen (für nur einen Ausgang).
Mir fällt gerade auf, dass meine Arrayauswahl mit array[1] beginnt, dass 
muss natürlich array[0] sein für die erste Position (verwechselt mit 
Matlab).
So wie´s in deinem Code aussieht schalten in der ISR beide Comparewerte 
nach 20 maligem Schleifendurchlauf für beide Kanäle um. Beide Kanäle 
werden also parallel gleichzeitig ausgegeben.

von Falk B. (falk)


Lesenswert?

@ Peter A. (elkopeter)

>Danke für die Antwort. Allerdings war der Zweck meines umständlichen
>Codes die beiden Ausgänge nacheinander zu schalten und der Duty-Cycle
>sollte von 0% bis x% und wieder zurück auf 0% Prozent während einer
>Halbwelle laufen (für nur einen Ausgang).

Auch das ist kein Problem, das kann man auch in die ISR packen.

>nach 20 maligem Schleifendurchlauf für beide Kanäle um. Beide Kanäle
>werden also parallel gleichzeitig ausgegeben.

Niemand hintert dich, mit ein BISSCHEN Logik die Indizes der beiden 
Kanäle verschieden laufen zu lassen. Das ist eine Öbung för den 
Schööööööler. ;-)

von Peter A. (elkopeter)


Lesenswert?

Der faule Schöler wird sich hierzu was überlegen.
Nochmal zu den Arraywerten eine Frage:

Nehm ich jetzt meine Taktfrequenz 3,6864 Mhz und eine Timerauflösung von 
9Bit, dann hab ich eine PWM Frequenz von ~7,2 kHz. (zumindest bei fast 
pwm)

Wenn ich mir jetzt einen 50Hz Sinus erzeugen will und meine beiden 
Transistorpaare jeweils 10ms lang pulsen sollen, dann Teile ich 
sozusagen
meine 10ms/(1/7,2kHz) und erhalte 72. Ich kann also 72 mal meine 
Comparewerte pro Halbwelle ändern. D.h. also 72 Comparewerte für das 
Array.
Ist meine Überlegung korrekt oder trampele ich da wieder fernab jeder 
Logik?

von Falk B. (falk)


Lesenswert?

Ja, das ist korrekt.

von Peter A. (elkopeter)


Lesenswert?

Ok also soweit sieht das LED Blinken ganz gut aus.
Ich hab jetzt mal alles in die ISR gepackt.

Allerdings für die Weiterschaltung der Comparewerte muss ich mir noch 
etwas eine elgantere Lösung einfallen lassen. Für Vorschläge/Kritik 
stehe ich gerne bereit.


#define F_CPU 3686400L                                      // Externes 
Quarz -> 3,6864 MHz

#include <avr/io.h>                                        // Einbindung 
der Bibliotheken
#include <avr/interrupt.h>
                                                // 50Hz 
Sinus-Ausgangssignal (Grundschwingung)
                                                // -> T/2 = 10ms
uint16_t array [36];                                      // 9-Bit 
Timer1-Auflösung -> f_PWM = 7,2 kHz
                                                // 10ms/(1/7,2kHz)= 72 
-> 72 Änderungen
                                                // des Comparewertes je 
Halbwelle
volatile uint8_t n1 = 0;                                    // 
Comparewert wird hoch- und runergezählt
volatile uint8_t n2 = 0;                                    // -> 36 
Arraywerte (512/36 = 14,2 ->14)
volatile uint8_t x = 0;                                      // -> 
Duty_max = 490/512 = 95,7%




void InitPWM()                                          // 
Initialisierung der PWM-Modi/Prescaler
{
  TCCR1A |= (1<<WGM12) | (1<<WGM11) | (1<<COM1A1) | (0<<COM1A0) 
|(1<<COM1B1) | (0<<COM1B0);  // Aktivieren des Fast-PMW Modus des Timer1 
9-Bit-Breite(512).
  TCCR1B |= (1<<CS10);                                    // Abschalten 
der Ausgänge beim Erreichen des Compare-
  DDRB   |= (1<<PORTB1) | (1<<PORTB2);                            // 
Wertes. Prescaler = 1. PortB1 u. PortB2 als Ausgänge
                                                // festlegen.
}

int main()
{
  InitPWM();
  for(uint8_t i=0; i<36; i++)                                  // 
Schleife füllt array;
  {
    array[i] = array[i-1]+14;
  }
  TIMSK |= (1<<TOIE1);                                    // 
Freischalten des OverflowInterrupts 
// für beide Compare-Register
  sei();                                            // globale 
Interruptfreigabe
  for(;;)
  {
  }
  return 0;
}


ISR(TIMER1_OVF_vect)                                      // ISR für 
Overflowinterrupt
{
  //static uint16_t cnt;                                    // cnt 
verlangsamt die Comparewertumschaltung
  OCR1A = array[n1];                                      // 
Comparewert1 wird überschrieben
  OCR1B = array[n2];                                      // 
Comparewert2 wird überschrieben 
// x als Zählvariable für die Einteilung in T/4
  //cnt++;
  //if(cnt==19)                                        // Vorteiler für 
die Comparewert Weiterschaltung
  //  {
      x++;
  //    cnt=0;
      if(x<=35)                                      // Hochzählen des 
Comparewertes für Q1/Q4
        {
          n2 = 0;
          n1++;
        }
      if((x>35)&&(x<=71))                                  // 
Runterzählen des Comparewertes für Q1/Q4
        {
          n2=0;
          n1--;
        }
      if((x>71)&&(x<=107))                                // Hochzählen 
des Comparewertes für Q2/Q3
        {
          n1=0;
          n2++;
        }
      if((x>107)&&(x<=143))                                // 
Runterzählen des Comparewertes für Q2/Q3
        {
          n1=0;
          n2--;
        }
      if(x==144)                                      // Rücksetzen von 
x nach dem letzten Comparewert
        {
          x=0;
        }
    //}
}

von Falk B. (falk)


Lesenswert?

@ Peter A. (elkopeter)

>Ich hab jetzt mal alles in die ISR gepackt.

>Allerdings für die Weiterschaltung der Comparewerte muss ich mir noch
>etwas eine elgantere Lösung einfallen lassen. Für Vorschläge/Kritik
>stehe ich gerne bereit.

Poste deine Quelltexte als Anhang, dann gibt es auch keine komischen 
zeilenumbrüche und der Quelltext bleib lesbar. Oder nutze die Markierung 
für Quelltext [ c ] [ /c ].

Soweit ist dein Programm OK. Ich würde aber im letzten if sowohl n1 als 
auch n2 definiert setzen.

von Peter A. (elkopeter)


Lesenswert?

Das mit der C-Code Formatierung tut mir leid. Ich werds zukünftig 
berücksichtigen.
Das mit n1 und n2 werde ich noch abändern.

Ich hab mir vorhin mal die beiden Ausgagnssignale aufm Oszi angeschaut 
und bin positiv überrascht. Das sieht schon ganz gut aus bisher (mal 
sehen wie´s dann mit H-Brückentreiber aussieht).

Was mir noch ein Rätsel ist:

Beim Ausgeben des Ausgang1 bzw. Ausgang2 Signals habe ich immer einen 
kurzen peak im anderen Ausgang. Das ganze ist direkt in der Mitte.

z.B.

_|-----|_ Ausgang1

___/\____ Ausgang2

Ich werd mal morgen Bilder mit dem Oszi aufnehmen. Dann sieht man´s 
besser.

An was kann das liegen?

von Peter A. (elkopeter)


Angehängte Dateien:

Lesenswert?

Also ich hab jetzt meinen Code abgeändert und wiedermal versucht eine 50 
Hz Sinusfrequenz(Grundschwingung) zu erzeugen.
Allerdings weiß ich nicht warum meine beiden Ausgangssignale ab und an 
gleichzeitig Schalten (siehe Bild vom Oszi).
Was mir noch Kopfweh bereitet ist meine Berechnung der Schrittweite über 
eine float Variable (Schritt) = 512/Anzahl der Arraywerte. Diese wird 
dann ins Array geschrieben und mit den Timer1 Werten verglichen. Geht 
das so ohne weiteres oder liegt hier der Fehler.
An was kann das liegen? 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
#include <util/delay.h>
6
#define arraysize(x) (sizeof(x)/sizeof(uint16_t))                      // 50Hz Sinus-Ausgangssignal (Grundschwingung)
7
                                                // -> T/2 = 10ms
8
uint16_t samples [36];                                      // 9-Bit Timer1-Auflösung -> f_PWM = 7,2 kHz
9
float Schritt = (512)/(arraysize(samples));                           // 10ms/(1/7,2kHz)= 72 -> 72 Änderungen
10
                                                // des Comparewertes je Halbwelle
11
volatile uint8_t n1 = 0;                                    // Comparewert wird hoch- und runergezählt 
12
volatile uint8_t n2 = 0;                                    
13
volatile uint8_t x = 0;                                
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-PMW Modus des Timer1 9-Bit-Breite(512).
18
  TCCR1B |= (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 festlegen.                                    
20
}                                              
21
22
int main()
23
{
24
  InitPWM();
25
  PORTD  |= 0x04;
26
  for(uint8_t i=0; i<arraysize(samples); i++)                          // Schleife füllt array;
27
  {
28
    samples[i] = samples[i-1]+Schritt;
29
  }
30
                                                
31
  TIMSK |= (1<<TOIE1);                                    // Freischalten des OverflowInterrupts                                        // für beide Compare-Register
32
  sei();                                            // globale Interruptfreigabe
33
  for(;;)
34
  {
35
  }
36
  return 0;
37
}
38
39
40
ISR(TIMER1_OVF_vect)                                      // ISR für Overflowinterrupt
41
{
42
  //static uint16_t cnt;                                    // cnt verlangsamt die Comparewertumschaltung
43
  OCR1A = samples[n1];                                      // Comparewert1 wird überschrieben
44
  OCR1B = samples[n2];                                      // Comparewert2 wird überschrieben                                            // x als Zählvariable für die Einteilung in T/4
45
  //cnt++;                                          
46
  //if(cnt==19)                                        // Vorteiler für die Comparewert Weiterschaltung
47
  //  {
48
      x++;
49
  //    cnt=0;
50
      if(x<arraysize(samples))                              // Hochzählen des Comparewertes für Q1/Q4
51
        {
52
          n2 = 0;
53
          n1++;
54
        }
55
      if((x>=arraysize(samples))&&(x<(2*arraysize(samples))))                // Runterzählen des Comparewertes für Q1/Q4
56
        {
57
          n2=0;
58
          n1--;
59
        }
60
      if((x>=(2*arraysize(samples)))&&(x<(3*arraysize(samples))))              // Hochzählen des Comparewertes für Q2/Q3
61
        {
62
          n1=0;
63
          n2++;
64
        }
65
      if((x>=(3*arraysize(samples)))&&(x<(4*arraysize(samples))))              // Runterzählen des Comparewertes für Q2/Q3
66
        {
67
          n1=0;
68
          n2--;
69
        }
70
      if(x==(4*arraysize(samples)))                            // Rücksetzen von x nach dem letzten Comparewert
71
        {
72
          x=0;
73
          n1=0; 
74
          n2=0;
75
        }
76
    //}
77
    
78
}

von Falk B. (falk)


Lesenswert?

@ Peter A. (elkopeter)

>>Was mir noch Kopfweh bereitet ist meine Berechnung der Schrittweite über
>eine float Variable (Schritt) = 512/Anzahl der Arraywerte.

Mir auch. Macht kein Mensch so. Denn der Tabellenindex kann am Ende nur 
ganzzahlig sein. Schau duir mal das Thema DDS an, dort sieht man, 
wie es geht. Oder, wenn du nur eine feste Frequenz erzeugen willst, 
nutze ein passende Tabelle mit passender Länge, die exakt EINMAL pro 
Periode ausgegeben wird. Dann braucht man auch keine Mehrfachvergleiche, 
sondern nur einen einfachen Tabellenzugriff.

von Falk B. (falk)


Lesenswert?


von Karl H. (kbuchegg)


Lesenswert?

>
1
> float Schritt = (512)/(arraysize(samples));
2
>

512 ist ein integer.
arraysize(samples) liefert einen Integer

Damit wird diese Division als Integer-Division durchgeführt.

Und erst das Ergebnis davon wird dann auf float umgewandelt, damit es an 
den float zugewiesen werden kann.


>
1
>     ..... (1<<COM1A1) | (0<<COM1A0) |(1<<COM1B1) | (0<<COM1B0) ....
2
>

mit dieser Einstellung bedeutet ein OCR Wert von 0 NICHT, dass die 
jeweilige PWM komplett abgeschaltet ist. Auch ein PWM Wert von 0 erzeugt 
kleine Spikes. Denn der Ausgang wird beim Erreichen der 0 in TCNT auf 
jeden Fall eingeschaltet. Selbst dann wenn ihn der nachfolgende Compare 
Match sofort wieder abschaltet.

Wenn das stört (wie bei dir), dann musst du den inverting mode nehmen 
und die OCR Werte entsprechend anpassen

von Karl H. (kbuchegg)


Lesenswert?

1
>  for(uint8_t i=0; i<arraysize(samples); i++)
2
>  {
3
>    samples[i] = samples[i-1]+Schritt;
4
>  }
5
>

wenn du das schon ausrechnest, dann rechne es so aus
1
  for(uint8_t i=0; i<arraysize(samples); i++)                          // Schleife füllt array;
2
  {
3
    samples[i] = i * 512.0 / arraysize(samples);
4
  }

dann schleppst du keine Summenfehler mit. Und die Zeit hast du beim 
hochfahren des µC allemal.

von Falk B. (falk)


Lesenswert?


von Falk B. (falk)


Lesenswert?


von Peter A. (elkopeter)


Lesenswert?

Danke für die Antworten.

Hmm also bei DDS verstehe ich gerade noch Bahnhof.
Ich erzeuge mir einen Phasenakkumulator von 0-2^m-1 Werten.
In den oberen Bit Werten soll meine Adresse für meine Lookup Tabelle 
stehen und in den unteren Bit Werten die Nachkommastellen.

Wie erzeuge ich mir jetzt meine Look-up Tabelle und wo wird diese 
gespeichert? Muss meine Look-up Tabelle die gleiche Anzahl an Werten 
haben , wie die Anzahl an Adressen des Phasenakkumulators?

Wie erzeuge ich mir meinen Phasenakkumulator und wie greift dieser auf 
meine Look-up Tabelle zu?(Pointer?)

Mit der Schrittweite meines Phasenmodulators kann ich meine Frequenz 
einstellen.

von Karl H. (kbuchegg)


Lesenswert?

Peter A. schrieb:

> Ich erzeuge mir einen Phasenakkumulator von 0-2^m-1 Werten.

gut

> In den oberen Bit Werten soll meine Adresse für meine Lookup Tabelle
> stehen und in den unteren Bit Werten die Nachkommastellen.

Auch gut

> Wie erzeuge ich mir jetzt meine Look-up Tabelle und wo wird diese
> gespeichert?

Genauso wie jetzt auch

> Muss meine Look-up Tabelle die gleiche Anzahl an Werten
> haben , wie die Anzahl an Adressen des Phasenakkumulators?

nur wie der Teil vom Phasenakkumulator, den du zur Adressierung benutzt.

> Wie erzeuge ich mir meinen Phasenakkumulator und wie greift dieser auf
> meine Look-up Tabelle zu?(Pointer?)

Genau wie jetzt auch.

Dir scheint die Idee nicht klar zu sein.
Bringen wir das ganze mal aus dem Binärsystem ins Dezimalsystem.

Du hast eine Lookuptabelle mit 5 Einträgen.
Dazu hast du noch eine Variable (den Phasenakkumulator), der dir sagt, 
welcher Wert als nächstes auszugeben ist.

uint8_t Werte[5];
uint8_t nextWert;

nextWert muss offenbar immer von 0 bis 4 durchzählen ...
1
   Werte[0]
2
   Werte[1]
3
   Werte[2]
4
   Werte[3]
5
   Werte[4]
6
   Werte[0]
7
   Werte[1]
8
   ...
... den du möchtest ja mit einer bestimmten Frequenz die Werte an den 
Ausgang legen und bei einer bestimmten Frequenz mag das eben bedeuten, 
dass man im Takt einen Wert nach dem anderen an die Ausgänge legt.

soweit so gut. Jetzt möchtest du die Frequenz erhöhen. Dazu 
'überspringst' du immer einen der Werte aus der Lookup Tabelle. nextWert 
wird also nicht mehr um 1 erhöht, sondern um 2.
nextWert nimmt also die Werte an
1
   0, 2, 4, 1, 3, 0, 2, 4
und dementsprechend werden auch die jeweiligen Werte aus der Lookup 
Tabelle ausgegeben.

Tja. Das Problem ist nur: dadurch hast du die Frequenz verdoppelt! Um 
eine kleinere Frequenzerhöhung zu erreichen, dürftest du nextWert nicht 
um 2 erhöhen sondern nur um, sagen wir mal 1.001
Nur: das geht natürlich nicht bei einem uint8_t. Der ist ganzzahlig und 
ist auch immer ganzzahlig.

Aber du kannst was anderes machen.
Du kannst sagen: nextWert soll bei mir nicht Werte von 0 bis 4 annehmen, 
sondern er soll Werte von 0 bis 4000 annehmen. Zum addressieren in die 
Lookup Tabelle benutzt ich dann eben nextWert nicht direkt, sondern ich 
teile den Wert erst durch 1000. d.h. egal ob nextWert den tatsächlich 
Wert 68 oder 920 hat, es wird dafür immer der Tabellenwert Werte[0] 
ausgegeben. Sinngemäss auch für alle anderen Bereiche.
Aber: dadurch habe ich erreicht, dass ich nextWert nicht nur um 1 oder 
um 2 (respektive um 1000 bzw. 2000) erhöhen kann, sondern zb auch um 
1.5, indem ich 1500 zu nextWert dazuzähle.
Bei 1500 nimmt nextWert dann nacheinander die Werte an
0, 1500, 3000, 4500, 1000, 2500, 4000, 500, 2000, 3500, ...
und dementsprechend werden dann ausgegeben
1
  Werte[0]          // weil    0 / 1000
2
  Werte[1]          // weil 1500 / 1000
3
  Werte[3]          // weil 3000 / 1000
4
  Werte[4]          // weil 4500 / 1000
5
  Werte[1]          // weil 1000 / 1000
6
  Werte[2]          // weil 2500 / 1000
7
  Werte[4]          // weil 4000 / 1000
8
  Werte[0]          // weil  500 / 1000
9
  Werte[2]          // weil 2000 / 1000
10
  Werte[3]          // weil 3000 / 1000
11
  ...

was du damit erreichst hast ist, dass du in guter Näherung die 
Lookuptabelle mit der 1.5 fachen 'Geschwindigkeit' der Aufruffrequenz 
ausgibst.

Nur das du das ganze natürlich nicht mit einem Vielfachen von 10 machst, 
sondern einem Vielfachen von 2. Denn dadurch wird die Division für den 
µC wieder trivial.

Aber an der Größe der Lookup Tabelle hat sich deswegen nichts geändert. 
Und auch nicht, wie sie benutzt wird. Du hast dir im Grunde mit der 
Konvention, alle Zahlen mal 1000 zu nehmen, 3 Stück künstlicher 
Kommastellen eingeführt, die du brauchst um den nächsten jeweils 
auszugebenden Werte 'mit einem Index zu berechnen, der auch Kommastellen 
hat'. Um dann tatsächlich aufs Array zuzugreifen wirst du dann ganz 
einfach die 'Kommastellen' wieder los, indem du geeignet dividierst.

von Peter A. (elkopeter)


Lesenswert?

Erstmal danke für die ausführliche Erklärung.
Ich hoffe ich habs jetzt richtig verstanden.

Ich habe meine feste Wertetabelle mit z.B. 36 Werten. Also laufen meine 
Indizes von 0..35.
Meine Indizes müssen also jetzt auf eine Zahl mit der Basis 2 
umgeschlüsselt werden.
D.h. ich nehme eine Schrittweite von 2^8.
Meine Indizes werden jeweils mit 2^5 multipliziert.
Im Schleifenlauf wird jetzt der Zähler um 2^8 erhöht.
Die Indizes erhält man dann über ein Schieberegister <<5.

von Karl H. (kbuchegg)


Lesenswert?

Peter A. schrieb:
> Erstmal danke für die ausführliche Erklärung.
> Ich hoffe ich habs jetzt richtig verstanden.
>
> Ich habe meine feste Wertetabelle mit z.B. 36 Werten. Also laufen meine
> Indizes von 0..35.
> Meine Indizes müssen also jetzt auf eine Zahl mit der Basis 2
> umgeschlüsselt werden.
> D.h. ich nehme eine Schrittweite von 2^8.
> Meine Indizes werden jeweils mit 2^5 multipliziert.
> Im Schleifenlauf wird jetzt der Zähler um 2^8 erhöht.
> Die Indizes erhält man dann über ein Schieberegister <<5.

Ich würde mir die Sache einfach machen, (da die Tabellenindizes nur von 
0 bis 35 laufen) und ganz einfach alles mal 256 nehmen :-)

Dann hast du einen 16 Bit Index, von dem das obere Byte (High_Byte) 
direkt der Index ist, mit dem du ins Array gehst. Dann braucht auch 
nichts geschoben werden und eine 16 Bit Addition stellt einen AVR noch 
nicht vor massive Probleme in der Laufzeit.

von Peter A. (elkopeter)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Ich würde mir die Sache einfach machen, (da die Tabellenindizes nur von
> 0 bis 35 laufen) und ganz einfach alles mal 256 nehmen :-)
>
> Dann hast du einen 16 Bit Index, von dem das obere Byte (High_Byte)
> direkt der Index ist, mit dem du ins Array gehst. Dann braucht auch
> nichts geschoben werden und eine 16 Bit Addition stellt einen AVR noch
> nicht vor massive Probleme in der Laufzeit.

Ah ok klasse Idee.
Jetzt muss ich doch ganz blöd fragen, wie ich auf das Highbyte der 
16-Bit Integer Werte zugreifen kann ohne das zu schieben >>8?

Zu zwei anderen Fragen:

Ab welcher Lookup Tabellenauflösung ist mein Sinussignal halbwegs 
verwendbar? Ich möchte Pwm-Frequenzen von 20-40kHz erzeugen.

Du hast vorhin erwähnt, dass mein PWM-Modus(Ausschalten der Ausgänge bei 
erreichen der Comparewerte) mir die Probleme mit den peaks im anderen 
Ausgang bescheren. Einfach den Modus tooglen und den niedrigsten 
Comparewert anpassen oder?

von Falk B. (falk)


Lesenswert?

@ Peter A. (elkopeter)

>Jetzt muss ich doch ganz blöd fragen, wie ich auf das Highbyte der
>16-Bit Integer Werte zugreifen kann ohne das zu schieben >>8?

Schieb einfach. Der GCC ist meist schlau genug zu erkennen, dass er 
einfach direkt auf das high Byte zugreifen kann.

>Ab welcher Lookup Tabellenauflösung ist mein Sinussignal halbwegs
>verwendbar? Ich möchte Pwm-Frequenzen von 20-40kHz erzeugen.

256 wurden schon mehrfach genannt, da wird wohl was dran sein.
Fetischisten nehmen das Doppelte oder Vierfache.

>Du hast vorhin erwähnt, dass mein PWM-Modus(Ausschalten der Ausgänge bei
>erreichen der Comparewerte) mir die Probleme mit den peaks im anderen
>Ausgang bescheren. Einfach den Modus tooglen und den niedrigsten
>Comparewert anpassen oder?

Nimm den Phase correct Mode, dann hast du das Problem nicht.

von Peter A. (elkopeter)


Lesenswert?

Also ich hab mir heut den ganzen Tag den Kopf zerbrochen, aber irgendwie 
scheitere ich an der Umsetzung der DDS. Ich hab das Ganze auf einen 
PWM-Ausgang beschränkt aber es will partout nicht klappen.

Hier nochmal 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
6
uint16_t samples[256];     
7
const uint16_t stepsize = 2;                                
8
volatile uint16_t phaseaccu = 0;
9
uint8_t i;
10
uint8_t j;
11
void InitPWM()                                          // Initialisierung der PWM-Modi/Prescaler
12
{
13
  TCCR1A |= (1<<WGM11) | (1<<COM1A1) | (1<<COM1A0);                      // Aktivieren des Fast-PMW Modus des Timer1 9-Bit-Breite(512).
14
  TCCR1B |= (1<<WGM12) | (1<<CS10);                              // Einschalten der Ausgänge beim Erreichen des Compare-
15
  DDRB   |= (1<<PORTB1)| (1<<PORTB2);                              // Wertes. Prescaler = 1. PortB1 u. PortB2 als Ausgänge festlegen.
16
}
17
18
int main()
19
{
20
  InitPWM();
21
  for(i=0; i<128; i++)                                    // Schleife füllt 256 Werte von 0-512-0
22
  {
23
    samples[i] = i*512.0/128;
24
  }
25
  for(i=128,j=128; i<256;i++,j--)
26
  {
27
    samples[i] = j*512.0/128;
28
  }
29
  TIMSK |= (1<<TOIE1);                                    // Freischalten des OverflowInterrupts                                        // für beide Compare-Register
30
  sei();                                            // globale Interruptfreigabe
31
  for(;;)
32
  {
33
  }
34
  return 0;
35
}
36
37
38
ISR(TIMER1_OVF_vect)                                      // ISR für Overflowinterrupt
39
{
40
  
41
  OCR1A = samples[phaseaccu>>8];                                // Comparewert1 wird überschrieben
42
  if(phaseaccu>=65536)
43
  {
44
    phaseaccu =0;
45
  }
46
  phaseaccu+=stepsize;
47
}

von Falk B. (falk)


Lesenswert?

@ Peter A. (elkopeter)

>Also ich hab mir heut den ganzen Tag den Kopf zerbrochen, aber irgendwie
>scheitere ich an der Umsetzung der DDS. Ich hab das Ganze auf einen
>PWM-Ausgang beschränkt aber es will partout nicht klappen.

Was geht denn nicht. Der Code ist auf den ersten Blick OK.

>  if(phaseaccu>=65536)
>  {
>    phaseaccu =0;
>  }

Das ist unsinnig und unnötig, ein Überlauf passiert allein. Kan man 
weglassen.

>    samples[i] = i*512.0/128;

Was soll das? Ein Fließkommaarechung bringt hier exakt NULL Vorteile, 
nur viel Flash-Verbrauch.

    samples[i] = i*4;

macht EXAKT das Gleiche.

: Bearbeitet durch User
von Peter A. (elkopeter)


Lesenswert?

Ich stell bei der Veränderung der Schrittweite keinen 
Frequenzunterschied fest. Wenn ich den Ausgang an eine LED hänge, dann 
bleibt diese dauerhaft an.

von Karl H. (kbuchegg)


Lesenswert?

1
  for(i=128,j=128; i<256;i++,j--)


i als uint8_t ist IMMER kleiner als 256. Der maximale Wert, den i 
annehmen kann ist 255. Bei der nächsten Erhöhung wird i dann 0 und ist 
somit wieder kleiner als 256

D.h. das hier ist eine Endlosschleife.


PS: dir ist schon klar, dass dein Timer läuft, sobald er einen Vorteiler 
hat?
D.h. nach Aufruf von InitPWM läuft der Timer bereits und generiert 
frisch fröhlich eine PWM vor sich hin.
-> es ist immer gut, wenn man eine gewisse Reihenfolge im Programm 
einhält. Dazu muss man sich klar machen, was von wem abhängt. Da der 
Timer, bzw. die zu erzeugende PWM von den SampleWerten abhängt, wäre es 
vielleicht nicht verkehrt die SampleWerte fertig zu haben, ehe man die 
PWM einschaltet.

: Bearbeitet durch User
von Peter A. (elkopeter)


Lesenswert?

Hab folgendes geändert:

Gleitkommazahl berechnung geändert auf i*4, j*4
If-Anweisung in der ISR gelöscht.
Abbruch-Bedingung der for-Schleife zu i==255 geändert.
InitPWM vor die Dauerschleife der main-Funktion gesetzt.

Wenn ich jetzt eine Schrittweite von 2 definiere dimmt sich meine LED 
ca. alle 5sek herab und schnellt dann wieder auf volle Helligkeit.
Ich schau mir das Ganze jetzt mal aufm Oszi an.

Danke an Herrn Brunner und Herrn Buchegger für ihre Geduld mit mir und 
die tatkräftige Unterstützung bei meinen Problemen.

von Karl H. (kbuchegg)


Lesenswert?

Peter A. schrieb:

> Wenn ich jetzt eine Schrittweite von 2 definiere dimmt sich meine LED
> ca. alle 5sek herab und schnellt dann wieder auf volle Helligkeit.
> Ich schau mir das Ganze jetzt mal aufm Oszi an.

Ja, das deckt sich auch ungefähr mit meinen Berechnungen über die zu 
erwartende Zykluszeit.

von Karl H. (kbuchegg)


Lesenswert?

> alle 5sek herab und schnellt dann wieder auf volle Helligkeit.

Das könnte damit zusammenhängen:

> Abbruch-Bedingung der for-Schleife zu i==255 geändert.

Wenn da jetzt steht
1
  for(i=128,j=128; i == 256;i++,j--)
2
                   ********

dann ist das klar. Denn dann wird die Schleife nie ausgeführt.

von Karl H. (kbuchegg)


Lesenswert?

Peter A. schrieb im Beitrag #3324703:

> Hmm ok also hab´s gedebugged und die Schleife wird nich ausgeführt.
> Hab die Bedingung auf i<=255geändert. Nur hab ich dann kein

Welche Werte kann i annehmen?

Was gilt für alle diese Wert in Bezug auf die Bedingung <=255 ?

Hinweis:
1
  i < x
ist exakt dasselbe wie
1
  i <= ( x - 1)
Die Bedingung
1
   i < 7
ist für genau die gleichen Werte TRUE, für die auch gilt
1
   i <= 6

: Bearbeitet durch User
von Peter A. (elkopeter)


Lesenswert?

Ah ok. Habs gedebugged und die Schleife wird übersprungen.
Ich hab dann die Bedingung wieder auf i<=255 geändert und eine 
if-Anweisung mit i==255 -> break eingebaut.

von Karl H. (kbuchegg)


Lesenswert?

Peter A. schrieb:
> Ah ok. Habs gedebugged und die Schleife wird übersprungen.
> Ich hab dann die Bedingung wieder auf i<=255 geändert und eine
> if-Anweisung mit i==255 -> break eingebaut.


Oh Mann.

Du könntest zb so vorgehen
1
  for(i=0; i<128; i++)                                    // Schleife füllt 256 Werte von 0-512-0
2
  {
3
    samples[i] = i*4;
4
  }
5
  for(i=0,j=128; i<127; i++,j--)
6
  {
7
    samples[i+128] = j*4;
8
  }

oder so
1
  for(i=0; i<128; i++)                                    // Schleife füllt 256 Werte von 0-512-0
2
  {
3
    samples[i] = i*4;
4
  }
5
  for(i=128,j=128; i != 0; i++,j--)   // Hier wird der Overflow von i ausgenutzt, i wird 0 nachdem es bei 255 inkrementiert wird
6
  {
7
    samples[i] = j*4;
8
  }

: Bearbeitet durch User
von Peter A. (elkopeter)


Lesenswert?

Sorry ich bin manchmal völlig von der Rolle, was Programmierung angeht.
Die Variante mit i!=0 erscheint mir die beste. Danke für die unendliche 
Geduld.

von Falk B. (falk)


Lesenswert?

Andere Leute hätten einfach die Variabeln von i und j als uint16 
definiert . .  .

von Peter A. (elkopeter)


Lesenswert?

Falk Brunner schrieb:
> Andere Leute hätten einfach die Variabeln von i und j als uint16
> definiert . .  .

Wäre natürlich auch eine simple Methode gewesen.

Zu einem anderen Thema:
So wie ich aktuell meine samples-Werte berechne geht bei samples[128] 
etwas schief, da mein Timer ja nur bis Top=511 läuft.
Ich hab das jetzt mal folgendermaßen abgeändert:
1
              
2
  for(i=0; i<128; i++)                                    // Schleife füllt 256 Werte von 3-511-3
3
  {
4
    samples[i] = i*4+3;
5
  }
6
  for(i=128,j=128; i!=0;i++,j--)
7
  {
8
    samples[i] = j*4-1;
9
    
10
  }
Ist das passabel oder schüttelt ihr da nur mit dem Kopf?

von Falk B. (falk)


Lesenswert?

@Peter A. (elkopeter)

>So wie ich aktuell meine samples-Werte berechne geht bei samples[128]
>etwas schief,

Was denn?

Nochmal. Mit Fast-PWM kann man beim AVR keine PWM mit alles Null und 
alls HIGH erzeugen, es bleibt immer ein Kurzer Puls übrig. Wenn man den 
weghaben will und bei PWM=0 das Pin dauerhaft auf Null sowie PWM=max das 
Pin dauerhaft auf High haben will, muss man den Phase Correct PWM Modus 
benutzen. OK, dann ist die PWM-Frequnez halbiert, aber damit muss man 
halt leben. mit CTC und PWM kombiniert kriegt man das aber hin.

> da mein Timer ja nur bis Top=511 läuft.
> Ich hab das jetzt mal folgendermaßen abgeändert:


>  for(i=0; i<128; i++)                                    // Schleife >füllt 256 
Werte von 3-511-3
>  {
>    samples[i] = i*4+3;
>  }

Soweit OK.

>  for(i=128,j=128; i!=0;i++,j--)
>  {
>    samples[i] = j*4-1;
>  }

>Ist das passabel oder schüttelt ihr da nur mit dem Kopf?

Letzteres. Dein Index muss bis 255 gehen, dann ist Schluss, Jaja, der 
alte Hackertrick mit dem Überlauf geht, ist aber sehr unsauberer 
Programmierstil. Sowas bringt einem meist mehr Probleme als es löst. 
Ausnahmen bestätigen die Regel.

Also nimm uint16_t Variablen und mach es solide.

von Peter A. (elkopeter)


Lesenswert?

Ok dann werde ich uint16_t verwenden und die Abbruchbedingung als i<256 
setzen. Den Phase Correct Modus werde ich dann auch einstellen.
Ist das mit der Werteanpassung von 3-511-3 in Ordnung? D.h. ich kann 
dann keinen Duty-Cycle von 0% einstellen (bsw. je nach COMA/COMB 
Einstellungen 100% Duty-Cycle).

von Falk B. (falk)


Lesenswert?

@ Peter A. (elkopeter)

>Ist das mit der Werteanpassung von 3-511-3 in Ordnung? D.h. ich kann
>dann keinen Duty-Cycle von 0% einstellen (bsw. je nach COMA/COMB
>Einstellungen 100% Duty-Cycle).

Menschenskinder, bist du so lernresistent? Gerade mit Phase Correct 
PWM-Mode KANN man PROBLEMLOS 0-511 als PWM nutzen und deine Anpassung 
mit 3-511-3 ist NICHT nötig!

von Peter A. (elkopeter)


Angehängte Dateien:

Lesenswert?

Der Lernresistente meldet sich nochmal. So ich verwende den 
Phase-Correct-PWM Modus sonst ist eig. alles beim Alten geblieben. Keine 
Anpassung mehr. Ich zeig jetzt mal die Oszi Bilder. Schrittweite 256.

Ist die Signalausgabe so korrekt?

von Karl H. (kbuchegg)


Lesenswert?

Oszi ist schön.
Aber eine LED ist besser zum debuggen.

Häng eine LED an, mach deine OCR Updates ganz langsam und sieh nach, ob 
die LED auch tatsächlich komplett aus geht (ja, man sieht mit freiem 
Auge ob eine PWM tatsächlich aus ist, oder ob da noch kurze Spikes sind. 
Raum abdunkeln und bei den kleinen Leveln auf den Chip in der LED sehen. 
Er muss ganz ausgehen. Nicht dunkel, nicht sehr dunkel, sondern komplett 
aus)

Ansonsten: wie ist das Helligkeitsverhalten der LED. Gibt es Sprünge in 
der Helligkeit in der falschen Richtung? Also: Wenn die LED eigentlich 
heller werden sollte, wird sie zwischendurch mal eine Stufe dunkler? Und 
umgekehrt: Wenn die Helligkeit auf dem abnehmenden Ast ist, wird sie 
dann zwischendurch bei irgendeinem Schritt mal heller?
Bei den großen Helligkeiten kann es schwierig sein das festzustellen, 
weil unser Auge da die UNterschiede nicht mehr gut auflösen kann. Aber 
bei den kleinen Helligkeiten sieht man das sehr gut.


Sei ein bischen kreativer! Auch in den Methoden, wie du dein Programm 
kontrollieren kannst! Ja, manchmal muss man auch dazu ein wenig um die 
Ecke denken und experimentieren. Aber so ist das nun mal. Im Berufsleben 
kannst du auch nicht wegen jedem 10-Zeiler zu deinen Kollegen laufen und 
fragen ob das stimmt.

von Peter A. (elkopeter)


Angehängte Dateien:

Lesenswert?

Hier noch die Aufnahmen für beide Kanäle + 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
6
uint16_t samples[256];
7
    
8
const uint16_t stepsize =1024;                                
9
volatile uint16_t phaseaccu = 0;
10
volatile uint8_t cnt=0;
11
uint16_t i;
12
uint8_t j;
13
14
void InitPWM()                                          // Initialisierung der PWM-Modi/Prescaler
15
{
16
  TCCR1A |= (1<<WGM11) | (1<<COM1A1) | (0<<COM1A0) |(1<<COM1B1) | (0<<COM1B0);        // Aktivieren des Fast-PMW Modus des Timer1 8-Bit-Breite(255=TOP).
17
  TCCR1B |= (0<<WGM12) | (1<<CS10);                              // Einschalten der Ausgänge beim Erreichen des Compare-
18
  DDRB   |= (1<<PORTB1)| (1<<PORTB2);                              // Wertes. Prescaler = 1. PortB1 u. PortB2 als Ausgänge festlegen.
19
}
20
21
int main()
22
{
23
  for (i=0;i<128;i++)
24
  {
25
    samples[i]=i*4;
26
  }  
27
  for (i=128,j=127;i<256;i++,j--)
28
  {
29
    samples[i]=j*4;
30
  }
31
  TIMSK |= (1<<TOIE1);                                    // Freischalten des OverflowInterrupts                                        // für beide Compare-Register
32
  sei();                                            // globale Interruptfreigabe
33
  InitPWM();
34
  for(;;)
35
  {
36
  }
37
  return 0;
38
}
39
40
41
ISR(TIMER1_OVF_vect)                                      // ISR für Overflowinterrupt
42
{
43
  if(phaseaccu==0)                                      // Bei Überlauf soll cnt erhöht werden
44
  {
45
    cnt++;
46
  }
47
  if(cnt&1)
48
  {
49
    OCR1A=samples[phaseaccu>>8];
50
    OCR1B=samples[255];
51
  }
52
  else
53
  {
54
    OCR1B=samples[phaseaccu>>8];
55
    OCR1A=samples[0];
56
  }
57
  phaseaccu+=stepsize;
58
}

von Peter A. (elkopeter)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Oszi ist schön.
> Aber eine LED ist besser zum debuggen.
>
> Häng eine LED an, mach deine OCR Updates ganz langsam und sieh nach, ob
> die LED auch tatsächlich komplett aus geht (ja, man sieht mit freiem
> Auge ob eine PWM tatsächlich aus ist, oder ob da noch kurze Spikes sind.
> Raum abdunkeln und bei den kleinen Leveln auf den Chip in der LED sehen.
> Er muss ganz ausgehen. Nicht dunkel, nicht sehr dunkel, sondern komplett
> aus)
> Sei ein bischen kreativer! Auch in den Methoden, wie du dein Programm
> kontrollieren kannst! Ja, manchmal muss man auch dazu ein wenig um die
> Ecke denken und experimentieren. Aber so ist das nun mal. Im Berufsleben
> kannst du auch nicht wegen jedem 10-Zeiler zu deinen Kollegen laufen und
> fragen ob das stimmt.

Ich werde mich in zukunft zurückhalten mit meiner Fragerei. Ich bin 
einfach in Sachen C-Programmierung und Microcontroller eingerostet, da 
ich damit zuletzt vor 2 Jahren etwas zu tun hatte. Aber nochmals ein 
großes Lob an Herrn Buchegger und Herrn Brunner, die mir jetzt zum 
Schluss am liebsten den Kopf abgerissen hätten.

von Falk B. (falk)


Lesenswert?

@ Karl Heinz Buchegger (kbuchegg) (Moderator)

>Oszi ist schön.
>Aber eine LED ist besser zum debuggen.

Noch besser. Ein RC-Filter.

von Falk B. (falk)


Lesenswert?


von Peter A. (elkopeter)


Lesenswert?


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.