Forum: Mikrocontroller und Digitale Elektronik RP2040 RPI Pico - Frequenz erzeugen


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Thomas O. (tom-tom92)


Angehängte Dateien:

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.

: Bearbeitet durch User
von Jester (Gast)


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

von donvido (Gast)


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.

von TUX 4eva (Gast)


Lesenswert?

KAuf dir eine anständigen uC, nicht dies "overpriced, underdelivering" 
hype teil.

von imschritthalter (Gast)


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"

von imschritthalter (Gast)


Lesenswert?

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

Gehirnjogging hat noch keinem Gehirnträger geschadet, aber da zählst Du 
nicht mit.

von donvido (Gast)


Lesenswert?

imschritthalter schrieb:
> es gibt sicher cleverere Schrittweiten als "+1", Stichwort "sukzessive
> Approximation"

Ja das kann man mit Sicherheit noch beliebig optimieren.
Die Frage ist nur, ob es den Aufwand (und Lesbarkeit) bei 256+16 
Durchläufen rechtfertigt.

von Thomas O. (tom-tom92)


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.

: Bearbeitet durch User
von Michael S. (Gast)


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.

von Thomas O. (tom-tom92)


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.

von c-hater (Gast)


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...

von Michael S. (Gast)


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.

von Michael S. (Gast)


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.

von Norbert (Gast)


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.

von Michael S. (Gast)


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.

von Norbert (Gast)


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)

von Veit D. (devil-elec)


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.

von Norbert (Gast)


Lesenswert?

Veit D. schrieb:
> Du möchtest einem Quarz Konkurrenz machen?

NEIN, SELBSTVERSTÄNDLICH NICHT.

Die Berechnung sollte zeigen das die Auflösung des PIO-Systems weit 
größer ist, als sinnvoll für die Quarze.

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.