Forum: Mikrocontroller und Digitale Elektronik PIC32MX350 läuft zu langsam


von Harstad (Gast)


Lesenswert?

Hi,

meine PIC32-Applikation läuft deutlich langsamer als erwartet, selbst 
wenn ich -O2 aktiviere, erreiche ich nicht die 
Ausführungsgeschwindigkeit, die bei 120 MHz zu erwarten wäre.

Google spuckt mir jetzt zwei Funktionen

SYSTEMConfig(120000000, SYS_CFG_ALL) und 
SYSTEMConfigPerformance(120000000) aus, welche sämtliche Peripherie 
(inklusive RAM) ordnungsgemäß und zur Taktfrequenz passend 
initialisieren sollten. Allerdings: SYSTEMConfigPerformance() benötigt 
irgend eine veraltete externe Bibliothek, SYSTEMConfig() ist noch nicht 
mal mehr in irgend welchen Headerfiles zu finden.

Deswegen meine Frage: Wie wäre der korrekte und aktuelle Weg, den PIC 
vernünftig zu initialisieren, so dass er entsprechend seiner 
Möglichkeiten ausreichend schnell läuft?

Danke!

von TR.OLL (Gast)


Lesenswert?

Schnellere CPU

von Dirk F (Gast)


Lesenswert?

Hi,
ich verwende die PLIB von Microchip.
Ist eigentlich veraltet, ich komme mit dem Harmony aber nicht 
zurecht....

bei mir sieht die Initialisierung dann so aus:
1
// Enable multi-vectored interrupts
2
INTEnableSystemMultiVectoredInt();          SYSTEMConfigPerformance
3
4
// Enable optimal performance
5
(GetSystemClock());  
6
7
mOSCSetPBDIV(OSC_PB_DIV_1);                 // Use 1:1 CPU Core:Peripheral clocks
8
DelayMs(50);                                // wait 50ms
9
DDPCONbits.JTAGEN = 0;                      // Disable JTAG port so we get our I/O pins back
10
DDPCONbits.TROEN = 0;                       // Disable TRACE port

von Dirk F (Gast)


Lesenswert?

sorry, so:
1
INTEnableSystemMultiVectoredInt();          // Enable multi-vectored interrupts
2
SYSTEMConfigPerformance(GetSystemClock());  // Enable optimal performance
3
mOSCSetPBDIV(OSC_PB_DIV_1);                 // Use 1:1 CPU Core:Peripheral clocks
4
DelayMs(50);                                // wait 50ms
5
DDPCONbits.JTAGEN = 0;                      // Disable JTAG port so we get our I/O pins back
6
DDPCONbits.TROEN = 0;                       // Disable TRACE port

von Nick M. (Gast)


Lesenswert?

Harstad schrieb:
> Wie wäre der korrekte und aktuelle Weg, den PIC
> vernünftig zu initialisieren, so dass er entsprechend seiner
> Möglichkeiten ausreichend schnell läuft?

Hmmm. Verwendest du Harmony?
Dann kannst du doch in der MPLAB X IDE den Takt komfortabel 
konfigurieren, mitsamt den Peripherietakten.

Und wie kommst du zu dem Schluss, dass er zu langsam läuft? Kommt bei 
USART irnkwie eine zu niedrige Baudrate raus?

von neuer PIC Freund (Gast)


Lesenswert?

Einfach in MPLABX zusammenklicken. Sieht dann so ähnlich aus wie
1
// DEVCFG2
2
#pragma config FPLLIDIV = DIV_2         // PLL Input Divider (2x Divider)
3
#pragma config FPLLMUL = MUL_24         // PLL Multiplier (24x Multiplier)
4
#pragma config FPLLODIV = DIV_2         // System PLL Output Clock Divider (PLL Divide by 2)
5
6
// DEVCFG1
7
#pragma config FNOSC = FRCPLL           // Oscillator Selection Bits (Fast RC Osc with PLL)
8
9
...
10
11
#include <xc.h>


Alternativ zur Laufzeit selbst über NOSC die Standardkonfiguration 
modifizieren.

von jemand (Gast)


Lesenswert?

Ohne diese Funktionsaufrufe mit den veralteten Libs kommt der PIC32 mit 
Defaults hoch. Also maximum waitstates auf SRAM und Flash, als Beispiel. 
Wenn er einen Cache hat, wird dieser nicht initialisiert.
Was waitstates auf SRAM heißt: Wenn 4 waitstates eingestellt sind, 
benötigt ein SRAM-Zugriff 4 Clockcycles.
Waitstates sind für alle schnelleren CPUs nötig, weil Flash/SRAM keine 
120MHz können. Dafür hat deine CPU einen Cache (der deaktiviert ist...) 
und Prefetching (das per Default ebenfalls deaktiviert ist).

Dadurch ist die CPU natürlich viel zu langsam. Ich habe Faktor 7 
Leistungssteigerung zwischen nicht initialisierter und initialisierter 
CPU gehabt.

Die Lösung lautet:
Entweder du schaust in der veralteten Lib nach, was die Funktionen genau 
tun (keine Angst, viel ist es beim PIC32MX noch nicht).

Oder du schaust im Datenblatt nach, wie du Waitstates und dergleichen 
initialisierst. Dazu gibt es ein eigenes Datenblatt für den Core.

Oder du benutzt Harmony (die neue Lib).

Ich habe Variante 2 benutzt.

Das kann so aussehen:
1
#include <xc.h>
2
#include <stdint.h>
3
#include <sys/attribs.h>
4
#include <stdbool.h>
5
#include <proc/p32mx370f512h.h>  //<-- Das ist für dich anders!
6
7
/*set cache, flash and RAM waitstates*/
8
void setupCore(uint32_t sysclk){
9
    //enable instruction and data caching
10
    CHECONbits.DCSZ = 0b11;     //enable data caching with 4 lines
11
    //enable instruction prefetch
12
    CHECONbits.PREFEN = 0b11;   //enable prefech for chachable and non cachable regions
13
    //set SRAM / Flash waitstates
14
    if(sysclk <= 40000000)          CHECONbits.PFMWS = 0;     //zero waitstates for f<40MHz
15
    else if(sysclk <= 80000000)     CHECONbits.PFMWS = 2;     //unclear: 1 waitstates throws exception???
16
    else if(sysclk <= 100000000)    CHECONbits.PFMWS = 2; 
17
    else                            CHECONbits.PFMWS = 3;
18
    //set bus wait states
19
    BMXCONbits.BMXWSDRM = 0;
20
}
Weil das meinem Verständniss des Datenblattes entspricht, muss das nicht 
richtig sein.
Viel bingt der Cache. Viel mehr als eine Reduktion der waitstates.

Bau bei der Gelegenheit gleich Exception-Handler ein. Sehr praktisch, 
diese Exceptions.

Viel Erfolg :-)

von Harstad (Gast)


Lesenswert?

Nick M. schrieb:
> Hmmm. Verwendest du Harmony?

Nein, bei Harmony scheitere ich schon daran, ein neues Harmony-Projekt 
anzulegen: er akzeptiert den Framework-Path einfach nicht, obwohl ich 
über den Framework-Downloader alles dort hinein heruntergeladen habe, 
was relevant sein sollte.

von Nick M. (Gast)


Lesenswert?

Harstad schrieb:
> Nein, bei Harmony scheitere ich schon daran, ein neues Harmony-Projekt
> anzulegen:

OK, kenn mich nur mit Harmony aus. Die Probleme hab ich aber nicht 
(verwende aber auch nicht die 3.x-Version.
Ich hab aber mitbekommen, dass paar Leute damit scheitern. Hier gibt es 
bestimmt eine Lösung dafür: https://www.microchip.com/forums/f291.aspx

Allerdings muss man bei der 3.x aufpassen, ob der Prozessor (schon) 
unterstützt wird, lies also genau nach!

von Harstad (Gast)


Lesenswert?

OK, danke für die Antworten, das hat mir weitergeholfen!

von Harstad (Gast)


Angehängte Dateien:

Lesenswert?

OK, trotz aller Optimierungen bekomme ich nicht die Performance, die ich 
benötige. Für eine windige Schleife in der nur zwei Zähler hochgezählt 
und je drei Bytes über SPI1 und SPI2 rausgeschoben werden, geht jede 
Menge Zeit drauf, wo ich nur ein paar hundert Taktzyklen erwarten würde. 
Für 2^20 Durchläufe benötigt er eine halbe Minute.

Ich habe das Gefühl, dass der PIC nicht auf den 120 MHz läuft, die ich 
eigentlich meine eingestellt zu haben (siehe Screenshot des MCC).

Kann es sein, dass da mit meine Clock-Konfiguration was nicht stimmt?

Danke!

von Nick M. (Gast)


Lesenswert?

Harstad schrieb:
> Ich habe das Gefühl, dass der PIC nicht auf den 120 MHz läuft,

Dann lass doch mal einen USART Pin wackeln und miss das mit dem Oszi.

von jemand (Gast)


Lesenswert?

Harstad schrieb:
> OK, trotz aller Optimierungen bekomme ich nicht die Performance, die ich
> benötige. Für eine windige Schleife in der nur zwei Zähler hochgezählt
> und je drei Bytes über SPI1 und SPI2 rausgeschoben werden, geht jede
> Menge Zeit drauf, wo ich nur ein paar hundert Taktzyklen erwarten würde.
> Für 2^20 Durchläufe benötigt er eine halbe Minute.
>
> Ich habe das Gefühl, dass der PIC nicht auf den 120 MHz läuft, die ich
> eigentlich meine eingestellt zu haben (siehe Screenshot des MCC).
>
> Kann es sein, dass da mit meine Clock-Konfiguration was nicht stimmt?
>
> Danke!

Ich kann dir aus einem Projekt mit PIC32MX370 versichern, dass PIC32MX 
zumindest 80MHz die angepriesene Performance bringen. Ich decodiere 
damit mp3s in Software, was problemlos nebenher geht.

Wenn du Performance testen willst, solltest du dir die 
"Stopwatch-Funktion" von MPLABX ansehen. Damit kannst du die 
Durchlaufzeit in Taktzyklen zwischen zwei Breakpoints messen, auch mit 
dem PICkit. Das reicht schon mal für eine Plausibilitätsprüfung, und um 
Schwachstellen zu identifizieren.

Außerdem kannst du den Takt auf einen Timer geben, und zu einem Pin 
einen um ein definiertes Verhältnis heruntergeteilten Takt ausgeben, und 
mit dem Oszilloskop oder sogar einer Stoppuhr messen.

Vergiss nicht die Compilersettings! Wenn die Optimierungen ausgeschaltet 
sind, produziert der Compiler sehr, sehr langsamen Code. Schon Stufe 1 
bringt eine Menge.

von Harstad (Gast)


Lesenswert?

Ich compiliere mit -O2, die Peripherals sind per 
SYSTEMConfigPerformance(120000000) auf 120 MHz konfiguriert und die 
relevanten Funktionen sind alle mit _ramfunc_ in den RAM verschoben, 
an der Stelle sehe ich also keine Probleme.

Was ich halt nicht verstehe, sind die komischen Clock-Settings und was 
da ganz oben die 8 MHz zu sagen haben - wenn das der Takt für die 
Programmcode ist und die 120 MHz nur auf irgend welche Peripherie 
wirken, kann es natürlich nicht schnell sein...

von Chris B. (dekatz)


Lesenswert?

Die Verwendung des FRC (8MHz) und einem PLL Input Divider von 1 
widerspricht der Spezifikatione des MX350 Oscillators/PLL (siehe DB 
Figur 4.1).

Die Eingangsfrequenz der PLL muss zwischen 4 und 5 MHz liegen, also muss 
der PLL Input Divider auf 2 gestellt werden (PLL-Input = 4MHz), der 
Multiplier auf 24 (PLL-Output 96MHz) und der OutputDivider bleibt auf 1. 
Dann läuft der MX350 mit 96MHz.

Was bei 8MHz PLL Input passiert?? keine Ahnung, vielleicht werden 
einfach die 8MHz "durchgereicht".....

von Harstad (Gast)


Lesenswert?

Chris B. schrieb:
> Die Eingangsfrequenz der PLL muss zwischen 4 und 5 MHz liegen, also muss
> der PLL Input Divider auf 2 gestellt werden (PLL-Input = 4MHz), der
> Multiplier auf 24 (PLL-Output 96MHz) und der OutputDivider bleibt auf 1.
> Dann läuft der MX350 mit 96MHz.

OK, wenn ich das so einstelle, zeigt er mir einen PBCLK von 96 MHz an. 
Der wird mit einem Teiler von 6 als SPI-Clock verwendet, trotzdem 
erhalte ich auf SCLK nur eine Frequenz von ca. 3,8 MHz. Heißt das nicht, 
dass ich gar keine 96 MHz habe, sondern nur auf ca. 22.8 laufe?

Und wie bekomme ich den PIC auf die Maximalfrequenz von 120 MHz - geht 
das nur mit externem Clock?

von Chris B. (dekatz)


Angehängte Dateien:

Lesenswert?

Habe das mit einem PIC32MX340F512H gestestet. Bei einem SysClock von 
96MHz und einem Teiler von 16 in der SPI Konfiguration messe ich einen 
SPI-Clock von 3MHz (genau 2,85MHz).

Nachtrag:
Im DB findet sich unter 17.2.5 die Formel zur Berechnung von F_SCK 
(SPI-Clock):
F_SCK = F_PB / (2 * (SPIxBRG + 1) ) ergibt bei 96MHz SPI1BRG = 16:

96 / (2 * (16 + 1) ) = 2,82....MHz

Passt also so ziemlich zur Messung!

: Bearbeitet durch User
von Harstad (Gast)


Lesenswert?

OK, dann passt das so weit - was mich aber zu meinem ursprünglichen 
Problem zurück bringt: wieso läuft mein Code so langsam? Ich habe mal 
nachgemessen, er benötigt für ein paar Zähler und um den SPI-FIFO zu 
füllen tatsächlich 0,5 msec (das wären also 48000 Taktzyklen wenn der 
Code mit 96 MHz läuft).

Ich mache eigentlich nichts dramatisches, eine Schleife, um zwei Zähler 
hochzuzählen (sieht hier etwas kompliziert aus, da es für die 
eigentliche Funktion vorbereitet ist):
1
struct raw_data
2
{
3
   union
4
   {
5
      unsigned char bData[4];
6
      unsigned int iData;
7
   } d;
8
};
9
10
static struct raw_data rawX[2],rawY[2];
11
static bufferOK[2]={0,0};
12
13
void __longramfunc__ main_loop(void)
14
{
15
   uint_fast32_t cntX=0,cntY=0;
16
   uint_fast8_t shift;
17
18
   for (;;)
19
   {
20
      cntX+=1;
21
      cntY+=2;
22
      bufferOK[1-buffer]=1;
23
      rawX[1-buffer].d.iData=cntX;
24
      rawY[1-buffer].d.iData=cntY;
25
      useBuffer=1-buffer;
26
      if (bufferOK[useBuffer])
27
      {
28
         DAC_LDAC_SetLow();
29
         DAC_CS_SetLow();
30
         bufferOK[useBuffer]=0;
31
         shift=33-bitCnt[useBuffer];
32
         rawX[useBuffer].d.iData=rawX[useBuffer].d.iData<<shift; // shift up to 24 bit data length and align at top
33
         rawY[useBuffer].d.iData=rawY[useBuffer].d.iData<<shift; // shift up to 24 bit data length and align at top
34
         // spi transmission stuff here...
35
         SPI_ExchangeBuffer(&rawX[useBuffer].d.bData[0],&rawY[useBuffer].d.bData[0]);
36
         DAC_CS_SetHigh();
37
         DAC_LDAC_SetHigh();
38
      }
39
   }
40
}

...und die Funktion, um SPI1 und SPI2 mit Daten zu füttern:
1
void __ramfunc__ SPI_ExchangeBuffer(uint8_t *pSendX, uint8_t *pSendY)
2
{
3
   SPI1BUF=pSendX[2];
4
   SPI2BUF=pSendY[2];
5
   SPI1BUF=pSendX[1];
6
   SPI2BUF=pSendY[1];
7
   SPI1BUF=pSendX[0];
8
   SPI2BUF=pSendY[0];
9
10
   //while (SPI1STATbits.SPITBE==0); assumption: when SPI2 is empty, SPI1 must be empty too as it started sending slightly earlier
11
   while (SPI2STATbits.SPITBE==0);
12
   //while (SPI1STATbits.SPITBF!=0); assumption: when SPI2 is empty, SPI1 must be empty too as it started sending slightly earlier
13
   while (SPI2STATbits.SPITBF!=0);
14
   // leave only when transmit buffer is empty and all data have been sent 
15
}

Optmiierung mit -O2 bringt keinen wesentlichen Gewinn, die Fuktionen 
liegen alle schon mit _ramfunc_ im RAM - also wo könnte ich die 
Geschwindigkeit verlieren?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Harstad schrieb:
> also wo könnte ich die Geschwindigkeit verlieren?
Hast du dir mal den erzeugten Assemblercode angeschaut? Und von wo bis 
wo braucht dein uC nun die halbe ms?  Ist da das Warten auf den SPI 
Transfer auch mit dabei?

> // shift up to 24 bit data length
Hat dein uC einen Barrelshifter? Oder nimmt der jedes Bit einzeln in die 
Hand?

: Bearbeitet durch Moderator
von neuer PIC Freund (Gast)


Lesenswert?

Mit SPITBE wartest du, bis das FIFO leergelaufen ist. SPITBF ist 
befremdlich, denn ein leeres FIFO ist jederzeit nicht voll. Für das 
letzte Byte musst du zusätzlich das Schieberegister pollen (SRMT).

Die Geschwindigkeit hängt davon ab, was in SPIxBRG steht. Hast du hier 
vielleicht etwas unpassendes eingetragen? Oder reduzierst du den Takt 
über REFCLK?

von Hermann U. (Firma: www.pcb-devboards.de) (gera82)


Lesenswert?

Harstad schrieb:
> void _ramfunc_ SPI_ExchangeBuffer(uint8_t *pSendX, uint8_t *pSendY)
> {
>    SPI1BUF=pSendX[2];
>    SPI2BUF=pSendY[2];
>    SPI1BUF=pSendX[1];
>    SPI2BUF=pSendY[1];
>    SPI1BUF=pSendX[0];
>    SPI2BUF=pSendY[0];
>
>    //while (SPI1STATbits.SPITBE==0); assumption: when SPI2 is empty,
> SPI1 must be empty too as it started sending slightly earlier
>    while (SPI2STATbits.SPITBE==0);
>    //while (SPI1STATbits.SPITBF!=0); assumption: when SPI2 is empty,
> SPI1 must be empty too as it started sending slightly earlier
>    while (SPI2STATbits.SPITBF!=0);
>    // leave only when transmit buffer is empty and all data have been
> sent
> }

das geht doch gar nicht, zeig mal deine SPI Konfiguration, du muss 
mindesten
1
SPI2CONbits.ENHBUF = 1; // 1 = Enhanced Buffer mode is enabled


einschalten. Erst dann kannst du SPIxBUF bis zu 128bit auf einmal laden.

hier mal Codeschnipsel (PIC32MX470):
1
void sendData(char addr, char dat5, char dat4, char dat3, char dat2, char dat1, char dat0)
2
{
3
     // create an ongoing XOR sum of all bytes sent
4
     char crc=0, start=84, stop=204;
5
6
     QF_INT_LOCK();
7
     
8
9
     crc^=addr;
10
     crc^=dat5;
11
     crc^=dat4;
12
     crc^=dat3;
13
     crc^=dat2;
14
     crc^=dat1;
15
     crc^=dat0;
16
17
     SPI2BUF=start;
18
     SPI2BUF=addr;
19
     SPI2BUF=dat5;
20
     SPI2BUF=dat4;
21
     SPI2BUF=dat3;
22
     SPI2BUF=dat2;
23
     SPI2BUF=dat1;
24
     SPI2BUF=dat0;
25
     SPI2BUF=crc;
26
     SPI2BUF=stop;
27
28
     QF_INT_UNLOCK();
29
30
     //SRMT:
31
     // Shift Register Empty bit (valid only when ENHBUF = 1)
32
     // 1 = When SPI module shift register is empty
33
     // 0= When SPI module shift register is not empty
34
     while (!SPI2STATbits.SRMT);
35
36
37
}
38
39
40
41
void Init_SPI2(void)
42
{
43
// SPI2CON Register Settings
44
SPI2CONbits.DISSDO = 0; // SDOx pin is controlled by the module
45
SPI2CONbits.DISSDI = 1; // 1 = SDIx pin is not used by the SPI module (pin is controlled by PORT function
46
SPI2CONbits.MODE16 = 0; // Communication 8bit
47
SPI2CONbits.MODE32 = 0; // Communication 8bit
48
SPI2CONbits.SMP = 1; // Input data is sampled at the middle of data output time
49
SPI2CONbits.CKE = 0; // Serial output data changes on transition from Idle clock state to active clock state
50
SPI2CONbits.CKP = 1; // Idle state for clock is a low-level;active state is a high-level
51
SPI2CONbits.SSEN = 0; //0 = SSx pin not used for Slave mode, pin controlled by port function.
52
SPI2CONbits.MSTEN = 1; // Master mode enabled
53
SPI2CONbits.ON = 1; // Enable SPI module
54
SPI2CONbits.ENHBUF = 1; // 1 = Enhanced Buffer mode is enabled
55
56
57
SPI2BRG = 23;  //2.5Mhz
58
//SPI2BRG = 14;  //4Mhz
59
//SPI2BRG = 5;  //10Mhz
60
61
    IFS1bits.SPI2TXIF = 0;              //Clear interrupt flag (no interrupt)
62
//    IEC1bits.SPI2TXIE = 1;              //Enable SPI2 interrupts
63
}

von Harstad (Gast)


Lesenswert?

Hermann U. schrieb:

> das geht doch gar nicht, zeig mal deine SPI Konfiguration, du muss
> mindesten
> SPI2CONbits.ENHBUF = 1; // 1 = Enhanced Buffer mode is enabled

Das ist gesetzt, das Senden der Daten ist ja auch nicht das Problem.

>      //SRMT:
>      // Shift Register Empty bit (valid only when ENHBUF = 1)
>      // 1 = When SPI module shift register is empty
>      // 0= When SPI module shift register is not empty
>      while (!SPI2STATbits.SRMT);

OK, Danke, das probiere ich mal - ich gehe aber davon aus, dass das 
momentan (zufällig?) trotzdem funktioniert hat. Ich sehe am Oszi ja die 
kurzen, schnellem SCLK-Pulse und zwischendrin die viel zu lange Zeit, 
die er benötigt, um meine paar Variablen zu verschieben...

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Harstad schrieb:
> Ich sehe am Oszi ja die kurzen, schnellem SCLK-Pulse und zwischendrin
> die viel zu lange Zeit, die er benötigt, um meine paar Variablen zu
> verschieben...
Wenn du shcon ein Oszi hast, dann setze einfach mal an beliebigen 
Stellen im Code einen Portpin und irgendwann später wieder zurück. Daran 
kannst du dann leicht herausfinden, wo die Zeit verplempert wird.

Zusammen mit dem vom Compiler erzeugten Assemblerlisting lässt sich der 
"Rechenzeitfresser" dann schnell aufspüren.

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.