Given that I don't know Python worth a dang, the most challenging part was the Python. First, I searched google for Python GUI, and found a plethora of packages. After a brief browse I went for the "native package" TkInter. Starting with a few lines of example code, I got the buttons, slider bars and everything on the screen. Writing to serial was as simple as writing to a console via serial.write().
The GUI, show above, was to have buttons to connect to the Arduino via the serial port, three sliding scrollbars for red green and blue LED intensity, and a 'disco mode' button which cycles through the colours. The program window is shown here:
There were a few nuisances. One of them was in naming. What I wanted was a sliding widget which was a scrollbar returning an interger withing a specific range. In Java, it's called a Scrollbar. In tKinter, a Scrollbar is a separate object, which is attached to other objects. What I really wanted was a "Scale". Next, in Java, there is a function called whenever the Scrollbar is updated, so that when the user slides from 12 to 25, you can automatically run code. As far as I could tell, this is not the case in TkInter. There are several quick workarounds: one is to have a button run a function which polls the Scales. A slightly better solution is binding which allows you to run code every time some pre-determined thing (releasing the right mouse-button for example) in a widget.
The code I used is here:
from Tkinter import *
import serial
import time
class Application(Frame):
# Store LED brightness, stored as integers, sent as bytes
rBrightness = -1
gBrightness = 0
bBrightness = 0
# Slight delay between sending serial data so as not to miss a char
# Possibly unnecessary.
delay = 0.01
# Strictly for keeping track of what label to put on buttons
on = False;
discoOn = False;
# GUI action Function definitions
# Open or close the serial port connection. For now hardwired to COM3
def toggle_connect(self):
if self.ser.isOpen():
self.ser.close()
print("Closed COM 3")
self.COM["text"] = "Open",
else:
self.ser = serial.Serial('COM3', 9600, timeout=0)
print("Opened COM 3")
self.COM["text"] = "Close",
# Shuts down the python interpreter. Last resort for port closing
def shut_down(self):
self.ser.close()
exit()
# Send a code to Arduino to switch LED off.
def send_on_off(self):
self.ser.write('0')
if self.on:
self.on = False
self.LED["text"] = "Turn On LED"
else:
self.on = True
self.LED["text"] = "Turn Off LED"
# Disco light show baby.
def disco_on_off(self):
self.ser.write('d')
if self.discoOn:
self.discoOn = False
self.disco["text"] = "Start Party"
else:
self.discoOn = True
self.disco["text"] = "Stop Party"
# Sends RGB values to the arduino encoded as a 'char'
# Method is bound to mouse clicks on the slider
def update_LED(self,event):
self.rBrightness = self.scaleRed.get()
self.gBrightness = self.scaleGreen.get()
self.bBrightness = self.scaleBlue.get()
print("(R,G,B) = (%i,%i,%i)") %(self.rBrightness,self.gBrightness,self.bBrightness)
self.ser.write('1')
time.sleep(self.delay)
self.ser.write(str(unichr(self.rBrightness)))
time.sleep(self.delay)
self.ser.write('2')
time.sleep(self.delay)
self.ser.write(str(unichr(self.gBrightness)))
time.sleep(self.delay)
self.ser.write('3')
time.sleep(self.delay)
self.ser.write(str(unichr(self.bBrightness)))
# GUI stuff
def createWidgets(self):
# First, create buttons
self.QUIT = Button(self)
self.QUIT["text"] = "QUIT"
self.QUIT["fg"] = "red"
self.QUIT["command"] = self.shut_down
self.COM = Button(self)
self.COM["text"] = "Open",
self.COM["command"] = self.toggle_connect
self.LED = Button(self)
self.LED["text"] = "Turn On LED"
self.LED["command"] = self.send_on_off
self.disco = Button(self)
self.disco["text"] = "Start Party"
self.disco["command"] = self.disco_on_off
# Sliders (if this were java, I'd say scroll bars. In python, scrollbar is another thing altogether)
self.scaleRed = Scale(self,from_=0, to=255)
self.scaleRed["bg"] = "red"
self.scaleGreen = Scale(self,from_=0, to=255)
self.scaleGreen["bg"] = "green"
self.scaleBlue = Scale(self,from_=0, to=255)
self.scaleBlue["bg"] = "blue"
# Add widgets to the screen
self.QUIT.pack({"side": "left"})
self.COM.pack({"side": "left"})
self.LED.pack({"side": "left"})
self.scaleRed.pack({"side": "left"})
self.scaleGreen.pack({"side": "left"})
self.scaleBlue.pack({"side": "left"})
self.disco.pack({"side": "left"})
# Bind scrollbars to mouseclicks
self.scaleGreen.bind('',self.update_LED)
self.scaleBlue.bind('',self.update_LED)
self.scaleRed.bind('',self.update_LED)
# initialize GUI
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.ser = serial.Serial('COM3', 9600, timeout=0)
self.ser.close()
root = Tk()
app = Application(master=root)
app.mainloop()
root.destroy()
The Arduino code was next. The idea was to have have the Arduino constantly looking for a new char sent through the COM port. If it receives a particular character, it will change state accordingly. For example, a '0' will toggle the LED output on or off. A 'd' will start disco party mode. A '1', '2', or '3' will put the chip in a listening state for red, green or blue respectively. In this state, the next char received is assigned to the corresponding LED's brightness. The code shown here:
// Accept commands from serialComm.py Python script
// Controls the RGB values of a 3-color LED.
// Optional 'disco mode' which cycles through the colours
// Author: Andrew MacRae (macrae@berkeley.edu)
// PWM output ports
const int RLED = 9;
const int GLED = 10;
const int BLED = 11;
// Device states
const int LISTEN = 0; // Accepting data.
const int READ_RED = 1; // next char read assigned to RED.
const int READ_GREEN = 2;// Same, but for green.
const int READ_BLUE = 3; // ditto, for blue.
boolean on = false; // Outputing to the LED?
boolean disco = false; // Disco mode
// Stores the RGB values recieved from the GUI
int rBright = 28;
int gBright = 128;
int bBright = 68;
// For disco mode. These will be automatically adjusted
int iR = 0;
int iG = 0;
int iB = 0;
byte byteRead = 'x'; // current byte read from serial
int state = LISTEN; // Initially, wait for instructions
// Setup ports for input and start the serial connection
void setup()
{
pinMode(RLED, OUTPUT);
pinMode(GLED, OUTPUT);
pinMode(BLED, OUTPUT);
Serial.begin(9600);
}
// main loop
void loop()
{
if (Serial.available())
{
// is a byte has been sent, store it
byteRead = Serial.read();
// If previously recieved instruction to read in colour,
// set the value to that of the byte and reset state to LISTEN
if(state == READ_RED)
{
rBright = byteRead;
state = LISTEN;
}
else if(state == READ_GREEN)
{
gBright = byteRead;
state = LISTEN;
}
else if(state == READ_BLUE)
{
bBright = byteRead;
state = LISTEN;
}
// Otherwaise, check if state change is needed:
// 'd' means toggle disco mode
else
{
if(byteRead == 'd')
{
disco = !disco;
}
// '0' means toggle output on/off
if(byteRead == '0')
{
on = !on;
byteRead= 'x';
}
// '1'/'2'/'3' means prepare to read in red/green/blue
if(byteRead == '1')
{
state = READ_RED;
}
if(byteRead == '2') // Toggle On/Off
{
state = READ_GREEN;
}
if(byteRead == '3') // Toggle On/Off
{
state = READ_BLUE;
}
}
}
// if the LED is on
if(on)
{
// loop disco mode variables
iR+=3;
iG+=5;
iB+=7;
if(iR>255) iR=0;
if(iG>255) iG=0;
if(iB>255) iB=0;
// and use these variables if in disco mode
if(disco)
{
analogWrite(RLED,iR);
analogWrite(GLED,iG);
analogWrite(BLED,iB);
delay(30);
}
// otherwaise use the variables set by the user
else
{
analogWrite(RLED,rBright);
analogWrite(GLED,gBright);
analogWrite(BLED,bBright);
}
}
// if off, set all outputs low
else
{
digitalWrite(RLED,LOW);
digitalWrite(GLED,LOW);
digitalWrite(BLED,LOW);
}
}
The circuit is simplicity itself. Three separate PWM outputs are sent to the led which is grounded through a 100 Ohm resistor. The green and blue chanels get an extra 220 Ohms to set the relative brightness of each LED about equal. A sketch is included below:
Finally, here's a very crappily shot video of the circuit in action! To reduce the brightness and display the colour more uniformly, I used a high-tech solution known as a Perforated Fibrous Diffusive Membrane. Paper towel to the layperson.
http://www.youtube.com/watch?v=MZN09ancg_U


No comments:
Post a Comment