maasier/src/main.py
2025-11-23 03:25:03 +01:00

534 lines
15 KiB
Python

# test of printing multiple fonts to the ILI9341 on an M5Stack using H/W SP
# MIT License; Copyright (c) 2017 Jeffrey N. Magee
from ili934xnew import ILI9341, color565
from machine import Pin, SPI,Timer, UART,PWM
from machine import Counter
from ST7735 import TFT,TFTColor
import m5stack
import tt14
import glcdfont
import tt14
import tt24
import tt32
import uasyncio
import time
import math
from rotary_irq_esp import RotaryIRQ
fonts = [glcdfont,tt14,tt24,tt32]
from sysfont import sysfont
import json
from pid import PID
COILS = 14
# from enum import Enum
class SpinnyBoy(object):
def __init__(self,state,timer1,timer2,display):
self.state = state
self.timer1 = timer1
self.timer2 = timer2
self.display = display
def splash():
# display.fill(0)
# display.fill_rect(0, 0, 32, 32, 1)
# display.fill_rect(2, 2, 28, 28, 0)
# display.vline(9, 8, 22, 1)
# display.vline(16, 2, 22, 1)
# display.vline(23, 8, 22, 1)
# display.fill_rect(26, 24, 2, 4, 1)
# display.text("MicroPython", 40, 0, 1)
# display.text("SSD1306", 40, 12, 1)
# display.text("OLED 128x64", 40, 24, 1)
#display.fill(0)
# display.text("SPIN", 34, 4, 1)
# display.text("COATER", 50, 14, 1)
# display.print("Spinner")
# display.print("COATER")
# tft.text((0,y),"Spin Coater",TFT.RED,sysfont,1,nowrap=True)
display.print("Spincoater",size=1,nowrap=True)
display.print("Spincoater",size=3,nowrap=True)
# #display.show()
def fafo():
with open('fafogreen.bmp','rb') as f:
if f.read(2) == b'BM': #header
print("BMP@")
dummy = f.read(8) #file size(4), creator bytes(4)
offset = int.from_bytes(f.read(4), 'little')
hdrsize = int.from_bytes(f.read(4), 'little')
width = int.from_bytes(f.read(4), 'little')
height = int.from_bytes(f.read(4), 'little')
print("Image size:", width, "x", height)
if int.from_bytes(f.read(2), 'little') == 1: #planes must be 1
depth = int.from_bytes(f.read(2), 'little')
print("DEPTH",depth)
if depth == 24 and int.from_bytes(f.read(4), 'little') == 0:#compress method == uncompressed
print("Image size:", width, "x", height)
rowsize = (width * 3 + 3) & ~3
if height < 0:
height = -height
flip = False
else:
flip = True
w, h = width, height
if w > 128: w = 128
if h > 160: h = 160
tft._setwindowloc((0,0),(w - 1,h - 1))
for row in range(h):
if flip:
pos = offset + (height - 1 - row) * rowsize
else:
pos = offset + row * rowsize
if f.tell() != pos:
dummy = f.seek(pos)
for col in range(w):
bgr = f.read(3)
tft._pushcolor(TFTColor(bgr[2],bgr[1],bgr[0]))
time.sleep(5)
def tftprinttest():
tft.fill(TFT.BLACK)
v = 30
tft.text((0, v), "Hello World!", TFT.RED, sysfont, 1, nowrap=True)
v += sysfont["Height"]
tft.text((0, v), "Hello World!", TFT.YELLOW, sysfont, 2, nowrap=True)
v += sysfont["Height"] * 2
tft.text((0, v), "Hello World!", TFT.GREEN, sysfont, 3, nowrap=True)
v += sysfont["Height"] * 3
tft.text((0, v), str(1234.567), TFT.BLUE, sysfont, 4, nowrap=True)
time.sleep_ms(100)
tft.fill(TFT.BLACK)
v = 0
tft.text((0, v), "Hello World!", TFT.RED, sysfont)
v += sysfont["Height"]
tft.text((0, v), str(math.pi), TFT.GREEN, sysfont)
v += sysfont["Height"]
tft.text((0, v), " Want pi?", TFT.GREEN, sysfont)
v += sysfont["Height"] * 2
tft.text((0, v), hex(8675309), TFT.GREEN, sysfont)
v += sysfont["Height"]
tft.text((0, v), " Print HEX!", TFT.GREEN, sysfont)
v += sysfont["Height"] * 2
tft.text((0, v), "Sketch has been", TFT.WHITE, sysfont)
v += sysfont["Height"]
tft.text((0, v), "running for: ", TFT.WHITE, sysfont)
v += sysfont["Height"]
tft.text((0, v), str(time.ticks_ms() / 1000), TFT.PURPLE, sysfont)
v += sysfont["Height"]
tft.text((0, v), " seconds.", TFT.WHITE, sysfont)
def start_view(state, rotary):
display.set_pos(0,0)
text="Spin Coater"
display.print(text,size=2)
#display.set_font(tt14)
before = "> " if rotary.value() == 0 else " "
display.print(before + "Edit")
before = "> " if rotary.value() == 1 else " "
display.print(before + "Start")
#print(rotary.value())
def draw_edit_menu(state, rotary):
display.print("Deposit speed:")
display.print("{: >{w}} RPM".format(config["deposit_rpm"], w=5))
display.print("Coating speed:")
display.print("{: >{w}} RPM".format(config["coating_rpm"], w=5))
display.print("Coating time:")
display.print("{: >{w}} sec".format(config["coating_time"],w=2))
display.reset_pos()
def edit_deposit_view(state, rotary):
config["deposit_rpm"] = rotary.value() * 100
draw_edit_menu(state, rotary)
def edit_coating_rpm_view(state, rotary):
config["coating_rpm"] = rotary.value() * 100
draw_edit_menu(state, rotary)
def edit_coating_time_view(state, rotary):
config["coating_time"] = rotary.value()
draw_edit_menu(state, rotary)
def draw_rpm(rpm):
display.print("RPM:{: >{w}.0f}".format(rpm, w=5))
def deposit_view(state, rotary):
# display.fill_rect(0, 0, 127, 14, 1)
display.print("Deposit")
draw_rpm(state["rpm"])
display.print("Press to")
display.print("continue")
def coating_view(state, rotary):
# display.fill_rect()
display.print("Coating")
draw_rpm(state["rpm"])
display.print("{: >{w}} sec".format(state["timer"], w=4))
#def decode_ESC_telemetry(data, motor_poles=14):
# if len(data) > 10:
# # use latest telemetry
# data = data[-10:]
#
# temperature = int(data[0]) # degrees Celsius
# voltage = int((data[1] << 8) | data[2]) * 0.01 # Volt
# current = (
# int((data[3] << 8) | data[4]) * 0.01
# ) # Amps, only available if the ESC has a current meter
# consumption = int(
# (data[5] << 8) | data[6]
# ) # mAh, only available if the ESC has a current meter
# erpm = int((data[7] << 8) | data[8]) * 100
# rpm = erpm / (motor_poles / 2)
# crc = data[9]
#
# print(" Temp (C):", temperature)
# print(" Voltage (V):", voltage)
# print(" Current (A):", current)
# print("Consumption (mAh):", consumption)
# print(" Erpm:", erpm)
# print(" RPM:", rpm)
# print(" CRC:", crc)
# print()
#
# return temperature, voltage, current, consumption, erpm, rpm
async def update_display():
global state
global rotary
while True:
display.print("LOLOLOL")
state["view"](state, rotary)
await uasyncio.sleep_ms(200)
async def update_motor():
global state
#we want to use 18 for the SPI-Bus!
#https://docs.micropython.org/en/latest/esp32/quickref.html#hardware-spi-bus
# dshot = Dshot(pin=Pin(25))
# rpm_pid = PID(
# Kp=config["PID"]["Kp"],
# Ki=config["PID"]["Ki"],
# Kd=config["PID"]["Kd"],
# setpoint=0,
# sample_time=None,
# output_limits=(0.0, 1.0),
# # proportional_on_measurement=True,
# )
# while True:
# rpm_pid.setpoint = state["target_rpm"]
# # read ESC telemetry
# if uart.any() >= 10:
# telemetry = decode_ESC_telemetry(uart.read())
# if telemetry is not None:
# state["rpm"] = telemetry[5]
# throttle = rpm_pid(state["rpm"])
# print(
# "Throttle:",
# throttle,
# "pid components:",
# rpm_pid.components,
# "RPM:",
# state["rpm"],
# )
# if state["target_rpm"] == 0 and state["rpm"] < 1000:
# throttle = 0
# rpm_pid.reset()
# dshot.set_throttle(throttle)
# await uasyncio.sleep_ms(1)
old_value=0
current_rpm=0
def timer_callback(f):
global old_value
global current_rpm
val = counter.value()
current_rpm = val-old_value
old_value = val
# print(current_rpm)
def kill_me(f):
state["target_rpm"] = 0
def update_motor_pwm():
global timer3
global current_rpm
pwm = PWM(Pin(26))
rpm_pid = PID(
Kp=config["PID"]["Kp"],
Ki=config["PID"]["Ki"],
Kd=config["PID"]["Kd"],
setpoint=0,
sample_time=None,
output_limits=(0.0, 1.0),
# proportional_on_measurement=True,
)
pwm.freq(50)
timer3.init(mode=Timer.PERIODIC, period=100, callback=timer_callback)
pwm.duty_ns(1055*10**3)
time.sleep(1)
print((current_rpm*60)/COILS)
pwm.duty_ns(1055*10**3)
#pwm.duty_ns(1200*10**3)
#global rpm_counter
#rpm_counter= 0
#time.sleep(10)
# time.sleep(10) # keep low signal to arm
# # for i in range(1060,1800):
# # pwm.duty_u16(i*3)
# # time.sleep(1)
# pwm.duty_ns(1070*10**3)
# pwm.duty_u16(1700)
# time.sleep(200)
pwm.duty_ns(1060*10**3)
state["target_rpm"] = 5000
timer2.init(mode=Timer.PERIODIC, period=10000, callback=kill_me)
while(True):
print(f'target RPM:{state["target_rpm"]}')
rpm_pid.setpoint = state["target_rpm"]
# read ESC telemetry
telemetry = (current_rpm*600)/COILS
if telemetry is not None:
state["rpm"] = telemetry
throttle = rpm_pid(state["rpm"])
print(
"Throttle:",
throttle,
"pid components:",
rpm_pid.components,
"RPM:",
state["rpm"],
)
if state["target_rpm"] == 0:
print("Killing Coater!")
throttle = 0
rpm_pid.reset()
#pwm.duty_ns(1800*10**3)
#time.sleep(3)
#return
print(throttle)
if throttle !=0:
new_duty = 1060+(int(660*throttle))
pwm.duty_ns(new_duty*10**3)
else:
new_duty = 1055
pwm.duty_ns(new_duty*10**3)
timer2.deinit()
break
def set_throttle(pwm,us):
hertz = 50
#we now have to calculate the duty_cycle!
# Period defined as 1/Hertz, we need to multiply by 1_000_000 to calculate to microseconds
period = (1/hertz)*1000000
# us = max(1060, min(1860, us))
#our thing has to be one for x microseconds during the period!
duty_cycle_percentage = (us/period)*100
duty = int((duty_cycle_percentage/100))*65535
print(duty)
pwm.duty_u16(duty)
def debounce_button(p):
p.irq(trigger=Pin.IRQ_FALLING, handler=None) # remove irq
timer0 = Timer(0)
timer0.init(period=20, mode=Timer.ONE_SHOT, callback=lambda t: on_button_press(p))
def on_button_press(p):
p.irq(trigger=Pin.IRQ_FALLING, handler=debounce_button) # restore irq
if p.value() == 1: # debounce
return
global state
global config
global rotary
if state["view"] == start_view:
if rotary.value() == 0:
state["view"] = edit_deposit_view
rotary.set(
min_val=0,
range_mode=RotaryIRQ.RANGE_BOUNDED,
value=int(0.01 * config["deposit_rpm"]),
)
return
if rotary.value() == 1:
state["view"] = deposit_view
state["target_rpm"] = config["deposit_rpm"]
return
if state["view"] == edit_deposit_view:
state["view"] = edit_coating_rpm_view
rotary.set(
min_val=0,
max_val=1000,
range_mode=RotaryIRQ.RANGE_BOUNDED,
value=int(0.01 * config["coating_rpm"]),
)
return
if state["view"] == edit_coating_rpm_view:
state["view"] = edit_coating_time_view
rotary.set(
max_val=1000,
min_val=0,
max_val=9999,
range_mode=RotaryIRQ.RANGE_BOUNDED,
value=config["coating_time"],
)
return
if state["view"] == edit_coating_time_view:
save_config()
rotary.set(min_val=0, max_val=1, range_mode=RotaryIRQ.RANGE_BOUNDED, value=0)
state["view"] = start_view
if state["view"] == deposit_view:
return
state["view"] = coating_view
start_coating(state)
return
if state["view"] == coating_view:
stop_coating()
return
def start_coating(state):
global timer1
global timer2
state["timer"] = config["coating_time"]
timer1.init(
period=config["coating_time"] * 1000,
mode=Timer.ONE_SHOT,
callback=lambda t: stop_coating(),
)
def decrement_timer(t):
state["timer"] -= 1
timer2.init(period=1000, mode=Timer.PERIODIC, callback=decrement_timer)
# state["throttle"] = 0.10
state["target_rpm"] = config["coating_rpm"]
def stop_coating():
global state
global rotary
global timer1
global timer2
timer1.deinit()
timer2.deinit()
state["target_rpm"] = 0
rotary.set(min_val=0, max_val=1, range_mode=RotaryIRQ.RANGE_BOUNDED, value=0)
state["view"] = start_view
def save_config():
global config
with open("config.json", "w") as f:
json.dump(config, f)
power = Pin(m5stack.TFT_LED_PIN, Pin.OUT)
power.value(1)
timer1 = Timer(1)
timer2 = Timer(2)
counter = Counter(0,Pin(19,Pin.IN))
timer3 = Timer(3)
# No need to change the software. It's just a matter of different names.. Use this translation:
# SDO(MISO) <not used>
# LED BL
# SCK CLK/SCK
# SDI(MOSI) DIN/SDA
# DC D/C (A0)
# RESET RST
# CS CS
# GND GND
# VCC VCC
spi = SPI(
2,
baudrate=40000000,
miso=Pin(m5stack.TFT_MISO_PIN),
mosi=Pin(m5stack.TFT_MOSI_PIN),
sck=Pin(m5stack.TFT_CLK_PIN))
# display = ILI9341(
# spi,
# cs=Pin(m5stack.TFT_CS_PIN),
# dc=Pin(m5stack.TFT_DC_PIN),
# rst=Pin(m5stack.TFT_RST_PIN),
# w=160,
# h=128,
# r=5)
tft = TFT(spi,m5stack.TFT_DC_PIN,m5stack.TFT_RST_PIN,m5stack.TFT_CS_PIN)
display = tft
tft.initr()
tft.rgb(True)
#tftprinttest()
rotary = RotaryIRQ(
pin_num_clk=25,
pin_num_dt=13,
min_val=0,
max_val=1,
range_mode=RotaryIRQ.RANGE_BOUNDED,
pull_up=True,
)
button = Pin(15, Pin.IN, Pin.PULL_UP)
button.irq(trigger=Pin.IRQ_FALLING, handler=on_button_press)
#interrupt for rpm sensor!
state = {
"view": start_view,
"rpm": 0,
"target_rpm": 0,
"timer": 0,
"rotary_val":-1
}
with open("config.json", "r") as f:
config = json.load(f)
#display.erase()
#display.set_pos(0,0)
#display.set_font(tt32)
tft.fill(TFT.BLACK)
# splash()
fafo()
# for ff in fonts:
# display.set_font(ff)
# display.print(text)
update_motor_pwm()
event_loop = uasyncio.get_event_loop()
# event_loop.create_task(update_display())
# #event_loop.create_task(update_motor_pwm())
# event_loop.run_forever()