6. Embedded Programming

The primary goal of this week was to program microcontrollers and establish connections with other devices.

Microcontrollers are integrated electronic devices used to control and perform automated tasks in various systems. These tiny integrated circuits contain a central processing unit (CPU), memory, input/output ports, and other components needed to perform specific functions. The automotive industry widely employs microcontrollers to control braking systems, fuel injection, and vehicle entertainment systems. Microcontrollers are utilized in household appliances like washing machines, ovens, and air conditioners to manage control and monitoring functions. In consumer electronics, they are found in devices such as cell phones, digital cameras, and electronic toys to control various operations and enhance user interactivity.

Research

At first, I intended to undertake a TinyML project, despite my lack of prior experience in the field. However, I encountered several obstacles, which I will detail below, in attempting to explain the process of running a neural network on the edge and the challenges involved.

TinyML, short for Tiny Machine Learning, is the implementation of machine learning models in devices with limited resources, such as microcontrollers. This technology enables machine learning algorithms to run directly at the edge, without the need for cloud connections, allowing for optimized local data processing and inference. TinyML has various applications in fields ranging from healthcare to industry. TinyML has various applications in the medical field, such as real-time monitoring of vital signs using portable devices. It also helps optimize manufacturing processes and improve energy efficiency in industries. The advantages of TinyML include local data processing, reducing latency, and improving privacy by avoiding external dependencies. Additionally, it promotes energy efficiency and extends battery life by running on low-power devices. However, the limitations of embedded devices restrict model complexity due to memory and processing constraints.

In the field of machine learning, there exists a subfield known as deep learning. This subfield involves the use of neural networks with multiple layers to learn data representations in a hierarchical manner. By using this approach, complex tasks such as speech recognition, computer vision, and natural language processing can be tackled with higher levels of accuracy and generalization than traditional machine learning methods. The use of deep neural networks enables the extraction of meaningful features and the resolution of complex problems in various application areas.

A neural network is a computational model inspired by the functioning of the human brain. It is composed of a set of interconnected units called artificial neurons, which are organized in layers. These neurons process and transmit information through weighted connections. Neural networks are utilized in artificial intelligence and machine learning to execute tasks such as pattern recognition, classification, and regression by adjusting connection weights to learn from input data and generate desired outputs.

Artificial Neural Network

Image taken for: https://es.linkedin.com/pulse/y-las-redes-neuronales-d%C3%B3nde-est%C3%A1n-lisandro-m-carnielli

In the context of neural networks, connection weights are essential parameters that determine the strength of influence of one neuron over another in the transmission of information. Each connection between neurons is associated with a specific weight that indicates the relative importance of the signal being transmitted. During the neural network training process, optimization algorithms such as gradient descent are used to adjust the weights. This is done to minimize the loss function and improve the model's predictive capability. By modifying the connection weights, the neural network can accurately learn complex patterns in the input data and perform tasks such as classification, pattern recognition, or prediction. Properly tuning the connection weights is crucial for the effective functioning of a neural network in solving machine learning problems.

The process of TinyML can be summarized in the following image:

Artificial Neural Network

The initial stages of neural network training typically involve utilizing publicly available databases. After determining the appropriate neural network for the task, TensorFlow offers the essential resources for its implementation. This platform streamlines the creation and refinement of deep learning models, enabling users to work productively on their AI projects.

For the task of developing a digit image classifier, the literature recommends using a convolutional neural network with two hidden layers, two convolutional layers, and two clustering layers. This network design efficiently captures the relevant features of the images and accurately classifies the digits present in them.

The final four steps are specific to TinyML and typically involve the use of the TensorFlow Lite tool.

The third step, which involves using the functions provided by the tool, is relatively straightforward. However, my progress was limited due to my incipient knowledge of the subject. Nevertheless, reaching this point represents a significant first step in my learning and exploration of the field of TinyML.

The optimization of machine learning models for implementation at the edge is a developing field of technology. Currently, there is no clear consensus on the best techniques to employ in each situation, making it an active area of research. This optimization process is crucial for adapting machine learning models to resource-constrained devices and improving their efficiency in edge computing environments.

Additional information about the subject can be found on the Ringa Tech and Real Time LAB YouTube channels. And the advances of this initiator project can be found here.

Project completed

At this stage, I attempted to regulate the speed of a servomotor by utilizing a rotary encoder in conjunction with the previously designed board. Regrettably, the board experienced a malfunction that I was unable to identify despite my best efforts. This setback hindered my ability to accurately and effectively control the servomotor with this board. For this reason, I decided to replace the board with a Raspberry Pi Pico RP2040. See the week 8 documentation for an advanced version of this assignment, this time using a board I designed and built.

Servo board

A rotary encoder, also referred to as a shaft encoder or pulse generator, is an electromechanical device utilized to convert the angular position of a shaft to a digital code. There exist two encoder types, yet for the purposes of this project, we will employ a relative or incremental rotary encoder, which uses a very small disk attached to a small shaft marked with a large number of lines on the radial side, similar to the spokes of a wheel. The optical switch, analogous to a photodiode, generates an electrical pulse each time one of the lines traverses its field of view. An electronic control circuit counts the pulses to ascertain the angle at which the shaft rotates. In its most basic configuration, this system cannot measure the absolute angle of the shaft. It can only quantify the change in angle in relation to a reference point, such as the shaft position at the time the power was initiated. In the event that absolute position is required, a second sensor can be added that detects the shaft passing through its zero position. However, this system presents a second issue: it cannot determine the direction of shaft rotation. To address this, the optical sensor must be augmented with two sensors placed at different angles around the shaft. The direction of rotation can then be deduced by analysing the order in which the two sensors detect each radial line. This type of encoder is known as a quadrature encoder.

Codificadores-incrementales

Image taken for: https://www.celeramotion.com/inductive-sensors/es/asistencia/documentacion-tecnica/codificadores-absolutos-y-codificadores-incrementales/.


Initially, I simulated two variants of the code using Wokwi - one in the Arduino IDE language and the other using MicroPython.

Simulation1 Simulation2

The sample codes below:

C++ code


          #include <RotaryEncoder.h>
          RotaryEncoder encoder(1, 2);  // (DT, CLK)
          
          #include "Servo.h"
          Servo servo;
          
          #define STEPS  10
          #define POSMIN 0
          #define POSMAX 180
          
          int lastPos, newPos;
          
          void setup() {
             servo.attach(3);
             Serial.begin(9600);
             encoder.setPosition(10 / STEPS);
          }
          
          void loop() {
             encoder.tick();
             newPos = encoder.getPosition() * STEPS;
          
             if (newPos < POSMIN) { 
             encoder.setPosition(POSMIN / STEPS); 
             newPos = POSMIN;
             }
             else if (newPos > POSMAX) { 
             encoder.setPosition(POSMAX / STEPS); 
             newPos = POSMAX; 
             }
          
             if (lastPos != newPos) {
                Serial.println(newPos);
                lastPos = newPos;
             }
          
             servo.write(lastPos);
          }
        



MicroPython code


          from machine import Pin
          from rotary_encoder import RotaryEncoder
          from servo import Servo
          
          STEPS = 10
          POSMIN = 0
          POSMAX = 180
          
          lastPos = 0
          newPos = 0
          
          servo = Servo(3)
          encoder = RotaryEncoder(1, 2)
          
          def setup():
              global lastPos
              servo.attach(3)
              encoder.setPosition(10 / STEPS)
              print("Setup completed")
          
          def loop():
              global lastPos, newPos
              encoder.tick()
              newPos = encoder.getPosition() * STEPS
          
              if newPos < POSMIN:
                  encoder.setPosition(POSMIN / STEPS)
                  newPos = POSMIN
              elif newPos > POSMAX:
                  encoder.setPosition(POSMAX / STEPS)
                  newPos = POSMAX
          
              if lastPos != newPos:
                  print(newPos)
                  lastPos = newPos
          
              servo.write(lastPos)
        

The result is shown in the following video:

In fulfillment of the group assignment, we undertook an investigation of the datasheet of our microcontrollers, with the objective of comparing their respective performance and development workflows with those of other architectures. The results of this endeavor are presented here.