Aug 7, 2009
Colour Selector from UCIT course
Here are some details of the colour selector made by Jaakko Hakulinen.
The application maps data from accelerometer to colour and uses LEDs to display this colour. The x-y axis data is converted into polar coordinates and angle is then used as hue and distance as colour intensity. Brightness is always maximum. This HSB value is then converted to RGB values. In addition, touch sensor is used to switch between reading the accelerometer data and controlling the LEDs using PWM.
The system runs entirely on BS2. It does send some debug output, which can be read on PC side.
The colour selector from Jaakko Hakulinen on Vimeo.
The following contains first BASIC code for BS2 and then a small Python, which read the debug stream and displays the colour on PC screen.
—————————–
‘{$STAMP BS2}
‘{$PBASIC 2.5}
‘ —[ I/O Definitions ]—
rLED PIN 15
gLED PIN 14
yLED PIN 13
pXin PIN 1
pYin PIN 0
touch PIN 2
‘ —[ Variables ]—
‘ accelerometer variables
wXPulse VAR Word ‘ raw data from accelerometer
wXGravity VAR Word ‘ X axis gravity
wYPulse VAR Word ‘ raw data from accelerometer
wYGravity VAR Word ‘ Y axis gravity
‘ input variables for goPolar: wXGravity, wYGravity
‘ output variables for goPolar:
angle VAR Word ‘ in degrees
d VAR Byte ‘ distance from origo
‘ colour related variables
redDuty VAR Byte ‘ brightness
greenDuty VAR Byte ‘ brightness
blueDuty VAR Byte ‘ brightness
highDuty CON 255 ‘ constant max brightness
lowDuty VAR Byte ‘ temp value for brighness
medDuty VAR Byte ‘ temp value for brighness
‘ —[ Constants ]—
cPulseState CON 1
cScale CON $200 ‘ 2.0 us per unit
cBS2 CON $100 ‘ x 1.0, cycle adjustment (for ms)
cCycle CON 5 ‘Required Charge time (Cycle equation) = 5 * Resistance * Capacity
‘Charge time = 5 * 10KR(10000R) * 0.1µF(1/1000000F) = 0.005 seconds = 5ms
‘ must use short charge time to keep three leds reasonably stable
‘ with PWM. Must use 0.1uf (104) capasitators to get acceptable time
‘ —[ Main Code ]—
Main:
DEBUG “Colour flower”, CR
DO
GOSUB Read_Accelerometer
‘ convert x and y accelometer data points to polar coordinates, i.e.,
‘ angle (variable angle) AND distance (variable d). Angle is in degrees,
‘ distance is between 0 AND 128.
GOSUB goPolar
DEBUG “angle: “, DEC angle, ” d: “, DEC d, CR
‘ hue of final colour is mapped to angle, saturation to distance
‘ brightness of HSB model is alway max.
‘ one colour always has max intensity
‘ another one has lowest intensity, when saturation is
‘ maximum, low value is 0 and when saturation is minimum,
‘ LOW value is maximum intensity.
‘ Medium colour has intensity between max and low value,
‘ its intensity depends on hue and saturation
‘ which colour (r, g, b) is which, depends on hue
‘ calculate colours based on the polar coordinates
‘ calculate medium intensity initial value (pre saturation)
IF angle / 60 & 1 = 0 THEN rising
IF angle / 60 & 1 > 0 THEN lowering
rising:
‘ medium value is rising in this range
medDuty = (angle // 60)
GOTO riseandlow
lowering:
‘ medium value is decreasing in this range
medDuty = (60 – (angle // 60))
GOTO riseandlow
riseandlow:
‘ scale from 0-60 to 0-256, very roughly
medDuty = medDuty */ $0444 ‘ * 4.25 approx
‘ calculate the low and med values (from saturation)
‘ clamp distance
IF d > 127 THEN d = 127
lowDuty = 256 – (2 * d)
medDuty = medDuty+(255-medDuty)-((255-medDuty)*d/128)
‘ branch based on degrees, map low, med, hi to colours
BRANCH angle / 60, [case_rg, case_gr, case_gb, case_bg, case_br, case_rb]
‘ in case names, first letter refers to high value color, second to med
case_rg:
redDuty = highDuty
greenDuty = medDuty
blueDuty = lowDuty
GOTO overCases
case_gr:
redDuty = medDuty
greenDuty = highDuty
blueDuty = lowDuty
GOTO overCases
case_gb:
redDuty = lowDuty
greenDuty = highDuty
blueDuty = medDuty
GOTO overCases
case_bg:
redDuty = lowDuty
greenDuty = medDuty
blueDuty = highDuty
GOTO overCases
case_br:
redDuty = medDuty
greenDuty = lowDuty
blueDuty = highDuty
GOTO overCases
case_rb:
redDuty = highDuty
greenDuty = lowDuty
blueDuty = medDuty
GOTO overCases
overCases:
DEBUG “built color: “, DEC redDuty, ” “, DEC greenDuty, ” “, DEC blueDuty, “.”, CR
GOSUB illuminateLEDs
LOOP
END
‘ —[ Subroutines ]—
Read_Accelerometer:
DO
‘ PULSIN Pin, State, Variable
‘ Pin: Pin number (0 – 15)
‘ State: Specifies whether the puse to be mesured is low(0) or high(1)
‘ Variable: the measured pulse duration will be stored.
PULSIN pXin, cPulseState, wXPulse ‘ read pulse output
wXPulse = wXPulse */ cScale ‘ convert to uSecs for BS2
wXGravity = ((wXPulse / 10) – 500) ‘ -127-128 scale when g <= 1
PULSIN pYin, cPulseState, wYPulse
wYPulse = wYPulse */ cScale
wYGravity = ((wYPulse / 10) – 500)
greenDuty = wYGravity
‘ check touch sensor to know if its time to light the leds again
IF touch = 1 THEN EXIT
LOOP
DEBUG “Broke out of sensor loop”, CR
RETURN
illuminateLEDs:
DO
‘ Pulse-width modulation (PWM)
‘ PWM Pin, Duty, Cycles
‘ Pin: Pin number (0-15)
‘ Duty: Specifies the analog output level (0-5v)
‘ Cycles: Specifies the duration of the PWM signal
PWM rLED, redDuty, cCycle*/cBS2 ‘PWM Pin(0-15), Duty(0-255), Cycles(0-255)ms
‘Average Voltage = (Duty / 255) * 5 volts.
PWM gLED, greenDuty, cCycle*/cBS2 ‘PWM Pin(0-15), Duty(0-255), Cycles(0-255)ms
PWM yLED, blueDuty, cCycle*/cBS2 ‘PWM Pin(0-15), Duty(0-255), Cycles(0-255)ms
‘ Check touch sensor to see, if we need to start reading position again
IF touch = 0 THEN EXIT
LOOP
DEBUG “Broke out of led loop”, CR
RETURN
goPolar:
‘ calculate angle and distance from cartesian coordinates
angle = wXGravity ATN wYGravity
‘ from drads to degrees
angle = angle * 180 / 128
‘ distance
d = wXGravity HYP wYGravity
RETURN
—————————–
The following is a Python program, which reads serial port and parses the debug data to display the selected colour on screen.
This is very ugly python code, so don’t use it as a good example.
The program uses pySerial (http://pyserial.sourceforge.net/), which is not part of Python so it needs to be installed separately, for serial communication and the used serial port is hardcoded so this won’t most likely work without modifications for anybody else.
In addition, indentation, which is relevant inPython, is likely wrong.
—————————–
import serial
import threading
from Tkinter import *
import re
colourPattern = re.compile(“built color..(\d+).(\d+?).(\d+?)\.”)
ser = serial.Serial(10);
# ser.open()
def readNumber():
res = “”
while (True):
r = ser.read(1);
if (r != ‘\r’):
res += r
else:
print(res)
if (res.isdigit()):
return int(res)
res = “”
def readColour():
res = “”
while (True):
r = ser.read(1);
if (r != ‘\r’):
res += r
else:
print res
match = colourPattern.search(res)
if match != None:
print “found colour”
return (int(match.group(1)), int(match.group(2)), int(match.group(3)))
res = “”
class Application(Frame):
def say_hi(self, number):
if (number == 1):
self.txt[“bg”] = “white”
else:
self.txt[“bg”] = “black”
def setColour(self, colour):
print “in set colour”
red = hex(colour[0])[2:4]
if (len(red) == 1):
red = “0” + red
green = hex(colour[1])[2:4]
if (len(green) == 1):
green = “0” + green
blue = hex(colour[2])[2:4]
if (len(blue) == 1):
blue = “0” + blue
hexCol = “#” + red + green + blue
print colour, hexCol
self.txt[“bg”] = hexCol
def createWidgets(self):
self.txt = Text(self, width = 100, height = 20)
self.txt.pack({“side”: “top”})
self.QUIT = Button(self)
self.QUIT[“text”] = “QUIT”
self.QUIT[“fg”] = “red”
self.QUIT[“command”] = self.quit
self.QUIT.pack({“side”: “bottom”})
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
class Reader(threading.Thread):
def __init__(self, app):
threading.Thread.__init__(self)
self.app = app
def run(self):
current = -1
while (True):
col = readColour()
print “received colour ” + str(col[0]) + str(col[1]) + str(col[2])
if (col != None):
app.setColour(col)
print(col)
root = Tk()
app = Application(master=root)
background = Reader(app)
background.start()
app.mainloop()
root.destroy()