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