Hi, ich habe ein merkwürdiges Problem mit dem ATMega32@16Mhz. Dieser soll zur Ansteuerung von 4 Servomotoren verwendet werden. Die Generierung der PWM-Signale klappt soweit sehr gut. Ich verwende alle vier möglichen PWM-Kanäle (Timer0, Timer1 (OC1A, OC1B) und Timer2). Das Modul zur PWM-Erzeugung enthält eine Initialisierungsfunktion, die die Timer intialisiert und vier weitere mit denen die Pulsbreite zur Laufzeit festgelegt wird:
1 | ...
|
2 | |
3 | void initPWM() |
4 | {
|
5 | // Note: _BV(x) => 1 << x
|
6 | // (converts a bit number into a byte value)
|
7 | |
8 | //-- Timer 0 (for pwm_8_1 alias OC0 alias PB3, Low Resolution)
|
9 | // Mode: Fast PWM
|
10 | // => WGM00 = 1, WGM01 = 1
|
11 | // Prescaler = 1024 => f= 61,03 Hz
|
12 | // => CS02 and !CS01 and CS00
|
13 | //Clear OC0 on compare match, set OC0 at BOTTOM,
|
14 | //(non-inverting mode)
|
15 | // => !COM00 and COM01
|
16 | TCCR0 = _BV(WGM00) | _BV(WGM01) | _BV(COM01) | _BV(CS02) | _BV(CS00); |
17 | // Set PB3 in Data Direction Register as output pin
|
18 | DDRB |= _BV(PB3); |
19 | |
20 | //-- Timer 1 (for pwm_16_1/pwm_16_2 alias PD4/PD5 alias OC1A/OCAB, High Resolution)
|
21 | // Mode: Phase and Frequency Correct PWM
|
22 | // WGM13 = 1 , WGM12 = WGM11 = WGM10 = 0
|
23 | // Set OC1A/OC1B on compare match when upcounting.
|
24 | // Clear OC1A/OC1B on compare match when downcounting.
|
25 | // COM1A1 = COM1B1 = 1 , COM1A0 = COM1B0 = 1
|
26 | TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(COM1A0) | _BV(COM1B0); |
27 | // [!CS12 && CS11 && !CS13) => Prescaler = 8
|
28 | TCCR1B = _BV(WGM13) | _BV(CS11); |
29 | // Set PD4/Pd5 in the Data Direction Register as output pin
|
30 | DDRD |= _BV(PD4) | _BV(PD5); |
31 | // Set the upper limit of Timer 1 (=> 50 Hz pwm freq.)
|
32 | ICR1 = TIMER1_TOP; |
33 | |
34 | //-- Timer 2 (for pwm_8_2 alias OC0 alias PB3, Low Resolution)
|
35 | // Mode: Fast PWM
|
36 | // => WGM21=1 and WGM20=1
|
37 | // Clear OC2 on compare match, set OC2 at BOTTOM,
|
38 | // (non-inverting mode)
|
39 | // COM21 = 1 COM20=0
|
40 | // Prescaler = 1024 => f= 61,03 Hz
|
41 | // => CS22 and CS21 and CS20
|
42 | TCCR2 = _BV(WGM21) | _BV(WGM20) | _BV(COM21) | _BV(CS22) | _BV(CS21) | _BV(CS20); |
43 | // Set PD7 in the Data Direction Register as output pin
|
44 | DDRD |= _BV(PD7); |
45 | }
|
46 | |
47 | // Sets a pulswidth between 0 and 20000 µs (0% and 100% of 20 ms) on PWM Channel 1 (OC1A/PD5)
|
48 | void setPwm16_1(uint16 pulsWidth) |
49 | {
|
50 | if(pulsWidth >= 0 && pulsWidth <= TIMER1_TOP) |
51 | {
|
52 | // as closer as we reach the TOP Value as smaller the pulse becomes
|
53 | // since we use the inverted Mode
|
54 | // Output Compare Register 1A = TOP Value - pulsWidth
|
55 | OCR1A = TIMER1_TOP - pulsWidth; |
56 | }
|
57 | }
|
58 | |
59 | // Sets a pulswidth between 0 and 20000 µs (0% and 100% of 20 ms) on PWM Channel 2 (OC1B/PD4)
|
60 | void setPwm16_2(uint16 pulsWidth) |
61 | {
|
62 | if(pulsWidth >= 0 && pulsWidth <= TIMER1_TOP) |
63 | {
|
64 | // as closer as we reach the TOP Value as smaller becomes the pulse (inverted mode)
|
65 | // Output Compare Register 1B = TOP Value - pulsWidth
|
66 | OCR1B = TIMER1_TOP - pulsWidth; |
67 | }
|
68 | }
|
69 | |
70 | // Sets a pulswidth between 0 and 256 (0% and 100% of 1/(61,03 Hz) = 15,87 ms) on PWM Channel 3 (OC0/PB3)
|
71 | void setPwm8_1(uint8 pulsWidth) |
72 | {
|
73 | OCR0 = pulsWidth; |
74 | }
|
75 | |
76 | // Sets a pulswidth between 0 and 256 (0% and 100% of 1/(61,03 Hz) = 15,87 ms ) on PWM Channel 4 (OC2/PD7)
|
77 | void setPwm8_2(uint8 pulsWidth) |
78 | {
|
79 | OCR2 = pulsWidth; |
80 | }
|
Wie erwähnt klappt das im Prinzip ganz gut. Jetzt geht es daraum die Servoparameter über zwei Taster zu verändern, da ich die viele Pulsweiten für die Anwendung durch experimentieren ermitteln muss. Dazu nutze ich den Timer2 Overflow Interrupt um zyklisch die Tasterwerte abzufragen. Soweit ich das Datenblatt verstanden habe ist es möglich sowohl den Timer2 zur PWM-Erzeugung (Fast PWM Mode) als auch den Timer 2 Overflow Interrupt zu nutzen, oder? Bevor ich die Interrupt-Routine vorstelle will ich nochmal auf ein für diese Experimente entscheidendes Modul eingehen, dass die Datenstruktur definiert mit dem die vier PWM-Parameter verändert werden können:
1 | // Represents a pwm parameter
|
2 | struct pwmParameter |
3 | {
|
4 | // name of the pwm parameter
|
5 | char name[10]; |
6 | // value of the pwm parameter
|
7 | uint16 value; |
8 | // max value of the pwm parameter value (255 for 8-bit pwm, 20000 for 16-bit pwm)
|
9 | uint16 maxValue; |
10 | // pointer to the next parameter (lsingle-linked-list)
|
11 | struct pwmParameter* next; |
12 | // Pointer to a function which increases the pwm parameter value by one ( + 1)
|
13 | void (*increase)(struct pwmParameter*); |
14 | // // Pointer to a function which decreases the pwm parameter value by one (- 1)
|
15 | void (*decrease)(struct pwmParameter*); |
16 | // Updates the pwm parameter. Not needed to be called after increase/decrease! Its called within increase/decrease functions
|
17 | void (*update)(struct pwmParameter*); |
18 | };
|
19 | |
20 | typedef struct pwmParameter* psPwmParameter; |
21 | |
22 | // Initializes the pwm parameters (Should be called before getFirstParameter() is called!!!)
|
23 | void initPwmParameters(void); |
24 | // Gets the first parameter of the of the linked-list of parameters
|
25 | psPwmParameter getFirstParameter(void); |
26 | |
27 | void increase(psPwmParameter); |
28 | void decrease(psPwmParameter); |
29 | |
30 | void updatePwm16_1(psPwmParameter); |
31 | void updatePwm16_2(psPwmParameter); |
32 | void updatePwm8_1(psPwmParameter); |
33 | void updatePwm8_2(psPwmParameter); |
34 | |
35 | struct pwmParameter pwm16_1 = {"pwm16_1", (uint16) 1500,(uint16) 20000u, 0, &increase, &decrease, &updatePwm16_1}; |
36 | struct pwmParameter pwm16_2 = {"pwm16_2", (uint16) 1500, (uint16)20000u, 0, &increase, &decrease, &updatePwm16_2 }; |
37 | struct pwmParameter pwm8_1 = {"pwm8_1", (uint16)20, (uint16)255, 0 , &increase, &decrease, &updatePwm8_1}; |
38 | struct pwmParameter pwm8_2 = {"pwm8_2", (uint16)20, (uint16)255, 0, &increase, &decrease, &updatePwm8_2}; |
39 | |
40 | // initialize function (bind together the linked-list
|
41 | void initPwmParameters() |
42 | {
|
43 | pwm16_1.next = &pwm16_2; |
44 | pwm16_2.next = &pwm8_1; |
45 | pwm8_1.next = &pwm8_2; |
46 | pwm8_2.next = &pwm16_1; |
47 | }
|
48 | |
49 | // Gets the first parameter of a single linked list of pwm parameters
|
50 | psPwmParameter getFirstParameter(void) |
51 | {
|
52 | return &pwm16_1; |
53 | }
|
54 | |
55 | void increase(psPwmParameter parameter) |
56 | {
|
57 | if( parameter->value < parameter->maxValue ) |
58 | parameter->value = (parameter->value)++; |
59 | |
60 | parameter->update(parameter); |
61 | }
|
62 | |
63 | void decrease(psPwmParameter parameter) |
64 | {
|
65 | if( parameter->value > 0 ) |
66 | parameter->value = (parameter->value)--; |
67 | |
68 | parameter->update(parameter); |
69 | }
|
70 | |
71 | void updatePwm16_1(psPwmParameter pwm16_1) |
72 | {
|
73 | setPwm16_1(pwm16_1->value); |
74 | }
|
75 | |
76 | void updatePwm16_2(psPwmParameter pwm16_2) |
77 | {
|
78 | setPwm16_2((pwm16_2->value)); |
79 | }
|
80 | |
81 | void updatePwm8_1(psPwmParameter pwm8_1) |
82 | {
|
83 | setPwm8_1((uint8) (pwm8_1->value)); |
84 | }
|
85 | |
86 | void updatePwm8_2(psPwmParameter pwm8_2) |
87 | {
|
88 | setPwm8_2((uint8) (pwm8_2->value)); |
89 | }
|
Das Modul in dem die Interrupt-Routine definiert wird und die mit dem ersten pwm Parameter (element der verketteten Liste) initialisiert wird sieht wie folgt aus:
1 | volatile uint8 SW_PrescalerCounter; |
2 | // Value of the software prescaler. The value affects the effective interrupt frequency.
|
3 | // Higher values lead to slower up-/downcounting of the current parameter.
|
4 | const uint8 SW_PRESCALER = 2; |
5 | // counter used to measure how long _both_ buttons are pressed.
|
6 | volatile uint8 changeModeCounter; |
7 | // The Threshold value used to changed the mode.
|
8 | const uint8 CHANGEMODETHRESHOLD = 30; |
9 | |
10 | // Displays the current parameter
|
11 | void displayCurrentParameter(void); |
12 | |
13 | // pointer to the (current) struct pwmParameter. Will be initialized when InitPwmParameterConfigurationControl(...) is called.
|
14 | psPwmParameter current = 0; |
15 | |
16 | void InitPwmParameterConfigurationControl(psPwmParameter firstPwmParameter) |
17 | {
|
18 | current = firstPwmParameter; |
19 | }
|
20 | |
21 | // ISR-Routine is called each time the timer 2 overflows (61,04 times per second)
|
22 | // An additional software prescaler reduces the frequency to 61,04 / 2 = 30,52 Hz
|
23 | ISR(TIMER2_OVF_vect) |
24 | {
|
25 | // Additional SW Prescaler
|
26 | if((SW_PrescalerCounter++ % SW_PRESCALER) != 0) |
27 | {
|
28 | return; |
29 | }
|
30 | // button s1 pressed?
|
31 | if(!(PIND & _BV(PD3))) |
32 | {
|
33 | current->decrease(current); |
34 | }
|
35 | // button s2 pressed?
|
36 | if(!(PIND & _BV(PD1))) |
37 | {
|
38 | current->increase(current); |
39 | }
|
40 | // both pressed (means that the user wants to change the pwm parameter to configure)?
|
41 | if(!(PIND & _BV(PD3)) & !(PIND & _BV(PD1))) |
42 | {
|
43 | changeModeCounter++; |
44 | |
45 | // pressed _both_ buttons long enough?
|
46 | if(changeModeCounter == CHANGEMODETHRESHOLD) |
47 | {
|
48 | changeModeCounter = 0; |
49 | // change the current pwm param to configure
|
50 | current = current->next; |
51 | // sleep 1 ms.
|
52 | sleep_ms(1); |
53 | // clearDisplay();
|
54 | }
|
55 | }
|
56 | // print the current parameter on the display
|
57 | displayCurrentParameter(); |
58 | }
|
59 | |
60 | void displayCurrentParameter() |
61 | {
|
62 | writeToDisplay_s(0,1,current->name); |
63 | writeToDisplay_uint16(8,1,current->value); |
64 | }
|
Jedenfalls funktioniert das Verändern des aktuelle PWM-Parameter prinzipiell gut über die beiden Taster und es klappt auch der Wechsel des aktuellen parameters zum nächsten. Die vier PWN-Signale werden auch generiert ABER nur wenn ich den Timer 2 Overflow Interrupt NICHT aktiviert habe, d.h. wenn ich am Anfang des Programs einmal alle vier set_pwm_xx-Funktionen mit hardcodierten Werten aufrufe und dann nichts mehr (while(1);). Wenn ich die Werte mit dem obigen Interrupt Routine ändere funktioniert zwar das verändern des aktuellen parameters (sehe ich an ausgaben am Display) aber es werden KEINE PWM-Signale (weder Timer2 noch Timer1 und Timer0) mehr erzeugt. Liegt es vielleicht am sleep_ms(1) in der ISR? Meint ihr die Interrupt-Routine ist zu lang?? Da ich mit einem Prescaler von 1024 arbeite müssten doch genügend Takte zur Verfügung stehen? Es muss sich um einen systematischen Fehler handeln, da beide Teile einzeln (PWM-signalerzeugung selbst als auch der Teil zur Veränderung der Parameter zur laufzeit) funktionieren. Bin mal gespannt was ihr dazu meint, Laut den Antworten im Forum die ich bisher gelesen habe müsste es eigentlich funktionieren. Vielen Dank schonmal!