Electronics and C Programming for a Mobile Robot

Johan Schwind
8 min readDec 11, 2020

--

In this article of my three part series on mobile robots, we will look at all the electronics required to make our mobile robot Cerus move. We will walk through connecting the most important components using common interfacing strategies and we’ll implement first bits of code to enable low-level control over our robot. This article requires you to have a basic understanding of electronics and programming but I hope I will be able to explain all the specifics. Let’s get started.

⚠️ When programming and testing code on a live robot, make sure the wheels are not touching the ground until you are certain your code works as expected.

Block Diagrams

Whenever designing a complex system, I like to use block diagrams to organize my thinking. In the simplest form of a block diagram, each component of the system is represented by a box, with lines indicating connections between components. The nice thing is that we can start this at a very high level (even throw in some blank boxes for components that you are unclear on) and then zoom in on specific areas and add more detail as we go. This process helps to consider the complete system and the interplay of components from the start and prevents us from getting lost in spec sheets.

Here is an example of a block diagram I used to figure out the components for Cerus:

With this rough sketch in place, we can now go into component selection.

Building a number of mobile robots, one thing that I’ve learned is that it’s not ideal to try and run motors using a microcomputer (e.g. the Raspberry Pi). Since most motor controllers rely on the input of a PWM signal (more on that in a minute), a microcontroller is much better suited to handle low-level controls (such as turning the motors) of our robot. This leads to a clear hierarchy in Cerus’ block diagram: Microcomputer, Microcontroller, Motor Controllers, Motors. Beyond that, I’ve added a 5V step-down and a 12 V current-limiting converter. The 5V step-down converts the battery’s native 12V into a voltage suitable for the microcomputer. The 12V step-down acts as a current limiter, so that stalling motors don’t risk crashing our computer.

Microcomputer

The microcomputer is the brain of our robot. There are many viable options out there, including the recently released Raspberry Pi 4. Most microcomputers run an implementation of Linux and have a number of I/O ports, making it easy to write and test software for your robot directly on the device.

Making a mobile robot autonomous takes a lot of processing power. And while we won’t get into machine learning in this series, I decided to choose the NVIDIA Jetson Nano as the brain for Cerus. The Jetson Nano has a similar performance to the Raspberry Pi in traditional applications but outperforms it significantly in parallelizable applications, specifically computer vision and machine learning. The Jetson Nano runs Ubuntu and Jupyter (an interactive Python environment) which allows for very elegant remote programming and control of our Robot WiFi (we’ll cover that in article 3). Unfortunately, the Jetson Nano doesn’t have is a WiFi adapter. Many guides suggest using a USB WiFi dongle, but they are all not great. I ended up adding a dedicated Intel WiFi module (Intel AC8265) which slots directly into the dev board for much better WiFi connectivity. I also added a fan because the Jetson can run a little hot.

Microcontroller

Microcontrollers are great for low level signal processing, making them ideal for a applications involving hardware. Most motor drivers require a square wave signal as a control input. This square wave can be modulated via PWM (pulse-width modulation) to control the speed of our motors. Microcontrollers excel at PWM and many of them offer multiple channels of true hardware PWM. Another big advantage of microcontrollers is that they offer Interrupt Service Routines (ISR). This is important when we want to observe changes in a sensor signal (for example from a motor encoder) at a low level. Rather than continuously polling sensors (and eating up resources) an ISR allows us to have a new sensor reading interrupt whatever else the microcontroller was doing, process the signal, and then keep going. I ended up choosing the Arduino Mega 2560 which just offers a little bit more processing power and more inputs and outputs over the classic Arduino Uno.

Power Supplies

Pulling power directly out of a battery is almost never a good idea, so I added two power converters to Cerus. A 10A current limiting converter to power the motors (via the motor drivers) and a 5V step down converter to power the Arduino and Jetson. These are inexpensive components that provide a number of practical safety benefits beyond just adjusting the voltage to match your components.

The Need for Encoders

Wheel encoders can be a little bit intimidating at first, because they add four extra wire connection per motor and reading them is not completely straightforward. Maybe that’s why a lot of simple robot platforms including the Jetbot don’t use them. They are, however, an essential part of any robust mobile robot for two reasons:

1. Most motors aren’t perfect. Even if you use four identical motors in your design, chances are that they won’t spin at the same speed. Without using encoders for feedback that means your robot will not drive in a straight line even if you tell it to.

2. They are a straightforward way to determine our robot’s position in the world. We’ll take advantage of that in part three of this series.

Arduino Code

With all the components selected and connected as shown in the block diagram, it’s time to start thinking about our first lines of code. We will start with very low-level control, building a solid foundation and then work our way up to more abstract (but more interesting) code. One of the first things I wanted to figure out when building Cerus was how to efficiently interface between the Jetson Nano and the Arduino Microcontroller. Down the line, this would allow me to automatically issue control command from the Jetson to the Arduino, a prerequisite for teleoperation and autonomous driving.

As with any programming challenge, it’s worth to think long and hard about the problem at hand before jumping into writing code. After a few failed attempts I was able to break the problem down into four parts:

· Identifying the right format for movement data using ROS on the Jetson Nano

· Writing a custom serial interface to communicate efficiently between the Jetson Nano and the Arduino

· Converting the movement data from the Jetson to motor controller commands on the Arduino

· Reading our wheel encoder and reporting back how far our wheels have turned

The Right Data Format

There are many ways to communicate movement data, but I wanted to use a format that already exists in the Robot Operating System (ROS), because I knew I wanted to migrate my system to that framework later. A common way to relay movement information in ROS is using the Twist Message*:

# This expresses velocity in space broken into its linear and angular parts. Vector3 linear
Vector3 angular

Because our robot moves in 2D space (it can’t go up and down like a drone), our linear and angular velocity are scalars, meaning they are single real numbers we can represent as a float (or double) data type, so our command message looks like this:

(FLOAT) LINEAR_VELOCITY, (FLOAT) ANGULAR_VELOCITY

*Thanks to Daniel Snider for the great guide.

A Custom Serial Interface

Note: ROS offers an Arduino integration that can be used to skip this part. Since I was interested in learning more about Serial interfaces, I took this extra step.

To relay the move command message, I used the Serial interface available both on the Arduino and Jetson Nano. Serial has great libraries in both Python (pySerial) as well as for Arduino (Arduino Serial) and we can use it to build custom data interfaces. Because serial communication has a lot of intricacies and resources are limited on the Arduino, I leaned heavily on this amazing guide by Robin2 to find the best way of reading data from the Serial port on Arduino.

I decided to add start and end markers (in this case ‘<’ and ‘>’) to the command, just to be able to avoid jumbled commands. The final control command looks like this:

(CHAR)<, (FLOAT)LINEAR_VELOCITY, (FLOAT)ANGULAR_VELOCITY, (CHAR)>

Onto some code! First, we read the data from the serial buffer:

And then parse the characters into usable variables of type float:

Converting Movement Data to Motor Commands

While our movement commands get parsed into float variables for linear and angular velocity, our motor controllers expect signed integer values between -255 and 255 for clockwise or counter-clockwise rotation for each motor.

We can convert linear and angular velocity to motor rotation like this:

Left wheel velocity (m/s) = linear_speed + angular_speedRight wheel velocity (m/s) = linear_speed — angular_speed

For this to work well, we also need to make sure that the sum of our linear and angular velocity is not greater than 1.0 and then map our left wheel and right wheel speeds to the motors on either side.

In C++ code:

Reading Encoders using Hardware Interrupts

It would be inefficient to constantly poll our encoders and it would prevent the Arduino from doing other things, such as interpreting move commands. Luckily, we can use a Interrupt Service Routine, to monitor our encoder channels and execute code only when a change to the channel is detected.

attachInterrupt(2, flag1, RISING);
attachInterrupt(4, flag2, RISING);

flag1 and flag2 are function calls that read the encoder channels, checks if our wheels are spinning forward or reverse and then increments a global count variable:

INTFLAG is then checked in our main program loop and if the flag has been set by our function above, we report the current encoder readings back to the Jetson via Serial.

The delay is important, it ensures that we’re not spamming our Serial connection with every tick but rather wait for a few ticks to pass before sending the data. The data itself is similar to our motor control command:

(INT)LEFT_COUNT, (INT)RIGHT_COUNT

Per spec sheet, the Pololu motors used for Cerus report 48 signal changes per revolution. Multiplying this by the gearbox ratio of ~75:1, one wheel revolution equals 3600 signal changes. Because we’re only observing the rising edge of the encoder signal we end up with about 450 signal changes or ‘ticks’ per wheel revolution, which means our robot will drive ~0.4 mm per tick.

You can view the complete Arduino code here, on GitHub

Conclusion

With our electronic components selected and connected and our Serial interface and low-level motor control code built, we now have a solid foundation for all the fun stuff: Remote controlling our robot, and making it do interesting things autonomously. We’ll work through these things in our third chapter.

Continue reading part three, Mobile Robot Teleoperation and Go-To-Goal with Python.

--

--

Johan Schwind
Johan Schwind

Written by Johan Schwind

I help companies build technology-driven solutions for the future of cities by combining human-centric design with rapid prototyping and testing.