Forum: Mikrocontroller und Digitale Elektronik PIC24 @ 80MHz und Timer1 zu langsam?


von Matthias B. (pegasi)


Lesenswert?

Hallo,
ich habe einen PIC24HJ128GP306. Mit einer internen PLL kann ich dessen 
Takt auf 80MHz erhöhen. Dies entspricht 40MIPS. Lasse ich folgenden code 
laufen bekomme ich eine Ausgangsfrequenz von mehr als 1 MHz:
1
while(true) {
2
    output_high(PIN_C2);
3
    delay_us(0.2);                    //delay of 0.2 us
4
    output_low(PIN_C2);
5
    delay_us(0.2);                    //delay of 0.2 us
6
}

Steuere ich meine Ausgabe hingegen mit einem Timer1-Interrupt, der 
direkt mit dem Systemtakt gekoppelt ist und einen Vorteiler von 1 
besitzt, so erreiche ich maximal eine Frequenz von 388kHz. Code:
1
void timer1_ISR(void) {
2
    static int a = 0;
3
    if (a == 0 && schalter_an == 1) {
4
         output_high(PIN_C2);
5
         a = 1;
6
    }
7
    else {
8
         output_low(PIN_C2);
9
    a = 0;
10
    }
11
}

schalter_an ist immer "1" wenn der Interrupt arbeitet.
Die Einträge in den relevanten Registern sagen mir es ist alles in 
Ordnung.
Warum ist der Timer1 also so langsam?

Register:
T1CON-Register is: 0xA000
PR1-Register is 0x0001
OSCCON: 0x3320
CLKDIV: 0x0000
PLLFBD: 0x0012

von Benedikt K. (benedikt)


Lesenswert?

Matthias B. wrote:

> Warum ist der Timer1 also so langsam?

Schau dir mal den erzeugten Assemblercode an, und schau im Datenblatt 
nach, wie groß die Interruptlatenzzeit ist, dann sollte es schnell klar 
sein, wieso es nicht schneller geht. Bzw. lass die Software mal im 
Simulator laufen und messe mit der Stopwatch nach, wie lange ein 
Durchlauf durch den Code dauert.

von Matthias B. (pegasi)


Lesenswert?

Das ist mein Assemblercode zum Interrupt. Es sind ca. 30 Befehle.

.................... #int_Timer1 level=2
.................... void timer1_ISR(void) {
*
00200:  PUSH    42
00202:  PUSH    36
00204:  MOV     W0,[W15++]
00206:  MOV     #2,W0
00208:  REPEAT  #C
0020A:  MOV     [W0++],[W15++]
....................    static int a = 0;
*
00320:  MOV     #0,W4
00322:  MOV     W4,3F38
....................    if (a == 0 && schalter_an == 1) {
*
0020C:  MOV     3F38,W0
0020E:  CP0     W0
00210:  BRA     NZ,224
00212:  MOV     3F36,W0
00214:  CP      W0,#1
00216:  BRA     NZ,224
....................       output_high(PIN_C2);
00218:  BCLR.B  2CC.2
0021A:  BSET.B  2D0.2
....................       a = 1;
0021C:  MOV     #1,W4
0021E:  MOV     W4,3F38
....................    }
....................    else {
00220:  GOTO    22C
....................       output_low(PIN_C2);
00224:  BCLR.B  2CC.2
00226:  BCLR.B  2D0.2
....................       a = 0;
00228:  MOV     #0,W4
0022A:  MOV     W4,3F38
....................    }
.................... }
....................
0022C:  BCLR.B  84.3
0022E:  MOV     #1A,W0
00230:  REPEAT  #C
00232:  MOV     [--W15],[W0--]
00234:  MOV     [--W15],W0
00236:  POP     36
00238:  POP     42
0023A:  RETFIE

von Benedikt K. (benedikt)


Lesenswert?

Matthias B. wrote:
> Das ist mein Assemblercode zum Interrupt. Es sind ca. 30 Befehle.
> 00208:  REPEAT  #C
> 0020A:  MOV     [W0++],[W15++]

Durch das Repeat wird der folgende Befehl 13x ausgeführt, macht also 13 
zusätzliche Takte. Am Ende des Interrupts das ganze nochmal.
Macht also rund 55 Takte inkl. Interrupteintritt und Verlassen. 
40MHz/55/2=363kHz. Kommt also in etwa hin.
Der Assemblercode ist ziemlich ineffizient, sind die Optimierungen an?

von Chris S. (schris)


Lesenswert?

Timer1 dürfte ein 16bit Timer sein, also bekommst du einen Interrupt 
alle
256*256 Instruktionszyklen. Timer0 könnte ein 8bit timer sein.
Du könntest aber auch den Timer auf einen Ausgangswert setzten, und
dann im Interrupt als erstes denselben Timer wieder auf denselben 
Ausgangswert setzen. Mußt halt die Latenzzeit, sowie die Uptime-Zeit des 
Timers beachten.

von (prx) A. K. (prx)


Lesenswert?

Matthias B. wrote:

> ....................    static int a = 0;
> *
> 00320:  MOV     #0,W4
> 00322:  MOV     W4,3F38

Dieser Teil sieht sehr merkwürdig aus. Die Initialisierung einer 
"static" Variablen direkt im Code???

von (prx) A. K. (prx)


Lesenswert?

Chris S. wrote:

> Timer1 dürfte ein 16bit Timer sein, also bekommst du einen Interrupt
> alle 256*256 Instruktionszyklen.

Nicht mit automatischem Reload, und ohne das jetzt genau analysiert zu 
haben dürfte genau das hier der Fall sein.

> Du könntest aber auch den Timer auf einen Ausgangswert setzten, und
> dann im Interrupt als erstes denselben Timer wieder auf denselben
> Ausgangswert setzen.

Nixda! Das wäre ein exzellenter Weg um ein exakt wiederholtes 
Zeitverhalten möglichst zu vermeiden. Dafür verwendet man Hardware-Modi, 
wie immer die bei jeweiligen Controller nun heissen mögen (CTC-Mode bei 
AVR).

von Matthias B. (pegasi)


Lesenswert?

A. K. wrote:
> Matthias B. wrote:
>
>> ....................    static int a = 0;
>> *
>> 00320:  MOV     #0,W4
>> 00322:  MOV     W4,3F38
>
> Dieser Teil sieht sehr merkwürdig aus. Die Initialisierung einer
> "static" Variablen direkt im Code???

"static" ist dazu da, um bei jedem Interrupt den entsprechenden 
folgenden Zustand auszuwählen. Der Wert bleibt über den Interrupt in der 
Funktion erhalten. Habe ich kein "static" drinnen, gibts nur nen high 
output und keinen Takt.

Benedikt K. wrote:
> Durch das Repeat wird der folgende Befehl 13x ausgeführt, macht also 13
> zusätzliche Takte. Am Ende des Interrupts das ganze nochmal.
> Macht also rund 55 Takte inkl. Interrupteintritt und Verlassen.
> 40MHz/55/2=363kHz. Kommt also in etwa hin.
> Der Assemblercode ist ziemlich ineffizient, sind die Optimierungen an?

Danke für die Info. Bin in Assembler nicht so fit. Leider habe ich auch 
keine Ahnung, wo ich im Compiler Optimierungen ein/aus-schalten kann. 
Ich benutze den CCS C PCD in Verbindung mit MPLAB.

Unten mal meine Messungen mit verschiedenen Zählereinstellungen. 
Seltsam, dass sich am Anfang (bei kleinem Zähler) nichts an der Frequenz 
ändert.
Erst ab ca. 200kHz wird das Signal dann linear so wie es sein sollte.

counter -- frequenz in kHz

1 --  377

2 --  377

3 --  377

5 --  377

10  --  377

20  --  377

30  --  322

40  --  325

50  --  356

60  --  328

70  --  281

80  --  247

90  --  220

100  --  198

150  --  132

200  --  99

300  --  66

400  --  50

500  --  40

600  --  33.3

700  --  28.5

1000  --  20

2000  --  10

3000  --  6.66

4000  --  5

5000  --  4

10000  --  2

15000  --  1.33

20000  --  1.00

von Benedikt K. (benedikt)


Lesenswert?

Matthias B. wrote:

> Danke für die Info. Bin in Assembler nicht so fit. Leider habe ich auch
> keine Ahnung, wo ich im Compiler Optimierungen ein/aus-schalten kann.
> Ich benutze den CCS C PCD in Verbindung mit MPLAB.

Ich kenne nur den C30 Compiler, da geht das über Project -> Buid Options 
-> Project -> MPLAB C30 -> Optimization
Vielleicht ist das bei dem ja ähnlich.
Ansonsten würde ich auf den C30 umsteigen, 53 Takte für solch einen 
simplen Interrupt ist nämlich ziemlich ineffizient, das macht der C30 
auf jedenfall besser.

> Seltsam, dass sich am Anfang (bei kleinem Zähler) nichts an der Frequenz
> ändert.
> Erst ab ca. 200kHz wird das Signal dann linear so wie es sein sollte.

Das ist nicht seltsam, sondern genau so habe ich das erwartet: Der 
Interruptdurchlauf benötigt etwa 53 Takte. Wenn er also häufiger 
aufgerufen wird, gehen Interrupts verloren.

von Matthias B. (pegasi)


Lesenswert?

edit:
Hab die Option für Code-Optimierung gefunden. Ist schon auf Maximum.

von Benedikt K. (benedikt)


Lesenswert?

OK, dann bleibt nur ein Compilerwechsel oder den Code in Assembler zu 
packen übrig.
Der C30 spuckt übrigends das aus aus:
1
113:               void __attribute__((__interrupt__)) _T1Interrupt(void) {
2
  04B8  781F80     mov.w 0x0000,[0x001e++]
3
  04BA  F80034     push.w 0x0034
4
  04BC  B3C000     mov.b #0x0,0x0000
5
  04BE  8801A0     mov.w 0x0000,0x0034
6
114:                   static int a = 0;
7
115:                   if (a == 0 && schalter_an == 1) {
8
  04C0  E20800     cp0.w 0x0800
9
  04C2  3A0003     bra nz, 0x0004ca
10
  04C4  BFC802     mov.b 0x0802,0x0000
11
  04C6  504FE1     sub.b 0x0000,#1,[0x001e]
12
  04C8  320005     bra z, 0x0004d4
13
116:                        _LATB0=1;
14
  04D4  A802CA     bset.b 0x02ca,#0
15
117:                        a = 1;
16
  04D6  200010     mov.w #0x1,0x0000
17
  04D8  884000     mov.w 0x0000,0x0800
18
118:                   }
19
119:                   else {
20
120:                        _LATB0=0;
21
  04CA  A902CA     bclr.b 0x02ca,#0
22
121:                   a = 0;
23
  04CC  EF2800     clr.w 0x0800
24
122:                   }
25
123:               }
26
  04CE  F90034     pop.w 0x0034
27
  04D0  78004F     mov.w [--0x001e],0x0000
28
  04D2  064000     retfie
29
  04DA  F90034     pop.w 0x0034
30
  04DC  78004F     mov.w [--0x001e],0x0000
31
  04DE  064000     retfie
Und das ist von den Takten her um mehr als die Hälfte kürzer.

von Chris S. (schris)


Lesenswert?

Werden die Takte beim Pic24 durch 2 und nicht durch 4 wie sonst bei
Microchip üblich geteilt ? Auch beim DsPic werden sie durch 4 geteilt,
und der pic24, ist der nicht eine abgespeckt Version vom DsPic ?

von Master S. (snowman)


Lesenswert?

für mich wäre das ein weiterer (betonung liegt auf "weiterer") grund, 
CSS nicht zu benutzen sondern die kostenlosen compiler C18/C30 vom 
hersteller selbst ;-) ..ist ja echt unglaublich, wie ineffizient CSS ist 
:-(

edit: bei den PIC24er musst du den takt /2 teilen: 80MHz -> 40MIPS

2. edit: @Benedikt K: es ist eher 1/3 als die hälfte

von Benedikt K. (benedikt)


Lesenswert?

Chris S. wrote:
> Auch beim DsPic werden sie durch 4 geteilt,

Jain: Die 30F teilen durch 4, die 33F nur durch 2.

Master Snowman wrote:
> ..ist ja echt unglaublich, wie CSS ineffizient ist

Ja, eindeutig. Einfach mal alle Register im Interrupt sichern ist 
ziemlicher Mist.
Abgesehen davon dass der C30 keine DSP Befehle verwendet, ist der Code 
ansonsten ziemlich nahe an dem was man in Assembler programmieren würde. 
Die Optimierung finde ich wirklich ziemlich gut.

von (prx) A. K. (prx)


Lesenswert?

Matthias B. wrote:

> "static" ist dazu da, um bei jedem Interrupt den entsprechenden
> folgenden Zustand auszuwählen. Der Wert bleibt über den Interrupt in der
> Funktion erhalten. Habe ich kein "static" drinnen, gibts nur nen high
> output und keinen Takt.

Schon klar.

Ich hatte die Adressen nicht beachtet. Dieser Code ist offenbar Teil 
einer Initialisierungs-Sektion. Was aber auch suggeriert, dass der 
Compiler in die Mülltonne gehört, denn sowas überlässt man dem Loader 
bzw. Startup-Code statt dafür eigens Code zu verschwenden.

Von der "Qualität" des übrigen Codes ganz abgesehen.

Der C30 ist übrigens einmal mehr eine Variante des GCC. Und ganz 
passabel.

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.