Interface

Lesson 12

This week's assignment is to write an application that interfaces with an input &/or output device.

I took a look at neil's code (hello.light.45.c and hello.light.45.py) for the light board and studied what these programs actually do. Bas adviced me to download docklight to see what kind of data is actually being send by the attiny microchip. I discovered that the microchip sends a pattern of 1,2,3,4 and then it sends two numbers. These two numbers represent a 10bit number. The range of numbers that can be send by the attiny45 range from 0 to 1023, which means that this number will not fit into a byte (which is 2 to the power of 8 = 256), so we need to have a 2 to the power of 10 to get the right representation of 1023.

hello.light.45.py is waiting for the right pattern (1,2,3,4) and it will then read two numbers, which it combines into one number (value = 256*high + low). The ord() function changes the data send by the attiny microchip into numbers.

   low = ord(ser.read())
   high = ord(ser.read())
   value = 256*high + low

ser.read() is where the actual serial port reading is being done. ser is being defined later in the python program.

#  check command line arguments
#
if (len(sys.argv) != 2):
   print "command line: hello.light.45.py serial_port"
   sys.exit()
port = sys.argv[1]
#
# open serial port
#
ser = serial.Serial(port,9600)
ser.setDTR()

When you start the python program, you will need to give one argument. This is the com port, which is the serial port you are connected to. In my case this was com6, but this can differ everytime. I look at my arduino software to see, which com port i was connected to. 9600 is the baud rate, which is the speed attiny is sending the data.

In my case, the hello.light.45.c was already installed on my board, but if you need to know how to do that go back to input devices lesson. Next i downloaded and saved the hello.light.45.py in the python27 folder and changed the code to add a live plotting graph.

I googled on live plotting graph in python and most of the answers were refering to matlibplot. This is a package to be imported into your python program to enable live plotting graphs. To make the graph work, i needed a array. One of the tutorial on this subject referred me to deque collections, which has a handy feature to add and delete data to a collection/array.

y = deque([1,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,900])
m = np.arange(30)
PLT.ion()
fig = PLT.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(m, y, 'r-') # Returns a tuple of line objects, thus the comma
line1.set_ydata(y)

To make plot you need and x and y array. In my case a m and y value. I did not want to have a very long data set, so i chose the length of my y to be 30 data points. The parts after this sets up the initial graph. PLT.ion() sets the live graph on.

   y.popleft() 
   y.append(filter)
   z = sum(y) / float(len(y))
   print z
   #print len(y)                  
   line1.set_ydata(y)
   fig.canvas.draw() 

In the loop part of the program, i delete the left most number in the y array and on the right part of the y array, i add the new number. z is the average number of the array and is printed in the console. line1.set_ydata(y) and fig.canvas.draw() update and draw the graph. The problem i had with this program was that the data was coming to fast for the graph to update. The graph was lagging behind and was really live, so i decided to slow down the data with counta. In my program, the pattern 1,2,3,4 is not sufficient to break and get the data, it needs to have a counta of higher than 40. This slows down the serial data feed with a factor of 40.

This is the end result of the program, when you type: python hello.light.45.v2.py com6 in the python27 directory.

This is the entire code:

#
# hello.light.45.py
#
# receive and display light level
# hello.light.45.py serial_port
#
# Neil Gershenfeld
# CBA MIT 10/24/09
#
# (c) Massachusetts Institute of Technology 2009
# Permission granted for experimental and personal use;
# license for commercial sale available from MIT
#


from Tkinter import *
import numpy as np
from numpy import log
import serial
from matplotlib import pyplot as PLT
from collections import deque # added

WINDOW = 600 # window size
eps = 0.5 # filter time constant
filter = 0.0 # filtered value

# added 
y = deque([1,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,900])
m = np.arange(30)
PLT.ion()
fig = PLT.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(m, y, 'r-') # Returns a tuple of line objects, thus the comma
line1.set_ydata(y)
# added

def idle(parent,canvas):
   global filter, eps
   #
   # idle routine
   #
   byte2 = 0
   byte3 = 0
   byte4 = 0
   ser.flush()
   counta=0
   while 1:
      #
      # find framing 
      # The serial data feed is to quick for the graph to update, so we needed to slow it down with counta.
      byte1 = byte2
      byte2 = byte3
      byte3 = byte4
      byte4 = ord(ser.read())
      counta =counta+1
      if ((byte1 == 1) & (byte2 == 2) & (byte3 == 3) & (byte4 == 4) & (counta > 50)):
         break # wait until the your have pattern 1,2,3,4 and the teller is bigger than 50.
   low = ord(ser.read())
   high = ord(ser.read())
   value = 256*high + low
   filter = (1-eps)*filter + eps*value
  
  
#added
   y.popleft() 
   y.append(filter)
   z = sum(y) / float(len(y))
   print z
   #print len(y)                  
   line1.set_ydata(y)
   fig.canvas.draw() 
  
#added
    
   x = int(.2*WINDOW + (.9-.2)*WINDOW*filter/1024.0)
   canvas.itemconfigure("text",text="%.1f"%filter)
   canvas.coords('rect1',.2*WINDOW,.05*WINDOW,x,.2*WINDOW)
   canvas.coords('rect2',x,.05*WINDOW,.9*WINDOW,.2*WINDOW)
   canvas.update()
   parent.after_idle(idle,parent,canvas)

#
#  check command line arguments
#
if (len(sys.argv) != 2):
   print "command line: hello.light.45.py serial_port"
   sys.exit()
port = sys.argv[1]
#
# open serial port
#
ser = serial.Serial(port,9600)
ser.setDTR()
#
# set up GUI
#
root = Tk()
root.title('hello.light.45.py (q to exit)')
root.bind('q','exit')
canvas = Canvas(root, width=WINDOW, height=.25*WINDOW, background='white')
canvas.create_text(.1*WINDOW,.125*WINDOW,text=".33",font=("Helvetica", 24),tags="text",fill="#0000b0")
canvas.create_rectangle(.2*WINDOW,.05*WINDOW,.3*WINDOW,.2*WINDOW, tags='rect1', fill='#b00000')
canvas.create_rectangle(.3*WINDOW,.05*WINDOW,.9*WINDOW,.2*WINDOW, tags='rect2', fill='#0000b0')
canvas.pack()
#
# start idle loop
#
root.after(100,idle,root,canvas)
root.mainloop()