The Arduino Component

The Arduino component connects a page to a small board over WebUSB, to move servos, switch outputs and read sensors. It works with three boards, each running its own Protobject firmware. The page does not need to know which one is plugged in: the same code drives all of them.

Board Firmware How it is installed
Arduino Leonardo protobject-leonardo.ino from the Arduino IDE
Seeed Studio XIAO RP2040 protobject-xiao-rp2040.uf2 by dragging a file, no IDE
Waveshare RP2040-Zero protobject-rp2040-zero.uf2 by dragging a file, no IDE

Pins#

Code always uses the Leonardo's pin numbers. Each firmware maps them onto the board it runs on, so a program written for one board runs unchanged on the others.

In code Arduino Leonardo XIAO RP2040 RP2040-Zero
output 3 3 D3 GP3
output 11 11 D10 GP11
output 13 13, and the L LED D4, and the on-board LED GP13
servo 5 5 D5 GP5
servo 6 6 D6 GP6
input d7, d8, d9 7, 8, 9 D7, D8, D9 GP7, GP8, GP9
input a0, a1, a2 A0, A1, A2 A0, A1, A2 (pads D0, D1, D2) GP26, GP27, GP28

The XIAO has no pins 11 and 13, so its firmware moves them to free pads. The RP2040-Zero has every digital pin under its own number; its only LED is an addressable RGB one, so nothing on it follows pin 13. On both boards the analog inputs are GPIO 26, 27 and 28, because those are the only pins of the RP2040 that can read an analog value.

Servos are powered from the board's 5V pin, with GND shared. Analog inputs read from 0 to 1023 on every board.

Programming the Board#

XIAO RP2040 and RP2040-Zero#

No Arduino IDE is needed:

  1. Download the firmware for your board: XIAO RP2040 or RP2040-Zero.
  2. Hold the board's BOOT button (marked B on the XIAO) while plugging it into the computer. A drive called RPI-RP2 appears.
  3. Drag the file onto that drive. The board restarts by itself and is ready.

Both boards run the same sketch, protobject-rp2040.ino, which picks its pins from the board it is built for. To build it yourself in the Arduino IDE:

  • Board: Seeed XIAO RP2040 or Waveshare RP2040 Zero, from the Raspberry Pi Pico/RP2040 core by Earle Philhower;
  • Tools › USB Stack: Adafruit TinyUSB, which provides WebUSB;
  • Library: ArduinoJson, from the Library Manager.

Arduino Leonardo#

The Leonardo is programmed from the Arduino IDE with the following code, which needs the Servo and ArduinoJson libraries (Library Manager) and the WebUSB library. If you're not familiar with WebUSB, follow this guide.

#include <Servo.h>
#include <WebUSB.h>
#include <ArduinoJson.h>

WebUSB WebUSBSerial(1 /* https:// */, "");
#define Serial WebUSBSerial

Servo rservo;
Servo lservo;
int defaultL = 1500;
int defaultR = 1500;
DynamicJsonDocument dynamicJSONDocument(128);

void setup() {
  while (!Serial) {
    ;
  }
  Serial.begin(9600);
  Serial.write("Sketch begins.\r\n> ");
  Serial.flush();
  lservo.attach(5); 
  rservo.attach(6); 
  pinMode(7, INPUT);
  pinMode(8, INPUT);
  pinMode(9, INPUT);
  pinMode(3, OUTPUT); 
  pinMode(11, OUTPUT); 
  pinMode(13, OUTPUT); 
  rservo.writeMicroseconds(defaultR);
  lservo.writeMicroseconds(defaultL);  
}

void loop() {
  const auto deser_err = deserializeJson(dynamicJSONDocument, Serial);
  if (!deser_err){
      JsonObject obj = dynamicJSONDocument.as<JsonObject>();
      if (obj["device"] == "robot") { //and servos in general, continuous and angular
        int rightSpeed = obj["right"];
        int leftSpeed = obj["left"];
        rservo.writeMicroseconds(defaultR-rightSpeed);
        lservo.writeMicroseconds(defaultL+leftSpeed);
      }
      if (obj["device"] == "digitalWrite") { //pins 3,11,13
        int pin = obj["pin"];
        int val = obj["val"];
        if (val==1){
          digitalWrite(pin, HIGH);
        } else {
          digitalWrite(pin, LOW);
        }
        
      }
      if (obj["device"] == "analogWrite") { //pins 3,11,13
        int pin = obj["pin"];
        int val = obj["val"];
        analogWrite(pin, val);
      }
      if (obj["device"] == "readAll") { //digital 7,8,9 //analog 0,1,2
        StaticJsonDocument<200> doc;
        doc["d7"] = digitalRead(7);
        doc["d8"] = digitalRead(8);
        doc["d9"] = digitalRead(9);
        doc["a0"] = analogRead(0);
        doc["a1"] = analogRead(1);
        doc["a2"] = analogRead(2);
        serializeJson(doc, Serial);
        Serial.flush();
      }
    }
}

Once programmed, any of the three boards can be connected to a PC or an Android smartphone via USB. No Arduino IDE is required to work with it. WebUSB is available in Chrome and Edge; a board can be used by one page at a time, so close other pages that are using it before connecting.

Starting the Arduino Component#

To start connecting to a board, call the start method. This will display a popup for the user to confirm the connection, and then the browser's list of boards to choose from.

Protobject.Arduino.start();

Handling Arduino Data#

To handle data received from the board, set a callback function using the onData method. This callback function will be called with the data whenever it is received.

Protobject.Arduino.onData((data) => {
    console.log('Received data:', data);
});

It receives data from a0, a1, a2 and d7, d8, d9.

Handling Disconnection#

To know when the board stops answering — usually because it was unplugged — set a callback with the onReceiveError method. After it is called, call start() again to reconnect.

Protobject.Arduino.onReceiveError((error) => {
    console.log('The board was disconnected:', error);
});

Sending Commands to Arduino#

You can send various commands to the board to control its pins.

Digital Write#

To set the value of a digital pin:

Protobject.Arduino.digitalWrite({ pin: 3, value: 1 }); // Sets pin 3 to HIGH

Valid pins are 3, 11 and 13.

Analog Write#

To set the value of an analog pin:

Protobject.Arduino.analogWrite({ pin: 3, value: 128 }); // Sets pin 3 to a PWM value of 128

Valid pins are 3, 11 and 13.

Servo Write#

To set the position of a servo motor connected to pin 5 or 6. The value is an offset from the centre, in microseconds of pulse: 0 is the centre, and a servo sweeps its whole range between about -950 and 950. The pulse is 1500 + value on pin 5 and 1500 − value on pin 6, so the two servos of a robot turn in opposite directions.

Protobject.Arduino.servoWrite({ pin: 5, value: 500 }); // Servo on pin 5: pulse of 1500 + 500 = 2000 microseconds
//https://www.arduino.cc/reference/en/libraries/servo/writemicroseconds/

Continuous Servo Write#

To set the speed of a continuous servo motor connected to pin 5 or 6. The value works as in servoWrite: 0 stops the motor, and the further from 0, the faster it turns, one way or the other.

Protobject.Arduino.contServoWrite({ pin: 5, value: 500 }); // Servo on pin 5: pulse of 1500 + 500 = 2000 microseconds
//https://www.arduino.cc/reference/en/libraries/servo/writemicroseconds/

Usage Example#


Protobject.Arduino.start();
Protobject.Arduino.onData((data) => {
    console.log('Received data:', data);
});

let myButton = new Protobject.Button();

myButton.onPressed(function(){
  Arduino.digitalWrite({ pin: 13, value: 1 }); // Sets pin 13 to HIGH
})

myButton.onRelease(function(){
  Arduino.digitalWrite({ pin: 13, value: 0 }); // Sets pin 13 to LOW
})

To try every pin of a board from the browser, open the board test demo.