Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@
HEATER_3 = machine.Pin(9,machine.Pin.OUT)
TEMPERATURE_PIN = machine.Pin(10, machine.Pin.IN)
DS_SENSOR = ds18x20.DS18X20(onewire.OneWire(TEMPERATURE_PIN))
roms = DS_SENSOR.scan()
MIXER_PIN = machine.Pin(11, machine.Pin.OUT)
BUTTONS_PIN = machine.Pin(27, machine.Pin.IN)
BUTTONS_PIN_ADC = machine.ADC(BUTTONS_PIN)

TARGET_PHASE_1 = 78.0
TARGET_PHASE_2 = 81.0
TARGET_PHASE_3 = 83.0
TARGET_PHASE_4 = 90.0
TARGET_PHASE_4 = 95.0
TARGET_TEMPERATURE = [TARGET_PHASE_1, TARGET_PHASE_2, TARGET_PHASE_3, TARGET_PHASE_4]

CYCLE_TIME_SECONDS = 0.1

display = lcd_4bit_mode.LCD16x2(RS,ENABLE,BACK_LIGHT,D4,D5,D6,D7)


Expand Down
36 changes: 36 additions & 0 deletions control.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from constants import DS_SENSOR, HEATER_1, HEATER_2, HEATER_3, MIXER_PIN, roms
from pid import PidController
from program_state import ProgramState

class Control:
def __init__(self, pid: PidController):
self.pid = pid

def measure_temperature(self, state: ProgramState):
DS_SENSOR.convert_temp()
measured = DS_SENSOR.read_temp(roms[0])

#print("Measured temperature:", measured)
state.set_measured_temp(measured)

def control_heaters(self, state: ProgramState):
#print("Target temp:", state.get_target_temp_for_phase(), "Measured temp:", state.measured_temp)

# Calculate PID output
pid_output = self.pid.calculate_pid_output(state)

# Determine which heaters should be active
heaters_in_use = self.pid.determine_heater_states(pid_output)

state.switch_heaters(heaters_in_use)

# Apply heater states to hardware
HEATER_1.value(state.calculate_heater_state(0))
HEATER_2.value(state.calculate_heater_state(1))
HEATER_3.value(state.calculate_heater_state(2))

def control_mixer(self, state: ProgramState):
if state.mix:
MIXER_PIN.on()
else:
MIXER_PIN.off()
113 changes: 113 additions & 0 deletions lcd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from utime import sleep
from constants import BUTTONS_PIN_ADC, CHAR_HEATER, CHAR_HEATER_ENABLED_OFF, CHAR_HEATER_ENABLED_ON, CHAR_MIX, CHAR_MIX_OFF, CHAR_MIX_ON, CHAR_TARGET, CHAR_THERMOMETER
from program_state import ProgramState


class LCD:
def __init__(self, display, state: ProgramState):
self.display = display
self.backlight_on()

# Create custom characters
self.display.CreateChar(slot=0, bitmap=CHAR_HEATER_ENABLED_OFF)
self.display.CreateChar(slot=1, bitmap=CHAR_TARGET)
self.display.CreateChar(slot=2, bitmap=CHAR_THERMOMETER)
self.display.CreateChar(slot=3, bitmap=CHAR_HEATER_ENABLED_ON)
self.display.CreateChar(slot=4, bitmap=CHAR_HEATER)
self.display.CreateChar(slot=5, bitmap=CHAR_MIX)
self.display.CreateChar(slot=6, bitmap=CHAR_MIX_ON)
self.display.CreateChar(slot=7, bitmap=CHAR_MIX_OFF)

self.target_temp: float = state.get_target_temp_for_phase()
self.measured_temp: float = state.measured_temp
self.phase = state.phase
self.mix = state.mix
self.heaters_enabled = state.heaters_enabled.copy()
self.heaters_in_use = state.heaters_in_use.copy()

def backlight_on(self):
self.display.BackLightOn()

def backlight_off(self):
self.display.BackLightOff()

def handle_buttons(self, state: ProgramState):
buttons_adc = BUTTONS_PIN_ADC.read_u16()
#print("Buttons ADC value:", buttons_adc)

if 200 <= buttons_adc < 6000:
pass
# RIGHT
# PID controls it

# if(sum(state.heaters_enabled) == 0):
# state.heaters_enabled = [True, True, True]
# sleep(0.3) # Simple debounce
# return
# state.heaters_enabled[sum(state.heaters_enabled) - 1] = False
# sleep(0.3) # Simple debounce
elif 6000 <= buttons_adc < 14000:
# UP
state.change_target_temp_for_phase_by(0.1)
#print("Increased target temperature to:", round(TARGET_TEMPERATURE[phase - 1], 1))
elif 14000 <= buttons_adc < 20000:
# DOWN
state.change_target_temp_for_phase_by(-0.1)
#print("Decreased target temperature to:", round(TARGET_TEMPERATURE[phase - 1], 1))
elif 20000 <= buttons_adc < 32000:
# LEFT
state.switch_mix()
sleep(0.3) # Simple debounce
elif 32000 <= buttons_adc < 40000:
# SELECT
state.switch_phase()
sleep(0.3) # Simple debounce

def update_display(self, state: ProgramState):
if (round(self.target_temp, 1) == round(state.get_target_temp_for_phase(), 1) and
round(self.measured_temp, 1) == round(state.measured_temp, 1) and
self.phase == state.phase and
self.mix == state.mix and
self.heaters_enabled == state.heaters_enabled and
self.heaters_in_use == state.heaters_in_use):
return # No changes, skip updating the display

self.target_temp = state.get_target_temp_for_phase()
self.measured_temp = state.measured_temp
self.phase = state.phase
self.mix = state.mix
self.heaters_in_use = state.heaters_in_use.copy()
self.heaters_enabled = state.heaters_enabled.copy()

self.display.ClearScreenCursorHome()

# First row
# Temperatures
self.display.WriteLine(f" {round(self.target_temp, 1)}C {round(self.measured_temp, 1)}C", 1)
# Target temperature
self.display.DrawCustomChar(line_number=1, col=1, slot=1)
# Measured temperature
self.display.DrawCustomChar(line_number=1, col=10, slot=2)

# Second row
# Phase
self.display.WriteLine(f"F{self.phase}", 2)
# Mixer icon
self.display.DrawCustomChar(line_number=2, col=8, slot=5)
# Mixer on/off
if(self.mix):
self.display.DrawCustomChar(line_number=2, col=9, slot=6)
else:
self.display.DrawCustomChar(line_number=2, col=9, slot=7)
# Heater icon
self.display.DrawCustomChar(line_number=2, col=13, slot=4)
# Heaters on/off
if(self.heaters_enabled[0]):
if (self.heaters_in_use[0]): self.display.DrawCustomChar(line_number=2, col=14, slot=3)
else: self.display.DrawCustomChar(line_number=2, col=14, slot=0)
if(self.heaters_enabled[1]):
if (self.heaters_in_use[1]): self.display.DrawCustomChar(line_number=2, col=15, slot=3)
else: self.display.DrawCustomChar(line_number=2, col=15, slot=0)
if(self.heaters_enabled[2]):
if (self.heaters_in_use[2]): self.display.DrawCustomChar(line_number=2, col=16, slot=3)
else: self.display.DrawCustomChar(line_number=2, col=16, slot=0)
163 changes: 18 additions & 145 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,159 +1,32 @@
from control import Control
from lcd import LCD
from pid import PidController
from program_state import ProgramState
from utime import sleep
from constants import *
from constants import CYCLE_TIME_SECONDS, display, HEATER_1, HEATER_2, HEATER_3


# Program state
measured_temp: float = 0.0
phase = 1
mix = False
heaters_in_use = [False, False, False]
heaters_enabled = [True, True, True]
# Initial setup
HEATER_1.off()
HEATER_2.off()
HEATER_3.off()

# Sensors
buttons_adc = 0
roms = DS_SENSOR.scan()
# Program state
state = ProgramState()

# Screen state
screen_target_temp = 0.0
screen_measured_temp = measured_temp
screen_phase = phase
screen_mix = mix
screen_heaters_enabled = heaters_enabled.copy()
screen_heaters_in_use = heaters_in_use.copy()

# Initialize LCD
display.BackLightOn()
display.CreateChar(slot=0, bitmap=CHAR_HEATER_ENABLED_OFF)
display.CreateChar(slot=1, bitmap=CHAR_TARGET)
display.CreateChar(slot=2, bitmap=CHAR_THERMOMETER)
display.CreateChar(slot=3, bitmap=CHAR_HEATER_ENABLED_ON)
display.CreateChar(slot=4, bitmap=CHAR_HEATER)
display.CreateChar(slot=5, bitmap=CHAR_MIX)
display.CreateChar(slot=6, bitmap=CHAR_MIX_ON)
display.CreateChar(slot=7, bitmap=CHAR_MIX_OFF)

def measure_temperature():
# TODO set precision?
global measured_temp
DS_SENSOR.convert_temp()
measured = DS_SENSOR.read_temp(roms[0])

#print("Measured temperature:", measured)
measured_temp = measured
lcd = LCD(display, state)

def control_heaters():
global heaters_in_use, measured_temp, TARGET_TEMPERATURE
if measured_temp < TARGET_TEMPERATURE[phase - 1]:
heaters_in_use = [True, True, True]
HEATER_1.value(heaters_in_use[0] & heaters_enabled[0])
HEATER_2.value(heaters_in_use[1] & heaters_enabled[1])
HEATER_3.value(heaters_in_use[2] & heaters_enabled[2])
else:
heaters_in_use = [False, False, False]
HEATER_1.off()
HEATER_2.off()
HEATER_3.off()

def control_mixer():
global mix
if mix:
MIXER_PIN.on()
else:
MIXER_PIN.off()

def draw_to_lcd(target_temp, measured_temp, phase, heaters_enabled, heaters_in_use):
global screen_target_temp, screen_measured_temp, screen_phase, screen_mix, screen_heaters_in_use, screen_heaters_enabled

if (round(screen_target_temp, 1) == round(target_temp, 1) and
round(screen_measured_temp, 1) == round(measured_temp, 1) and
screen_phase == phase and
screen_mix == mix and
screen_heaters_enabled == heaters_enabled and
screen_heaters_in_use == heaters_in_use):
return # No changes, skip updating the display

screen_target_temp = target_temp
screen_measured_temp = measured_temp
screen_phase = phase
screen_mix = mix
screen_heaters_in_use = heaters_in_use.copy()
screen_heaters_enabled = heaters_enabled.copy()

display.ClearScreenCursorHome()
display.WriteLine(f" {round(screen_target_temp, 1)}C {round(screen_measured_temp, 1)}C", 1)
# Target temperature
display.DrawCustomChar(line_number=1, col=1, slot=1)
# Measured temperature
display.DrawCustomChar(line_number=1, col=10, slot=2)
# Control
pid = PidController()
control = Control(pid)

display.WriteLine(f"F{screen_phase}", 2)
# Mixer icon
display.DrawCustomChar(line_number=2, col=8, slot=5)
# Mixer on/off
if(screen_mix):
display.DrawCustomChar(line_number=2, col=9, slot=6)
else:
display.DrawCustomChar(line_number=2, col=9, slot=7)
# Heater icon
display.DrawCustomChar(line_number=2, col=13, slot=4)
# Heaters on/off
if(heaters_enabled[0]):
if (heaters_in_use[0]): display.DrawCustomChar(line_number=2, col=14, slot=3)
else: display.DrawCustomChar(line_number=2, col=14, slot=0)
if(heaters_enabled[1]):
if (heaters_in_use[1]): display.DrawCustomChar(line_number=2, col=15, slot=3)
else: display.DrawCustomChar(line_number=2, col=15, slot=0)
if(heaters_enabled[2]):
if (heaters_in_use[2]): display.DrawCustomChar(line_number=2, col=16, slot=3)
else: display.DrawCustomChar(line_number=2, col=16, slot=0)

def handle_buttons():
global TARGET_TEMPERATURE, buttons_adc, phase, mix, heaters_enabled
buttons_adc = BUTTONS_PIN_ADC.read_u16()
if 200 <= buttons_adc < 6000:
# RIGHT
if(sum(heaters_enabled) == 0):
heaters_enabled = [True, True, True]
sleep(0.3) # Simple debounce
return
heaters_enabled[sum(heaters_enabled) - 1] = False
sleep(0.3) # Simple debounce
elif 6000 <= buttons_adc < 14000:
# UP
TARGET_TEMPERATURE[phase - 1] += .1
print("Increased target temperature to:", round(TARGET_TEMPERATURE[phase - 1], 1))
elif 14000 <= buttons_adc < 20000:
# DOWN
TARGET_TEMPERATURE[phase - 1] -= .1
print("Decreased target temperature to:", round(TARGET_TEMPERATURE[phase - 1], 1))
elif 20000 <= buttons_adc < 32000:
# LEFT
mix = not mix
sleep(0.3) # Simple debounce
elif 32000 <= buttons_adc < 40000:
# SELECT
phase = (phase % 4) + 1
sleep(0.3) # Simple debounce

def array_and(b1, b2):
return [a and b for a, b in zip(b1, b2)]

while True:
try:
measure_temperature()
handle_buttons()
control_heaters()
control_mixer()
draw_to_lcd(
TARGET_TEMPERATURE[phase - 1],
measured_temp,
phase,
heaters_enabled,
heaters_in_use,
)
sleep(0.1)
control.measure_temperature(state)
lcd.handle_buttons(state)
control.control_heaters(state)
control.control_mixer(state)
lcd.update_display(state)
sleep(CYCLE_TIME_SECONDS)
except KeyboardInterrupt:
break
Loading