Ich will wissen, wie schnell man den ADC eines PiPico 2 mit konstanter
Abtastrate betreiben kann.
1 | from machine import ADC
| 2 | import time
| 3 |
| 4 | # Initialisierung des ADC0 (GPIO26)
| 5 | adc = ADC(0)
| 6 |
| 7 | values=[]
| 8 | laenge=1000
| 9 | start=time.ticks_us()
| 10 | for n in range(0,laenge):
| 11 | val = adc.read_u16()
| 12 | values.append(val)
| 13 | stop=time.ticks_us()
| 14 |
| 15 | dt=(stop-start)/laenge
| 16 | print("sampling time [us] :"+str(dt))
| 17 | print("sampling frequency [Hz] : "+str(1/dt*1000000))
|
Das Ergebnis:
sampling time [us] :16.332
sampling frequency [Hz] : 61229.48
Die Abtastrate schwankt leicht. Welche besseren Methoden gibt es?
Moin,
Christoph M. schrieb:
> Welche besseren Methoden gibt es?
Programmiersprache verwenden, in der man das Gewurschtel mit dem ADC in
einer Tickerinterruptroutine machen kann.
Gruss
WK
Christoph M. schrieb:
> Welche besseren Methoden gibt es?
In aufsteigender Geschwindigkeit:
* Function mit native decorator
* Function mit viper decorator
* Asm-Function mit asm_thumb decorator
* DMA in einen Buffer
* DMA in einen Ring-Buffer
* DMA in zwei wechselnde Buffer
Höchste erzielte Geschwindigkeit: 3Msps.
Weit außerhalb der Spezifikation und mit zunehmend schlechterer
Linearität.
PS. Nicht auf Leute hören, welche offensichtlich keinerlei Erfahrung mit
einer Programmiersprache haben.
Nur mal als kleine Richtungsangabe.
(_ADC_BASE auf Prozessortyp anpassen)
1 | #!/python
| 2 | # -*- coding: UTF-8 -*-
| 3 | # vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
| 4 | from machine import ADC
| 5 | from array import array
| 6 | from time import ticks_us, ticks_diff
| 7 |
| 8 | @micropython.native
| 9 | def test_n(values, laenge):
| 10 | method = adc.read_u16
| 11 | start = ticks_us()
| 12 | for index in range(laenge):
| 13 | val = method()
| 14 | values[index] = val
| 15 | stop = ticks_us()
| 16 | return ticks_diff(stop, start) / laenge
| 17 |
| 18 | @micropython.viper
| 19 | def test_v(values: ptr16, laenge: int):
| 20 | method = adc.read_u16
| 21 | start: int = ticks_us()
| 22 | index: int = 0
| 23 | while index < laenge:
| 24 | values[index] = int(method())
| 25 | index += 1
| 26 | stop: int = ticks_us()
| 27 | return float(ticks_diff(stop, start)) / float(laenge)
| 28 |
| 29 |
| 30 | # _ADC_BASE = const(0x4004c000) # rp2040
| 31 | _ADC_BASE = const(0x400a0000) # rp2350
| 32 | _ADC_CS = const(0x00 >> 2)
| 33 | _ADC_FCS = const(0x08 >> 2)
| 34 | _ADC_FIFO = const(0x0c >> 2)
| 35 |
| 36 | @micropython.viper
| 37 | def adc_on():
| 38 | ADC: ptr32 = ptr32(_ADC_BASE)
| 39 | ADC[_ADC_FCS] = (1<<0) # EN write result to the FIFO after each conversion.
| 40 | ADC[_ADC_CS] = ( (1<<3) # START_MANY Continuously perform conversions.
| 41 | | (1<<0) ) # EN Power on ADC and enable its clock.
| 42 |
| 43 | @micropython.viper
| 44 | def adc_off():
| 45 | ADC: ptr32 = ptr32(_ADC_BASE)
| 46 | ADC[_ADC_FCS] = 0
| 47 | ADC[_ADC_CS] = 0
| 48 |
| 49 | @micropython.viper
| 50 | def test_v_regs(values: ptr16, laenge: int):
| 51 | FIFOEMPTY: int = (1<<8) # prepare mask
| 52 | ADC: ptr32 = ptr32(_ADC_BASE)
| 53 |
| 54 | adc_on()
| 55 | index: int = 0
| 56 | start: int = ticks_us()
| 57 | while (ADC[_ADC_FCS] & FIFOEMPTY) == 0:
| 58 | ADC[_ADC_FIFO]
| 59 | while index < laenge:
| 60 | while ADC[_ADC_FCS] & FIFOEMPTY: pass
| 61 | values[index] = ADC[_ADC_FIFO]
| 62 | index += 1
| 63 | stop: int = ticks_us()
| 64 | adc_off()
| 65 | return float(ticks_diff(stop, start)) / float(laenge)
| 66 |
| 67 | def show_results(dt):
| 68 | print(f'sampling time: {dt:10} µs')
| 69 | print(f'sampling frequency: {1e6/dt:10} Hz')
| 70 |
| 71 | adc = ADC(0)
| 72 | laenge = 1000
| 73 | values = array('H', (0 for _ in range(laenge)))
| 74 | show_results(test_n(values, laenge))
| 75 | show_results(test_v(values, laenge))
| 76 | show_results(test_v_regs(values, laenge))
| 77 |
| 78 | # sampling time: 7.887 µs
| 79 | # sampling frequency: 126791 Hz
| 80 | # sampling time: 4.68 µs
| 81 | # sampling frequency: 213675 Hz
| 82 | # sampling time: 2 µs
| 83 | # sampling frequency: 500000 Hz
|
Norbert (der_norbert)
>Nur mal als kleine Richtungsangabe.
Das ist ein super Richtungsangabe, danke :-)
Ich habe mal versucht, das Ganze in eine Klasse zu packen, damit man es
als externes Modul benutzen kann.
Als Klasse scheint es aber etwas langsamer zu werden: 1 | sampling time: 2.004 µs
| 2 | sampling frequency: 499002 Hz
| 3 | sampling time: 2.006 µs
| 4 | sampling frequency: 498504 Hz
| 5 | sampling time: 2.006 µs
| 6 | sampling frequency: 498504 Hz
| 7 | sampling time: 2.005 µs
|
Am einfachsten wäre meiner Meinung nach folgende Form: 1 | import fastAdc
| 2 | laenge=1000
| 3 | fs=500000
| 4 | adc=fastAdc.FastAdc()
| 5 | print(adc.get_samples(laenge,fs))
|
Ich habe es aber nicht geschafft, die Array-Initialisierung in die
Klasseninitialisierung zu verschieben, weil Viper scheinbar ein Problem
mit dem Array als Klassenvariable hat.
Christoph M. schrieb:
> Als Klasse scheint es aber etwas langsamer zu werden:
Die paar Nanosekunden sind lediglich eine Meßunschärfe. Sieht man sobald
größere Mengen (50_000) gesammelt werden.
Christoph M. schrieb:
> Ich habe es aber nicht geschafft, die Array-Initialisierung in die
> Klasseninitialisierung zu verschieben, weil Viper scheinbar ein Problem
> mit dem Array als Klassenvariable hat.
Array in __init_() errichten und dann mit self referenzieren.
Viper kann das problemlos.
Hab mal ein wenig modifiziert.
>Hab mal ein wenig modifiziert.
Super, danke. Muss ich später noch ausprobieren.
Gerade habe ich noch so was für variable Sampling-Raten probiert: 1 | from machine import Timer
| 2 | from machine import ADC
| 3 |
| 4 | global adc
| 5 | adc = ADC(0)
| 6 |
| 7 | def tick(timer):
| 8 | global adc
| 9 | adc0 = adc.read_u16()
| 10 | print(adc0)
| 11 |
| 12 | Timer().init(freq=10, mode=Timer.PERIODIC, callback=tick)
| 13 |
| 14 | print("hello")
|
Was ist vom Timer-Interrupt zu halten?
Christoph M. schrieb:
> Was ist vom Timer-Interrupt zu halten?
Viel zu lahmarschig, meiner Meinung nach.
Da kannst du besser den ADC Pacing Timer im Register setzen. Ist um
Größenordnungen präziser und einfacher.
Register DIV (0x10 >> 2) unterhalb der _ADC_BASE.
> fastadc.py (2,03 KB)
>Hab mal ein wenig modifiziert
Das Array scheint problematisch. es gibt nur einen Rückgabewert (ich
nehme an der Pointer auf das Array? ):
1 | # Usage example:
| 2 | if __name__ == "__main__":
| 3 | fast_adc = FastADC(0, 100)
| 4 | values,dt = fast_adc.get_samples()
| 5 | print(values)
|
Ergebnis:
536958224
Christoph M. schrieb:
> print(values)
Stimmt. Geschrieben ohne zu testen.
Muss
return self.values, samplingTime_us
heißen.
Danke für die Antwort.
Jetzt ist das Problem, dass ich das array in ein json array umwandeln
will:
Das klappt aber nicht mit dem u16 Format, welches "get_samples" zurück
liefert.
Eine Möglichkeit, die mir einfällt aber recht unelegant erscheint ist,
eine Funktion zu in der FastADC Klasse zu implementieren, mit der ich
Einzelwerte abholen kann: 1 | @micropython.viper
| 2 | def get_sample(self,idx):
| 3 | values: ptr16 = ptr16(self.values)
| 4 | value: int = self.values[idx]
| 5 | return value
|
und dann sie dann mühsellig mit hohem Speicherverbrauch umzuwandeln: 1 | vs=[]
| 2 | for n in range(0,10):
| 3 | vs.append(adc.get_sample(n))
|
Gibt es eine einfachere Lösung?
Du musst keine Klimmzüge veranstalten um auf die Daten des Arrays
zuzugreifen.
Einfach nur mit values[index].
Dein JSON String besteht einfach nur aus einer Auflistung der Werte,
Komma-getrennt.
Du brauchst also bis zu fünf mal soviel Speicherplatz wie du Werte im
Array hast (0…4095 + ,).
Oder noch einfacher: 1 | #!/python
| 2 | val_array = array('H', (1000,2000,3000,4000))
| 3 | result = json.dumps([x for x in val_array])
| 4 | print(result, len(result))
| 5 | # [1000, 2000, 3000, 4000] 24
|
Oder noch einfacher: 1 | #!/python
| 2 | result = json.dumps(list(val_array))
| 3 | print(result, len(result))
| 4 | # [1000, 2000, 3000, 4000] 24
|
>Du musst keine Klimmzüge veranstalten um auf die Daten des Arrays
>zuzugreifen.
Vielen Dank. Irgendwie geht es ja meistens einfach mit Python. Die Kunst
ist nur, die richtige Funktion zu finden.
Im Anhang mal den Screenshot des Signals mit einem PiPico W gesampelt.
Hier mal die vollständigen Sourcen für eine Art "PiPico Oszi".
Eine PiPico W wird als Access-Point konfiguriert auf den man dann autark
zugreifen kann.
Christoph M. schrieb:
> Welche besseren Methoden gibt es?
C/C++ benutzen.
Ada J. Quiroz schrieb:
> C/C++ benutzen.
Werd' erwachsen. Der Profi macht's in Maschinensprache!
Ich habe nur mitgelesen, aber: Was für ein Qualitätbeitrag, Norbert. Wow
:-)
>Ich habe nur mitgelesen, aber: Was für ein Qualitätbeitrag, Norbert.
Ich finde die sarkastische Antwort von Norbert durchaus passend, denn
immerhin hat er gezeigt, wie sich das Problem in Micropython auf
elegante Art lösen lässt. Außer dümmliche Kommentare, die an der
Eingangsfrage vorbei gehen, hat ja ansonsten niemand zur Lösung
beigetragen
Vielleicht hat jemand Erfahrung damit: Kann man mit Micropython
kontinuierliche Signalverarbeitung machen?
z.B:
Via DMA ein Microphon samplen und dann mit Blocksignalverarbeitung einen
Goertzel Algorithmus oder einen Bandpassfilter laufen lassen.
ADC DMA:
https://iosoft.blog/2021/10/26/pico-adc-dma/
Christoph M. schrieb:
> Goertzel Algorithmus
Goertzel geht prima, hatte ich vor geraumer Zeit schon gemacht.
Habe aber im Moment keine Meßwerte zum Zeitverhalten.
Es war aber, selbst ohne Optimierung, schon recht flugs.
Wenn man dann noch den zweiten Kern bemüht…
Christoph M. schrieb:
> ADC DMA:
> https://iosoft.blog/2021/10/26/pico-adc-dma/
Eines noch, der Artikel ist völlig veraltet.
So haben wir's vor Jahren noch gemacht.
Selbstverfreilich bietet µPy (mittlerweile) ein fertiges DMA System an.
Heute bin ich zufällig auf einen Vortrag des Micropython-Gründers
gestoßen:
https://www.youtube.com/watch?v=iEQQWpjgFd8
Laut ihm gibt es eine Kooperation mit der ESA, die Micropthon in
Satelliten einsetzen will.
>Selbstverfreilich bietet µPy (mittlerweile) ein fertiges DMA System an.
Danke für den Hinweis.
Leider gibt es in den Micropython-Docs kein Beispiel für die Verwendung
der DMA-Klasse, objwohl das eine der wichtigsten Anwendungen der DMA
sein dürfte:
https://docs.micropython.org/en/latest/library/rp2.DMA.html
Die Beispiele für DMA-ADC sind wohl alle Register-bassiert:
https://wokwi.com/projects/320232834674459219
Nun, die Beispiele im Zwischennetz sind vermutlich alle veraltet.
Wie der ADC in den Ecstasy-Mode versetzt wird, hatten wir ja schon.
Als Data-Request Quelle (treq_sel) muss je nach µC DREQ_ADC:48 oder
DREQ_ADC:36 gesetzt werden.
Im ADC: FCS Register einmal DREQ_EN einschalten.
Soweit aus der Erinnerung…
Datenblatt studieren hilft bei jeder Programmiersprache. ;-)
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
|