Making Self-Driving Robot “Smarter” — Part I

Love Robots to Death
9 min readOct 3, 2022

--

Hi, welcome back.

As I have a big project, I decided to divide the story to several parts. In this part we are going to speak about our task and aims, modify our robot’s platform, install special software on it and check if we can send pin commands.

So in previous story I told you how I developed Primitive Self-Driving Robot only with Arduino. The only we could do there — to move forward, backward, left, right, to understand that we have an obstacle before us, trying to move around someway. We did not know anything about the space around and about our internal coordinates in this space. We did not know how far we moved from the start point and had no idea how we can move back to the start. So, let’s make it “smarter”.

What I mean here — is to develop system, where we can work not only with recent data and primitive control; but also build a local map and use robot’s odometry and coordinates on a map. And after that we can build a plan of moving and also make robot control smarter.

The global task.

Let’s go to our task. My goal is to run ROS Navigation Core and ROS Move Base, which look like that:

Image 1: ROS Navigation Core

The key components of navigation stack are maps and move planners. The global costmap is a map created and saved on the map server before running the navigation core. Usually costmap is just similar to grey scale image where we consider every pixel as some space area with definite area size and consider pixels > 0 as obstacles. Also we have local costmap which is the map got from SLAM using sensors. I will talk about SLAM in more details later, when we would discuss sensors’ interaction. Now all we are going to know about SLAM — it is a way to know something about environment from several sensors.

And we have two types of move planners (Image 1) which just help us to move considering our state on map. They create path from point A to point B.

Why we need the two types of planners and maps? It is because of the probabilistic concept of moving and sensor data. In other words, we consider that our GPS system, IMU sensor or perception sensors can be noisy and get inaccurate data, so we decrease uncertainty by using SLAM and by comparison global and local position. It is similar to your actions in unfamiliar place — you look in your GoogleMaps and trying to compare it with what you see around.

Image 2: ROS move base

Move base is the wrapper for navigation core. We speak about navigation core as some abstract thing, move base give us the possibility to connect navigation stack with sensors’ output and motor driver controller. In other words, it is the plan how to install it in self-driving system.

Let’s talk about it external components (Image 2):

  • Sensor sources: we are using perception sensors as a camera or a lidar to know more about the environment. In our case we have the Ultrasonic sensor and also we can install a web-camera on our robot platform;
  • Sensor transforms and odometry: we can receive coordinates and also recive the odometry data from IMU sensor OR we can receive it from the difference of velocity values;
  • Base controller: the main task of our global and local planners is not just build a path, but also move our hell machine from point A to B. So we compare our start coordinates on global map and local map and send to base controller the Twist commands where there are values of angular and linear velocities. The base controller is the node or service to translate Twist commands which is published in /cmd_vel topic to motor driver commands. What is it — “topic” — read further;

So, that is, our global task is developing the self-driving system based on ROS move base. Let’s go to sub tasks.

Upgrading Robot’s platform.

First of all we need a different board for that. Because we need more RAM and also we need to save our data and sources somewhere. I had the Raspberry Pi 3 (later I will replace it with Raspberry Pi 4)— our first sub tasks are to set it on the robot platform and to check if it interacts with another shields. So I left:

  • L298P Shield — Motor Driver Module;
  • Tank Robot classic — the platform with tank wheels and wheels’ motors;

Let’s look at Raspberry Pi 3 pins map:

Image 3: Raspberry Pi 3 pin maps

Here we have the following types of pins:

  • Power pins;
  • GPIO — general purpose input/output pins;

All GPIO pins can be used for input/output logic 0 or 1. But some of them have the special cases. Usually, there are more then one possibility to use GPIO. On Image 3, we can see that some are used for sending PCM data, for example. We are interested in GPIO for PWM — as you can remember, we have used PWM for motor driver.

Image 4: Connect Raspberry Pi to L298P Shield

We are connecting the motor drive module with the Raspberry Pi 3. We are using the same pins on L298P Shield and chose 32, 33 pins for PWM and 29 and 35 pins for simple output for connection to L298P Shield.

Image 4: Connect Raspberry Pi to L298P Shield

I faced with lack of supply from accumulators I first used. In our start kit we had two accumulators, and it was not enough for L98P Shield and Raspberry. So I added one more accumulator.

Image 6: extra accumulator

Install software on Raspberry.

After install Raspbian image I should install also ROS. Although ROS is very popular for different robotic prototypes and simple self-driving models, it has lots of problems:

  • ROS1 can be installed only on definite distributive of Ubuntu or Debian (some time ago they don’t have debian package at all);
  • ROS1 uses only tcp-channel for node connection. The websockets are used for interaction. With a complicated design of nodes and topics and using big data in topics (such as images) it causes the lack of tcp-sockets and the lack of memory for buffers which save data for tcp. You can notice that problem when the orphan sockets appear;
  • As ROS1 is used mostly for prototyping some standard ROS packages are not optimized enough for memory consumption and time performance;

There is ROS2 which decides some of problems above, but I do not have an experience with the building and running it on a board, so I thought about developing my own node communication system. But it is too complicated task, so I decided to proceed with ROS1.

There are plenty of descriptions what is ROS about and how it works, so I am not going to repeat it again, I will just leave useful links here:

http://wiki.ros.org/ROS/Tutorials

https://link.medium.com/RlMNIYqcItb

The thing we should know about ROS for getting started is the key components, they are nodes and topics. Node is some running app, which do some encapsulated task like translating velocity values to PWM values or getting the image data and returning object detection results. And there are topics that can be viewed as communication channels. Any node can subscribe on the definite topic and get the data from it. And also any node can publish something in topic. It was build on the pub/sub pattern.

As we have a special architecture on Raspberry, we cannot just install the packages from repositories. Here is the guide for installing the ROS on Raspberry, but check that your ROS version matches your Raspbian version.

Now we can check if the ROS works on Raspberry. Just run “roscore” in console. It is the ROS master node:

Image 7: running roscore on Raspberry

Learning to work with pins.

Let’s come back to Image 2. Obviously we need to learn how to send commands on pins at least for two parts of move base stack: the base controller and getting sensor data. In our base kit motor driver and Ultrasonic Sensor do not have the ROS modules, so we should develop these nodes by ourselves.

You have two options for pins interaction. First, you can use pseudo files from /sys/class/gpio. You can write or read the data with them. It works the following way:

  • You are finding what GPIO number your current pin has. Following the scheme on Image 3, the 33 pin has GPIO 13 and 32 has GPIO 12, also there are GPIO 5 and 19 for 29 and 35 pins;
  • Then you should create the files or files’ descriptor with this GPIO number in /sys/class/gpio. For example, in bash script it will be that way:
#!/bin/shPWM1_PIN=12
PWM2_PIN=13
DIR1_PIN=5
DIR2_PIN=19
GPIO_PATH=/sys/class/gpioecho $PWM1_PIN > $GPIO_PATH/export
echo $PWM2_PIN > $GPIO_PATH/export
echo $DIR1_PIN > $GPIO_PATH/export
echo $DIR2_PIN > $GPIO_PATH/export
echo out > $GPIO_PATH/gpio$PWM1_PIN/direction
echo out > $GPIO_PATH/gpio$PWM2_PIN/direction
echo out > $GPIO_PATH/gpio$DIR1_PIN/direction
echo out > $GPIO_PATH/gpio$DIR2_PIN/direction
  • After that just send 0 or 1 to pins:
echo 1 > $GPIO_PATH/gpio$PWM1_PIN/value
echo 1 > $GPIO_PATH/gpio$PWM2_PIN/value
echo 0 > $GPIO_PATH/gpio$DIR1_PIN/value
echo 0 > $GPIO_PATH/gpio$DIR2_PIN/value
sleep 1echo 1 > $GPIO_PATH/gpio$DIR1_PIN/value
echo 1 > $GPIO_PATH/gpio$DIR2_PIN/value
sleep 1echo 0 > $GPIO_PATH/gpio$DIR1_PIN/value
echo 1 > $GPIO_PATH/gpio$DIR2_PIN/value
sleep 1echo 1 > $GPIO_PATH/gpio$DIR1_PIN/value
echo 0 > $GPIO_PATH/gpio$DIR2_PIN/value
sleep 1echo 0 > $GPIO_PATH/gpio$PWM1_PIN/value
echo 0 > $GPIO_PATH/gpio$PWM2_PIN/value
echo 0 > $GPIO_PATH/gpio$DIR1_PIN/value
echo 0 > $GPIO_PATH/gpio$DIR2_PIN/value
echo $PWM1_PIN > $GPIO_PATH/unexport
echo $PWM2_PIN > $GPIO_PATH/unexport
echo $DIR1_PIN > $GPIO_PATH/unexport
echo $DIR2_PIN > $GPIO_PATH/unexport

I made a video of running a script above.

https://www.youtube.com/shorts/Uajdj3cjCEo

You can notice, that in the script we do not use PWM. For working with PWM we can use WiringPi library.

I faced some problems, of course (despite of messing up pins and running after moving robot). The first problem is another pins numbering. Here is the output of “gpio readall” where you can find your current GPIO number using Wiring Pi.

Image 8: gpio readall output

You can see that now we have GPIO 23 and GPIO 26 instead of 12 and 13 and GPIO 21 and 24 instead of 19 and 5.

The second problem is the maximum and minimum PWM values which are different from the previous used. In previous story we could move with the PWM value 200, but now we could start moving only with PWM value 500. Values under were not enough to move platform.

Why is it so important? We will use the PWM values diapason when we will develop our PID regulator, so we should know the possible maximum and minimum.

Here is my simple script for moving forward, backward and turn left. The code is quite similar to the arduino version and it is very useful.

Image 9: simple script for moving with Wiring Pi

And here is the result on video:

You can notice, that although we use the same PWM value for moving backward and forward, in fact it moves with different velocity. That will create us some problems with PID regulator development, but let’s talk about it in the next chapter.

As I said before, we also have Ultrasonic sensor. Well, here is the same actions:

  • Choosing pins. We need one simple pin for OUTPUT — for servo moving and two pins for reading and writing for Ultrasonic sensor itself;
  • Then we can just repeat the arduino code from previous part but using Wiring Pi;

That’s all for today. In next chapter we are going to wrap our GPIO scripts in ROS nodes to create our wrapper for move base stack.

--

--

Love Robots to Death

Hi. My name is Olesya Krindach and I am software engineer and data scientist with background in deep learning both CV and voice.