Forum: Mikrocontroller und Digitale Elektronik Micropython PiPico constant sampling ADC


von Christoph M. (mchris)


Lesenswert?

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?

von Dergute W. (derguteweka)


Lesenswert?

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

von Norbert (der_norbert)


Lesenswert?

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.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

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

: Bearbeitet durch User
von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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.

von Norbert (der_norbert)


Angehängte Dateien:

Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

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

von Norbert (der_norbert)


Lesenswert?

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.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

> 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

von Norbert (der_norbert)


Lesenswert?

Christoph M. schrieb:
> print(values)

Stimmt. Geschrieben ohne zu testen.
Muss
return self.values, samplingTime_us
heißen.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

Danke für die Antwort.
Jetzt ist das Problem, dass ich das array in ein json array umwandeln 
will:
1
json.dumps(signal)

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?

von Norbert (der_norbert)


Lesenswert?

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

: Bearbeitet durch User
von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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

: Bearbeitet durch User
von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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.

von Ada J. Quiroz (inschnier)


Lesenswert?

Christoph M. schrieb:
> Welche besseren Methoden gibt es?

C/C++ benutzen.

von Norbert (der_norbert)


Lesenswert?

Ada J. Quiroz schrieb:
> C/C++ benutzen.

Werd' erwachsen. Der Profi macht's in Maschinensprache!

von Pferd O. (pferdo)


Lesenswert?

Ich habe nur mitgelesen, aber: Was für ein Qualitätbeitrag, Norbert. Wow 
:-)

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

>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

von Christoph M. (mchris)


Lesenswert?

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/

von Norbert (der_norbert)


Lesenswert?

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…

von Norbert (der_norbert)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

>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

von Norbert (der_norbert)


Lesenswert?

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.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.