RP2040 RPI Pico - Frequenz erzeugen

OP #7160316
Lesenswert?

Hallo,

ich möchte den Raspberry Pico als Frequenzgenerator nutzen.

Ausgangsfrequenz soll 10 Hz - 2MHz betragen.
Um die Auflösung zu verbessern möchte ich den "Fractional Clock Divider" 
nutzen.
Klar, die Lösung ist nicht perfekt, aber irgendeinen Tod muss man 
sterben ;)

Ich habe ein Bild von der Architektur der 16-bit-Timer angehängt.
Daraus ergibt sich ja diese Formel (aus RP2040 Datenblatt Seite 530).
1
Wertebereiche für Parameter:
2
TOP      = 0 - 65535
3
DIV_INT  = 1 - 255
4
DIV_FRAC = 0 - 15

Das Dumme ist nur: Ich möchte die Frequenz Soll-Frequenz "eingeben" und 
die Werte für DIV_INT, DIV_FRAC, TOP erhalten, bei denen der Fehler zur 
Soll-Frequenz am geringsten ist.
1
Beispiel:
2
für Soll-Frequenz = 18673 Hz
3

4
Errechnet durch Durchlaufen und Prüfen aller möglichen Werte:
5
DIV_INT = 4
6
DIV_FRAC = 13
7
TOP = 1390
8
Proberechnung ergibt für f = 18672,9160... Hz

Kann man das effizient lösen?

Aktuell fällt mir nur nur ein, die 256 x 16 x 65536 Werte zu durchlaufen 
und zu überprüfen bei welcher Konfiguration der Fehler am geringsten 
ist.
Eventuell kann man's zum Beschleunigen vorher etwas eingrenzen ...
Bin etwas mit meinem Latein am Ende.
Angehängte Dateien:
Gast #7160370
Lesenswert?

Ich bin kein Hardcore Mathematiker, und ohne das genauer untersucht zu 
haben, könnte dich ev. die Recherche Richtung CFRAC (continued fraction 
factorization method) in den Bereich einer Lösung bringen.

just my 2ct
Gast #7160419
Lesenswert?

Erst mit DIV_FRAC = 0 eine Lösung für TOP finden, die im erlaubten 
Wertebereich liegt (uint16).
Dabei DIV_INT inkrementieren, bis es passt.
Wenn TOP und DIV_INT gefunden sind DIV_FRAC inkrementieren, bis der 
Fehler minimal ist.
Das sind dann im schlimmsten Fall 256 + 16 Durchläufe
Je nach gesuchter Frequenz kann man dann mit DIV_INT und DIV_FRAC 
entweder am oberen oder unteren Ende des Wertebereichs starten und so 
das ganze nochmal Beschleunigen.
Gast #7160429
Lesenswert?

donvido schrieb:
> Erst mit DIV_FRAC = 0 eine Lösung für TOP finden, die im erlaubten
> Wertebereich liegt (uint16).
> Dabei DIV_INT inkrementieren, bis es passt.
> Wenn TOP und DIV_INT gefunden sind DIV_FRAC inkrementieren, bis der
> Fehler minimal ist.
> Das sind dann im schlimmsten Fall 256 + 16 Durchläufe

es gibt sicher cleverere Schrittweiten als "+1", Stichwort "sukzessive 
Approximation"
OP #7160470
Lesenswert?

donvido schrieb:
> Das sind dann im schlimmsten Fall 256 + 16 Durchläufe

Der Ansatz gefällt mir gut, sollte ich auch mit meinen Mathe-Kentnissen 
umgesetzt bekommen

TUX 4eva schrieb:
> KAuf dir eine anständigen uC, nicht dies "overpriced, underdelivering"
> hype teil.

@donvido hat's schon richtig gesagt, hier gehts auch ein kleines 
bisschen um die Machbarkeit.

Zum anderem sieht das Projekt so aus: Ich möchte PWMs generieren.
* 4x unabhängige PWM
* 0.0Hz-1000.0Hz mit 0.0-100.0% DutyCycle
* 1.000k-20.00kHz mit 0-100% DutyCycle

Mit Sicherheit habe ich auch Einbußen bei der Genauigkeit. Wie stark 
versuche ich gerade auszurechnen.

Ich wäre gerne in der Arduino/Atmega/RPI Pico Ecke geblieben, da ich da 
schon einiges gemacht habe. Aber auch ein grobe Recherche in Richtung 
STM32 haben gezeigt, dass auch die größeren uCs keine eierlegende 
Wollmilchsau abgeben.

Die 8 unabhängigen 16bit Timer vom RP 2040 finde ich eigentlich ganz 
interessant.

Meine aktuelle Idee:
* 4 Timer des RP2040 erzeugen eine einstellbare Frequenz
* diese Speise ich bei den anderen 4 Timern als Takt ein und erzeuge ein 
PWM-Signal.
* Dann werde ich mal testen ob mich der Jitter stört. Falls ja, gibts ja 
die Option mit einem PLL um die Frequenz vor dem Einspeisen zu glätten.

Ein älterer Ansatz war mit einem DDS die Frequenz zu erzeugen 
(AD9833/34) um den PWM zu füttern.
Aber ich möchte die Kirche erst einmal im Dorf lassen.

Sollte jemand zufällig die perfekte LowBudget-Idee haben, nehm ich diese 
natürlich auch gerne.
Gast #7160515
Lesenswert?

Hallo,

Wenn es um ein Rectecksignal geht, dann schau Dir mal Pico's Pios an.

Damit ist die Lösung ein Kinderspiel:

F_PIO auf 100MHz stellen, 25 Takte Pin auf low, 25 Takte Pin auf high = 
2 MHz
F_PIO auf 2000Hz stellen, 25 Takte Pin auf low, 25 Takte Pin auf high = 
40 Hz.
Der Zwischenbereich kann linear interpoliert werden.

Für den Bereich unter 40 Hz (2_000 > F_PIO < 125_000_000)
muss das Delay halt grösser werden (der Pico kann nicht langsamer).
Das Programm benötigt ca. 5 Instruktionen.

Es gibt Beispiele, bei denen die Pios sogar Sinussignale erzeugen.

Michael S.
OP #7160571
Lesenswert?

Michael S. schrieb:
> Hallo,
>
> Wenn es um ein Rectecksignal geht, dann schau Dir mal Pico's Pios an.
>
> Damit ist die Lösung ein Kinderspiel:
>
> F_PIO auf 100MHz stellen, 25 Takte Pin auf low, 25 Takte Pin auf high =
> 2 MHz
> F_PIO auf 2000Hz stellen, 25 Takte Pin auf low, 25 Takte Pin auf high =
> 40 Hz.
> Der Zwischenbereich kann linear interpoliert werden.
>
> Für den Bereich unter 40 Hz (2_000 > F_PIO < 125_000_000)
> muss das Delay halt grösser werden (der Pico kann nicht langsamer).
> Das Programm benötigt ca. 5 Instruktionen.
>
> Es gibt Beispiele, bei denen die Pios sogar Sinussignale erzeugen.
>
> Michael S.

Klingt echt interessant. Wie darf ich mir das mit der Auflösung bei z.B. 
2MHz vorstellen?
die PIOs hatte ich dafür noch nicht auf dem Schirm.
Gast #7160610
Lesenswert?

Michael S. schrieb:

> Wenn es um ein Rectecksignal geht, dann schau Dir mal Pico's Pios an.

Ähem nö. So sehr ich die PIOs auch mag, das hier wäre wirklich Perlen 
vor die Säue. Die PIOs spart man natürlich für höhere Aufgaben auf.

Zumal die Taktrechnerei für ein optimales Ergebnis dadurch auch kein 
bissel einfacher wird. Auch die PIOs haben schließlich INT_DIV und 
FRAC_DIV in ihren Vorteilern.

Also nö, was mit den Timern geht, wird natürlich auch mit den Timern 
gemacht.

Die Rechnerei ist übrigens garnicht so wild. Man kann das Problem so 
umformen, dass aus INT_DIV, FRAC_DIV und TOP eine binäre Teilerkette 
(sprich eine Zahl) mit 28 Bit entsteht. Und diese Zahl läßt sich ganz 
einfach aus Eingangstakt und gewünschtem Takt berechnen.

Etwas komplizierter ist dann, aus den gefundenen 28Bit die drei 
benötigten Werte sinnvoll zu extrahieren. Aber mit ein wenig Nachdenken 
sollte das jeder Programmierer, der den Namen verdient, problemlos 
hinbekommen.

Was ich außerdem noch anmerken möchte: die 125MHz clk_per sind nur der 
Standardwert, aber keinesfalls eine Art Naturkonstante. Man kann diesen 
Takt ändern! Alle selbstgebastelte Rechnerei sollte also immer davon 
ausgehen, dass dieser Takt auch mal ein anderer sein kann und 
dementsprechend den tatsächlichen Takt als Eingangsgröße verwenden, 
nicht den Standardwert...
Gast #7160641
Lesenswert?

Hallo,

das Prinzip ist ziemlich einfach:
1
Einen Output-Pin auswählen.
2
Die Instruktion (Programm) schreiben:
3
label(start)      # zur Veranschaulichung, einfacerr mit wrap target
4
set(pins, 0) [24] # Pin auf low setzen + 24 Takte nix tun
5
set(pins, 1) [24] # dito, aber high
6
jmp(start)

Das Programm mit der Angabe der gewünschten Frequenz×50 für F_PIO 
starten.

Für Frequenzen unterhalb 40 Hz müssen nop()s eingefügt werden.
Um 10 Hz zu erreichen bei F_PIO 2000 muss 1 Takt 200 Pio-Takte 
verbummeln.
1
# label(start)      # zur Veranschaulichung, einfacerr mit wrap target
2
set(pins, 0) [24] # Pin auf low setzen + 24 Takte nix tun
3
nop()        [24] # 1 + 24 Takte nix tun 
4
nop()        [24] # 1 + 24 Takte nix tun 
5
nop()        [24] # 1 + 24 Takte nix tun        
6
set(pins, 1) [24] # dito, aber high
7
nop()        [24] # 1 + 24 Takte nix tun 
8
nop()        [24] # 1 + 24 Takte nix tun
9
nop()        [24] # 1 + 24 Takte nix tun
10
wrap
11

12
[xx] sind Wartecycles, der max. Wert ist 31 (5 Bit).
Macht zusammen 200 Takte.
In Verbindung mit dem Takt, mit dem die Statemachine rennt, ergibt sich 
eine genau definierte Frequenz am gewählten Ausgangspin (es dürfen sogar 
mehrere Pins sein, bei Bedarf mit invertiertem Pegel).

Michael S.
Gast #7162627
Lesenswert?

c-hater schrieb:
> Zumal die Taktrechnerei für ein optimales Ergebnis dadurch auch kein
> bissel einfacher wird. Auch die PIOs haben schließlich INT_DIV und
> FRAC_DIV in ihren Vorteilern.


Stimmt so nicht wirklich.

Denn es gibt einen kleinen, aber dafür entscheidenden Unterschied 
zwischen Timer und Statemachine:

The clock divider slows the state machine’s execution by a constant 
factor, represented as a 16.8 fixed-point fractional number.

Die Timer haben nur einen 8.4 divider.

Wenn ich das Beispiel 'Soll 18693Hz' bei 50 Takten / Wave rechne, dann 
muss F_PIO 50 × 18693Hz = 934650Hz betragen.

Daraus ergibt sich DIV_INT = 133 und DIV_FRAC = 189/256.
Zurückgerechnet wird ein Rectecksignal von 18693.23Hz erzeugt.

Ganz ohne aufwändige Rechnerei.

Michael S.
Gast #7162667
Lesenswert?

Da man innerhalb zweier »PIO-cycles« einen Pin ein- und wieder 
ausschalten kann:
1
div = F_cpu / F_target / 2
2
divint = int(div)
3
divfrac = round((div - divint) * 256)
4
div_nearest = divint + dicfrac / 256
5
F_result = F_cpu / div_nearest
6
deviation_ppm = (F_result / F_target - 1) * 1e6
7

8
F_cpu = 125E6
9
F_target = 18693 Hz
10

11
div: 3343,49756593377
12
divint: 3343
13
divfrac: 127
14
div_nearest: 3343,49609375
15
F_result: 18693,0082307652
16
deviation_ppm: 0,440312693861245

Falls ich mich nicht irgendwo verrechnet haben sollte.
Gast #7162777
Lesenswert?

Norbert schrieb:
> Falls ich mich nicht irgendwo verrechnet haben sollte

Alles korrekt.
Im Register CLOCKDIV steht 0x0d0f7f00.

Je grösser DIV_INT, um so feiner wird die Auflösung.
Aber der abdeckbsre Wertebereich verändert sich.
Die 50 Takte hatte ich nur deshalb gewählt, um die gewünschten 2MHz noch 
erreichen zu können, ohne am unteren Ende eine zu grosse Lücke zu 
lassen.


Michael S.
Gast #7162790
Lesenswert?

Michael S. schrieb:
> Norbert schrieb:
>> Falls ich mich nicht irgendwo verrechnet haben sollte
>
> Alles korrekt.
> Im Register CLOCKDIV steht 0x0d0f7f00.
>
> Je grösser DIV_INT, um so feiner wird die Auflösung.
> Aber der abdeckbsre Wertebereich verändert sich.
> Die 50 Takte hatte ich nur deshalb gewählt, um die gewünschten 2MHz noch
> erreichen zu können, ohne am unteren Ende eine zu grosse Lücke zu
> lassen.
>
> Michael S.

Prima, danke für's Korrekturlesen.
Baue gerade eine Klasse die mit variablen PIO Zyklen arbeitet.
Die berechnet immer die bestmöglichen Werte-Paare (PIO-Zyklen und DIV).
Braucht nur ca. 460µs auf'm Pico.

Da stößt man von der Auflösung her in Bereiche vor, welche weit besser 
als Quarz-Genauigkeit sind.
Bis 32kHz < 1ppm
Bis 320kHz < 10ppm
Bis 1.6MHz < 50ppm

Sollte für Hobbyzwecke genügen.
(Und hat Spaß gemacht)
#7162794
Lesenswert?

Norbert schrieb:
> Prima, danke für's Korrekturlesen.
> Baue gerade eine Klasse die mit variablen PIO Zyklen arbeitet.
> Die berechnet immer die bestmöglichen Werte-Paare (PIO-Zyklen und DIV).
> Braucht nur ca. 460µs auf'm Pico.
>
> Da stößt man von der Auflösung her in Bereiche vor, welche weit besser
> als Quarz-Genauigkeit sind.
> Bis 32kHz < 1ppm
> Bis 320kHz < 10ppm
> Bis 1.6MHz < 50ppm
>
> Sollte für Hobbyzwecke genügen.
> (Und hat Spaß gemacht)

Du möchtest einem Quarz Konkurrenz machen? Überlege dir einmal welchen 
gemeinsamen Nenner dein Programm und dein Pico als Taktquelle haben. Und 
dann überlegst du dir nochmal ob deine Jitter Messung das macht was sie 
soll. Du wärest nicht der Erste der sich damit vertut.

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