Embedded Programming

Week 4


The objective of this week was to browse the datasheet for a microcontroller to understand its technical specifications and pinout. The assignment required writing and testing a program for a microcontroller to interact with local input and/or output devices and communicate through remote wired or wireless connections.

For this assignment, I worked with the Raspberry Pi Pico 2, taking advantage of its extensive GPIO flexibility. However, I am considering to use the XIAO ESP32-C3 for my final project. Its compact and practical form factor is perfect for my design's portability, and its integrated Wi-Fi and Bluetooth capabilities offer the specific connectivity features I intend to use for my final application.

Group Assignment

Check here the group assignment for this week for more information.

DataSheet

To ensure the correct integration of the components and to understand the electrical limits of each board, I consulted the official technical documentation. Reviewing the Datasheets was essential to identify the pinout mapping, voltage tolerances, and the specific capabilities of the RP2350 and ESP32-C3 architectures.

Here are the datasheets I used:

Raspberry Pi Pico 2

View Datasheet

XIAO ESP32-C3

View Datasheet

Microcontroller

Feature Raspberry Pi Pico 2 XIAO ESP32-C3
Microcontroller RP2350 (Dual-core) ESP32-C3 (Single-core)
Architecture ARM Cortex-M33 / RISC-V RISC-V
Clock Speed 150 MHz 160 MHz
SRAM Memory 520 KB 400 KB
Flash Memory 4 MB 4 MB
GPIO Pins 26 multi-function pins 11 digital pins
Analog Inputs 4 channels (12-bit) 4 channels (12-bit)
Communication 2 × I2C, 2 × SPI, 2 × UART 1 × I2C, 1 × SPI, 1 × UART
Wireless None Wi-Fi 2.4GHz & Bluetooth 5.0
Languages C/C++, MicroPython, CircuitPython C/C++ (Arduino), MicroPython, ESP-IDF
Operating Voltage 3.3V 3.3V
Dimensions 21 mm x 51 mm 17.5 mm x 21 mm
Fig 1. Raspberry Pi Pico 2 Pinout
Fig 2. XIAO ESP32-C3 Pinout

GPIO Overview

GPIO (General Purpose Input/Output) are the physical pins on the microcontroller that can be programmed to either be an Input (reading data) or an Output (sending signals) to interact with external components.

Code Language

The microcontroller supports multiple programming languages, including C/C++, MicroPython, and CircuitPython. These languages allow developers to write code that can be compiled or interpreted to control the microcontroller's behavior.

MicroPython Programming Fundamentals

To program the Raspberry Pi Pico 2 , I used MicroPython. Below is a quick reference guide to the syntax and logic of MicroPython:

1. Basic Syntax & Output

Comments are essential for documenting the workflow, and the print() function is used to display data in the Serial Monitor for debugging.

  • #: Used for single-line comments.
  • ''' ''': Used for multi-line comments or documentation strings.
  • print ("Text"): Outputs strings.
  • print ('a'): Outputs one character.
  • print (variable): Outputs the value of a variable.

2. Variables & Data Types

Variables are used to store information that the program can process later:

  • String: Sequence of characters (e.g., "Hello Fab").
  • Int: Whole numbers (e.g., 10).
  • Float: Decimal numbers (e.g., 3.14).
  • Boolean: Logical values, either True or False.

3. Arithmetic Operations

Basic operators used for calculations:

  • Sum (+), Subtraction (-), Multiplication (*), and Division (/).
  • Modulo (%): Returns the remainder of a division, useful for timing cycles or parity checks.

4. Control Structures (Conditionals)

Conditionals allow the microcontroller to make decisions based on sensor inputs or states:

  • if / elif / else: Executes specific blocks of code depending on whether a condition is met.
  • Logic Operators: And (all conditions must be true) and Or (at least one condition must be true).

5. Loops

Loops are used to repeat tasks, such as reading a sensor continuously:

  • While: Repeats a block of code as long as a condition is true (commonly used as while True: for the main program loop).
  • For i in range(x): Repeats the code for a specific number of iterations.

6. Functions & Libraries

To maintain clean and modular code, we use functions and external modules:

  • Functions (def): Encapsulates multiple lines of code into a single command to avoid redundancy.
  • Imports: Using import time or import machine grants access to hardware-specific tools.

7. Error Handling (Try / Except)

Embedded systems can encounter unexpected hardware errors (e.g., a disconnected sensor). Error handling prevents the entire program from crashing:

  • Try: The program attempts to execute a block of code.
  • Except: If an error occurs within the "Try" block, the program jumps to this section instead of stopping, allowing for a safe recovery.

Arduino Programming Fundamentals (C++)

To program the XIAO ESP32-C3, I used Arduino. Arduino is based on C++, which is a compiled language that requires specific structures and data types. Below is a reference guide to the fundamental concepts:

1. Basic Structure (setup & loop)

Every Arduino program (sketch) must have two main functions:

  • void setup() { }: Runs once when the board starts. Used for initialization (pin modes, serial start).
  • void loop() { }: Runs repeatedly. This is where the main logic of the controller happens.

2. Syntax Rules

Unlike Python, C++ is very strict about punctuation:

  • //: Single-line comments.
  • /* */: Multi-line comments.
  • Semicolons (;): Every instruction must end with a semicolon.
  • Curly Braces ({ }): Used to define the beginning and end of code blocks.

3. Variables & Strongly Typed Data

In Arduino, you must declare the type of data a variable will hold:

  • int: Integers (e.g., int ledPin = 10;).
  • float: Decimal numbers (e.g., 3.14).
  • bool: Boolean (true or false).
  • char: Single characters.
  • String: Text sequences.

4. Conditionals & Logic

Decisions are made using if-statements with parentheses:

  • if / else if / else: Basic decision-making structure.
  • Comparison: == (equal to), != (not equal), > (greater), < (less).
  • Logic: && (AND), || (OR), ! (NOT).

5. Loops

Used to repeat actions within the code:

  • for (int i=0; i < 5; i++) { }: Repeats code a fixed number of times.
  • while (condition) { }: Repeats code as long as a condition is true.

6. Built-in Functions for Hardware

Arduino provides specific commands to interact with the GPIOs:

  • pinMode(pin, mode): Configures a pin as INPUT or OUTPUT.
  • digitalWrite(pin, value): Sends a HIGH (3.3V) or LOW (0V) signal.
  • analogRead(pin): Reads voltage from an analog pin (0-4095 on ESP32-C3).
  • Serial.print(): Sends data to the computer for debugging.

7. Libraries

External code can be included to add functionality:

  • #include <Wire.h>: Used for I2C communication.
  • #include <WiFi.h>: Used for the wireless capabilities of the ESP32-C3.

Simulation

To test the code without needing the physical hardware, I used the online simulator Wokwi. This platform allows you to create virtual circuits and write code to see how it would behave in real life. It was a great tool for debugging and understanding the logic of my programs before uploading them to the actual microcontroller. I used it to simulate the same circuit on both the Raspberry Pi Pico 2 and the XIAO ESP32-C3, in the languages MicroPython and Arduino.

For my final project, I plan to use two vibration motors and two electret microphones. Since these specific components are not available in the simulator, I substituted the microphones with linear potentiometers to simulate variable sound levels, and the vibration motors with buzzers to test the response logic. I also added two LEDs for better visual feedback. The system is designed so that when one "microphone" receives a stronger signal, the corresponding "vibration motor" on that side activates, allowing me to verify my directional detection logic.

Quick Guide: How to use the Wokwi Simulator

  1. Open the Wokwi simulator.
  2. In the main menu you can select the microcontroller.
  3. Fig 3. Selecting the microcontroller
  4. Select the language to program.
  5. Fig 4. Selecting the programming language
  6. Also you can choose to start with a pre-built circuit template.
  7. Fig 5. Selecting a template
  8. You will find the main editor window in the left panel where you can write your code and the circuit simulation on the right panel.
  9. Fig 6. Main editor window
  10. In the simulation menu you will find three buttons. The first one is the play button to start the simulation, the second one is to add components, and the third one edit the simulation area and the position of the components.
  11. Fig 7. Simulation menu buttons
  12. To open the components gallery, click the blue button
  13. Fig 8. Components gallery
  14. Choose the component you want to add to your circuit.
  15. The component will be added to your circuit and you can drag it to a desired position. To wire components, click on the component wire and then click on the pins you want to connect.
  16. Fig 9. Wiring components
  17. Write your code in the main editor window and now you can run the simulation by clicking the play button in the simulation menu.

Simulation circuit

Here are the simulations I created for both microcontrollers. The first one is the simulation for the Raspberry Pi Pico 2 programmed in MicroPython, and the second one is the simulation for the XIAO ESP32-C3 programmed in Arduino.

Raspberry Pi Pico 2

Fig 10. Raspberry Pi Pico 2 circuit

MicroPython Code

 
                # Import hardware control libraries
                from machine import Pin, ADC, PWM
                import time

                # Initialize ADC for the potentiometers on GPIO 27 and 28
                pot1 = ADC(Pin(27))
                pot2 = ADC(Pin(28))

                # Initialize Digital Output pins for the LEDs on GPIO 0 and 1
                led1 = Pin(0, Pin.OUT)
                led2 = Pin(1, Pin.OUT)

                # Initialize PWM for buzzers on GPIO 10 and 11 to control sound
                buzzer1 = PWM(Pin(10))
                buzzer2 = PWM(Pin(11))

                # Function to turn off all outputs (LEDs and sound)
                def silence():
                    led1.value(0)          # Turn off LED 1
                    led2.value(0)          # Turn off LED 2
                    buzzer1.duty_u16(0)    # Set buzzer 1 volume to 0
                    buzzer2.duty_u16(0)    # Set buzzer 2 volume to 0

                # Main execution loop
                while True:
                    # Read analog values from potentiometers (range 0-65535)
                    val1 = pot1.read_u16()
                    val2 = pot2.read_u16()
                    
                    # Print values to the Serial Monitor
                    print(f"Pot1: {val1} | Pot2: {val2}")

                    # Condition 1: If both potentiometers are below a certain threshold, stay silent
                    if val1 < 20000 and val2 < 20000:
                        silence()
                    
                    # Condition 2: If Pot 1 is significantly higher than Pot 2
                    elif val1 > (val2 + 1000):
                        led1.value(1)           # Turn on LED 1
                        led2.value(0)           # Turn off LED 2
                        buzzer1.freq(1000)      # Set buzzer 1 frequency to 1000Hz
                        buzzer1.duty_u16(32768) # Play sound
                        buzzer2.duty_u16(0)     # Mute buzzer 2
                        
                    # Condition 3: If Pot 2 is significantly higher than Pot 1
                    elif val2 > (val1 + 1000):
                        led1.value(0)           # Turn off LED 1
                        led2.value(1)           # Turn on LED 2
                        buzzer2.freq(1500)      # Set buzzer 2 frequency to 1500Hz
                        buzzer2.duty_u16(32768) # Play sound
                        buzzer1.duty_u16(0)     # Mute buzzer 1
                        
                    # If values are too close, turn everything off
                    else:
                        silence()

                    time.sleep(0.1)
            
            

XIAO ESP32-C3

Fig 11. XIAO ESP32-C3 circuit

Arduino Code

 

            #include 

            const int pot1Pin = 3;  // Analog input from Potentiometer 1
            const int pot2Pin = 2;  // Analog input from Potentiometer 2
            const int led1 = 10;    // Digital output for LED 1
            const int led2 = 9;     // Digital output for LED 2
            const int buzzer1 = 5;  // PWM output for Buzzer 1
            const int buzzer2 = 4;  // PWM output for Buzzer 2

            // Thresholds adjusted for 12-bit resolution
            const int threshold = 500; // Minimum value to trigger activation
            const int margin = 200;    // Difference required to detect a clear "dominant" side

            // Function to reset all outputs to an idle state
            void silence() {
            digitalWrite(led1, LOW);
            digitalWrite(led2, LOW);
            noTone(buzzer1);
            noTone(buzzer2);
            }

            void setup() {
            Serial.begin(115200); // Initialize serial communication at 115200 baud
            
            // Configure pin modes
            pinMode(led1, OUTPUT);
            pinMode(led2, OUTPUT);
            pinMode(buzzer1, OUTPUT);
            pinMode(buzzer2, OUTPUT);
            
            analogReadResolution(12);
            
            }

            void loop() {
            // Read raw analog values from the sensors
            int val1 = analogRead(pot1Pin);
            int val2 = analogRead(pot2Pin);

            // Print values to the Serial Monitor for real-time debugging
            Serial.print("Pot1: "); Serial.print(val1);
            Serial.print(" | Pot2: "); Serial.println(val2);

            // Case 1: If both inputs are below the threshold, keep outputs OFF
            if (val1 < threshold && val2 < threshold) {
                silence();
            } 
            // Case 2: If Pot 1 is higher than Pot 2 by at least the margin
            else if (val1 > (val2 + margin)) {
                digitalWrite(led1, HIGH);
                digitalWrite(led2, LOW);
                tone(buzzer1, 1000); 
                noTone(buzzer2);     // Ensure the other buzzer is silent
            } 
            // Case 3: If Pot 2 is higher than Pot 1 by at least the margin
            else if (val2 > (val1 + margin)) {
                digitalWrite(led1, LOW);
                digitalWrite(led2, HIGH);
                tone(buzzer2, 1500);
                noTone(buzzer1);     // Ensure the other buzzer is silent
            } 
            // If values are too balanced, enter silence mode
            else {
                silence();
            }
            
            delay(50);
            }
            
            

Physical Circuit

To test the code on the physical microcontroller, I built the same circuit using the components I have available. To program the Raspberry Pi Pico 2, I used Visual Studio Code.

Fig 12. Physical circuit