How to build a Pico-powered Hull Pixelbot

In this tutorial, Rob Miles shows you how to make a Pico-powered pixel packing robot you can program from your browser in ‘Python-ish’.

Find out how to create a little robot you can program from your PC browser. Along the way, we’ll dig into the deepest recesses of computer interrupts, build a programming language that has angry and happy commands, and use a web-based robot development environment.

Figure 1 It’s called the Hull Pixelbot because it was created in the city of Hull in the UK
Figure 1 It’s called the Hull Pixelbot because it was created in the city of Hull in the UK

The Hull Pixelbot is a little robot with a pixel on the top. Figure 1 shows the very first one. The author wanted to find out just how much you could do with a robot powered by an Arduino Uno, a very popular (if somewhat limited) microcontroller. Version 1 just moved around the desk. Version 2 had a pixel and distance sensor and became the Hull Pixelbot. At first the robot was controlled by C++ compiled on a PC and loaded into the robot. But then a tiny programming language, ‘Python-ish’, was developed that runs directly on the robot. The robot can obey Python-ish commands directly at any time – even when running a program. Compiled code can be stored on the robot and executed automatically when the robot is switched on. You can add a network connection (coming next month) so that you can update the program from anywhere. 

The Pixelbot is a great way to upcycle old Arduinos and use them to power fun robots. There are also versions powered by the Raspberry Pi Pico or the Espressif ESP32. You can download the software and find out all about the project at hullpixelbot.com.

Robot Circuitry

Figure 2 shows the circuit diagram for the robot. The devices fit into a 400-point breadboard which sits on top of the robot. There are plenty of spare connections for other interfaces you might want to add. Let’s start by looking at the various devices on the robot, starting with the one that tells the robot if there is something in front of it. 

Figure 2 The red component is a level converter which is needed because the distance sensor uses 5-volt signals rather than the 3.3 volts used by the Pico
Figure 2 The red component is a level converter which is needed because the distance sensor uses 5-volt signals rather than the 3.3 volts used by the Pico

Measuring distance

Robots that crash into things look silly. So, the robot has a distance sensor on the front to detect any wayward brick walls. The HC-SR04 sensor sends out a burst of high-frequency sound and generates a signal pulse when it gets a response. Software in the robot measures the length of the pulse and because we know the speed of sound, it can calculate the distance the robot is from an obstacle. The distance sensor uses two signals. The trigger signal asks the sensor to start a measurement and send a burst of sound. The sensor lifts the echo signal to 5 volts when the sound reflection is detected. We could write a function to trigger the sensor and spin in a loop counting and waiting for the echo signal to go up. This would work, but it would mean that the program would have to stop while distance reading is taken. Sound travels at around 330 metres a second, so if an obstacle is a metre away, it would take around 150th of a second for the reading to be taken. This doesn’t sound like much, but the author hates writing code that waits around for a response, so hardware interrupts are used to process the echo signal. 

If I may interrupt you…

The first computers didn’t have interrupts. Programs had to constantly check for changes to their inputs. The interrupt was added in the 1950s. Hardware in the computer processor detects a change in state of an input and diverts program execution to a piece of code called an ‘interrupt handler’ to process the incoming event. When the event has been dealt with, the original program continues. Interrupts are part of how modern computers work. Whenever you press a key, move the mouse, or your computer receives a network message, an interrupt handler takes control to deal with the event. We can use interrupts to allow the robot control program to run while a distance reading is being taken. 

#define DISTANCE_TRIG_PIN 17
#define DISTANCE_ECHO_PIN 16

The code above defines the two symbols which will represent the trigger and echo pin numbers to be used on the Pico. Using symbols makes it easy for us to change the pin numbers if the hardware design changes. We are going to use pins 16 and 17.

pinMode(DISTANCE_TRIG_PIN, OUTPUT);
pinMode(DISTANCE_ECHO_PIN, INPUT);

The two statements above set up the pins. The trigger will be an output, and the echo an input. 

attachInterrupt(digitalPinToInterrupt(DISTANCE_ECHO_PIN),
    pulseEvent,
    CHANGE);

This statement attaches an interrupt handler to the echo pin. The digitalPinToInterrupt function takes the number of the interrupt pin and converts it into a corresponding number for the specific hardware in use. The handler function is called pulseEvent and the interrupt will fire when the pin changes state (either goes from high to low or low to high). 

void pulseEvent()
{
    if(digitalRead(DISTANCE_ECHO_PIN)) {
    // pulse gone high - record start
    pulseStartTime = micros();
  }
  else
  {
    pulseWidth = micros() - pulseStartTime;
    distanceSensorState = DISTANCE_SENSOR_READING_READY;
  }
}

The pulseEvent function runs when the echo signal changes state. The echo signal goes high at the start of a reading and low when the response has been received. The code above checks to see if the pulse has gone high. If it has, it records the current time in microseconds. If the pulse has gone low, the code determines the width of the pulse by subtracting the start time from the current time in milliseconds. It then sets a flag to indicate that a new distance sensor reading has been obtained. The robot code can check this flag every now and then to see if a new reading has arrived and update the @distance value in the Python-ish program running on the robot. This means you can use constructions like the code below which makes the robot pixel turn red and flash in an angry way if you get too close.

if @distance < 100:
    angry
    red

Spinning the wheels

Stepper motors are used to turn the robot wheels. These are slower than servos or simple electric motors, but they have much greater precision. You can instruct the robot to move 100 millimetres and it will move exactly that distance. The author is happy to trade speed for accuracy. He’s found that fast-moving robots tend to crash into things or fall off the table. 

Figure 3 The coils and magnets are arranged slightly differently in a real motor, but the principle is the same
Figure 3 The coils and magnets are arranged slightly differently in a real motor, but the principle is the same

Figure 3 represents the inside of the robot
stepper motor. The round thing in the middle is the motor shaft, and the block on the shaft is a little magnet. If we turn on one of the four coils, the magnet is attracted to that coil. In Figure 3, coil 1 is turned on and the magnet has been pulled towards it. We can make the shaft turn by performing the following:

• Turn on coil 2 (magnet is pulled between coils 1 and 2)

• Turn off coil 1 (magnet is pulled to coil 2)

• Turn on coil 3 (magnet is pulled between coils 2 and 3)

• Turn off coil 2 (magnet is pulled to coil 3)

• Turn on coil 4 (magnet is pulled between coils 3 and 4)

• Turn off coil 3 (magnet is pulled to coil 4)

• Turn on coil 1 (magnet is pulled between coils 4 and 1)

• Turn off coil 4 (magnet is pulled to coil 1)

A program that repeated these changes would make the shaft turn continuously. We can represent the motor coil settings using an array of eight bytes:

const byte rightMotorWaveformLookup[8] = { 0b01000, 0b01100, 0b00100, 0bB00110, 0b00010, 0b00011, 0b00001, 0b01001 };

If you look at the bit patterns in the binary constants in the code above, you can see that coil 1 is represented by bit 0b01000, coil 2 by bit 0b00100, and so on. In the Arduino Uno, these bit patterns can be loaded directly into an output register to turn on the specified outputs. 

PORTB = (PORTB & 0xF0) +
   rightMotorWaveformLookup [rightMotorWaveformPos];

The statement above takes a value from the lookup table and drops it onto the bottom four bits of the PORTB output register, setting all four signal bits at the same time. This is much more efficient than setting each output bit individually. 

Figure 4 The C++ program that implements Python-ish is being debugged using a Pico Debug Probe. This lets us look inside the program and see what it is doing, even when it is running an interrupt handler
Figure 4 The C++ program that implements Python-ish is being debugged using a Pico Debug Probe. This lets us look inside the program and see what it is doing, even when it is running an interrupt handler

A program can make the right-hand motor turn just by stepping rightMotorWaveformPos from 0 to 7, which would access each of the elements in the rightMotorWaveformLookup array. The program would need to pause between updates to allow the magnet to move into position. We can control the speed of the motor by changing the delay between each update. 

The robot contains two motors, so the above code must be repeated to drive them both. However, we now have a problem. We want the robot to be able to do things while the wheels are turning. This is easy if we are using a DC servo motor that turns when power is applied, but stepper motors require continuously changing signals from the software. If the processor is updating the motor position, it can’t do anything else. What we need is a way of generating the above signals without the processor having to wait around. It turns out that there is a way, and it involves using interrupts again. 

Get the full tutorial in HackSpace magazine issue 74

Download issue 74 of HackSpace magazine and head to pages 72 – 78 for the full tutorial.

hackspace front cover issue 74

Each month, HackSpace magazine brings you the best projects, tips, tricks and tutorials from the makersphere. You can get HackSpace from the Raspberry Pi Press online store or your local newsagents.

No comments
Jump to the comment form

Leave a Comment