Naive Bayes Algorithm From Scratch | Machine Learning

In this post, I will walk you through the Naive Bayes machine learning algorithm, step-by-step. We will develop the code for the algorithm from scratch using Python. We will then run the algorithm a real-world data sets from the UCI Machine Learning Repository. On one of the data sets, we will predict if a patient has breast cancer or not based on ten different attributes. Let’s get started!

Table of Contents

What is Naive Bayes?

The Naive Bayes algorithm is a technique based on Bayes Theorem for calculating the probability of a hypothesis (H) given some pieces of evidence (E).

For example, suppose we are trying to identify if a person is sick or not. Our hypothesis is that the person is sick.

nurse_checks_blood_pressure_1

We would naturally take a look at the evidence (eye color, body temperature, blood pressure, etc.) to determine if the person is sick or not. Each piece of evidence provides us clues. From that evidence, we can then use the Naive Bayes algorithm to calculate two probabilities:

  • Probability 1: The probability that the person is sick given she has red eyes, a body temperature of 99°F, and has normal blood pressure.
  • Probability 2: The probability that the person is not sick given she has red eyes, a body temperature of 99°F, and has normal blood pressure.

We then classify the person as being sick or not based on which probability (Probability 1 vs. Probability 2) is the highest.

Mathematically, Bayes theorem can be expressed as follows:

naive-bayes-1

Or in expanded form, we have:

naive-bayes-2

Or…

naive-bayes-3

Where:

  • P = probability
  • | = given
  • E = evidence (e.g. red eyes, body temperature, etc.)
  • H = hypothesis (e.g. sick)
  • ¬ = not
  • P(H|E1, E2,E3,…,EN) = posterior probability: the probability of a hypothesis after taking the evidence into account (e.g. probability of being sick given all this evidence)
  • P(E1, E2,E3,…,EN|H)= likelihood: the probability of the evidence given the hypothesis (e.g. probability of having red eyes given that a person is sick)
  • P(H) = class prior probability: the known probability of the hypothesis (e.g. probability of being sick for the population or entire sample of instances)

The equation above says: “The probability of the hypothesis (e.g. a person is sick) given the evidence (e.g. eye color, body temperature, blood pressure) is equal to the probability of the evidence given the hypothesis times the probability of the hypothesis divided by the probability of the evidence.”

The key assumption with the Bayes theorem is that all of the attributes are conditionally independent. In other words, the occurrence of one piece of evidence gives no information about the probability of another piece of evidence occurring.

For example, Bayes theorem would assume that the probability of having red eyes gives no information about the probability of having a high body temperature. We know this is not often the case. Such an assumption is naive, and that is why we call this classification algorithm the Naive Bayes algorithm.

We can therefore rewrite the equation based on the probability rule of conditional independence, which is:

naive-bayes-4

Bayes equation can be rewritten as:

naive-bayes-5

Return to Table of Contents

Algorithm Steps

Training Phase

Recall in Naive Bayes, for a 2-class classification problem (e.g. sick or not sick), we need to calculate two probabilities for each instance. The highest probability is our prediction.:

Probability 1 (sick): The probability that the person is sick given she has red eyes, a body temperature of 99°F, and has normal blood pressure.

naive-bayes-6-1

Probability 2 (not sick): The probability that the person is not sick given she has red eyes, a body temperature of 99°F, and has a normal blood pressure.

naive-bayes-7

If Probability 1 > Probability 2, she is sick. Otherwise, she is not sick

Notice, the denominators above are both equal. Because they are both equal, we can ignore them for training our model since all we need to do is to compare the numerators.

  • Probability 1 (sick): The probability that the person is sick given she has red eyes, a body temperature of 99°F, and has a normal blood pressure.
naive-bayes-8
  • Probability 2 (not sick): The probability that the person is not sick given she has red eyes, a body temperature of 99°F, and has a normal blood pressure.
naive-bayes-9

This makes our lives easier, since now all Naive Bayes algorithm needs to do to train on a data set is to calculate those values in blue above in order to make a classification prediction (sick or not sick). That is, we need to calculate two class prior probabilities (sick or not sick for the whole sample or population) plus the conditional probability of each unique value in each attribute for each class:

Number of Probabilities Calculated during Training Phase of Naive Bayes = 2 class prior probabilities + 2 * (# of unique values for E1) + 2 * (# of unique values for E2) + … 2 * (# of unique values for EN)

If all pieces of evidence were binary (e.g. red eyes, no red eyes) and the class is binary (sick or not sick), we would need to calculate four probabilities for each attribute. The total number of probabilities calculated in the training phase is therefore (where N is the number of attributes):

Number of Probabilities Calculated during Training Phase of Naive Bayes = 2 + 4N

For example, let’s see all the probabilities that would need to be calculated for binary attribute E1 (e.g. red eyes or no red eyes):

naive-bayes-10

We have our two class prior probabilities. These will be the same for all attributes:

  1. P(1) = (Total number of people that are sick in the training data set) / (Total number of people in the training data set)
  2. P(0) =  (Total number of people that are not sick in the training data set) / (Total number of people in the training data set)

And since (N = number, S = total number of instances in the training data set)…

naive-bayes-11

To complete the table for attribute E1, we calculate four different probabilities:

naive-bayes-12

We have to store these probabilities somewhere so they can be looked up during the testing phase. In my program, I stored them in a Python dictionary, with the following search key: <attribute_name><attribute_value><class_value>.

For example, the search key redeyes01, would return the probability of not having red eyes given someone is sick:

naive-bayes-13

That’s it. Once we have the tables for each attribute along with the class prior probabilities, the algorithm can go to work and make predictions for new instances.

Return to Table of Contents

Testing Phase

Having calculated the required probabilities and stored them somewhere, the algorithm is ready to make its predictions for new instances. As mentioned in the previous section, for each instance (i.e. row of the testing data set), two calculations need to be made and then the results are compared.

1. Probability 1 (sick): The probability that the person is sick given she has red eyes, a body temperature of 99°F, and has normal blood pressure.

naive-bayes-14

2.Probability 2 (not sick): The probability that the person is not sick given she has red eyes, a body temperature of 99°F, and has a normal blood pressure.

naive-bayes-15

3. If Probability 1 > Probability 2, she is sick. Otherwise, she is not sick

4. Proceed to the next instance and repeat 1-3.

Return to Table of Contents

Naive Bayes Implementation

The Naive Bayes algorithm was implemented from scratch. The Breast Cancer, Glass, Iris, Soybean (small), and Vote data sets were preprocessed to meet the input requirements of the algorithms. I used five-fold stratified cross-validation to evaluate the performance of the models.

Required Data Set Format for Naïve Bayes

Columns (0 through N)

  • 0: Instance ID
  • 1: Attribute 1
  • 2: Attribute 2
  • 3: Attribute 3
  • N: Actual Class

The program then adds two additional columns for the testing set.

  • N + 1: Predicted Class
  • N + 2: Prediction Correct? (1 if yes, 0 if no)

Breast Cancer Data Set

This breast cancer data set contains 699 instances, 10 attributes, and a class – malignant or benign (Wolberg, 1992).

Modification of Attribute Values

The actual class value was changed to “Benign” or “Malignant.”

I transformed the attributes into binary numbers so that the algorithms could process the data properly and efficiently. If attribute value was greater than 5, the value was changed to 1, otherwise it was 0.

Missing Data

There were 16 missing attribute values, each denoted with a “?”. I chose a random number between 1 and 10 (inclusive) to fill in the data.

Glass Data Set

This glass data set contains 214 instances, 10 attributes, and 7 classes (German, 1987). The purpose of the data set is to identify the type of glass.

Modification of Attribute Values

If attribute values were greater than the median of the attribute, value was changed to 1, otherwise it was set to 0.

Missing Data

There are no missing values in this data set.

Iris Data Set

This data set contains 3 classes of 50 instances each (150 instances in total), where each class refers to a different type of iris plant (Fisher, 1988).

Modification of Attribute Values

If attribute values were greater than the median of the attribute, value was changed to 1, otherwise it was set to 0.

Missing Data

There were no missing attribute values.

Soybean Data Set (small)

This soybean (small) data set contains 47 instances, 35 attributes, and 4 classes (Michalski, 1980). The purpose of the data set is to determine the disease type.

Modification of Attribute Values

If attribute values were greater than the median of the attribute, value was changed to 1, otherwise it was set to 0.

Missing Data

There are no missing values in this data set.

Vote Data Set

This data set includes votes for each of the U.S. House of Representatives Congressmen (435 instances) on the 16 key votes identified by the Congressional Quarterly Almanac (Schlimmer, 1987). The purpose of the data set is to identify the representative as either a Democrat or Republican.

  • 267 Democrats
  • 168 Republicans

Modification of Attribute Values

I did the following modifications:

  • Changed all “y” to 1 and all “n” to 0.

Missing Data

Missing values were denoted as “?”. To fill in those missing values, I chose random number, either 0 (“No”) or 1 (“Yes”).

Return to Table of Contents

Naive Bayes Algorithm in Python, Coded From Scratch

Here are the input files for the code below:

Here is the driver code that contains the main method. I recommend copying and pasting it into a text editor like Notepad++ or an IDE so that you don’t have to do any horizontal scrolling to see the entire code. The code is long (don’t be scared!), but that is because it includes a ton of notes so that you know what is going on:

import pandas as pd # Import Pandas library 
import numpy as np # Import Numpy library
import five_fold_stratified_cv
import naive_bayes

# File name: naive_bayes_driver.py
# Author: Addison Sears-Collins
# Date created: 7/17/2019
# Python version: 3.7
# Description: Driver for the naive_bayes.py program 
# (Naive Bayes)

# Required Data Set Format for Disrete Class Values
# Columns (0 through N)
# 0: Instance ID
# 1: Attribute 1 
# 2: Attribute 2
# 3: Attribute 3 
# ...
# N: Actual Class

# The naive_bayes.py program then adds 2 additional columns for the test set.
# N + 1: Predicted Class
# N + 2: Prediction Correct? (1 if yes, 0 if no)

ALGORITHM_NAME = "Naive Bayes"
SEPARATOR = ","  # Separator for the data set (e.g. "\t" for tab data)

def main():

    print("Welcome to the " +  ALGORITHM_NAME + " Program!")
    print()

    # Directory where data set is located
    data_path = input("Enter the path to your input file: ") 
    #data_path = "breast_cancer.txt"

    # Read the full text file and store records in a Pandas dataframe
    pd_data_set = pd.read_csv(data_path, sep=SEPARATOR)

    # Show functioning of the program
    trace_runs_file = input("Enter the name of your trace runs file: ") 
    #trace_runs_file = "breast_cancer_naive_bayes_trace_runs.txt"

    # Open a new file to save trace runs
    outfile_tr = open(trace_runs_file,"w") 

    # Testing statistics
    test_stats_file = input("Enter the name of your test statistics file: ") 
    #test_stats_file = "breast_cancer_naive_bayes_test_stats.txt"

    # Open a test_stats_file 
    outfile_ts = open(test_stats_file,"w")

    # The number of folds in the cross-validation
    NO_OF_FOLDS = 5 

    # Generate the five stratified folds
    fold0, fold1, fold2, fold3, fold4 = five_fold_stratified_cv.get_five_folds(
        pd_data_set)

    training_dataset = None
    test_dataset = None

    # Create an empty array of length 5 to store the accuracy_statistics 
    # (classification accuracy)
    accuracy_statistics = np.zeros(NO_OF_FOLDS)

    # Run Naive Bayes the designated number of times as indicated by the 
    # number of folds
    for experiment in range(0, NO_OF_FOLDS):

        print()
        print("Running Experiment " + str(experiment + 1) + " ...")
        print()
        outfile_tr.write("Running Experiment " + str(experiment + 1) + " ...\n")
        outfile_tr.write("\n")

        # Each fold will have a chance to be the test data set
        if experiment == 0:
            test_dataset = fold0
            training_dataset = pd.concat([
               fold1, fold2, fold3, fold4], ignore_index=True, sort=False)                
        elif experiment == 1:
            test_dataset = fold1
            training_dataset = pd.concat([
               fold0, fold2, fold3, fold4], ignore_index=True, sort=False) 
        elif experiment == 2:
            test_dataset = fold2
            training_dataset = pd.concat([
               fold0, fold1, fold3, fold4], ignore_index=True, sort=False) 
        elif experiment == 3:
            test_dataset = fold3
            training_dataset = pd.concat([
               fold0, fold1, fold2, fold4], ignore_index=True, sort=False) 
        else:
            test_dataset = fold4
            training_dataset = pd.concat([
               fold0, fold1, fold2, fold3], ignore_index=True, sort=False) 
        
        # Run Naive Bayes
        accuracy, predictions, learned_model, no_of_instances_test = (
            naive_bayes.naive_bayes(training_dataset,test_dataset))

        # Replace 1 with Yes and 0 with No in the 'Prediction 
        # Correct?' column
        predictions['Prediction Correct?'] = predictions[
            'Prediction Correct?'].map({1: "Yes", 0: "No"})

        # Print the trace runs of each experiment
        print("Accuracy:")
        print(str(accuracy * 100) + "%")
        print()
        print("Classifications:")
        print(predictions)
        print()
        print("Learned Model (Likelihood Table):")
        print(learned_model)
        print()
        print("Number of Test Instances:")
        print(str(no_of_instances_test))
        print() 

        outfile_tr.write("Accuracy:")
        outfile_tr.write(str(accuracy * 100) + "%\n\n")
        outfile_tr.write("Classifications:\n")
        outfile_tr.write(str(predictions) + "\n\n")
        outfile_tr.write("Learned Model (Likelihood Table):\n")
        outfile_tr.write(str(learned_model) + "\n\n")
        outfile_tr.write("Number of Test Instances:")
        outfile_tr.write(str(no_of_instances_test) + "\n\n")

        # Store the accuracy in the accuracy_statistics array
        accuracy_statistics[experiment] = accuracy

    outfile_tr.write("Experiments Completed.\n")
    print("Experiments Completed.\n")

    # Write to a file
    outfile_ts.write("----------------------------------------------------------\n")
    outfile_ts.write(ALGORITHM_NAME + " Summary Statistics\n")
    outfile_ts.write("----------------------------------------------------------\n")
    outfile_ts.write("Data Set : " + data_path + "\n")
    outfile_ts.write("\n")
    outfile_ts.write("Accuracy Statistics for All 5 Experiments:")
    outfile_ts.write(np.array2string(
        accuracy_statistics, precision=2, separator=',',
        suppress_small=True))
    outfile_ts.write("\n")
    outfile_ts.write("\n")
    accuracy = np.mean(accuracy_statistics)
    accuracy *= 100
    outfile_ts.write("Classification Accuracy : " + str(accuracy) + "%\n")
   
    # Print to the console
    print()
    print("----------------------------------------------------------")
    print(ALGORITHM_NAME + " Summary Statistics")
    print("----------------------------------------------------------")
    print("Data Set : " + data_path)
    print()
    print()
    print("Accuracy Statistics for All 5 Experiments:")
    print(accuracy_statistics)
    print()
    print()
    print("Classification Accuracy : " + str(accuracy) + "%")
    print()

    # Close the files
    outfile_tr.close()
    outfile_ts.close()

main()

Here is the code for Naive Bayes:

import pandas as pd # Import Pandas library 
import numpy as np # Import Numpy library
 
# File name: naive_bayes.py
# Author: Addison Sears-Collins
# Date created: 7/17/2019
# Python version: 3.7
# Description: Implementation of Naive Bayes 
# This code works for multi-class 
# classification problems (e.g. democrat/republican/independent)
# Calculate P(E1|CL0)P(E2|CL0)P(E3|CL0)...P(E#|CL0) * P(CL0) and
# P(E1|CL1)P(E2|CL1)P(E3|CL1)...P(E#|CL1) * P(CL1) and
# P(E1|CL2)P(E2|CL2)P(E3|CL2)...P(E#|CL2) * P(CL2), etc. and 
# predict the class with the maximum result. 
# E is an attribute, and CL means class.
# Only need class prior probability and likelihoods to make a prediction
# (i.e. the numerator of Bayes formula) since denominators are 
# same for both the P(CL0|E1,E2,E3...)*P(CL0) and 
# P(CL1|E1,E2,E3...)*P(CL1), etc. cases where P means "probability of" 
# and | means "given".
 
# Required Data Set Format for Disrete Class Values
# Columns (0 through N)
# 0: Instance ID
# 1: Attribute 1 
# 2: Attribute 2
# 3: Attribute 3 
# ...
# N: Actual Class
 
# This program then adds 2 additional columns for the test set.
# N + 1: Predicted Class
# N + 2: Prediction Correct? (1 if yes, 0 if no)

def naive_bayes(training_set,test_set):
    """
    Parameters:
      training_set: The training instances as a Pandas dataframe
      test_set: The test instances as a Pandas dataframe
    Returns:
      accuracy: Classification accuracy as a decimal
      predictions: Classifications of all the test instances as a 
        Pandas dataframe
      learned_model: The likelihood table that is produced
        during the training phase
      no_of_instances_test: The number of test instances
    """   
 
    # Calculate the number of instances, columns, and attributes in the
    # training data set. Assumes 1 column for the instance ID and 1 column
    # for the class. Record the index of the column that contains 
    # the actual class
    no_of_instances_train = len(training_set.index) # number of rows
    no_of_columns_train = len(training_set.columns) # number of columns
    no_of_attributes = no_of_columns_train - 2
    actual_class_column = no_of_columns_train - 1
 
    # Store class values in a column, sort them, then create a list of unique
    # classes and store in a dataframe and a Numpy array
    unique_class_list_df = training_set.iloc[:,actual_class_column]
    unique_class_list_df = unique_class_list_df.sort_values()
    unique_class_list_np = unique_class_list_df.unique() #Numpy array
    unique_class_list_df = unique_class_list_df.drop_duplicates()#Pandas df
 
    # Record the number of unique classes in the data set
    num_unique_classes = len(unique_class_list_df)
 
    # Record the frequency counts of each class in a Numpy array
    freq_cnt_class = training_set.iloc[:,actual_class_column].value_counts(
        sort=True)
 
    # Record the frequency percentages of each class in a Numpy array
    # This is a list of the class prior probabilities
    class_prior_probs = training_set.iloc[:,actual_class_column].value_counts(
        normalize=True, sort=True)
 
    # Add 2 additional columns to the testing dataframe
    test_set = test_set.reindex(
        columns=[*test_set.columns.tolist(
        ), 'Predicted Class', 'Prediction Correct?'])
 
    # Calculate the number of instances and columns in the
    # testing data set. Record the index of the column that contains the 
    # predicted class and prediction correctness (1 if yes; 0 if no)
    no_of_instances_test = len(test_set.index) # number of rows
    no_of_columns_test = len(test_set.columns) # number of columns
    predicted_class_column = no_of_columns_test - 2
    prediction_correct_column = no_of_columns_test - 1
 
    ######################### Training Phase of the Model #####################
    # Create a an empty dictionary
    my_dict = {}
 
    # Calculate the likelihood tables for each attribute. If an attribute has
    # four levels, there are (# of unique classes x 4) different probabilities 
    # that need to be calculated for that attribute.
    # Start on the first attribute and make your way through all the attributes
    for col in range(1, no_of_attributes + 1):
 
        # Record the name of this column 
        colname = training_set.columns[col]
 
        # Create a dataframe containing the unique values in the column
        unique_attribute_values_df = training_set[colname].drop_duplicates()

        # Create a Numpy array containing the unique values in the column
        unique_attribute_values_np = training_set[colname].unique()
     
        # Calculate likelihood of the attribute given each unique class value
        for class_index in range (0, num_unique_classes):
         
            # For each unique attribute value, calculate the likelihoods 
            # for each class
            for attr_val in range (0, unique_attribute_values_np.size) :
                running_sum = 0
 
                # Calculate N(unique attribute value and class value)
                # Where N means "number of" 
                # Go through each row of the training set
                for row in range(0, no_of_instances_train):
                    if (training_set.iloc[row,col] == (
                        unique_attribute_values_df.iloc[attr_val])) and (
                        training_set.iloc[row, actual_class_column] == (
                        unique_class_list_df.iloc[class_index])):
                            running_sum += 1
 
                # With N(unique attribute value and class value) as the numerator
                # we now need to divide by the total number of times the class
                # appeared in the data set
                try:
                    denominator = freq_cnt_class[class_index]
                except:
                    denominator = 1.0
             
                likelihood = min(1.0,(running_sum / denominator))
             
                # Add a new likelihood to the dictionary
                # Format of search key is 
                # <attribute_name><attribute_value><class_value>
                search_key = str(colname) + str(
                    unique_attribute_values_df.iloc[
                    attr_val]) + str(unique_class_list_df.iloc[
                    class_index])
                my_dict[search_key] = likelihood
  
    # Print the likelihood table to the console
    learned_model = pd.DataFrame.from_dict(my_dict, orient='index')
 
    ################# End of Training Phase of the Naive Bayes Model ########
 
    ################# Testing Phase of the Naive Bayes Model ################
 
    # Proceed one instance at a time and calculate the prediction
    for row in range(0, no_of_instances_test):
 
        # Initialize the prediction outcome
        predicted_class = unique_class_list_df.iloc[0]
        max_numerator_of_bayes = 0.0
 
        # Calculate the Bayes equation numerator for each test instance
        # That is: P(E1|CL0)P(E2|CL0)P(E3|CL0)...P(E#|CL0) * P(CL0),
        # P(E1|CL1)P(E2|CL1)P(E3|CL1)...P(E#|CL1) * P(CL1)...
        for class_index in range (0, num_unique_classes):
 
            # Reset the running product with the class
            # prior probability, P(CL)
            try:
                running_product = class_prior_probs[class_index]
            except:
                running_product = 0.0000001 # Class not found in data set
         
            # Calculation of P(CL) * P(E1|CL) * P(E2|CL) * P(E3|CL)...
            # Format of search key is 
            # <attribute_name><attribute_value><class_value>
            # Record each search key value
            for col in range(1, no_of_attributes + 1):
                attribute_name = test_set.columns[col]
                attribute_value = test_set.iloc[row,col]
                class_value = unique_class_list_df.iloc[class_index]
 
                # Set the search key
                key = str(attribute_name) + str(
                          attribute_value) + str(class_value)
 
                # Update the running product
                try:
                    running_product *= my_dict[key]
                except:
                    running_product *= 0
 
            # Record the prediction if we have a new max
            # Bayes numerator
            if running_product > max_numerator_of_bayes:
                max_numerator_of_bayes = running_product
                predicted_class = unique_class_list_df.iloc[
                             class_index] # New predicted class
 
        # Store the prediction in the dataframe
        test_set.iloc[row,predicted_class_column] = predicted_class
     
        # Store if the prediction was correct
        if predicted_class == test_set.iloc[row,actual_class_column]:
            test_set.iloc[row,prediction_correct_column] = 1
        else: 
            test_set.iloc[row,prediction_correct_column] = 0
 
    # Store the revamped dataframe
    predictions = test_set

    # accuracy = (total correct predictions)/(total number of predictions)
    accuracy = (test_set.iloc[
        :,prediction_correct_column].sum())/no_of_instances_test
 
    # Return statement
    return  accuracy, predictions, learned_model, no_of_instances_test 
    ####################### End Testing Phase #################################

Here is the code for five-fold stratified cross-validation:

import pandas as pd # Import Pandas library 
import numpy as np # Import Numpy library

# File name: five_fold_stratified_cv.py
# Author: Addison Sears-Collins
# Date created: 7/17/2019
# Python version: 3.7
# Description: Implementation of five-fold stratified cross-validation
# Divide the data set into five random groups. Make sure 
# that the proportion of each class in each group is roughly equal to its 
# proportion in the entire data set.

# Required Data Set Format for Disrete Class Values
# Columns (0 through N)
# 0: Instance ID
# 1: Attribute 1 
# 2: Attribute 2
# 3: Attribute 3 
# ...
# N: Actual Class

def get_five_folds(instances):
    """
    Parameters:
        instances: A Pandas data frame containing the instances
    Returns: 
        fold0, fold1, fold2, fold3, fold4
        Five folds whose class frequency distributions are 
        each representative of the entire original data set (i.e. Five-Fold 
        Stratified Cross Validation)
    """
    # Shuffle the data set randomly
    instances = instances.sample(frac=1).reset_index(drop=True)

    # Record the number of columns in the data set
    no_of_columns = len(instances.columns) # number of columns

    # Record the number of rows in the data set
    no_of_rows = len(instances.index) # number of rows

    # Create five empty folds (i.e. Panda Dataframes: fold0 through fold4)
    fold0 = pd.DataFrame(columns=(instances.columns))
    fold1 = pd.DataFrame(columns=(instances.columns))
    fold2 = pd.DataFrame(columns=(instances.columns))
    fold3 = pd.DataFrame(columns=(instances.columns))
    fold4 = pd.DataFrame(columns=(instances.columns))

    # Record the column of the Actual Class
    actual_class_column = no_of_columns - 1

    # Generate an array containing the unique 
    # Actual Class values
    unique_class_list_df = instances.iloc[:,actual_class_column]
    unique_class_list_df = unique_class_list_df.sort_values()
    unique_class_list_np = unique_class_list_df.unique() #Numpy array
    unique_class_list_df = unique_class_list_df.drop_duplicates()#Pandas df

    unique_class_list_np_size = unique_class_list_np.size

    # For each unique class in the unique Actual Class array
    for unique_class_list_np_idx in range(0, unique_class_list_np_size):

        # Initialize the counter to 0
        counter = 0

        # Go through each row of the data set and find instances that
        # are part of this unique class. Distribute them among one
        # of five folds
        for row in range(0, no_of_rows):

            # If the value of the unique class is equal to the actual
            # class in the original data set on this row
            if unique_class_list_np[unique_class_list_np_idx] == (
                instances.iloc[row,actual_class_column]):

                    # Allocate instance to fold0
                    if counter == 0:

                        # Extract data for the new row
                        new_row = instances.iloc[row,:]

                        # Append that entire instance to fold
                        fold0.loc[len(fold0)] = new_row
                                    
                        # Increase the counter by 1
                        counter += 1

                    # Allocate instance to fold1
                    elif counter == 1:

                        # Extract data for the new row
                        new_row = instances.iloc[row,:]

                        # Append that entire instance to fold
                        fold1.loc[len(fold1)] = new_row
                                    
                        # Increase the counter by 1
                        counter += 1

                    # Allocate instance to fold2
                    elif counter == 2:

                        # Extract data for the new row
                        new_row = instances.iloc[row,:]

                        # Append that entire instance to fold
                        fold2.loc[len(fold2)] = new_row
                                    
                        # Increase the counter by 1
                        counter += 1

                    # Allocate instance to fold3
                    elif counter == 3:

                        # Extract data for the new row
                        new_row = instances.iloc[row,:]

                        # Append that entire instance to fold
                        fold3.loc[len(fold3)] = new_row
                                    
                        # Increase the counter by 1
                        counter += 1

                    # Allocate instance to fold4
                    else:

                        # Extract data for the new row
                        new_row = instances.iloc[row,:]

                        # Append that entire instance to fold
                        fold4.loc[len(fold4)] = new_row
                                    
                        # Reset counter to 0
                        counter = 0
        
    return fold0, fold1, fold2, fold3, fold4

Return to Table of Contents

Output Statistics of Naive Bayes

Here are the trace runs:

Here are the results:

results-naive-bayes

Here are the test statistics for each data set:

Return to Table of Contents

References

Alpaydin, E. (2014). Introduction to Machine Learning. Cambridge, Massachusetts: The MIT Press.

Fisher, R. (1988, July 01). Iris Data Set. Retrieved from Machine Learning Repository: https://archive.ics.uci.edu/ml/datasets/iris

German, B. (1987, September 1). Glass Identification Data Set. Retrieved from UCI Machine Learning Repository: https://archive.ics.uci.edu/ml/datasets/Glass+Identification

Kelleher, J. D., Namee, B., & Arcy, A. (2015). Fundamentals of Machine Learning for Predictive Data Analytics. Cambridge, Massachusetts: The MIT Press.

Michalski, R. (1980). Learning by being told and learning from examples: an experimental comparison of the two methodes of knowledge acquisition in the context of developing an expert system for soybean disease diagnosis. International Journal of Policy Analysis and Information Systems, 4(2), 125-161.

Rebala, G., Ravi, A., & Churiwala, S. (2019). An Introduction to Machine Learning. Switzerland: Springer.

Schlimmer, J. (1987, 04 27). Congressional Voting Records Data Set. Retrieved from Machine Learning Repository: https://archive.ics.uci.edu/ml/datasets/Congressional+Voting+Records

Wolberg, W. (1992, 07 15). Breast Cancer Wisconsin (Original) Data Set. Retrieved from Machine Learning Repository: https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+%28Original%25

Y. Ng, A., & Jordan, M. (2001). On Discriminative vs. Generative Classifiers: A Comparison of Logistic Regression and Naive Bayes. NIPS’01 Proceedings of the 14th International Conference on Neural Information Processing Systems: Natural and Synthetic , 841-848.

Return to Table of Contents

Useful Metrics for 2-Class Classification Problems

In machine learning, two-class classification problems are when we want to predict the class of an event given some attributes (e.g. spam/not spam, sick/not sick, pregnant/not pregnant, cancer/not cancer, etc.). We keep track of the prediction accuracy by constructing a confusion matrix and make a tally of the number of true positives, false positives, false negatives, and true negatives.

confusion-matrix

Here are some useful statistical metrics for two-class classification problems:

Accuracy: The proportion of all instances that are correctly predicted.

  • Accuracy = (TP + TN)/(TP + TN + FP + FN)

Specificity: The proportion of actual negatives (i.e. 0) that were correctly predicted as such (e.g., the percentage of people who are healthy who were correctly predicted as being healthy).

  • Specificity = (TN/(TN + FP))

Precision: The proportion of all positive predictions that were correct (e.g. the percentage of people who were predicted to have the disease and actually had the disease).

  • Precision = (TP/(TP + FP))

Recall: The proportion of actual positives that were correctly identified as such (e.g., the percentage of people who have the disease who were correctly predicted to have the disease).

  • Recall = (TP/(TP + FN))

Negative Predictive Value: The proportion of all negative predictions that were correct (e.g. the percentage of people who were predicted to be healthy who actually are healthy).

  • Negative Predictive Value = (TN/(TN + FN))

Miss Rate: The proportion of actual positives that were predicted to be negative (e.g. the percentage of people who have the disease that were predicted to be healthy).

  • Miss Rate = (FN/(FN + TP))  

Fall-Out: The proportion of actual negatives that were predicted to be positive (e.g. the percentage of people who are healthy that were predicted to have the disease).

  • Fall-Out = (FP/(FP + TN))

False Discovery Rate: The proportion of all positive predictions that were incorrect (e.g. the percentage of people who were predicted to have the disease that are actually healthy).

  • False Discovery Rate = (FP/(FP + TP))

False Omission Rate: The proportion of all negative predictions that were incorrect (e.g. the percentage of people who were predicted to be healthy that actually have the disease).

  • False Omission Rate = (FN / (FN + TN))

F1 Score: Measures prediction accuracy as a function of precision and recall. An F1 score of 1 is good…perfect precision and recall. An F1 score of 0 is bad, the worst-case precision and recall.

  • F1 Score = ((2TP)/(2TP + FP + FN))

How to Control a Servo Motor Using Arduino

Motors are what enable robots to move and do things. Without motors, robots are just big pieces of metal and plastic that can’t move.

The simplest type of motor is the direct current (DC) motor. This type of motor spins around and around in one direction, rotating 360 degrees. It only stops rotating when it is disconnected from a power source. DC motors are common in wheeled robots.

Another type of motor is known as the servo motor (“servo”). Instead of rotating continuously for 360 degrees in one direction, servo motors move to specific angles, typically anything between 0 and 180 degrees. Servo motors are common in robotics. You will see them in all types of applications where a motor needs to move a part of the robot to a specific position. Examples include robot arms, hands, legs, and humanoid robots. NASA’s Robonaut 2, for example, has a total of 54 servo motors which are used to move the robot’s joints, head, and other body parts.

In this post, I will explain how servos work, and then we will get our hands dirty by powering up our Arduino and using it to control a servo.

How Servos Work

Servos work by receiving electrical signals. The length of this signal controls the angle the motor turns…the longer the signal, the greater the motor will turn. This process is known as pulse-width-modulation because the width (i.e. duration) of the electrical pulse modulates (modifies) the motor’s angle of rotation. Here is what that looks like:

controlling-a-servo-motor-2

Requirements

Here are the requirements:

  • Control a servo motor’s angle of rotation.

You Will Need

controlling-a-servo-motor-1

The following components are used in this project. You will need:

Directions

Control a Servo Using Arduino’s Power Supply

controlling-a-servo-motor-3

Here is the hardware we need to set up:

servo-motor-arduino

Connect the red wire of the servo to the 5V pin of the Arduino Uno.

Connect the black wire of the servo to the GND (ground) pin of the Arduino Uno.

Connect the yellow control wire of the servo to Digital Pin 9 of the Arduino Uno. This yellow wire is the one that will receive commands from the Arduino. Pin 9 is one of Arduino’s Pulse-Width Modulation pins.

Power up your Arduino by plugging in the USB cord to your computer.

Open the Arduino IDE, and in a new sketch, write the following code:

/* Sweep
 by BARRAGAN <http://barraganstudio.com>
 This example code is in the public domain.

 modified 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Sweep
*/

#include <Servo.h>

Servo myservo;  // create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
}

void loop() {
  for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
  }
  for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(15);                       // waits 15ms for the servo to reach the position
  }
}

Upload the code to your board. This code will make the shaft of the motor sweep back and forth 180 degrees.

Control a Servo Using Arduino and a Potentiometer

controlling-a-servo-motor-4

In some applications, we might want to control the angle a servo rotates without having to constantly modify the code. One way to do this is to use a potentiometer. Think of a potentiometer as a variable resistor. By turning the knob, you can control the voltage output of the potentiometer.

In this piece of the project, we will set up software that reads the voltage output of the potentiometer. It then converts that number it into an angle for the servo.

A potentiometer has 3 terminals:

  1. Two outer terminals are used for power: one outer pin connects to ground and the other connects to positive voltage. Potentiometers don’t have polarity, so it doesn’t matter which one is ground and which one is connected to positive voltage.
  2. A central control terminal is used for voltage output: turning the knob of the potentiometer increases or decreases the resistance, which lowers or increases the voltage output.

So let’s set all this up. Here is the schematic diagram:

servo-motor-arduino-potentiometer

With your Arduino unplugged, stick the 10k Ohm potentiometer into the solderless breadboard. Make sure each terminal is connected to separate row in the breadboard.

Connect one of the outer terminals to the blue (ground) rail of the breadboard.

Connect the other outer terminal to the red (positive) rail of the breadboard.

Connect the central pin of the potentiometer to Analog Input pin A0 of the Arduino.

Connect the black and red wires of the servo to the blue and red rail of the breadboard, respectively.

Connect the yellow control wire to pin 9 of the Arduino.

Connect a wire from the +5V pin of the Arduino to the positive red rail of the breadboard.

Connect a wire from Ground (GND) of the Arduino to the blue rail of the breadboard.

This completes the hardware setup.

Plug in the Arduino.

controlling-a-servo-motor-5

Open up the IDE. Inside a new sketch, write the following code:

/*
 Controlling a servo position using a potentiometer (variable resistor)
 by Michal Rinott <http://people.interaction-ivrea.it/m.rinott>

 modified on 8 Nov 2013
 by Scott Fitzgerald
 http://www.arduino.cc/en/Tutorial/Knob
*/

#include <Servo.h>

Servo myservo;  // create servo object to control a servo

int potpin = 0;  // analog pin used to connect the potentiometer
int val;    // variable to read the value from the analog pin

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
}

void loop() {
  val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
  val = map(val, 0, 1023, 0, 180);     // scale it to use it with the servo (value between 0 and 180)
  myservo.write(val);                  // sets the servo position according to the scaled value
  delay(15);                           // waits for the servo to get there
}

Upload the code to your board.

Turn the knob on your potentiometer to move the servo.

Your Arduino microcontroller is reading the voltage that is coming in from the potentiometer on Analog Input pin 0. It is then converting this voltage into a value between 0 and 180. This degree value then gets sent down the yellow control wire to the servo, and the servo moves accordingly. Thus, the higher the voltage output by the potentiometer, the greater the servo’s angle of rotation.

Control a Servo Using an External Power Supply

controlling-a-servo-motor-6

We don’t always want to use the Arduino to power our servos. Sometimes we might want to use an external power supply. Here is a basic schematic (Note: Ignore the AAA on the batteries below. They are actually AA.):

servo-motor-arduino-external-power

The biggest change from the previous implementation is that now the servos are connected to the 4xAA battery holder instead of the Arduino’s power supply.

With your Arduino powered off, move the red and black wires of the servo to the other red and blue rails of your solderless breadboard.

Connect the red wire of the 4xAA battery holder to the red rail of the solderless breadboard (the one electrically connected to the servo).

Connect the black wire of the 4xAA battery holder to the blue rail of the breadboard.

Make sure your external power supply is connected to the same ground as the Arduino. Use a male-to-male jumper wire to connect both ground rails. This is called common ground.

Power up the Arduino, open the IDE, and find the same sketch as the previous section of this blog post.

Upload the sketch to your Arduino.

Turn the knob of the potentiometer, and watch the servo move.

Congratulations! You now know how to control a servo and move it to a specific position.

How to Configure Raspberry Pi to Run Programs On Startup

In this post, I’ll show you how to configure your Raspberry Pi so that your programs can run as soon as you plug in your Raspberry Pi to a power source.

Requirements

Here are the requirements:

  • Run a program as soon as the Raspberry Pi is turned on.

Directions

Power up your Raspberry Pi, and open up a terminal window.

Type:

sudo nano /etc/rc.local

Scroll down the file to the area right after fi but before exit 0. Type:

python3 /home/pi/robot/ball_following_yellow.py &

The format is

python3 /your/file/path/here/filename.py &

Press CTRL-X, and save the file.

Restart your Raspberry Pi.

sudo reboot

Your program, ball_following_yellow.py, should run on startup.

How to Make an Object Tracking Robot Using Raspberry Pi

In this tutorial, I will show you how to give your wheeled robot the ability to follow a colored ball. You will get your first taste of computer vision and image processing.

Video

Here is a video of what we will build in this tutorial.

Requirements

Here are the requirements:

  • Build a wheeled robot powered by Raspberry Pi that must identify and follow a yellow rubber ball using OpenCV, a library of programming functions for real-time computer vision and image processing.

You Will Need

The following components are used in this project. You will need:

Directions

Connecting the Raspberry Pi Camera Module

Make sure the Raspberry Pi is turned OFF.

Open the Camera Serial Interface on the Raspberry Pi. It is located next to the 3.5mm audio jack. Pull it upwards delicately from either side.

Insert the ribbon of the camera module into the Camera Serial Interface. Make sure the silver contacts face away from the 3.5mm audio jack.

2019-06-03-194539

Hold the ribbon in place while pushing down on the Camera Serial Interface port. Make sure it is closed.

2019-06-03-194547

Mount the camera to the front of the robot.

2019-06-03-200735

Power up Raspberry Pi.

Open up a configuration window:

sudo raspi-config

Interfacing Options –> ENTER –> Camera –> ENTER –> Yes

The camera is enabled.

Now, we need to set the resolution.

Advanced Options –> Resolution –> DMT Mode 82 1920×1080 60Hz 16: 9 –> ENTER –> Finish

Restart the Raspberry Pi by typing the following in a terminal window.

sudo reboot

Testing the Raspberry Pi Camera Module.

We need to take a test photo with our newly installed camera module.

Open a terminal window. Type the following command:

raspistill -o test_photo.jpg

Go to your home directory to see if the test photo is there. Here is the photo that mine took (back of my head).

test-camera

Setting Up Object Tracking

Now, we need to configure our system so the robot can track a yellow rubber ball.

Download the dependencies for OpenCV, a library of programming functions for real-time computer vision and image processing.

Type the following command into a terminal window:

sudo apt-get update
sudo apt-get install libblas-dev libatlas-base-dev libjasper-dev libqtgui4 libqt4-test

Y –> ENTER.

Wait a few moments while everything installs.

Install OpenCV using pip.

sudo pip3 install opencv-python
installing-open-cvPNG

Install the PiCamera library.

sudo apt-get install python3-picamera

Determining the HSV Value of the Yellow Ball

We need to select an appropriate HSV (hue, saturation, value) value for the yellow ball. HSV is an alternative color representation that is frequently used instead of the RGB (Red Green Blue) color model I covered in my light and sound wheeled robot post.

Here is the HSV table.

512px-HSV_color_solid_cylinder_saturation_gray

Since the ball is yellow, I’ll choose 60 as my starting number.

Open IDLE in your Raspberry Pi, and create a new file in your robot directory. Name it color_tester.py.

Here is the code for the program:

# import the necessary packages
from picamera.array import PiRGBArray
from picamera import PiCamera
import time
import cv2
import numpy as np


# initialize the camera and grab a reference to the raw camera capture
camera = PiCamera()
camera.resolution = (640, 480)
camera.framerate = 32
rawCapture = PiRGBArray(camera, size=(640, 480))

while True:
	while True:
		try:
			hue_value = int(input("Hue value between 10 and 245: "))
			if (hue_value < 10) or (hue_value > 245):
				raise ValueError
		except ValueError:
			print("That isn't an integer between 10 and 245, try again")
		else:
			break

	lower_red = np.array([hue_value-10,100,100])
	upper_red = np.array([hue_value+10, 255, 255])

	for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
		image = frame.array

		hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

		color_mask = cv2.inRange(hsv, lower_red, upper_red)

		result = cv2.bitwise_and(image, image, mask= color_mask)

		cv2.imshow("Camera Output", image)
		cv2.imshow("HSV", hsv)
		cv2.imshow("Color Mask", color_mask)
		cv2.imshow("Final Result", result)

		rawCapture.truncate(0)

		k = cv2.waitKey(5) #&amp; 0xFF
		if "q" == chr(k &amp; 255):
			break

Place your ball about a yard in front of the camera.

2019-06-03-203156

Run the newly created program.

python3 color_tester.py

Choose 60.

You will see four windows.

  • Window 1. RGB representation
  • Window 2: HSV representation
  • Window 3: Show the portions of the frame that match a hue value of 60.
  • Window 4: Entire frame minus all portions that do NOT have a 60 hue value.
open-cv-robot-yellow-ballPNG

To try a different hue value, select any of the four windows above. Press Q to halt the output of the video.

Go to the terminal window, and try a new hue valve. I’ll try 29 this time. It worked!

You keep trying different numbers until Window 4 shows mostly your ball and nothing else. Be patient and try LOTS of numbers.

try-try-againPNG

Write down the hue value you ended up with on a sheet of paper.

Press CTRL-C in the terminal window to stop running color_tester.py.

Coding the Ball-Following Program

Open IDLE. Create a new file in your robot directory named:

ball_following_yellow. py

Here is the code (Credit to Matt Timmons-Brown, the author of a really good book on Raspberry Pi robotics: (Learn Robotics with Raspberry Pi):

from picamera.array import PiRGBArray
from picamera import PiCamera
import cv2
import numpy as np
import gpiozero

camera = PiCamera()
image_width = 640
image_height = 480
camera.resolution = (image_width, image_height)
camera.framerate = 32
rawCapture = PiRGBArray(camera, size=(image_width, image_height))
center_image_x = image_width / 2
center_image_y = image_height / 2
minimum_area = 250
maximum_area = 100000

robot = gpiozero.Robot(left=(22,27), right=(17,18))
forward_speed = 1.0
turn_speed = 0.8

HUE_VAL = 29

lower_color = np.array([HUE_VAL-10,100,100])
upper_color = np.array([HUE_VAL+10, 255, 255])

for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
	image = frame.array

	hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

	color_mask = cv2.inRange(hsv, lower_color, upper_color)

	image2, countours, hierarchy = cv2.findContours(color_mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

	object_area = 0
	object_x = 0
	object_y = 0

	for contour in countours:
		x, y, width, height = cv2.boundingRect(contour)
		found_area = width * height
		center_x = x + (width / 2)
		center_y = y + (height / 2)
		if object_area < found_area:
			object_area = found_area
			object_x = center_x
			object_y = center_y
	if object_area > 0:
		ball_location = [object_area, object_x, object_y]
	else:
		ball_location = None

	if ball_location:
		if (ball_location[0] > minimum_area) and (ball_location[0] < maximum_area):
			if ball_location[1] > (center_image_x + (image_width/3)):
				robot.right(turn_speed)
				print("Turning right")
			elif ball_location[1] < (center_image_x - (image_width/3)):
				robot.left(turn_speed)
				print("Turning left")
			else:
				robot.forward(forward_speed)
				print("Forward")
		elif (ball_location[0] < minimum_area):
			robot.left(turn_speed)
			print("Target isn't large enough, searching")
		else:
			robot.stop()
			print("Target large enough, stopping")
	else:
		robot.left(turn_speed)
		print("Target not found, searching")

	rawCapture.truncate(0)

Running the Ball – Following Program

Place your robot in an open space on the floor with the yellow rubber ball.

Run the program.

python3 ball_following_yellow.py

Whenever you want to stop the program, type CTRL-C.

How to Make a Line Following Robot Using Raspberry Pi

You all might remember the line-following robot I built using Arduino. Well today, we are going to do the same thing, but with a Raspberry Pi.

Video

Here is a video of what we will build in this tutorial.

Requirements

Here are the requirements:

  • Build a line-following robot using Raspberry Pi.

You Will Need

line-following-robot-6

The following components are used in this project. You will need:

Directions

Connecting the Infrared Line Sensor

Make sure the Raspberry Pi is turned OFF.

Connect the VCC pin of the IR line sensor to pin 1 of the Raspberry Pi.

Connect the GND pin of the line sensor to the blue (negative) ground rail of the solderless breadboard.

Connect the OUT pin of the line sensor to pin 21 (GPIO 9) of the solderless breadboard.

Testing the Infrared Line Sensor

Power up your Raspberry Pi, and open IDLE.

Create a new program called test_line_following.py.

Save it in to your robot directory.

Here is the code:

import gpiozero
import time
# Description: Code for testing the
# TCRT5000 IR Line Track Follower Sensor
# Author: Addison Sears-Collins
# Date: 05/29/2019


# Initialize line sensor to GPIO9
line_sensor = gpiozero.DigitalInputDevice(9)

while True:
  if line_sensor.is_active == False:
    print("Line detected")
  else:
    print("Line not detected")

  time.sleep(0.2) # wait for 0.2 seconds

Create a line-following course using your black electrical tape and your poster board. It should look something like this.

line-following-robot-3

I recommend leaving a 3-inch margin between the side of the poster board and the course. Don’t make curves that are too sharp.

line-following-robot-4

Run your test program from a terminal window.

cd robot
python3 test_line_following.py

Move your track in front of the sensor to see if the terminal prints out “Line detected. “

line-following-robot-1
line-following-robot-8

If you are running into issues, use a screwdriver to adjust the sensitivity of the sensor.

That white and blue potentiometer is what you should tweak.

Connect the other IR line sensor.

Connect the VCC pin of the IR line sensor to pin 17 of the Raspberry Pi using a female-to-female jumper wire.

Connect the GND pin of the IR line sensor to the blue (negative) ground rail of the solderless breadboard.

Connect the OUT pin of the IR line sensor to pin 23 (GPIO 11) of the Raspberry Pi.

Attaching the Sensors

Attach the first IR line sensor you wired up to the front, left side of the robot.

Attach the second IR line sensor to the right side of the robot.

Both sensors need to be just off the ground and can be mounted using 2×2 Lego blocks that extend downward from the body of the robot.

A piece of VELCRO is sufficient to attach both sensors.

Run the wires connected to the IR line sensors down through the gap in the robot body.

line-following-robot-2
line-following-robot-7

Create the Line-Following Program in Python

Open IDLE on your Raspberry Pi.

Create a new file inside your robot directory named

line_following_robot.py

Here is the code:

import gpiozero

# File name: line_following_robot.py
# Author: Addison Sears-Collins
# Date created: 5/29/2019
# Python version: 3.5.3
# Description: Follow a line using a TCRT5000 IR
# Line Following Sensor

robot = gpiozero.Robot(left=(22,27), right=(17,18))

left = gpiozero.DigitalInputDevice(9)
right = gpiozero.DigitalInputDevice(11)

while True:
  if (left.is_active == True) and (right.is_active == True):
    robot.forward()
  elif (left.is_active == False) and (right.is_active == True):
    robot.right()
  elif (left.is_active == True) and (right.is_active == False):
    robot.left()
  else:
    robot.stop()

Deploying Your Line-Following Robot

Place your robot on your track. Make sure the line-following sensors are directly above the black line.

line-following-robot-5

Verify that your Raspberry Pi is connected to battery power, and your 4xAA battery pack is turned on.

Run your program from inside the robot directory.

cd robot
python3 line_following_robot.py

Watch your robot follow the line! Press CTRL-C anytime to stop the program.

How to Add Light to a Raspberry Pi Wheeled Robot

In this post, I will show you how to add light to a Raspberry Pi wheeled robot.

Requirements

Here are the requirements:

  • Add lights to a wheeled robot using the Adafruit NeoPixel Stick with 8 RGB LEDs.

You Will Need

The following components are used in this project. You will need:

Directions

Setting Up the RGB LED Stick

Cut four pins off one of the male pin header connectors with a pair of scissors.

Solder the four pins to the DIN (stands for “data-in”) side of the RGB LED stick.

lights-robot-rpi-1

If you don’t know how to solder, check out my video below.

Connect the 5VDC pin of the RGB LED stick to the positive (red) 5V rail of the solderless breadboard.

lights-robot-rpi-2

Connect the GND (Ground) pin of the RGB LED stick to the blue (negative) rail of the solderless breadboard.

lights-robot-rpi-3

Connect the DIN pin of the RGB LED stick to pin 19 (GPIO 10) of the Raspberry Pi. This is the Master Output Slave Input (MOSI) pin that the Pi uses to send information out via the SPI protocol (more on this in a second).

lights-robot-rpi-4

Mount the RGB LED on the Robot using Velcro. You can mount it anywhere you want.

lights-robot-rpi-5

Power up your Raspberry Pi.

Open a terminal window.

Verify that pip is installed. Pip is a tool that enables you to install and manage libraries that are not a part of Python’s standard library. Type the following commands in the terminal window.

sudo apt-get update
lights-robot-rpi-6
sudo apt-get install python3-pip
Install the rpi_ws 281x library.
sudo pip3 install rpi_ws281x

Make sure the SPI bus is enabled.

SPI is a communication interface built-in to several of the GPIO pins of the Raspberry Pi. It is good to use the SPI bus when you want data to be streamed over short distances, continuously with no interruptions.

Type this terminal command:

sudo raspi-config

Interfacing Options → SPI

lights-robot-rpi-7

Select Yes

Finish

Reboot the Raspberry Pi.

sudo reboot

Wait for the Raspberry Pi to reboot, then open a terminal window again in Raspberry Pi (I’m using Putty).

Modify the Graphics Programming Unit core frequency so that it is 250 MHz.

sudo nano /boot/config.txt

Scroll down, and add this to the bottom of the file.

core_freq = 250
lights-robot-rpi-8

CTRL-X –> Y –> ENTER to save

Reboot the Raspberry Pi.

sudo reboot 

Testing the rpi_ws281x Library

Open IDLE on your Raspberry Pi.

Create a new file.

Save it as rgb_library_test.py

Type the following code:

#!/usr/bin/env python3
# NeoPixel library strandtest example
# Author: Tony DiCola (tony@tonydicola.com)
#
# Direct port of the Arduino NeoPixel library strandtest example.  Showcases
# various animations on a strip of NeoPixels.

# Minor edits by Matt Timmons-Brown for "Learn Robotics with Raspberry Pi"

import time
from rpi_ws281x import *
import argparse

# LED strip configuration:
LED_COUNT      = 8      # Number of LED pixels.
#LED_PIN       = 18      # GPIO pin connected to the pixels (18 uses PWM!).
LED_PIN        = 10      # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0).
LED_FREQ_HZ    = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA        = 10      # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 255     # Set to 0 for darkest and 255 for brightest
LED_INVERT     = False   # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL    = 0       # set to '1' for GPIOs 13, 19, 41, 45 or 53
LED_STRIP      = ws.WS2811_STRIP_GRB # Strip type and color ordering

# Define functions which animate LEDs in various ways.
def colorWipe(strip, color, wait_ms=50):
    """Wipe color across display a pixel at a time."""
    for i in range(strip.numPixels()):
        strip.setPixelColor(i, color)
        strip.show()
        time.sleep(wait_ms/1000.0)

def theaterChase(strip, color, wait_ms=50, iterations=10):
    """Movie theater light style chaser animation."""
    for j in range(iterations):
        for q in range(3):
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, color)
            strip.show()
            time.sleep(wait_ms/1000.0)
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, 0)

def wheel(pos):
    """Generate rainbow colors across 0-255 positions."""
    if pos < 85:
        return Color(pos * 3, 255 - pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return Color(255 - pos * 3, 0, pos * 3)
    else:
        pos -= 170
        return Color(0, pos * 3, 255 - pos * 3)

def rainbow(strip, wait_ms=20, iterations=1):
    """Draw rainbow that fades across all pixels at once."""
    for j in range(256*iterations):
        for i in range(strip.numPixels()):
            strip.setPixelColor(i, wheel((i+j) &amp; 255))
        strip.show()
        time.sleep(wait_ms/1000.0)

def rainbowCycle(strip, wait_ms=20, iterations=5):
    """Draw rainbow that uniformly distributes itself across all pixels."""
    for j in range(256*iterations):
        for i in range(strip.numPixels()):
            strip.setPixelColor(i, wheel((int(i * 256 / strip.numPixels()) + j) &amp; 255))
        strip.show()
        time.sleep(wait_ms/1000.0)

def theaterChaseRainbow(strip, wait_ms=50):
    """Rainbow movie theater light style chaser animation."""
    for j in range(256):
        for q in range(3):
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, wheel((i+j) % 255))
            strip.show()
            time.sleep(wait_ms/1000.0)
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, 0)

# Main program logic follows:
if __name__ == '__main__':
    # Process arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-c', '--clear', action='store_true', help='clear the display on exit')
    args = parser.parse_args()

    # Create NeoPixel object with appropriate configuration.
    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
    # Intialize the library (must be called once before other functions).
    strip.begin()

    print ('Press Ctrl-C to quit.')
    if not args.clear:
        print('Use "-c" argument to clear LEDs on exit')

    try:

        while True:
            print ('Color wipe animations.')
            colorWipe(strip, Color(255, 0, 0))  # Red wipe
            colorWipe(strip, Color(0, 255, 0))  # Blue wipe
            colorWipe(strip, Color(0, 0, 255))  # Green wipe
            print ('Theater chase animations.')
            theaterChase(strip, Color(127, 127, 127))  # White theater chase
            theaterChase(strip, Color(127,   0,   0))  # Red theater chase
            theaterChase(strip, Color(  0,   0, 127))  # Blue theater chase
            print ('Rainbow animations.')
            rainbow(strip)
            rainbowCycle(strip)
            theaterChaseRainbow(strip)

    except KeyboardInterrupt:
        if args.clear:
            colorWipe(strip, Color(0,0,0), 10)

Save it.

Open a new terminal window.

Run the test using the following command:

python3 rgb_library_test.py -c

The -c at the end makes sure that the RGB LEDs turn off when you type CTRL-C in the terminal window.

Managing the RGB LEDs Using the Nintendo Wii Remote Control

Now we will create a program to control the LEDs using our Wii remote. Open a new terminal window on your Raspberry Pi, and go to the robot directory.

cd robot

Open a new file in IDLE named rgb_wii_remote.py

Type the following code into the program:

import gpiozero
import cwiid
import time
from rpi_ws281x import *

# Author: Matt Timmons-Brown for "Learn Robotics with Raspberry Pi"
# Description: Enables control of the LED with the Wii Remote
# Minor modifications made by Addison Sears-Collins

robot = gpiozero.Robot(left=(22,27), right=(17,18))

print("Press and hold the 1+2 buttons on your Wiimote simultaneously")
wii = cwiid.Wiimote()
print("Connection established")
wii.rpt_mode = cwiid.RPT_BTN | cwiid.RPT_ACC

LED_COUNT      = 8
LED_PIN        = 10
LED_FREQ_HZ    = 800000
LED_DMA        = 10
LED_BRIGHTNESS = 150
LED_INVERT     = False
LED_CHANNEL    = 0
LED_STRIP      = ws.WS2811_STRIP_GRB

strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP)
strip.begin()

def colorWipe(strip, color, wait_ms=50):
	"""Wipe color across display a pixel at a time."""
	for i in range(strip.numPixels()):
		strip.setPixelColor(i, color)
		strip.show()
		time.sleep(wait_ms/1000.0)

while True:
	buttons = wii.state["buttons"]
	if (buttons &amp; cwiid.BTN_PLUS):
		colorWipe(strip, Color(255, 0, 0))  # Red wipe
	if (buttons &amp; cwiid.BTN_HOME):
        colorWipe(strip, Color(0, 255, 0))  # Blue wipe
	if (buttons &amp; cwiid.BTN_MINUS):
        colorWipe(strip, Color(0, 0, 255))  # Green wipe
	if (buttons &amp; cwiid.BTN_B):
		colorWipe(strip, Color(0, 0, 0)) # Blank

	x = (wii.state["acc"][cwiid.X] - 95) - 25
	y = (wii.state["acc"][cwiid.Y] - 95) - 25

	if x < -25:
		x = -25
	if y < -25:
		y = -25
	if x > 25:
		x = 25
	if y > 25:
		y = 25

	forward_value = (float(x)/50)*2
	turn_value = (float(y)/50)*2

	if (turn_value < 0.3) and (turn_value > -0.3):
		robot.value = (forward_value, forward_value)
	else:
		robot.value = (-turn_value, turn_value)

Save the program.

Place your robot in an open space, and run the following command from inside your Pi’s robot directory.

python3 rgb_wii_remote.py

You can press the home, minus, and plus buttons to activate different lights.

Turn off the lights using the B button, and stop the program by typing CTRL-C.

How to Make an Obstacle Avoiding Robot Using Raspberry Pi

In this tutorial, I will show you how to program a Raspberry Pi-controlled wheeled robot so that it can detect and avoid obstacles autonomously (fancy word for “all by itself”). I have already done this for an Arduino-controlled robot, now let’s do it for our Raspberry Pi-controlled robot.

Real-world applications of obstacle avoidance in robotics can be seen in devices such as iRobot’s autonomous vacuum cleaner.

roomba_discovery

Video

Here is a video of what we will build in this tutorial.

Requirements

Here are the requirements:

  • Configure a wheeled robot so that it can detect and avoid obstacles autonomously.

You Will Need

obstacle-avoiding-robot-2-1

The following components are used in this project. You will need:

Directions

How Distance to an Object is Calculated

In order to give our robot the ability to “see” obstacles in front of it, we need to connect it up to a sensor. We will use the HC-SR04 ultrasonic sensor, a popular sensor in the do-it-yourself robotics world.

The HC-SR04 has a transmitter and a receiver. The transmitter sends out a 10 microsecond sound wave that is beyond the range of what humans are able to hear. The receiver waits for that sound wave to reflect back. The distance to the object is calculated based on the duration of the echoed sound wave, which is directly proportional to the duration the sound wave traveled (from transmitter to object to receiver).

How-ultrasonic-sensor-works-1

Image Source: Random Nerd Tutorials

hc-sr04-timing-diagram

Image Source: MCU on Eclipse

The distance is calculated based on our knowledge of the speed of sound in dry air at 20°C, which is 343 meters per second (1 mile in 4.7 seconds).

Distance in meters = Speed in meters per second x [(Time in seconds) / 2]

We divide the time by 2 because the sound wave traveled from the HC-SR04 sensor to the object and then back to the sensor. We only want to know the one-way distance from the sensor to the object, not the roundtrip distance. Therefore, in our distance calculation, time is divided by 2.

Determining What Resistors You Need

The Raspberry Pi’s GPIO pins have a working voltage of 3.3V. The HC-SR04 operates on 5V logic. If you connect the HC-SR04 directly to the Raspberry Pi, you might break your Raspberry Pi. This is because when the Echo pin (the receiver) of the HC-SR04 receives the sound wave that was originally emitted by the Trigger pin (the transmitter), the Echo pin will go from LOW (0V) to HIGH (5V). This HIGH signal is then sent to one of the GPIO pins on the Raspberry Pi. But since the GPIO pins can only handle 3.3V, you will overload the Raspberry Pi with voltage.

We must reduce the voltage produced by the ECHO pin (5V) to a level that the Raspberry Pi can handle (3.3V). In order to transform a larger voltage into a smaller voltage, we need to make a circuit known as a voltage divider. The output voltage of a voltage divider is a fraction of the input voltage.

A voltage divider uses two resistors to divide a single voltage into two smaller voltages. The voltage drop across a resistor in a series circuit is directly proportional to the size of the resistor because of a famous law, known as Ohm’s Law (V = IR; Voltage = Current x Resistance).

So, what do we need in order to build a voltage divider? All that is needed are two resistors connected in series (one right after the other) from the input voltage (the voltage you want to reduce) to the ground. You also need a jumper wire to insert in between the two resistors. This jumper wire is the output (target) voltage of the voltage divider and connects to the GPIO pin of the Raspberry Pi.

Here is a diagram of a voltage divider:

voltage-divider

Image Source: Ohms Law Calculator

The output voltage (which we want to be 3.3V) is related to the input voltage (5V in our case) via the following mathematical expression:

VoltageOut = VoltageIn x [(Resistor 2)/(Resistor 1 + Resistor 2)]         

Notice that the output voltage is the proportion of the input voltage that drops across Resistor 2.

Let’s work with some actual numbers.

  • VoltageOut = Vout = 3.3V   (This is the target voltage; the voltage we desire.)
  • VoltageIn = Vin = 5V   (This is the voltage emitted by the ECHO pin; the voltage we need to reduce)
  • Resistor 1 = R1 = 1000 Ω   (I picked this number; you can choose any reasonable number)
  • Resistor 2 = R2 = ?   (This is what we need to solve for)

Plug these number into the equation. You can work out the algebra with me by hand on a scratch piece of paper. I’ll show my work, step-by-step.

3.3 = 5 x [(R2)/(1000 + R2)]
3.3/5 = R2/(1000 + R2)          Divide both sides by 5
(3.3/5)(1000 + R2) = R2          Multiply (1000 + R2) on both sides   
(3.3/5)(1000) + (3.3/5)R2 = R2               Distributive Property
(3.3/5)(1000) = [1 - (3.3/5)]R2          Gather similar terms 
[(3.3/5)(1000)]/[1 - (3.3/5)] = R2           Divide by [1 - (3.3/5)]
1941.176 Ω = R2 

1941 Ω resistors don’t exist, but we do have a 2000 Ω (2kΩ) resistor. Let’s see what the output voltage is in this case:

  • R1 = 1000 Ω
  • R2 = 2000 Ω
  • Vin = 5V
  • Vout = ?

Remember the voltage divider equation:

Let’s solve for Vout

Vout = 5 x [2000/(1000 + 2000)]
Vout = 5 x [2000/3000]
Vout = 3.3333...        which is close enough to our target voltage

What would have happened if we used a 3000 Ω resistor instead?

Vout = 5 x [3000/(1000 + 3000)]
Vout = 5 x (3000/4000)
Vout = 3.75V       which is >3.3V and will break the Raspberry Pi

Therefore, it is always good to use a resistor for R2 that has a little bit less resistance than what you calculate mathematically. That way you protect your Raspberry Pi from damage.

Connect the HC-SR04

Make sure the Raspberry Pi is turned OFF.

Grab two long female-to-male jumper wires.

Connect the VCC pin of the HC-SR04 to the red (positive) rail of the solderless breadboard (the one connected to the Raspberry Pi).

obstacle-avoiding-robot-1-1

Connect the GND pin of the HC-SR04 to the blue (negative) rail of the solderless breadboard (the one connected to your Raspberry Pi).

obstacle-avoiding-robot-3-1

Here is the Raspberry Pi pin diagram:

rpi_pin_diagram_2-1

Using a female-to-female jumper wire, connect the Trigger (labeled Trig) pin of the HC-SR04 to pin 16 (GPIO 23) of the Raspberry Pi.

obstacle-avoiding-robot-4

Connect the Echo pin of the HC-SR04 to an empty row on the solderless breadboard. I connected it to e26.

obstacle-avoiding-robot-5

Connect a 1 kΩ resistor from a hole on the same row as the jumper wire (connected to the Echo pin) to an empty row on the solderless breadboard. I connected it from d26 to d22.

obstacle-avoiding-robot-6

Connect a jumper wire from the 1 kΩ resistor to pin 18 (GPIO 24) of the Raspberry Pi. I connected it to c22.

obstacle-avoiding-robot-7

Connect a 2 kΩ resistor from the same row as the pin 18 jumper wire (you just inserted) to the blue (negative) ground rail of the solderless breadboard. I put the resistor in cell b22 of the breadboard.

obstacle-avoiding-robot-8

Write the Python Program

Now we need to write some code so that the Raspberry Pi can use the HC-SR04 sensor.

Power up the Raspberry Pi.

Open a terminal window.

Navigate to the robot directory

cd robot

Create a new program in the Nano Text Editor, We will name it test_ultrasonic_sensor.py.

nano  test_ultrasonic_sensor.py

Here is the code.

import gpiozero  # GPIO Zero library
import time  # Time library

# File name: test_ultrasonic_sensor.py
# Author: Addison Sears-Collins
# Date created: 5/28/2019
# Python version: 3.5.3
# Description: Test the HC-SR04 ultrasonic
# distance sensor

# Assign the GPIO pin number to these variables.
TRIG = 23
ECHO = 24

# This sends out the signal to the object
trigger = gpiozero.OutputDevice(TRIG)

# This variable is an input that receives
# the signal reflected by the object
echo = gpiozero.DigitalInputDevice(ECHO)

# Send out a 10 microsecond pulse (ping)
# from the trasmitter (TRIG)
trigger.on()
time.sleep(0.00001)
trigger.off()

# Start timer as soon as the reflected sound
# wave is "heard" by the receiver (echo)
while echo.is_active == False:
	pulse_start = time.time() # Time of last LOW reading

# Stop the timer one the reflected sound wave
# is done pushing through the receiver (ECHO)
# Wave duration is proportional to duration of travel
# of the original pulse.
while echo.is_active == True:
	pulse_end = time.time() # Time of last HIGH reading

pulse_duration = pulse_end - pulse_start

# 34300 cm/s is the speed of sound
distance = 34300 * (pulse_duration/2)

# Round to two decimal places
round_distance = round(distance,2)

# Display the distance
print("Distance: ", round_distance)

You can place an object a certain distance away from the sensor to check its accuracy. Let’s do 12 cm, and see what distance reading we get printed out on the terminal window.

Run the program by typing:

python3 test_ultrasonic_sensor.py
obstacle-avoiding-robot-9

If you get a reasonable reading, move on to the next section below.

Mounting the HC-SR04 Ultrasonic Sensor

Mount the ultrasonic sensor to the front of the robot using VELCRO or permanent mounting tape.

Let’s program our robot so that it avoids any objects that are less than 15 cm away from it.

Since this program will be somewhat lengthy, I will open IDLE, an Integrated Development Environment that makes developing and debugging Python code a whole lot easier than using Nano, the Linux command line text editor.

Open VNC Viewer to access your Raspberry Pi.

Click the Raspberry Pi icon in the upper left part of the screen, and go to Programming -> Python 3 (IDLE).

Go to File → New File

Here is the code for the program.

import gpiozero  # GPIO Zero library
import time  # Time library

# File name: obstacle_avoiding_robot.py
# Author: Addison Sears-Collins
# Date created: 5/28/2019
# Python version: 3.5.3
# Description: A robot that avoids objects
# using an HC-SR04 ultrasonic distance sensor.

# Assign the GPIO pin number to these variables.
TRIG = 23
ECHO = 24

# This sends out the signal to the object
trigger = gpiozero.OutputDevice(TRIG)

# This variable is an input that receives
# the signal reflected by the object
echo = gpiozero.DigitalInputDevice(ECHO)

# Create a Robot object that is attached to 
# GPIO pins 17, 18, 22, and 27 of the 
# Raspberry Pi. These pins are inputs for the
# L293D motor controller.
# Objects have data and behavior that is 
# predefined by the Robot class (i.e. blueprint) 
# declared inside the GPIO Zero library.
# Change the order of the numbers inside
# the parentheses until you get the desired 
# behavior.
robot = gpiozero.Robot(left=(22,27), right=(17,18))

# Get the distance to the object
def get_distance(trigger, echo):

  # Send out a 10 microsecond pulse (ping)
  # from the trasmitter (TRIG)
  trigger.on()
  time.sleep(0.00001)
  trigger.off()

  # Start timer as soon as the reflected sound
  # wave is "heard" by the receiver (echo)
  while echo.is_active == False:
    pulse_start = time.time() # Time of last LOW reading

  # Stop the timer one the reflected sound wave
  # is done pushing through the receiver (ECHO)
  # Wave duration is proportional to duration of travel
  # of the original pulse.
  while echo.is_active == True:
    pulse_end = time.time() # Time of last HIGH reading

  pulse_duration = pulse_end - pulse_start

  # 34300 cm/s is the speed of sound
  distance = 34300 * (pulse_duration/2)

  # Round distance to two decimal places
  round_distance = round(distance,2)

  return(round_distance)

while True:
  distance_to_object = get_distance(trigger,echo)
  
  # Avoid objects less than 15 cm away
  if distance_to_object <= 15:
    robot.right() # Right for
    time.sleep(0.25) # 0.25 seconds
  else:
    robot.forward() # Forward for
    time.sleep(0.1) # 0.1 seconds
	

Save the file as obstacle_avoiding_robot.py.

Exit IDLE.

When you are ready to run the program, place your robot on the floor in a large open space.

Open a terminal window on the Raspberry Pi and type:

python3 obstacle_avoiding_robot.py

Congratulations! You have developed an autonomous robot that can avoid running into objects all on its own.

When you are done watching your robot avoid obstacles, stop running your program by typing CTRL-C.

Potential Improvements to the Robot

Right now, our robot has only one ultrasonic sensor. We could add more HC-SR04 sensors to make our robot more robust. This would require only small modifications to our original code.

How to Make a Remote Controlled Robot Using Raspberry Pi

In this post, I will show you how to make a remote-controlled robot using Raspberry Pi.

Video

Here is a video of what we will build in this tutorial.

Requirements

Here are the requirements:

  • Make a remote-controlled robot using Raspberry Pi and the Bluetooth-ready Nintendo Wii remote control.

You Will Need

The following components are used in this project. You will need:

Directions

Familiarizing Yourself with the L293D H-Bridge Motor Driver

You may recall in my post where we constructed the robot’s body that we had four input pins on the L293D motor controller (two for each motor). These input pins control the direction of the motors. For example, for the motor attached to the side of the motor controller connected to Inputs 3 and 4 of the L293D, we have:

Input 3Input 4Motor
HIGHHIGHOFF
HIGHLOWRotation in one direction
LOWHIGHRotation in the other direction
LOWLOWOFF

HIGH = voltage applied; LOW = voltage not applied

The four input pins set their signals from the Raspberry Pi’s GPIO (General Purpose Input/Output) pins.

To make all of this work, under the hood of the L293D motor controller, there is an H-bridge circuit. An H-bridge circuit enables a motor to be driven both forwards and backwards. It is a useful circuit in robotics.

Here is what an H bridge looks like:

h-bridge_diagramPNG

Image Source: Practical and Experimental Robotics

An H-bridge is made from electronically-controlled switches (rather than finger-controlled, such as the wall light switch in your bedroom) known as transistors.

Switch 1 and Switch 2 as well as Switch 3 and Switch 4 can never be closed at the same time. Otherwise, a bad short circuit would be created.

If Switch 3 and Switch 2 close at the same time, the motor spins in one direction. If Switch 1 and Switch 4 are closed (and Switch 2 and Switch 3 remain open), the motor will spin in the other direction because of the change in the path of the electric current.

Testing Your Robot to See If It Can Move

We need to verify that the robot can move properly. We will create a basic program in Python that will make the robot move in a box-shaped pattern three times.

First, power up the Raspberry Pi by connecting it to a wall outlet. Best practice is to connect your Raspberry Pi to a wall outlet (rather than the battery pack) when you are programming it.

Connect to your Raspberry Pi either through Putty or VNC Viewer. I’ll do VNC Viewer.

Open a terminal window. Move to the directory where you are saving your robotics projects.

remote-control-robot-1PNG

In my case, I type:

cd robot
remote-control-robot-2PNG

I then create a program in Python by typing:

nano box_bot.py
remote-control-robot-3PNG

I called the program box_bot.py because the robot will move in a shape that looks like a box.

Here is the Python code for the program:

import gpiozero  # GPIO Zero library
import time  # Time library

# File name: box_bot.py
# Author: Addison Sears-Collins
# Date created: 5/27/2019
# Python version: 3.5.3
# Description: Makes a wheeled robot move in a
# shape that looks like a box.

# Create a Robot object that is attached to 
# GPIO pins 17, 18, 22, and 27 of the 
# Raspberry Pi. These pins are inputs for the
# L293D motor controller.
# Objects have data and behavior that is 
# predefined by the Robot class (i.e. blueprint) 
# declared inside the GPIO Zero library.
# Change the order of the numbers inside
# the parentheses until you get the desired 
# behavior.
robot = gpiozero.Robot(left=(22,27), right=(17,18))

# Repeat this loop three times.
# Robot will make three boxes.
for i in range(3):
  robot.forward() # Move forward for
  time.sleep(1.0) # 1 second
  robot.right()   # Move right for 
  time.sleep(0.4) # 0.4 second
  robot.forward() # Move forward for
  time.sleep(1.0) # 1 second
  robot.right()   # Move right for
  time.sleep(0.4) # 0.4 second
  print("Box completed") # Box completed
remote-control-robot-4PNG-1

Exit the program editor by pressing CTRL-X. Make sure to save it, so press Y, then press ENTER to write (i.e. save) the file to the directory.

Shutdown your Raspberry Pi by typing:

sudo shutdown -h now
remote-control-robot-5PNG

Wait 5 seconds (or when you see the tiny green light on the Raspberry Pi is no longer illuminated), and unplug your Raspberry Pi from the wall.

Now, turn on the 4xAA battery holder.

Connect the Raspberry Pi battery pack to the Raspberry Pi.

Place the robot on a smooth floor in a wide open flat space, away from any object. You could also hold the body in your hand. Grip it by the back of the robot, being careful to keep your hands away from the wheels.

remote-control-robot-1-1
remote-control-robot-2-1

Run the box_bot.py program by opening a terminal window, going to your robot directory (or whatever directory your program is saved in) and typing:

python3 box_bot.py
remote-control-robot-6PNG

Your robot likely won’t make perfect boxes and might not even make fewer than three boxes (due to the weight the motors are pulling). That is fine. The key to this exercise is to just make sure the robot can go forwards and make a right turn.

If you need to stop the robot at any time, you can press CTRL-C.

If you see that your robot is not moving like it should, switch the order of the numbers inside the parentheses on this line:

robot = gpiozero.Robot(left=(22,27), right(17,18))

You can also edit the sleep time in your program. Also check to make sure the wiring is exactly like I indicated in my wheeled robot post.

If your robot isn’t moving at all, don’t worry, robots rarely work as they should the first time around. Keep tinkering with the code until you get the desired result. Take your time. No need to hurry.

Setting up the Nintendo Wii Remote Control

remote-control-robot-3-1

Power up your Raspberry Pi.

Open a terminal window. Type in the following command, and wait while it updates:

sudo apt-get update

The Raspberry Pi has a list of all the software to packages that are available for installation. If you don’t run sudo apt-get update before installing a new software package, you might end up with an outdated piece of software.

Install the Bluetooth package.

Bluetooth is a wireless technology that enables data to be sent back and forth over short distances using radio waves.

sudo apt-get install bluetooth
remote-control-robot-7PNG

Bluetooth might already be installed on your Raspberry Pi. If it is, you will get a message saying so.

Download the open source cwiid Python library that enables the Raspberry Pi to receive information from the remote control.

Type this in the terminal window:

git clone https://github.com/azzra/python3-wiimote
remote-control-robot-8PNG

Install four additional software packages:

sudo apt-get install bison flex automake libbluetooth-dev
remote-control-robot-9PNG

Type Y and press ENTER to continue. Wait while the software installs on your Raspberry Pi.

Move to the python3-wiimote directory.

cd python3-wiimote

Compile the python3-wiimote source code by typing in these commands, one by one in order:

remote-control-robot-10PNG
aclocal
autoconf
./configure
make 
sudo make install

The cwiid library is now installed on your Raspberry Pi.

Move to the robot directory:

cd
cd robot

Create a new Python program.

This program enables the Nintendo Wii remote control to make the robot move forwards (UP button), backwards (DOWN button), to the right (RIGHT button), to the left (LEFT button), and stop (B button).

nano wii_remote.py

Here is the Python code:

import gpiozero  # GPIO Zero library
import cwiid  # Wii library

# File name: wii_remote.py
# Author: Addison Sears-Collins
# Date created: 5/27/2019
# Python version: 3.5.3
# Description: This program enables the
# Nintendo Wii remote control to make a robot
# move forwards (UP), backwards (DOWN), to the
# right (RIGHT), to the left (LEFT), and stop
# (B).

# Create a Robot object that is attached to 
# GPIO pins 17, 18, 22, and 27 of the 
# Raspberry Pi. These pins are inputs for the
# L293D motor controller.
robot = gpiozero.Robot(left=(22,27), right=(17,18))

print("Press down and hold buttons 1 and 2 on",
      " the Nintendo Wii remote.")
print(" ")
print("Hold down until you see a success message.")
print("If unsuccessful, try again.")

# Create a Wiimote object and assign it to the
# variable named wii. Bluetooth handshake
# is performed between the Wii remote
# and the Raspberry Pi
wii = cwiid.Wiimote()

print("Wiimote is Ready!")

# Turn on Wiimote reporting to enable the Python
# code to receive input from the Wiimote.
wii.rpt_mode = cwiid.RPT_BTN

while True:
  # Save the value of the button that was pressed
  # into a variable named buttons
  buttons = wii.state["buttons"]

  # Bit-wise AND operation. Returns 1 in each
  # bit position for which buttons and
  # cwiid.BTN_XXXX are ones.
  if (buttons &amp; cwiid.BTN_LEFT):
      robot.left()
  if (buttons &amp; cwiid.BTN_RIGHT):
      robot.right()
  if (buttons &amp; cwiid.BTN_UP):
      robot.forward()
  if (buttons &amp; cwiid.BTN_DOWN):
      robot.backward()
  if (buttons &amp; cwiid.BTN_B):
      robot.stop() 

Press CTRL-X and save the program.

Run the Program

Make sure you Wii remote control has AA batteries in it.

Turn on the Wii remote control by pressing the Power button.

Power up your Raspberry Pi, and go to a terminal window. Open the robot directory:

cd robot

Run the wii_remote.py program.

python3 wii_remote.py

Press down the 1 and 2 buttons on the Wii remote at the same time and hold until you get a message that a Bluetooth connection has been made between the Raspberry Pi and the Wii remote control.

Bluetooth can be tricky to deal with on the Raspberry Pi, so if you fail to make the Bluetooth connection immediately, try again. Try to press and hold down 1 and 2 as soon as you run the code.

Once a connection has been made and assuming your robot is on the floor, press the arrow buttons on the remote control to drive your robot around.

The B button (trigger finger) enables you to stop the robot.

Typing CTRL – C in the terminal window stops the program.

Typing sudo shutdown – h now shuts off the Raspberry Pi. It is good practice to wait five seconds after you type this command before disconnecting the Rapberry Pi’s power.

To restart the Raspberry Pi, you just need to plug it into a power supply again.

Controlling the Speed of the Robot

You can control the speed of the robot by entering a number between 0 and 1 in the parentheses of the code I presented in the previous section. 0.50, for example, means 50% of full speed.

robot.forward(0.50)

GPIO pins in the Raspberry Pi can only be in two states, ON (+3.3 V) or OFF (0 V). By using a procedure known as pulse-width modulation (PWM), the GPIO pins switch ON and OFF so fast that the motor only perceives the average voltage. This technique enables the motor to have variable speeds as opposed to just two speeds (full speed and no speed at all).

We can also use the accelerometers of the Nintendo Wii remote to control the speed and direction of the robot. An accelerometer measures acceleration. And since the robot moves on only one plane, the x-y plane, we can move the Nintendo Wii remote forwards and backwards, and get the robot to respond to those actions.

For example, we can pitch the Wii remote forward (in the positive x-direction) to get the robot to move faster in the forward direction.

To make this happen, write the following program on your Raspberry Pi, and save it to your robot directory. You can name it wii_remote_variable.py.

import gpiozero  # GPIO Zero library
import cwiid  # Wii library

# File name: wii_remote_variable.py
# Author: Addison Sears-Collins
# Date created: 5/27/2019
# Python version: 3.5.3
# Description: This program enables the
# Nintendo Wii remote control to make a robot
# move forwards, backwards, to the
# right, to the left using the accelerometer.

# Create a Robot object that is attached to 
# GPIO pins 17, 18, 22, and 27 of the 
# Raspberry Pi. These pins are inputs for the
# L293D motor controller.
robot = gpiozero.Robot(left=(22,27), right=(17,18))

print("Press down and hold buttons 1 and 2 on",
      " the Nintendo Wii remote.")
print(" ")
print("Hold down until you see a success message.")
print("If unsuccessful, try again.")

# Create a Wiimote object and assign it to the
# variable named wii. Bluetooth handshake
# is performed between the Wii remote
# and the Raspberry Pi
wii = cwiid.Wiimote()

print("Wiimote is Ready!")

# Turn on Wiimote reporting to enable the Python
# code to receive input from the Wiimote.
wii.rpt_mode = cwiid.RPT_BTN | cwiid.RPT_ACC

while True:
  # Store the x and y values of the accelerometer
  # into variables and scale to a number between
  # 95 and 145 and then -25 and 25. Negative
  # numbers mean backwards movement.
  x = (wii.state["acc"][cwiid.X] - 95) - 25
  y = (wii.state["acc"][cwiid.Y] - 95) - 25

  # Error checking to keep values in the interval
  # -25 to 25
  if x < -25:
    x = -25
  if y < -25:
    y = -25
  if x > 25:
    x = 25
  if y > 25:
    y = 25

  # Calculate a forward and turn value between -1
  # and 1
  forward_value = (float(x)/50)*2
  turn_value = (float(y)/50)*2

  # This code ensures the robot is not impacted
  # by slight movements in the remote control
  if (turn_value < 0.3) and (turn_value > -0.3):
    robot.value = (forward_value, forward_value)
  else:
    robot.value = (-turn_value, turn_value)

By holding the remote control sideways, with the Nintendo Wii’s direction pad (the cross on the remote) on your left, you can tilt and pitch the robot to move like you want it to.

Run the program (make sure the Raspberry Pi is not plugged in to a wall outlet, but rather the battery pack) by typing in the following command while inside your Raspberry Pi’s robot directory:

python3 wii_remote_variable.py

That’s it! At this point, your robot should be responding to your actions on the remote control. Move it forward, pitch it backward, and observe how your robot responds!

Press CTRL-C at any time in the terminal to stop the program.

How to Make a Wheeled Robot Using Raspberry Pi

In this post, I will show you how to make a wheeled robot using Raspberry Pi as the “brain” of the robot.

Requirements

Here are the requirements:

  • Make a wheeled robot using Raspberry Pi as the “brain” of the robot.

You Will Need

wheeled-robot-rpi-9

The following components are used in this project. You will need:

Directions

Building the Robot’s “Body”

Let’s start by building the body of the robot.

Grab the two 8×16 Lego plates, and place them apart from each other.

wheeled-robot-rpi-10

Connect the two 8×16 Lego plates with the two 2×8 Lego plates. Place them across to form a bridge.

wheeled-robot-rpi-11

Connect the other two 2×8 Lego plates to the underside of the 8×16 Lego plates to form a sandwich.

wheeled-robot-rpi-12
wheeled-robot-rpi-13

Solder male-to-male wires to both terminals of each motor (they might already be soldered). If you need a quick tutorial on how to solder, check out this video, or just Google “How to Solder”:

wheeled-robot-rpi-14
wheeled-robot-rpi-15
wheeled-robot-rpi-16
wheeled-robot-rpi-17

Pop the tires on to the white rod on both motors. Give it a strong push in there. The wheels should be on the other side of the motor terminals.

wheeled-robot-rpi-18

Stick the motor wires up through the gap in the robot body.

Mount the motors with tire to the underside of the robot’s body so that the tires are exactly in the middle of the body. Make sure the tires are exactly parallel to each other.

wheeled-robot-rpi-19

Secure the motors to the body of the robot using your hot glue gun (100W setting). If you don’t want the motors to be permanently stuck to the robot’s body, you can use Velcro or Scotch permanent mounting tape.

wheeled-robot-rpi-20
wheeled-robot-rpi-21
wheeled-robot-rpi-22

Stabilize the robot by adding five 2×4 Lego bricks to both the front of the body.

wheeled-robot-rpi-23

Mount the Raspberry Pi battery pack to the underside of the robot, slightly off-center of the body, using Velcro or Scotch permanent mounting tape. The small cable of the battery pack should face the front of the car.

Mount the 4xAA battery holder to the battery pack. Use Velcro or Scotch permanent mounting tape to secure it into place. Make sure that you are still able to reach the ON/OFF switch of the 4xAA battery pack.

wheeled-robot-rpi-24

Feed the negative (black) and positive (red) leads through the gap in the robot body.

wheeled-robot-rpi-25

Strip 1-2 cm of insulation off the end of the battery pack wires using the wire strippers.

Wrap the red and black wires of the battery pack around male-to-male jumper wires.

Solder the wires together so that they remain in place.

Apply black electrical tape around the connection once it has cooled.

wheeled-robot-rpi-26
wheeled-robot-rpi-27

Giving the Robot a “Brain” by Adding the Raspberry Pi

Our robot needs to have a brain. Otherwise, it is just a bunch of plastic parts that can’t do anything useful. In this project, we’ll use the Raspberry Pi as the robot’s brain.

Grab some Velcro and stick the Raspberry Pi on top of the front end of the robot’s body. Make sure it looks exactly like the image below.

wheeled-robot-rpi-28

Grab some Velcro and stick the 400-point solderless breadboard on the back end of the robot, opposite to where the Raspberry Pi is located. You could also peel off the back of the sticker on the solderless breadboard.

wheeled-robot-rpi-29
wheeled-robot-rpi-30
wheeled-robot-rpi-32

Giving the Robot a “Nervous System”

Now that the robot has a brain (Raspberry Pi mounted on the front of the robot) and a body, it needs a “nervous system,” communication lines that enable the brain to transmit signals to and from different parts of its body. In the context of this project, those communication lines are the wires that we need to connect between the different parts of the robot we’re building.

Setting up the Breadboard

Sink the 16 pins of the L293D motor controller down into the holes of the solderless breadboard so that the controller straddles the gap that runs the length of the breadboard.

Here is the diagram of the L293D.

L293D-with-motors

Put pin 1 (the pin just to the left of the half-circle notch in the L293D into pin e3 of the solderless breadboard. You’ll have to bend the legs a bit of the L293D to get it to sink down. Note: Ignore the AAA on the batteries below. They are actually AA.

pin1
wheeled-robot-rpi-33
wheeled-robot-rpi-34

Here is the pin diagram of the Raspberry Pi.

rpi_pin_diagram_2

Power up one set of positive/negative rails of the solderless breadboard:

  • 5V pin (pin 4) of the Raspberry Pi connects to the red (positive) power rail of the breadboard using a male-to-female jumper wire.
  • Connect the Ground pin (pin 6) of the Raspberry Pi to the blue (negative) power rail of the solderless breadboard.
pin2
wheeled-robot-rpi-36

Power up the other set of positive/negative rails of the solderless breadboard:

  • Connect the blue (negative) power rail to the other blue (negative) power rail using a male-to-male jumper wire.
  • Put the red positive lead of the 4xAA battery holder into a hole on the unused red (positive) rail of the solderless breadboard.
  • Put the black lead of the 4xAA battery holder into the blue (negative) rail of the solderless breadboard.
pin3
wheeled-robot-rpi-38
wheeled-robot-rpi-39

Connecting the 16 Pins of the L293D

Here is the diagram of the L293D motor controller.

L293D-with-motors-1

The L293D motor controller needs a power supply:

  • Connect pin 16 (vss 1) to the 5V red (positive) power rail of the solderless breadboard, the rail that is powered by the Raspberry Pi. This pin is the one that will provide power to the L293D motor controller. You can stick a male-to-male pin in g3 of the solderless breadboard and connect that to the red rail.
  • Connect all the GND pins of the L293D (pins 4, 5, 12, and 13) to the closest blue (ground) power rail of the solderless breadboard.
pin4

The motors need a power supply:

  • Connect a male-to-male wire from the red 6V power rail (the rail connected to the 4xAA battery pack) to pin 8 (vcc) of the L293D integrated chip.
pin5

In order for the motors to accept commands from the Raspberry Pi, we need to connect both enable pins (pins 1 and 9) of the L293D to red (positive) 5V power rails. Here are the steps:

  • Take a male-to-male jumper wire and make a connection between pin 1 of the L293D and the the red (positive) rail of the breadboard (the one connected to the 5V pin of the Raspberry Pi).
  • Take a male-to-male jumper wire and make a connection between pin 9 of the L293D and the the red (positive) rail of the breadboard (the one connected to the 5V pin of the Raspberry Pi).
pin6


We need to connect the motors to the output pins of the L293D.

  • Motor 1 (Right Motor)
    • Connect one of the leads to Output 1 (pin 3) of the L293D.
    • Connect the other lead to Output 2 (pin 6).
  • Motor 2 (Left Motor)
    • Connect one of the leads to Output 3 (pin 11) of the L293D.
    • Connect the other lead to Output 4 (pin 14).
pin8

Now, we need to connect the input pins of the L293D to the Raspberry Pi. There are two input pins for each motor.

  • Connect Pin 11 (GPIO 17) of the Raspberry Pi to pin 2 (Input 1) of the L293D.
  • Connect Pin 12 (GPIO 18) of the Raspberry Pi to pin 7 (Input 2) of the L293D.
  • Connect Pin 13 (GPIO 27) of the Raspberry Pi to pin 10 (Input 3) of the L293D.
  • Connect Pin 15 (GPIO 22) of the Raspberry Pi to Pin 15 (Input 4) of the L293D.
pin7

Insert fresh AA batteries into the 4xAA battery holder.

wheeled-robot-rpi-35

Whew! That was a lot of work. If you made it this far, congratulations! You have completed construction of your Raspberry Pi wheeled robot.

In order for it to do something useful (e.g. move from one place to another), we need to program its brain, the Raspberry Pi. We will tackle this in the next post.