Group Assignment Individual Assignment Read Datasheet Simulate Program Test on Board Trying Different Language
Week 4

Embedded Programming

This week, we explored Embedded Programming, learning to program microcontrollers and understand their architectures, toolchains, and workflows. We worked with different development environments, comparing compiled and interpreted languages. Key topics included memory management, peripheral interfacing, and debugging techniques. Through hands-on practice, we wrote, compiled, and flashed firmware, testing hardware interactions and communication protocols. This helped us understand how software controls embedded systems efficiently.

Learning Objectives

  • Implement programming protocols.

  • Assignments

    Group Assignments

  • Demonstrate and compare the toolchains and development workflows for available embedded architectures
  • Document your work to the group work page and reflect on your individual page what you learned

  • Individual Assignments

  • Browse through the datasheet for your microcontroller
  • Write a program for a microcontroller, and simulate its operation, to interact and communicate
  • extra credit: test it on a development board
  • extra credit: try different languages &/or development environments

  • Group Assignment

    Compiler vs Interpreter

    What is a compiler

    A compiler is a computer program that transforms code written in a high-level programming language into the machine code. It is a program which translates the human-readable code to a language a computer processor understands (binary 1 and 0 bits). The computer processes the machine code to perform the corresponding tasks.

    What is an Interpreter

    An interpreter is a computer program, which converts each high-level program statement into the machine code. This includes source code, pre-compiled code, and scripts.

    Compiler vs Interpreter

    Both compiler and interpreters do the same job which is converting higher level programming language to machine code. However, a compiler will convert the code into machine code (create an exe) before program run. Interpreters convert code into machine code when the program is run.

    I refered to this site for knowing more about compilers and interpreters

    Compiler vs Interpreter

    I asked ChatGPT, what are compilers and interpreters in context of embedded programming. The prompt for the same was: compilers vs interpreters in context of embedded programming

    For embedded programming

    Compiler: A compiler translates the entire high-level source code into machine code (binary or assembly) before execution. The resulting binary file is flashed onto the microcontroller, allowing direct execution by the hardware.

    Interpreter: An interpreter translates and executes code line-by-line at runtime, without requiring compilation into machine code beforehand.

    Characteristics of Compilers:

    • Fast Execution - Entire code is precompiled, leading to optimized performance.
    • Efficient Resource Usage - No runtime interpreter, reducing memory and CPU overhead.
    • Standalone Binary - Runs independently on the microcontroller after flashing.
    • Slower Development Cycle - Requires recompilation and flashing after every change.

    Characteristics of Interpreters:

    • Rapid Development - No need to recompile; modify and run instantly.
    • Easier Debugging - Interactive execution allows quick testing and debugging.
    • Slower Execution - Code is processed line-by-line instead of precompiled.
    • Higher Memory Usage - Requires an interpreter to be stored in memory.

    This week, we explored various embedded architectures, focusing on their development workflows and toolchains. We worked with the XIAO RP2040 using both Arduino IDE (C/C++) and Thonny (MicroPython), comparing compiled and interpreted approaches. The Arduino IDE allowed us to write and upload C++ code using the RP2040 core and GCC toolchain, while Thonny provided an interactive REPL environment for MicroPython, enabling quick testing without compilation.

    Additionally, we experimented with the ATtiny1614, a lightweight microcontroller, using AVR toolchains and UPDI programming. We also explored an FPGA controller, understanding its hardware description language (HDL) workflow, synthesis, and bitstream uploading. Unlike microcontrollers, FPGAs provide hardware-level reconfigurability, requiring a different design approach.

    Finally, we tested the Raspberry Pi 5, a powerful SBC running Linux, which supports high-level languages like Python and C++. We explored GPIO control, I2C, and SPI communication, demonstrating its flexibility for embedded applications. This hands-on experience gave us a deeper understanding of the strengths and limitations of each architecture, helping us choose the right tools for different applications.

    Comparing different microcontrollers and toolchains

    Microcontroller Toolchain Components Programming Language IDE/Editor
    RP2040 Arduino Core for RP2040, GCC, avrdude C/C++ Arduino IDE
    RP2040 MicroPython Firmware, REPL MicroPython Thonny
    ATtiny1614 ATTinyCore, avrdude C/C++ Arduino IDE
    ATtiny84 ATTinyCore, avrdude C/C++ VS Code + PlatformIO
    ESP32 ESP-IDF, GCC, esptool C/C++ VS Code + PlatformIO
    STM32F303RE STM32Duino Core, GCC, STLink C/C++ Arduino IDE
    Raspberry Pi 5 Linux-based toolchain (GCC, Python, etc.) Python, C, C++ VS Code, Thonny, Terminal

    The group assignment can be found here Group Assignment

    Individual Assignments

    Read through the datasheet of a microcontroller

    A microcontroller (MCU) is a small computer on a single integrated circuit that is designed to control specific tasks within electronic systems. It combines the functions of a central processing unit (CPU), memory, and input/output interfaces, all on a single chip.

    The microcontroller that I chose to explore this week was the Raspberry Pi RP2040

    RP2040

    The RP2040 is a powerful, low-cost microcontroller from Raspberry Pi, featuring a dual-core ARM Cortex-M0+ processor running at up to 133MHz. It includes 264KB SRAM, supports up to 16MB external QSPI flash, and has 30 GPIO pins, with 2 UARTs, 2 SPI, 2 I2C, 16 PWM channels, and USB 1.1. A unique Programmable I/O (PIO) subsystem allows custom peripherals. Its DMA controller, AHB-Lite bus fabric, and low-power modes enhance performance and efficiency. With UF2 bootloader and MicroPython support, it’s beginner-friendly while offering advanced capabilities for professionals in IoT, robotics, and embedded systems.

    The documentation for Raspberry Pi RP2040 can be found on this link

    RP2040 is a low-cost, high-performance microcontroller device with flexible digital interfaces. Key features:

    • Dual Cortex M0+ processor cores, up to 133MHz
    • 264kB of embedded SRAM in 6 banks
    • 30 multifunction GPIO
    • 6 dedicated IO for SPI Flash (supporting XIP)
    • Dedicated hardware for commonly used peripherals
    • Programmable IO for extended peripheral support
    • 4 channel ADC with internal temperature sensor, 500ksps, 12-bit conversion
    • USB 1.1 Host/Device

    System Overview of RP2040

    The above image shows the system overview of the RP2040 Microcontroller. I will explain the key features that I found important while choosing RP2040 as a microcontroller:

    • Dual Cortex M0+ processor cores:

      The processor unit has two ARM Cortex M0+ cores which can run upto 133MHz, with a SIO(Single-Cycle Input Output), system interrupts and an SWD. Two cores allow for parallel processing, making it powerful for multitasking applications. Cortex-M0+ is optimized for low-power consumption and efficiency.
      The RP2040 has 26 system-level interrupts, which can be routed to both processors.
      The Serial Wire Debugger(SWD) interface is a debugging bus used to connect an external debugger to both processors.

      RP2040 Processor Layout
    • 264kB SRAM and QSPI for Flash Memory:

      The RP2040 does not have built-in Flash memory for storing programs. Instead, it executes code directly from external QSPI (Quad SPI) Flash memory. This approach provides customizable storage, allowing developers to choose the appropriate Flash size for their application.
      For volatile (temporary) data, the RP2040 has 264KB SRAM, which is divided into six independent banks. This banked memory architecture prevents bottlenecks by allowing simultaneous access from different system components, improving overall performance.

    • GPIO and Peripherals

      30 GPIO Pins (4 Analog Inputs)

      The RP2040 has 30 multifunctional GPIO pins, which can be used as digital inputs or outputs. Each GPIO pin can be configured for SPI, I2C, UART, PWM, PIO, or digital I/O. 4 GPIOs (Pins 26, 27, 28, and 29) can be used as analog inputs. The GPIOs operate at 3.3V, and they are not 5V tolerant

      12-Bit Analog to Digital Converter (ADC)

      The RP2040 features a 12-bit ADC that allows it to convert analog signals (voltages) into digital values. The ADC outputs values from 0 to 4095 (2¹² = 4096 levels). This means it can divide the input voltage into 4096 discrete steps for precise readings. This is useful for reading sensors, measuring voltages, or interfacing with analog devices.

      The four Analog Input GPIO Pins are:

      • GPIO26 (ADC0)
      • GPIO27 (ADC1)
      • GPIO28 (ADC2)
      • GPIO29 (ADC3)
      There is also a inbuilt temprature sensor at ADC4. It measures the chip's temperature by sensing the voltage of an internal diode.


        Multiple Communication Interfaces:
      • 2x UARTs → For serial communication (e.g., talking to a PC or another microcontroller).
      • 2x SPI Controllers → Fast data exchange with sensors, displays, and storage devices.
      • 2x I2C Controllers → Used for connecting multiple sensors with just two wires.
      • 16 PWM Channels:

        Useful for generating analog-like signals (e.g., dimming LEDs, motor speed control).

      RP2040 Pinout

      What are the different communication interfaces

      • SPI

        SPI (Serial Peripheral Interface) is a high-speed, full-duplex communication protocol commonly used to connect microcontrollers with sensors, displays, memory chips, and other peripherals. It has 4 wires : SCLK, MISO, MOSI, CS. One to Many Communication. Requires more wires (one CS line per child device).

      • I2C

        I2C (Inter-Integrated Circuit) is a two-wire, half-duplex communication protocol, ideal for connecting multiple low-speed peripherals to a microcontroller. Two wires: SCL(Clock) and SDA(Data). One to Many Communication. It uses an addressing system - Each device has a unique 7-bit or 10-bit address, so multiple devices can share the same bus.

      • UART

        UART (Universal Asynchronous Receiver/Transmitter) is a serial communication protocol used for direct communication between two devices, such as a microcontroller and a computer. It is Asynchronous, thus doesn't require a clock. Both the parties commmunicate on the same baud rate. One to One Communication.

      • Voltage and Current Parameters
        • Core Voltage (Vcore): 1.1V (generated internally by the onboard LDO regulator).
        • GPIO Voltage: 3.3V - supplied externally, powers GPIOs and peripherals

            GPIO Logic Levels (3.3V Logic)
          • Low (0): 0V - 0.8V
          • High (1): 2.0V - 3.3V

            GPIO Current Limits
          • Max Source Current (per pin): 4mA
          • Max Sink Current (per pin): 4mA
          • Total Max Current (all GPIOs combined): 50mA

          • Power Supply Requirements
          • USB Power (VUSB): 5V (used when powering via USB).
          • VSYS (System Power): 1.8V - 5.5V (supplies internal 3.3V regulator).
          • 3V3 Out (3.3V Output): Supplies up to 300mA for external components.
        • Power Consumption:
          Active Mode (Dual Core Running @ 133MHz): ~15mA - 20mA
          Deep Sleep Mode: ~1mA
          Standby Mode: ~100µA
      Comparision table of different interfaces
      USB 1.1 with Host & Device Support:

      Can act as a USB device (like a keyboard/mouse) or a USB host (reading data from peripherals).

      Programmable Input Output

      PIO (Programmable Input/Output) is a special feature of the RP2040 that allows the microcontroller to handle custom I/O protocols and high-speed data operations without using the CPU.

    Write a program for a microcontroller, and simulate its operation, to interact and communicate

    Embedded programming involves writing software that runs directly on microcontrollers to control hardware components. This requires handling input/output, managing communication protocols, and optimizing code for real-time execution.

    Wokwi
    Pico on Wokwi
    Raspberry Pi Pico using Wokwi Simulator

    For this assignment, I programmed the Raspberry Pi Pico in MicroPython using the Wokwi simulator.

    Raspberry Pi Pico

    The Raspberry Pi Pico is a microcontroller board developed by Raspberry Pi, designed for embedded applications, automation, and hardware interfacing. Unlike Raspberry Pi single-board computers (which run Linux), the Pico is built for real-time control and runs bare-metal code or lightweight operating systems like MicroPython and C/C++. Key features include:

    Pico Pinout
    Wokwi Home PAge
    • Microcontroller: RP2040 chip designed by Raspberry Pi
    • Processor: Dual-core Arm Cortex M0+, up to 133 MHz
    • Memory: 264KB SRAM, 2MB flash storage
    • USB: USB 1.1 with device and host support
    • Power Modes: Low-power sleep and dormant modes
    • Programming: Drag-and-drop mass storage via USB
    • GPIO: 26 multi-function pins
    • Interfaces: 2 x SPI, 2 x I2C, 2 x UART, 3 x 12-bit ADC, 16 x PWM channels
    • Additional Features: On-chip clock and timer, temperature sensor
    • Custom Peripherals: 8 x Programmable I/O (PIO) state machines
    • Module Types: Pico (castellated module), Pico H (pre-soldered headers)
    Wokwi Simulator

    Wokwi is an online simulator for microcontrollers, allowing users to test and debug embedded systems without needing physical hardware. It supports various microcontrollers, including the Raspberry Pi Pico, Arduino boards, and ESP32, along with virtual peripherals like LEDs, buttons, sensors, and serial monitors.
    To access Wokwi, visit wokwi.com and choose a microcontroller to simulate.

    Wokwi
    Wokwi Home PAge

    The program reads a button press, toggles an LED, and transmits messages through a wired UART connection.

    Micropython

    MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments.

    You can found it's documentation on micropython.org.
    Micropython

    Pico-Wokwi Workflow

    • Setting Up the Environment

      The first step in the process was setting up the Wokwi Environment. Open Wokwi and add a new project and select Pico as Microcontroller. And while selecting the language, select Micropython.

      Image 1 Image 2

      Selecting Raspberry PI Pico

      Selecting Micropython

      The Pico template will open as shown in the below figure. You can add components as you need and write code on the left window.

      Concept of Kerf
      Pico Template
    • Designing the basic circuit
    • The first basic circuit that I wanted to test on the simualtion was to include a LED, a push button and the raspberrypi Pico.

      Image 1 Image 2

      The basic circuit consits of an an LED with resistor, Push button and the Raspberry pi Pico

      Concept of Kerf
      Basic Circuit with IO
      How to add components in Wokwi

      Concept of Kerf
      Adding Components
      Concept of Kerf
      Search Bar for component search

      While adding components, you can change certain parameters such as color, add keyboard shortcuts and simulate real life parameters such as Mechanical bounce in push buttons (What is bouncing)

      Concept of Kerf
      Adding Push Button: Different Parameters
    • Writing the Code

      This is my first progam on Raspberry Pi Pico on Wokwi with Micropython.

      The program is designed to read an input from a push button and control an LED based on button presses and also communicate via the Serial Monitor.

      
      from time import sleep
      from machine import Pin
      
      sleep(0.1)  # Wait for USB to become ready
      
      
      led = Pin(0, Pin.OUT)  
      pb = Pin(28, Pin.IN, Pin.PULL_UP)
      
      state = 0  
      
      while True:
        
              
              if pb.value() == 0: 
                  sleep(0.2)
                  state = not state  # Toggle state (0 -> 1 or 1 -> 0)
                  led.value(state)  
                  print("Button Pressed - LED Toggled")
      
                  # Wait until button is released before detecting another press
                  while pb.value() == 0:
                      sleep(0.1)  
      
      

      I'll explain the code in detail

      1. Importing libraries

        The top-most two lines are for importing required librarires for our program

        
        from time import sleep
        from machine import Pin
        
        
        Th Pin function is imported from the machine library. The Pin function enables GPIO use for our Raspberry Pi-Pico and the sleep function helps to add a delay to the program.

      2. Setting Up Pins

        Before using the pins, they must be properly configured. The LED is connected to GPIO 0, which is set as an output pin, allowing the microcontroller to control its state (ON/OFF). The push button is connected to GPIO 28, which is configured as an input pin with an internal pull-up resistor. This ensures the pin remains in a stable HIGH (1) state when the button is not pressed, preventing it from floating—a condition where an unconnected input pin picks up random noise, leading to unpredictable behavior.

        
        pin = Pin(0,Pin.OUT)
        pb = Pin(28,Pin.IN,Pin.PULL_UP)
        	

      3. Initialising State Variable

        The state variable is initialized to 0. This variable will be used to track the LED

        
        state = 0
        	
      4. Writing the Main Loop

        The push button is pulled up by the internal pull-up resistor in the pin of the Pico. Therefore the push button is always HIGH unless it is pressed. The value of the pin is 0 when the push button is pressed. The sleep(0.2) adds a delay of 200ms to the program to avoid bouncing.

        		
        while True:
        if pb.value() == 0: 
        	sleep(0.2)  # Debounce delay
        	state = not state  # Toggle state (0 -> 1 or 1 -> 0)
        	led.value(state)  
        	print("Button Pressed - LED Toggled")
        	

        Bouncing happens when a mechanical switch or button is pressed or released. Instead of an instant, clean transition from HIGH to LOW (or vice versa), the contact inside the switch vibrates for a few milliseconds, causing multiple rapid changes in state. This can lead to unintended multiple detections of a single press. Debouncing techniques, such as adding a small delay in code or using capacitors, help to eliminate this issue.


        Bouncing

        The delay of 200ms is added to the program to avoid bouncing.

        The state variable toggles every time the push button is detected

        led.value(state) controls the state of the LED

        led.value(1) = LED is ON
        led.value(0) = LED is OFF

      5. Waiting for the release of button

        The program waits for the button to be released before it continues to the next iteration of the loop. So while the push button is pressed pb.value() will read 0, and a delay will be there unless the button is not released.

        
        while pb.value() == 0:
        	sleep(0.1)
        	

    • Simulating the Circuit

      Simulate the program by clicking the play button and click on the push button. The Simulation helps to see the LED light up when the push buutton is pressed and the LED stays on until the push button is pressed again. The Serial Monitor This basic program helped me understand how to program a microcontroller to interact with input/output devices and communicate via wired connections. Simulating it in Wokwi allowed for quick testing and debugging.

    Interfacing ADC for Analog Pins

    RP2040 allows for analog inputs using the ADCs.

    ADC Interfacing

    I tried to just connect the potentiometer and write a program like a digital pin expecting a 0 and 1 at the extreme ends of the potentiometer. I tried this approach because of my prior expereience in Arduino, wherein analogRead() and digitalRead() where used for analog and digital input, respectively. The digitalRead() value read atleast HIGH or LOW for analog pins in C, but I couldn't get any values for pot.value() in Micropython.

    I refered to this documentation on potentiometers and Raspberry Pi Pico

    Documentation: Pico and Potentiometer
    ADC Interfacing

    Here is the basic code for the ADC interfacing

    
    from machine import ADC
    from time import sleep
    
    #initialising pin as ADC
    pot=ADC(28) #ADC pins available are 26,27,28,29
    
    pot_value=pot.read_u1
    

    The below code adds some functions to map the ADC reading from the potentiometer to the GPIO Voltage i.e from 0 to 3.3 and the ScalePercent function maps that to a percentage value from 0 to 100.

    
    from machine import ADC
    from time import sleep
    
    def ReadPotentiometer():
    	adcpin = 28
    	pot = ADC(adcpin)
    	
    	adc_value = pot.read_u16()
    	volt = (3.3/65535)*adc_value #65535 is 2^16 because of the program reads 16bit
    	
    	percentPot = ScalePercent(volt)
    	
    	return percentPot
    
    def ScalePercent(volt):
    	percent = (volt/3.3)*100
    	return int(percent)
    
    
    
    while True:
    	potvalue = ReadPotentiometer()
    	print(potvalue)
    	sleep(1)
    
    								

    The RP2040's ADC has a 12-bit resolution, meaning it produces values ranging from 0 to 4095 (2¹² levels). However, MicroPython scales ADC readings to 16 bits for consistency across different microcontrollers. This means the .read_u16() function returns values from 0 to 65535 (2¹⁶ levels). Internally, the 12-bit reading is left-shifted by 4 bits (multiplied by 16) to fit the 16-bit range.

    Interfacing PWM pins

    PWM, or Pulse Width Modulation, is a technique that controls the power delivered to devices. It achieves this by turning the power on and off very quickly. There is no Digital to Analog Converters on the PICO, therfore to simulate analog signals PWM is used.

    ADC Interfacing

    PWM works by producing an output that changes between HIGH and LOW at a very high frequency. The duty cycle is responsible for the intensity of the signal. The duty cycle refers to the percentage of time the power is on compared to the total time of the on-off cycle—take a look at the following diagram.

    ADC Interfacing

    A duty cycle of 50 percent results in 50 percent LED brightness, a duty cycle of 0 means the LED is fully off, and a duty cycle of 100 means the LED is fully on. Changing the duty cycle is how you produce different levels of brightness.

    The Raspberry Pi Pico has 8 independent PWM generators called slices. Each slice has two channels, which makes a total of 16 PWM channels.



    Test on Board

    Test the program on the board

    After succesfully simulating my program, I wanted to run the program on the board. As we have a XIAO RP2040 Board, I tried blinking an LED on XIAO RP2040 using two workflows: Arduino IDE (c++) and Thonny (Micropython)

    The documentation is given below:
    XIAO-RP2040-with-Arduino IDE
    XIAO-RP2040-with-Thonny-using-Micropython

    Test the program on the board using Arduino IDE

    Using uploading the code we found an error with the bootloader, because we were switching our workflow. We tried different methods to figure out what was going wrong, but failed. The below message is the error what was shown every time we tried to upload the code.

    Failed Upload in Arduino IDE

    The documentation mentioned about this issue, and we tried to follow the steps but we couldn't figure out what the run button. Due to my presumption that the R on the board must be the reset button and not run. But later our instructor said to follow the below step: Hold the boot button and press Run

    About the issue on documentation
    Hold Boot + Press Run

    The above step makes the system into bootloader mode and the device(XIAO-RP2040) is detected as a Mass Storage Class device

    MSC Detected

    The Mass Storage Class (MSC) is a USB device class that allows embedded systems, like the Xiao RP2040, to appear as a flash drive when connected to a computer. This simplifies firmware updates, as users can drag-and-drop firmware files instead of using specialized flashing tools.

    Mass Storage Class

    In the Xiao RP2040, MSC is used for firmware programming via the UF2 bootloader. When the board is in bootloader mode, it mounts as a USB drive, making firmware installation as easy as copying a file.

    UF2 Bootloader in Xiao RP2040: UF2 (USB Flashing Format) is a special file format designed for easy firmware flashing. It was developed by Microsoft and is used in microcontrollers like the Xiao RP2040 to facilitate firmware updates via USB without needing external programmers.

    Succesfully flashed program

    The XIAO RP2040 is programmed using the ARDUINO IDE using C

    
    int blue = 25;
    int red = 17;
    int green = 16;
    
    void setup() {
    	pinMode(blue, OUTPUT);
    	pinMode(green, OUTPUT);
    	pinMode(red, OUTPUT);
    	
    	//The LEDs are Pulled UP HIGH
    
    	digitalWrite(green, HIGH); // Turn OFF green LED
    	digitalWrite(red, HIGH);   // Turn OFF red LED
    }
    
    void loop() {
    	digitalWrite(blue, LOW);   // Turn ON blue LED
    	delay(1000);               // Wait for a second
    	digitalWrite(blue, HIGH);  // Turn OFF blue LED
    	delay(1000);               // Wait for a second
    }
    

    The change from interpreter to compiler workflow for the same RP2040 showed an error because of we failed to load the bootloader incorrectly. We couldn't understand what went wrong. The documentation helped to make it right.

    Files

    You can download my Wokwi files from below
    Wokwi File: Push button and LED
    Wokwi File: Potentiometer and LED PWM

    References

    x