Forum: Mikrocontroller und Digitale Elektronik PiPico2 Frequenzzähler


von Christoph M. (mchris)



Lesenswert?

Ich will mit dem PiPico2 Frequenzen zählen. Das Ganze soll über die 
Periodendauermessung funktionieren, da ich eine möglichst schnelles 
Update der aktuellen Frequenz benötige.

Mein Testaufbau ist ein Frequenzgenerator mit 100kHz Rechteck an den 
Pin16 des PiPico geführt. So wie es aussieht, entstehen hier Fehler bis 
zu ca. 20%.
1
#include <Arduino.h>
2
3
volatile uint32_t DeltaT = 0;
4
5
#define TESTPIN 16
6
7
void myISR() {
8
  static uint64_t last_cycles = 0;
9
  uint64_t t=rp2040.getCycleCount64();
10
  DeltaT=t-last_cycles;
11
  last_cycles = t; 
12
}
13
14
void setup() {
15
  Serial.begin(115200);
16
  //while (!Serial) {}
17
  pinMode(LED_BUILTIN,OUTPUT);
18
}
19
20
void setup1()
21
{
22
  pinMode(TESTPIN, INPUT_PULLUP);
23
  attachInterrupt(digitalPinToInterrupt(TESTPIN), myISR, RISING);
24
}
25
26
void loop() {
27
  // read last_cycles (simple read; if strict atomicity needed use atomic ops)
28
  static bool state;
29
  uint64_t c = DeltaT;//last_cycles;
30
  uint64_t f = rp2040.f_cpu();               // CPU frequency in Hz
31
  double ns = (double)c * 1e9 / (double)f;   // cycles -> nanoseconds
32
  float fmeas=1/(ns*1e-9);
33
  Serial.println(fmeas);
34
  delay(20);
35
36
}

von Michael P. (mipo)


Lesenswert?

Kannst du den Fehler besser beschreiben? Ist für mich nicht klar ob du 
einen jitter hast oder nur falsche Anzeige. Kenne den Pico nicht, aber 
brauchst du unbedingt Variable-Typen inkl. double und print?

von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> Ich will mit dem PiPico2 Frequenzen zählen. Das Ganze soll über die
> Periodendauermessung funktionieren, da ich eine möglichst schnelles
> Update der aktuellen Frequenz benötige.

Wenn es sowohl schnell, als auch genau und ohne CPU Belastung gehen 
soll, dann empfehle ich nachdrücklich den Schwenk auf die PIO.
Der Pico2 läuft (default) mit 150MHz,
dh. damit ist eine Auflösung von 6 2/3ns möglich.
Wer glatte Zahlen mag, bei 200MHz──▶5ns.

von Norbert (der_norbert)


Lesenswert?

Michael P. schrieb:
> Kenne den Pico nicht, aber
> brauchst du unbedingt Variable-Typen inkl. double und print?

Der Pico2 hat zwei FPUs, bei float Addition und Multiplikation innerhalb 
eines Taktzyklus, Division innerhalb max. acht (IIRC)
Double braucht mit dem CoPro etwas länger, was aber an der Stelle völlig 
egal ist.

Ich könnte mir vorstellen, dass der ganze multiple Arduiono 
Überbau/Abstraktion einer präzisen Messung nicht unbedingt zuträglich 
ist.

von Christoph M. (mchris)


Lesenswert?

Michael P. (mipo)
08.10.2025 07:12
>Kannst du den Fehler besser beschreiben?

Die Anzeige funktioniert ziemlich sicher. Das Diagramm zeigt eher 
Ausreißer als Jitter. Klar, dass es so was bisweilen gibt. Manchmal auch 
durch Übertragungsfehlern. Der Screenshot ist vom Plotter der 
Arduino-IDE und das funktioniert stabil.
Die Ausreißer würde ich eher als Interrupt-Querschläger ansehen. Ich 
hätte da weniger erwartet, weil ich die Zeitmessung mit "setup1" ja auf 
den zweiten Kern des PiPico gelegt habe.

Norbert (der_norbert)
>Wenn es sowohl schnell, als auch genau und ohne CPU Belastung gehen
>soll, dann empfehle ich nachdrücklich den Schwenk auf die PIO.

Da wäre die Frage, ob es überhaupt mit der hohen Auflösung geht. Auch 
die PIO muss ja ein paar paar Instruktionen durchführen.
Vor allen Dingen scheint mir der Aufwand recht hoch:
Laut

https://github.com/earlephilhower/arduino-pico/discussions/221

soll man den Online-PIO Assembler

https://wokwi.com/tools/pioasm

verwenden.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> Da wäre die Frage, ob es überhaupt mit der hohen Auflösung geht. Auch
> die PIO muss ja ein paar paar Instruktionen durchführen.

Zur ersten Frage: Ja, geht es.
Die PIO arbeitet jede Instruktion innerhalb eines Taktzyklus ab.
Zählen braucht eine, springen braucht eine. (Wenn man's sauber macht)

Da man aber zwölf Statemachines zur Verfügung hat, kann man getrost zwei 
davon zeitversetzt das gleiche messen lassen.

Ich selbst habe ganz sicher schon tausende Zeilen PIO-Assembler 
geschrieben (nicht übertrieben) und nicht einmal auf einen 
Online-Gehgips zurück greifen müssen. Zwei Hände voll Instruktionen 
stellen jetzt lernmäßig nicht die Eiger Nordwand in den Schatten (Sorry 
für's Wortspiel)

von Mi N. (msx)


Lesenswert?

Christoph M. schrieb:
> So wie es aussieht, entstehen hier Fehler bis zu ca. 20%.

Das wundert nicht, da ohne richtige Synchronisierung von Eingangssignal 
und Referenztakt keine stabile Messungen möglich sind. Entweder 
verwendest Du externe D-FFs oder eine interne PIO + DMA.
Intern habe ich eine Lösung für den RP2040 auch für Arduino-IDE, die 
aber beim PicoPi2 wegen vermutlich wieder veränderter Arduino-Version 
nicht richtig compiliert wird. Beispiele kannst Du hier in der 
Codesammlung finden.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Läuft die serielle Kommunikation über USB? Dann unterbrechen oder
blockieren die USB-Interrupts sporadisch deinen GPIO-Interrupt, was dann
zu ungenauen Zeitmessungen führt.

Lässt du die Ausgabe der Ergebnisse über einen UART laufen, sollte das
nicht passieren.

von Christoph M. (mchris)



Lesenswert?

Yalu X. (yalu) (Moderator)
08.10.2025 09:38

>Läuft die serielle Kommunikation über USB? Dann unterbrechen oder
>blockieren die USB-Interrupts sporadisch deinen GPIO-Interrupt, was dann
>zu ungenauen Zeitmessungen führt.

Ich habe das Programm noch mal umgeschrieben und Interrupt-frei gemacht. 
Es sind dann keine Ausreißer mehr sichtbar. Das Ganze läuft extra ohne 
alles auf dem zweiten Kern. Aber die maximale Abweichung liegt immer 
noch einiges über der anvisierten Nanosekundenauflösung (siehe Anhang, 
y-Achse: us).
1
/*
2
 * Periodendauermessung auf zweitem Kern
3
 * 
4
 * Abweichung ca. 0.7us => zu ungenau
5
 * 
6
 */
7
#include <Arduino.h>
8
9
volatile uint32_t DeltaT = 0;
10
11
#define TESTPIN 16
12
13
void setup() {
14
  Serial.begin(115200);
15
  pinMode(LED_BUILTIN, OUTPUT);
16
}
17
18
void setup1()
19
{
20
  pinMode(TESTPIN, INPUT_PULLUP);
21
}
22
void loop1()
23
{
24
  while (1)
25
  {
26
    static uint32_t oldPin = 0;
27
    if (gpio_get(TESTPIN) != oldPin)
28
    {
29
      static uint64_t last_cycles = 0;
30
      uint64_t t = rp2040.getCycleCount64();
31
      DeltaT = t - last_cycles;
32
      last_cycles = t;
33
      oldPin = gpio_get(TESTPIN);
34
    }
35
  }
36
}
37
38
void loop() {
39
  static bool state;
40
  uint64_t c;
41
  uint64_t f;
42
  float ns;
43
  float us;
44
  float minVal=1e6;
45
  float maxVal=0;
46
  f = rp2040.f_cpu();               // CPU frequency in Hz
47
    
48
  for (int n = 0; n < 100;n++)
49
  {
50
    c = DeltaT;
51
    ns = (double)c * 1e9 / (double)f;   // cycles -> nanoseconds
52
    us = ns/1000;
53
    Serial.println(us); //
54
    delay(20);
55
    if(minVal>ns)minVal=ns;
56
    if(maxVal<ns)maxVal=ns;
57
  }
58
  //Serial.println("max[ns]:"+String(maxVal));
59
  //Serial.println("min[ns]:"+String(minVal));
60
}

>Intern habe ich eine Lösung für den RP2040 auch für Arduino-IDE, die
>aber beim PicoPi2 wegen vermutlich wieder veränderter Arduino-Version
>nicht richtig compiliert wird.

Ich verwende das Early-Hill-Power Framework, das sollte recht stabil 
sein und besser als das originale Arduino PiPico mit MBed:
https://github.com/earlephilhower/arduino-pico
Dort kann man auch die nativen SDK-Funktionen benutzen (siehe oben im 
Code "gpio_get" statt digitalRead).
Wo genau kann ich deinen RP2040 Code finden?

: Bearbeitet durch User
von Mi N. (msx)


Angehängte Dateien:

Lesenswert?

Christoph M. schrieb:
> Wo genau kann ich deinen RP2040 Code finden?

Hier findest Du die Entwicklungsgeschichte zu meinen RP2040 Schaltungen: 
http://mino-elektronik.de/fmeter/fm_software.htm#bsp_RP2040

Bei der 'Luxusversion' mit TDC gibt es die Ausführungen für IAR-EWARM, 
Segger-IDE und Arduino, die nahezu identisch sind. Die Messungen laufen 
alle über Interrupts im Hintergrund. Das bedingt wegen der standardmäßig 
bei Arduino vorhandenen USB-Schnittstelle auf der einen Seite, daß die 
Interruptvektoren ins RAM gelegt werden müssen. Auf der anderen Seite 
muß die 'RP2040.h' der beiden anderen IDEs für Arduino passen und darf 
nicht mit den dortigen *.h-Dateien kollidieren.

Für Arduino funktionierte das mit IDE V 2.3.2 package "Raspberry Pi Pico 
3.7.0". Bei neueren Versionen sind unverständlicherweise die *.h-Dateien 
geändert/getauscht worden. Mir war es dann zu blöd, diesen Änderungen 
hinterher zu programmieren.

Im Anhang findest Du meine unvollendete Version, die eigentlich noch um 
serielle Befehle und Speicherung von Konstanten im FLASH-Speicher 
ergänzt werden sollte. Zur Datenübertragung wird USB benutzt.
DEF_F1_MESSZEIT (in ms) kannst Du ja auf den benötigten Wert 
verkleinern.
Ich hoffe, daß die Kommentare schon zur Verion passen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Christoph M. schrieb:
> Ich habe das Programm noch mal umgeschrieben und Interrupt-frei gemacht.
> Es sind dann keine Ausreißer mehr sichtbar.

Sehr gut, dann lag es also tatsächlich an den Interrupts.

> Aber die maximale Abweichung liegt immer noch einiges über der
> anvisierten Nanosekundenauflösung

Mit welche Taktfrequenz läuft der Pico2? Glaubst du wirklich, man könne
damit 1ns auflösen?

> (siehe Anhang, y-Achse: us).

Ich habe mal mit großer Anstrengung versucht, die Schwankungsbreite aus
deinem maximal schlecht skalierten Diagramm herauszulesen und komme auf
etwa ±46ns. Das ist besser als ich von einer Lösung mit Arduino ohne PIO
erwartet hätte.

Besser geht es natürlich mit PIOs.

von Norbert (der_norbert)


Lesenswert?

Yalu X. schrieb:
> Mit welche Taktfrequenz läuft der Pico2?

150MHz ist Standard.

> Glaubst du wirklich, man könne damit 1ns auflösen?

Nein, das würde 1GHz erfordern.
Bei 200MHz kann man aber schon präzise 5ns auflösen.

Beitrag #7948316 wurde vom Autor gelöscht.
von Christoph M. (mchris)


Lesenswert?

Norbert (der_norbert)
08.10.2025 11:35
>Pro Schritt 100 Messungen, von 1997cycles … 2003cycles.
>Bei 200MHz: 100150.230 Hz … 99850.230 Hz

Sind das Messungen? Wenn ja, dann sind sie verdächtig: 1 Bit 
Quantisierungsfehler sollte immer drinn sein.

von Christoph M. (mchris)


Lesenswert?

Mi N.
>Hier findest Du die Entwicklungsgeschichte zu meinen RP2040 Schaltungen:
>http://mino-elektronik.de/fmeter/fm_software.htm#bsp_RP2040

Danke. Mit DMA, beeindruckend :-)
1
/*
2
 PIO0-SM0:
3
 Zeitzaehlung im x-Register mit SystemCoreClock/PIO_TIME_DIV (=4)
4
 und Ausgabe des Wertes bei negativer Flanke des Eingangssignals
5
*/
6
void init_PIO0(void)
7
{
8
// PIO0 aktivieren  
9
  RESETS_CLR->RESET = RESETS_RESET_pio0_Msk;
10
  while (!(RESETS->RESET_DONE & RESETS_RESET_pio0_Msk));
11
12
// FIN1_PIN f�r SM0 initialisieren
13
  IO_BANK0->GPIO2_CTRL = IO_BANK0_GPIO0_CTRL_FUNCSEL_sio_0; // GPIO-Eingang
14
  PADS_BANK0->GPIO2 = PADS_BANK0_GPIO0_IE_Msk |            
15
//                      PADS_BANK0_GPIO0_PUE_Msk |          // pullup
16
                      PADS_BANK0_GPIO0_SCHMITT_Msk;         //  + schmitttrigger
17
  
18
  PIO0->SM0_PINCTRL |= 1<<PIO0_SM0_PINCTRL_SIDESET_BASE_Pos | 1<<PIO0_SM0_PINCTRL_SET_BASE_Pos;
19
  PIO0->SM0_EXECCTRL = (31 << PIO0_SM0_EXECCTRL_WRAP_TOP_Pos) | 
20
                        (0 << PIO0_SM0_EXECCTRL_WRAP_BOTTOM_Pos) |
21
                        (FIN1_PIN  << PIO0_SM0_EXECCTRL_JMP_PIN_Pos);  // Pin f�r JMP
22
// ADR0  
23
  PIO0->INSTR_MEM0 = 0x0241;      // jmp x-- -> ADR1, 2 x delay
24
// ADR1
25
  PIO0->INSTR_MEM1 = 0x00c0;      // jmp FIN1_PIN -> ADR0 warten bis '0'
26
// input-pin 1 -> 0
27
  PIO0->INSTR_MEM2 = 0x4020;      // mov x->isr, x-Wert ausgeben
28
// ADR3
29
  PIO0->INSTR_MEM3 = 0x0044;      // jmp x-- -> ADR4
30
// ADR4  
31
  PIO0->INSTR_MEM4 = 0x00c1;      // jmp FIN1_PIN 0-> 1 dann ADR0
32
  PIO0->INSTR_MEM5 = 0x0103;      // jmp ADR3, 1 x delay
33
  
34
  PIO0_SET->SM0_SHIFTCTRL = PIO0_SM0_SHIFTCTRL_FJOIN_RX_Msk | // FIFO = 8
35
                            PIO0_SM0_SHIFTCTRL_AUTOPUSH_Msk;  // autopush
36
  PIO0_SET->CTRL = (1 << (PIO0_CTRL_SM_ENABLE_Pos));          // starten
37
}
38
39
40
// Zeitstempel erzeugen:
41
// DMA-Wert = Zeitpunkt, DMA-TRANS-COUNT = Ereignisse
42
// wenn CH0 fertig wird CH10 angestossen, der wiederum CH0 startet
43
// daher ist keine ISR notwendig
44
void init_dma_pio0(void)
45
{
46
  RESETS_SET->RESET = RESETS_RESET_dma_Msk;   // reset all CHx
47
  RESETS_CLR->RESET = RESETS_RESET_dma_Msk;
48
  while (!(RESETS->RESET_DONE & RESETS_RESET_dma_Msk));
49
50
// Zaehlen der Ereignisse und lesen des Zeitstempels  
51
  DMA->CH0_READ_ADDR = (uint32_t) &PIO0->RXF0;
52
  DMA->CH0_WRITE_ADDR = (uint32_t) &F1_time_stamp;
53
  DMA->CH0_TRANS_COUNT = DMA_COUNT;
54
  DMA->CH0_CTRL_TRIG =  DREQ_PIO0_RX0 << DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos |     // PIO0-RX0
55
                        10 << DMA_CH0_CTRL_TRIG_CHAIN_TO_Pos |  // trigger CH10
56
                        2 << DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos |  // word
57
                        DMA_CH0_CTRL_TRIG_EN_Msk;               // enable
58
59
// Hilfskanal zum Nachtriggern con CH0  
60
  DMA->CH10_READ_ADDR = (uint32_t) &dma_dummy;                  // dummy location
61
  DMA->CH10_WRITE_ADDR = (uint32_t) &dma_dummy;
62
  DMA->CH10_TRANS_COUNT = 1;                                    // single transfer
63
  DMA->CH10_CTRL_TRIG =  DREQ_AUTO << DMA_CH0_CTRL_TRIG_TREQ_SEL_Pos | // free running
64
                        0 << DMA_CH0_CTRL_TRIG_CHAIN_TO_Pos |   // Trigger CH0  
65
                        2 << DMA_CH0_CTRL_TRIG_DATA_SIZE_Pos |  // word
66
                        DMA_CH0_CTRL_TRIG_EN_Msk;               // enable
67
}

von Norbert (der_norbert)


Angehängte Dateien:
  • log (20,5 KB)

Lesenswert?

Sieben mal je 100 Messungen, von 1997 … 2003 Taktzyklen(à 5ns)
Bei 200MHz ergeben sich Frequenzen von 100150.230 Hz … 99850.230 Hz

von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> Sind das Messungen? Wenn ja, dann sind sie verdächtig: 1 Bit
> Quantisierungsfehler sollte immer drinn sein.

Das sind Messungen. Aber auf einem Controller.
PIO1 erzeugt die Frequenz auf GPIO16, PIO0 misst sie auf GPIO17.
Da muss man nicht so eine große Brücke bauen! ;-)

Also 100% synchron.
Aber du hast recht, wenn man etwas Externes messen würde, dann käme 1 
cycle Unschärfe hinzu. (Plus die ppms der beteiligten Quarze)

: Bearbeitet durch User
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.