Skip to content

Instantly share code, notes, and snippets.

@lvidarte
Created July 22, 2024 04:25
Show Gist options
  • Save lvidarte/313fffbfdc82da589da4d19b732a6782 to your computer and use it in GitHub Desktop.
Save lvidarte/313fffbfdc82da589da4d19b732a6782 to your computer and use it in GitHub Desktop.
Mini oscilloscope for ESP32 TTFO
"""
MINI OSCILLOSCOPE
Author: Leo Vidarte <https://minirobots.com.ar>
This is free software,
you can redistribute it and/or modify it
under the terms of the GPL version 3
as published by the Free Software Foundation.
"""
from machine import SPI, ADC, Pin
from utime import sleep_ms
import st7789
import vga1_8x16 as font1
WIDTH = 240
HEIGHT = 135
VOLT_MAX = 3.3
VOLT_FACTOR = VOLT_MAX / 65536
# The st7789 module exposes predefined colors:
# BLACK, BLUE, RED, GREEN, CYAN, MAGENTA, YELLOW, and WHITE
THEMES = {
'DEFAULT': {
'BG': st7789.BLACK,
'FRAME': st7789.BLUE,
'RULE': st7789.BLUE,
'PLOT_FG': st7789.YELLOW,
'PLOT_BG': st7789.BLACK,
'CUR_FG': st7789.WHITE,
'CUR_BG': st7789.BLUE,
'MAX_FG': st7789.BLACK,
'MAX_BG': st7789.YELLOW,
'MIN_FG': st7789.BLACK,
'MIN_BG': st7789.YELLOW,
},
'BLOOD': {
'BG': st7789.BLACK,
'FRAME': st7789.RED,
'RULE': st7789.RED,
'PLOT_FG': st7789.WHITE,
'PLOT_BG': st7789.BLACK,
'CUR_FG': st7789.WHITE,
'CUR_BG': st7789.RED,
'MAX_FG': st7789.WHITE,
'MAX_BG': st7789.RED,
'MIN_FG': st7789.WHITE,
'MIN_BG': st7789.RED,
},
'GREEN': {
'BG': st7789.BLACK,
'FRAME': st7789.GREEN,
'RULE': st7789.GREEN,
'PLOT_FG': st7789.GREEN,
'PLOT_BG': st7789.color565(32, 32, 32),
'CUR_FG': st7789.BLACK,
'CUR_BG': st7789.GREEN,
'MAX_FG': st7789.BLACK,
'MAX_BG': st7789.GREEN,
'MIN_FG': st7789.BLACK,
'MIN_BG': st7789.GREEN,
},
'REDMOND': {
'BG': st7789.BLACK,
'FRAME': st7789.WHITE,
'RULE': st7789.WHITE,
'PLOT_FG': st7789.BLACK,
'PLOT_BG': st7789.BLUE,
'CUR_FG': st7789.BLUE,
'CUR_BG': st7789.WHITE,
'MAX_FG': st7789.BLUE,
'MAX_BG': st7789.WHITE,
'MIN_FG': st7789.BLUE,
'MIN_BG': st7789.WHITE,
},
'BLACK_AND_WHITE': {
'BG': st7789.BLACK,
'FRAME': st7789.WHITE,
'RULE': st7789.WHITE,
'PLOT_FG': st7789.BLACK,
'PLOT_BG': st7789.color565(200, 200, 200),
'CUR_FG': st7789.BLACK,
'CUR_BG': st7789.WHITE,
'MAX_FG': st7789.BLACK,
'MAX_BG': st7789.WHITE,
'MIN_FG': st7789.BLACK,
'MIN_BG': st7789.WHITE,
},
'FIESTA': {
'BG': st7789.BLACK,
'FRAME': st7789.YELLOW,
'RULE': st7789.MAGENTA,
'PLOT_FG': st7789.MAGENTA,
'PLOT_BG': st7789.BLACK,
'CUR_FG': st7789.BLACK,
'CUR_BG': st7789.MAGENTA,
'MAX_FG': st7789.BLACK,
'MAX_BG': st7789.MAGENTA,
'MIN_FG': st7789.BLACK,
'MIN_BG': st7789.MAGENTA,
},
'MULTICOLOR': {
'BG': st7789.BLACK,
'FRAME': st7789.BLUE,
'RULE': st7789.BLUE,
'PLOT_FG': st7789.RED,
'PLOT_BG': st7789.BLACK,
'CUR_FG': st7789.BLACK,
'CUR_BG': st7789.YELLOW,
'MAX_FG': st7789.BLACK,
'MAX_BG': st7789.MAGENTA,
'MIN_FG': st7789.BLACK,
'MIN_BG': st7789.CYAN,
},
'BLOOD2': {
'BG': st7789.RED,
'FRAME': st7789.RED,
'RULE': st7789.WHITE,
'PLOT_FG': st7789.WHITE,
'PLOT_BG': st7789.RED,
'CUR_FG': st7789.WHITE,
'CUR_BG': st7789.RED,
'MAX_FG': st7789.WHITE,
'MAX_BG': st7789.RED,
'MIN_FG': st7789.WHITE,
'MIN_BG': st7789.RED,
},
'VINTAGE': {
'BG': st7789.color565(200, 200, 200),
'FRAME': st7789.CYAN,
'RULE': st7789.BLACK,
'PLOT_FG': st7789.BLUE,
'PLOT_BG': st7789.CYAN,
'CUR_FG': st7789.WHITE,
'CUR_BG': st7789.BLUE,
'MAX_FG': st7789.BLACK,
'MAX_BG': st7789.MAGENTA,
'MIN_FG': st7789.BLACK,
'MIN_BG': st7789.YELLOW,
},
}
spi = SPI(
1,
baudrate=30000000,
polarity=1,
phase=0,
sck=Pin(18),
mosi=Pin(19),
miso=Pin(13),
)
display = st7789.ST7789(
spi,
HEIGHT,
WIDTH,
reset=Pin(23, Pin.OUT),
cs=Pin(5, Pin.OUT),
dc=Pin(16, Pin.OUT),
backlight=Pin(4, Pin.OUT),
rotation=1,
)
class Buffer:
SIZE = 106 # values to store
PLOT_HEIGHT = 100
PLOT_DOT_SIZE = 2
PLOT_OFFSET_X = 4
PLOT_OFFSET_Y = 4
def __init__(self, display):
self.display = display
self.items = []
def add(self, volts):
if len(self.items) == self.SIZE:
self.items.pop(0)
self.items.append((
self._volts_to_y(volts),
volts,
))
def max(self):
return max([item[1] for item in self.items[1:-1]])
def min(self):
return min([item[1] for item in self.items[1:-1]])
def cur(self):
return self.items[-1][1]
def get_y_values(self):
return [item[0] for item in self.items[1:-1]]
def _volts_to_y(self, volts):
y = int(round(volts * self.PLOT_HEIGHT / VOLT_MAX))
return self.PLOT_HEIGHT - y + self.PLOT_OFFSET_Y
def plot(self, theme):
y_values = self.get_y_values()
for i, y in enumerate(y_values):
x = (i * 2) + self.PLOT_OFFSET_X
self._clear_column(x, theme)
y_prev = self.items[i][0]
y_next = self.items[i+2][0]
if y < y_next <= y_prev:
self._draw_value_by_y2(x, y, y_prev, theme)
elif y < y_prev < y_next:
self._draw_value_by_y2(x, y, y_next, theme)
elif y < y_prev:
self._draw_value_by_y2(x, y, y_prev, theme)
elif y < y_next:
self._draw_value_by_y2(x, y, y_next, theme)
else:
self._draw_value(x, y, self.PLOT_DOT_SIZE, theme)
return bool(y_values)
def _clear_column(self, x, theme):
self.display.fill_rect(
x,
self.PLOT_OFFSET_Y,
self.PLOT_DOT_SIZE,
self.PLOT_HEIGHT + self.PLOT_DOT_SIZE,
theme['PLOT_BG'],
)
def _draw_value_by_y2(self, x, y, y2, theme):
height = y2 - y + self.PLOT_DOT_SIZE
self._draw_value(x, y, height, theme)
def _draw_value(self, x, y, height, theme):
self.display.fill_rect(x, y, self.PLOT_DOT_SIZE, height, theme['PLOT_FG'])
class ThemeCycler:
def __init__(self):
self.themes = list(THEMES.values())
self.index = 0
def next(self):
theme = self.themes[self.index]
self.index = (self.index + 1) % len(self.themes)
return theme
def draw_frame(theme):
# frame
display.fill_rect(1, 1, 214, 107, theme['PLOT_BG'])
display.rect(0, 0, 216, 109, theme['FRAME'])
display.rect(1, 1, 214, 107, theme['FRAME'])
# rule
display.hline(218, 4, 4, theme['RULE'])
display.hline(218, 14, 8, theme['RULE'])
display.hline(218, 24, 4, theme['RULE'])
display.hline(218, 34, 4, theme['RULE'])
display.hline(218, 44, 8, theme['RULE'])
display.hline(218, 54, 4, theme['RULE'])
display.hline(218, 64, 4, theme['RULE'])
display.hline(218, 74, 8, theme['RULE'])
display.hline(218, 84, 4, theme['RULE'])
display.hline(218, 94, 4, theme['RULE'])
display.hline(218, 104, 8, theme['RULE'])
# rule numbers
display.text(font1, '3', 229, 8, theme['RULE'], theme['BG'])
display.text(font1, '2', 229, 38, theme['RULE'], theme['BG'])
display.text(font1, '1', 229, 68, theme['RULE'], theme['BG'])
display.text(font1, '0', 229, 98, theme['RULE'], theme['BG'])
def draw_voltage_frames(theme):
# voltage values
display.fill_rect(0, 112, 70, 23, theme['CUR_BG'])
display.fill_rect(74, 112, 69, 23, theme['MAX_BG'])
display.fill_rect(147, 112, 69, 23, theme['MIN_BG'])
def draw_voltage_values(buffer, theme):
display.text(font1, f'cur {buffer.cur()}v', 3, 115, theme['CUR_FG'], theme['CUR_BG'])
display.text(font1, f'max {buffer.max()}v', 77, 115, theme['MAX_FG'], theme['MAX_BG'])
display.text(font1, f'min {buffer.min()}v', 150, 115, theme['MIN_FG'], theme['MIN_BG'])
def init(theme):
display.fill(theme['BG'])
draw_frame(theme)
draw_voltage_frames(theme)
if __name__ == '__main__':
display.init()
pot = ADC(Pin(26))
button_l = Pin(0, Pin.IN)
button_r = Pin(35, Pin.IN)
buffer = Buffer(display)
themes = ThemeCycler()
theme = themes.next()
init(theme)
while True:
volts = round(pot.read_u16() * VOLT_FACTOR, 1)
buffer.add(volts)
if buffer.plot(theme):
draw_voltage_values(buffer, theme)
# Change Theme
if not button_r.value():
sleep_ms(200)
theme = themes.next()
init(theme)
# Pause
if not button_l.value():
sleep_ms(200)
while button_l.value():
sleep_ms(100)
sleep_ms(200)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment