Laser timer from Cambridge PiWars

Hi,

What was the hardware used for the laser timer from Cambridge PiWars?

Thanks,

Alex

piborg's picture

The PiWars timer was built from the following components:

  • 2x 1 mW lasers to produce the light beam
  • 2x Photo diodes to detect the presence of the light beam
  • 2x small tubes which we placed the photo diodes in to block out the surrounding light (aimed at lasers)
  • 4x RS485 driver chips:
    Two to convert the signal from the sensor to a + and - pair to send down wires
    Two to convert the + and - pair back to a logic signal for the Raspberry Pi
  • Various resistors et cetera to get the correct voltage levels
  • Some metal frame to keep the components stable
piborg's picture

In case anyone is interested, here is the code we used for the timing:

#!/usr/bin/env python
# coding: latin-1

# Import library functions we need (we are using pigpio so we can get pin change events)
#  See http://abyz.co.uk/rpi/pigpio/download.html for setup of pigpio
import pigpio
import Tkinter
import time

# Set which GPIO pins the sensor beams are connected to
SENSOR_START  = 9   # White
SENSOR_FINISH = 11  # Red

# Settings about the run
trackLength    = 7.31
maxRobotLength = 0.42
sensorDistance = trackLength - maxRobotLength

# Set some state names for GUI reporting
STATE_DONE      = 1
STATE_GO        = 2
STATE_RUNNING   = 3
STATE_FINISHED  = 4

# Global values
global pi
global filePath
filePath = './PiWars_Results.txt'

# Setup the GPIO
pi = pigpio.pi()

# Class representing the GUI dialog
class PiWarsTimer_tk(Tkinter.Tk):
    # Constructor (called when the object is first created)
    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        self.protocol('WM_DELETE_WINDOW', self.OnExit) # Call the OnExit function when user closes the dialog
        self.Initialise()

    # Initialise the dialog
    def Initialise(self):
        global filePath

        # Setup our dialog components
        self.title('PiWars Timer')
        self.grid()

        self.scrLists = Tkinter.Scrollbar(self, orient = Tkinter.VERTICAL)
        self.scrLists.grid(column = 5, row = 1, columnspan = 1, rowspan = 4, sticky = 'NSEW')
        self.scrLists.config(command = self.scrLists_scroll)

        self.lblResultName = Tkinter.Label(self, text = 'Robot')
        self.lblResultName['font'] = ('Arial', 20, 'bold')
        self.lblResultName.grid(column = 2, row = 0, columnspan = 1, rowspan = 1, sticky = 'NSEW')

        self.lstResultName = Tkinter.Listbox(self, selectmode = Tkinter.EXTENDED, exportselection = 0,
                                             yscrollcommand = self.scrLists.set)
        self.lstResultName.grid(column = 2, row = 1, columnspan = 1, rowspan = 4, sticky = 'NSEW')

        self.lblResultTime = Tkinter.Label(self, text = 'Time')
        self.lblResultTime['font'] = ('Arial', 20, 'bold')
        self.lblResultTime.grid(column = 3, row = 0, columnspan = 1, rowspan = 1, sticky = 'NSEW')

        self.lstResultTime = Tkinter.Listbox(self, selectmode = Tkinter.EXTENDED, exportselection = 0,
                                             yscrollcommand = self.scrLists.set)
        self.lstResultTime.grid(column = 3, row = 1, columnspan = 1, rowspan = 4, sticky = 'NSEW')

        self.lblResultSpeed = Tkinter.Label(self, text = 'Average speed')
        self.lblResultSpeed['font'] = ('Arial', 20, 'bold')
        self.lblResultSpeed.grid(column = 4, row = 0, columnspan = 1, rowspan = 1, sticky = 'NSEW')

        self.lstResultSpeed = Tkinter.Listbox(self, selectmode = Tkinter.EXTENDED, exportselection = 0,
                                             yscrollcommand = self.scrLists.set)
        self.lstResultSpeed.grid(column = 4, row = 1, columnspan = 1, rowspan = 4, sticky = 'NSEW')

        self.lblRobot = Tkinter.Label(self, text = 'Next Robot')
        self.lblRobot['font'] = ('Arial', 20, 'bold')
        self.lblRobot.grid(column = 0, row = 0, columnspan = 2, rowspan = 1, sticky = 'NSEW')

        self.lblPinStart = Tkinter.Label(self, text = 'Start')
        self.lblPinStart['font'] = ('Arial', 20, 'bold')
        self.lblPinStart['fg'] = '#FFFFFF'
        self.lblPinStart['bg'] = '#404040'
        self.lblPinStart.grid(column = 0, row = 1, columnspan = 1, rowspan = 1, sticky = 'NSEW')

        self.lblPinFinish = Tkinter.Label(self, text = 'Finish')
        self.lblPinFinish['font'] = ('Arial', 20, 'bold')
        self.lblPinFinish['fg'] = '#FFFFFF'
        self.lblPinFinish['bg'] = '#404040'
        self.lblPinFinish.grid(column = 1, row = 1, columnspan = 1, rowspan = 1, sticky = 'NSEW')
    
        self.lblRobotName = Tkinter.Label(self, text = 'Name')
        self.lblRobotName['font'] = ('Arial', 20, '')
        self.lblRobotName.grid(column = 0, row = 2, columnspan = 2, rowspan = 1, sticky = 'SEW')
        
        self.txtRobotName = Tkinter.Entry(self)
        self.txtRobotName['font'] = ('Arial', 20, '')
        self.txtRobotName.grid(column = 0, row = 3, columnspan = 2, rowspan = 1, sticky = 'NSEW')

        self.butGo = Tkinter.Button(self, text = 'GO!', command = self.butGo_click)
        self.butGo['font'] = ('Arial', 20, 'bold')
        self.butGo['bg'] = '#444444'
        self.butGo['activebackground'] = '#004400'
        self.butGo.grid(column = 0, row = 4, columnspan = 2, rowspan = 1, sticky = 'NSEW')

        self.lblLastTime = Tkinter.Label(self, text = 'Set name and press GO')
        self.lblLastTime['font'] = ('Arial', 20, 'bold')
        self.lblLastTime['bg'] = '#000000'
        self.lblLastTime['fg'] = '#FFFFFF'
        self.lblLastTime.grid(column = 0, row = 5, columnspan = 6, rowspan = 1, sticky = 'NSEW')
        
        # Set the grid scaling
        self.grid_columnconfigure(0, weight = 1)
        self.grid_columnconfigure(1, weight = 1)
        self.grid_columnconfigure(2, weight = 10)
        self.grid_columnconfigure(3, weight = 10)
        self.grid_columnconfigure(4, weight = 10)
        self.grid_columnconfigure(5, weight = 1)
        self.grid_rowconfigure(0, weight = 1)
        self.grid_rowconfigure(1, weight = 1)
        self.grid_rowconfigure(2, weight = 2)
        self.grid_rowconfigure(3, weight = 2)
        self.grid_rowconfigure(4, weight = 4)
        self.grid_rowconfigure(5, weight = 2)

        # Set the size of the dialog
        self.resizable(True, True)
        self.geometry('800x600')

        # Set initial state
        self.selectedName = None
        self.selectedTime = None
        self.runState = STATE_DONE

        # Place a file header
        fileOut = open(filePath, 'a')
        now = time.localtime()
        fileOut.write('=== %04d/%02d/%02d %02d:%02d:%02d ===\n' % (
            now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec))
        fileOut.close()

        # Start polling
        self.poll()

    # Polling function
    def poll(self):
        global pi
        
        # Check for list selection
        selectedName  = self.lstResultName.curselection()
        selectedTime  = self.lstResultTime.curselection()
        selectedSpeed = self.lstResultSpeed.curselection()
        if selectedName != self.selectedName:
            # Name selection updated, reflect in others
            self.selectedName = selectedName
            self.lstResultTime.select_clear(0, self.lstResultTime.size() - 1)
            self.lstResultSpeed.select_clear(0, self.lstResultTime.size() - 1)
            for index in selectedName:
                self.lstResultTime.select_set(int(index))
                self.lstResultSpeed.select_set(int(index))
            self.selectedTime = self.lstResultTime.curselection()
            self.selectedSpeed = self.lstResultSpeed.curselection()
        elif selectedTime != self.selectedTime:
            # Time selection updated, reflect in others
            self.selectedTime = selectedTime
            self.lstResultName.select_clear(0, self.lstResultName.size() - 1)
            self.lstResultSpeed.select_clear(0, self.lstResultTime.size() - 1)
            for index in selectedTime:
                self.lstResultName.select_set(int(index))
                self.lstResultSpeed.select_set(int(index))
            self.selectedName = self.lstResultName.curselection()
            self.selectedSpeed = self.lstResultSpeed.curselection()
        elif selectedSpeed != self.selectedSpeed:
            # Speed selection updated, reflect in others
            self.selectedSpeed = selectedSpeed
            self.lstResultName.select_clear(0, self.lstResultName.size() - 1)
            self.lstResultTime.select_clear(0, self.lstResultTime.size() - 1)
            for index in selectedSpeed:
                self.lstResultName.select_set(int(index))
                self.lstResultTime.select_set(int(index))
            self.selectedName = self.lstResultName.curselection()
            self.selectedTime = self.lstResultTime.curselection()

        # Update the robot running state
        if self.runState == STATE_DONE:
            # Waiting for the GO button
            pass
        elif self.runState == STATE_GO:
            # Go button has been pressed
            pass
        elif self.runState == STATE_RUNNING:
            # First sensor was tripped
            self.SetRobotRunning()
        elif self.runState == STATE_FINISHED:
            # Second sensor was tripped
            time = self.endTime - self.startTime
            self.SetLatestTime(self.robotName, time)
            self.runState = STATE_DONE
        else:
            # Unexpected state code
            self.lblLastTime['text'] = 'Unexpected state %d' % (self.runState)
            self.lblLastTime['bg'] = '#000000'
            self.lblLastTime['fg'] = '#FFFFFF'
            self.runState = STATE_DONE

        # Read the pin states
        start  = pi.read(SENSOR_START)
        finish = pi.read(SENSOR_FINISH)

        # Set the GUI status for start
        if start == 0:
            self.lblPinStart['bg'] = '#008000'
        elif start == 1:
            self.lblPinStart['bg'] = '#800000'
        else:
            self.lblPinStart['bg'] = '#000080'

        # Set the GUI status for finish
        if finish == 0:
            self.lblPinFinish['bg'] = '#008000'
        elif finish == 1:
            self.lblPinFinish['bg'] = '#800000'
        else:
            self.lblPinFinish['bg'] = '#000080'

        # Prime the next poll
        #self.after(1000, self.poll)
        self.after(20, self.poll)

    # Add a new time to the screen
    def SetLatestTime(self, name, finalTime):
        global filePath
        averageSpeed = (sensorDistance / finalTime) * 3.6 # kph
        fileOut = open(filePath, 'a')
        fileOut.write('%-20s %01.3f s (%01.2f kph)\n' % (name, finalTime, averageSpeed))
        fileOut.close()
        self.lstResultName.insert(Tkinter.END, name)
        self.lstResultTime.insert(Tkinter.END, '%01.3f s' % (finalTime))
        self.lstResultSpeed.insert(Tkinter.END, '%01.2f kph' % (averageSpeed))
        self.lstResultName.yview_scroll(1, 'pages')
        self.lstResultTime.yview_scroll(1, 'pages')
        self.lstResultSpeed.yview_scroll(1, 'pages')
        self.lblLastTime['text'] = '%s finished in %01.3f s, averaged %01.2f kph' % (name, finalTime, averageSpeed)
        self.lblLastTime['bg'] = '#000000'
        self.lblLastTime['fg'] = '#FFFFFF'

    # Called when the GO button is pressed
    def butGo_click(self):
        # Arm the measurement
        self.startTime = None
        self.endTime = None
        self.runState = STATE_GO
        self.robotName = self.txtRobotName.get()
        # Set the dialog display
        self.lblLastTime['text'] = 'Ready for %s to start' % (self.robotName)
        self.lblLastTime['bg'] = '#008000'
        self.lblLastTime['fg'] = '#FFFFFF'

    # Called when the robot is running
    def SetRobotRunning(self):
        now = time.time()
        current = now - self.startTime
        self.lblLastTime['text'] = '%s has been running for %01.3f s...' % (self.robotName, current)
        self.lblLastTime['bg'] = '#800000'
        self.lblLastTime['fg'] = '#FFFFFF'

    # Called when the scroll bar is moved
    def scrLists_scroll(self, *args):
        # Pass the scrolling on to all list boxes
        apply(self.lstResultName.yview, args)
        apply(self.lstResultTime.yview, args)
        apply(self.lstResultSpeed.yview, args)

    # Called when the user closes the dialog
    def OnExit(self):
        # Release GPIO and end the program
        global pi
        global filePath
        fileOut = open(filePath, 'a')
        fileOut.write('\n')
        fileOut.close()
        pi.stop()       
        self.quit()
    
    # Called when the start sensor pin changes state
    def StartSensorEdge(self, gpio, level, tick):
        if self.runState == STATE_GO:
            # Waiting for start marker, set to running mode
            self.startTime = time.time()
            self.runState = STATE_RUNNING
    
    # Called when the finish sensor pin changes state
    def FinishSensorEdge(self, gpio, level, tick):
        if self.runState == STATE_RUNNING:
            # Waiting for finish marker, set to finished mode
            self.endTime = time.time()
            self.runState = STATE_FINISHED

# Setup the GUI and the callbacks
gui = PiWarsTimer_tk(None)
pi.set_mode(SENSOR_START, pigpio.INPUT)
pi.set_mode(SENSOR_FINISH, pigpio.INPUT)
pi.callback(SENSOR_START, pigpio.RISING_EDGE, gui.StartSensorEdge)
pi.callback(SENSOR_FINISH, pigpio.RISING_EDGE, gui.FinishSensorEdge)

# Hand control over to the GUI
gui.mainloop()
Subscribe to Comments for "Laser timer from Cambridge PiWars"