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
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.
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
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?
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.
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???
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).
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
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.
edit:
Hab die Option für Code-Optimierung gefunden. Ist schon auf Maximum.
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.
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 ?
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
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.
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.
|