Forum: Mikrocontroller und Digitale Elektronik XMega HighRes PWM - irgendwie nicht so HighRes


von Johannes N. (strangeman)


Lesenswert?

Hallo!

Ich beschäftige mich gerade mit der HighRes extension des Timers in 
einem Xmega128A3.
Der soll eine einfache (single-slope) PWM mit 10bit an PORTD:0-3 
erzeugen - und das möglichst flink. Dazu habe ich die HighRes extension 
eingeschaltet und die Clocks so configuriert, dass Fper4 auf 128MHz 
läuft und die CPU auf 32MHz.
Auf dem Oszi sehe ich schön meine PWM mit 125kHz. Im Datenblatt steht, 
dass Werte kleiner als 4 keinen Output erzeugen, das leuchtet mir im 
anbetracht der allgemeinen Funktionsweise des HighRes Moduls ein. Ich 
kann das Verhalten auch am Oszi sehen.

Was ich aber ebenfalls sehe, ist dass die Werte 4,5,6,7 allesamt die 
selbe Wellenform erzeugen, ebenso die Werte 8,9,10,11 usw.
De facto ist der Output also in nur 4er Schritten einstellbar. Warum?! 
Dann hätte ich ja gleich meine PWM Auflösung um 2bit verringern und den 
Timer ohne HighRes Extension betreiben können.

Hier kommt mein Code.
1
/** initializes the 4 pwm outputs.
2
 **/
3
void pwm_init( void )
4
{
5
  // set pins to output
6
  PORTD.DIRSET = PIN0_bm | PIN1_bm | PIN2_bm | PIN3_bm;
7
  // enable all four outputs, single slope PWM
8
  TCD0.CTRLB = TC0_CCAEN_bm | TC0_CCBEN_bm 
9
        | TC0_CCCEN_bm | TC0_CCDEN_bm
10
        | TC_WGMODE_SS_gc;
11
  // configure 10bit operation
12
  TCD0.PER = 1023;
13
  // configure highres option (128MHz base speed)
14
  HIRESD.CTRLA = HIRES_HREN_BOTH_gc;
15
  
16
  // enable the timer
17
  TCD0.CTRLA = TC_CLKSEL_DIV1_gc;
18
  
19
  // set dummy values
20
  TCD0.CCABUF = 512;
21
  TCD0.CCBBUF = 256;
22
  TCD0.CCCBUF = 128;
23
  TCD0.CCDBUF = 64;
24
25
}
26
27
/** sets a channel to the desired value
28
 * @param channel   channel number (0-3)
29
 * @param value    channel value (0-1024)
30
 **/
31
 void pwm_setChannel(uint8_t channel, uint16_t value)
32
 {
33
   // no values > 1023 allowed!
34
   value = (value > 1023)?1023:value;
35
   
36
   switch (channel)
37
   {
38
     case 0: TCD0.CCABUF = value; break;
39
     case 1: TCD0.CCBBUF = value; break;
40
     case 2: TCD0.CCCBUF = value; break;
41
     case 3: TCD0.CCDBUF = value; break;
42
     default: break;
43
   }
44
 }


Danke für eure Hilfe!
Johannes

von Thomas F. (tomasf)


Lesenswert?

Dein Code sieht soweit OK aus

Hier solltest du das nur für den Timer einschalten, den du auch benutzt.
1
 HIRESD.CTRLA = HIRES_HREN_BOTH_gc;

Der Fehler liegt also wahrscheinlich in deinem anderem Code.

Es gibt auch eine Atmel Appnote zu Hires/Awex mit Beispiel, im Zweifel 
kannst du damit anfangen.

von Johannes N. (strangeman)


Lesenswert?

Hi Thomas,

ist es relevant, ob ich tatsächlich beide Timer verwende?
später soll auch der andere Timer hinzukommen, daher habe ich das Bit 
schon gesetzt, auch wenn der Timer noch nicht läuft. Du hast Recht, 
vielleicht spinnt die HiRes Extension, wenn einer der beiden Timer nicht 
läuft.
Wenn ich zuhause bin, probiere ich nochmal, ob es geht, wenn ich den 
Hires nur für den einen Timer einschalte.

Die Appnote habe ich gelesen, nur die macht einfach genau das selbe wie 
ich (abgesehen von dem Fehler, den du beschrieben hast)

Ich frage mich allerdings, wie die Ports betrieben werden. Die haben ja 
auch irgendeine Art von Taktung. Wenn die Ports nur mit 32MHz geupdatet 
werden, dann würde mein Ergebnis nicht verwundern. Aber dann wäre eben 
auch die ganze HiRes Extension sinnlos.

von Thomas F. (tomasf)


Lesenswert?

Das Timer-Bit sollte egal sein, aber wenns nicht funktioniert, würde ich 
es erstmal auschalten.

Ich habe Hires(+Awex) bereits verwendet und es funktioniert, wie es soll 
(auch nachgemessen) (mit xmegaA4U)

sonst:
-Clockeinstellungen Ok?
-xmega A3 oder A3U?, die U-Serie hat weniger Bugs

von Johannes N. (strangeman)


Lesenswert?

Es ist die A3 serie. Ich schau mal auf der Atmel Seite, obs irgendwo 
known bugs gibt. EDIT: Es gibt ja einen haufen Errata, aber leider steht 
nix zum HiRes drin. Die A3U serie scheint ja weitestgehend Bugfrei zu 
sein.

Hier ist der Code, mit dem ich die Clocks initialisiere. Ich finde 
keinen Fehler darin.
1
static void clock_init(void)
2
{
3
  // configure external 16MHz crystal, low power mode on 32kHz external crystal,
4
  // 16k cycles startup time
5
  OSC.XOSCCTRL = OSC_FRQRANGE_12TO16_gc | OSC_X32KLPM_bm | OSC_XOSCSEL_XTAL_16KCLK_gc;
6
  
7
  // enable external clock, keep internal 2MHZ clock running (it is still used!)
8
  // all other clocks disabled
9
  OSC.CTRL = OSC_XOSCEN_bm | OSC_RC2MEN_bm;
10
  
11
  // PLL clock source: external clock; factor: 8 => 128MHz output
12
  OSC.PLLCTRL = (OSC_PLLSRC_XOSC_gc | (8 & OSC_PLLFAC_gm));
13
  
14
  // wait for external clock to be ready
15
  while (!(OSC.STATUS & OSC_XOSCRDY_bm));
16
  
17
  // enable PLL
18
  OSC.CTRL |= OSC_PLLEN_bm;
19
  
20
  // wait for pll to be ready
21
  while (!(OSC.STATUS & OSC_PLLRDY_bm));
22
  
23
  // protect during prescaler change
24
  CCP = CCP_IOREG_gc;
25
  // set Prescalers:
26
  // Prescaler A: factor 1 => clk_4 runs at 128MHz
27
  // Prescaler B: factor 1/2 => clk_2 runs at 64MHz
28
  // Prescaler C: factor 1/2 => cpu and periphery runs at 32MHz
29
  CLK.PSCTRL = CLK_PSADIV_1_gc | CLK_PSBCDIV_2_2_gc;
30
  
31
  // protect during clock source change
32
  CCP = CCP_IOREG_gc;
33
  // switch clock source to PLL
34
  CLK.CTRL = CLK_SCLKSEL_PLL_gc;
35
  
36
  // Turn off int. RC osc.
37
  OSC.CTRL &= ~OSC_RC2MEN_bm;
38
}

: Bearbeitet durch User
von Thomas F. (tomasf)


Lesenswert?

Der Code für die Clock scheint OK zu sein.

Den hier benutzte ich mit 8Mhz-Quarz
1
OSC.XOSCCTRL = OSC_FRQRANGE_2TO9_gc | OSC_XOSCSEL_XTAL_16KCLK_gc; 
2
OSC.CTRL |= OSC_XOSCEN_bm;
3
while (!(OSC.STATUS & OSC_XOSCRDY_bm)); 
4
5
CCP = CCP_IOREG_gc;
6
CLK.PSCTRL = CLK_PSADIV_1_gc | CLK_PSBCDIV_2_2_gc; 
7
  
8
OSC.PLLCTRL = OSC_PLLSRC_XOSC_gc | 16; 
9
OSC.CTRL |= OSC_PLLEN_bm;
10
while (!(OSC.STATUS & OSC_PLLRDY_bm));
11
  
12
CCP = CCP_IOREG_gc;
13
CLK.CTRL = CLK_SCLKSEL_PLL_gc;

Dann kannst Du eigentlich nur noch genau das Atmel-Beispiel ausprobieren 
und sehen, was da raus kommt.

von Johannes N. (strangeman)



Lesenswert?

Hi Thomas,

ich habe jetzt den HiRes nur für TC0 aktiviert und das Problem bleibt 
bestehen.

Interessanterweise erhalte ich neuerdings ziemlich starken Jitter, der 
aber besteht, egal ob ich HiRes für einen oder für beide Timer 
einschalte. Anbei ein Foto.

Evtl. muss ich mich mal direkt an Atmel wenden? Die Appnote macht, wie 
gesagt, das selbe wie ich. Vielleicht sollte ich das mal 
sicherheitshalber kompillieren und schauen, was passiert...

: Bearbeitet durch User
von Johannes N. (strangeman)


Angehängte Dateien:

Lesenswert?

Ich habe gerade nochmal das Beispiel aus der Appnote getestet. Es 
funktionierte auf Anhieb. Allerdings lief die CPU da auf 8MHz und die 
Timer auf 32MHz. Also habe ich meinen Clock-initialisierungs Code 
eingefügt. Es ging immer noch.

Jetzt habe ich allerding etwas eigenartiges bemerkt und würde mich 
freuen, wenn jemand es nachprüfen kann. Folgenden Code verwende ich:
1
// include file aus der Appnote AVR1311
2
#include "avr_compiler.h"
3
4
5
/* Prototyping of functions. */
6
void ConfigClockSystem( void );
7
8
int main( void )
9
{
10
11
  ConfigClockSystem();
12
13
  /* Enable output on PORTD. */
14
  PORTD.DIR = 0xFF;
15
16
  // enable all four outputs, single slope PWM
17
  TCD0.CTRLB = TC0_CCAEN_bm | TC0_CCBEN_bm | TC_WGMODE_SS_gc;
18
  // configure 10bit operation
19
  TCD0.PER = 1023;
20
  // configure highres option (128MHz base speed)
21
  HIRESD.CTRLA = HIRES_HREN_TC0_gc;
22
  
23
  // enable the timer
24
  TCD0.CTRLA = TC_CLKSEL_DIV1_gc;
25
26
  TCD0.CCABUF = 4; // hier verschiedene Werte einsetzen!!
27
  TCD0.CCBBUF = 5;
28
29
  do {
30
  } while (1);
31
}
32
33
34
void ConfigClockSystem( void )
35
{
36
  // configure external 16MHz crystal, low power mode on 32kHz external crystal,
37
  // 16k cycles startup time
38
  OSC.XOSCCTRL = OSC_FRQRANGE_12TO16_gc | OSC_X32KLPM_bm | OSC_XOSCSEL_XTAL_16KCLK_gc;
39
  
40
  // enable external clock, keep internal 2MHZ clock running (it is still used!)
41
  // all other clocks disabled
42
  OSC.CTRL = OSC_XOSCEN_bm | OSC_RC2MEN_bm;
43
  
44
  // PLL clock source: external clock; factor: 8 => 128MHz output
45
  OSC.PLLCTRL = (OSC_PLLSRC_XOSC_gc | (8 & OSC_PLLFAC_gm));
46
  
47
  // wait for external clock to be ready
48
  while (!(OSC.STATUS & OSC_XOSCRDY_bm));
49
  
50
  // enable PLL
51
  OSC.CTRL |= OSC_PLLEN_bm;
52
  
53
  // wait for pll to be ready
54
  while (!(OSC.STATUS & OSC_PLLRDY_bm));
55
  
56
  // protect during prescaler change
57
  CCP = CCP_IOREG_gc;
58
  // set Prescalers:
59
  // Prescaler A: factor 1 => clk_4 runs at 128MHz
60
  // Prescaler B: factor 1/2 => clk_2 runs at 64MHz
61
  // Prescaler C: factor 1/2 => cpu and periphery runs at 32MHz
62
  CLK.PSCTRL = CLK_PSADIV_1_gc | CLK_PSBCDIV_2_2_gc;
63
  
64
  // protect during clock source change
65
  CCP = CCP_IOREG_gc;
66
  // switch clock source to PLL
67
  CLK.CTRL = CLK_SCLKSEL_PLL_gc;
68
  
69
  // Turn off int. RC osc.
70
  OSC.CTRL &= ~OSC_RC2MEN_bm;
71
}

Nun setzt man in der main() am Ende verschiedene Werte ein und 
beobachtet die zwei Ausgänge am Oszi.

Variante 1 (siehe Bild 1)
1
TCD0.CCABUF = 4; // hier verschiedene Werte einsetzen!!
2
TCD0.CCBBUF = 0;
Alles okay.

Variante 2 (siehe Bild 2)
1
TCD0.CCABUF = 5; // hier verschiedene Werte einsetzen!!
2
TCD0.CCBBUF = 0;
Alles okay. HiRes offensichtlich aktiv.

Variante 3 (siehe Bild 3)
1
TCD0.CCABUF = 4; // hier verschiedene Werte einsetzen!!
2
TCD0.CCBBUF = 4;
Alles okay.

Variante 4 (siehe Bild 4)
1
TCD0.CCABUF = 5; // hier verschiedene Werte einsetzen!!
2
TCD0.CCBBUF = 5;
Alles okay. HiRes offensichtlich aktiv.

Variante 5 (siehe Bild 5)
1
TCD0.CCABUF = 4; // hier verschiedene Werte einsetzen!!
2
TCD0.CCBBUF = 5;
MOMENT! Was ist mit Channel 2 passiert? Der ist auch auf 4 runter 
gegangen!


Das riecht mir doch stark nach einen Bug.
Kann das jemand bestätigen?

PS: Das erklärt auch den (verdächtig in 4er Schritten quantisierten) 
Jitter in meiner vorherigen Messung: Ich habe Channel 2 gemessen (TCCB). 
Auf TCCA lief parallel eine Rampenfunktion, welche in jedem zyklus den 
Wert in Compare register um eins erhöht hat und damit alle Werte 
durch-scannt. Immer, wenn Channel 1 also die Werte 4,5,6,7 angenommen 
hat, ging channel 2 mit. Daher der Jitter. War das verständlich erklärt?

: Bearbeitet durch User
von Thomas F. (tomasf)


Angehängte Dateien:

Lesenswert?

Ich hab deinen Code mal laufen lassen.
xmegaA3, PORTF

Ich würde mal sagen, das funktioniert.

Edit: Du hast auf jeden Fall ein Problem mit der Signalqualität (anderer 
Code?, Port kaputt?) (vllt. liegt 'bessere' Darstellung am Oszi)

: Bearbeitet durch User
von Johannes N. (strangeman)


Lesenswert?

Danke dir für's Ausprobieren. Schon mal gut zu wissen, dass es 
prinzipiell funktioniert.
Ich werde nochmal den selben Code für andere Ports probieren. Vielleicht 
gehts ja aus irgendeinem komischen Grund nur bei PortD nicht.

Hmm. Problem mit der Signalqualität? Für mich sieht das eher nach einem 
hochauflösenderen Oszi aus. Meins kann 100MHz (analoge Bandbreite). Wie 
siehts bei deinem aus? Vielleicht bekommen wir deswegen andere 
Signalverläufe. Die Flankensteilheit spricht jedenfalls dafür.

von Thomas F. (tomasf)


Lesenswert?

> Hmm. Problem mit der Signalqualität? Für mich sieht das eher nach einem
> hochauflösenderen Oszi aus. Meins kann 100MHz (analoge Bandbreite).

Das wird wohl stimmen.

> Wie
> siehts bei deinem aus?

Abtastrate entspricht Hires-Frequenz, sodass man immerhin die 
Unterschiede noch sehen kann.

von Johannes N. (strangeman)


Lesenswert?

Ich habe noch ein wenig weiter untersucht.
Meines Wissens funktioniert die HiRes Extension so: Der Timer schaltet 
die Pins wie bei einer normalen PWM und zählt dabei in 4er Schritten 
aufwärts. Die HiRes Extension springt dann ein, wenn der Timer den Pin 
low schalten würde und verlängert die High-Phase noch ein wenig, sodass 
die feine Abstufung entsteht. Bei einem CC-Wert von z.B. 9 würde der 
Timer selbst bei Schritt 8 den Pin abschalten, die HiRes Extension hält 
den Pin dann aber noch einen weiteren Schritt high.

Soweit ich das überblicke, scheint die grobe Einstellung durch den Timer 
selbst zu funktionieren. Die feine Abstufung in 4er Schritten, die die 
HiRes Extension vornimmt, scheint aber für alle Kanäle synchronisiert zu 
sein - was ja nicht dem Sinn der Sache entspricht.
Grundsätzlich bedeutet das, dass Channel B die gleiche Anzahl an feinen 
HiRes Stufen erhält, wie Channel A.

Hier nochmal einige Beispiele:
1
+------+----------------------+----------------------+
2
|      | Eingestellter Wert   | Tatsächlicher Wert   |    
3
| Nr.  +----------+-----------+----------+-----------+
4
|      | Chan A   | Chan B    | Chan A   | Chan B    |
5
+------+----------+-----------+----------+-----------+
6
|  1   | 4        | 5         | 4        | 4         | 
7
|  2   | 5        | 5         | 5        | 5         | 
8
|  3   | 9        | 5         | 9        | 5         |
9
|  4   | 9        | 4         | 9        | 5         |
10
|  5   | 4        | 9         | 4        | 8         |
11
12
Bemerkungen:
13
1 - HiRes-Schritte von Chan B zu Chan A synchronisiert
14
2 - gleiche Anzahl an HiRes Schritten, daher kein Problem
15
3 - gleiche Anzahl an HiRes Schritten, daher kein Problem
16
4,5 - HiRes-Schritte von Chan B zu Chan A synchronisiert

Das Problem tritt bei mir an mehreren Ports auf.

EDIT: Bei dir auf einem A3 läuft es. Ich verwende den A3U. Vielleicht 
könnte nochmal jemand mit einem A3U meinen Code von oben testen?
EDIT2: Haha, das quality inquiry Formular auf atmel.com ist offline mit 
einen 404 error. Prima.

: Bearbeitet durch User
von Johannes N. (strangeman)


Lesenswert?

Mir ist gerade aufgefallen, dass ich im makefile die ganze Zeit den 
atxmega128a3 angegeben habe, aber den atxmega128a3_U_ verwende. Jetzt 
habe ich nach langem Suchen endlich Pakete der avr-libc gefunden, die 
den atxmega128a3_U_ beinhalten. Allerdings sagt dann avrdude, er kenne 
kein Bauteil mit diesem Namen. Ich verwende eine veraltete Version von 
avrdude (5.11) weil alle neueren Versionen meinen LUFA-basierten 
PDI-Programmer nicht mehr unterstützen. MANN. deadlock.

Bevor ich jetzt einen neuen Programmer kaufe, damit ich eine neuere 
Version von avrdude verwenden kann, wollte ich erstmal sichergehen, ob 
es überhaupt einen Unterschied macht, ob ich für die U version übersetze 
oder für die nicht-U version.

Die includefiles (iox128a3.h bzw. iox128a3u.h) scheinen jedenfalls kaum 
Unterschiede zu haben.

PS: habe jetzt die datei /etc/avrdude.conf manuell editiert und dem 
avrdude die U version beigebracht. Die Device ID ist bei beiden gleich, 
also musste einfach nur der Absatz des 128A3 kopiert werden. Jetzt 
konnte ich meine Firmware endlich für den 128a3_u_ übersetzen und auf 
den chip brennen. Es hat sich aber an meinem problem dadurch nichts 
geändert.

: Bearbeitet durch User
von Thomas F. (tomasf)


Lesenswert?

Benutzt du die aktuelle Atmel-Toolchain? (die Atmel Studio benutzt) 
Falls nicht, würde ich das damit noch mal testen. Optimierung -Os oder 
-O1.

Wenn man sich sicher ist, es ist ein Device-Bug, ist es dann dann oft 
doch "nur" ein Compilerbug.

: Bearbeitet durch User
von Johannes N. (strangeman)


Lesenswert?

Habe es eben nochmal mit der aktuellen toolchain des Atmel Studio 
probiert. Das Problem bleibt bestehen

von Ingo L. (corrtexx)


Lesenswert?

Dann scheint es sich wirklich um einen Bug zu handeln

von Johannes N. (strangeman)


Lesenswert?

Hi Ingo,

hättest du evtl die Möglichkeit, den Fehler zu reproduzieren?

von Johannes N. (strangeman)


Lesenswert?

Ich habe hier gerade den AtXmega128a1u verlötet und die HiRes-Extension 
funktioniert anstandslos. Es scheint also ein spezifisches Problem des 
xmega128a3u zu sein.

von W. Nickel (Gast)


Lesenswert?

@Johannes: vielen Dank für die Erklärung mit dem Rest, den die HIRES 
übernimmt.

Ich habe den ATXMega32A4U und beobachte das gleiche Problem: die 
HIRES-Extension funktioniert nur auf Kanal A. Ich benutze Kanal A und 
Kanal B des Timers TC0 für eine H-Brücke, wobei Kanal A die Linke Hälfte 
(positive Welle) und Kanal B die rechte Hälfte (negative Welle) 
übernehmen (jeweils immer nur ein Kanal an).

Folgender einfacher code (wird alle Paar ms aufgerufen):
1
if(vset < vact){
2
  if(TCC0_CCB){
3
    TCC0_CCBBUF--;
4
  } else {
5
    if(TCC0_CCA < MAX_VREG_TERM) TCC0_CCABUF++;
6
  }
7
} else if(vset > vact){
8
  if(TCC0_CCA){
9
    TCC0_CCABUF--;
10
  } else {
11
    if(TCC0_CCB < MAX_VREG_TERM){
12
      TCC0_CCBBUF++;
13
    }
14
  }
15
}
Auf dem Scope ist die Linke Welle glatt und geschmeidig und die rechte 
gestuft.

Dank deiner Erklärung habe ich für meine Anwendung einen Workaround 
gebastelt: ich schreibe in Kanal A die unteren zwei Bits des Kanals B, 
wenn Kanal B benutzt wird (dann ist in meinem Fall Kanal A ungenutzt und 
eh immer 0).
1
if(vset < vact){
2
  if(TCC0_CCB){
3
    TCC0_CCBBUF--;
4
    TCC0_CCABUF = TCC0_CCBBUF & 0x0003; // HiRes workaround
5
  } else {
6
    if(TCC0_CCA < MAX_VREG_TERM) TCC0_CCABUF++;
7
  }
8
} else if(vset > vact){
9
  if(TCC0_CCA){
10
    TCC0_CCABUF--;
11
  } else {
12
    if(TCC0_CCB < MAX_VREG_TERM){
13
      TCC0_CCBBUF++;
14
      TCC0_CCABUF = TCC0_CCBBUF & 0x0003; // HiRes workaround
15
    }
16
  }
17
}
Nun sind beide Wellen hoch aufgelöst. Die zwei unteren Bits auf Kanal A 
stören übrigens nicht, weil die HiRes erst ab 4 funktioniert.

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.