Sniff is a "Scratch-like" programming language that's designed to help Scratchers move gently from Scratch to more conventional languages. They can start writing programs, without having to learn a new language because Sniff is based on Scratch. They learn a little more about variables, compiling, syntax errors (!), and they can have fun controlling real hardware while they're doing it.

Sunday, 22 March 2015

Race Control

I've been running an Arduino robot workshop for the last few weekends, and next week is the final session. So far the kids have done a great job, and have build and coded IR controlled robots similar to SniffBot Jr. This design is well suited to the task, as its cheap enough that the cost of the session can cover the components, and everyone gets to take their robot home at the end (I've seen ads for some pretty expensive workshops where the students projects go back in the parts bin at the end of the week - I think the kids would be devastated to have to give their creations back!).

For the final week I want to set them some challenges so they can race their robots against each other. However as they've all got the same IR controllers the events will have to be time trials: a salom/obstacle course, sumo against some smaller bots, and finally an all out race around a track. I could just time each event on a stopwatch (phone!), but I thought I'd so something more fun.

I happened to have an Arduino Uno and some Neo Pixels attached to a sheet of perspex, so I realised I could turn that in to a great "race control"/starting mechanism and timer. In motor racing, races are started by 5 lights lighting up red one at a time. The lights then hold for a few seconds (randomised) before turning off to signify the start of the race.

That would be pretty easy to build, but I had a lot more neoPixels and just lighting them up red would be a bit boring, so I chose to light them yellow to signify the ready state. I'd then press a button, and the yellow lights would turn red in sequence. Once they were all red, they'd hold for a random time before turning green to start the race. That would also start the timer. On pressing the button again the timer would stop and the lights would display something colourful to celebrate the end of the race.



To code that we start by defining the hardware:

make neoPixel ws2811 device A1
make neoColor list of number
make neoShade list of number

make lcd device
make displayFlush boolean

make message string

make button analog input A0

make ledCount number
make startTime number
make index number

The NeoPixels are attached to A1 using a sensor shield. Depending on the version you have, one of the neat features is that it breaks out A0-5 around the edge, so that even when you have another shield on top you can still get to the Analog pins.

For the timer display I use an "LCD Shield". These are available everywhere, and there are dozens of different brands, but they're all the same. They're support cheap, and are far easier than wiring up a regular LCD (which I've never even tried - either use a shield or get an i2c LCD which just uses two pins but is slower to refresh). The only gotcha is that there's a design fault on many of them - just never use pin 10 for anything and you'll be fine! There are 5 buttons plus a reset, but rather than using 5 pins they're attached via resistor network to A0. Each button returns a different analog value. 

I could have used something like an Embedded Adventures 7 segment display, or even one of their larger displays, but then it would have needed a bit more "construction". the LCD shield just plugs in and is pretty robust.

when start
.set ledCount to 16
.repeat ledCount
..add 60 to neoColor
..add 50 to neoShade
.tell neoPixel to "show"
.
.tell lcd to "clear"
.set message to "ready"
.tell lcd to "show"
.
.wait until button< 0.8


Step 1 is to load up the neoPixel colour and shade array with 60 to mean yellow and a brightness of 50 which means full brightness/saturated colour. For testing you can drop this down to around a 10 - it's easier on the eyes! Once loaded we call "show" to write the values to the pixels.

.wait until button< 0.8
.
.tell lcd to "clear"
.set message to "set"
.tell lcd to "show"
.
.repeat ledCount using index
..replace item index of neoColor with 0
..tell neoPixel to "show"
..wait 0.2 secs

Now we wait for a button to be pressed. We don't care which button so just wait till the value drops below 0.8. Then clear the LCD and display the message "set".

We now need to turn each pixel red in turn. I've used the "new" repeat using syntax as I find it really helpful - the code is the equivalent of:

.set index to 1
.repeat ledCount
..replace item index of neoColor with 0
..tell neoPixel to "show"
..wait 0.2 secs
..change index by 1

But its a little clearer once you're up to speed with it. For younger children just transitioning from Scratch you should probably stick with the version they recognise, and then once they've got the idea of it you can introduce the "short cut".

.wait (pick random 5 to 30)*0.2  secs
.
.delete all of neoColor
.repeat ledCount
..add 120 to neoColor
.tell neoPixel to "show"

Once all the lights are red we wait for a random time between 1 and six seconds before switching the lights to green. We could have done this as we did for the previous code with an index counter, but its easier to throw them all away and replace them all.

.set startTime to timer
.
.repeat until button < 0.8
..tell lcd to "clear"
..set message to [timer - startTime]
..tell lcd to "show"
..wait 0.1 secs
.

So now we're racing, so record the start time, and loop until the button is pressed again to finish the race. Each time around the loop we update the screen with the new elapsed time. We need a bit of a delay so that the screen isn't too flickery - we don't want to clear it just after we've displayed the message! 0.1 seconds is faster than I can press the stop button so don't mind the timing error this might introduce.

The LCD screen will already be displaying the final time, so there's no need to update it any more. All that's left is do make the neoPixels look pretty:

.
.forever
..set index to timer*10
..delete all of neoColor
..repeat ledCount
...add ((index mod ledCount)*2/ledCount)*360 to neoColor
...change index by 1
..tell neoPixel to "show"


Potentially we could put all this code in a loop to time multiple races, but there's a reset button on the shield which starts the program again, so its easier to just use that.

This was never meant to be example code, or used as a classroom exercise - it was just something cool that I thought would make the robot race more fun, but now its done I realise that actually it's a pretty great project for a short workshop. It would also run really well (without the timer) on the 4tronix smart badge I just got...

No comments:

Post a Comment