534 lines
15 KiB
Python
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()
|
|
|
|
|