AI+Microcontrollers: A practical example with Arduino
Tags: machinelearning, programming, computerscience, arduino
Published: 2020-12-29 20:43:53

# **Introduction**
Artificial Intelligence has become more and more important in now-days. In this case, I'll show you how to implement a Perceptron inside a microcontroller. Albeit I'm doing this for an Arduino Uno, you can follow these step to do it in any other microcontroller, like ATmega, PIC, STM, and so on.

# **What is a Microcontroller (MCU)?**
You can see a Microcontroller Unit or MCU as a small computer embedded in an integrated circuit (IC) chip. A Microcontroller could have one or more Central Processing Unit (CPU), a system clock, memory, peripherals (inputs/outputs), and so on. While computers can execute multiple tasks at the same time, MCUs are typically dedicated to a specific task.

<figure>
  <img src="https://www.tutorialspoint.com/microprocessor/images/8051_architecture.jpg" alt="Blocks Diagram of the 8051 microcontroller">
  <figcaption>Blocks Diagram of the 8051 microcontroller.</figcaption>
</figure>

<figure>
  <img src="https://microcontrollerslab.com/wp-content/uploads/2015/06/8051-microcontroller-pinout-diagram.jpeg" alt="Pin Diagram of the 8051 microcontroller">
  <figcaption>Pin Diagram of the 8051 microcontroller.</figcaption>
</figure>

<figure>
  <img src="https://5.imimg.com/data5/OG/KA/MY-58765457/318469-500x500.png" alt="8051 microcontroller">
  <figcaption>8051 microcontroller.</figcaption>
</figure>


# **What is a Perceptron?**
The Perceptron Algorithm was created in 1957 by Frank Rosenblatt. This was the forerunner of modern neural networks, our grandpa or granny.

The Perceptron is based on the mathematical abstraction of a neuron at the biological level. We've got a serial of inputs <math xmlns="http://www.w3.org/1998/Math/MathML">m&#772; = [m<sub>0</sub> m<sub>1</sub> ... m<sub>D</sub>]<sup>T</sup></math> which are linearly combined by a vector of weights (or synaptic bonds) <math xmlns="http://www.w3.org/1998/Math/MathML">w&#772; = [w<sub>0</sub> w<sub>1</sub> ... w<sub>D</sub>]<sup>T</sup></math> in the network function:

{% katex %}
  \gamma (m) = \sum_{i=0}^D m_i w_i = \vec{w}^T \vec{m}
{% endkatex %}

A neuron is excited according to an activation function f(&gamma;), which receives as input the result of the network function &gamma;, and can correspond, for example, to the signum function:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9ydz5j9eb5b736d98xaw.png)

In this case, our simple Perceptron will solve a classification problem for K=2, where K is the number of classes. The classification problem aims to assign a label (class) to an input of size n. You can view this as if someone has given you pictures of dogs, cats, rabbits, and so on. and you would have the task of assigning a class to each photo.

The vector of weights will be created with random values and will be updated if the classification was incorrect. I won't explain how this all works in-depth as it would be out of my goal, but you can read the original paper: [The perceptron: a probabilistic model for information storage and organization in the brain by Frank Rosenblatt](http://homepages.math.uic.edu/~lreyzin/papers/rosenblatt58.pdf) or this [post](https://dev.to/smakosh/build-your-perceptron-neural-net-from-scratch-4co) written by [smakosh](https://dev.to/smakosh).

{% post smakosh/build-your-perceptron-neural-net-from-scratch-4co %}


# **Perceptron Code**
```Cpp
class Perceptron
{
public:
  // Inputs vector
  float* inputs;

  // Perceptron constructor
  // int ninputs - number of inputs
  Perceptron(int ninputs)
  {
    // Default learning rate
    alpha = 0.001;
    // Set the number of inputs
    n = ninputs + 1;

    // Asking for memory
    inputs = new float[n];
    weights = new float[n];

    // Bias
    inputs[n - 1] = 1;
    // Start with random weights
    randomize();
  }

  // Random weights
  void randomize()
  {
    for (int i = 0; i < n; i++)
    {
      weights[i] = (float) random(-1000, 1000) / 1000.0;
    }
  }

  // Training function
  void train(int T, float predicted)
  {
    float error = T - predicted;

    // Update weights
    for (int i = 0; i < n; i++)
    {
      weights[i] += alpha*error*inputs[i];
    }
  }

  // Forward function
  int feed_forward()
  {
    float sum = 0.0;

    for (int i = 0; i < n; i++)
    {
      sum += inputs[i]*weights[i];
    }

    return activate(sum);
  }

private:
  // Random weights vector
  float* weights;
  // Inputs number
  int n;
  // Learning rate
  float alpha;

  // Activation function
  int activate(float sum)
  {
    // Signum function
    return (sum >= 0) ? 1:-1;
  }
};
```

# **Circuit Schema**
In this example, we will train our model to classify a distance into near (-1) and far (1). This distance will be taken by the ultrasonic sensor HC-SR04 and we will say that any distance that is greater than 150cm will be far and for values less than or equal to 150cm we will say that it is near. To indicate this, when the distance is far we will turn on the red LED and when it is near the green LED.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rr9uue3ca87xvtp6an6b.png)


# **Arduino Code**
Here we've got the code. We have trained our Perceptron before to predict to see the results quickly. You can also train the model by adding a button to tell it when has made a wrong prediction and needs to learn, so it can learn what is happening.

```Cpp
#include "perceptron.h"

// Constant values
const int ECHO_PIN = 5;
const int TRIGGER_PIN = 6;
const int GREEN_LED_PIN = 12;
const int RED_LED_PIN = 11;

// Vars
float distance;
long time;

// Our perceptron
Perceptron perceptron(1);

// Train the perceptron
void train()
{
  int distance_step = 5;

  // Train for 160 epochs
  for (int epoch = 0; epoch < 160; epoch++)
  {
    for (int d = 0; d < 340; d += distance_step)
    {
      // Set the input in the model
      perceptron.inputs[0] = d;

      // Make a prediction
  	  int predicted = perceptron.feed_forward();

      if ((d <= 150 && predicted == -1) || (d > 150 && predicted == 1))
      {
        perceptron.train(-predicted, predicted);
      }
    }
  }
}

void setup()
{
  Serial.begin(9600);

  // Set the pins
  pinMode(TRIGGER_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(GREEN_LED_PIN, OUTPUT);
  pinMode(RED_LED_PIN, OUTPUT);

  // Train the model before 
  train();
}
 
void loop()
{
  // Send a pulse to activate the sensor
  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIGGER_PIN, LOW);

  // Measure response pulse
  time = pulseIn(ECHO_PIN, HIGH)/2; 
  
  // Calculate the distance in cm
  // d = v * t
  // Speed of sound = 343m/s
  // Time is microseconds
  distance = float(time*0.0343);
  Serial.print("Distance: ");
  Serial.println(distance);
  
  // Set the input in the model
  perceptron.inputs[0] = distance;

  // Classify
  int predicted = perceptron.feed_forward();

  Serial.print("Predicted: ");
  Serial.println(predicted);
  
  if (predicted == -1)
  {
    digitalWrite(GREEN_LED_PIN, LOW);
    digitalWrite(RED_LED_PIN, HIGH);
  }
  else
  {
    digitalWrite(GREEN_LED_PIN, HIGH);
    digitalWrite(RED_LED_PIN, LOW);
  }

  delay(1000);
}
```


# **Training Results**
We can see how the green LED lights up with a distance of 74.7cm, and the red LED with a distance of 244.9cm.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lvvl4nwpqe0006h0tj9z.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f259m7q3vet2ngf94dow.png)

<iframe width="725" height="453" src="https://www.tinkercad.com/embed/lPBHZkEKKOT?editbtn=1" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>

You can also play with the model in [TinkerCAD](https://www.tinkercad.com/things/lPBHZkEKKOT).


# **Final thoughts**
The Perceptron convergence theorem states that the vector of weights converges after a finite number of iterations, even when a change in such vector involves one or more new errors in samples that were correctly classified in previous iterations. This only if the set of samples to be classified is linearly separable. In this way, a simple Perceptron is not enough for complex problems, this is where I introduce the Multilayer Perceptron, but this is a topic for another post.