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
piborg
Wed, 12/17/2014 - 08:27
Permalink
PiWars laser timer
The PiWars timer was built from the following components:
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
piborg
Wed, 12/17/2014 - 08:41
Permalink
PiWars laser timer software
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 <a href="http://abyz.co.uk/rpi/pigpio/download.html">http://abyz.co.uk/rpi/pigpio/download.html</a> 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()