Go back

Pico Multi-Protocol Demo: Serial, I2C, SPI & ADC on RP2040

Introduction

Welcome to the Pico Multi-Protocol Demo! In this tutorial, we’ll explore how to use the Raspberry Pi Pico (RP2040) to communicate over multiple protocols simultaneously: Serial, I2C, SPI, and ADC. This is an advanced example that brings together several key skills for embedded development. We’ll build the circuit in the Velxio simulator, write the code, and walk through each part step by step.

Circuit diagram of pico-multi-protocol on the Velxio simulator canvas

Circuit Walkthrough

Let’s look at the components and wiring. The circuit uses a Raspberry Pi Pico (Nano RP2040 Connect) and several peripherals:

All LEDs share a common ground. The potentiometer is powered from the 3.3V rail. The wiring is straightforward, but note that we’re using the Pico’s built-in I2C and SPI peripherals (though not shown explicitly in the schematic, the code assumes they are available on the default pins).

Code Walkthrough

The code is written in Arduino C++ and uses the Wire and SPI libraries. Let’s break it down section by section.

Setup

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  delay(500);

  Serial.println("==============================");
  Serial.println(" Pico Multi-Protocol Demo");
  Serial.println("==============================");
  Serial.println();

We start by initializing the built-in LED and Serial at 115200 baud. The delay gives time for the serial monitor to connect.

1. I2C Scanner

  Wire.begin();
  Serial.println("[I2C] Scanning bus...");
  int found = 0;
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      found++;
      Serial.print("  Found device at 0x");
      if (addr < 16) Serial.print('0');
      Serial.println(addr, HEX);
    }
  }
  Serial.print("  Total devices: "); Serial.println(found);
  Serial.println();

This scans all possible I2C addresses (1-126) and reports any that acknowledge. In the simulator, you may see virtual devices at addresses like 0x50 (EEPROM) and 0x68 (RTC).

2. I2C EEPROM R/W

  Serial.println("[I2C] EEPROM test at 0x50...");
  Wire.beginTransmission(0x50);
  Wire.write(0x00); // register 0
  Wire.write(0x42); // data
  Wire.endTransmission();
  delay(5);

  Wire.beginTransmission(0x50);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(0x50, 1);
  if (Wire.available()) {
    byte val = Wire.read();
    Serial.print("  Wrote 0x42, Read 0x");
    Serial.print(val, HEX);
    Serial.println(val == 0x42 ? " — OK" : " — FAIL");
  }
  Serial.println();

We write the value 0x42 to register 0 of the EEPROM at address 0x50, then read it back to verify. This tests basic I2C read/write.

3. I2C RTC

  Serial.println("[I2C] Reading DS1307 RTC at 0x68...");
  Wire.beginTransmission(0x68);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(0x68, 3);
  if (Wire.available() >= 3) {
    byte sec = ((Wire.read() & 0x7F) >> 4) * 10 + (Wire.read() & 0x0F);
    byte min2 = Wire.read();
    (void)sec; (void)min2;
    Serial.println("  RTC responded OK");
  }
  Serial.println();

Here we attempt to read the time registers from a DS1307 RTC at address 0x68. The code reads three bytes (seconds, minutes, hours) but only checks if the device responded.

4. SPI Loopback

  Serial.println("[SPI] Loopback test...");
  SPI.begin();
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  byte tx = 0xAB;
  byte rx = SPI.transfer(tx);
  Serial.print("  TX: 0x"); Serial.print(tx, HEX);
  Serial.print("  RX: 0x"); Serial.println(rx, HEX);
  SPI.endTransaction();
  Serial.println();

This performs a simple SPI loopback: it sends 0xAB and reads back whatever is received. In the simulator, the MISO and MOSI are not connected externally, so the received value may be 0xFF or something else. This tests the SPI hardware.

5. ADC

  Serial.println("[ADC] Reading analog channels...");
  analogReadResolution(12);
  int a0 = analogRead(A0);
  Serial.print("  A0 (GP26): "); Serial.println(a0);
  Serial.println();

We set the ADC resolution to 12 bits and read the value from A0 (GP26). The potentiometer’s wiper voltage is read, giving a value between 0 and 4095.

  Serial.println("[GPIO] Blinking LED...");
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(200);
    digitalWrite(LED_BUILTIN, LOW);
    delay(200);
  }
  Serial.println("  3 blinks done");
  Serial.println();

  Serial.println("=== All protocol tests complete ===");
}

Finally, we blink the built-in LED three times to indicate the setup is complete.

Loop

void loop() {
  // Heartbeat
  static unsigned long last = 0;
  if (millis() - last >= 3000) {
    last = millis();
    Serial.print("[Heartbeat] ");
    Serial.print(millis() / 1000);
    Serial.println("s");
  }
}

The loop prints a heartbeat message every 3 seconds to show the program is still running.

Key Concepts

Common Pitfalls & Debugging Tips

Suggested Extensions

Try It Yourself!

Ready to experiment? Open the live example in Velxio and run the simulation. You can modify the code, add components, and see the results instantly.

Launch the Pico Multi-Protocol Demo on Velxio

Velxio editor toolbar with compile and run controls

Velxio canvas toolbar above the simulation viewport

Velxio component picker modal browsing parts by category

Happy hacking!


Share this post on:
David Montero

Written by

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

Related posts


Previous Post
Velxio vs Wokwi, Tinkercad, Proteus and Fritzing: an honest comparison
Next Post
Read Analog Sensors on Raspberry Pi Pico with Velxio