Forum: Mikrocontroller und Digitale Elektronik ATMega32: PWM _und_ Timer Overflow, Expertenfrage


von Sascha N. (sadaniro)


Lesenswert?

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!

von Sascha N. (Gast)


Lesenswert?

Hat keiner ne Idee?

von Sascha N. (Gast)


Lesenswert?

Hmm.. kann mir wirklich keiner helfen?

von Oliver (Gast)


Lesenswert?

Sascha N. schrieb:
> Hmm.. kann mir wirklich keiner helfen?

Code komplett als Anhang posten
Frage bzw. Problem kurz und prägnant formulieren.

Dann klappst auch mit der Hilfe. Durch den Wust in deiner Frage arbeitet 
sich freiwillig keiner durch.

Oliver

von ... (Gast)


Lesenswert?

Sascha N. schrieb:
> 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?

Möglich, das sleep in der ISR ist jedenfalls keine gute Idee. Das 
"clearDisplay();" (OK auskommentiert) und das 
"displayCurrentParameter();" sind ebenfalls daneben.
Setz einfach ein Flag und mach die Ausgaben in main.

In Deinem Fall scheint das zwar nicht allzu kritisch zu sein (zumindest 
wenn in main eine leere Schleife steht), Du verlierst eventuell ein paar 
Interrupts und Deine Tasten reagieren etwas träge, schön ist das aber 
trotzdem nicht.

Ansonsten fehlt ein großer Teil Deines Programms ==> Fehler in Zeile 42
Eventuell geht Dir einfach der Ram aus.

PS:
Das hier ist auch ziemlich daneben:
1
parameter->value = (parameter->value)++;
Eine Variable sich selbst zuzuweisen und sie anschließend zu erhöhen 
macht irgendwie keinen Sinn.
Also entweder so:
1
parameter->value++;
Oder so:
1
parameter->value = parameter->value + 1;
Beim decrease das Gleiche dann analog.

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.