Lab2 2024
Lab2 2024
Lab2 2024
2024/2025
Lab 2. Control 1
Aims. A smart system consists of three components: sensors, actuators and controllers. The software
controller - also called an agent - gives autonomy to the system, and coordinates the sensor data with
the actions that have to be taken by the actuators. In this lab tutorial, you will learn exactly this - how to
write software controllers for a simple sensor-actuator system. Its name is Neville, and it is a robotic
vehicle that mimics a self-driving car (non-CS students can choose to work with another Lego
Mindstorms ev3 car, please ask your TA). You will use both types of control algorithms: open-loop, and
feedback closed loop.
Method. You will work in groups of three students. Neville is built on top of a Pololu m3pi robot. The
controller is an Arduino Nano 33 IoT microcontroller programmed in C++ which replaced the original
mbed LPC1798. You will write software agents that control the robot actions, based on the input read
from its sensors. If you need an adapter to USB-A, ask your TA.
You need to study before coming to this lab. Therefore you must solve all the preparation exercises (PE)
published in this document, and hand in the answers on paper to your TA, at the start of the lab. These
answers will not be graded. However, you cannot start the practical session without handing in these
answers. This tutorial uses knowledge from Lecture 3. Control and from book Chapter 3. Control
systems.
Moreover, your group has to hand in the answers to the lab exercises in Canvas, clearly marked as
Exercise 1 to Exercise 4. You should hand in a pdf file, with commented C++ code and results. Your
1
Neville started in 2018 with the pioneering work of two of our TAs, Mike Spadaru and Tomas Karosas. It owes its
name to Fiachra O’Cochlain, one of our CS alumni. The first six Pololu m3pi robots were donated by Sioux B.V.
Neville is designed and developed in the VU Beta fine mechanics and electronics workshops with great help from
Niels Althuisius. This is already the second version, carrying an Arduino micro-controller instead of the original
mbed NXP LPC1798. This new version is made possible thanks to the work of Joshua Kenyon, Koen Kahlman,
Samuel Jonathan, Geoffrey Frankhuizen and Stef van Schie, together with all the TAs Pervasive computing from
November-December 2023 who tested it and offered valuable suggestions.
1
answers to the Lab2 exercises will be graded. The deadline for this assignment is on Sunday, 10
November, 23:59.
Preparation steps
• The theoretical aspects needed for this lab were explained in Lecture 3 – Control systems. Be
sure to read the lecture slides and Chapter 3. Control systems from the book.
• Answer the preparation exercises (below), to make sure you are well prepared for this lab. Your
answers must be written on paper, and you have to hand them in to your TA at the start of the
lab.
PE1. What is an ultrasound sensor? What does it measure, and how does it work?
PE2. What is a passive opto-sensor? What does it measure?
PE3. What is an active opto-sensor? What does it measure, and how? What is the difference between an
active and a passive sensor?
PE4. Write (in pseudocode) a controller for a robot that drives forward and stops in front of an obstacle.
PE5. Write (in pseudocode) a controller for a robot with two opto-sensors, that follows a black line.
PE6. Write (in pseudocode) a controller for a robot that drives forward, and when it feels that it enters a
tunnel, it switches on its lights.
2
Note. To spare batteries and avoid hazards, at the start of the lab, please check
that all the lights on the robot are switched off. If this is not the case, ask help
from your TA.
The self-driving car used in this lab (Fig. 1) is built on top of a Pololu m3pi robot with an mbed socket.
On this mbed socket, we placed an Arduino Nano 33 IoT prototyping board. The control software,
written in C++, runs on this prototyping board. As you can see in Fig. 2, this board is actually more than
just a microcontroller; it takes the microcontroller and surrounds it with some useful support circuitry
for serial communication, networking, interrupts, etc.
The board can be accessed using the pins situated on the left and right side of the electronic circuit (see
Fig. 2). Each pin has a label and a predefined role. For example on the right side you can see a pin
labeled with GND, meaning Ground. This pin has the role of input pin, and has to be always connected
to the common ground of the system (0V). On the left side you can see a pin labeled with 3.3 V. This pin
has a fixed role of an output pin, with a constant voltage of 3.3V always available. Some pins with
multiple labels are multifunctional pins, that can be programmed to be both analog input (A0, A1, etc) or
digital output (D2, D3, etc). Analog input pins are needed to measure analog signals coming for example
from a sensor. Digital output pins are needed for example to turn on a LED.
Note: Be aware that you cannot access the Arduino pins on the board itself. You only can access them
via two parallel rows of holes situated on the mbed socket marked with red circles in Fig.3.
3
Fig. 2. The Arduino Nano 33 IoT development board.
The power of a microcontroller is that it can exchange data with a variety of sensors and actuators. For
example, by construction the Pololu robot is equipped with an array of five active light sensors (or opto-
sensors), all facing the ground. These sensors can be used to detect and follow a black line on the floor.
Also, Neville has an additional ultrasound sensor facing forwards, that measures the distance to objects
found in proximity. Finally, you will also work with a passive opto-sensor to measure the ambient light.
This sensor can be used to detect that the car entered a tunnel. As actuators, Neville has two
servomotors connected to its two wheels, and two LEDs, one red and one green.
The small breadboard on Neville’s roof (Fig. 3) is in fact a device with holes electrically connected that
will help you to realize electrical connections between sensors and the pins of Arduino. On this
breadboard, 5 holes in a row are internally connected with each other.
4
Fig. 3. A breadboard used to realize electrical circuits. A LED and different types of wires (male-to-male -
in the middle and male-to-female on the right).
Note: In this practical, you are not allowed to change anything in the robot’s
construction, except for wiring the LEDs and different sensors using the
breadboard.
2. Getting started
The goal of this lab tutorial is to learn how to write programs for a microcontroller. Any embedded code
development cycle is based on the simple loop, shown in Fig. 4. The source code is written in C++ on a
computer (host computer) using some sort of editor. The source code needs to be compiled into a
binary machine code, which is downloaded or flashed to the microcontroller and placed in your end
product (target system). Program download to the target system requires temporary connection to the
host computer. In our case, a USB cable will be used to transfer the program to the microcontroller (Fig.
4).
5
Fig. 4. The program development cycle. The USB cable connects the Arduino board to the computer.
First, you have to install the Arduino IDE. During the next steps, you may be prompted by the operating
system to allow or install additional USB drivers. Make sure to give these permissions, or your computer
will not be able to connect to the Arduino.
6
• Connect the Neville’s Arduino to your laptop using a micro USB cable like shown in the figure
below.
• Click File > Examples > 01.Basics > Blink to open a blinky example program.
• Click the blue right arrow button to compile the program and flash the binary. If all went well, a
LED on the Neville should start blinking. If it did not work, ask your TA for help!
Let us now go back to the blinky program and read carefully the C++ source code, in order to understand
what exactly happened there.
7
void setup() { The setup() function is
pinMode(LED_BUILTIN, OUTPUT); automatically called once when the
} program starts.
You may notice that Arduino programs do not use a standard C++ main() function. You can think of the
program as running the following code automatically.
main(){
setup();
while(1){
loop();
}
}
Hopefully you do now understand why the effect of this program was a continuous blinking of a LED.
Warming up exercise. Try to change the Blink program and select a different flashing rate for the LED.
Arduino programs are called sketches. A sketch consists of a single .ino file contained within a folder
with the same name. All other .ino files in this folder, with names that do not match the folder name,
will also become part of the program. The compiler will simply append these underneath the primary
.ino file in alphabetical order. Make sure that there are no overlapping function definitions or global
variable names!
8
3. Activating the actuators
Up to now we used the internal diagnostic LED. But as you already said, an Arduino board has also the
possibility to control external LEDs and motors. This makes use of the digital output feature, and we will
investigate it in this section.
In digital circuits, logic values such as 0 and 1 are represented using electrical voltages. The Arduino
board can output at its digital output pins 0 Volts as a logic “0” and 3.3 Volts as a logic “1”. All Arduino
pins can be accessed through the two rows of holes, marked with 2 red circles in Fig.3. The mapping
between the Arduino pins and the access holes is also shown in Fig. 3. For example, if we want to access
the digital output pin D20 of Arduino, we have first to find the hole corresponding to pin D20 in the two
rows of holes marked in Fig.3 with (the lowest hole on the left row), and insert a breadboard
wire in this hole.
Also note that some Arduino pins are multifunctional, for example digital output pin D20 can also be
used as an analog input pin, labelled as A6. So the analog input pin A6 can be accessed with a wire
inserted in hole labeled D20.
Fig. 3. The mapping scheme between the Arduino pins and the two rows of access holes.
First, connect the anode of the LED (long leg) to a digital output pin of Arduino, for example pin D20, by
using a female-male breadboard wire. Refer to Fig. 3 to locate the access hole corresponding to pin D20.
Next, connect the cathode of the LED (short leg) to the ground, labelled with GND, also using female-
male breadboard wire. The hardware setup is now ready.
Caution! Because the Arduino board is a fragile and rather expensive device,
which can be easily damaged by wrong electronic connections, ask the TA to
check any wiring before powering the device.
The next step is to write a program to ask the microcontroller to control this LED. This can be done by
programming pin D20 as a digital output pin.
Next you want to instruct Arduino to drive a current through the LED to turn it on. This can be done with
an endless loop that keeps providing a “high” voltage to the pinD20, which you connected to the anode
of the LED.
Create a new program, and write the following code. Note that in the code, pin D20 is addressed as just
20.
10
void setup() {
pinMode(20, OUTPUT);
}
void loop() {
digitalWrite(20, HIGH);
}
Caution! Because the board is a fragile and rather expensive device, which can be easily
damaged by wrong electronic connections, ask the TA to check your wiring before powering the
device.
Flash the program to the Arduino. What do you observe? If everything goes fine, the program starts to
run immediately and you will see that the LED was turned ON. Do you understand why?
The effect of the program execution was that a signal of 3.3. V was generated on pin D20, which
resulted in an electrical current flowing from the anode to the cathode of the LED (which is the good
direction) and eventually to the ground. The result is emitted light.
Obviously, if we want to switch off the LED, we will have to assign the value LOW to the same pin D20.
Warming up exercise [Optional]. Try to modify your program and make the LED blink.
Warming up exercise [Optional]. Wire the other LED of Neville to pin D19 from the Arduino board, and
try to control both LEDs. Remember that you can use the breadboard to create a common ground GND
island for the two LEDs, like shown in the figure below.
11
3.2. Activate the motors
If you write your program from scratch instead of using our helper sketch, then be aware that you will
have to write your setup() function yourself.
The helper sketch contains many useful functions. At this moment, we need only one function to drive
the motors, called activateMotor.
activateMotor(0, 0.3);
activateMotor(1, 0.3);
The first argument indicates which wheel is powered: 0 for left, 1 for right. The second argument, a
number between -1 and 1 is the speed. Here 1 is the fastest possible, and 0 means stop. Negative
numbers make the wheel spin in the opposite direction. The motor remains going at the indicated speed
until it receives another command.
Warming up exercise. Write a program that makes the Neville wait for 2 seconds and then drive
forward for 5 seconds.
Flash the program to Neville. Disconnect the USB cable. Place the robot in the middle of the racing plate,
or even better, on the floor. Switch on the robot by using the ON/OFF switch (see figure below). The red
and blue LEDs should shine under the robot. Sometimes it is necessary to press two times the ON button
to start it. If the robot does not start, an empty powerbank (battery) could be the problem. Ask your TA
to change the powerbank with a full one.
If everything goes fine, the robot will wait 2 seconds and will drive forwards for 5 seconds.
12
ON/OFF switch
Note: Neville can drive quite fast! Be careful not to drive it off the table.
Note: If you set the speed too low, for example 0.05, the motors will not receive enough power
to overcome friction and Neville will not move at all.
Note: To spare energy, always switch off the robot when you do not use it.
Now that we covered the actuators part, we can move on to learn how to program the microcontroller
to read data from sensors.
An Arduino board can interface with a large scale of sensors. Reading the signals from these sensors
uses its analog input functionality. The problem with embedded systems is that there is no screen
where we can print the value we measure. What we can do it write all the measured data to a so-called
serial terminal. A serial terminal is a very useful tool for debugging.
13
Arduino IDE has a terminal interface that works automatically. Connect the USB cable again. Use the
initialize() function from the helper sketch to configure the USB connection. If the Serial output window
doesn’t open automatically, click on Tools > Serial Monitor.
void loop() {
Serial.println("Hello World!");
delay(10000);
}
For example, we can print to the serial terminal the battery status of Neville’s powerbank retrieved
using the function battery() from sketch_percomp_2023.ino.
void loop() {
Serial.print("Battery: ");
Serial.println(battery());
delay(5000);
}
You will see on the terminal screen the status of the battery in volts. A fully charged battery will display
5V.
The Pololu robot has an array of five in-built light sensors (see figure below). They are of type QTR-8RC
reflectance sensors. Each sensor is a pair of an IR emitter and an IR receiver (phototransistor). These are
active opto-sensors. The emitter shines light and the phototransistor measures the reflected light. The
value read by the sensor is inversely proportional with the amount of reflected IR light. So high
measured values around 2000 mean black , and low values around 50 mean white. In this way, we can
use the active sensors to measure the colour of the floor.
14
Write the following program and flash it to the Arduino board.
void loop() {
unsigned int sensors[5];
char buffer[50];
calibratedSensors(sensors);
sprintf(buffer, "Active Optosensors: %d; %d; %d; %d; %d\n", sensors[0],
sensors[1], sensors[2],
sensors[3], sensors[4]);
Serial.println(buffer);
delay(500);
First, the opto-sensors need to be calibrated. This is done by placing the robot on a black line, and
calling the function sensorAutoCalibrate(). The robot will start rotating left and right to measure the
whole spectrum of colors from white to black. When the robot finishes the calibration, the function
calibratedSensors() can be called, which will read all the five sensors in one vector.
Be careful not to touch with your fingers the active opto-sensors. If they get greasy you will only
get incorrect or no data. If it happens, your TA can clean the sensors using some alcohol and a
piece of paper.
Off we go! Turn on the robot. Make sure that the lights on the bottom are turned on, indicating that the
robot is really turned on. If the lights are off, try to press again the ON/OFF button.
Place the robot on the black light as shown above and run the program. Wait until the robot will run its
calibration procedure. Next, you will see on the screen of the emulator rows with 5 values, returned by
15
the active light sensors. Remember that if the sensor sees black, then the returned value is higher. A
very low value means white. Try to measure different colours and write down the sensor values
corresponding to each colour.
We equipped Neville with a passive resistive opto-sensor that measures the ambient light. You have to
build a simple circuit using the breadboard and some wires to connect the sensor to the Arduino like in
the figure below.
16
Caution! Because the Arduino board is a fragile and rather expensive device,
which can be easily damaged by wrong electronic connections, ask the TA to
check your wiring before powering the device.
The opto-sensor generates between its pins a voltage proportional with the amount of ambient
light measured. The voltage can be measured using the analog input feature of the Arduino
board. Eight of the Arduino’s pins can be set to analog mode, this is done by calling them with
identifiers A0, …, A7. For example you can use pin A4. Fig.3. you can see that analog pin A4
can be accessed with a wire inserted in access hole D18.
pinMode(A4, INPUT);
void loop() {
17
float output = analogRead(A4);
int lightpercent = (1023-output)/1023 * 100;
Serial.print("Light level: ");
Serial.print(lightpercent);
Serial.println("%");
}
If you open the serial terminal and run this program, you will see on the screen continuously the
changing values of the ambient light. Cover with your hand the sensor and see how the measured values
change. Shine a light over the sensor and observe what happens with the measured values. Write down
these values because you will need them for your exercises.
We extended the original Pololu robot with a Parallax PING))) Ultrasonic Distance Sensor, shown in the
figure below. This sensor measures the distance to close objects and returns a digital value equal to the
distance to the object measured in cm.
The sensor has three terminals, one connected to the GND, one to 5V and one carries the signal
measuring the distance. Internally, all these connections are already made for you. You do not have to
connect anything.
In order to read the values measured by this sensor, you already installed a third party library called
Ultrasonic. The library can now be used in your programs by adding the following include statement at
the top of your program.
#include <Ultrasonic.h>
#include <Ultrasonic.h>
18
void loop() {
Ultrasonic ultrasonic(10);
int distance = ultrasonic.read();
Serial.print("Distance in CM: ");
Serial.println(distance);
delay(500);
}
The ultrasonic.read() function activates the ultrasound sensor, waits for a short time and then computes
the distance to an obstacle in centimeters based on the time it took for the signal to return.
Important! The argument 10 in ultrasonic(10) refers to the digital pin D10. This pin is internally
hardwired to the ultrasound connector cable. This means you cannot use pin D10 for other purposes.
----------------------------End of tutorial-----------------------------------------------
When done with the Neville, remove any breadboard connector wires
and put everything back the way you found it! Do not disconnect the
power bank cable from the Pololu!
Write a C++ program that continuously measures the ambient light, using Neville’s passive opto-sensor
and when Neville enters a tunnel, at least one LED turns on. When Neville gets out of the tunnel, the
LED should be turned off.
Write a C++ program that makes the robot drive forwards, and then stop in front of the first
encountered (fixed) obstacle.
Write a C++ program that makes the robot follow a curved black line, and then stop at a T-junction. Use
the 90 cm x 120 cm boards with a black track, provided for this purpose (see figure below). Start with a
simple algorithm. Use for example only two sensors, nr.1. and nr.5. Start first on a segment without
sharp corners and curves. Increase the complexity gradually until you can follow the whole line. Explain
how your algorithm works and evaluate it. Is it smooth or does the robot shake while driving, can it take
sharp curves, is it fast, is it slow, just good?
19
BONUS #1. Put things together [1p]
For this exercise you must combine everything you have learned and write a C++ program that makes
the robot follow the racing track from the start T-junction until the finish T-junction. If obstacles are on
the way, the robot should pause. When the obstacle is out of the way, the robot should resume its
driving. In the tunnel, the lights should switch on. On the green lane the robot is allowed to drive with a
higher speed.
Try to optimize your program for following the whole race circuit and take part in the race. Let the TA
time your race. Upload a video in Canvas. Explain in the report your key to success.
20
Troubleshooting
General
It may happen that your program does compile, but the uploading process times out. In this case, the
Arduino board could be in a state where it does not accept new programs being flashed to it. To make it
accept new programs again, you need to bring it to a state that always accepts new programs. We can
do this by running the bootloader. To run the bootloader, simply press the reset button on the Arduino
board twice in quick succession when the board is powered.
Linux
then your user account does not have the right permissions to access the Arduino board. You can give
yourself these permissions by entering the following command:
After running this command from the terminal, reboot your machine. You should have permission to
flash to the Arduino board now. If this does not work, you can also temporarily give yourself permission
to access the Arduino board with the following command:
The path to the Arduino board can be seen in the Arduino IDE by hovering over Arduino NANO 33 IoT in
the top left. It is typically /dev/ttyACM0 or /dev/ttyACM1. You may have to enter this command every
time you flash or reconnect the Arduino board.
21