Forum: Mikrocontroller und Digitale Elektronik SAMD20 Problem mit PWM


von Bernd E. (berecke)


Angehängte Dateien:

Lesenswert?

Hallo Forum,
ich habe ein Problem mit der PWM des ATSAMD20J18A auf dem Board von 
Microchip (Atmel) SAMD20-XPRO. Das Programmbeispiel habe ich aus einer 
Elektor-Zeitschrift entnommen. Das Programm macht erstmal was es soll, 
dennoch zeigen sich regelmäßig Überlagerungen im PWM-Verlauf (siehe 
Anhang). Leider übersteigt der Fehler meinen derzeitigen Kenntnisstand. 
Kann jemand helfen? Die LEDs sind an PB02 und PB03
1
#include <asf.h>
2
3
volatile uint16_t i;                 //declarations of the variables
4
volatile uint16_t c0value = 65535;
5
volatile uint16_t c1value = 0;
6
7
void configure_tc(void);  //function prototype
8
9
struct tc_module tc_instance; //generate a instance-structure
10
11
void configure_tc(void) //configurate the TC
12
{
13
  struct tc_config config_tc;
14
15
  tc_get_config_defaults(&config_tc);
16
17
  config_tc.counter_size    = TC_COUNTER_SIZE_16BIT;
18
  config_tc.wave_generation = TC_WAVE_GENERATION_NORMAL_PWM;
19
  
20
  config_tc.counter_16_bit.compare_capture_channel[0] = 0xFFFF;  //compare-channel0
21
  config_tc.pwm_channel[0].enabled = true;
22
  config_tc.pwm_channel[0].pin_out = EXT1_PWM_0_PIN;
23
  config_tc.pwm_channel[0].pin_mux = EXT1_PWM_0_MUX;
24
  config_tc.counter_16_bit.compare_capture_channel[1] = 0xFFFF;  //compare-channel1
25
  config_tc.pwm_channel[1].enabled = true;
26
  config_tc.pwm_channel[1].pin_out = EXT1_PWM_1_PIN;
27
  config_tc.pwm_channel[1].pin_mux = EXT1_PWM_1_MUX;
28
29
  tc_init(&tc_instance, EXT1_PWM_MODULE, &config_tc);
30
  tc_enable(&tc_instance);
31
}
32
33
int main(void)
34
{
35
  system_init();
36
  delay_init();
37
  configure_tc();
38
39
  while (true) {
40
    for(i = 0; i < 65535; i++ ){  //turn channel0 slowly off and channel1 slowly on
41
      c0value = 65535 - i;
42
      c1value = i;
43
      tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_0, c0value);
44
      tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_1, c1value);
45
      delay_us(20);
46
    }
47
    for(i = 0; i < 65535; i++ ){  //turn channel1 slowly off and channel0 slowly on
48
      c0value = i;
49
      c1value = 65535 - i;
50
      tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_0, c0value);
51
      tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_1, c1value);
52
      delay_us(20);
53
    }
54
  }
55
}

von foobar (Gast)


Lesenswert?

Ohne jetzt die Timer des SAMD20 zu kennen, vermute ich, dass durch dein 
wildes Rumgeballere auf das Compare-Register (mit 50kHz, 20µs) der Timer 
ab und zu keinen Match macht (z.B. während der Timer hochzählt, setzt du 
das Compare-Register auf eine kleineren Wert) und dann für diese Periode 
der Ausgang nicht abgeschaltet wird.

von Bernd E. (berecke)


Lesenswert?

Das klingt plausibel, zu mal in der main-Routine nicht geprüft wird, ob 
der timer seine Runde beendet hat.

von Bernd E. (berecke)


Lesenswert?

Leider lag es nicht daran. Es sieht aus, als wenn ein Interrupt- oder 
Event-Ereignis dazwischen funkt. Ist doch aber alles im Grundzustand 
abgeschaltet ?!

von Jim M. (turboj)


Lesenswert?

Bernd E. schrieb:
> Leider lag es nicht daran. Es sieht aus

Wir sehen Deinen Source Code nicht. Vermutlich schreibst Du immer nocht 
eines der Capture Register zum falschen Zeitpunkt.

Übrigens mache ich sowas gerne mit DMA...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jim M. schrieb:
> Übrigens mache ich sowas gerne mit DMA...

Alternativ (so habe ich es in der avr-libc im einfachsten Beispiel
mal gemacht): man aktualisiert den PWM-Wert immer nur dann, wenn
gerade ein Timerinterrupt durch ist, d.h. der aktuelle Zyklus eben
begonnen hat.

von Bernd E. (berecke)


Lesenswert?

Jim M. schrieb:
> Bernd E. schrieb:
>> Leider lag es nicht daran. Es sieht aus
>
> Wir sehen Deinen Source Code nicht. Vermutlich schreibst Du immer nocht
> eines der Capture Register zum falschen Zeitpunkt.
>
> Übrigens mache ich sowas gerne mit DMA...


Leider nicht ganz weg. LED an PB3 sieht gut aus, oder Fehler ist 
verdeckt.
LED an PB2 hat immer noch den beschriebenen Fehler.

1
#include <asf.h>
2
3
volatile uint16_t i;                 //declarations of the variables
4
volatile uint16_t c0value = 65535;
5
volatile uint16_t c1value = 0;
6
7
void configure_tc(void);  //function prototype
8
9
struct tc_module tc_instance; //generate a instance-structure
10
11
void configure_tc(void) //configurate the TC
12
{
13
  struct tc_config config_tc;
14
15
  tc_get_config_defaults(&config_tc);
16
17
  config_tc.counter_size    = TC_COUNTER_SIZE_16BIT;
18
  config_tc.wave_generation = TC_WAVE_GENERATION_NORMAL_PWM;
19
  
20
  config_tc.counter_16_bit.compare_capture_channel[0] = 0xFFFF;  //compare-channel0
21
  config_tc.pwm_channel[0].enabled = true;
22
  config_tc.pwm_channel[0].pin_out = EXT1_PWM_0_PIN;
23
  config_tc.pwm_channel[0].pin_mux = EXT1_PWM_0_MUX;
24
  config_tc.counter_16_bit.compare_capture_channel[1] = 0xFFFF;  //compare-channel1
25
  config_tc.pwm_channel[1].enabled = true;
26
  config_tc.pwm_channel[1].pin_out = EXT1_PWM_1_PIN;
27
  config_tc.pwm_channel[1].pin_mux = EXT1_PWM_1_MUX;
28
29
  tc_init(&tc_instance, EXT1_PWM_MODULE, &config_tc);
30
  tc_enable(&tc_instance);
31
}
32
33
int main(void)
34
{
35
  system_init();
36
  delay_init();
37
  configure_tc();
38
39
  while (true) {
40
    if (tc_get_count_value(&tc_instance) == 0x0000){
41
      for(i = 0; i < 65535; i++ ){  //turn channel0 slowly off and channel1 slowly on
42
        c0value = 65535 - i;
43
        c1value = i;
44
        tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_0, c0value);
45
        tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_1, c1value);
46
        delay_us(20);
47
      }
48
      for(i = 0; i < 65535; i++ ){  //turn channel1 slowly off and channel0 slowly on
49
        c0value = i;
50
        c1value = 65535 - i;
51
        tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_0, c0value);
52
        tc_set_compare_value(&tc_instance, TC_COMPARE_CAPTURE_CHANNEL_1, c1value);
53
        delay_us(20);
54
      }
55
    }
56
  }
57
}

von Jim M. (turboj)


Lesenswert?

Dachte ich mir, Dein Programm synct überhaupt nicht ernsthaft mit den 
Timer.

Einmal am Anfang reicht nicht!

von doppelschwarz (Gast)


Lesenswert?

Wenn bisheriger PWM-Wert gleich 0, dann direkt setzen, ansonsten Wert 
merken und den Match or Capture Channel Interrupt setzen (INTENSET->MC1 
oder MC0, je nach verwendetem Kanal). Im richtigen Interrupt (INTFLAG 
abfragen nach Kanal) dann den Interrupt löschen und wieder abschalten, 
zum Schluss den gemerkten Wert setzen. Register-Synchronisierung nicht 
vergessen (STATUS->SYNCBUSY), falls nötig.
Damit wird ein neuer Wert immer nur gesetzt, wenn der aktuelle gerade 
erreicht wurde und der Zähler von vorne beginnt. Bei sehr hoher 
PWM-Frequenz und niedrigem Wert könnte es zum gleichen Effekt kommen, 
wenn der Code im Interrupt den neuen Wert nicht rechtzeitig setzen kann 
(PWM läuft ja weiter), von daher den Wert möglichst früh setzen.

von Bernd E. (berecke)


Lesenswert?

Danke für die Antworten. Ich merke schon, da fehlt mir noch einiges an 
Hintergrundwissen über die Timer der SAMs. Ich dachte, dass es auch ohne 
Interrupts geht. Nun fehlen mir die passenden Beispiele zum Timer.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bernd E. schrieb:
> Ich dachte, dass es auch ohne Interrupts geht.

Interrupts sind etwas sehr Essenzielles bei einem Mikrocontroller. Mit
ihnen musst du dich so oder so vertraut machen.

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.