Forum: Mikrocontroller und Digitale Elektronik PiPico PIO programmieren


von Christoph M. (mchris)


Lesenswert?

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.

von Norbert (der_norbert)


Lesenswert?

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.

von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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

von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> Was mich an dem Programm etwas wundert, sind die nop(). Die gibt es bei
> den PIO-Befehlen nicht.
1
3.3.7. Pseudoinstructions
2
Currently pioasm provides one pseudoinstruction, as a convenience:
3
nop
4
Assembles to mov y, y . "No operation", has no particular side effect, but a useful vehicle for a side-set
5
operation or an extra delay.

von Christoph M. (mchris)


Lesenswert?

>3.3.7. Pseudoinstructions
Hab ich glatt überlesen. Danke.

von Christoph M. (mchris)


Lesenswert?

Gibt es die PIO eigentlich als VHDL-Code?

edit: suchen hilft
https://github.com/lawrie/fpga_pio

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

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.

von Norbert (der_norbert)


Lesenswert?

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

von Christoph M. (mchris)


Lesenswert?

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
4
until new data is available.

Mit
1
for n in range(1,500):
2
    play(n*5)
3
    time.sleep(0.01)
4
play(0)

ergibt sich dann ein asynchron gesetzter Chirp.

von Stefan K. (stk)


Lesenswert?

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.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

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
1
 1e6/double((int32(1e6/440)))
2
ans = 439.95

Wenn sie mit 10MHz läuft wird das schon genauer:
1
10e6/double((int32(10e6/440)))
2
ans = 440.01

von Norbert (der_norbert)


Lesenswert?

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!

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

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)
12
13
class PulseCounter:
14
    def __init__(self, sm_id, pin):
15
        self.sm = rp2.StateMachine(sm_id, pulse_counter, in_base=pin)
16
        self.sm.put(0)           # Initialize pulse count to 0
17
        self.sm.exec("pull()")
18
        self.sm.exec("mov(x, osr)")
19
        self.sm.active(1)        # Start the state machine
20
21
    def get_pulse_count(self):
22
        self.sm.exec("mov(isr, x)")  # Move current count into ISR
23
        self.sm.exec("push()")       # Push ISR to FIFO
24
        count = self.sm.get()        # Read the count
25
        # Convert count from decrementing format to positive value
26
        return -count & 0x7fffffff
27
    
28
    def clear_count(self):
29
        self.sm.exec("set(x, 0)")  
30
        
31
32
# Read pulse count from pin 16
33
34
pin16 = Pin(16, Pin.IN, Pin.PULL_UP)
35
pulse_counter = PulseCounter(0, pin16)
36
37
for n in range(1,10):
38
    print("Pulse count:", pulse_counter.get_pulse_count())
39
    time.sleep(0.5)

von Norbert (der_norbert)


Lesenswert?

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
from machine import freq as cpufreq
4
from rp2 import PIO, StateMachine, asm_pio
5
from time import sleep_ms
6
7
@rp2.asm_pio(sideset_init=PIO.OUT_LOW)
8
def toggle():
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
def play(freq):
21
    if freq:
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)
31
play(20)
32
sleep_ms(1000)
33
play(5)
34
sleep_ms(1000)
35
play(0)

von Joachim B. (jar)


Lesenswert?

Christoph M. schrieb:
> Ich
> vermute, man kann den PiPico direkt in einen UKW-Sender verwandeln

das konnte schon der erste Raspi, warum soll das auf einem PiPico anders 
sein?
https://tutorials-raspberrypi.de/raspberry-pi-als-radio-sendestation-verwenden/

von Christoph M. (mchris)


Lesenswert?

>Ich würde es zwar so wohl nicht machen, aber angelehnt an das Beispiel…

Welchen Tipp hättest du, es besser zu machen?

von Norbert (der_norbert)


Lesenswert?

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.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

> 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
import rp2
5
from machine import Pin
6
from rp2 import PIO
7
import time
8
from machine import freq as cpufreq
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
def echo():
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
def play(freq):
31
    if freq:
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)
38
39
40
play(1000)
41
#time.sleep(5)
42
#play(0)                    # Stop output after sweep completes

von Norbert (der_norbert)


Lesenswert?

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.

: Bearbeitet durch User
von Stefan K. (stk)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

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

von Norbert (der_norbert)


Lesenswert?

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.

: Bearbeitet durch User
von Ob S. (Firma: 1984now) (observer)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

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

von Norbert (der_norbert)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

Norbert schrieb
>Leute! Jetzt aber mal STOPP!

Bist du Amateurfunker? Wie viel Leistung kann den so ein Raspi-Pin 
abstrahlen?

von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> Bist du Amateurfunker? Wie viel Leistung kann den so ein Raspi-Pin
> abstrahlen?

Nein, bin ich nicht. Ich weiß aber auch nicht was da hinten noch alles 
dran hängt.
Und die Flugfunknavigation, Localizer, Glidepath, VOR, … stören, auch 
wenn nur möglicherweise, da ist meine Toleranzschwelle noch nicht einmal 
ein Epsilon über 0.
Das mag damit zusammen hängen, dass man sich da oben recht gerne gerade 
in IMC auf die Geräte verlassen und nicht zu einer Statistik werden 
möchte.

von Christoph M. (mchris)


Lesenswert?

Ich versuche, die Länge eines Pulses zu messen, aber es sieht so aus, 
als wenn die PIO das Eingangssignal ignoriert. Nach ca.32 Sekunden läuft 
der Zähler über, dann kommt jeweils ein Wert.
Eigentlich sollte das Programm ganz einfach funktionieren: Auf eine 1/0 
Flanke an PIN16 warten und dann solange zählen, bis der Pegel wieder auf 
1 springt. Es will aber einfach nicht funktionieren. Weis jemand, warum?
1
#!/python
2
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
3
4
import rp2
5
from machine import Pin
6
import time
7
8
######### PROGRAMm funktioniert nicht !!! ####################
9
10
@rp2.asm_pio()
11
def pulse_counter():
12
    label("start")
13
    mov(isr,x)
14
    push()
15
    wait(1, pin, 0)   
16
    wait(0, pin, 0)    
17
    
18
    label("loop")  
19
    jmp(pin,"start")
20
    jmp(x_dec, "loop")
21
    push()
22
    
23
class PulseMeasure:
24
    def __init__(self, sm_id, pin):
25
        self.sm = rp2.StateMachine(sm_id, pulse_counter, in_base=pin)
26
        self.sm.put(0x7FFFFFFF)      # Large initial count
27
        self.sm.exec("mov(x, osr)")
28
29
        self.sm.active(1)        # Start the state machine
30
31
    def get_count(self):
32
        #self.sm.exec("mov(isr, x)")  # Move current count into ISR
33
        #self.sm.exec("push()")       # Push ISR to FIFO
34
        count = self.sm.get()        # Read the count
35
36
        return count #-count & 0x7fffffff
37
38
    def clear_count(self):
39
        self.sm.exec("set(x, 0)")  
40
41
# Read pulse count from pin 16
42
pin16 = Pin(16, Pin.IN, Pin.PULL_UP)
43
device = PulseMeasure(0, pin16)
44
45
for n in range(1,10):
46
    print("Pulse count:", device.get_count())
47
    time.sleep(0.5)

von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> self.sm = rp2.StateMachine(sm_id, pulse_counter, in_base=pin)

Wenn du mit PIO-JMP arbeitest, dann auch in der StateMachine 
Instanziierung darauf hinweisen.
1
#!python
2
self.sm = rp2.StateMachine(sm_id, pulse_counter, in_base=pin, jmp_pin=pin)

von Christoph M. (mchris)


Lesenswert?

>Wenn du mit PIO-JMP arbeitest, dann auch in der StateMachine
>Instanziierung darauf hinweisen.

Danke für den Hinweis. Ohne diesen Hinweis hätte ich wahrscheinlich noch 
eine Woche gesucht ..

Hier mal das aktuelle Programm. Der PIO Assembler ist ziemlich 
gewöhnungsbedürftig.
1
#!/python
2
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
3
4
import rp2
5
from machine import Pin
6
import time
7
from machine import freq as cpufreq
8
9
# Frequency Measurement
10
# 1. wait for falling edge
11
# 2. measure low pulse cycles
12
# 3. calculate frequency
13
14
@rp2.asm_pio()
15
def lowPulseCount():
16
    #### START ###
17
    label("store_result")
18
    mov(isr,x)          # move counter to ISR
19
    push()              # store ISR (result) in FIFO
20
    set(x,0)            # clear counter
21
    jmp(x_dec,"skip")   # preset counter to 0xffffffff 
22
    label("skip")       # to avoid overflow at the end of this programm
23
    # wait for falling edge
24
    wait(1, pin, 0)   
25
    wait(0, pin, 0)
26
    #### LOOP ####
27
    label("loop")  
28
    jmp(pin,"store_result")    # [1] leave on rising edge
29
    jmp(x_dec, "loop")         # [1] dec x
30
    jmp("loop")                # overflow .. should not be needed
31
    
32
    
33
class PulseMeasure:
34
    def __init__(self, sm_id, pin):
35
        self.sm = rp2.StateMachine(sm_id, lowPulseCount, in_base=pin, jmp_pin=pin)
36
        self.sm.active(1)        # Start the state machine
37
38
    def get_freq(self):
39
        count = self.sm.get()        # Read count from FIFO
40
        cyclesPerLoop=2
41
        sigLowCycles=(0xffffffff-(count & 0xffffffff))*cyclesPerLoop
42
        if sigLowCycles!=0:
43
            f=cpufreq()/(2*sigLowCycles)
44
        else:
45
            f=None        
46
        return f
47
    
48
    def end(self):
49
        self.sm.active(0) 
50
51
# Read pulse count from pin 16
52
pin16 = Pin(16, Pin.IN, Pin.PULL_UP)
53
device = PulseMeasure(0, pin16)
54
55
print("cpufreq: ",cpufreq())
56
for n in range(1,10):
57
    print( "f=",device.get_freq(),"Hz")
58
    time.sleep(0.1)
59
60
device.end()

Man kann ja z.b. keine Konstanten >31 direkt ins X-Register laden. 
Außerdem scheint der Jump über den Testpin nur auf High-Werte zu 
funktionieren. Aus diesem Grunde habe ich nur die Low-Periode gemessen 
und dann einfach x2 genommen. Das Ergebnis kann man nicht unbedingt als 
genau bezeichnen. Hier mal für 1kHz:
1
>>> %Run -c $EDITOR_CONTENT
2
cpufreq:  125000000
3
f= 1000.032 Hz
4
f= 1000.032 Hz
5
f= 1000.032 Hz
6
f= 1000.032 Hz
7
f= 1000.032 Hz
8
f= 1000.032 Hz
9
f= 1000.032 Hz
10
f= 1000.032 Hz
11
f= 1000.032 Hz

Ich vermute, es liegt an der unscharfen Flanke, weil ich den 
Funktionsgenerator mit 3.3kOhm angeschlossen habe.

von Norbert (der_norbert)


Lesenswert?

Von:
1
set(x,0)            # clear counter
2
jmp(x_dec,"skip")   # preset counter to 0xffffffff 
3
label("skip")       # to avoid overflow at the end of this programm
In:
1
#!python
2
mov (x, invert(null)) # ones-complement
Von:
1
mov(isr,x)          # move counter to ISR
2
push()              # store ISR (result) in FIFO
In:
1
#!python
2
mov (isr, invert(x))# revert ones-complement
3
push()              # store ISR (result) in FIFO

Da sparst du Speicher und den späteren Tanz mit 2³².
Der Rest ist Zyklen zählen.

Christoph M. schrieb:
> Ich vermute, es liegt an der unscharfen Flanke, weil ich den
> Funktionsgenerator mit 3.3kOhm angeschlossen habe.

Jaaaa, das ist so'n Ding mit den Vermutungen… ;-)

Wenn du es zunächst erst einmal Zyklen-genau bauen willst, dann 
generiere dir den präzisen Takt auf dem Ding selbst.

von Hugo H. (hugo_hu)


Lesenswert?

Norbert schrieb:
> Die Russen hauen schon in weiten Arealen
> die GNSS Navigation kaputt.

Ja, die Kloschüsseln auch! (vermutlich :-) )

von Norbert (der_norbert)


Lesenswert?

Hugo H. schrieb:
> Ja, die Kloschüsseln auch! (vermutlich :-) )

Realitätsverweigerer oder Doppelkorn-Liebhaber?

von Hugo H. (hugo_hu)


Lesenswert?

Norbert schrieb:
> Hugo H. schrieb:
>> Ja, die Kloschüsseln auch! (vermutlich :-) )
>
> Realitätsverweigerer oder Doppelkorn-Liebhaber?

Weder noch - Mensch mit Verstand :-)

von Norbert (der_norbert)


Lesenswert?

Hugo H. schrieb:
> Norbert schrieb:
>> Hugo H. schrieb:
>>> Ja, die Kloschüsseln auch! (vermutlich :-) )
>>
>> Realitätsverweigerer oder Doppelkorn-Liebhaber?
>
> Weder noch - Mensch mit Verstand :-)

Möglich … Unwahrscheinlich.
Verstand würde implizieren, dass man sich mit der momentanen Situation 
um den größeren Bereich ›baltic sea‹ vertraut macht und Informationen 
diesbezüglich sammelt. Und da wir uns über Aviation unterhielten, wäre 
das wohl der maßgebende Bereich. Mit ein wenig Glück fände derjenige 
sogar eine oder mehrere offizielle Webseiten, auf denen dies ständig 
dokumentiert wird.

ad1: Ich mach's noch leichter. Einfach mal ›EASA GPS outage‹ versuchen.

: Bearbeitet durch User
von Hugo H. (hugo_hu)


Lesenswert?

Norbert schrieb:
> offizielle Webseiten

Wird da auch über den Osterhasen berichtet? Vermutlich ... :-)

von Norbert (der_norbert)


Lesenswert?

Hugo H. schrieb:
> Wird da auch über den Osterhasen berichtet? Vermutlich ... :-)

Hugo, ich finde es interessant, dass es noch Menschen gibt die sich 
selbst so ungeniert in der Öffentlichkeit zum Affen machen.

von Christoph M. (mchris)


Lesenswert?

Könnt ihr die Diskussion im "offtopic" weiter führen?

von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> Könnt ihr die Diskussion im "offtopic" weiter führen?

Ist schon an die Administration gemeldet.

von Christoph M. (mchris)


Lesenswert?

Norber schrieb:

>Da sparst du Speicher und den späteren Tanz mit 2³².
>Der Rest ist Zyklen zählen.

Super Tipps. Die nehme ich in meine "Knowledge Base" a.k. Hirn auf.

von Christoph M. (mchris)


Lesenswert?

So, jetzt sieht's deutlich besser aus, ich bin begeistert :-)
1
>>> %Run -c $EDITOR_CONTENT
2
cpufreq:  125000000
3
f= 1000.0 Hz
4
f= 1000.0 Hz
5
f= 1000.0 Hz
6
f= 1000.0 Hz
7
f= 1000.0 Hz
8
f= 1000.0 Hz
9
f= 1000.0 Hz
10
f= 1000.0 Hz
11
f= 1000.0 Hz

Ein wenig von Mi N.(msx) inspiriert:
1
#!/python
2
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
3
4
import rp2
5
from machine import Pin
6
import time
7
from machine import freq as cpufreq
8
9
# Frequency Measurement
10
# 1. wait for rising edge
11
# 2. count high pulse cycles
12
# 3. count high pulse cycles
13
# 4. calculate frequency
14
15
# inspired from
16
# Mi N. (msx)
17
# 08.10.2025 10:21
18
# https://www.mikrocontroller.net/topic/580836#7948280
19
20
@rp2.asm_pio()
21
def lowPulseCount():
22
    #### START ###
23
    label("store_result")
24
25
    mov(isr, x)                # store result
26
    push()
27
28
    mov (x, invert(null)) # ones-complement
29
30
    # wait for rising edge
31
    wait(0, pin, 0)   
32
    wait(1, pin, 0)            
33
    
34
    #### count high period ####
35
    label("isOneLoop")
36
    
37
    jmp(x_dec, "xdec1") [1]    # [2] decrement x
38
    label("xdec1")
39
    
40
    jmp(pin, "isOneLoop")      # [1] wait for 0
41
    
42
    #### count low period #### 
43
    label("isZeroLoop")
44
    
45
    jmp(x_dec, "xdec")         # decrement x
46
    label("xdec")
47
    jmp(pin, "store_result")   # leave when 1
48
    
49
    jmp("isZeroLoop")          # 
50
    
51
    
52
class PulseMeasure:
53
    def __init__(self, sm_id, pin):
54
        self.sm = rp2.StateMachine(sm_id, lowPulseCount, in_base=pin, jmp_pin=pin)
55
        self.sm.active(1)        # Start the state machine
56
        self.old=0    
57
    
58
    def get_freq(self):
59
        count = self.sm.get()        # Read count from FIFO
60
        cyclesPerLoop=3
61
        correctionCycles=1
62
        sigCycles=(0xffffffff-(count & 0xffffffff))*cyclesPerLoop-correctionCycles
63
64
        if sigCycles!=0:
65
            f=cpufreq()/(sigCycles)
66
        else:
67
            f=None        
68
        #f=count
69
        return f
70
    
71
    def end(self):
72
        self.sm.active(0) 
73
74
# Read pulse count from pin 16
75
pin16 = Pin(16, Pin.IN, Pin.PULL_UP)
76
device = PulseMeasure(0, pin16)
77
78
print("cpufreq: ",cpufreq())
79
for n in range(1,10):
80
    print( "f=",device.get_freq(),"Hz")
81
    time.sleep(0.1)
82
83
device.end()

von Hugo H. (hugo_hu)


Lesenswert?

Christoph M. schrieb:
> Ich versuche, die Länge eines Pulses zu messen, aber es sieht so aus,
> als wenn die PIO das Eingangssignal ignoriert. Nach ca.32 Sekunden läuft
> der Zähler über, dann kommt jeweils ein Wert.

Mal etwas Themen-bezogenes. Du Kennst das "Erratum/Errata 9" für den PI 
Pico 2?

https://forums.raspberrypi.com/viewtopic.php?t=375954

Hat mir, trotz externer Pulldown-Beschaltung, sporadisch Fehler bei der 
Auswertung eines Eingangssignals beschert. Ich musste auf den PI Pico 
zurückgreifen. Eine neue Chip-Version des PI Pico 2 (A4) ist als Chip 
schon verfügbar - aber habe ich bisher nicht als Developement-board 
verfügbar gesehen.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

>Mal etwas Themen-bezogenes. Du Kennst das "Erratum/Errata 9" für den PI
>Pico 2?

Das ist ein guter Hinweis. Für die bisherigen Experimente habe ich einen 
RP20240 benutzt. Heute morgen habe ich mal den selben 
Micropython-Frequenzzähler wie oben ohne Änderung auf einem RP2350 
laufen lassen. Es hat auch funktioniert, nur die Frequenzauflösung war 
etwas besser, weil der RP2 etwas schneller läuft.

Wie man oben sieht, ist der Pin aber als Pull-Up konfiguriert. Auf den 
ersten Blick habe ich keine Ausreißer gesehen.

Durch die Codezeilen
1
    # wait for rising edge
2
    wait(0, pin, 0)   
3
    wait(1, pin, 0)

kann man nur jede zweite Welle messen. Ich mal versucht, diese 
Synchronisation weg zu lassen, aber dann wird die Messung ziemlich 
verrauscht. Das liegt aber wahrscheinlich am langsamen lesen des FIFOs 
durch die Python-Loop, die dann die PIO bremst.

Falls sich deine Beobachtung auch beim Konfiguration als Pull-Up 
bestätigen würde, müsst man man einen Langzeitversuch machen und die 
Daten über ein paar Minuten aufzeichnen.

von Norbert (der_norbert)


Lesenswert?

Wenn man den Pin (input) mit weniger als 8kΩ ansteuert, tritt dieser 
Fehler nicht auf. Selbst bei höchsten Frequenzen im 100MHz Bereich kann 
man noch, wie hier, einen 160Ω Widerstand in Serie zwischen input und 
output legen.

Habe das ganze Zeug sowohl auf RP2040 als auch auf RP2350 getestet und 
völlig problemlos laufen.

Der geschilderte Fehler tritt nur bei hochohmiger Ansteuerung auf, wenn 
weniger als ca. 100µA bei low Pegel aus dem input fließen können.

ad1:
Diese Ausgaben der Messungen hier kommen direkt aus dem RP2350:
1
⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_⋂_
2
100.000000 MHz   (5.0 ns, 5.0 ns)
3
4
⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__⋂__
5
66.666670 MHz   (5.0 ns, 10.0 ns)
6
7
⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_⋂⋂_
8
66.666670 MHz   (10.0 ns, 5.0 ns)
9
10
⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__⋂⋂__
11
50.000000 MHz   (10.0 ns, 10.0 ns)

: Bearbeitet durch User
von Hugo H. (hugo_hu)


Angehängte Dateien:

Lesenswert?

Christoph M. schrieb:
> Falls sich deine Beobachtung auch beim Konfiguration als Pull-Up
> bestätigen würde, müsst man man einen Langzeitversuch machen und die
> Daten über ein paar Minuten aufzeichnen.

Wäre vielleicht sinnvoll - aber ein Zurück auf den PI Pico tuts auch. 
Anbei ein Bild in welchem ich Taktsignale (gelb) auswerte - und bei 
Erkennen ausgebe (grün). Der Fehler ist sporadisch, aber im Kontext (ich 
werte Signale zur Display-Steuerung aus) mehr als störend.

von Norbert (der_norbert)


Lesenswert?

Hugo H. schrieb:
> Der Fehler ist sporadisch, aber im Kontext (ich
> werte Signale zur Display-Steuerung aus) mehr als störend.

In der Richtung wie in der Grafik gezeigt – low to high bleibt aus – 
ist der Fehler aber gar nicht existent.

Die Richtung low to high geht bei korrekter Ansteuerung immer und 
hat mit RP2350-E9 nichts zu tun.

Nur wenn der ›input‹ bereits latched high ist, müssen aus ihm die ca. 
100µA heraus fließen können um ihn wieder low zu bringen.

Das es mit dem RP2040 geht, liegt wohl eher daran, dass bei dessen 
interner Pad Konfiguration und der externen Beschaltung es so gerade 
noch geht.

von Christoph M. (mchris)


Lesenswert?

Micropython ist für das Programmieren der PIO unglaublich praktisch, 
weil der Syntax viel einfacher als für C ist.
Um die maximale Geschwindigkeit zu erreichen, ist aber C unvermeidlich.
Mich wundert dass noch keiner einen Konverter geschrieben hat, um 
Micropython PIO-Code in C umzuwandeln so wie es hier gebraucht wird:
https://wokwi.com/tools/pioasm

von Norbert (der_norbert)


Angehängte Dateien:

Lesenswert?

Christoph M. schrieb:
> Um die maximale Geschwindigkeit zu erreichen, ist aber C unvermeidlich.

Wenn man wirklich maximale Geschwindigkeit erreichen will, ist 
Assembler unvermeidlich. Braucht man aber fast nie, genauso wenig wie C.

Ich selbst habe in den letzten Jahren nur eine Handvoll C-module für 
µPy geschrieben. Die waren dann aber auch wirklich ziemlich extrem.
Alles andere kann man durch vernünftige Programmierung abfrühstücken. 
Wenn man sich eingehend mit dem System beschäftigt.

Nur – im Sinne von ausschließlich – mit µPy geschrieben:
* IRQs pro Sekunde irgendwo im hohen fünf, niedrigen sechs-stelligen 
Bereich.
* Monitoransteuerung inkl. Grafik- und Fensterroutinen.
* Signalerzeugung und Frequenzmessung bis in den drei-stelligen MHz 
Bereich.
* Logic-Analyser 16 Kanäle
* Audio-Signalerkennung und Auswertung

Ich such' mal ein oder zwei Videos heraus.

ad1: Direkt mit dem Clever-Telefon vom Eizo 22" Monitor
Bisschen komprimiert, bei der Originalgröße käme ich hier auf die 
Strafbank.

Die Datei heißt eigentlich µPy4.mp4, ist aber von der Foren-SW verändert 
worden. Abspielbar mit mpv,vlc,mplayer,smplayer

: Bearbeitet durch User
von Vanye R. (vanye_rijan)


Lesenswert?

> Wenn man wirklich maximale Geschwindigkeit erreichen will, ist
> Assembler unvermeidlich. Braucht man aber fast nie, genauso wenig wie C.

Du brauchst die hohe Geschwindigkeit zum Beispiel IMMER dann wenn du 
Energieprobleme hast. Es ist ein Unterschied ob deine Kiste 2Monate oder 
2Jahre mit einer Batterie laeuft. Oder auch wenn die Energie begrenzt 
ist wegen Solarzelle oder Feldbus. Oder einfach wenn du kommerziell 
unterwegs bist weil es bedeutet das du mit derselben (preislich) 
Hardware wie die Konkurrenz viel mehr Funktionalitaet hast.

Zum basteln zuhause ist es sicher oft egal! Aber sobald deine Ansprueche 
darueber hinaus gehen ist C/C++ im Embeddedbereich selbstverstaendlich.

Programmierung in Assembler dagegen ist in der Tat sehr exotisch/selten 
geworden. Es ist aber immer noch notwendig es zu koennen damit du bei 
komplexen Problemen auf Assemblerlevel debuggen kannst. Auch nicht jeden 
Tag, aber so 1x im Jahr kommt das vor. Das ist dann manchmal der Tag an 
dem du einen Bugreport an den Compilerhersteller schreiben kannst. Ich 
meine irgendeiner muss ja die Bugs finden oder? :-)

Vanye

von Norbert (der_norbert)


Lesenswert?

Vanye R. schrieb:
> Du brauchst die hohe Geschwindigkeit zum Beispiel IMMER dann wenn du
> Energieprobleme hast.

Wer Energieprobleme hat, nutzt ganz sicher keinen Pico (oder nur wenn's 
gar nicht anders geht). Die Dinger sind nun wirklich nicht für 
Batteriebetrieb ausgelegt.

Vanye R. schrieb:
> Programmierung in Assembler dagegen ist in der Tat sehr exotisch/selten
> geworden.

Wenn die ersten drei erlernten Sprachen Maschinencode/Assembler waren, 
dann empfindet man das als völlig natürlich. Natürlich schreibt man 
heute kein komplettes Programm in Assembler, nur die Routinen wo es 
massig Umpf braucht.
Hatte ich erwähnt, das µPy ganz nebenbei einen Assembler eingebaut hat? 
;-)

von Norbert (der_norbert)


Angehängte Dateien:

Lesenswert?

Und so ein Bild auf dem Monitor kann man mit µPy übrigens auch erzeugen.
Wenn man's richtig macht. Ohne Assembler und ohne C übrigens.
Da habe ich viel über Video-Formate gelernt.
Muss man allerdings – wie bei dem gezeigten Video auch – alles, wirklich 
alles, selbst programmieren. Nix Schublade auf und module importieren.

von Vanye R. (vanye_rijan)


Lesenswert?

> Wenn die ersten drei erlernten Sprachen Maschinencode/Assembler waren,
> dann empfindet man das als völlig natürlich.

Ich hab mal MCS48, MCS51, Z80, ST6, Saturn, ESRH, 6502 und 68k Assembler 
gelernt. Trotzdem wuerde ich nicht sagen das ich heute ARM oder Risc/V 
mal eben gut also sinnvoll in Assembler programmieren kann. Irgendwas 
hinschludern das geht, ja, aber programmieren ist was anderes! Wo man es 
genau drauf hat um wirklich noch besser wie ein Compiler zu sein. Das 
ist nicht mal eben so gemacht. Deshalb ist es ja auch weitestgehend 
ausgestorben.

Vanye

von Mi N. (msx)


Lesenswert?

Christoph M. schrieb:
> Micropython ist für das Programmieren der PIO unglaublich praktisch,
> weil der Syntax viel einfacher als für C ist.

Was ich bislang als Code so gesehen habe, scheint mir doch recht 
gewöhnungsbedürftig ;-)
Syntax hin oder her: wenn es schnell gehen soll, muß man genau wissen, 
was man eingibt, und da helfen ein Blatt Papier und eigene Kopfarbeit 
enorm - bei mir zumindest.

Die PIOs habe ich mir mit einer IAR-IDE in C programmiert. Damit kann 
man ganz elegant zur Laufzeit eine PIO starten und stoppen, einzelene 
Befehle von C aus ausführen lassen und beim Debuggen das PIO-Programm 
manuell verändern.
Für größere PIO-Programme könnte man per DMA auch die Befehle aus einer 
Tabelle ausführen lassen oder eine PIO zur Laufzeit umprogrammieren.
Aber gut, wohl etwas speziell das Ganze.

von Christoph M. (mchris)


Lesenswert?

Norbert (der_norbert)
>Wenn man wirklich maximale Geschwindigkeit erreichen will, ist
>Assembler unvermeidlich.

Das kann sein, ist aber ein wenig mühselig.

>Braucht man aber fast nie, genauso wenig wie C.

Wenn du mal ein Beispiel willst, bei dem Micropython scheitert:
Beitrag "6502 Emulator PiPico"

von Christoph M. (mchris)


Lesenswert?

Ich habe das Frequenzmessprogramm

Beitrag "Re: PiPico PIO programmieren"

mal in die C-Version verwandelt.
Wie zu erwarten, gestaltet sich die Inbetriebnahme schwierig. Es scheint 
kein Wert im FIFO anzukommen.

Hat jemand eine Idee?
1
#include "fmes.pio.h"           
2
#include <hardware/gpio.h>
3
#include <hardware/pio.h>
4
#include <stdio.h>
5
6
#define TESTPIN 16              
7
8
// function to translate
9
#if defined(ARDUINO_ARCH_MBED)
10
#include <pinDefinitions.h>
11
static int translate_pin(int pin) {
12
  return digitalPinToPinName(pin);
13
}
14
#else
15
static int translate_pin(int pin) {
16
  return pin;
17
}
18
#endif
19
20
static PIO pio;
21
static int sm;
22
23
static inline void fmes_program_init(PIO pio, uint sm, uint pin)
24
{
25
  pio_gpio_init(pio, pin);
26
27
  // Set pin direction: input
28
  pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
29
30
  pio_sm_config c = fmes_program_get_default_config(0);
31
  sm_config_set_in_pins(&c, pin); // for WAIT, IN
32
33
  // don't join FIFO's
34
  sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE);
35
36
  // always run at sysclk (125 MHz default)
37
  sm_config_set_clkdiv(&c, 1.0);
38
39
  pio_sm_init(pio, sm, 0, &c);
40
41
  // Use SDK function to enable state machine
42
  pio_sm_set_enabled(pio, sm, true);
43
}
44
45
void setup() {
46
  Serial.begin(115200);
47
  //while (!Serial.available());
48
  //Serial.println("start");
49
  while (!Serial) {
50
    delay(10);
51
  }
52
  Serial.println("start");
53
54
  gpio_init(TESTPIN);                    // Initialize the GPIO pin
55
  gpio_set_dir(TESTPIN, GPIO_IN);        // Set the TESTPIN as input (standard GPIO)
56
57
  // Assign pio to pio0
58
  pio = pio0;
59
60
  // Claim an unused state machine
61
  sm = pio_claim_unused_sm(pio, true);
62
63
  int gpio_pin = translate_pin(TESTPIN);
64
65
  pio_add_program(pio, &fmes_program);
66
67
  // Initialize PIO program on the claimed state machine
68
  fmes_program_init(pio, sm, gpio_pin);
69
70
  Serial.printf("Using PIO instance %p, state machine %d on pin %d\n", pio, sm, gpio_pin);
71
}
72
73
static inline int32_t pio_read(PIO pio, uint sm)
74
{
75
  int32_t value = -1;
76
  uint32_t ints;
77
78
  uint8_t count = pio_sm_get_rx_fifo_level(pio, sm);
79
80
  ints = save_and_disable_interrupts();
81
  if (count > 0)
82
    value = pio_sm_get_blocking(pio, sm);
83
  else value = -1;
84
  restore_interrupts(ints);
85
86
  return value;
87
}
88
89
void loop()
90
{
91
  Serial.print("click ");
92
  int32_t val = pio_read(pio, sm);
93
94
  Serial.println(val);
95
96
  delay(200);
97
}


Hier das zugehörige PIO-Programm:

1
// -------------------------------------------------- //
2
// This file is autogenerated by pioasm; do not edit! //
3
// -------------------------------------------------- //
4
5
#pragma once
6
7
#if !PICO_NO_HARDWARE
8
#include "hardware/pio.h"
9
#endif
10
11
// ---- //
12
// fmes //
13
// ---- //
14
15
#define fmes_wrap_target 0
16
#define fmes_wrap 9
17
18
static const uint16_t fmes_program_instructions[] = {
19
            //     .wrap_target
20
    0xa0c9, //  0: mov    isr, !x                    
21
    0x8020, //  1: push   block                      
22
    0xa02b, //  2: mov    x, !null                   
23
    0x2020, //  3: wait   0 pin, 0                   
24
    0x20a0, //  4: wait   1 pin, 0                   
25
    0x0046, //  5: jmp    x--, 6                     
26
    0x00c5, //  6: jmp    pin, 5                     
27
    0x0048, //  7: jmp    x--, 8                     
28
    0x00c0, //  8: jmp    pin, 0                     
29
    0x0007, //  9: jmp    7                          
30
            //     .wrap
31
};
32
33
#if !PICO_NO_HARDWARE
34
static const struct pio_program fmes_program = {
35
    .instructions = fmes_program_instructions,
36
    .length = 10,
37
    .origin = 0,
38
};
39
40
static inline pio_sm_config fmes_program_get_default_config(uint offset) {
41
    pio_sm_config c = pio_get_default_sm_config();
42
    sm_config_set_wrap(&c, offset + fmes_wrap_target, offset + fmes_wrap);
43
    return c;
44
}


#endif
[c]

von Norbert (der_norbert)


Lesenswert?

Du weißt aber schon, dass es im SDK einen pioasm gibt? ;-)
cmake richtig einbinden.
Das muss man nicht selbst machen…

Edit: Ups, sehe gerade: Arduino

: Bearbeitet durch User
von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

>Edit: Ups, sehe gerade: Arduino

Genau .. die zweit einfachste Variante nach Micropython.
Ich verwende das Early-Hill-Power Framework

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

weil es ohne mbed auskommt.

Wie man im Code sieht, kann man die SDK-API benutzen.
Ich habe den angehängten PIO-Code, der dem Micropython-Code entspricht 
mit dem Onlineassembler assemblieren lassen:
https://wokwi.com/tools/pioasm
Leider hängt es noch irgendwo ..

von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

Ok, jetzt funktioniert es.
Es war quasi der gleiche Fehler wie schon im anfänglichen 
Micropython-Programm: Es muss der Jump-Pin konfiguriert werden.

von M. G. (t1nk3r)


Lesenswert?

Christoph M. schrieb:
> Gibt es die PIO eigentlich als VHDL-Code?

Ein paar ganz kleine Sachen davon sind auch in 
https://github.com/Wren6991/libfpga

von Joachim B. (jar)


Lesenswert?

Christoph M. schrieb:
> Ich habe

nicht gelesen!

Wichtige Regeln - erst lesen, dann posten!
    Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

mann mann mann
Angemeldet seit   01.04.2018 09:32
Beiträge   1639

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

Joachim B. (jar)
>nicht gelesen!

[ ] Du hast zum Thema beigetragen.

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.