Whee! This entry was the basis of the cover article of The MagPi issue 7. Read it on Issuu, or download the PDF.
Okay, so maybe I can stop answering the StackExchange question “How to attach an Arduino?” now. While I got the Arduino working with pyFirmata on the Raspberry Pi before, it wasn’t that pretty. With a TkInter front end, it actually looks like some effort was involved. You can happily brighten and dim the LED attached to the Arduino all you want, while the temperature quietly updates on the screen independent of your LED frobbing.
I’d never used TkInter before. For tiny simple things like this, it’s not that hard. Every widget needs a callback; either a subroutine it calls every time it is activated, or a variable that the widget’s value is tied to. In this case, the Scale widget merely calls a function set_brightness()
that sets a PWM value on the Arduino.
Updating the temperature was more difficult, though. After TkInter has set up its GUI, it runs in a loop, waiting for user events to trigger callback events. It doesn’t really allow you to run another loop alongside its main event loop. What you have to do then is set up a routine which is called periodically using TkInter’s after()
function, which calls a subroutine after a set amount of time. If this subroutine ends with another call to after()
to call itself again, it will maintain its own event loop separate from TkInter’s GUI loop. This is what I do in the get_temp()
subroutine, which schedules itself after a ½ second.
#!/usr/bin/python # -*- coding: utf-8 -*- # graphical test of pyfirmata and Arduino; read from an LM35 on A0, # brighten an LED on D3 using PWM # Connections: # - small LED connected from D3, through a 1kΩ resistor to GND; # - LM35: +Vs -> +5V, Vout -> A0, and GND -> GND. # scruss, 2012-08-16 - tested on Raspberry Pi and Arduino Uno import pyfirmata import sys # just for script name and window from Tkinter import * # Create a new board, specifying serial port board = pyfirmata.Arduino('/dev/ttyACM0') # start an iterator thread so that serial buffer doesn't overflow it = pyfirmata.util.Iterator(board) it.start() # set up pins pin0=board.get_pin('a:0:i') # A0 Input (LM35) pin3=board.get_pin('d:3:p') # D3 PWM Output (LED) # IMPORTANT! discard first reads until A0 gets something valid while pin0.read() is None: pass def get_temp(): # LM35 reading in °C to label selection = "Temperature: %6.1f °C" % (pin0.read() * 5 * 100) label.config(text = selection) root.after(500, get_temp) # reschedule after half second def set_brightness(x): # set LED; range 0 .. 100 called by Scale widget y=float(x) pin3.write(y / 100.0) # pyfirmata expects 0 .. 1.0 def cleanup(): # on exit print("Shutting down ...") pin3.write(0) # turn LED back off board.exit() # now set up GUI root = Tk() root.wm_title(sys.argv[0]) # set window title to program name root.wm_protocol("WM_DELETE_WINDOW", cleanup) # cleanup called on exit scale = Scale( root, command=set_brightness, orient=HORIZONTAL, length=400, label='Brightness') # a nice big slider for LED brightness scale.pack(anchor=CENTER) label = Label(root) label.pack(anchor='nw') # place label up against scale widget root.after(500, get_temp) # start temperature read loop root.mainloop()
The program takes a few seconds to start on the Raspberry Pi, mainly because initializing pyFirmata over a serial line and waiting for the duff values to subside takes time. I tried to exit the program gracefully in the cleanup()
subroutine, but sometimes one of the loops (I suspect pyFirmata’s iterator) doesn’t want to quit, so it takes a few clicks to exit.
The program also seems to chew up a fair bit of CPU on the Raspberry Pi; I had it at around 40% usage just sitting idle. I guess those serial ports don’t read themselves, and you have to remember that this computer is basically no more powerful than a phone.
So there you are; a simple demo of how to control an output and read an input on an Arduino, from a Raspberry Pi, written in Python (the Raspberry Pi’s official language) with a simple GUI. Considering I’d never written a line of Python before the beginning of this month, I think I’m doing not too badly.
Hi Stewart, I think that could be “while pin0.read() is None: pass” rather than checking their types. There is only one “None” and “is” checks object identity. == would work too but “is” is a simpler check in general.
Thanks, Ralph. That code is much clearer!