Skip to content

Instantly share code, notes, and snippets.

@cjhearn
Last active November 20, 2022 14:39
Show Gist options
  • Save cjhearn/5ec428dd61f8c947e6f078e26d4f41ca to your computer and use it in GitHub Desktop.
Save cjhearn/5ec428dd61f8c947e6f078e26d4f41ca to your computer and use it in GitHub Desktop.
Raspberry Pi Pico-Powered Borg Mask
################
# BORG COSTUME #
# Chris Hearn #
# 2022 #
################
# Wishlist
from machine import ADC, Pin, PWM, Timer
from time import sleep
import utime
import _thread
import random
# SETUP
# LEDs
brightLevel = 2 # 5 steps of 13005, default = 3 = 39015 = 60%
# we've moved the setting of individual levels to a function, set later
ledList = ["led1"]
led1 = PWM(Pin(25))
timLED = Timer()
# Battery
batteryFull = 4.28
batteryEmpty = 2.75
vsys = ADC(29)
batteryCharging = Pin(24, Pin.IN)
batteryCF = 3 * 3.3 / 65535
timBatt = Timer()
# Eyepiece LED circle
eyeLock = _thread.allocate_lock()
circle = 1
runCircle = True
spinMode = 1 # 1 = with fade, 2=no fade, 3=full on static, 4=off
c1 = PWM(Pin(18))
c2 = PWM(Pin(19))#20
c3 = PWM(Pin(21))#21
c4 = PWM(Pin(20))#19
cC = PWM(Pin(17))
c1.freq(1000)
c2.freq(1000)
c3.freq(1000)
c4.freq(1000)
cC.freq(1000)
timCircle = Timer()
# Servo
MIN = 900000 # was 450000
MID = 1500000
MAX = 1600000
pos = MID
servo = PWM(Pin(22))
servo.freq(50)
servo.duty_ns(MID)
servoLocked = False
timServo = Timer()
runServo = True
# Ultrasonic Distance Sensor
trigger = Pin(3, Pin.OUT)
echo = Pin(2, Pin.IN, Pin.PULL_DOWN)
ultraPower = Pin(4, Pin.OUT)
ultraPower.high()
timDistance = Timer()
# Button breakout board
rows = [11,10,13]
cols = [14,15]
KEY_UP = const(0)
KEY_DOWN = const(1)
keys = [['2', '1'],['3','4'],['5', '6']] # each tuple represents a row
row_pins = [Pin(pin_name, mode=Pin.IN, pull=Pin.PULL_DOWN) for pin_name in rows]
col_pins = [Pin(pin_name, mode=Pin.OUT, pull=Pin.PULL_UP) for pin_name in cols]
# row_pins = [Pin(pin_name, mode=Pin.OUT) for pin_name in rows]
# col_pins = [Pin(pin_name, mode=Pin.IN, pull=Pin.PULL_DOWN) for pin_name in cols]
timButtons = Timer()
def brightSet():
# Set the duty phases for the LEDs. This function gets called once at power on, then with every push of the brightness adjust button
global brightLevel
global brightDuty
global brightHalf
global brightQtr
brightDuty = brightLevel * 13005
brightHalf = round(brightDuty / 2)
brightQtr = round(brightDuty / 4)
def buttonInit():
# Set the pins for the breakout button board
for row in range(0,3):
for col in range(0,2):
row_pins[row].low()
def buttonRead(timer):
# Scan the breakout button board for pushes, and trigger an action
global brightLevel
global runServo
global spinMode
key = None
for row in range(3):
for col in range(2):
col_pins[col].high()
if row_pins[row].value() == KEY_DOWN:
key = KEY_DOWN
if row_pins[row].value() == KEY_UP:
key = KEY_UP
col_pins[col].low()
if key == KEY_DOWN:
print("Key pressed = "+ keys[row][col])
last_key_press = keys[row][col]
if last_key_press == "1": # check battery level
sleep(0.2)
runCircle = False
batteryCheck()
elif last_key_press == "2": # LED brightness
sleep(0.2)
print("Brightness level was " + str(brightLevel))
brightLevel += 1
if brightLevel > 3:
brightLevel = 1
brightSet()
print("Brightness level now " + str(brightLevel))
elif last_key_press == "3": # eyepiece spin mode
sleep(0.2)
spinMode += 1
if spinMode > 4:
spinMode = 1
print("Eyepiece spin mode now " + str(spinMode))
elif last_key_press == "4": # start / stop servo scanning
sleep(0.2)
if runServo == True:
runServo = False
servo.duty_ns(MID)
print("Servo off")
else:
runServo = True
print("Servo on")
elif last_key_press == "5":
sleep(0.2)
elif last_key_press == "6":
sleep(0.2)
def batteryCheck():
# Read the LiPo battery voltage and display it as percentages in eyepiece circle
global runCircle
eyeLock.acquire()
led1.duty_u16(0)
batteryVoltage = vsys.read_u16() * batteryCF
batteryPercentage = 100 * ((batteryVoltage - batteryEmpty) / (batteryFull - batteryEmpty))
print("Battery voltage: " + str(batteryVoltage))
print("Battery percent: " + str(batteryPercentage))
if 0 <= batteryPercentage <= 25:
c1.duty_u16(brightDuty)
c2.duty_u16(0)
c3.duty_u16(0)
c4.duty_u16(0)
cC.duty_u16(0)
print("Battery check: 0-25%")
sleep(1)
for bf in range(1):
led1.duty_u16(brightDuty)
sleep(0.5)
led1.duty_u16(0)
sleep(0.5)
elif 25.0001 <= batteryPercentage <= 50:
c1.duty_u16(brightDuty)
c2.duty_u16(brightDuty)
c3.duty_u16(0)
c4.duty_u16(0)
cC.duty_u16(0)
print("Battery check: 25-50%")
sleep(1)
for bf in range(2):
led1.duty_u16(brightDuty)
sleep(0.5)
led1.duty_u16(0)
sleep(0.5)
elif 50.0001 <= batteryPercentage <= 75:
c1.duty_u16(brightDuty)
c2.duty_u16(brightDuty)
c3.duty_u16(brightDuty)
c4.duty_u16(0)
cC.duty_u16(0)
print("Battery check: 50-75%")
sleep(1)
for bf in range(3):
led1.duty_u16(brightDuty)
sleep(0.5)
led1.duty_u16(0)
sleep(0.5)
elif 75.0001 <= batteryPercentage <= 105:
c1.duty_u16(brightDuty)
c2.duty_u16(brightDuty)
c3.duty_u16(brightDuty)
c4.duty_u16(brightDuty)
cC.duty_u16(0)
print("Battery check: 75-100%")
sleep(1)
for bf in range(4):
led1.duty_u16(brightDuty)
sleep(0.5)
led1.duty_u16(0)
sleep(0.5)
else: # something's wrong, show centre lights but nothing on ring
c1.duty_u16(0)
c2.duty_u16(0)
c3.duty_u16(0)
c4.duty_u16(0)
cC.duty_u16(brightDuty)
print("Battery check error")
sleep(1)
for bf in range(10):
led1.duty_u16(brightDuty)
sleep(0.2)
led1.duty_u16(0)
sleep(3)
eyeLock.release()
runCircle = True
def batteryAutoCheck(timer):
# Read the LiPo battery voltage every 10 minutes and reduce brightness if necessary
global brightLevel
batteryVoltage = vsys.read_u16() * batteryCF
batteryPercentage = 100 * ((batteryVoltage - batteryEmpty) / (batteryFull - batteryEmpty))
if 0 <= batteryPercentage <= 20:
newBright = 1
elif 20.0001 <= batteryPercentage <= 40:
newBright = 2
elif 40.0001 <= batteryPercentage <= 60:
newBright = 3
elif 60.0001 <= batteryPercentage <= 80:
newBright = 4
elif 80.0001 <= batteryPercentage <= 105:
newBright = 5
else: # something's wrong, go to minimum brightness just in case
newBright = 1
if brightLevel > newBright:
brightLevel = newBright
def servoScan(timer):
# Move the servo (and its LED) as if it's scanning the area
global servoLocked
global pos
if runServo == True:
if servoLocked == False:
posmin = pos * 0.7
posmax = pos * 1.3
if posmin < MIN:
posmin = MIN
if posmax > MAX:
posmax = MAX
#pos = (random.randint(int(posmin),int(posmax)))
pos = random.randint(MIN,MAX)
servo.duty_ns(pos)
# print("Servo random move to "+str(pos))
else:
servo.duty_ns(MID)
# print("Servo lock")
def distanceCheck(timer):
# Pulse the ultrasonic sensor to see if anything is close
global servoLocked
trigger.low()
utime.sleep_us(2)
trigger.high()
utime.sleep_us(2)
trigger.low()
while echo.value() == 0:
signaloff = utime.ticks_us()
while echo.value() == 1:
signalon = utime.ticks_us()
timepassed = signalon - signaloff
distance = (timepassed * 0.0343) / 2
# print("The distance from object is ",distance,"cm")
if distance < 50:
servoLocked = True
cC.duty_u16(brightDuty)
print("Near object detected")
else:
servoLocked = False
cC.duty_u16(0)
# print("No near object detected")
def circleSpin(timer):
# Runs on core 1. Controls the LEDs in the eyepiece.
global circle
global brightDuty
global brightQtr
global brightHalf
global brightLevel
print("Bright level = " + str(brightLevel) + "; bright duty = " + str(brightDuty) + "; bright half = " + str(brightHalf) + "; bright qtr = " + str(brightQtr))
eyeLock.acquire()
if runCircle == True:
if spinMode == 1: # fade the two LEDs behind leader
if circle == 1:
c1.duty_u16(brightDuty)
c2.duty_u16(0)
c3.duty_u16(brightQtr)
c4.duty_u16(brightHalf)
print("Eyepiece LED 1 with fade")
elif circle == 2:
c1.duty_u16(brightHalf)
c2.duty_u16(brightDuty)
c3.duty_u16(0)
c4.duty_u16(brightQtr)
print("Eyepiece LED 2 with fade")
elif circle == 3:
c1.duty_u16(brightQtr)
c2.duty_u16(brightHalf)
c3.duty_u16(brightDuty)
c4.duty_u16(0)
print("Eyepiece LED 3 with fade")
elif circle == 4:
c1.duty_u16(0)
c2.duty_u16(brightQtr)
c3.duty_u16(brightHalf)
c4.duty_u16(brightDuty)
print("Eyepiece LED 4 with fade")
circle += 1
if circle > 4:
circle = 1
elif spinMode == 2: # no fade behind leader LED
if circle == 1:
c1.duty_u16(brightDuty)
c2.duty_u16(0)
c3.duty_u16(0)
c4.duty_u16(0)
print("Eyepiece LED 1")
elif circle == 2:
c1.duty_u16(0)
c2.duty_u16(brightDuty)
c3.duty_u16(0)
c4.duty_u16(0)
print("Eyepiece LED 2")
elif circle == 3:
c1.duty_u16(0)
c2.duty_u16(0)
c3.duty_u16(brightDuty)
c4.duty_u16(0)
print("Eyepiece LED 3")
elif circle == 4:
c1.duty_u16(0)
c2.duty_u16(0)
c3.duty_u16(0)
c4.duty_u16(brightDuty)
print("Eyepiece LED 4")
circle += 1
if circle > 4:
circle = 1
elif spinMode == 3: # all LEDs stay on
c1.duty_u16(brightDuty)
c2.duty_u16(brightDuty)
c3.duty_u16(brightDuty)
c4.duty_u16(brightDuty)
print("Eyepiece static")
elif spinMode == 4: # all LEDs stay off
c1.duty_u16(0)
c2.duty_u16(0)
c3.duty_u16(0)
c4.duty_u16(0)
print("Eyepiece LEDs off")
eyeLock.release()
def ledFlash(timer):
# Randomly flicker the additional LEDs to give a HDD or network activity-style effect
for x in ledList:
rndLED = random.randint(1,10)
if rndLED >= 6:
eval(x).duty_u16(round(brightDuty / 6))
else:
eval(x).duty_u16(round(brightQtr / 6))
def core1_thread():
# Triggered by the main code below, let core 1 handle the eyepiece circle LEDs, and random scan motion of the servo
timCircle.init(period=1000, callback=circleSpin)
timServo.init(period=750, callback=servoScan)
# timBatt.init(period=600000, callback=batteryAutoCheck)
# EXECUTE
# We're all setup, now get things started!
# First, let's set some more initial variables, and initialise the breakout button board
brightSet()
buttonInit()
# On core 0, start the random LED flashes, distance scanning, and button scanning
timLED.init(freq=15, callback=ledFlash)
timDistance.init(freq=1, callback=distanceCheck)
timButtons.init(freq=4, callback=buttonRead)
# On core 1, run the eyepiece LED circle and servo motion
_thread.start_new_thread(core1_thread,())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment