Forum: Mikrocontroller und Digitale Elektronik PWM mit Timer0 & 2 mit ATmega168PA läuft nicht


von Hans Schmidt (Gast)


Lesenswert?

Hallo,

stehe gerade mit der PWM Funktion (phase correct PWM, Mode 5) der Timer 
0 und 2 bei meinem ATmega168PA auf Kriegsfuss. An den Ausgangspins 
passiert einfach nichts, d.h. die wechseln nie von 0 auf 1. Die Timer 
zählen/laufen brav, der Wert für TOP ist gesetzt und die vier OCxx Bits 
sind auch gesetzt.
Was habe ich an der Stelle vergessen, bzw. mache ich falsch?

Gruß, Hans
1
/*****************************************************************
2
**
3
** Includes/externe Bibliotheken die im Programm genutzt werden
4
**
5
******************************************************************/
6
// zentrale Konfigurationsdaten einbinden
7
#include "config.h"
8
// zur Nutzung von I/O-Pins
9
#include <avr/io.h>
10
// Warteschleifen
11
#include <util/delay.h>
12
// Interrupts nutzen
13
#include <avr/interrupt.h>
14
// Funktionen zum Zugriff auf den UART (RS232)
15
#include "./USART/uart.h"
16
// zur Konvertierung eines Integer in einen String mittles itoa
17
#include <stdlib.h>
18
19
/*****************************************************************
20
**
21
** Initialisiert die Ports des uControllers
22
** Parameter: keine
23
** Rückgabewert: keiner
24
**
25
******************************************************************/
26
void init_ports (void)
27
{
28
  // Port B
29
  //  Pin 0: ungenutzt
30
  //  Pin 1: ungenutzt
31
  //  Pin 2: ungenutzt
32
  //  Pin 3: Ausgang, PWM Timer 2
33
  //  Pin 4: ungenutzt
34
  //  Pin 5: ungenutzt
35
  //  Pin 6: ungenutzt
36
  //  Pin 7: ungenutzt
37
  PORTB |= (1 << PB0) | (1 << PB1) | (1 << PB2) | (1 << PB4) | (1 << PB5) | (1 << PB6) | (1 << PB7);
38
  DDRB |= (1 << DDB3); // Pin 3 im Richtungsregister als Ausgang definieren (ohne Pull-Up)
39
  // Port D
40
  //  Pin 0: Eingang, serielle Schnittstelle (RxD)
41
  //  Pin 1: Ausgang, serielle Schnittstelle (TxD)
42
  //  Pin 2: Ausgang, An-Ausschalter Pumpe 1
43
  //  Pin 3: Ausgang, PWM Timer 2
44
  //  Pin 4: ungenutzt
45
  //  Pin 5: Ausgang, PWM Timer 0
46
  //  Pin 6: Ausgang, PWM Timer 0
47
  //  Pin 7: Ausgang, An-Ausschalter Pumpe 2
48
  PORTD |= (1 << PD0) | (1 << PD2) | (1 << PD4) | (1 << PD7);
49
  DDRD |= (1 << DDD1) | (1 << DDD2) | (1 << DDD3) | (1 << DDD5) | (1 << DDD6) | (1 << DDD7); // Pin 1; 2; 3; 5; 6 & 7 im Richtungsregister als Ausgang definieren (2 & 7 mit Pull-Up)
50
}
51
52
/*****************************************************************
53
**
54
** Initialisiert den Timer 0 im PWM Modus
55
** Parameter: keine
56
** Rückgabewert: keiner
57
**
58
******************************************************************/
59
void init_pwm0 (void)
60
{
61
  // entsprechende Pins (Output Compare 0, Kanal A & B) als Ausgang schalten
62
  DDRD |= (1 << DDD5) | (1 << DDD6);
63
  // Phase Correct PWM Modus, setze OC0A & OC0B bei Null/unten, loesche OC0A & OC0B wenn der Timerwert OCR0A bzw. OCR0B erreicht hat
64
  TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM00);
65
  //  Takt = F_CPU (kein Vorteiler)
66
  TCCR0B |= (1 << WGM02) | (1 << CS00);
67
  // Vergleichswert für den Timer = oberes Ende setzen
68
  OCR0A = 0xE0;
69
  OCR0B = 0xE0;
70
}
71
72
/*****************************************************************
73
**
74
** Initialisiert den Timer 2 im PWM Modus
75
** Parameter: keine
76
** Rückgabewert: keiner
77
**
78
******************************************************************/
79
void init_pwm2 (void)
80
{
81
  // entsprechende Pins (Output Compare 2, Kanal A & B) als Ausgang schalten
82
  DDRB |= (1 << DDB3);
83
  DDRD |= (1 << DDD3);
84
  // Phase Correct PWM Modus, setze OC2A & OC2B bei Null/unten, loesche OC2A & OC2B wenn der Timerwert OCR2A bzw. OCR2B erreicht hat
85
  TCCR2A |= (1 << COM2A1) | (1 << COM2B1) | (1 << WGM20);
86
  //  Takt = F_CPU (kein Vorteiler)
87
  TCCR2B |= (1 << WGM22) | (1 << CS20);
88
  // Vergleichswert für den Timer = oberes Ende setzen
89
  OCR2A = 0xE0;
90
  OCR2B = 0xE0;
91
}
92
93
/*****************************************************************
94
**
95
** Hauptprogramm
96
** Parameter: keine
97
** Rückgabewert: Exit code (irrelevant, wird nie erreicht)
98
**
99
******************************************************************/
100
int main(void) // Beginn des Hauptprogrammes
101
{
102
  // (zur Sicherheit) Interrupts sperren (sollte unnötig sein)
103
  cli ();
104
  // Ports initialisieren
105
  init_ports ();
106
  // PWM an Timer 0 initialisieren
107
  init_pwm0 ();
108
  // PWM an Timer 2 initialisieren
109
  init_pwm2 ();
110
  // serielle Schnittstelle zum debuggen aktivieren
111
  uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );
112
  sei();
113
  while (1)
114
  {
115
    itoa (OCR2A, str, 10);
116
    uart_puts("OCR2A Wert: ");
117
    uart_puts(str);
118
    uart_puts("\r\n");
119
    itoa (TCNT2, str, 10);
120
    uart_puts("TCNT2 Wert: ");
121
    uart_puts(str);
122
    uart_puts("\r\n");
123
    _delay_ms(1000);
124
  }
125
}

serielle Ausgabe
1
OCR2A Wert: 224
2
TCNT2 Wert: 168
3
OCR2A Wert: 224
4
TCNT2 Wert: 54
5
OCR2A Wert: 224
6
TCNT2 Wert: 205
7
OCR2A Wert: 224
8
TCNT2 Wert: 90
9
OCR2A Wert: 224
10
TCNT2 Wert: 207
11
OCR2A Wert: 224
12
TCNT2 Wert: 126
13
[...]

Beitrag #6530560 wurde von einem Moderator gelöscht.
von Stefan F. (Gast)


Lesenswert?

> TCCR0A |= (1 << COM0A1) | (1 << COM0B1) | (1 << WGM00);
> TCCR0B |= (1 << WGM02) | (1 << CS00);

> TCCR2A |= (1 << COM2A1) | (1 << COM2B1) | (1 << WGM20);
> TCCR2B |= (1 << WGM22) | (1 << CS20);

Das klappt nur, wenn die Register vorher 0 waren. Vielleicht hast du 
einen Bootloader der die Register ändert, deswegen würde ich lieber = 
anstatt |= verwenden.

von Hans Schmidt (Gast)


Lesenswert?

Hallo Stefan,

Bootloader nutze ich nicht, beim (neu-)programmieren des AVRs wird der 
AVR gelöscht und dann neu beschrieben.
Habe es aber auch einmal mit "=" probiert: keine Änderung. Die Pins 
wackeln nicht.
Nehme ich die Initialisierung des Timers 0 raus und packe ein
1
  PORTD |= (1<<PD5) | (1<<PD6);
2
  _delay_ms(10);
3
  PORTD &= ~(1<<PD5) & ~(1<<PD6);
4
  _delay_ms(10);
in die while Schleife, so lässt das die beiden Pins vom Timer 0 schön 
wackeln.
Bin so langsam echt ratlos...

von S. Landolt (Gast)


Lesenswert?

> PWM Funktion (phase correct PWM, Mode 5) der Timer ...

Im Modus 5 ist OCRA der TOP-Wert - ist das berücksichtigt?

von mmmhh (Gast)


Lesenswert?

Schon mal OCR0B bzw OCR2B etwas kleiner als OCR0A bzw OCR2A angegeben?

von Hans Schmidt (Gast)


Lesenswert?

@Landolt & mmmhh: so wie ich die PWM Funktion des Timers bis jetzt 
verstanden habe, ist es doch wie folgt: der Zähler des Timers (TCNTx) 
zählt von Null bis TOP, von dort wieder runter auf Null, wieder hoch auf 
TOP usw. Wenn ich nun das TOP per OCRxA oder OCRxB auf einen Wert 
kleiner (oder auch größer) als den aktuellen Zählerstand ändere (da TOP 
eben genau diesen Werten entspricht), so läuft der Zähler des Timers 
(TCNTx) über/bis zum max. möglichen Wert (hier: 2^8 da 8-Bit Zähler). 
Den Zähler selbst stört das aber nicht, er zählt dann wieder ganz normal 
von null an wieder weiter nach dem Überlauf (oder von 0xFF eben runter 
Richtung null). Somit hätte ich einmalig ein einziges "unsauberes" PWM 
weil eben nicht bis zu OCRxA gezählt wird, sondern bis 2^8. Sofern OCRxA 
aber konstant bleibt, ist das eine einmalige Sache und im Anschluss 
läuft der Zähler des Timers zw. Null und OCRxA=TOP (bzw. OCRxB). Oder 
habe ich das völlig falsch verstanden?

von Hans Schmidt (Gast)


Lesenswert?

mmmhh schrieb:
> Schon mal OCR0B bzw OCR2B etwas kleiner als OCR0A bzw OCR2A angegeben?

OCR0B soltle im Mode 5 komplett außen vor sein, als TOP wird immer OCRxA 
genommen, oder?

von Stefan F. (Gast)


Lesenswert?

Da ich sonst keinen weiteren möglichen Fehler entdecken kann, klammere 
ich mich an den letzten Strohhalm: Nimm mal die config.h und die uart.h 
raus.

von S. Landolt (Gast)


Lesenswert?

> OCR0B soltle im Mode 5 komplett außen vor sein
?
Einfach mal ausprobieren wie von mmmhh vorgeschlagen, also z.B. 
OCR0B=$A0. Dann blinkt das nämlich bei mir an OC0B, während OC0A 
konstant bleibt.

von Hans Schmidt (Gast)


Lesenswert?

Was auch immer das Datenblatt unter 
https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48_88_168_megaAVR-Data-Sheet-40002074.pdf 
sagt, entweder ist da ein Fehler drin, oder ich kapiere es gar nicht. 
Habe mich schon gewundert, dass z.B. in der Tabelle Table 15-8 immer 
OCRA genannt ist (und nicht OCRnA) und auch an anderer Stelle kleinere 
Ungereimtheiten sind. Auf jeden Fall hat OCRnB einen entscheidenden 
Einfluss auf das Verhalten! Meine Vermutung ist, dass der eine OCR Werte 
der Vergleichswert ist, während der andere TOP festlegt. Sind beide 
ungleich, dann gibt es auch einen PWM am Ausgangspin.

@Landolt: danke für deine Hartnäckigkeit, das war der entscheidende 
Tipp!!!

@all: nutze nun den fast PWM mode und bin glücklich damit. Funktioniert 
alles wie es soll, auch wenn die Phase nun bei Änderungen nicht sauber 
ist. Aber die wird eh nur alle ~2min in kleinen Schritten geändert.

von S. Landolt (Gast)


Lesenswert?

> Sind beide ungleich, dann gibt es auch einen PWM am Ausgangspin.

Nicht ganz - OCRnB, welches den Tastgrad der PWM festlegt, muss kleiner 
als der TOP-Wert, d.h. OCRnA, sein, Letzteres bestimmt die Frequenz.

von S. Landolt (Gast)


Lesenswert?

... im Modus 5, natürlich.
Und falls Sie eine, mit OCRnA festgelegte Frequenz mit Tastgrad 0.5 
möchten, einfach (1 << COM0A0) (statt (1 << COM0A1)) setzen.

Beitrag #6530954 wurde von einem Moderator gelöscht.
von Hans Schmidt (Gast)


Lesenswert?

ja, der Code ist recht gut dokumentiert... Sollte man oben ja sehen und 
die Info hängt mit dran ;-) Aber solche Dinge werde ich auch in zehn 
Jahren noch wissen, denn ich habe schon locker 36h gegrübelt und zig 
Werte per LEDs und seriell ausgegeben weil ich verzweifelte. Das bleibt 
hängen

von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

In meinem Datenblatt steht in der Fußnote zur Tabelle:

"A special case occurs when OCR0B equals TOP and COM0B1 is set. In this 
case, the Compare Match is ignored, but the set or clear is done at 
TOP."

Heisst das nicht, dass das Vorhaben des TO von Anfang an hätte 
funktionieren müssen?

von Hans Schmidt (Gast)


Lesenswert?

@Stefan: das steht bei mir auch da drin und hatte ich auch so 
verstanden, deswegen hatte ich ja auch noch meine Begründung/Erklärung 
30.12.2020 18:07 geschrieben. Das Silizium ist aber anders gestrickt...

von Stefan F. (Gast)


Lesenswert?

Wenn nur der zweite Compare Ausgang nicht funktioniert hätte, wäre ich 
ja vielleicht noch experimentiell drauf gekommen. Aber dass der erste 
Ausgang auch ausfällt wenn OCRnA=OCRnB, das ist schon sehr fies.

Ich werde mir das mal als Textdatei neben das Datenblatt legen. Denn 
irgendwann in den nächsten 10 Jahren wird es bestimmt auch mir passieren 
und dann erinnere ich mich nicht mehr an diesen Thread.

von Stefan F. (Gast)


Lesenswert?

Wie ist das denn mit Timer 1, haben wir da die gleiche Abweichung 
zwischen Datenblatt und Chip?

von Stefan F. (Gast)


Lesenswert?

Dieses mal ist ausnahmsweise nicht Microchip der Schuldige. Der fehler 
befindet sich schon im Datenblatt von Atmel Rev. A – 06/2016.

von Stefan F. (Gast)


Lesenswert?

Ich habe gerade getestet: TTimer 1 ist auch betroffen. Ich würde das so 
zusammen fassen:
1
Fall: Timer Phase Correct PWM Mode mit OCRnA als TOP.
2
3
Die OCR Register sind falsch beschrieben. 
4
OCRnB (=Pulsbreite) muss >0 und <TOP sein, sonst erhält man kein
5
Signal am OCRnB Ausgang.
6
7
Der OCRnA Ausgang kann in diesem Modus kein PWM Signal erzeugen.

Hat das schon jemand Microchip gemeldet?

von S. Landolt (Gast)


Lesenswert?

> Der OCRnA Ausgang kann in diesem Modus kein PWM Signal erzeugen.

Ist das nicht eine reine Definitions- oder Ansichtssache? Da OCRA=TOP 
wäre das "PWM Signal" doch allenfalls ein Nadelimpuls, wenn überhaupt, 
oder?

von Stefan F. (Gast)


Lesenswert?

S. Landolt schrieb:
> Ist das nicht eine reine Definitions- oder Ansichtssache? Da OCRA=TOP
> wäre das "PWM Signal" doch allenfalls ein Nadelimpuls, wenn überhaupt,
> oder?

Man würde einen symmetrisches PWM Signal mit 50/50 Tastverhältnis 
erwarten, denn es geht im den Phase Correct Mode.

Der Zähler zählt immer abwechselnd hoch und runter. Bei Erreichen von 
TOP (OCRnA) geht der Ausgang auf LOW, bei Erreichen vom BOTTOM (0) geht 
der Ausgang auf HIGH.

Im Datenblatt steht:

"the Output Compare (OC1x) is cleared on the compare match between TCNT1 
and OCR1x while upcounting, and set on the compare match while 
downcounting."

Tatsächlich passiert folgendes:

OCnA geht einmal auf HIGH und bleibt dann so.
OCnB verhält sich wie beschrieben, aber nur wenn OCRnB >0 und <TOP ist.

Das war mein Code:
1
    // Phase correct PWM, Prescaler 256, output A+B 
2
    TCCR1A = (1<<WGM11) + (1<<WGM10) + (1<<COM1A1) + (1<<COM1B1);
3
    TCCR1B = (1<<WGM13) + (1<<CS12);
4
5
    // Set TOP value
6
    OCR1A = 0x8000;
7
8
    // Set pulse width
9
    OCR1B = 0x7FF0;
10
11
    // Output compare outputs A+B
12
    DDRB = (1<<PB1) + (1<<PB2);

von S. Landolt (Gast)


Lesenswert?

> Man würde einen symmetrisches PWM Signal mit 50/50
> Tastverhältnis erwarten

Man vielleicht, ich nicht unbedingt. Aber:
Beitrag "Re: PWM mit Timer0 & 2 mit ATmega168PA läuft nicht"

von Veit D. (devil-elec)


Lesenswert?

Hallo,

lässt sich hier jemand verunsichern?  :-)
Auf welche Timereinstellung bezieht sich denn der angebliche Fehler im 
Manual? Der TO hat nämlich eine völlig konfuse Timereinstellung.

Die Timer sollen laut TO im Phase Correct Mode arbeiten. In welchen 
denn?
Da schau ich mir seine WGM Bits an und stelle fest in dem mit 
einstellbaren TOP Wert in OCRxA. Danach sehe ich in seinem Code das er 
jeweils den Compare Wert für Channel A und B gleich gesetzt hat. Da kann 
nichts toggeln. Welche Pulsweite soll sich ändern?
A bestimmt die Periodendauer und B die Pulsweite. Wenn beide gleich sind 
pulst nichts, es liegt Ub an.
Das entspricht 100% Duty Cycle. Genauso muss das auch sein. Alles andere 
wäre ein Fehler.

Wenn B = 0, liegt dauerhaft Masse an, 0% Duty Cycle.
Wenn B = TOP, liegt dauerhaft Ub an, 100% Duty Cycle.
Irgendwas dazwischen erzeugt eine Pulsweite.
50% Duty erhält man nur mit OCRnB = OCRnA / 2.

Der TO hat 2 Möglichkeiten.
a) In diesem Timermode kann er nur einen Ausgang nutzen, nämlich OCRnB 
und kann damit die Frequenz und Pulsweite selbst einstellen.
b) Man nimmt den anderen Phase Correct Mode mit TOP = 255. Dann kann man 
die Frequenz nur noch mittels Prescaler einstellen und beide Ausgänge 
OCRnA und OCRnB für PWM nutzen.

Bsp. nach Möglichkeit a) für einen ATmega328PB, sollte ohne weitere 
Überprüfung zum 168 kompatibel sein. Die Pinbelegung könnte vielleicht 
etwas abweichen.
1
/*
2
  Timer-Demo
3
  ATmega328PB
4
  31.12.2020
5
*/
6
7
#include <avr/io.h>
8
#include <avr/interrupt.h>
9
10
void setTimer0PWM(const uint8_t, const uint8_t);
11
void runTimer0(const uint16_t);
12
void stopTimer0(void);
13
14
void setTimer2PWM(const uint8_t, const uint8_t);
15
void runTimer2(const uint16_t);
16
void stopTimer2(void);
17
18
int main(void)
19
{
20
    DDRD  = DDRD | _BV(PD5);   // OC0B Ausgang
21
    DDRD  = DDRD | _BV(PD3);   // OC2B Ausgang
22
    
23
    setTimer0PWM(200, 150);     // TOP, Duty
24
    runTimer0(8);               // Prescaler
25
    
26
    setTimer2PWM(200, 50);          
27
    runTimer2(8);            
28
       
29
    sei();                      // Interrupts enabled
30
    
31
    while (1) 
32
    {
33
    }
34
}
35
36
// ****** Funktionen ********* //
37
38
// ****** TIMER 0 ************ //
39
void setTimer0PWM(const uint8_t top, const uint8_t duty)   //  Phase Correct, Mode 5
40
{   
41
    TCCR0A = 0;             // Reset
42
    TCCR0B = 0; 
43
    TIMSK0 = 0; 
44
    TCNT0 = 0;  
45
    OCR0A  = top;           // Compare Match Register <= 255 
46
    OCR0B  = duty;          // Duty Cycle <= TOP
47
    TCCR0A = _BV(COM0B1) | _BV(WGM00);  // toggle OC0B pin
48
    TCCR0B = _BV(WGM02);
49
}  
50
51
void runTimer0 (const uint16_t prescaler)
52
{
53
    switch (prescaler) {  // set Prescaler Clock Select Bits
54
        case    1 : TCCR0B = TCCR0B | _BV(CS00);              break;
55
        case    8 : TCCR0B = TCCR0B | _BV(CS01);              break;
56
        case   64 : TCCR0B = TCCR0B | _BV(CS01) | _BV(CS00);  break;
57
        case  256 : TCCR0B = TCCR0B | _BV(CS02);              break;
58
        case 1024 : TCCR0B = TCCR0B | _BV(CS02) | _BV(CS00);  break;
59
        default :  break;
60
    }
61
}
62
63
void stopTimer0 (void)
64
{
65
    TCCR0B = TCCR0B & ~(_BV(CS02) | _BV(CS01) | _BV(CS00));
66
}
67
68
69
// ****** TIMER 2 ************ //
70
void setTimer2PWM(const uint8_t top, const uint8_t duty)   // Phase Correct, Mode 5
71
{
72
    TCCR2A = 0;             // Reset
73
    TCCR2B = 0;  
74
    TIMSK2 = 0;   
75
    TCNT2 = 0;   
76
    OCR2A = top;            // Compare Match Register <= 255 
77
    OCR2B =  duty;          // Duty Cycle <= TOP
78
    TCCR2A = _BV(COM2B1) | _BV(WGM20);    // toggle OC2B pin
79
    TCCR2B = _BV(WGM22);
80
}
81
82
void runTimer2 (const uint16_t prescaler)
83
{
84
    switch (prescaler) {  // set Prescaler Clock Select Bits
85
        case    1 : TCCR2B = TCCR2B | _BV(CS20);                break;
86
        case    8 : TCCR2B = TCCR2B | _BV(CS21);                break;
87
        case   32 : TCCR2B = TCCR2B | _BV(CS21) | _BV(CS20);    break;
88
        case   64 : TCCR2B = TCCR2B | _BV(CS22);                break;
89
        case  128 : TCCR2B = TCCR2B | _BV(CS22) | _BV(CS20);    break;
90
        case  256 : TCCR2B = TCCR2B | _BV(CS22) | _BV(CS21);    break;
91
        case 1024 : TCCR2B = TCCR2B | _BV(CS22) | _BV(CS21) | _BV(CS20);    break;
92
        default :  break;
93
    }
94
}
95
96
void stopTimer2 (void)
97
{
98
    TCCR2B = TCCR2B & ~(_BV(CS22) | _BV(CS21) | _BV(CS20));
99
}

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Veit D. schrieb:
> 50% Duty erhält man nur mit OCRnB = OCRnA / 2.

Passt. Ich habe da wohl etwas missverstanden.

> cWenn B = TOP, liegt dauerhaft Ub an, 100% Duty Cycle.

Genau das ist eben nicht der Fall. Wenn OCRnB=TOP ist bleibt der Ausgang 
permanent auf LOW.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

noch ein Bsp. im Mode 1 Phase Correct, damit sollte der TO nun klar 
kommen.
Wie gesagt, ggf. Pinbelegung überprüfen.
1
/*
2
  Timer-Demo PWM-2
3
  ATmega328PB
4
  31.12.2020
5
  Timer Mode: Phase Correct Mode 1, TOP = 255
6
*/
7
8
#include <avr/io.h>
9
#include <avr/interrupt.h>
10
11
void setTimer0PWM(const uint8_t, const uint8_t);
12
void runTimer0(const uint16_t);
13
void stopTimer0(void);
14
15
void setTimer2PWM(const uint8_t, const uint8_t);
16
void runTimer2(const uint16_t);
17
void stopTimer2(void);
18
19
int main(void)
20
{
21
    DDRD  = DDRD | _BV(PD6);   // OC0A Ausgang 
22
    DDRD  = DDRD | _BV(PD5);   // OC0B Ausgang
23
    DDRB  = DDRB | _BV(PB3);   // OC2A Ausgang
24
    DDRD  = DDRD | _BV(PD3);   // OC2B Ausgang
25
    
26
    setTimer0PWM(210, 30);      // Duty-A, Duty-B
27
    setTimer2PWM(105, 40);          
28
              
29
    sei();                      // Interrupts enabled
30
    
31
    runTimer0(8);               // Prescaler
32
    runTimer2(8);  
33
    
34
    while (1) 
35
    {
36
    }
37
}
38
39
// ****** Funktionen ********* //
40
41
// ****** TIMER 0 ************ //
42
void setTimer0PWM(const uint8_t dutyA, const uint8_t dutyB)   //  Phase Correct, Mode 1
43
{    
44
    TCCR0A = 0;    // Reset
45
    TCCR0B = 0; 
46
    TIMSK0 = 0; 
47
    TCNT0 = 0;  
48
    OCR0A  = dutyA;           // Duty Cycle <= 255
49
    OCR0B  = dutyB;           // Duty Cycle <= 255
50
    TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);  // toggle OC0A und OC0B pin
51
}  
52
53
void runTimer0 (const uint16_t prescaler)
54
{
55
    switch (prescaler) {  // set Prescaler Clock Select Bits
56
        case    1 : TCCR0B = TCCR0B | _BV(CS00);              break;
57
        case    8 : TCCR0B = TCCR0B | _BV(CS01);              break;
58
        case   64 : TCCR0B = TCCR0B | _BV(CS01) | _BV(CS00);  break;
59
        case  256 : TCCR0B = TCCR0B | _BV(CS02);              break;
60
        case 1024 : TCCR0B = TCCR0B | _BV(CS02) | _BV(CS00);  break;
61
        default :  break;
62
    }
63
}
64
65
void stopTimer0 (void)
66
{
67
    TCCR0B = TCCR0B & ~(_BV(CS02) | _BV(CS01) | _BV(CS00));
68
}
69
70
71
// ****** TIMER 2 ************ //
72
void setTimer2PWM(const uint8_t dutyA, const uint8_t dutyB)   // Phase Correct, Mode 1
73
{
74
    TCCR2A = 0;    // Reset
75
    TCCR2B = 0;  
76
    TIMSK2 = 0;   
77
    TCNT2 = 0;   
78
    OCR2A = dutyA;           // Duty Cycle <= 255
79
    OCR2B = dutyB;           // Duty Cycle <= 255
80
    TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);    // toggle OC2A und OC2B pin
81
}
82
83
void runTimer2 (const uint16_t prescaler)
84
{
85
    switch (prescaler) {  // set Prescaler Clock Select Bits
86
        case    1 : TCCR2B = TCCR2B | _BV(CS20);                break;
87
        case    8 : TCCR2B = TCCR2B | _BV(CS21);                break;
88
        case   32 : TCCR2B = TCCR2B | _BV(CS21) | _BV(CS20);    break;
89
        case   64 : TCCR2B = TCCR2B | _BV(CS22);                break;
90
        case  128 : TCCR2B = TCCR2B | _BV(CS22) | _BV(CS20);    break;
91
        case  256 : TCCR2B = TCCR2B | _BV(CS22) | _BV(CS21);    break;
92
        case 1024 : TCCR2B = TCCR2B | _BV(CS22) | _BV(CS21) | _BV(CS20);    break;
93
        default :  break;
94
    }
95
}
96
97
void stopTimer2 (void)
98
{
99
    TCCR2B = TCCR2B & ~(_BV(CS22) | _BV(CS21) | _BV(CS20));
100
}

von Stefan F. (Gast)


Lesenswert?

Kapitel 2.9.5 Phase Correct PWM sagt

"The extreme values for the OCRnx Register represents special cases when 
generating a PWM waveform output in the phase correct PWM mode. If the 
OCRnx is set equal to BOTTOM the output will be continuously low"

[x] check

"and if set equal to TOP the output will be set to high for non-inverted 
PWM mode."

[ ] nope, funktioniert nicht, wenn OCRnA den TOP Wert bestimmt.

von Veit D. (devil-elec)


Lesenswert?

Stefan ⛄ F. schrieb:

>> cWenn B = TOP, liegt dauerhaft Ub an, 100% Duty Cycle.
>
> Genau das ist eben nicht der Fall. Wenn OCRnB=TOP ist bleibt der Ausgang
> permanent auf LOW.

Zeige mir einmal deinen kompletten Code, auf das Wesentliche reduziert, 
aber komplett.

von Kosmo Polit (Gast)


Lesenswert?

Veit D. schrieb:
> Zeige mir einmal deinen kompletten Code, auf das Wesentliche reduziert,
> aber komplett.

Und jetzt bitte als Datei-Anhang damit hier nicht so ein
Chaos entsteht.

von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

Stefan ⛄ F. schrieb:
> Das war mein Code:
1
  
2
     // Phase correct PWM, Prescaler 256, output A+B
3
     TCCR1A = (1<<WGM11) + (1<<WGM10) + (1<<COM1A1) + (1<<COM1B1);
4
     TCCR1B = (1<<WGM13) + (1<<CS12);
5
 
6
     // Set TOP value
7
     OCR1A = 0x8000;
8
 
9
     // Set pulse width
10
     OCR1B = 0x7FF0;
11
 
12
     // Output compare outputs A+B
13
     DDRB = (1<<PB1) + (1<<PB2);

Ich habe die Arduino IDE verwendet und genau das in die setup() Funktion 
geschrieben. loop() ist leer. Wenn ich bei OCR1B den gleichen Wert 
verwende wie bei OCR1A, dann bleibt der OC1B (PB2) Ausgang auf LOW.

von S. Landolt (Gast)


Lesenswert?

Merkwürdig.
Mangels eines ATmega168PA verwende ich einen ATmega328P, und bei dem 
geht PB2 auf high; in Assembler:
1
    puti    TCCR1A,(1<<WGM11)+(1<<WGM10)+(1<<COM1B1)
2
    puti    TCCR1B,(1<<WGM13)+(1<<CS11)
3
    putiw   OCR1A,$8000
4
    putiw   OCR1B,$8000
5
    sbi     DDRB,2

von S. Landolt (Gast)


Lesenswert?

Dito bei einem ATmega48PA.

von c-hater (Gast)


Lesenswert?

S. Landolt schrieb:

> Mangels eines ATmega168PA verwende ich einen ATmega328P, und bei dem
> geht PB2 auf high; in Assembler:
>
1
>     puti    TCCR1A,(1<<WGM11)+(1<<WGM10)+(1<<COM1B1)
2
>     puti    TCCR1B,(1<<WGM13)+(1<<CS11)
3
>     putiw   OCR1A,$8000
4
>     putiw   OCR1B,$8000
5
>     sbi     DDRB,2
6
>

Also, wenn du schon in Beispielcode eigene Macros verwendest, solltest 
du aber auch die passenden Includes dazu posten, damit es 
nachvollziehbar wird.

Natürlich ist hier klar, was die Intention ist und es ist auch 
kinderleicht, sich selber die passenden Macros zu bauen. Aber vermutlich 
nicht für jeden...

von S. Landolt (Gast)


Lesenswert?

> Natürlich ist hier klar, was die Intention ist

Eben!

> Aber vermutlich nicht für jeden...

Aber für die hier Beteiligten ganz sicher.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich habe das auch nochmal nachvollzogen und die Ausgänge gehen auf High, 
also 100% Duty Cycle.

Ich weiß aber wo das Problem liegt. "Arduino IDE" ist die entscheidende 
Information. In der Arduino IDE muss man die Timerkonfiguration anders 
angehen. Alle Timer sind für PWM (analogWrite) vorbelegt. Wenn man mit 
setup() und loop() programmiert ist das alles im Hintergrund 
vorbereitet. Hier muss man vorher alle Timerregister nullen, also auch 
TIMSK1 usw.. Kannst ja mal vor deiner Änderung alle Timerregister 
auslesen.

Oder man programmiert in der Arduino IDE klassisch mit
1
int main(void)
2
{
3
  while(1)
4
  {
5
  }
6
}
dann wird nichts vorbelegt usw.

von Veit D. (devil-elec)


Lesenswert?

c-hater schrieb:
> S. Landolt schrieb:
>
>> Mangels eines ATmega168PA verwende ich einen ATmega328P, und bei dem
>> geht PB2 auf high; in Assembler:
>>
1
>>     puti    TCCR1A,(1<<WGM11)+(1<<WGM10)+(1<<COM1B1)
2
>>     puti    TCCR1B,(1<<WGM13)+(1<<CS11)
3
>>     putiw   OCR1A,$8000
4
>>     putiw   OCR1B,$8000
5
>>     sbi     DDRB,2
6
>>
>
> Also, wenn du schon in Beispielcode eigene Macros verwendest, solltest
> du aber auch die passenden Includes dazu posten, damit es
> nachvollziehbar wird.

Mit Verlaub, die stehen alle im Headerfile des Controllers. Das sollte 
auch jedem Assembler Programmierer bewusst bzw. bekannt sein. Warum soll 
man Standard defines nochmal neu in eigene Makros gießen? Zudem sich 
genau diese Benennung im Manual wieder findet. Einfach gehts nicht 
fremden Code zu lesen.

von S. Landolt (Gast)


Lesenswert?

Nein, c-hater meinte die Makros puti und putiw; er wollte aber wohl 
nur mal wieder meckern.

Ihnen, Veit Devil, wünsche ich einen guten Rutsch ins Neue Jahr!

von Veit D. (devil-elec)


Lesenswert?

Hallo S.Landolt,

ach'so, alles klar. Dachte das sind Standard Assemblerbefehle. Naja 
egal. Habs verstanden. Ebenfalls guten und gesunden Rutsch ins neue 
Jahr!

von c-hater (Gast)


Lesenswert?

S. Landolt schrieb:

> Nein, c-hater meinte die Makros puti und /putiw/

Genau.

> er wollte aber wohl
> nur mal wieder meckern.

Nein. Überleg' dir doch einfach mal, was passiert, wenn jemand ohne 
Assemblerkenntnisse versucht, deinen Beispielcode zu verwenden...

Er wird zu der Erkenntnis kommen, dass Assembler Scheiße und unbenutzbar 
ist, weil nix funktioniert.

Ist es das, was du willst?

von S. Landolt (Gast)


Lesenswert?

an c-hater:

> Er wird zu der Erkenntnis kommen, dass Assembler Scheiße
> und unbenutzbar ist, weil nix funktioniert.
Mit einer solchen Einstellung und Befähigung wäre der hypothetisch 
Interessierte für Assembler ohnehin verloren.

> Ist es das, was du willst?
Mir doch egal - ich bin kein Assembler-Priester.

Und nun: Da sprach der Prinz von Pakistan ...

von c-hater (Gast)


Lesenswert?

S. Landolt schrieb:

> Mit einer solchen Einstellung und Befähigung wäre der hypothetisch
> Interessierte für Assembler ohnehin verloren.

Verloren, bevor er es überhaupt jemals ernsthaft benutzt hat.

>> Ist es das, was du willst?
> Mir doch egal - ich bin kein Assembler-Priester.

Meinetwegen.

Aber, wenn das Ziel war, kryptische, in ihrer Wirkung im Detail nur noch 
sehr schwer nachvollziehbare Makros zu verwenden, kannst du aber auch 
gleich C-Code schreiben. Denn das genau ist ja, was C tut...

von Wer readln will, muss vorher writeln! (Gast)


Lesenswert?

Das wäre eine gute Gelegenheit gewesen, ein Makro auch "Putin" zu 
nennen, wenn es schon "Puti" und "Putiw" gibt.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ c-hater, dass ist einem Streit doch gar nicht würdig. Das Forum ist 
voller Code Beispiele mit Funktionsaufrufen wo die Funktionsdefinitionen 
fehlen. Dafür aber brauchbare Namen verwendet wurden, sodass jeder 
halbwegs im Saft stehende weiß was gemeint ist und kann es 
nachprogrammieren. Notfalls fragt man nach. Oft ist es auch das Ziel nur 
einen Hinweis zu geben und nicht alles gleich offen für copy paste zu 
legen. Du legst doch auch nicht alles offen was du programmierst. Lasse 
das Jahr bitte ruhig ausklingen. Ich bitte darum. Im nächsten Jahr den 
Puls nicht gleich wieder hochfahren.  :-)

Noch ein Bsp. wie das bei mir in der Arduino IDE aussehen würde. Ich 
verwende hierfür allerdings einen ATmega2560. In diesem Bsp. ist ICR1 
für TOP zuständig. Atomic Block kann man sicherlich weglassen, habe ich 
mir allerdings so angewöhnt, weil sei() im Hintergrund aktiviert wird.
1
/*
2
  IDE 1.8.13
3
  avr-gcc 10.2.0
4
  Arduino Mega2560
5
  31.12.2020
6
  Timer 1 - Phase Correct Mode 10
7
*/
8
9
#include <util/atomic.h>
10
11
const byte pinPhaseA = 11;    // Pin OCR1A (am Uno/Nano Pin  9)
12
const byte pinPhaseB = 12;    // Pin OCR1B (am Uno/Nano Pin 10)
13
const unsigned int PRESCALER = 8;
14
const unsigned int TOP = 243;      
15
const unsigned int DUTYA = 210;   // <= TOP
16
const unsigned int DUTYB = 60;    // <= TOP
17
18
void setup (void)
19
{
20
  pinMode(pinPhaseA, OUTPUT);
21
  pinMode(pinPhaseB, OUTPUT);
22
  setTimer1(TOP, DUTYA, DUTYB);
23
  runTimer1(PRESCALER);
24
}
25
26
void loop (void)
27
{
28
  
29
}
30
31
32
// ****** Funktionen ******* //
33
void setTimer1 (const unsigned int top, const unsigned int dutyA, const unsigned int dutyB)
34
{
35
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)  // Mode 10, Phase Correct
36
  {
37
    TCCR1B = 0;             // Resets
38
    TCCR1A = 0;             //
39
    TIMSK1 = 0;             //
40
    TCNT1  = 0;             //
41
    ICR1 = top;
42
    OCR1A  = dutyA;
43
    OCR1B  = dutyB;
44
    TCCR1A = _BV(COM1B1) | _BV(COM1A1) | _BV(WGM11); 
45
    TCCR1B = _BV(WGM13);                         
46
  }                 
47
}
48
49
50
void runTimer1 (const unsigned int prescaler)
51
{
52
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
53
  {
54
    switch (prescaler) {  // set Prescaler Clock Select Bits
55
      case    1 : TCCR1B |= _BV(CS10);              break;
56
      case    8 : TCCR1B |= _BV(CS11);              break;
57
      case   64 : TCCR1B |= _BV(CS11) | _BV(CS10);  break;
58
      case  256 : TCCR1B |= _BV(CS12);              break;
59
      case 1024 : TCCR1B |= _BV(CS12) | _BV(CS10);  break;
60
      default :   TCCR1B |= _BV(CS11);              break;
61
    }  
62
  }
63
}
64
65
66
void stopTimer1 (void)
67
{
68
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
69
  {
70
    TCCR1B &= ~( _BV(CS12) | _BV(CS11) | _BV(CS10) );
71
  }
72
}

: Bearbeitet durch User
von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

S. Landolt schrieb:
> Mangels eines ATmega168PA verwende ich einen ATmega328P, und bei dem
> geht PB2 auf high;

Ich hatte auch einen ATmega368P verwendet, auf einem Arduino Nano Klon.

Veit D. schrieb:
> Alle Timer sind für PWM (analogWrite) vorbelegt.
> Hier muss man vorher alle Timerregister nullen

Interessant, das wusste ich nicht. Ich dachte, Arduino reserviert nur 
den Timer 0. Probiere ich gleich mal aus....

Ich das Problem nun nicht mehr reproduzieren. Bei dem angehängten Sketch 
gehen alle 6 Ausgänge auf High. Ich komme mir blöd vor. Trotzdem Danke 
Veit.

Beitrag #6532268 wurde von einem Moderator gelöscht.
von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

Hans,

ich habe gerade dein Programm ausprobiert (ohne serielle Ausgabe) und
bei mir gehen alle 4 Timer Ausgänge auf High. Also kann ich auch dein
Problem nicht reproduzieren.

Ich habe einen ATmega328P auf einem Arduino Nano Klon verwendet, sowie
avr-gcc 5.4.0 unter Debian Linux (dieses mal ohne Arduino Software).

Ich kann mir nur vorstellen, dass in deiner config.h irgend etwas
relevantes drin steht. Die hast du uns leider nicht gezeigt.

von Hans Schmidt (Gast)


Lesenswert?

Hallo zusammen,

da ist man mal 48h nicht am PC und dann geht es hier so hoch her...
Die config.h ist absolut unspektakulär:
1
#ifndef CONFIG_H_
2
#define CONFIG_H_
3
4
  #ifndef F_CPU
5
    #warning "F_CPU war noch nicht definiert, wird auf 14745600 festgesetzt"
6
    #define F_CPU 14745600UL
7
  #endif
8
  // Baudrate der UART definieren
9
  #define UART_BAUD_RATE 115200UL
10
  // Debuglevel festlegen (0 = keine Meldungen, 1 = Standard Ausgaben, 2 = erweiterte Ausgaben, 3 = alle Ausgaben)
11
  #define DEBUG 0
12
13
#endif
Wie gesagt: bin mittlerweile auch glücklich. Ich habe nur absolut Null 
Ahnung (mal wieder würde meine Frau und andere sagen) was da passiert 
ist...

von Stefan F. (Gast)


Lesenswert?

Hans Schmidt schrieb:
> Wie gesagt: bin mittlerweile auch glücklich

Was heißt das, funktionieren deine Ausgänge jetzt? Wenn ja: Warum? Was 
hast du geändert?

von Hans Schmidt (Gast)


Lesenswert?

@Stefan:
Hans Schmidt schrieb:
> @all: nutze nun den fast PWM mode und bin glücklich damit. Funktioniert
> alles wie es soll, auch wenn die Phase nun bei Änderungen nicht sauber
> ist. Aber die wird eh nur alle ~2min in kleinen Schritten geändert.

@all: nach euren Hilfen, Hinweisen und verständlichen Erklärungen bin 
ich nun doch wieder beim phasenkorrekten PWM gelandet. Ich finde es nur 
"fies", das es im Datenblatt nich schöner erklärt ist/wird. Egal, Leben 
ist kein Ponyhof.
Danke euch 2021 kann nur besser werden.

Hans

von Stefan F. (Gast)


Lesenswert?

Hans Schmidt schrieb:
> bin ich nun doch wieder beim phasenkorrekten PWM gelandet

Und es klappt auch dieses mal, nehme ich an. Aber warum? Was hast du 
korrigiert?

von S. Landolt (Gast)


Lesenswert?

Donnerwetter - da hat jemand aber einen besonders eifrigen Fan, um nicht 
zu sagen Claqueur; jeder noch so banale Beitrag wird beklatscht. 
Wirklich ganz erstaunlich.

von Stefan F. (Gast)


Lesenswert?

Es ist ein interessantes Problem, dessen Lösung ich gerne wüsste. 
Insbesondere weil ich es selbst nicht reproduzieren kann.

von S. Landolt (Gast)


Lesenswert?

Falls Ihnen doch noch ein Licht aufgeht, trösten Sie sich: ich hatte 
eben auch einen Moment gestutzt, bevor ich Ihren Beitrag begriff.


Da ich aber nun schon mal hier bin: ich wünsche allen hier ein gutes 
Neues Jahr (den meisten von Herzen, einigen mit (virtuellem) 
Handschlag), möge es zumindest nicht schlechter werden als 2020.

von c-hater (Gast)


Lesenswert?

S. Landolt schrieb:

> Da ich aber nun schon mal hier bin: ich wünsche allen hier ein gutes
> Neues Jahr (den meisten von Herzen, einigen mit (virtuellem)
> Handschlag), möge es zumindest nicht schlechter werden als 2020.

Naja, das wäre ja nun kaum mehr möglich. Abgesehen vom (idiotischerweise 
leider immer noch real möglichen weltweiten nuklearen Schlagabtausch) 
fällt mir da nichts mehr ein, was es noch mieser machen könnte, als 2020 
war.

Möge also dein Wunsch in Erfüllung gehen, die Chancen dafür sind relativ 
gut...

von Hans Schmidt (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Hans Schmidt schrieb:
>> bin ich nun doch wieder beim phasenkorrekten PWM gelandet
>
> Und es klappt auch dieses mal, nehme ich an. Aber warum? Was hast du
> korrigiert?

Eine Bedingung/Prüfung in den Code eingebaut die bei jeder Änderung der 
OCR Register zum Zuge kommt und dadurch sichergestellt ist, dass in 
allen Fällen OCRnA > OCRnB ist.

von Stefan F. (Gast)


Lesenswert?

Hans Schmidt schrieb:
> Eine Bedingung/Prüfung in den Code eingebaut die bei jeder Änderung der
> OCR Register zum Zuge kommt und dadurch sichergestellt ist, dass in
> allen Fällen OCRnA > OCRnB ist.

Aber OCRnA muss doch gar nicht > OCRnB sein!

von Hans Schmidt (Gast)


Lesenswert?

Kommt drauf an wie du es siehst, bzw. ich habe es ggf. immer noch nicht 
verstanden... Technisch hast du Recht und B dürfte auch größer als A 
sein. Aber was bringt das?
Der Zähler läuft von Null rauf auf TOP und wieder runter auf Null. Und 
TOP wird an der Stelle mit durch A festgelegt. Wenn ich also B größer 
als A setze, wird dieser Wert (vom Zähler) nie erreicht und ist somit 
wirkungslos.

von Stefan F. (Gast)


Lesenswert?

Hans Schmidt schrieb:
> Der Zähler läuft von Null rauf auf TOP und wieder runter auf Null. Und
> TOP wird an der Stelle mit durch A festgelegt. Wenn ich also B größer
> als A setze, wird dieser Wert (vom Zähler) nie erreicht und ist somit
> wirkungslos.

Das ist richtig. So weit sollst du auch gar nicht gehen, sondern nur bis 
B gleich TOP. Solange du um 1 darunter bleibst kommst du nie auf die 
maximal mögliche Pulsbreite von 100%.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ebenfalls gesundes Neues in die lustige Runde.

Ansonsten, blickt hier noch einer durch welchen Code der TO aktuell hat? 
Ich nicht. Deswegen nur ganz pauschal formuliert für den TO.
Es kommt darauf an welchen Timermode du verwendest.

Dabei hast du im Phase Correct Modus nur 5 Möglichkeiten - mit Timer 1 
erklärt. TOP ist fest auf 0xFF oder 0x01FF oder 0x03FF festgenagelt.
Damit kannste du die Pins OCnA und OCnB für PWM verwenden. Bedingung, 
die PWM Werte dürfen TOP nicht überschreiten.

Oder du nimmst den Mode mit ICRn als TOP.
Dann haste auch beide Pins OCnA und OCnB frei.
Bedingung, die PWM Werte dürfen TOP (ICR1) nicht überschreiten.

Oder du nimmst den Mode mit OCRnA als TOP.
Dann haste den Pins OCnB für PWM frei.
Bedingung, der PWM Wert darf TOP (OCRnA) nicht überschreiten.

Beim Update von OCRnA und OCRnB sind diese gepuffert. Das heißt die 
Änderung erfolgt immer zum richtigen Zeitpunkt. ICRn ist nicht 
gepuffert. Falls du Letzteres zur Laufzeit änderst kann es kurz zucken. 
Falls du die Frequenz ändern möchtest.

Und lass dich von der auf/ab Zählweise in diesem Mode nicht 
durcheinanderbringen. Für deine Einstellerei ist das unbedeutend. Das 
dient "nur" der richtigen Phasenlage. Sieht man schön wenn man beide 
Ausgänge am Oszi betrachtet.

Alles weitere nur mit entsprechenden Code.

: Bearbeitet durch User
Beitrag #6535891 wurde von einem Moderator gelöscht.
Beitrag #6535909 wurde von einem Moderator gelöscht.
Beitrag #6536073 wurde von einem Moderator gelöscht.
von Veit D. (devil-elec)


Lesenswert?

und wieder schrieb im Beitrag #6536073:
> @Veit
>
> DAS ist mal eine vernünftige Erklärung. DU solltest die Datenblätter
> schreiben!

Ich nehme es dankend zur Kenntnis.

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.