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:
- Download the firmware for your board: XIAO RP2040 or RP2040-Zero.
- Hold the board's BOOT button (marked B on the XIAO) while plugging it into the computer. A drive called
RPI-RP2appears. - 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.