from machine import Pin, SPI, ADC, PWM
import time
# ==========================================
# LCD CONNECTIONS
# ==========================================
# SPI communication
spi = SPI(
0,
baudrate=40000000,
polarity=0,
phase=0,
sck=Pin(18),
mosi=Pin(19)
)
# LCD control pins
cs = Pin(17, Pin.OUT)
rst = Pin(16, Pin.OUT)
dc = Pin(20, Pin.OUT)
# ==========================================
# TURBINE CONTROL
# ==========================================
# Turbine slider SIG is connected to GP26.
turbine_slider = ADC(Pin(26))
# ==========================================
# FISSION CONTROL
# ==========================================
# Fission slider SIG is connected to GP27.
fission_slider = ADC(Pin(27))
# ==========================================
# TURBINE GAUGE SERVO
# ==========================================
# Servo PWM is connected to GP14.
turbine_servo = PWM(Pin(14))
turbine_servo.freq(50)
def move_turbine_servo(angle):
# Keep the angle between 0 and 180 degrees.
angle = max(0, min(180, angle))
# Our previously calibrated servo range.
min_duty = 1638
max_duty = 7864
# Convert 0-180 degrees into PWM.
duty = int(
min_duty
+ (angle / 180)
* (max_duty - min_duty)
)
turbine_servo.duty_u16(duty)
# ==========================================
# LCD COMMAND FUNCTIONS
# ==========================================
def write_command(command):
dc.value(0)
cs.value(0)
spi.write(bytes([command]))
cs.value(1)
def write_data(data):
dc.value(1)
cs.value(0)
spi.write(bytes(data))
cs.value(1)
# ==========================================
# RESET LCD
# ==========================================
rst.value(0)
time.sleep(0.1)
rst.value(1)
time.sleep(0.1)
# ==========================================
# INITIALIZE ILI9341
# ==========================================
write_command(0x01) # Software reset
time.sleep(0.15)
write_command(0x11) # Sleep out
time.sleep(0.15)
# 16-bit RGB565 color mode
write_command(0x3A)
write_data([0x55])
# Set display orientation/color order.
write_command(0x36)
write_data([0x28])
# Turn display on
write_command(0x29)
print("LCD initialized!")
# ==========================================
# DRAW A SOLID COLOR
# ==========================================
def set_window(x0, y0, x1, y1):
# Tell the LCD which columns we want to draw in.
write_command(0x2A)
write_data([
x0 >> 8,
x0 & 0xFF,
x1 >> 8,
x1 & 0xFF
])
# Tell the LCD which rows we want to draw in.
write_command(0x2B)
write_data([
y0 >> 8,
y0 & 0xFF,
y1 >> 8,
y1 & 0xFF
])
# Tell the LCD we're about to send pixel colors.
write_command(0x2C)
def fill_screen(color):
# ILI9341 resolution is 320 wide × 240 tall.
set_window(0, 0, 319, 239)
# RGB565 uses two bytes per pixel.
high_byte = color >> 8
low_byte = color & 0xFF
pixel = bytes([high_byte, low_byte])
dc.value(1)
cs.value(0)
# Send one row at a time.
row = pixel * 320
for _ in range(240):
spi.write(row)
cs.value(1)
# RGB565 red
RED = 0xF800
def fill_rect(x, y, width, height, color):
# Select the rectangle we want to draw.
set_window(
x,
y,
x + width - 1,
y + height - 1
)
# Convert our RGB565 color into two bytes.
high_byte = color >> 8
low_byte = color & 0xFF
pixel = bytes([high_byte, low_byte])
dc.value(1)
cs.value(0)
# Create one horizontal row of pixels.
row = pixel * width
# Draw that row repeatedly to make the rectangle.
for _ in range(height):
spi.write(row)
cs.value(1)
# ==========================================
# TEST COLORS
# ==========================================
GREEN = 0x07E0
ORANGE = 0xFD20
BLUE = 0x001F
# ==========================================
# FIRST TURBINE GAUGE TEST
# ==========================================
BLACK = 0x0000
# Pretend the submarine currently needs
# about 60% turbine output.
target_turbine = 60
# Width of our gauge.
gauge_x = 20
gauge_y = 90
gauge_width = 280
gauge_height = 60
# Clear the screen.
fill_screen(BLACK)
# Draw the entire gauge orange first.
fill_rect(
gauge_x,
gauge_y,
gauge_width,
gauge_height,
ORANGE
)
# Give the recommended green area a temporary
# width of 20% of the entire gauge.
green_width_percent = 20
# Calculate where the green region starts.
green_start_percent = (
target_turbine - green_width_percent / 2
)
# Convert percentages into screen pixels.
green_x = int(
gauge_x
+ (green_start_percent / 100) * gauge_width
)
green_width = int(
(green_width_percent / 100) * gauge_width
)
# Draw the recommended region.
fill_rect(
green_x,
gauge_y,
green_width,
gauge_height,
GREEN
)
print("Target turbine:", target_turbine, "%")
# ==========================================
# FAKE SUBMARINE POWER SYSTEM
# ==========================================
# Maximum electrical output of our
# pretend reactor.
reactor_max_output = 5000
# Different loads our pretend submarine
# will experience.
ship_loads = [
1000,
2000,
3000,
4000,
2500,
1500
]
# Start with the first submarine load.
load_index = 0
ship_load = ship_loads[load_index]
# Remember when we last changed the load.
last_load_change = time.ticks_ms()
# ==========================================
# REACTOR TEMPERATURE
# ==========================================
# Starting temperature for our reactor simulation.
reactor_temperature = 50.0
while True:
# ======================================
# READ TURBINE CONTROL
# ======================================
raw_turbine = turbine_slider.read_u16()
actual_turbine = (
raw_turbine / 65535
) * 100
# ======================================
# READ FISSION CONTROL
# ======================================
# Read the physical fission slider.
raw_fission = fission_slider.read_u16()
# Convert the ADC reading into 0-100%.
actual_fission = (
raw_fission / 65535
) * 100
# ======================================
# REACTOR HEAT TEST
# ======================================
# Fission adds heat to the reactor.
heat_produced = (
actual_fission / 100
) * 0.1
# Turbine operation removes/uses heat.
heat_used = (
actual_turbine / 100
) * 0.1
# Temperature changes based on the
# balance between the two.
reactor_temperature += (
heat_produced - heat_used
)
# Keep simulated temperature within
# our temporary 0-100 range.
reactor_temperature = max(
0,
min(100, reactor_temperature)
)
# Convert 0-100% turbine output
# into 0-180 degrees for the servo.
turbine_angle = (
actual_turbine / 100
) * 180
move_turbine_servo(turbine_angle)
# ======================================
# CHECK SUBMARINE LOAD TIMER
# ======================================
current_time = time.ticks_ms()
if time.ticks_diff(
current_time,
last_load_change
) >= 2000:
# Move to the next fake ship load.
load_index += 1
# If we reach the end of the list,
# go back to the beginning.
if load_index >= len(ship_loads):
load_index = 0
ship_load = ship_loads[load_index]
# Remember when this change happened.
last_load_change = current_time
# ==================================
# CALCULATE REQUIRED TURBINE
# ==================================
required_turbine = (
ship_load / reactor_max_output
) * 100
required_turbine = max(
0,
min(100, required_turbine)
)
# ==================================
# UPDATE LCD TARGET
# ==================================
# Reset gauge to orange.
fill_rect(
gauge_x,
gauge_y,
gauge_width,
gauge_height,
ORANGE
)
green_start_percent = (
required_turbine
- green_width_percent / 2
)
green_start_percent = max(
0,
min(
100 - green_width_percent,
green_start_percent
)
)
green_x = int(
gauge_x
+ (green_start_percent / 100)
* gauge_width
)
fill_rect(
green_x,
gauge_y,
green_width,
gauge_height,
GREEN
)
# ==================================
# TERMINAL INFORMATION
# ==================================
print(
"Ship Load:",
ship_load,
"kW",
"| Required Turbine:",
round(required_turbine, 1),
"%",
"| Your Turbine:",
round(actual_turbine, 1),
"%",
"| Your Fission:",
round(actual_fission, 1),
"%",
"| Temperature:",
round(reactor_temperature, 1)
)
# Tiny delay so we don't hammer the simulator.
time.sleep(0.02)