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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#!/usr/bin/env python
# coding: latin-1
 
# Import library functions we need (we are using pigpio so we can get pin change events)
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"