Mark Gilbert's Blog

Science and technology, served light and fluffy.

Rover v2: Technical Breakdown

In Episode 31, Lucy and I show off the new Raspberry Pi-powered rover that we built.  In this post, I’ll go into the technical details of how we did it.

Parts List

The GPIO Overlay is a simply piece of thick paper with holes punched so that it can sit over the pins on the RPi, and has labels for all of them.  It was a minor indulgence, but it made hooking up the wires so much faster.

Up to this point, I only had male-to-male hookup wire, which worked great going from an Arduino’s female pins to a breadboard.  The RPi, however, comes with male pins, so I had to buy some different hookup wires.  This particular set worked extremely well, even when placed on consecutive pins on the RPi.

Each of the TN6612FNG motor drivers could control 2 motors, and since the chassis was a four-wheel drive, I purchased two of these boards.  So far, the rover only moves its motors in pairs (the left wheels always move in the same direction as each other; ditto for the right wheels), so I probably could have gotten away with a single motor driver.

Prepping the Motor Drivers

The first thing I did was solder some straight headers onto the two motor drivers.  That allowed me to place the drivers onto the breadboard, and wire them up with the hookup cables.  It also means we can completely tear the rover’s components down, and re-use these drivers with other projects.

On the podcast I mentioned that I soldered the headers in upside down.  That was so I could read the pinouts printed on the underside of the board.  That saved me having to think about which pin was which when I was wiring it up.

Raspberry Pi

We wanted to be able to upload programs to the Rover remotely, without a tether.  While it is possible to wirelessly reprogram an Arduino, I thought it would be simpler and a little less expensive to do so with the Raspberry Pi 2 (RPi) and a wireless USB dongle.  The RPi would appear on our home network as just another device, which means we could remote into it from any of our other machines.  I had also never worked with an RPi, and this project gave me the perfect excuse to learn.

So, the first task was to get the Raspberry Pi configured so that it could boot "headless" – without a keyboard, mouse, or monitor connected.

But before I could do any of that, I found I had to get an operating system for it.  I mistakenly assumed that the RPi would come with Linux pre-installed, and I could just boot it up.  When I got the unpacking instructions, and Step 2 was "Insert your microSD card", I had to stop and re-evaluate that assumption.

I started by temporarily appropriating the microSD card I’ve been using for my wearable, and formatted it using the SD Association’s Formatting Tool.  Next, I downloaded the basic, and appropriately-named, OS package called "noobs", for "New Out-Of-The-Box Software".  This was actually Debian Linux optimized for the Raspberry Pi (hence its proper name "Raspbian").  I extracted the contents from the ZIP file, and copied them to the root of the microSD card.

When that was done, I put the card into the RPi, and booted it up.  The RPi provides onboard HDMI output for video.  Luckily we had a male-to-male HDMI cable, but the only "monitors" we own with an HDMI input are actually our TVs.  I set up a card-table in front of one, and ran extension cords for power, plugged in a mouse and keyboard, and fired up the RPi.  It allowed me to select which operating system to boot into – my choices were Rasbian Linux, or direct to Scratch.  If you’ve never seen or used Scratch, it’s worth it to check it out.  It’s a visual programming language, where all of the statements like "if then" or "while" are actually blocks that you can snap together.  Only compatible blocks will fit, so it’s very easy for kids to learn what makes up a valid statement.  This was at the top of my list for the programming language for the Rover, so seeing a "boot straight to Scratch" was a nice bonus.

For now, though, I focused on just trying to get the RPi running Raspbian on our network.  Having relatively little experience with Linux compared to Windows, this was harder than I had anticipated.  I spent three nights playing settings-roulette, trying to get the RPi to be visible on the network, with a static IP, so I could remote into it.  (For the record, this had nothing to do with the Raspberry Pi – this was purely me being a noob when it comes to Linux.)

My network is WEP-secured, so I had to find a way to pass the network ID and key from the RPi.  I read several posts talking about modifying the /etc/network/interfaces, and ultimately that is what I ended up doing.  Here is my final interfaces file:

    # The loopback network interface
    auto lo
    iface lo inet loopback

    # The wired network interface (using dhcp)
    iface eth0 inet dhcp

    # The wifi network interface (static IP)
    auto wlan0
    allow-hotplug wlan0
    iface wlan0 inet static
        address 192.168.1.202
        netmask 255.255.255.0
        gateway 192.168.1.1
        wireless-essid ID_Here
        wireless-key Key_Here
        dns-nameservers 12.34.56.78 12.34.56.79

    iface defalt inet dhcp

Obviously, the "ID_Here" and "Key_Here" portions were replaced with my actual ID and Key.  One of the issues I had was how these appeared in the file, specifically with or without being surrounded by double-quotes.  The configuration that worked was without – the ID and Key just appear inline.

Additionally, the DNS Server IP Addresses shown here are fake – I filled those in with my provider’s actual IPs.  I ended up downloading a package directly to the RPi (see below), which meant the RPi needed to be able to get to more than just my home network.

With this setup, I could boot the RPi up on my desk, wait a minute or so, and then remote into it using PuTTY.  The RPi was now officially headless.

FTP Server

The next piece of the puzzle was an easy way to upload our programs to the RPi.  For that we needed an FTP server.  I looked at a couple of FTP servers for Linux, and settled on vsftpd.  I followed this procedure for setting it up (minus the initial step of changing the permission on the /var/www folder, since that’s not where I wanted things to be uploaded to).  It took me about 5 minutes to get installed and configured, and works like a champ.

Programming Language

Now it was time to decide what language would we be programming in.  As I mentioned before, I favored Scratch for its simplicity.  However, I also wanted to be able to define custom blocks to represent the commands MoveForward, MoveBackward, TurnLeft and TurnRight.  I found that Scratch 2.0 allows you to do exactly that.

Unfortunately, Scratch 2.0 requires Adobe Air, which does not run on the RPi.  The latest version of Scratch that does run on the RPi – version 1.4 – doesn’t allow us to define custom blocks.  So, scratch Scratch.

The next easiest language was Python, which also came bundled with Raspbian, so we went with that.  Now the question became how do we control the general purpose input/output (GPIO) pins on the RPi using Python?  For that, we turned to a Python module called RPi.GPIO which does exactly that, and which also came bundled on the OS image.

To control our motors, we simply needed to set the correct GPIO pins either high or low.  Doing that using the RPi.GPIO module is trivial:

    import RPi.GPIO as GPIO

    GPIO.setmode(GPIO.BCM)

    GPIO.setup(18, GPIO.OUT)
    GPIO.setup(23, GPIO.OUT)

    GPIO.output(18, True)
    GPIO.output(23, False)

    GPIO.cleanup()

Let’s break this down:

    import RPi.GPIO as GPIO

This simply imports the RPi.GPIO module, and gives it a local name of "GPIO".

    GPIO.setmode(GPIO.BCM)

This sets up the board in "BCM", or "Broadcom SOC channel" mode.  The other available option is "GPIO.BOARD", which simply numbers all of the GPIO pins from 1-40.  You can find out more information about these two modes, and what the numbering is, from this post on StackOverflow.

    GPIO.setup(18, GPIO.OUT)
    GPIO.setup(23, GPIO.OUT)

Like with an Arduino, you need to configure which pins you will be using, and whether they are going to be inputs ("GPIO.IN") or outputs ("GPIO.OUT").  In this example, I’ve configured pins 18 and 23 to be outputs.

    GPIO.output(18, True)
    GPIO.output(23, False)

Each pin can be set high ("True") or low ("False").  In this example, I’ve set pin 18 high and pin 23 low.

    GPIO.cleanup()

I found that if I didn’t reset the pins after each execution of the program, it would throw a bunch of warnings the next time I ran it, to the effect of "that channel is already in use".  So, I needed to be a good little Boy Scout, and clean up after myself.

Another interesting things I learned about the RPi is that all of the channels are digital.  The RPi doesn’t do analog at all – at least not natively.  I did find a post on the Raspberry Pi forums that described building a "poor man’s analog" circuit from a resistor and a capacitor in parallel, but for our first attempt I figured "on" and "off" for the motors would be sufficient.

Getting the wheels moving

Now it was time to put everything together, and actually get the rover moving.  Using this hookup guide, I wired up one set of wheels with the first motor driver, the battery pack that came with the rover chassis (which held 4 AA batteries) and the RPi.

Each motor required 5 pins on the motor driver – 2 that would lead to the motors themselves, and 3 that would be connected to the RPi: IN1, IN2, and PWM.  Form the RPi’s perspective, IN1 and IN2 were always set to be opposite to each other – one high and one low.  Setting IN1 high would drive the motor in one direction, and setting IN2 high would drive it in the other.  The PWM channel was basically the speed at which the motor would turn.  If I were using an analog signal, I could send a number from 0 to 255, where 0 is "stop" and 255 is "ramming speed".  Since I could only generate a digital signal, I just set this channel high (True, or the equivalent to 255) when I wanted the motor to turn, and low (False, or the equivalent to 0) when I wanted it to stop.

Once it was all wired up, it was just a matter of setting the correct pins high or low.  I started by creating my basic control functions:

    MoveLeftWheels(direction)
    MoveRightWheels(direction)
    StopLeftWheels()
    StopRightWheels()

Where "direction" is one of two constants (also defined in the script) – FORWARD or BACKWARD.  The wheels would keep turning until I explicitly told them to stop, which meant something like "MoveForward" was really a combination of "move all the wheels forward, wait a moment, and then stop all the wheels":

    def MoveForward(distance):
        MoveLeftWheels(FORWARD)
        MoveRightWheels(FORWARD)

        time.sleep(.1 * distance)

        StopLeftWheels()
        StopRightWheels()

        time.sleep(1)

We wanted to be able to control how far the rover moved with each step, so this function takes a parameter called "distance", which gets converted into the number of seconds to run the motors.  I wanted to be able to have the numbers represent the number of centimeters to move the rover.  However, these aren’t stepper motors, so we couldn’t get very precise with them.  The basic idea holds, though: pass a larger number to MoveForward, and the rover will move further (or at least, the wheels will turn for a longer time).

The "time" module is actually another Python module, and is included in the script by simply adding "import time" at the top.  The "sleep" function takes a parameter in seconds, or fractions of a second.  The RPi doesn’t go to sleep like a laptop goes into hibernation, though.  Whatever GPIO pins I set high and low would retain their settings while the RPi was "sleeping", which meant the motors would keep running.  Basically the MoveFoward function says "Run the motors forward while I snooze, and when I wake up I’ll tell you to stop."

Once I had this working, I wired up the second motor driver and the other two motors, and wrote the other three functions:

  • MoveBackward looks just like MoveForward, except it moves the wheels backward (the parameter passed to MoveLeftWheels and MoveRightWheels is BACKWARD instead of FORWARD).
  • TurnLeft() involves running one set of wheels (either left or right) in one direction, and the other set in the other direction.  These functions don’t currently take a parameter (for degrees of arc, for example), but they could be easily modified to do so.  With our current setup, though, turning will be just as imprecise as movement is.
  • TurnRight() does the same thing as TurnLeft(), but reverses the direction of all of the wheels.

I also created functions that would set the board up (SetUpBoard), and shut it down cleanly (CleanUp).  All of the commands that you want to send to the rover are made between these two calls.

The complete program, configured for use with the GPIO pins we used for our rover, can be found here.

Buttoning everything up

So far we have the hardware in place, the operating system configured, we can write programs in Python to run our motor, and we can upload them via FTP.  Now it was time to put something in place that would kick off the programs we uploaded.

My original intention was to be able to upload files to the RPi, and then have the RPi automatically run it.  That posed a couple of challenges:

  • Detect when a new program was present and run it in a timely manner.
  • Don’t try to run two programs, or two copies of the same program, at the same time.

My first thought was to set up a cron job (an automated scheduler for Linux) that would run a small shell script.  The shell script would look for new Python programs in a pre-defined folder, start them if found, and remove the program when it was done.  However, cron jobs can only be scheduled for at most once every minute.  That seemed like a long time to wait for the program to start once we uploaded it, and it would complicate debugging.  If the rover didn’t move, was it because we didn’t wait long enough, or was there actually a bug in our program? 

This approach also didn’t address the second challenge – if I managed to get the RPi looking for and running programs every 3 seconds (for example), but the current running program requires 15 seconds to complete, how do I prevent it from spinning up a second copy of the same program 3 seconds from now?

In the end, because I wanted to get the thing working quickly, I decided to just kick off the programs manually, via PuTTY.  We write our program on our laptop, upload it to the rover via FTP, then execute the program from the command line like so:

sudo python rover.py

It would not surprise me at all to learn that someone has built a better cron for Linux, so we can revisit this piece later.

What’s next?

We’d like to add a digital camera and transmit back pictures (or rough video) of what the rover was seeing.  That would add to the illusion that this little rover is on another planet, and we’re Mission Control.  The RPi actually has a dedicated camera slot onboard, and we’ve considered getting this camera for it sometime in the future.

The impreciseness of the motors in moving and turning can get a little frustrating.  Short of getting a different chassis with more precise motors, I’ve wondered if we could add a gyroscope or an IMU to the RPi to improve at least the turning.  We would send a TurnLeft or TurnRight signal to the RPi, and instead of simply running the motors for a certain number of seconds, the RPi would read the input from the gyroscope to determine when it had completed a 90 degree turn.

It occurred to me after the fact that having the entire operating system on the SD card meant that I could configure other operating systems, and swap them in when I needed.  Windows 10 IoT Core, for example, will run on the Raspberry Pi 2, so that may be something for us to explore later.

 

All in all, this was a fun project, and I’m still amazed at how quickly it came together.  I think we’ve put together a great platform for experimentation.

Advertisements

August 7, 2015 - Posted by | Engineering, Raspberry Pi

Sorry, the comment form is closed at this time.

%d bloggers like this: