RP2040 core1 nutzen

OP #7394864
Lesenswert?

Ich spiele gerade wieder mit dem RPi pico und habe ein kleines Testprogramm (PlattformIO, Arduino Core). Im setup() starte ich zum Schluss ein blinky auf dem core1 mit

1
uint32_t counter;
2

3
void core1_worker() {
4
  // DigitalOut  led(LED1);
5
  counter++;
6

7
  pinMode(LED_BUILTIN, OUTPUT);
8
  while(true) {
9
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
10
    sleep_ms(1000);                  // wait for a second
11
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
12
    sleep_ms(1000);
13

14
    counter++;
15
    dsTemp_1 += 1.0f;
16
  }
17
}
18

19
void setup() {
20
  Serial1.begin(115200);
21
  Serial1.println(F("Hello from Pico"));  
22

23
  sleep_ms(500);
24

25
  multicore_reset_core1();
26
  multicore_launch_core1(core1_worker);
27
}
28

29
void loop() {
30
 
31
  printf("test %d\r\n", counter);
32

33
  delay(2000);
34
}

Mit dem sleep von 500 ms funktioniert es, ohne oder mit 100 ms scheint der core1 zu hängen, der counter bleibt bei 1 stehen. Warum ist dieses sleep nötig?

(Firma: Gast) #7394882
Lesenswert?

J. S. schrieb:

der counter bleibt bei 1 stehen

Aus Sicht des Core0 loop ändert sich counter nicht. Der Compiler weis nicht, dass Core1 die Variable stetig verändert.

Stichworte: Atomic, Memory Barrier, volatile

J. S. schrieb:

Mit dem sleep von 500 ms funktioniert es, ohne oder mit 100 ms scheint der core1 zu hängen,

Das scheint mir eine Nebelkerze zu sein!

evtl ändert die sleep Größe was am optimierungsverhalten das Compilers.

J. S. schrieb:

scheint der core1 zu hängen

Blinken müsste es trotzdem!

OP #7394927
Lesenswert?

Arduino F. schrieb:

Aus Sicht des Core0 loop ändert sich counter nicht. Der Compiler weis nicht, dass Core1 die Variable stetig verändert.

beide cores arbeiten auf den gleichen Speicher, daher ist es egal wer den counter hochzählt. Und mit dem sleep funktioniert es ja, reproduzierbar.

Arduino F. schrieb:

Das scheint mir eine Nebelkerze zu sein!

was für eine Nebelkerze? Es ist deutlich sichtbar und reproduzierbar. Die Variable zählt hoch wenn es funktioniert und wird per printf am uart ausgegeben. Und die onboard LED blinkt im funktionierenden Fall. Ohne sleep blinkt die LED nicht und die counter Ausgabe bleibt bei 1, nicht 0.

Das sleep vor dem multicore_launch_core1(core1_worker); habe ich auch in anderen Beispielen gesehen, aber ohne Erklärung warum das nötig ist.

#7395057
Lesenswert?

J. S. schrieb:

multicore_reset_core1(); multicore_launch_core1(core1_worker);

Das schöne an Arduino ist ja, daß man nicht weiß, was man tut ;-) Ich würde den 1. Befehl weglassen, denn ein nicht initialisierter core1 kann kaum zurückgesetzt werden. Vielleicht besser

1
void multicore_launch_core1_with_stack   (   void(*)(void)    entry,
2
    uint32_t *    stack_bottom,
3
    size_t    stack_size_bytes 
4
  )

verwenden, damit man weiß, was Sache ist?

#7395078
Lesenswert?

J. S. schrieb:

diese Funktionen sind aus dem pico SDK, die sollten also ok sein.

Evtl. ist genau das das Problem... der Arduino-Core bringt schon was eigenes mit um den zweiten Core laufen zu lassen, nimmt es dir also vielleicht übel, wenn du da mit den Low-Level-SDK-Funktionen dazwischenpfuschst.

https://github.com/earlephilhower/arduino-pico/blob/master/docs/multicore.rst

aber eigentlich sollte das egal sein, wenn weder setup1 noch loop1 definiert sind:

1
//...
2
// Optional 2nd core setup and loop
3
extern void setup1() __attribute__((weak));
4
extern void loop1() __attribute__((weak));
5
extern "C" void main1() {
6
    rp2040.fifo.registerCore();
7
    if (setup1) {
8
        setup1();
9
    }
10
    while (true) {
11
        if (loop1) {
12
            loop1();
13
        }
14
    }
15
}
16
//...
17
extern "C" int main() {
18
//...
19
        if (setup1 || loop1) {
20
            delay(1); // Needed to make Picoprobe upload start 2nd core
21
            multicore_launch_core1(main1);
22
        }
23
//...
24
}
Angehängte Dateien:
OP #7395081
Lesenswert?

Dann klemmt man das USBSerial ab.

Mi N. schrieb:

in eine Enlosschleife packst, damit loop() selber garnicht mehr ausgeführt wird?

das ändert nichts, auch nicht wenn ich die endlos Schleife gleich in setup() einfüge.

Εrnst B. schrieb:

Evtl. ist genau das das Problem... der Arduino-Core bringt schon was eigenes mit um den zweiten Core laufen zu lassen

das ist 'der andere core', nicht der offizielle von Arduino. setup1() und loop1() werden im Arduino core nicht aufgerufen.

(Firma: Gast) #7395116
Lesenswert?

Chris K. schrieb:

Earlphil

Zu der rate ich!

Dann geht dieses:

1
int32_t counter;
2

3

4
void loop1()
5
{
6
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
7
  delay(1000);
8
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
9
  delay(1000);
10

11
  counter++;
12
}
13

14
void setup1()
15
{
16
  pinMode(LED_BUILTIN, OUTPUT);
17
}
18

19

20
void setup()
21
{
22
  Serial.begin(9600);
23
  Serial.println(F("Hello from Pico"));
24
}
25

26
void loop()
27
{
28
  delay(2000);
29
  Serial.println(counter);
30
}

Wenn es komplexer werden soll, ist auch FreeRTOS mit im Boot.

#7395133
Lesenswert?

Im Datatsheet (2.8.2) steht dass die Funktion multicore_launch_core1 ausreicht. Ich würde versuchshalber Mal das reset weglassen. In Setup wo du das aufrufst muss core1 eigentlich sowieso schon im Initialen Sleep hängen. Ich glaube aber selbst nicht dass es daran hängt.

Im gleichen Kapitel heißt es aber auch das beim Start des 2.Cores eine Statusmaschine abgearbeitet wird die auch Core0 betrifft. Wenn man sich dann etwas durchhangelt sieht man das in multicore_launch_raw die Interrupts abgeschalteten werden. Solange bis die Statusmaschine durch ist. Ich könnte mir vorstellen dass das der USB Teil im Core0 (gerade wenn es evtl noch viel auszuhandeln gibt) nicht sonderlich mag. Deshalb evtl auch der Fault im USB Teil den du oben gezeigt hast.

Nur Vermutungen...

#7395138
Lesenswert?

N. M. schrieb:

Nur Vermutungen...

Ich habe in den SDK-Routinen nachgesehen. Da wird reset_core1() implizit in multicore_launch_core1() aufgerufen. Das ist also egal.

J. S. schrieb:

Dann klemmt man das USBSerial ab.

Mi N. schrieb:

in eine Enlosschleife packst, damit loop() selber garnicht mehr ausgeführt wird?

das ändert nichts, auch nicht wenn ich die endlos Schleife gleich in setup() einfüge.

Die Idee dahinter war ja, daß keine USB-Routinen mehr bedient werden. Wenn es dann immer noch nicht läuft, könnte es ja ein Hardware-Problem mit der Versorgungsspannung sein, was mich dann generell interessieren würde.

Εrnst B. schrieb:

J. S. schrieb:

diese Funktionen sind aus dem pico SDK, die sollten also ok sein.

Evtl. ist genau das das Problem...

Auch das erscheint mir plausibel zu sein.

Selber programmiere ich auf Registerebene ohne Arduino und Co. Wenn man den 2. Kern startet, läuft das Programm (idealerweise) wie erwartet. Aber bei erneutem Download läuft - soweit ich das sehe - der 2. Kern einfach weiter und bleibt hängen, sobald sein eigener Programmbereich zerschossen wird. Da hilft dann nur noch ein Trennen der Versorgungsspannung oder ein Hardware-Reset. Vielleicht ist das auch hier das Problem. Einmal geht es, danach nicht mehr?

#7395146
Lesenswert?

Mi N. schrieb:

Ich habe in den SDK-Routinen nachgesehen. Da wird reset_core1() implizit in multicore_launch_core1() aufgerufen. Das ist also egal.

Ja, habe ich mittlerweile auch. Wie gesagt vermute ich auch eher was in Richtung USB und den Start des 2.Cores.

Ich denke Mal das hier ist ja genau dein Problem: https://forums.raspberrypi.com/viewtopic.php?t=347097

Scheint also kein Einzelfall zu sein.

Edit: Wenn ich das richtig lese wird vermutet dass der Timer so früh noch nicht richtig läuft und deshalb etwas Timeoutet. Im verlinkten Thread schreiben sie ja dass ein sleep_us(5000) als erste Zeile reichen würde im das Problem zu lösen.

Edit2: Hört sich doch eher nach einem Problem in OpenOCD/Picoprobe an wenn man die Links liest.

OP #7395164
Lesenswert?

Mi N. schrieb:

J. S. schrieb:

Dann klemmt man das USBSerial ab.

Den Satz wollte ich noch löschen, das war meine Vermutung und ich habe es vor dem Absenden noch getestet: das USBSerial läuft auch wenn ich in das setup() eine Endlosschleife einbaue. Der korrekte Test wäre also im setup den USB Int zu deaktivieren und erst nach dem Start von core1 zu aktivieren.

Das core1 reset hatte ich eingebaut nachdem ich auf das Problem gestossen bin, das ist also auch nicht. Der Mechanismus dahinter scheint ja einfach: core1 startet mit Code aus dem ROM und wartet darauf über den fifo SP und entry Pointer zu bekommen und rennt los wenn er alles bekommen hat.

OP #7395166
Lesenswert?

N. M. schrieb:

Edit2: Hört sich doch eher nach einem Problem in OpenOCD/Picoprobe an wenn man die Links liest.

viel zu lesen... ich kann auch nicht über den RPI debug probe Code flashen. Max Gerhardt hat mir darauf geantwortet das der OpenOCD im Arduino package evtl. zu alt ist und mir einen Link auf sein Repo gegeben. Muss ich auch noch testen... https://community.platformio.org/t/pico-upload-with-rpi-debug-probe-and-cmsis-dap-fails/33190

Gast #7395176
Lesenswert?

J. S. schrieb:

beide cores arbeiten auf den gleichen Speicher, daher ist es egal wer den counter hochzählt

Ist es nicht. Innerhalb einer schleife neigt der Compiler dazu, wiederholte Zugriffe auf RAM zu vermeiden, indem der Wert einmal in ein CPU Register kopiert wird und dann nur noch das Register gelesen wird. Das geht nämlich schneller, als RAM-Zugriffe.

Wann genau der Compiler das macht, und wann nicht, ist mehr oder weniger zufällig. Mit dem Schlüsselwort "volatile" vor der Deklaration der Variable kannst du den Compiler dazu zwingen, immer aufs RAM zuzugreifen.

1
volatile int32_t counter;
2

3
void loop1()
4
{
5
  counter++;
6
}
7

8
void loop()
9
{
10
  Serial.println(counter);
11
}
#7395179
Lesenswert?

Ah okay. Kannst ja auch Mal noch den Tipp versuchen den Timer nicht mit dem Debugger zu stoppen: timer_hw->dbgpause = 0; Als erste Zeile.

Hilft dir dann zwar nicht direkt weiter, man will ja oft dass die Timer stoppen, aber dann weißt du zumindest dass es da her kommt dass der Debugger scheinbar die Timer beim Startup nicht richtig bedient.

OP #7395187
Lesenswert?

Also der Fix von M. Gerhardt funktioniert, ich kann über die debug probe den upload machen. Das ist schon mal fein und geht einen Tick schneller. Das core1 Problem wird dadurch aber nicht behoben. Ich starte auch nicht immer den Debugger, hatte ich jetzt nur einmal um sehen wo es hängt. Volatile hilft nicht, die LED blinkt im Fehlerfall ja auch nicht und der Code darf nicht wegoptimiert werden.

#7395202
Lesenswert?

N. M. schrieb:

Wenn man sich dann etwas durchhangelt sieht man das in multicore_launch_raw die Interrupts abgeschalteten werden. Solange bis die Statusmaschine durch ist. Ich könnte mir vorstellen dass das der USB Teil im Core0 (gerade wenn es evtl noch viel auszuhandeln gibt) nicht sonderlich mag.

Oder bevor das Serial1.printf durch ist? Schon mal versucht, das hinter den Start von core1 zu verschieben?

OP #7395227
Lesenswert?

Michi S. schrieb:

Oder bevor das Serial1.printf durch ist? Schon mal versucht, das hinter den Start von core1 zu verschieben?

probiert und löst es auch nicht. Das USBSerial wird noch vor dem setup() aktiviert: https://github.com/arduino/ArduinoCore-mbed/blob/main/cores/arduino/main.cpp

Der core verwendet noch SDK 1.3.0, aktuell ist 1.5.0. Vielleicht ist in den Changelogs ja was zu finden. Ansonsten bleibt das sleep erstmal drin.

#7396365
Lesenswert?

Da ich für den RP2040 einen J-Link EDU mini benutze und damit Probleme habe, den 2. Kern "zielgerichtet" neu zu starten, hatte ich gehofft, hier eine Idee zur Lösung des Problems zu erhalten: Wenn der 2. Kern erst einmal läuft, gibt es Probleme, per SWD eine neue Programmversion zu starten. Mit ein paar Wiederholungen kann es dann doch laufen. Laden mit "Connect during reset" o.ä. scheinen nur wirkungslose Optionen zu sein. Andere Anwender haben dieses Problem in ähnlicher Form wohl auch.

Angeregt durch diesen Beitrag hier habe ich weitere Dinge probiert und u.a. gezielt im Debugger das reset_core1() manuell nachgebildet

1
  PSM_SET->FRCE_OFF = PSM_FRCE_OFF_proc1_Msk;       // core1 passiv
2
  while(!(PSM->FRCE_OFF & PSM_FRCE_OFF_proc1_Msk)); // abwarten
3
  PSM_CLR->FRCE_OFF = PSM_FRCE_OFF_proc1_Msk;       // Bit wieder loeschen

was aber keinen Erfolg bringt. Erst nach Trennen der Versorgungsspannung ist wieder Ruhe, was aber bei der Entwicklung extrem lästig ist.

Vielleicht sind delay(), sleep() und Programmumstellungen nur "Hilfeschreie", um den richtigen Zeitpunkt beim Start zu erwischen, den es eigentlich garnicht geben dürfte.

OP #7396378
Lesenswert?

ich habe da noch nicht weiterforschen können. Eine Idee war noch einen BP auf die Übergabe der Argumente für core1 zu setzen, also um zu sehen ob der Stackpointer richtig ist. Was im sleep passiert habe ich nicht nachgesehen. Im Mbed sleep wird ein Wecker gestellt und dann per WFI gewartet, evtl. sogar im deep sleep. Das ist gut zum Strom sparen, macht aber in einigen Fällen Ärger. Der Reset müsste ja auch core1 rücksetzen, und der sollte dann wieder aus dem ROM in seine Initialsierung laufen. Aber so genau habe habe ich mir das RefMan noch nicht angesehen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren