
RPi-Pico OLED-Oszilloskop
Elektronik-Labor
Projekte
Mikrocontroller
Raspberry

Kürzlich habe ich zum ersten Mal ein OLED-Display an den Pico
angeschlossen. Die Ansteuerung ist nicht schwer. Man muss die Datei
ssd1306 mit einbinden, die zuerst in den Pico geladen werden muss. Die
ersten Versuche verliefen erfolgreich. Deshalb habe ich gleich eine
richtige Anwendung angestrebt: Ein ganz kleines Oszilloskop, sozusagen
ein Picoskop.
#OLEDoszi2.py Scope
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
import machine
import time
import array
i2c = I2C(0, scl=Pin(9),sda=Pin(8),freq=100000)
oled = SSD1306_I2C(128,64,i2c)
u2 = machine.ADC(2)
ad_in = Pin(28, Pin.IN)
x = array.array('i', [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
while (True):
for i in range(128):
x[i]=63-u2.read_u16()//1366
for t in range(70):
a=1+1
oled.fill(0)
oled.text("3.3V 10ms/div",5,0)
oled.line(0, 60, 0, 64, 1)
oled.line(20, 60, 20, 64, 1)
oled.line(40, 60, 40, 64, 1)
oled.line(60, 60, 60, 64, 1)
oled.line(80, 60, 80, 64, 1)
oled.line(100, 60, 100, 64, 1)
oled.line(120, 60, 120, 64, 1)
for i in range(128):
u=x[i]
if (i>0):
oled.line(i, u0, i+1, u, 1)
u0=u
oled.show()
time.sleep(1)
In der oberen Zeile habe ich die Einstellungen angegeben. Ganz unten
stehen Punkte für die Skalenteile im Abstand 10 ms. Das Oszillogramm
zeigt ein 50-Hz-Signal. Für die Messung habe ich einfach die Hand nahe
an den Eingang gehalten, sodass die Brummspannung eingekoppelt wurde.
µPython Optimierungen von Andreas
Das Variablen-Gespiele mit a = 1+1 kann man auch außerhalb des
Programmlaufs mit "ticks_us()" messen. Deswegen habe ich mal den
Code effizienter umgeschaltet, so dass du für weitere Experimente auch
einen Triggerpunkt hast.
#OLEDosziV2.01.py Scope
from machine import Pin, I2C, ADC
from ssd1306 import SSD1306_I2C
from time import sleep, sleep_us, ticks_us, ticks_diff
def read(adc, wait = 315):
sleep_us(wait)
return 63 - adc.read_u16() // 1366
oled = SSD1306_I2C(128, 64, I2C(0, scl=Pin(1), sda=Pin(0), freq=400_000))
adc = ADC(2)
u0 = 63
while True:
start = ticks_us()
array = [read(adc, 328) for _ in range(128)]
lauf = ticks_diff(ticks_us(), start)
oled.fill(0)
oled.text("3.3V 10ms/div",5,0)
oled.line(0, 60, 0, 64, 1)
oled.line(20, 60, 20, 64, 1)
oled.line(40, 60, 40, 64, 1)
oled.line(60, 60, 60, 64, 1)
oled.line(80, 60, 80, 64, 1)
oled.line(100, 60, 100, 64, 1)
oled.line(120, 60, 120, 64, 1)
for i in range(0,128):
u=array[i]
if i > 0:
oled.line(i, u0, i+1, u, 1)
u0=u
oled.show()
sleep(1)
Damit kannst du mit dem zweiten Wert (hier noch 328) beim Aufruf den
Triggerpunkt verschieben. Array ist nicht wirklich notwendig, weil du
das auch mit einer LIST machen. Und eine Bitte noch: Diese
C++ Unart alles in () zu setzen wie bei "while" oder "if" ist in Python
verpönt, und führt nur zu Ausführungsverzögerungen im nsek
Bereich. Sagen wir mal so, die CPU muss mehr machen als
notwendig. Je nach Klammerninhalt sind das mind. 7 Takte zusätzlich,
was du auch mit "ticks_cpu()" nachmessen kannst .
So kannst du auch bei LIST als Array-Ersatz statt
liste = []
for _ in range(128):
liste.append(0)
folgendes schreiben:
liste = [0 for _in range(128)]
Du musst auch eine LIST in der Größe nicht vordefinieren. Ausgehend von
deinem Basis Programm musste ich das WAIT als Übergabeparamter zur
Zeitsteuerung von 309 auf 328 erhöhen, damit die Kurve auf dem OLED
wieder still steht. Damit du einen Anhaltspunkt erhältst, wie die
Durchlaufzeit der 128 Abfragen ist, habe ich noch die Variable "lauf"
eingefügt, damit du eine bessere Möglichkeit hast zu sehen, wie hoch
doch die Laufzeitunterschiede sein können, und du damit mehr
Spielraum für weitere Versuche hast
Ebenso ist es nicht notwendig einen ADC Pin, welcher als ADC Eingang
genutzt wird noch zusätzlich als Pin.IN zu definieren. Das hat
jetzt keine Auswirkung auf den Programmlauf, aber wenn der Speicher auf
Grund der Programmgröße knapp wird, kann man sich solche Zeilen einfach
ersparen. Und man importiert nur die Funktionen aus einer Bibliothek
die man wirklich braucht. Du hattest erst PIN und I2C importiert, dann
aber wegen des ADCs noch einmal die gesamte Bibliothek. Irgendwie
Platzverschwendung.
Falls du ZThonny als IDE nutzt, in der Kommando Zeile kannst du eine
Bibliothek importieren, und dann mit der Erweiterung über das Menü
Ansicht, Variablen und Objektinspektor ganz gezielt nachschauen, was
diese Bibliothek enthält. So kannst du auch schreiben:
from machine import Pin as GPIO, I2C, ADC as Analogeingang
Damit heißen dann die Funktionen im Programmaufruf GPIO statt Pin, und Analogeingang statt ADC.
Antwort BK: Hallo Andreas, ich
bin dir sehr dankbar für die Hinweise. Ich war nie ein richtiger
Programmierer. Bei mir läuft das so, wie das Basteln mit dem Lötkolben,
wenn es funktioniert, bin ich fertig. Die Zeitverzögerung mit einer
Zählschleife war mir auch schon etwas peinlich. Ich hatte nur die
Millisekunden gefunden und brauchte was kürzeres. Da habe ich es so
gemacht wie vor über 40 Jahren schon in BASIC. Aber deine Tipps werden
mir bei weiteren Programmen sehr helfen.
Picoskop mit umschaltbarer Zeitbasis
Nach
den Hinweisen von Andreas habe ich das Programm noch mal aufgebohrt. Es gibt
nun acht Zeitmessbereiche von 100 ms/div bis 0,5 ms/div. Die Zeitmessung beruht
jetzt auf ticks_us(), einer unbeirrbaren Mikrosekunden-Uhr im Hintergrund. Für
jede Messung wird der Zeitpunkt nach dem Start einer Serie berechnet und dann
in einer Schleife abgewartet.
Für die schnellsten Messungen war es nötig, den Controller auf 180 MHz
hochzutakten. Für die Umschaltung der Messbereiche wird nur ein
Tastschalter
gebraucht. Weil er sich mit seinen vier Beinchen so breit macht,
mussten zwei Einmgänge, p5 und p7 initialisiert werden. Das Programm
braucht nur p5 auszuwerten. Wenn man lange drückt, wird die
Zeitauflösung höher, bei einem kurzen
Druck wird sie gröber. Nach dem Start sind 5 ms/div eingestellt.
Zusätzlich wurde mit einem PWM-Ausgang ein Testsignal mit 1 kHz erzeugt. Damit
gibt es ein Referenzsignal wie an vielen großen Oszilloskopen. Es wird hier gerade
durch ein Tiefpassfilter mit 2,4 k und 100 nF geformt.
#OLEDoszi2.py Scope
from machine import Pin, I2C, ADC, PWM, freq as CPU_freq
from ssd1306 import SSD1306_I2C
from time import sleep, sleep_us, ticks_us
CPU_freq(180000000)
u2 = machine.ADC(2)
ad_in = Pin(28, Pin.IN)
p5 = Pin(5, Pin.IN, Pin.PULL_UP)
p7 = Pin(7, Pin.IN, Pin.PULL_UP)
i2c = I2C(0, scl=Pin(1),sda=Pin(0),freq=400000)
oled = SSD1306_I2C(128,64,i2c)
pwm1 = PWM(Pin(2))
pwm1.freq(1000)
pwm1.duty_u16(32768)
timebase = [('3.3V 100ms/div', 5000),
('3.3V 50ms/div', 2500),
('3.3V 20ms/div', 1000),
('3.3V 10ms/div', 500),
('3.3V 5ms/div', 250),
('3.3V 2ms/div', 100),
('3.3V 1ms/div', 50),
('3.3V 0.5ms/div', 25)]
n=4
x= [0 for _ in range(128)]
while True:
txt = timebase [n][0]
dt = timebase [n][1]
start = ticks_us()
for i in range(128):
x[i]=u2.read_u16()
start+=dt
while start > ticks_us():
pass
oled.fill(0)
oled.text(txt,5,0)
for j in range(7):
oled.line(j * 20, 60, j * 20, 64, 1)
for i in range(128):
u=63-x[i]//1366
if (i>0):
oled.line(i, u0, i+1, u, 1)
u0=u
oled.show()
t=0
for i in range(20):
sleep (0.05)
if p5.value()==0:
t+=1
if t>0:
n-=1
if t> 7:
n+=2
if n<0:
n=7
if n>7:
n=0
(Update 10.2.23 18 Uhr)
Hier sieht
man das originale Rechtecksignal in der schnellsten Einstellung. Wenn
man genau
hinsieht, erkennt man eine leichte Zeitverschiebung gegenüber den
Skalierungsstrichen.
Das ist ein Hinweis darauf, dass die 180 MHz bei diesem Versuch noch
nicht ganz ausreichten. Mit der letzten Änderung wurde das Programm
noch etwas schneller, jetzt passt es.
Bei 1 ms/div stimmte die Skala dagegen genau. Das gewählte Zeitmessverfahren arbeitet also wie gewünscht.
Picoskop mit Signalgenerator
Nach dem Vorbild von Dietrichs Geschwindigkeitsmesser habe
ich das Display nun in der oberen Reihe angeschlossen und mit einem
Zwischenstecker höher gelegt. SCL und SDA liegen jetzt an GP19 und
GP18. Die I2C-Taktrate wurde im Interesse der hohen Datensicherheit auf
100 kHz reduziert. Der Taster zur Änderung der Abtastrate wurde weiter
nach rechts verlegt. Damit ist nun Platz für insgesamt fünf
PWM-Ausgänge mit Frequenzen zwischen 100 Hz und 30 MHz, die man immer
mal brauchen kann.
#OLEDoszi3.py Scope
from machine import Pin, I2C, ADC, PWM, freq as CPU_freq
from ssd1306 import SSD1306_I2C
from time import sleep, sleep_us, ticks_us
CPU_freq(180000000)
u2 = machine.ADC(2)
ad_in = Pin(28, Pin.IN)
sw = Pin(15, Pin.IN, Pin.PULL_UP)
sw2 = Pin(13, Pin.IN, Pin.PULL_UP)
i2c = I2C(1, scl=Pin(19),sda=Pin(18),freq=100000)
oled = SSD1306_I2C(128,64,i2c)
pwm0 = PWM(Pin(0))
pwm0.freq(100) #100 Hz
pwm0.duty_u16(32768)
pwm1 = PWM(Pin(2))
pwm1.freq(1000) #1 kHz
pwm1.duty_u16(32768)
pwm2 = PWM(Pin(4))
pwm2.freq(10000) #10 kHz
pwm2.duty_u16(32768)
pwm3 = PWM(Pin(6))
pwm3.freq(100000) #100 kHz
pwm3.duty_u16(32768)
pwm3 = PWM(Pin(6))
pwm3.freq(1000000) # 1 MHz
pwm3.duty_u16(32768)
pwm3 = PWM(Pin(8))
pwm3.freq(30000000) # 30 MHz
pwm3.duty_u16(32768)
timebase = [('3.3V 100ms/div', 5000),
('3.3V 50ms/div', 2500),
('3.3V 20ms/div', 1000),
('3.3V 10ms/div', 500),
('3.3V 5ms/div', 250),
('3.3V 2ms/div', 100),
('3.3V 1ms/div', 50),
('3.3V 0.5ms/div', 25)]
n=7
x= [0 for _ in range(128)]
while True:
txt = timebase [n][0]
dt = timebase [n][1]
start = ticks_us()
for i in range(128):
x[i]=u2.read_u16()
start+=dt
while start > ticks_us():
pass
oled.fill(0)
oled.text(txt,5,0)
for j in range(7):
oled.line(j * 20, 60, j * 20, 64, 1)
for i in range(128):
u=63-x[i]//1366
if (i>0):
oled.line(i, u0, i+1, u, 1)
u0=u
oled.show()
t=0
for i in range(20):
sleep (0.05)
if sw.value()==0:
t+=1
if t>0:
n-=1
if t> 7:
n+=2
if n<0:
n=7
if n>7:
n=0
Die
Rechtecksignale bis 10 kHz kann der Poco selbst noch darstellen. Die höheren
Frequenzen dienen zum Test mit anderen Geräten. Das 30-MHz-Signal wird an
meinem 20-MHz-Hameg noch mit 2 Vss angezeigt, allerdings als Sinus. Das
Oszilloskop arbeitet selbst wie ein wirksames Tiefpassfilter.