C++ Robot Lab 1 – An Introduction to 3DoT & C++

Introduction

This lab training sequence is designed to introduce you to the 3DoT Board. the Arduino Integrated Development Environment (IDE), C++ programming, and the methodology used to navigate the maze. Plus, you will learn about the power of library files. Library files are simply files that you instruct the Arduino IDE to include in your program. In this lab, you are going to create a library file called 3DoTConfig.h that handles all of the initialization and configuration for your robot.

What Is New

New terminology and concepts are listed below under “Bit/Byte Operations” and “Pin Configuration & Control”. If you have any questions on this information, refer back to the lectures on the introduction to C++ and configuring the GPIO registers.

C++ Essential Training (Required)

This Linda tutorial provides a comprehensive review of the basics of C++.

Getting Started in Embedded Systems (Required) 

 This Linda tutorial offers a nice introduction to C++ and embedded systems.

Please download certificates when you have completed the tutorials and submit them on Beachboard.

Data Types

uint8_t  variable       // Define variable data type as an unsigned 8 bit integer (0-255)
uint16_t variable       // Define variable data type as an unsigned 16 bit integer (0-65,535)
int8_t   variable       // Define variable data type as a signed 8 bit integer (-128 to 127)
int16_t  variable       // Define variable data type as a signed 16 bit integer (-32,768 to 32,767)
const uint8_t variable  // Qualify variable as a constant
static uint8_t variable // Qualify variable that will persist after a function return

C++ Code

Preprocessor Directives

#define Name Text        // Replace every occurrence of Name with Text.
#include   // Include avr/io.h library located in the Arduino program folder 
#include “filename”      // Include library located in the sketch (i.e., program) folder

Bit / Byte Operations

variable  = byteValue << numberOfShifts;  // Left shift byte value by specified number of bits.
variable  = _BV (bitNumber);              // Create a byte with specified bit set to 1.
variable |= _BV(bitNumber);               // Set specified bit within a byte variable. 
variable &= ~_BV(bitNumber);              // Clear specified bit within a byte variable
variable = !another_variable              // C++ Boolean NOT operator

Arduino Built-In Functions

Pin Configuration & Control

pinMode(pin_number, TYPE);           // Define digital pin and set type as INPUT, OUTPUT, or INPUT_PULLUP
digitalRead(pin_number);             // Read digital value from specified digital INPUT or INPUT_PULLUP pin.
digitalWrite(pin_number, value);     // Write digital value to specified digital OUTPUT pin.

3DoT Overview & Mission

The end goal of these labs is to program a robot utilizing the 3DoT board and be able to navigate a maze autonomously. Shown below are the major features of the 3DoT board and the block diagram of the latest version.

3DoT is a micro-footprint 3.5 x 7 cm all-in-one Arduino compatible microcontroller board designed for robot projects.

Figure 1. 3DoT Board

  • MicrocontrollerATmega32U4
  • Bluetooth: FCC-certified BLE 5.0 module
  • Power Management:
    • RCR123A battery holder
    • Included 600 mAh rechargeable battery
    • Microchip MCP7383 battery charge controller
    • External battery connector – for input voltages between 4 – 18 V
    • Reverse polarity protection – plug in the battery backward? No problem
  • Motors & Servos:
    • 2x JST motor connectors
    • 2x standard servo connectors
  • Expansion:
    • 16-pin top female headers for shields – providing I/O, I²C, SPI, USART, 3.3 V, and 5 V.
    • The forward-facing 8-pin female header for sensor shields – providing 4 analog pins, I²C, and 3.3 V power – for sensor shields like infrared or metal-detecting shields. Great location for headlights, lasers, ultrasonics, etc.
  • Programming switch: Three-position switch for easy programming
    • No more double-tapping a button and rushing to program your board, or your robot trying to drive away while programming. Set the switch to PRG to program, RUN to execute your code.

It would be ideal to spend some time analyzing the block diagram to get a better understanding of the capabilities of the 3DoT board but we will focus on the connections between the motor driver and IR sensors for Lab 1. Figure 3 provides an isolated view of those connections.

3DoT Block Diagram v10 3DoT v10 Detail Block Diagram 201215

3DoT v10 Dtailed

Figure 2. 3DoT v10 Detailed Block Diagram

.

Figure 3. IR to Motor Driver

Errata: AIN1, AIN2/PWMA,BIN1,BIN2/PWMB,NSLEEP wired to 5V_VM

The main objective of Lab 1 is to develop the path following logic for your robot to move through the maze shown in Figure 4 “The Maze.” We will handle what to do at intersections in later labs. The path following algorithm reads data from two of the four IR sensors and decides how the control signals being sent to the motor driver need to change based on that information. The outside IR sensors provide the most relevant data about your robot’s position as it follows the path and is what we will be focused on. You can read the two inside sensors to verify the robot has not left the path. To start developing this code, you will need to configure the input and output pins.

Figure 4. The Maze

Introduction to the Arduino IDE

In the lab, you will be spending most of your time working within an Integrated Development Environment (IDE). For our labs, we will be working in the Arduino IDE. The IDE lets us write our program in a human-readable form (C++) and then translate it into a machine-readable form understood by the ATmega32U4. There are many other types of IDEs for C++ programmings such as Eclipse and Atmel Studio 7 that allow us to create very complex projects. The focus will be on the Arduino IDE because of how easy it is to learn and use.

 Before continuing complete the ARDUINO IDE 3DOT TUTORIAL, up to and including and running the BLINK test script.

Creating A New Sketch In Arduino IDE

To start, create a new sketch in the Arduino IDE. When the Arduino IDE launches, it should open a blank sketch. If you do not see a blank sketch, click File > New as shown in the figure below.

Figure 5. Create a New Sketch

The first thing you will be adding is a title block to describe the purpose of the sketch and the author. This will go before the setup function and should look something like this.

Figure 6. Title Block

Next, you will need to rename the sketch to a more descriptive name. Click on File -> Save and type in the name you want to save it as such as Lab01. The application will create a folder for your sketch in the location you have designated for all sketches (Default is in the Arduino folder located in My Documents). Now let’s take a look at the hardware before developing the code to control it.

Making the 3DoTConfig.h Library File

Libraries allow us to place useful definitions and initialization code outside our main program. Once created and included, the user can still call these functions and reference these definitions in their main .ino file. Libraries make the main program much cleaner and simpler to understand.

A C++ library file typically includes a header file (.h) that provides the prototypes of the functions in the library and a .cpp file that has all of the C++ code. For Lab 1 the library header file will include the actual C++ code and be used to configure the robot. First, a new header “.h” file will need to be created. Click on the icon and select “New Tab” as shown in Figure 7 below. Name the file 3DoTConfig.h

Figure 7. Make a New Tab

Simply copying and pasting the following HTML text may insert hidden characters into your code, resulting in multiple compiler errors. The author recommends manually typing in the following material.

Place the following include directives at the beginning of your sketch. The quotes (“) around “3DoTConfig.h” instructs the application to look in the folder containing the program (your .ino file).

#include "3DoTConfig.h"

void setup() {...

Using #define to Create Descriptive Names

The #define preprocessor directives (or macro) instructs the compiler to replace an identifier with its associated text string.

Unlike C++ instructions, preprocessor directives do not end with a semicolon (;).

For this series of lab, the #define preprocessor directive, associates an Arduino pin name with its 3DoT equivalent as defined in Figure 3 “Reflective IR Sensor to Motor Driver.” For example, this #define macro will replace every occurrence of the text string IR_R_O (i.e., the outside IR sensor trace) with the text string  A0 (i.e., it Arduino pin name).

#define IR_R_O A0    // IR_R_O = analog pin A0

Place the #define macro preprocessor definitions for IR sensor traces IR_R_O, IR_R_I, IR_L_I, and IR_L_O in the newly created 3DoTConfig.h file.

The same can be done for the control signals to the motor driver so that each motor and its associated pins can be clearly distinguished from the other. Add #define macro preprocessor definitions for AIN1, AIN2, NSLEEP, BIN1, and BIN2.

In addition, define synonyms PWMA and PWMB.

// Synonyms 
#define PWMA AIN2
#define PWMB BIN2

To provide visual feedback for future tests, we will be using the built-in LED wired to ATmega32U4 Port D pin 5. Physically, the LED is located between the switch and the micro USB port (Figure 1). See if you can find the built-in LED on the schematic in Figure 2 “3DoT Detailed Block Diagram.” Add a #define preprocessor directives for the built-in LED.

#define LED_BUILTIN PD5     // Built-in led is wired to ATmega Port D pin 5

Unlike the previous definitions which used the Arduino naming convention. The LED_BUILTIN definition uses the ATmega32U4 naming convention.

An alternative approach

For digital pins, this can also be done by creating a variable that is set to the pin number. Since these values will never change, a const qualifier is added. This new form has the advantage that it allows the compiler to check scope and datatype. It has the disadvantage that it uses SRAM to save these constants. Our first #define example could therefore be replaced with the variable definition

const uint8_t IR_R_O = A0;

In this instance, the author recommends the #define directive over a constant variable based on the savings in RAM.

Quick Check

The 3DoTConfig.h file should contain twelve (12) #define preprocessor directives.

Configuring the Input and Output Pins

The first thing that needs to be done is to define what pins are going to be used for the Atmega32U4. Just because components have been hardwired to specific pins does not mean the microcontroller knows what they are being used for. You will need to configure the input and output pins. In the next section, the Arduino’s built-in functions are used to configure the input and output pins. In the design challenge, you will apply what you learned in the lecture to directly modify the GPIO registers (DDRX and PORTX) in C++.

Add the following function definition to the 3DoTConfig.h file after the last # defines statement.

void init3DoT() {
  // add code here
};

Place the following line of code in the setup() function.

void setup() {
  init3DoT();
}

In a previous section, pins were assigned to the IR shield and motor driver. Once the Arduino preprocessor knows the name of a pin, the program can configure the pin as an input or output, using the pinMode() function. Once a pin has been configured, the program can read or write to the pin using the Arduino digitalRead() and digitalWrite() functions.

Initialize the Motor Driver

Start the initialization of the ATmega32U4 by defining Motor Drive pin AIN1 as an output and initializing it to zero (OFF).

void init3DoT() {
  pinMode(AIN1,OUTPUT);    // Digital pin AIN1 is an output
  digitalWrite(AIN1,LOW);  // Left Motor OFF
}

Following the above example, configure motor driver pins AIN2, NSLEEP, BIN1, BIN2 as outputs and initialize to zero.

Initialize the IR Sensors

Next, initialize the right outside IR sensor as a digital input.

void init3DoT() {
  pinMode(AIN1,OUTPUT);    // Digital pin AIN1 is an output
  digitalWrite(AIN1,LOW);  // Left Motor OFF
         :
  pinMode(IR_R_O,INPUT);   // Analog pin A0  
}

Following the above example, configure IR pins IR_R_I, IR_L_I, IR_L_O as inputs.

Initialize the Built-in LED

Finally, initialize the Built-in LED pin as an output and initialize to zero. The Arduino reserved words LOW and HIGH are of datatype uint8_t and have values 0x00 and 0x01 respectively. Also note that like LOW and HIGH, LED_BUILTIN is colored cyan.

  pinMode(LED_BUILTIN,OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);  // Turn the built-in led OFF

Reading and Interpreting the IR sensor Data

The IR sensors provide feedback as the robot follows a line. In this section, you will learn how to read the IR pins as digital inputs.

As a digital input, the input value read from the IR sensor when it is over a black line is HIGH (a value of 1), otherwise, it is LOW.

white 0
black 1

This is determined by the microprocessor’s own threshold for digital logic and cannot be modified. While there is no customization, it is very useful for situations that have only two states and is easy to implement. Within the Arduino IDE, this is done using the digitalRead()function.

IR Sensor Test

Before continuing, let’s do a quick check to see if the IR sensors are working. This test will also use the BLINK led to provide visual feedback as you debug your code. Place the following test code in the loop() section of the program.

void loop(){
  int val = digitalRead(IR_R_O);   // read IR sensor
  digitalWrite(LED_BUILTIN, val);  // display on blink led
}

To run the test you will need to upload the code to the robot. This is a two-step process after the hardware has been prepared. The first thing that needs to be done is to verify the code by clicking on the checkmark on the top left corner (the verify button). This will compile and check the code to see for any syntax errors. Any error messages will appear in orange at the bottom of the window and will need to be fixed before the program can be uploaded to the board. Once verified, upload the compiled code to the board using the right arrow button (upload button) that is next to the verify button. If everything went correctly, the led should turn OFF when the right outside IR sensor is over a white or reflective surface and ON otherwise. Tip: Placing and then slowly lifting your finger off the IR sensor will also turn it on and off.

Test the operation of the other IR sensors by replacing IR_R_I with the names of the other IR sensors (i.e, IR_R_O, IR_L_I, and IR_L_O), uploading, and rerunning the test.

Debugging Tips

If the output is always 1, this may be due to the sensor’s height off the path or the IR LED not being ON. First, try placing your sensor closer to the line. If this does not work, verify that the IR LED is ON by looking at the sensor with your cell phone camera. Some cell phone cameras have an IR filter that can block the light. Consequently, if your camera initially does not see the LED try another phone. If you still can not see the IR LED verify that the shield is powered (3.3v).

If the output is always 0. Stray light is probably saturating the photo-transistor. This problem may be solved by simply adding the paperBot shell or taping paper around the boot to block the light from entering the sensor.

If none of the above work, remove the IR shield and using a resistor and/or jumper, wire the analog pin to 3.3V and GND, and verify built-in LED turns ON and then OFF.

Controlling the Motors

Wiring the Motors

Now let’s teach the robot how to move forward. Verify that the robot’s motors are wired correctly as shown in the PaperBot Chassis Build Instructions video and Figure 8 “Wiring the Motors.”

Figure 8. Wiring the Motors

Motor Control Settings

Figure 9 “3DoT Pinout and Motor Driver Truth Table” is a great resource that you will be using over the course of the semester.

3DoT

Figure 9 3DoT Pinout and Motor Driver Truth Table

Use the following truth table when programming your robots.

Table 3. Motor Control Settings
Action Input
Left Motor Right Motor
AIN1 AIN2 BIN1 BIN2
Motors OFF 0 0 0 0
Forward 0 1 0 1
Spin Right 0 1 1 0
Spin Left 1 0 0 1
Reverse 1 0 1 0

Enable the Motors

In the “Configuring The Input and Outputs Pins” section you defined the ATmega32U4 pins controlling the motors as outputs using the Arduino pinMode() function. Whenever you define an output pin you must initialize it to some value. For the motors, this was done by setting all outputs to LOW  using the Arduino digitalWrite() function or by modifying the PORTD and PORTB registers (design challenge).  In other words, the robot was placed in a safe state (no running away). In this section, you teach the robot to move forward by editing the value parameters of the digitalWrite(pin, value) function calls.

Moving Forward

Add an initialization section to loop.

void loop() {
/* Initialization */

}

Add two (2) Arduino digitalWrite() functions to the Initialization section of loop(). Reading from Table 3 configure the robot to move forward (AIN1, BIN1). Now let’s do a quick check to see if our motors work. Add the following test code to the loop() section of your program.

void loop(){
  /* Initialization */
       :
  /* Test code */  
  digitalWrite(PWMA, HIGH);
  digitalWrite(PWMB, HIGH);
}

Verify that both PWM pins have been configured as outputs within the /*Initialization*/ block.

pinMode(PWMA,OUTPUT);   // PWMA is mapped to AIN2
pinMode(PWMB,OUTPUT);   // PWMB is mapped to BIN2  

Upload the code to your robot. If everything went correctly, your robot should start to run away.

Quick Check

At this point in the lab the init3DoT() function should be complete; containing ten (10) pinMode and six (6) digitalWrite instructions. Specifically, all ATmega32U4 IR sensor inputs and motor outputs should be configured (Figure 3).

The initialization section of the loop() function should contain four (2) digitalWrite instructions.

Path Following Algorithm

Now that you have the robot moving forward, let’s incorporate the IR sensors to provide feedback as your robot tries to follow a path. Replace your test code in loop() with the following code after your digitalWrite() function calls in the initialization section.

void loop() {
  /* Read the IR sensors */
  int sensorLeft = digitalRead(IR_L_O);  // White = 0, LOW
  int sensorRight = digitalRead(IR_R_O); // Black = 1, HIGH
  /* Run the path following algorithm */
  int motorLeft  = !sensorRight;          // we invert and cross the wires
  int motorRight = !sensorLeft;
  /* Write to the motors */ 
  digitalWrite(PWMA, motorLeft);
  digitalWrite(PWMB, motorRight);
}

How it Works

The first block reads the two outer IR sensors as digital inputs. In fact, the output from these sensors is an analog signal. As digital signals, anything below 0.56 V  will be interpreted as logic 0 (LOW) and above 1.56 V as logic 1 (HIGH). These are in compliance with the LVTTL JEDEC standard and defined in Section 29.2 DC Characteristics of the Atmel-7766J-USB-ATmega16U4/32U4-Datasheet_04/2016 Document.   Clearly, these may not be the optimal threshold settings for these IR sensors. In addition,  when an analog signal drifts between these two threshold values, the input transistors can inject noise into the chip, which can result in excessive current consumption, heating, and ultimately failure of the device. So why did we do this? The short answer is to keep this early lab as simple as possible.  A more technically correct answer is that the ATmega32U4 includes input circuitry to protect against drifting signals from damaging the device.  This potential problem will be corrected by reading each IR signal as an analog input in Lab 3.

The second block of code runs the line following algorithm. To help understand how this works, visualize the robot traveling down the path. When the robot starts to veer off the path towards the left, it means that the right motor is rotating faster than the left motor. This may be due to differences in friction within the gearbox and/or the DC motor.  At some point, the left IR sensor will detect the black line on the left side of the path, and sensorLeft = 1.  In order to adjust and get the robot back on the path, the algorithm will stop the right motor (motorRight = 0) until the left outside IR sensor is back on the white surface of the maze. In summary, when input sensorLeft = 1, we want the output motorRight = 0 and visa-versa, something easily accomplished using the C++ Boolean NOT operator (!) and swapping the inputs. The same logic applies when it veers to the right. While this simple algorithm is good enough for following a path and stopping at an intersection, it is not intelligent enough to teach the robot how to actually take a step (takeAStep) or turn in the maze. This problem will be addressed by writing a more advanced algorithm in Lab 4.

Like the IR sensor, using binary signals to control the motors (ON/OFF) is sub-optimal. So why did we do this? The short answer is again to keep this early lab as simple as possible. This problem will be corrected by writing a Pulse Width Modulated (PWM) signal to the motors, approximating an analog output in Lab 2.

Design Challenge

You can skip the remainder of this lab if you are happy receiving a passing or even a good grade on the lab. If you want to receive an excellent grade you will need to accept the challenge(s). Specifically, the maximum grade you can receive on the prelab 1 plus lab 1 if you do not accept the challenge(s) is 20 points out of 25 (80%).

Working with the GPIO Registers

The Arduino IDE is designed for non-engineers and therefore, as much as possible, it hides what is going on within the MCU. As engineers, we would  like to understand how the MCU really works and for this lab how the registers within the General Purpose Input/Output (GPIO) peripheral subsystems can be programmed to work with digital pins.

In order to configure a pin as an input, the corresponding bit position in the DDRX register needs to be cleared to 0. If that input pin needs to use a pull-up resistor, the corresponding bit position in the PORTX register needs to be set to 1. Obtaining the value that is being detected on that input is done by reading the corresponding bit position in the PINX register. For an output pin, the value in the DDRX register needs to be set to 1. You have to write the value that is to be outputted in the PORTX register. This information is summarized in Table 4.

Table 4. GPIO Pin Configuration

From figure 5 you will notice that our input pins for the IR sensors are PF7, PF6, PF5, and PF4. The output pins that go to the DRV8848 motor driver are PB7, PD7, PC7, and PB6. It is possible to define each pin individually but it would be more efficient to develop an expression to get the final value that needs to be written to each 8-bit register. It is also important to preserve the values of the other bits in the GPIO register because that would change the functionality of those pins. This can be done with a combination of the bit/byte operations that were introduced earlier.

As an example, let’s set Port B bits 6, 5, and 4 to logic 1 without modifying the other bits in PORTB. If you were to configure them individually, you might start with the following instruction.

The right-hand side of the expression can be read as; take 0b00000001 and shift it to the left 4 times, where PB4 is defined as equal to ‘4’. The resulting binary value is 0b0001000. You can say the same thing by using the bit value macro _BV(bit), included in the avr/io library.  A bit-wise OR (=|) is then performed with DDRB and this binary value. Recalling that 1 OR anything is 1 and 0 OR anything is anything, this expression will set only bit position 4, defining that pin as an output.

That right-hand side of the equation can set a single bit, or again by application of the bit-wise OR operation, multiple bits. This means we can set DDRB bits 6, 5, and 4 in a single line of code.

For clearing values, the following instruction would be used. It is performing the bit-wise AND operation on the original value of DDRD and the complement of the expression.

The Challenge

Turn the Arduino functions you wrote in 3DoTConfig.h into Comments (//), and replace them with their C++ equivalent. One advantage of working directly with GPIO port registers (DDRX and PORTX) is that one line of C++ will replace multiple Arduino lines of code, which work with one pin at a time.

Lab 1 Deliverable(s)

All labs should represent your own work – DO NOT COPY.

Submit all of the files in your sketch folder. Make sure that the code compiles without any errors. Do not forget to comment your code.

Lab 1 Demonstration

Be prepared to show your robot following a path on the maze.

Checklist

Remember before you turn in your lab…

  1. The configuration of the robot is written in the 3DoTConfig.h file.
  2. All of the configuration is done in the init3DoT() function.
  3. The init3DoT() function should be called in the setup() function.
  4. The robot should move forward and use the IR sensors to follow a line