Micropython PiPico constant sampling ADC

OP #7815984
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?

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

#7816027
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
OP #7816295
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.

Angehängte Dateien:
#7816307
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.

Angehängte Dateien:
OP #7816312
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?

OP #7816599
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

OP #7816618
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?

#7816637
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
OP #7818028
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

OP #7818306
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

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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren