Created
July 22, 2024 04:25
-
-
Save lvidarte/313fffbfdc82da589da4d19b732a6782 to your computer and use it in GitHub Desktop.
Mini oscilloscope for ESP32 TTFO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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