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, 4 February 2016

Stepper Motor Semaphore

For some reason I can't quite remember I stumbled across some web pages on semaphore, and thought wouldn't it be good to write some code to convert messages to semaphore. 




In Semaphore, each letter is represented by the position of two flags. There are 8 flag positions, numbered clockwise from 0 at the bottom, through 4 (straight up) to 7 bottom right. Of course we could just draw them on the screen but really we want to to actually wave some flags around. 

There are basically three options for physically moving stuff under computer control. For robots we just use DC motors, but all you can do is turn these on and off. Then they spin fast and you've no which orientation they're in. You can add a sensor to track the rotation, but that's hard work. The second option is Servos. These would be perfect - you send them a timing signal which represents a position, and they move to that position. However there's a gotcha - servos typically have a limited rotation of about 180 degrees. That would be Ok if each arm just had to move on its own side, but if you look at the chart, you'll see that sometimes both flags are on the same side - for example the left flag naturally moves from 0 (down) to 4 (up) on the left side (as we see it), but sometimes it needs to move to 5 or 7 - a total angle of 270 degrees. While there are servos that can do this (and we could add gearing to solve the problem) we need to consider a third option.

Stepper Motors have a magnet in the middle, and a set of coils (typically 4) around the outside. 
When we turn on one of the coils the motor turns to align the magnet with the field of the coil. If we turn on each coil in turn, then we can make the motor rotate. However its under our exact control - each time we turn on the next coil the motor turns by exactly one "step". Depending on how the motor is built, each step will be a known angle, so we can move the motor very precisely - with one proviso - we can move it precisely by any required angle, but we've no way of knowing where it is!


We bought two motors and controllers for under £5 on eBay. You can get them cheaper, but we wanted them now! Hook up is simple - two power pins, and 1 pin for each of the four coils. Now we can code this up:

make stepper number
make coilA digital output D4
make coilB digital output D5
make coilC digital output D6
make coilD digital output D7

when fullStep
.forever
..if stepper > 4
...change stepper by -4
..if stepper < 1
...change stepper by 4
..
..set coilA to (stepper=1)
..set coilB to (stepper=2)
..set coilC to (stepper=3)
..set coilD to (stepper=4)

when start
..broadcast fullStep
.
.set stepper to 1
.repeat 2048
..change stepper by 1
..wait 2000 microsecs
.repeat 2048
..change stepper by -1
..wait 2000 microsecs

The four coils are connected to digital outputs, and we run the "fullStep" script all the time in the background. The variable stepper represents which coil is currently turned on. In the main script we increment stepper 2048 times - as the motors I bought take 2048 steps to complete 1 revolution. We then then decrement it to rotate back. We have a pause of 2milliseconds between each step - these are physical things and take time to respond, so if you move them too fast they'll just sit there and jitter. This was as fast as the ones I bought could go.

when fullStep
.forever
..if stepper > 4
...change stepper by -4
..if stepper < 1
...change stepper by 4
..
..set coilA to (stepper=1 or stepper=2)
..set coilB to (stepper=2 or stepper=3)
..set coilC to (stepper=3 or stepper=4)
..set coilD to (stepper=4 or stepper=1)

The original version works fine, but we can get a little better performance out of it by turning on two coils at a time instead of one. When stepper is 2, both coils A and B are turned on, then when it changes to 3, A turns off and C turns on. The magnet now lines up half way between the two active coils.


when halfStep
.forever
..if phase > 8
...change phase by -8
..if phase < 1
...change phase by 8
..
..set coilA to phase = 8 or phase=1 or phase=2
..set coilB to phase = 2 or phase=3 or phase=4
..set coilC to phase = 4 or phase=5 or phase=6
..set coilD to phase = 6 or phase=7 or phase=8


If we combine these two ideas we can "half step" the motor - alternately lining it up with a single could and half way between coils. The sequence is on A, AB, B, BC, C, CD, D, DA... It's now taken 8 half steps to move the same distance as 4 full steps. Movement is a little smoother, and we have finer control over position.

Now its time to wrap all  that up in something we can use in our Semaphore code:

make position number
make target number
make stepsPerTurn number 2048

when halfStep
.make phase number
.
.forever
..if position<target
...change phase by 1
...change position by 0.5
..if position>target
...change phase by -1
...change position by -0.5
..
..if phase > 8
...change phase by -8
..if phase < 1
...change phase by 8
..
..set coilA to phase = 8 or phase=1 or phase=2
..set coilB to phase = 2 or phase=3 or phase=4
..set coilC to phase = 4 or phase=5 or phase=6
..set coilD to phase = 6 or phase=7 or phase=8
..
..wait 1000 microsecs



This improved halfStep script, uses three variables to keep track of the motor: position (where it currently is), target (where we'd like it to be), and phase (where it is in the step sequence). It then uses the half step sequence to move the motor towards the desired target. You'll note that now we're half stepping we can run twice as fast (delaying 1mS).


With that in place we can turn to the problem of semaphore. To do that we need to control 2 flags, and in a "real" programming language we'd do that with an array and a data structure containing the outputs and the positions. Unfortunatly Sniff can't do that - it is after all Scratch, and data structures are the main limitation of a language designed for at primary school children. Instead we need to duplicate the halfStep script and make it operate on a second set of coils.


make leftFlag list of numbers
make rightFlag list of numbers
make key string "abcdefghijklmnopqrstuvwxyz"
when load
.#A
.add 1 to leftFlag
.add 8 to rightFlag
.
.#B
.add 2 to leftFlag
.add 8 to rightFlag
.
.#C
.add 3 to leftFlag
.add 8 to rightFlag
.
.....
.
.#X
.add 5 to leftFlag
.add 7 to rightFlag
.
.#Y
.add 3 to leftFlag
.add 6 to rightFlag
.
.#Z
.add -1 to leftFlag
.add 6 to rightFlag

We add the flag positions to two lists - one for left and right (as we face them). However there's one trick to this - down is position 0, so when we move to position 4 from there we move clockwise, but for the right flag this would hit the left flag as it rotates. We want the right flag to move from the down position to its target position anticlockwise, so instead of recording it as 0 we record it as 8. Simarly when we want the left flag to move to the 7 position, we call it -1

Now we can write some code to look up letters in these lists:

make theLetter string
make theLeftPos number
make theRightPos number
when getPositionsForLetter
.make counter number
.repeat length of key using counter
..if letter counter of key=theLetter
...set theLeftPos to item counter of leftFlag
...set theRightPos to item counter of rightFlag
...stop script
.set theLeftPos to 0
.set theRightPos to 8

Finally we just need to initialise everything, get a message from the user, then step through the letters of the message broadcasting each in turn:

when start
.make counter number
.
.set position1 to stepsPerTurn
.set position2 to 0
.set target1 to position1
.set target2 to position2
.
.broadcast halfStep
.broadcast load and wait
.
.ask "What's the message?" and wait
.broadcast attention and wait
.
.repeat length of answer using counter
..set theLetter to letter counter of answer
..broadcast getPositionsForLetter and wait
..say join [theLeftPos] join ":" [theRightPos]
..set target1 to theRightPos*stepsPerTurn/8
..set target2 to theLeftPos*stepsPerTurn/8
..wait until position1 = target1 and position2 = target2
..wait 0.5 secs
.
.set target1 to stepsPerTurn
.set target2 to 0

And here's the robot actually running:

(idea for next project - use GSM shield to actually receive SMS messages and show them as semaphore!!!).

Download the source code.

No comments:

Post a Comment