Raspberry Pi Pico: Web Server to Control Outputs (Arduino IDE)

This guide shows how to create a basic web server with the Raspberry Pi Pico W programmed with Arduino IDE to control outputs. We’ll use the ESPAsyncWebServer library to create an asynchronous web server.

Raspberry Pi Pico Web Server to Control Outputs Arduino IDE

This tutorial is only compatible with Raspberry Pi Pico W and Raspberry Pi Pico 2W that support Wi-Fi.

To learn the most basic concepts of creating a web server with the RPi Pico programmed in the Arduino IDE, check out this previous tutorial: Raspberry Pi Pico: Web Server (Arduino IDE).

Raspberry Pi Pico with Arduino IDE

You need to install the Raspberry Pi Pico boards in the Arduino IDE and know how to upload code to the board. Check out one of the following tutorials first if you haven’t already:

Installing Libraries

To create a web server (asynchronous) with the Raspberry Pi Pico programmed with Arduino IDE, we’ll use the ESPAsyncWebServer library that is compatible with ESP32, ESP8266, and also RPi Pico (RP2040 and RP2350) and other related boards. For the RPi Pico, you also need to install the RPAsyncTCP library.

So, these are the libraries you need to install in Arduino IDE:

Follow the next instructions to install them.

1) Go to Sketch > Include Library > Manage Libraries or click on the Library Manager icon on the left sidebar.

2) Search for ESPAsyncWebServer and install the ESPAsyncWebServer by ESP32Async.

Install ESPAsyncWebServer in Arduino IDE

3) Search for RPAsyncTCP and install the RPAsyncTCP library by Ayush Sharma.

Install RPAsyncTCP Library Arduino IDE

Project Overview: Raspberry Pi Pico Web Server – Control Outputs

In this example, we’ll create a simple web page with two buttons to control a GPIO of the Raspberry Pi Pico.

1) When you access the Raspberry Pi Pico IP address in your web browser -> you’re making a request to the server (the Pico) on the root / URL.

2) The Pico responds with some HTML text to build a simple web page (as shown in the picture below).

Raspberry Pi Pico Web Server Control outputs

3) The web page shows two buttons to turn an LED on and off, connected to one GPIO of the Raspberry Pi Pico. When you click those buttons, a new request will be made to the following endpoints:

  • LED on button: request to /lighton?
  • LED off button: request to /lightoff?
Raspberry Pi Pico web server control outputs overview - Arduino IDE

4) We use an HTML template that adds the current LED state to the web page right after a request. In other words, after a request, the ESP32 sends the HTML page with the right GPIO state.

Note: it doesn’t actually confirm the state. It simply modifies the state based on the request that was made.

Wiring the Circuit

In this example, we’ll control an LED connected to the Raspberry Pi Pico GPIO 2. You can use any other suitable GPIO as long as you modify the code accordingly.

Parts Required

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Raspberry Pi Pico connected to LED circuit

Recommended reading: Raspberry Pi Pico Pinout Explained.

Code: Raspberry Pi Pico Web Server – Control Outputs

The following code builds the web server that serves the web page shown in the previous screenshot.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-web-server-outputs-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
// Import required libraries
#include <WiFi.h>
#include <RPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// LED to control
#define LED_PIN 2   

// Current LED state (to display on the web page)
String ledState = "OFF";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <title>Pico Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {
      font-family: Arial;
      display: inline-block;
      margin: 0px auto;
      text-align: center;
    }
    body {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }
    h1 {
      color: #333;
    }
    h2 {
      color: #666;
    }
    form {
      margin: 10px 0;
    }
    .button {
      background-color: #4CAF50;
      border: none;
      color: white;
      padding: 10px 20px;
      text-decoration: none;
      font-size: 20px;
      margin: 2px;
      cursor: pointer;
      border-radius: 5px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.2);
      transition: background-color 0.3s, box-shadow 0.3s;
    }
    .button:hover {
      background-color: #45a049;
      box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    }
    .button2 {
      background-color: #f44336;
    }
    .button2:hover {
      background-color: #da190b;
    }
    .state {
      font-size: 1.4em;
      margin: 20px 0;
      color: #444;
    }
  </style>
</head>
<body>
  <h1>Raspberry Pi Pico Web Server</h1>
  <h2>Led Control</h2>
  <p class="state">LED State: %STATE%</p>
  <form action="./lighton">
    <input type="submit" value="LED on" class="button" />
  </form>
  <form action="./lightoff">
    <input type="submit" value="LED off" class="button button2" />
  </form>
</body>
</html>
)rawliteral";

// Processor function
String processor(const String& var){
  if(var == "STATE"){
    return ledState;
  }
  return String();
}

void setup(){
  Serial.begin(115200);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print Pico Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html, processor);
  });

  // Route to turn LED ON
  server.on("/lighton", HTTP_GET, [](AsyncWebServerRequest *request){
    digitalWrite(LED_PIN, HIGH);
    ledState = "ON";
    request->send(200, "text/html", index_html, processor);
  });

  // Route to turn LED OFF
  server.on("/lightoff", HTTP_GET, [](AsyncWebServerRequest *request){
    digitalWrite(LED_PIN, LOW);
    ledState = "OFF";
    request->send(200, "text/html", index_html, processor);
  });

  // Start server
  server.begin();
}

void loop() {

}

View raw code

You need to insert your network SSID and password in the following lines. Then, you can upload the code to your board.

// Replace with your network credentials
const char* ssid = "";
const char* password = "";

How Does the Code Work?

Let’s take a quick look at the code to see how it works.

Include Libraries

First, include the required libraries to connect the Pico to your network and create the web server: WiFi, RPAsyncTCP, and ESPAsyncWebServer.

// Import required libraries
#include <WiFi.h>
#include <RPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

Network Credentials

Insert your network credentials in the following lines so that the Pico can connect to your network.

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

LED GPIO

We’ll control an LED connected to GPIO 2. You can use any other GPIO. You just need to change the following line of code accordingly.

#define LED_PIN 2   

LED State

We create a global variable to keep track of the LED state. When the program starts, the LED is off.

String ledState = "OFF";

AsyncWebServer on port 80

Create an AsyncWebServer object called server on port 80.

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

HTML Text

The index_html variable stores the HTML to build the web page. and it’s a raw string literal. It goes between R”rawliteral and rawliteral”. This allows you to write a string across multiple lines without the need for escape characters (\n) or ” in each line. This is very convenient for adding the HTML text as it is.

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <title>Pico Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {
        font-family: Arial;
        display: inline-block;
        margin: 0px auto;
        text-align: center;
    }
    body {
        max-width: 600px;
        margin: 0 auto;
        padding: 20px;
    }
    h1 {
        color: #333;
    }
    h2 {
        color: #666;
    }
    form {
        margin: 10px 0;
    }
    .button {
        background-color: #4CAF50;
        border: none;
        color: white;
        padding: 10px 20px;
        text-decoration: none;
        font-size: 20px;
        margin: 2px;
        cursor: pointer;
        border-radius: 5px;
        box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        transition: background-color 0.3s, box-shadow 0.3s;
    }
    .button:hover {
        background-color: #45a049;
        box-shadow: 0 4px 8px rgba(0,0,0,0.3);
    }
    .button2 {
        background-color: #f44336;
    }
    .button2:hover {
        background-color: #da190b;
    }
    .state {
        font-size: 1.4em;
        margin: 20px 0;
        color: #444;
    }
  </style>
</head>
<body>
  <h1>Raspberry Pi Pico Web Server</h1>
  <h2>Led Control</h2>
  <p class="state">LED State: %STATE%</p>
  <form action="./lighton">
    <input type="submit" value="LED on" class="button" />
  </form>
  <form action="./lightoff">
    <input type="submit" value="LED off" class="button button2" />
  </form>
</body>
</html>
)rawliteral";

This variable is stored in flash memory (PROGMEM).

We’ll serve the HTML text saved in the index_html variable to build the web page.

Let’s take a quick look at the HTML to build the web page to understand how it works.

This section that goes between the <style></style> tags is the CSS to customize the web page appearance.

<style>
  html {
      font-family: Arial;
      display: inline-block;
      margin: 0px auto;
      text-align: center;
  }
  body {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
  }
  h1 {
      color: #333;
  }
  h2 {
      color: #666;
  }
  form {
      margin: 10px 0;
  }
  .button {
      background-color: #4CAF50;
      border: none;
      color: white;
      padding: 10px 20px;
      text-decoration: none;
      font-size: 20px;
      margin: 2px;
      cursor: pointer;
      border-radius: 5px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.2);
      transition: background-color 0.3s, box-shadow 0.3s;
  }
  .button:hover {
      background-color: #45a049;
      box-shadow: 0 4px 8px rgba(0,0,0,0.3);
  }
  .button2 {
      background-color: #f44336;
  }
  .button2:hover {
      background-color: #da190b;
  }
  .state {
      font-size: 1.4em;
      margin: 20px 0;
      color: #444;
  }
</style>

Then, we add a heading and subheading to our web page. You can change it to whatever you like.

<h1>Raspberry Pi Pico Web Server</h1>
<h2>Led Control</h2>

The following paragraph is used to display the current LED state. %STATE% is a placeholder for the actual value of the LED state (ledState variable). Before sending the web page to the client, the processor() function will populate the placeholder with the right value.

<p class="state">LED State: %STATE%</p>

The following lines create the LED on and LED off buttons.

  • LED on button: request to /lighton?
  • LED off button: request to /lightoff?
<form action="./lighton">
    <input type="submit" value="LED on" class="button" />
</form>
<form action="./lightoff">
    <input type="submit" value="LED off" class="button button2" />
</form>

processor() function

The processor() function searches for the placeholders and replaces them with the values we define. In our case, we want to search for the STATE placeholder and replace it with the current LED state (ledState variable).

String processor(const String& var){
  if(var == "STATE"){
    return ledState;
  }
  return String();
}

setup()

In the setup(), initialize the Serial Monitor at a baud rate of 115200.

void setup(){
  Serial.begin(115200);

Set the LED pin as an OUTPUT and set it to LOW.

Connect the Pico to your local network using the credentials you’ve added earlier in the code.

// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi..");
}

Print the RPi Pico IP address. Later, you’ll use its IP address to access the web page.

// Print Pico Local IP Address
Serial.println(WiFi.localIP());

Handle Requests

The following lines handle the requests made to the Pico on the route / URL. This happens when you write the Pico IP address in the web browser.

When the Pico receives that request, it sends the text saved in the index_html variable to the client.

// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(200, "text/html", index_html, processor);
});

The parameters of the send() function are as follows:

  • 200 HTTP code: indicates the request was received
  • text/html: we’re sending a web page in HTML format
  • index_html: the content of the web page
  • processor: the processor function will search for placeholders and replace them with the right values

When it receives a request on the /lighton or /lightoff, it turns the LED on or off accordingly using the digitalWrite() function and updates the state of the ledState variable.

After that, we send the web page to the client. Notice that we also pass the processor function as a fourth parameter so that the web page is sent with the right GPIO state.

// Route to turn LED ON
server.on("/lighton", HTTP_GET, [](AsyncWebServerRequest *request){
  digitalWrite(LED_PIN, HIGH);
  ledState = "ON";
  request->send(200, "text/html", index_html, processor);
});

// Route to turn LED OFF
server.on("/lightoff", HTTP_GET, [](AsyncWebServerRequest *request){
  digitalWrite(LED_PIN, LOW);
  ledState = "OFF";
  request->send(200, "text/html", index_html, processor);
});

Finally, start the web server.

// Start server
server.begin();

loop()

Because this web server is asynchronous (the server.on() function will run when a request is received), we don’t need to add anything to the loop(). However, you can add more code, if that’s needed for your project.

void loop() {

}

Uploading the Code to the RPi Pico

To upload code to the Raspberry Pi Pico W, it needs to be in bootloader mode.

If the Raspberry Pi is currently running MicroPython firmware, you need to manually put it into bootloader mode. For that, connect the Raspberry Pi Pico to your computer while holding the BOOTSEL button at the same time. A new mass storage device window will open on your computer. You can ignore it and close that window.

Raspberry Pi Pico Bootloader mode

For future uploads using Arduino IDE, the board should go automatically into bootloader mode without the need to press the BOOTSEL button.

Now, open the top drop-down menu and click on Select other board and port…

arduino IDE 2 select other board and port

For the board, select Raspberry Pi Pico W or Raspberry Pi Pico 2W depending on the board you’re using.

The COM port might not show up on your first upload, so you need to tick the Show all ports option. Then, select the UF2 Board UF2 Devices option.

Arduino IDE 2 select Raspberry Pi Pico COM port.

Now, you can upload the code.

Arduino IDE 2 Upload Button

You should get a success message.

Upload code to Raspberry Pi Pico OK

Serial Monitor

Now, you need to get the Pico IP address. It will be printed in the Serial Monitor.

Open the Serial Monitor at a baud rate of 115200.

Arduino IDE Serial Monitor button

Then, unplug the Pico board and plug it back into your computer. It will establish a connection with the Serial Monitor and show its IP address after a few seconds. In my case, it’s 192.168.1.83.

Raspberry Pi Pico IP address printed in the Serial Monitor

Accessing the Web Server

Now, open a window in your web browser. Type the Pico’s IP address. It will serve the content saved in the index_html variable to build the following web page.

Raspberry Pi Pico Web Server Control outputs

Click the button to control the LED connected to GPIO 2 of the RPi Pico.

Raspberry Pi Pico Web Server - Turn LED Off
Raspberry Pi Pico Web Server - Turn LED On

Wrapping Up

In this tutorial, you learned how to create a web server with the Raspberry Pi Pico programmed with Arduino IDE and the ESPASyncWebServer library to control a GPIO of the RPi Pico.

This library is the one we usually use for ESP32 and ESP8266 web servers. The methods and functions for handling the web server on the ESP boards are the same for the RPi Pico.

We hope you’ve found this tutorial useful. Learn more about the RPi Pico with our resources:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.