Der einfachste Weg die PIO des PiPico zu benutzen, scheint Micropython
zu sein:
https://blues.com/blog/raspberry-pi-pico-pio/
Im Beispiel wundert mich die hohe Taktfrequenz von 2kHz, die dann ein
sichtbares blinken ergibt.
Christoph M. schrieb:> Im Beispiel wundert mich die hohe Taktfrequenz von 2kHz, die dann ein> sichtbares blinken ergibt.
Die CPU Frequenz kann nur durch maximal 2¹⁶ für die SMs dividiert
werden.
Ohne hinein geschaut zu haben, wird die sich ergebende Blinkfrequenz auf
delays ›[n]‹ für 0<=n<=31 in PIOASM zurück zu führen sein.
>Ohne hinein geschaut zu haben, wird die sich ergebende Blinkfrequenz auf>delays ›[n]‹ für 0<=n<=31 in PIOASM zurück zu führen sein.
Was mich an dem Programm etwas wundert, sind die nop(). Die gibt es bei
den PIO-Befehlen nicht.
Gibt es einen Grund dafür, dass man in diesem Tone-Beispiel
https://wokwi.com/projects/312236827469677121
die PIO-Frequenz auf 1MHz stellt? Eigentlich müsste es ja besser sein,
die Frequenz auf Maximum zu stellen, weil dann die Quantisierung der
Frequenzen am kleinsten ausfällt. Ich könnte mir nur vorstellen, dass
man bei 1MHz etwas Strom spart.
Christoph M. schrieb:> Gibt es einen Grund dafür, dass man in diesem Tone-Beispiel> https://wokwi.com/projects/312236827469677121>> die PIO-Frequenz auf 1MHz stellt? Eigentlich müsste es ja besser sein,> die Frequenz auf Maximum zu stellen, weil dann die Quantisierung der> Frequenzen am kleinsten ausfällt. Ich könnte mir nur vorstellen, dass> man bei 1MHz etwas Strom spart.
Nö, der Rest läuft ja volle Pulle. Man kann die halbe Periode ja auf
1µs einstellen.
Das Beispiel hat aber durchaus Arduino Qualität.
* Buzzer direkt an die GPIOs
* Ein wildes Durcheinander von import … und from … import … Statements
* Bei der gewünschten Frequenz muss man wohl die Hälfte ins FIFO
schütten
* Selbst dann passt sie nicht, da der Overhead nicht berücksichtigt
wird.
Trotzdem gut geeignet als abschreckendes Beispiel. ;-)
Norbert (der_norbert)
>* Bei der gewünschten Frequenz muss man wohl die Hälfte ins FIFO>schütten
Wieso das?
Da steht doch
1
pull(noblock)
2
mov(x, osr)
d.h. da wird nicht auf einen vollen FIFO gewartet sondern der alte Wert
aus dem OSR koppiert, wenn keine Werte im FIFO sind. Man kann die
Frequenz also asynchron setzten, wie es am Schluss gemacht wird:
1
play(500)
2
time.sleep(1)
3
play(1000)
4
time.sleep(1)
5
play(0)
Siehe Datenblatt
1
3.4.7.2. Operation
2
3
A nonblocking PULL on an empty FIFO has the same effect as MOV OSR, X. The program can either preload scratch register X with a suitable default, or execute a MOV X, OSR after each PULL NOBLOCK, so that the last valid FIFO word will be recycled
Christoph M. schrieb:> Norbert (der_norbert)>>* Bei der gewünschten Frequenz muss man wohl die Hälfte ins FIFO>>schütten>> Wieso das?
Weil man sonst etwa die halbe Frequenz erhält.
Der Ersteller des Scripts hat wohl nie geprüft ob die am GPIO
ausgegebene Frequenz der gewünschten entspricht.
Obwohl, bei einem Piezo-Plättchen, könnte es sich schon nach der
gewünschten Frequenz anhören.
Stefan K. (stk)
17.10.2025 08:37
>Weil man sonst etwa die halbe Frequenz erhält.
Da hast du recht.
Das lässt sich allerdings auch leicht korrigieren.
Mir ging es eher um die Frequenzauflösung.
Wenn die State-Machine mit 1MHz läuft, erhält man für 440HZ
Stefan K. schrieb:> Christoph M. schrieb:>> Norbert (der_norbert)>>>* Bei der gewünschten Frequenz muss man wohl die Hälfte ins FIFO>>>schütten>>>> Wieso das?>> Weil man sonst die halbe Frequenz erhält.> Der Ersteller des Scripts hat wohl nie geprüft ob die am GPIO> ausgegebene Frequenz der gewünschten entspricht.> Obwohl, bei einem Piezo-Plättchen, könnte es sich schon nach der> gewünschten Frequenz anhören.
Stefan hat's schon richtig beschrieben.
Für eine volle Periode braucht's zwei Polaritätswechsel. Hier findet
pro PIO Rundlauf nur einer statt, ergo halbe Frequenz.
Richtig wäre es, wenn man die PIO Frequenz auf 2MHz setzen würde, und
beim sm.put() den erheblichen Offset der restlichen PIOASM Befehle
subtrahieren würde.
Aber selbst dann wäre es falsch, denn der y_jmp loop braucht eine ›0‹ im
Register für einen Durchlauf, oder ›n-1‹ für ›n‹ Durchläufe.
Aber das Gute ist, zumindest einige Programmzeilen sind fehlerfrei. ;-)
add1: Und der ›Simulator‹ ist fehlerhaft!
Christoph M. schrieb:> Mir ging es eher um die Frequenzauflösung.
PIO mit vollem CPU Takt laufen lassen.
Alles andere ergibt keinen Sinn.
Im ›put‹ keine ›//‹ Division mit inhärentem round down, sondern:
sm.put(round(machine.freq()/freq/2) - offset)
Die Play Funktion ist ebenfalls fehlerhaft, sie stoppt den Ton bei ›0‹
nicht, sondern erzeugt (bei ›0‹) die Periode ca 2³²/F_CPU und bestromt
den Ausgang für längere Zeit kontinuierlich.
Norbert schrieb:
>Die Play Funktion ist ebenfalls fehlerhaft, sie stoppt den Ton bei ›0‹>nicht, sondern erzeugt (bei ›0‹) die Periode ca 2³²/F_CPU und bestromt>den Ausgang für längere Zeit kontinuierlich.
Da hast du Recht, das konnte ich auf dem Oszilloskop beobachten. Ich
vermute, man kann den PiPico direkt in einen UKW-Sender verwandeln,
einfach in dem man eine Antenne an den Pin hängt.
Das nächste Experiment ist das Zählen von Pulsen.
Die State-Machine in diesem Beispiel ist recht einfach, allerdings
erfolgt die Abfrage des Zählerstandes als "Code Injection" und braucht
daher extra Zeit. Die Frage ist, ob hier Zählerwerte verloren gehen
könnten. Ich vermute eher nicht, wenn die zu zählende Frequenz deutlich
kleiner als die Loopfrequenz der Zustandsmaschine ist.
Das Ziel ist, den Zähler an den Frequenzgenerator von vorher
anzuschließen. Am einfachsten erscheint es mir, einfach den gleichen Pin
zu verwenden und die beiten Zustandsmaschinen gleichzeitig laufen zu
lassen.
1
import rp2
2
from machine import Pin
3
import time
4
5
# PIO program to count pulses on an input pin
6
@rp2.asm_pio()
7
def pulse_counter():
8
label("loop")
9
wait(0, pin, 0) # Wait for pin to go low
10
wait(1, pin, 0) # Wait for pin to go high (rising edge)
11
jmp(x_dec, "loop") # Decrement x and jump to loop (counts pulses)
Christoph M. schrieb:> Norbert schrieb:>>Die Play Funktion ist ebenfalls fehlerhaft, sie stoppt den Ton bei ›0‹>>nicht, sondern erzeugt (bei ›0‹) die Periode ca 2³²/F_CPU und bestromt>>den Ausgang für längere Zeit kontinuierlich.>> Da hast du Recht, das konnte ich auf dem Oszilloskop beobachten. Ich> vermute, man kann den PiPico direkt in einen UKW-Sender verwandeln,> einfach in dem man eine Antenne an den Pin hängt.
Ich würde es zwar so wohl nicht machen, aber angelehnt an das Beispiel…
(Falls ich mich nicht verzählt habe ;-) )
1
#!/python
2
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
3
frommachineimportfreqascpufreq
4
fromrp2importPIO,StateMachine,asm_pio
5
fromtimeimportsleep_ms
6
7
@rp2.asm_pio(sideset_init=PIO.OUT_LOW)
8
deftoggle():
9
wrap_target()
10
pull(noblock)# 1 (neue Daten)OSR oder X──▶OSR
11
mov (x,osr)# 1
12
mov (y,x).side(1)# 1
13
label("loop1")
14
jmp (y_dec,"loop1")# n+1
15
mov (y,x).side(0)[2]# 3 symmetrisch machen
16
label("loop2")
17
jmp (y_dec,"loop2")# n+1
18
wrap()
19
20
defplay(freq):
21
iffreq:
22
# Bei höchster Frequenz nicht negativ werden lassen.
23
sm.put(max(0,round(cpufreq()/freq/2)-4))
24
sm.restart()# Falls ohne warten bei extrem niedriger Frequenz gewünscht
25
sm.active(1)
26
else:
27
sm.active(0)# Sonst beträgt (bei ›0‹ die Periode ca. 2³¹/F_SM
28
sm.exec('nop().side(0)')# Ausgang auf spezifizierten Pegel bringen
29
30
sm=rp2.StateMachine(0,toggle,sideset_base=25)# LED zum Test (Pin() ist hier unnötig)
Christoph M. schrieb:>>Ich würde es zwar so wohl nicht machen, aber angelehnt an das> Beispiel…>> Welchen Tipp hättest du, es besser zu machen?
Kleine Tabelle (2^n Größe) mit gewünschter ›Waveform‹ füllen. Per DMA
mit Ringsize ›n‹ entweder einen der >=acht PWM slices oder eine der
>=acht StateMachines mit variablem duty-cycle ansteuern. Klingt
angenehmer. Aber für ein Rechteck mit duty:50% geht's so auch.
Nebenbei: In meinem offensichtlich zu hastig veröffentlichen Code haben
sich zwei copy'n'paste Artefakte versteckt. Zeilen 7,30. Die ›rp2.‹
können natürlich weg.
> In meinem offensichtlich zu hastig veröffentlichen Code haben> sich zwei copy'n'paste Artefakte versteckt. Zeilen 7,30. Die ›rp2.‹> können natürlich weg.
Da ist mir nicht klar, was du meinst.
Da ich ein Rechtecksignal brauche, habe ich das Programm mal korrigiert.
Ich denke, die Zyklenkorrektur muss 6 betragen:
1
#!/python
2
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
3
4
importrp2
5
frommachineimportPin
6
fromrp2importPIO
7
importtime
8
frommachineimportfreqascpufreq
9
10
# Define a PIO assembly program that toggles the output pin at a controllable rate
11
@rp2.asm_pio(out_init=[PIO.OUT_LOW])
12
defecho():
13
wrap_target()# define the start of the main loop of the PIO program.
14
mov(pins,isr)# [1] Output current ISR value to the pin (initially LOW)
15
mov(isr,invert(isr))# [1] Toggle ISR between 0 and 1 for pin toggling
16
pull(noblock)# [1] Try to pull new data from FIFO (non-blocking)
17
mov(x,osr)# [1] Store new delay count (or reuse old one if no new data)
18
mov(y,x)# [1] Copy to Y register for countdown
19
label("loop")
20
jmp(y_dec,"loop")# [1] Decrement Y until 0 – creates delay
21
wrap()# Loop back to start
22
23
# Create a state machine (0) using the PIO program, output on GPIO16
24
sm=rp2.StateMachine(0,echo,out_base=Pin(16))
25
26
# Activate the state machine (start running)
27
sm.active(1)
28
29
# Function to set the output frequency (tone)
30
defplay(freq):
31
iffreq:
32
# Compute the delay count from CPU frequency and tone frequency
33
# cpufreq()/freq/2 is half the period in CPU cycles
34
sm.put(max(0,round(cpufreq()/freq/2)-6))# Send delay count to PIO program
35
else:
36
sm.put(0)# Send 0 to stop toggling
37
sm.active(0)# Deactivate the state machine (silence output)
Christoph M. schrieb:> Da ist mir nicht klar, was du meinst.
Nach Bereinigung der imports hatte sich dort mittels copy'n'paste error
Folgendes verewigt:
1
@rp2.asm_pio(sideset_init=PIO.OUT_LOW)
2
sm = rp2.StateMachine(0, toggle, sideset_base=25)
Da ich aber bereits selektiert importiert hatte:
1
from rp2 import PIO, StateMachine, asm_pio
sollten die Zeilen in:
1
@asm_pio(sideset_init=PIO.OUT_LOW)
2
sm = StateMachine(0, toggle, sideset_base=25)
also ohne ›rp2.‹ davor abgeändert werden. Es wird nur deshalb keine
Exception geworfen, da µPy insgeheim beim Start schon ›machine‹ und
›rp2‹ automagisch importiert.
Da bei deiner gezeigten Version noch mehrere Varianten importiert
werden,
also Autoimport rp2, manueller import rp2, selektierter import PIO,usw.
isses jedoch egal, welche der Arten man benutzt.
Sechs sieht mir auf Anhieb ganz gut aus.
Aber bedenke, nach einem play(0) ist das Ding ohne erneutes sm.active(1)
tot wie ein Papagei.
Und der erste sm.active(1) nach StateMachine erzeugt etwas
unkontrolliertes/uninitialisiertes. Erst der erstmalige play() Aufruf
setzt den Schleifenzähler korrekt.
Noch eins: Wenn der play(0) im ungünstigen Zeitpunkt kommt, dann kann
der Ausgang mit einer 50:50 Chance auf High bleiben, was man
möglicherweise nicht wirklich will.
Christoph M. schrieb:> Da ich ein Rechtecksignal brauche, habe ich das Programm mal korrigiert.> Ich denke, die Zyklenkorrektur muss 6 betragen:
So wie das Programm jetzt aussieht passt 6.
Das sieht man mit einem Oszilloskop oder Frequenzzähler sofort, wenn man
bei play() einen so hohen Wert eingibt, dass sm.put(0) aufgerufen wird.
Bei 125MHz CPU Takt erhält man damit etwa 10,42MHz am GPIO, also
125MHz/12.
Wenn man die beiden Zeilen
mov(pins, isr)
mov(isr, invert(isr))
durch die eine Zeile
mov(pins, invert(pins))
ersetzt kommt man auf max. 12,5MHz am GPIO.
>also ohne ›rp2.‹ davor abgeändert werden. Es wird nur deshalb keine>Exception geworfen, da µPy insgeheim beim Start schon ›machine‹ und>›rp2‹ automagisch importiert.
Ah, danke.
Stefan K. (stk)
18.10.2025 10:59
>So wie das Programm jetzt aussieht passt 6.>Das sieht man mit einem Oszilloskop oder Frequenzzähler sofort, wenn man>bei play() einen so hohen Wert eingibt, dass sm.put(0) aufgerufen wird.>Bei 125MHz CPU Takt erhält man damit etwa 10,42MHz am GPIO
Das ist eine gute Idee. Ich hatte das Signal mit einem Frequenzzähler
bei 1kHz gemessen und es sah erst mal gut aus. Aber an die Grenze zu
gehen, ist natürlich besser.
Interessant, dass man nur bis ca. 12MHz bei SysClk=150Mhz kommt. Darüber
hatte ich gar nicht nachgedacht, weil mich eher die Genauigkeit der
Frequenzen im Audiobereich interessiert haben.
Aber für das weiter oben angedachte Experiment als UKW Sender währen
natürlich 108MHz interessanter. Ob man wohl die PLL in Mikropython
schnelle genug für die FM-Modulation umkonfigurieren könnte?
Christoph M. schrieb:> Aber für das weiter oben angedachte Experiment als UKW Sender währen> natürlich 108MHz interessanter.
Da wäre die Luftfahrt bestimmt begeistert und außer Rand und Band.
Christoph M. schrieb:> Interessant, dass man nur bis ca. 12MHz bei SysClk=150Mhz kommt.
18.75MHz mit dem weiter oben gezeigten PIO Programm.
Ist aber komplett sinnbefreit, da die Abstufung astronomisch groß wird.
Die maximal mögliche erzeugbare Frequenz beträgt ½·SM_F.
sideset(1)
sideset(0)
in wrap() gewickelt.
Norbert schrieb:> Christoph M. schrieb:>> Aber für das weiter oben angedachte Experiment als UKW Sender währen>> natürlich 108MHz interessanter.>> Da wäre die Luftfahrt bestimmt begeistert und außer Rand und Band.>> Christoph M. schrieb:>> Interessant, dass man nur bis ca. 12MHz bei SysClk=150Mhz kommt.>> 18.75MHz mit dem weiter oben gezeigten PIO Programm.> Ist aber komplett sinnbefreit, da die Abstufung astronomisch groß wird.> Die maximal mögliche erzeugbare Frequenz beträgt ½·SM_F.> sideset(1)> sideset(0)> in wrap() gewickelt.
Das ist so nicht ganz richtig. Das ist, was man maximal als
Rechteck-Grundwelle erzeugen kann.
Mit klugen Tricks ist es aber sehr wohl möglich, basierend darauf ein
(wenn auch ziemlich schmutziges) UKW-FM-Signal zu erzeugen. Man nutzt
halt aus, dass Rechtecke sehr viele Oberwellen enthalten.
>Das ist so nicht ganz richtig. Das ist, was man maximal als>Rechteck-Grundwelle erzeugen kann.
Es müsste mehr gehen. Einfach die PIO nur den Pin togglen lassen und die
PIO mit Systemtakt laufen lassen. Dann den Systemtakt auf 216MHz.
Christoph M. schrieb:>>Das ist so nicht ganz richtig. Das ist, was man maximal als>>Rechteck-Grundwelle erzeugen kann.>> Es müsste mehr gehen. Einfach die PIO nur den Pin togglen lassen und die> PIO mit Systemtakt laufen lassen. Dann den Systemtakt auf 216MHz.
Leute! Jetzt aber mal STOPP! Ihr bewegt euch im Bereich der
Flugnavigation. Muss das sein? Die Russen hauen schon in weiten Arealen
die GNSS Navigation kaputt.