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.

Thursday, 28 May 2015

Mandelbot sets in Sniff

When I was a postgrad student many years ago, the group was loaned demo unit of a very expensive thermal transfer printer. It wasn't so much that the printer was expensive but the consumables were. For each A3 page it fed through 3 full sheets (CMY) of transfer material, and melted parts of each of them on to the page. The results were A3 photo-quality glossy prints that looked way better than anything you'd find outside a photo-lab even today. Back then colour printers barely existed.

So what did we do with this? Now days you'd download some cool images and print them, but the web and even jpeg wouldn't appear for another year or two. What we did have were some fast (for their day) parallel computers, and so we printed a few mandelbrot sets.

This happened to be just around the time of the big campus open day when thousands of 6th formers visited campus to be wow'ed by the tech on show... and we were asked if we had anything to show? Show? we could go one better. Not only could we show of your super fast parallel computer drawing Mandelbot's, we could sell them prints to take home! We sold dozens for £1 each. To be fair we weren't exploiting the kids - they got a great deal. The prints looked amazing, and those rolls of thermal transfer sheets were crazy expensive. If we'd been paying for the materials we'd have been loosing several pounds on each image, but you know how it is - we were engineering students, tripped out on high powered computers, and we had the demo printer to the end of the week!

Of course the inevitable happened... we ran out of transfer sheets shortly after lunchtime on the open day. Then someone had the best idea that the entire research group ever came up with... The printer had a roll of transfer film, and when it printed a sheet it only used part of it for each page, depending on what was printed. What was left on the film was a negative of what had been printed...

So I wrote a Postscript program to print a black rectangle that filled the whole page, while someone else rewound the whole roll of transfer material back onto the original spool  and put it back in the printer!!! We print the black page and all of the remaining transfer material is dumped onto the page, and we get a negative of the original print - or as we're just printing Mandelbrots, a new print with different colour scheme.

The new batch of prints didn't look quite as good, as the colour schemes weren't what we would have chosen, and the registration was a fraction off, but they still looked pretty amazing, so we sold the second batch for 50p.

Apparently the sales rep was a little shocked when he collected the demo printer, and discovered we'd used all the transfer film. He was even more surprised to find we'd used all the transfer film - every single last pixel of it!

Maths

Mandelbrots are based on a very simple equation
Z'=Z*Z+C

We have a value Z, we square it and add something to it, and that gives us a new value for Z. We start with Z=0 and use the coordinates of the pixel as the value for C. We repeat this until Z "gets big", and count how long it takes to get big - if it never gets big, then its "in the set", and we colour bit black. Otherwise we colour it based on how long it took to get big.

Simple, except that C isn't a "value" its a position on the screen. Rather than calling the coordinates x and y we call them "real" and "imaginary", so

Z=Zr+Zi
C=Cr+Ci

So now

Z'=(Zr+Zi)*(Zr+Zi)+Cr+Ci

or multiplying all that out:

Z'=Zr*Zr+2*Zr*Zi+Zi*Zi+Cr+Ci

but then need to split Z' into Z'r and Z'i. There are rules for how we split things that have been multiplied:
r*r=>r
r*i=>i
i*i=>-r

So 
Z'r = Zr*Zr-Zi*Zi+Cr
Z'i = 2*Zr*Zi+Ci



Code

Here's the core code:

when start
.broadcast setupColors and wait
.repeat 640 using displayX
..repeat 480 using displayY
...set Cr to (displayX-320)/160
...set Ci to (displayY-240)/160
...set Zr to 0
...set Zi to 0
...set count to 0
...repeat until count = length of colours or Zr*Zr+Zi*Zi>4
....set ZZr to Zr*Zr-Zi*Zi+Cr
....set ZZi to 2*Zr*Zi+Ci
....set Zr to ZZr
....set Zi to ZZi
....change count by 1
...set displayColor to item count of colours
...tell display to "setPixel"

And here it is in Sniff.

We loop over every pixel in a window (640 by 480), and based on that pixel we calculate a value for C (Cr from -2 to + 2, Ci slightly less, but with the same scale so it stays square). Then we initialise Z to 0, and iterate round calculating Z'. We stop when Z*Z>4 as once we go outside this circle Z gets very big very quick and we can stop counting.

We keep a count of how many times we've been around, and use that to pick a colour. The coloured points are outside the set, but they show how close we're getting. To make things nice and simple I created a list of colours, so you can fill in any colours you like - my colour scheme isn't very inventive When we run out of colours to use, we give up, and colour it black.

And here's the full code, including the setup of all the devices, and the colour list:

make nativeFile device
make display window device
make displayX number
make displayY number
make displayColor number

when start
.forever
..tell display to "getEvent"
..wait 0.1 secs

make colours list of numbers
when setupColors
.add 777 to colours
.add 700 to colours
.add 440 to colours
.add 070 to colours
.add 044 to colours
.add 007 to colours
.add 404 to colours
.add 700 to colours
.add 440 to colours
.add 070 to colours
.add 044 to colours
.add 007 to colours
.add 404 to colours
.add 700 to colours
.add 440 to colours
.add 070 to colours
.add 044 to colours
.add 007 to colours
.add 404 to colours
.add 700 to colours
.add 440 to colours
.add 070 to colours
.add 044 to colours
.add 007 to colours
.add 404 to colours
.add 700 to colours
.add 440 to colours
.add 070 to colours
.add 0 to colours

make Cr number
make Ci number
make Zr number
make Zi number
make ZZr number
make ZZi number
make count number
when start
.broadcast setupColors and wait
.repeat 640 using displayX
..repeat 480 using displayY
...set Cr to (displayX-320)/160
...set Ci to (displayY-240)/160
...set Zr to 0
...set Zi to 0
...set count to 0
...repeat until count = length of colours or Zr*Zr+Zi*Zi>4
....set ZZr to Zr*Zr-Zi*Zi+Cr
....set ZZi to 2*Zr*Zi+Ci
....set Zr to ZZr
....set Zi to ZZi
....change count by 1
...set displayColor to item count of colours
...tell display to "setPixel"

Try changing the colour scheme. Also try changing the range of values for Cr/i to see different parts of the set. e.g.: 

...set Cr to ((displayX-320)/320)*0.2-0.65
...set Ci to ((displayY-240)/320)*0.2+0.4


And of course to get it running on Arduino, just change the "window" device to be a "tft", device.

By co-incidence the 320x240 screen is pretty close to the resolution of the BBC micro Mode 1 screen that I first ran this sort of code on. The difference is that the BBC Micro took hours to generate this kind of image, while even an Arduino can do it in a minute or two. On a PC at 640x480 it takes only a second! 

Wednesday, 20 May 2015

HC-SR04 Distance sensor in Sniff


Anyone who's dabbled with Arduino's has come across the HC-SR04 Ultrasonic Distance Sensor. These amazing devices are fire a ping of sound, which bounces off objects and then gets detected. Measure the time of flight and you've got an accurate measure of distance to the object. This is a great science experiment. Best of all they're very cheap, and very accurate... worst of all they're ridiculously unreliable. When they work they're fantastic... and then they stop working. For that reason I'm always skeptical about using them with kids - I find them frustrating, and wouldn't want someone to have a bad experience with one in an introductory class which might discourage them. On the other hand they're really neat when they do work, so I thought I'd finally do a write up:

The hc-sr04 has two data pins: trigger and echo. There's a Sniff hcsr04 device that allows you to connect the sensor to a single arduino pin by connecting trigger and echo together, but today I'm going to to it all in Sniff, so we'll use separate input and output pins:

make trigger digital output 7
make echo digital input 8

I used pins 7 and 8 for absolutly no reason at all.

when start
.set trigger to off
.wait 1 secs

The first thing we'll do is turn the trigger off, and wait 1 second for everything to settle down, and power up correctly.

To send an audio pulse we just set trigger on, wait 10microSeconds and turn it off again:

.set trigger to on
.wait 10 microsecs
.set trigger to off

Then the trigger pin turns off the device emits a series of 8 high pitch clicks.


We need to wait for the echo pin to go high, indicating that the pulses have been sent, and record the start time. We then wait for echo to turn off again, and that gives us the end time.

.wait until echo
.set startTime to fasttimer
.wait until not echo
.say join "Time:"[(fasttimer - startTime)]

In Sniff (and Scratch) we access the time since the program started using the "timer" block. However there's a gotcha - accurate timers are tricky. Sometimes we want to measure time in days (like when we collected the temperature data from the pond), and sometimes (like here) we want to measure things in microseconds. That's a range of 11 orders of magnitude, which causes some technical problems.

To handle that as reliably and flexably as possible Sniff has two timers. The regular timer, which we use for normal stuff, and a special "fasttimer" when we want to measure small time periods accuratly (basically timer is only accurate to 1mS, while fastTimer operates at a microSecond level). Here we're measuring a small time interval and want it to be super accurate so we're using fasttimer.

Sound travels through the air at different speeds depending on the temperature and humidity. If we set up a sensor 1m from a wall we can measure the time it takes to travel the 2m back and forth. We could also measure temperature and use the equation:



Here we'll settle on a typical value of 340m/s. So we multiply the time taken, by the speed to get the distance travelled. Of course the sound travels to the object and back again, so we half the distance travelled by the audio pulses to get the distance to the object. Easy!!



.say join "Distance:" [(fasttimer - startTime)*340/2]


Here's the full code:

make trigger digital output 7
make echo digital input 8

make startTime number

when start
.set trigger to off
.forever
..wait 1 secs
..set trigger to on
 ..wait 10 microsecs
..set trigger to off
..
..wait until echo
..set startTime to fasttimer
..wait until not echo
..say [(fasttimer - startTime)*330/2]

It's in a loop, printing out the result to the serial terminal, but you could just as easily use an embedded display to make a portable, ultrasonic tape measure!

When it works the results are remarkably consistent and accurate - unlike an IR distance sensor its not measuring the intensity of the reflection, but rather the timing, so it doesn't matter of objects are big or small, light or dark.

Wednesday, 6 May 2015

Measuring Lego Motors with Wedo

If you've read through Lego's intro to wedo material (and the related simple mechanical machines) you'll find its an excellent introduction to gearing and ratios. Using a big and a small cog you get taught how to speed up, slow down and reverse the direction of rotation from either a hand crank or a motor.

But that section of the material doesn't actually use the Wedo... perhaps we could use it to put some numbers on those observations! How fast does a lego motor actually turn, and do the gear ratio's do what they're supposed to?

Here's the experimental setup:



The motor (driven by the wedo) drives the propeller blades which pass in front of the distance sensor (motor on connector 2, sensor on 1).  The first thing to do is check that the setup is viable:

make wedo device
make wedoConnector number
make wedoValue number

when start
.set wedoConnector to 2
.set wedoValue to 0
.tell wedo to "setMotorSpeed"
.forever
..set wedoConnector to 1
..forever
...tell wedo to "readDistance"
...say [wedoValue]
...wait 0.5 secs


This just turns the motor off, and prints out the reading from the sensor. Turning the blades slowly by hand shows that the value reported by the sensor does change. While we don't really care about the exact value, we can detect that the blade passes in front of the sensor when the value drops below 0.1.

when start
.set wedoConnector to 2
.set wedoValue to 0.3
.tell wedo to "setMotorSpeed"
.forever
..set wedoConnector to 1
..repeat until wedoValue >0.1
...tell wedo to "readDistance"
..say "ping"
..repeat until wedoValue <0.1
...tell wedo to "readDistance"
..say "pong"

Now we can check to see if there's a blade present - wait until there isn't (print ping), wait until there isn't (print pong). As the motor turns we're not tracking its rotation.

when start
.set wedoConnector to 2
.set wedoValue to 0.3
.tell wedo to "setMotorSpeed"
.forever
..reset timer
..set wedoConnector to 1
..repeat until wedoValue >0.1
...tell wedo to "readDistance"
..repeat until wedoValue <0.1
...tell wedo to "readDistance"
..say [1/(timer*2)]



Now we just measure the time it takes us to go around the loop. As there are two blades to the propeller, each iteration represents half a turn. A full turn takes twice that. We could print that out as time per revolution, but if we take the reciprocal that gives us revolutions per second.

For my motor (regular Wedo M-size) 0.3 is about as slow as it can go (in fact it takes a nudge to get started at this speed). We can measure speed for a few power settings:



0.3 Power = 0.85rps
0.5 Power = 1.55rps
0.75 Power =2.5rps
1(full) power=3.5rps



Theres a bit of variation, as the wedo probably only updates the reported value on a periodic basis, but the results are fairly consistent and representative.

Now lets mod the setup, and add an extra gear. This makes the fan turn faster (according to the lego introduction to cogs!)

0.3 Power = 2.5
0.5 Power = 4.6
0.75 Power = 7rps
1(full) power=10.4rps

In each case the new speed is almost exactly three times the previous one... The exception to that is the 0.75 which is a little low... However looking at the raw data for this particular measurement, it seemed to present the most variation 6.94 was the most common reading, but it was also the lowest, with occasional readings of 7.6 and 7.9 - no other reading had any where near such a wide variation, so it looks like this speed doesn't mesh well with the underlying sampling rate - lets just put it down to experimental error.


Now the moment of truth... Lets count the number of teeth on the cogs: The small one has 8, while the big one has 24... a factor of THREE!!!! One turn of the big cog turns the little cog three times, so the blade spins three times faster.
Awesome!


Tuesday, 5 May 2015

Lego Wedo Alligator

The Lego Wedo is awesome! It comes with a great set lesson plans, with lots of great projects, and ideas for how you can use the items you build as part of larger topics. However they're firmly aimed at KS2, which is a great shame as everyone loves Logo and having bought expensive hardware, far too many schools use it for a few sessions in year 4, then put it back in the cupboard.

The Wedo software is nice but limited and expensive, and its probably the main reason that Lego haven't made the Wedo available outside of primary education. Fortunately the Wedo works with Scratch and Sniff. This means there's lots of opportunity for frameworking and progression: we can revisit familiar (and fun) projects which have been completed in the Wedo Software, and rebuild them in Scratch and subsequently Sniff.

My favourite official project from the Lego set is the Hungry Alligator. Building instructions are built into the Wedo software, but you can download regular instructions here: https://education.lego.com/en-us/lesi/support/product-support/wedo/wedo-base-set-9580/building-instructions

Now lets code this in Sniff:

make wedo device
make wedoConnector number
make wedoValue number

when start
.set wedoConnector to 1
.
.forever
..set wedoValue to -1
..tell wedo to "setMotorSpeed"
..
..wait 0.4 secs
..
..set wedoValue to 1
..tell wedo to "setMotorSpeed"
..
..wait 0.35 secs

We start by making a wedo hub device. You can connect any number of hubs (more or less!), but we're just going to have one. Each hub as two connectors, and we've plugged the motor into connector 1 (the left 1). We set the wedoConnector and wedoValue, and the tell the wedo to "setMotorSpeed". -1 closes the mouth, while +1 opens it (that's just the way this model is built/geared), so this code closes the mouth, then opens it, repeating forever. We close for slightly longer than we open to ensure that the jaws fully close - the drive bands will slip when we get that far.

Having got the jaws snapping, we can move that into a separate script.

when snap
.set wedoConnector to 1
.repeat 3
..set wedoValue to -1
..tell wedo to "setMotorSpeed"
..wait 0.4 secs
..set wedoValue to 1
..tell wedo to "setMotorSpeed"
..wait 0.35 secs
.set wedoValue to 0
.tell wedo to "setMotorSpeed"

when start
.forever
..set wedoConnector to 2
..tell wedo to "readDistance"
..repeat until wedoValue <0.07
...tell wedo to "readDistance"
..broadcast snap and wait
..wait 0.2 secs

Then we use the distance sensor to wait until there's something to eat! The sensor is on connector 2, and we readDistance until it reads less than 8cm, at which point we fire off the snap script. You may need to tweak the timings, and range value to get everything working smoothly (so the jaws don't trigger another Snap)


It's impossible not to love this guy! Just leave him sitting on your desk, and feed him occasionally! If you've got some Wedo kicking around from working with younger year groups, then get them out, and get excited about building and programming!

Release 17: Hungry Alligator

Release 17 contains a lot of minor improvements to the compiler and runtime that should make it nicer to use. Programs now quit once all scripts have completed, and the compiler will now warn you if your program contains scripts and/or broadcasts that are never used (typically because you've miss-spelled one of the script names).

The big headline feature is that Wedo is now supported on all host platforms: Windows, Linux and Mac. This has been a priority for some time, but the code needed to support it was complex. Now its in place everything should just work. Wedo was the last Unix only feature, and now windows and Unix platforms are basically equivalently supported.

There are of course the usual round of bug fixes, and examples, including all the numerical code from the last few blog posts, and the multi shield Arduino code.

Download  R17 for Windows
Download R17 for all other Platforms

update: There was a minor, but quite obvious bug in the Unix release, which has been updated.

Friday, 1 May 2015

Raspberry Pi Desktop Shortcut for Sniff

Last year we put a lot of effort into getting the core of Sniff right. It's now a solid programming system, with a rich set of libraries, running on a wide range of platforms. We're not working with teachers to iron out the wrinkles and make it easy to use for non-techies.

Sniff is essentially command line based, with a  compile command which generates an executable. You can use it with any IDE, or simply a text editor. To make things easier for new users we wrote SniffPad - a simple IDE for Sniff, written in Sniff. However as that's a Sniff program you generally need to run it from the command line.

To make it even easer, on a Raspberry Pi or other Linux system you can make a desktop shortcut to Sniffpad, so users can just click the icon, and just straight into writing Sniff code.

The first thing to setup is the shortcut itself. Create a file called Sniff.desktop in your Desktop folder, containing the following text:

[Desktop Entry]
Name=SniffPad
Comment=Setup Sniff, and run Sniffpad
Icon=/home/ian/Sniff/utils/PiShortcut/sniff.bmp
Exec=/home/ian/Sniff/utils/PiShortcut/runSniffpad
Type=Application
Encoding=UTF-8
Terminal=false
Categories=None;


You'll need to edit the Icon and Exec lines to point wherever you've installed Sniff. In this case I've unpacked the Sniff distribution into /home/ian/Sniff. In future releases that's all you'll need to do, but if you want to try this out right now, then you need to go to the Snif/utils directory and make a folder called PiShortcut. Inside there put an image file sniff.bmp. 

Finally we need a script which is actually going to run. We can't link directly to SniffPad, as we first need to set up the Sniff environment. Place the following text in a file called runSniffpad inside the PiShortcut folder, and make sure its executable (chmod 755 runSniffpad).

DIR=`dirname $0`

cd $DIR/../..
. ./setup

mkdir -p $HOME/SniffProjects
cd $HOME/SniffProjects
sniffpad


This finds the Sniff folder based on where the runSniffPad command is, and runs setup. It then cd's to a folder called SniffProjects, where you can save your work.

Now just double click and you're good to go!