Project Overview

Control of a servo motor using a list of angles received via UART and navigated with buttons.

  • Project: Servo Angle List Control
  • Author: Rodrigo Zárate
  • Platform: Raspberry Pi Pico 2
  • Communication: UART 115200 bps
  • Control: PWM ~50 Hz

Datasheet Raspberry Pi Pico 2

General Information

Microcontroller RP2040 (designed by Raspberry Pi)
CPU Dual-core ARM Cortex-M0+
Clock Speed Up to 150 MHz
Architecture 32-bit
SRAM 264 KB on-chip RAM
Flash Memory External QSPI flash (typically 2 MB on Pico board)

GPIO & Peripherals

GPIO Pins 26 multi-function GPIO (digital I/O)
ADC 12-bit ADC (up to 4 external analog inputs)
PWM 16 PWM channels (8 slices)
UART 2 × UART controllers
SPI 2 × SPI controllers
I²C 2 × I²C controllers
PIO 2 × Programmable I/O blocks (custom hardware interfaces)
Timers Hardware timers and watchdog

Electrical Characteristics

Operating Voltage (Logic) 3.3V
Input Voltage (VSYS) 1.8V – 5.5V
GPIO Logic Level 3.3V (Not 5V tolerant!)
ADC Reference 3.3V default (external reference possible)

Special Features

  • Dual-core processing for multitasking applications.
  • Programmable I/O (PIO) for custom communication protocols.
  • Flexible clock system with PLL configuration.
  • USB 1.1 device and host support.
  • Low power sleep modes.
The RP2040 is optimized for embedded systems projects requiring precise timing, multiple communication interfaces, and flexible PWM control.

Pinout Legend (Color Key)

This table explains what each color category means in the Pico 2 pinout diagram.

Category Color Description Typical Use
Power Red Voltage supply pins that power the board and external devices (e.g., VBUS, VSYS, 3V3). Powering sensors, modules, and the Pico itself.
Ground Black 0V reference. Required as a common reference for power and signals. Shared GND between Pico and external circuits.
UART (default) Purple Serial communication pins (TX/RX). “Default” indicates common recommended assignments. Serial console, debugging, GPS/Bluetooth modules.
GPIO / PIO / PWM Green General-purpose digital pins. Can read inputs, drive outputs, generate PWM, and run PIO functions. Buttons, LEDs, relays, servo PWM, motor control PWM.
ADC Dark Green Analog-to-digital input pins for reading varying voltages (only certain GPIOs support ADC). Potentiometers, analog sensors, voltage measurement.
SPI (default) Pink High-speed serial bus (SCK/MOSI/MISO/CS). “Default” indicates common pin mapping. SD cards, fast displays, high-speed sensors.
I2C (default) Blue Two-wire bus (SDA/SCL). Multiple devices can share the same bus. OLEDs, IMUs, temperature sensors, RTC modules.
System Control Light Pink Pins related to internal board control (reset, regulator enable, analog reference, etc.). RUN reset, 3V3_EN, ADC_VREF (advanced use).
Debugging Orange Programming and debug interface pins (SWD). Used with external debug probes. SWDIO/SWCLK for debugging and low-level development.

Tip: Most beginner projects use Power, Ground, and GPIO/PWM. UART/I2C/SPI are used when communicating with other devices.

Diagram of connections

Important: Servo must be powered with 5–6V and share GND with Pico.

Connections

Device GPIO Function
Servo SignalGP15PWM 50 Hz
Mode ButtonGP16Change mode
Next ButtonGP17Next angle
Previous ButtonGP18Previous angle
UART TXGP0Serial TX
UART RXGP1Serial RX
Diagram of connections

Important: Servo must be powered with 5–6V and share GND with Pico.

Source Code

#include "pico/stdlib.h"
#include "hardware/uart.h"
#include 
#include 
#include "hardware/pwm.h"
#define UART_ID uart0
#define BAUD_RATE 115200
#define TX_PIN 0
#define RX_PIN 1
#define btn_mode 16
#define btn_next 17
#define btn_prev 18
#define PWM_PIN 0
#define SERVO_PIN 15
using namespace std;
#define list 10

volatile int programa = 0;

uint16_t angle_to_pwm(float angle) {
    float pulse_ms = 1.0f + (angle / 180.0f);   // 1.0 → 2.0 ms
    float duty = (pulse_ms / 20.0f) * 39062.0f; // 20 ms periodo total
    return (uint16_t)duty;
}

// --- Rutina de interrupción ---
static void button_isr(uint gpio, uint32_t events) {
    if (gpio == btn_mode && (events & GPIO_IRQ_EDGE_FALL)) {
        programa++;
        if (programa > 2) {
            programa = 0;
        }
        if (programa == 0) {
            printf("Modo de Escritura Activado\n");
        } else if (programa == 1) {
            printf("Modo de Lectura Activado\n");
        } else if (programa == 2) {
            printf("Modo de Navegacion Activado\n");
        }
        sleep_ms(500);
    }
}

int main() {
    int pos = 0;
    stdio_init_all();
    uart_init(UART_ID, BAUD_RATE);
    uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE);
    gpio_set_function(SERVO_PIN, GPIO_FUNC_PWM);
    uint slice = pwm_gpio_to_slice_num(SERVO_PIN);
    pwm_set_clkdiv(slice, 64.0f);
    pwm_set_wrap(slice, 39062);
    pwm_set_enabled(slice, true);

    int next1 = 0;
    int prev1 = 0;

    // Botones con pull-up interna
    gpio_init(btn_mode);
    gpio_set_dir(btn_mode, GPIO_IN);
    gpio_pull_up(btn_mode);

    gpio_init(btn_next);
    gpio_set_dir(btn_next, GPIO_IN);
    gpio_pull_up(btn_next);

    gpio_init(btn_prev);
    gpio_set_dir(btn_prev, GPIO_IN);
    gpio_pull_up(btn_prev);

    // Interrupción botón modo
    gpio_set_irq_enabled_with_callback(btn_mode, GPIO_IRQ_EDGE_FALL, true, &button_isr);

    string p1 = "";
    int lista[list] = {720, 720, 720, 720, 720, 720, 720, 720, 720, 720};
    int i = -1;

    while (true) {
        int pos = 0;

        if (programa == 0) {
            int ch = getchar_timeout_us(0);
            if (ch != PICO_ERROR_TIMEOUT) {

                if (ch == ',') {
                    if (p1 == "Escribir" || p1 == "write" || p1 == "WRITE" || p1 == "escribir" || p1 == "Write" || p1 == "ESCRIBIR") {
                        p1 = "";
                        int contador = 0;
                        for (int k = 0; k < list; k++) {
                            if (lista[k] != 720) {
                                contador++;
                            }
                        }
                        if (contador == list) {
                            printf("Lista llena, no se pueden agregar mas elementos.\n");
                            p1 = "";
                            continue;
                        } else {
                            i++;
                            while (ch != ';' && ch != '\n') {
                                ch = getchar_timeout_us(0);
                                if (ch != PICO_ERROR_TIMEOUT) {
                                    if (ch == ',') {
                                        printf("Eco: %s\n", p1.c_str());
                                        if (stoi(p1) > 180 || stoi(p1) < 0) {
                                            printf("Valor fuera de rango (0-180). Intente de nuevo.\n");
                                            i--;
                                        } else {
                                            lista[i] = stoi(p1);
                                            i++;
                                            p1 = "";
                                        }
                                    } else if (ch == ';' || ch == '\n') {
                                        if (stoi(p1) > 180 || stoi(p1) < 0) {
                                            printf("Valor fuera de rango (0-180). Intente de nuevo.\n");
                                            i--;
                                        } else {
                                            lista[i] = stoi(p1);
                                            p1 = "";
                                        }
                                    } else {
                                        p1 += (char)ch;
                                    }

                                    printf("Lista: ");
                                    for (int k = 0; k <= i; ++k) {
                                        printf("%d", lista[k]);
                                        if (k + 1 <= i) printf(", ");
                                    }
                                    printf("\n");
                                }
                            }
                        }
                    }
                }

                if (ch == ';' || ch == '\n') {
                    if (p1 == "Borrar" || p1 == "delete" || p1 == "DELETE" || p1 == "borrar" || p1 == "BORRAR") {
                        for (int k = 0; k < list; k++) lista[k] = 720;
                        i = -1;
                        printf("Ok, Lista borrada\n");
                    }
                    p1 = "";
                } else {
                    p1 += (char)ch;
                }
            }
        } else if (programa == 1) {
            while (programa == 1) {
                int contador = 0;
                for (int k = 0; k < list; k++) {
                    if (lista[k] != 720) contador++;
                }
                if (contador == 0) {
                    printf("La lista esta vacia. Cambie al modo de escritura.\n");
                    sleep_ms(1500);
                } else {
                    int j = 0;
                    while (programa == 1 && j < contador) {
                        printf("posicion: %d , valor: %d\n", j, lista[j]);
                        pwm_set_gpio_level(SERVO_PIN, angle_to_pwm(lista[j]));
                        printf("Angulo: %.1f°\n", lista[j]);
                        sleep_ms(1500);
                        j++;
                    }
                }
            }
        } else if (programa == 2) {
            while (programa == 2) {
                static int i = 0;
                const int LISTA_LEN = 10;

                if (lista[pos] == 720) pos--;
                int contador = 0;
                for (int k = 0; k < list; k++) if (lista[k] != 720) contador++;

                if (next1 == 0 && gpio_get(btn_next) == 1) {
                    if (contador == 0 && gpio_get(btn_next) == 1) {
                        printf("Lista Vacia, no se puede avanzar.\n");
                        sleep_ms(100);
                    } else {
                        pos++;
                        printf("siguiente \n");
                        if (lista[pos] == 720) {
                            printf("Valor no valido, regresando a %d\n", pos - 1);
                            pos--;
                        }
                        if (pos == list) pos = 0;
                        printf("posicion: %d , valor: %d\n", pos, lista[pos]);
                        pwm_set_gpio_level(SERVO_PIN, angle_to_pwm(lista[pos]));
                        printf("Angulo: %.1f°\n", lista[pos]);
                        sleep_ms(200);
                    }
                }
                next1 = gpio_get(btn_next);

                if (prev1 == 0 && gpio_get(btn_prev) == 1) {
                    if (contador == 0 && gpio_get(btn_prev) == 1) {
                        printf("Lista Vacia, no se puede avanzar.\n");
                        sleep_ms(100);
                    } else {
                        pos--;
                        printf("anterior \n");
                        if (pos == -1) {
                            printf("Valor no valido, regresando a %d\n", pos + 1);
                            pos++;
                        }
                        if (pos == list) pos = 0;
                        printf("posicion: %d , valor: %d\n", pos, lista[pos]);
                        pwm_set_gpio_level(SERVO_PIN, angle_to_pwm(lista[pos]));
                        printf("Angulo: %.1f°\n", lista[pos]);
                        sleep_ms(200);
                    }
                }
                prev1 = gpio_get(btn_prev);
            }
        }
    }
    return 0;
}

Detailed Code Explanation

Internal architecture and operation logic of the Servo Angle List system.

1. System Architecture

The program implements a small state machine controlled by an interrupt-driven button. The global variable programa defines the active mode:

  • 0 → Write Mode
  • 1 → Read Mode
  • 2 → Navigation Mode

The system continuously runs inside the main while(true) loop, executing different behaviors depending on the current mode.


2. PWM Configuration (Servo Control)

Standard hobby servos operate at approximately 50 Hz (20 ms period).

pwm_set_clkdiv(slice, 64.0f);
pwm_set_wrap(slice, 39062);

These values generate a 20 ms PWM period. The servo position is determined by varying the pulse width between:

  • 1.0 ms → 0°
  • 1.5 ms → 90°
  • 2.0 ms → 180°

Angle Conversion Function

uint16_t angle_to_pwm(float angle)

Converts an angle (0–180°) into the corresponding PWM duty cycle.

Internally:

pulse_ms = 1.0 + (angle / 180)

duty = (pulse_ms / 20 ms) × 39062

This ensures a linear mapping between angle and servo position.


3. Mode Switching via Interrupt

The mode button (GPIO 16) is configured with:

gpio_set_irq_enabled_with_callback(...)

When a falling edge is detected (button pressed), the interrupt routine button_isr() executes.

  • Increments programa
  • Cycles 0 → 1 → 2 → 0
  • Prints active mode to console

The 500 ms delay inside the ISR acts as a basic debounce, although best practice would move debounce logic outside the interrupt.


4. Write Mode (programa == 0)

In this mode, the system listens to UART input using:

getchar_timeout_us(0)

This allows non-blocking serial reading.

Supported Commands

  • Escribir,90,120,45; → Stores angles
  • Borrar; → Clears list

The list can store up to 10 values. The value 720 represents an empty slot.

Validation

  • Values must be between 0 and 180
  • If invalid → error message is printed
  • List overflow is prevented

After each insertion, the updated list is printed to the console.


5. Read Mode (programa == 1)

In this mode, the servo automatically plays back all stored angles.

  • Counts valid elements in list
  • Iterates sequentially
  • Moves servo every 1.5 seconds
pwm_set_gpio_level(SERVO_PIN, angle_to_pwm(lista[j]));

If the list is empty, the system prints a warning message.


6. Navigation Mode (programa == 2)

In this mode, the servo position is manually controlled using:

  • Next Button (GPIO 17)
  • Previous Button (GPIO 18)

Logic

  • Detects button edge transitions
  • Updates index position
  • Wraps around list boundaries
  • Skips invalid entries (value 720)

This allows interactive browsing through stored angles.


7. UART Communication

Configured at:

115200 bps
8 data bits
No parity
1 stop bit (8N1)

Even though UART0 is initialized on GPIO 0 and 1, stdio is available via USB CDC, allowing direct use of the serial monitor without external TX/RX wiring.


8. Memory Management Strategy

The list is implemented as:

int lista[10]

Using a fixed-size array simplifies memory usage, avoids dynamic allocation, and ensures deterministic behavior — important in embedded systems.


9. System Behavior Summary

Mode Function Interaction
Write Stores angles via UART Serial console
Read Automatic playback Autonomous
Navigation Manual control Physical buttons

10. Key Embedded Concepts Demonstrated

  • Interrupt-driven state switching
  • Non-blocking UART parsing
  • Edge detection with GPIO
  • Software validation of input data
  • Deterministic memory allocation
  • PWM timing for servo control

Demonstration Video