Serial communication
Asynchronous communication
- https://medium.com/@m.valizadeh/async-programming-in-arduino-unleashing-the-power-of-non-blocking-code-45205a691938
- https://stackoverflow.com/questions/67289670/how-to-make-serial-reading-asynchronously-to-main-loop
After doing the week’s lab, I found some more interesting code in GA class that I tried and blogged about.
- https://itp.nyu.edu/physcomp/lab-intro-to-serial-communications/
- https://itp.nyu.edu/physcomp/labs/labs-serial-communication/lab-webserial-input-to-p5-js/
- https://itp.nyu.edu/physcomp/labs/labs-serial-communication/lab-webserial-output-from-p5-js/
From Arduino to p5.js
/*
Adapted from https://itp.nyu.edu/physcomp/labs/labs-serial-communication/lab-webserial-input-to-p5-js/
Corresponding Arduino code can be found at the bottom of this sketch
*/
//I got this code from jack.b.du sketch https://editor.p5js.org/jackbdu/sketches/eEQIp8v6k
// variable to hold an instance of the p5.webserial library:
const serial = new p5.WebSerial();
// HTML button object:
let portButton;
let inData; // for incoming serial data
let outByte = 0; // for outgoing data
let redValue = 0; // for number value converted from inData
function setup() {
createCanvas(400, 300); // make the canvas
// check to see if serial is available:
if (!navigator.serial) {
alert("WebSerial is not supported in this browser. Try Chrome or MS Edge.");
}
// if serial is available, add connect/disconnect listeners:
navigator.serial.addEventListener("connect", portConnect);
navigator.serial.addEventListener("disconnect", portDisconnect);
// check for any ports that are available:
serial.getPorts();
// if there's no port chosen, choose one:
serial.on("noport", makePortButton);
// open whatever port is available:
serial.on("portavailable", openPort);
// handle serial errors:
serial.on("requesterror", portError);
// handle any incoming serial data:
serial.on("data", serialEvent);
serial.on("close", makePortButton);
makePortButton();
}
function draw() {
// use inValue as the red value for background
background(redValue,0,0);
fill(255);
text("raw incoming data: " + inData, 30, 50);
}
// if there's no port selected,
// make a port select button appear:
function makePortButton() {
// create and position a port chooser button:
portButton = createButton("choose port");
portButton.position(10, 10);
// give the port button a mousepressed handler:
portButton.mousePressed(choosePort);
}
// make the port selector window appear:
function choosePort() {
if (portButton) portButton.show();
serial.requestPort();
}
// open the selected port, and make the port
// button invisible:
function openPort() {
// wait for the serial.open promise to return,
// then call the initiateSerial function
serial.open().then(initiateSerial);
// once the port opens, let the user know:
function initiateSerial() {
console.log("port open");
}
// hide the port button once a port is chosen:
// if (portButton) portButton.hide();
}
// pop up an alert if there's a port error:
function portError(err) {
alert("Serial port error: " + err);
}
// read any incoming data as a byte
function serialEvent() {
// read one byte of data and store the raw data in inData
inData = serial.read();
// convert raw data to a number
redValue = Number(inData);
// only use console.log for debugging
// remove it after testing, otherwise console.log will
// slow down or even crash the sketch
// console.log(inData);
}
// try to connect if a new serial port
// gets added (i.e. plugged in via USB):
function portConnect() {
console.log("port connected");
serial.getPorts();
}
// if a port is disconnected:
function portDisconnect() {
serial.close();
console.log("port disconnected");
}
function closePort() {
serial.close();
}
/* Program your Arduino to read the analog input as follows:
int potPin = A0;
void setup() {
Serial.begin(9600); // initialize serial communications
}
void loop() {
// read the input pin:
int potReading = analogRead(potPin);
// remap the pot value to fit in 1 byte:
int mappedPotReading = map(potReading, 0, 1023, 0, 255);
// write/send one byte to the serial port:
Serial.write(mappedPotReading);
// slight delay to stabilize the ADC:
delay(1);
}
*/
From p5.js to Arduino
/*
Adapted from https://itp.nyu.edu/physcomp/labs/labs-serial-communication/lab-webserial-output-from-p5-js/
Corresponding Arduino code can be found at the bottom of this sketch
*/
// I got this code from jack.b.du sketch https://editor.p5js.org/jackbdu/sketches/5oGNavF6qZ
// variable to hold an instance of the p5.webserial library:
const serial = new p5.WebSerial();
// HTML button object:
let portButton;
let inData; // for incoming serial data
let outByte = 0; // for outgoing data
let redValue = 0; // for number value converted from inData
function setup() {
createCanvas(400, 300); // make the canvas
// check to see if serial is available:
if (!navigator.serial) {
alert("WebSerial is not supported in this browser. Try Chrome or MS Edge.");
}
// if serial is available, add connect/disconnect listeners:
navigator.serial.addEventListener("connect", portConnect);
navigator.serial.addEventListener("disconnect", portDisconnect);
// check for any ports that are available:
serial.getPorts();
// if there's no port chosen, choose one:
serial.on("noport", makePortButton);
// open whatever port is available:
serial.on("portavailable", openPort);
// handle serial errors:
serial.on("requesterror", portError);
// handle any incoming serial data:
serial.on("data", serialEvent);
serial.on("close", makePortButton);
}
function draw() {
// black background, white text:
background(redValue, 0, 0);
fill(255);
// display the incoming serial data as a string:
text("raw incoming data: " + inData, 30, 50);
text("click and drag mouse up and down on the canvas", 30, 80);
text("press key 0 to 9 (click canvas to activate the canvas first)", 30, 110);
}
function mouseDragged() {
// map the mouseY to a range from 0 to 255:
outByte = byte(map(mouseY, 0, height, 0, 255));
// send it out the serial port:
serial.write(outByte);
}
function keyPressed() {
if (key >= 0 && key <= 9) { // if the user presses 0 through 9
outByte = (key * 25); // map the key to a range from 0 to 225
serial.write(outByte); // send it out the serial port
}
}
// if there's no port selected,
// make a port select button appear:
function makePortButton() {
// create and position a port chooser button:
portButton = createButton("choose port");
portButton.position(10, 10);
// give the port button a mousepressed handler:
portButton.mousePressed(choosePort);
}
// make the port selector window appear:
function choosePort() {
serial.requestPort();
}
// open the selected port, and make the port
// button invisible:
function openPort() {
// wait for the serial.open promise to return,
// then call the initiateSerial function
serial.open().then(initiateSerial);
// once the port opens, let the user know:
function initiateSerial() {
console.log("port open");
}
// hide the port button once a port is chosen:
if (portButton) portButton.hide();
}
// read any incoming data as a byte:
function serialEvent() {
// read a byte from the serial port:
inData = serial.read();
// convert raw data to a number
redValue = Number(inData);
}
// pop up an alert if there's a port error:
function portError(err) {
alert("Serial port error: " + err);
}
// try to connect if a new serial port
// gets added (i.e. plugged in via USB):
function portConnect() {
console.log("port connected");
serial.getPorts();
}
// if a port is disconnected:
function portDisconnect() {
serial.close();
console.log("port disconnected");
}
function closePort() {
serial.close();
}
/* Program your Arduino to write the analog input as follows:
int ledpin = 2;
// int speakerpin = 2; // if you are using a speaker instead of an LED
void setup() {
Serial.begin(9600); // initialize serial communications
pinMode(ledpin, OUTPUT);
}
void loop() {
if (Serial.available() > 0) { // if there's serial data available
int inByte = Serial.read(); // read one byte of data
Serial.write(inByte); // send it back out as raw binary data
analogWrite(ledpin, inByte); // use it to set the LED brightness
// if you're using a speaker instead of an LED, uncomment line below and comment out the previous line:
// tone(speakerpin, inByte*10); // play tone on pin speakerpin
}
}
*/
Arduino IMU 3D visualizer
https://editor.p5js.org/jp7469/sketches/6ihFNYZmI
/*
p5.js Madgwick visualizer
Based on Helena Bisby's Processing Madgwick visualizer
Takes incoming serial data in the following form:
heading,pitch,roll\n
Uses heading, pitch, and roll numbers (all floats)
to position a 3D model of an Arduino Nano onscreen
created 4 Aug 2019
modified 12 Jun 2022
by Tom Igoe
original source code: https://github.com/ITPNYU/physcomp/tree/main/Labs/LabIMUs/MadgwickVisualizer
Corresponding Arduino code can be found at the bottom of this sketch
*/
//I got this code from jack.b.du sketch https://editor.p5js.org/jackbdu/sketches/US1KtE0FP
// Hold your arduino flat (pins facing down) with usb-c port facing away from yourself
// heading/yaw is rotation on horizontal surface closewise (-) and counterclockwise (+)
// pitch is tilting forward (+) and backforward (-)
// roll is tilting left (-) and right (+)
// variable to hold an instance of the p5.webserial library:
const serial = new p5.WebSerial();
// HTML button object:
let portButton;
// orientation variables:
let heading = 0.0;
let pitch = 0.0;
let roll = 0.0;
function setup() {
createCanvas(500, 600, WEBGL); // make the canvas
// check to see if serial is available:
if (!navigator.serial) {
alert("WebSerial is not supported in this browser. Try Chrome or MS Edge.");
}
// if serial is available, add connect/disconnect listeners:
navigator.serial.addEventListener("connect", portConnect);
navigator.serial.addEventListener("disconnect", portDisconnect);
// check for any ports that are available:
serial.getPorts();
// if there's no port chosen, choose one:
serial.on("noport", makePortButton);
// open whatever port is available:
serial.on("portavailable", openPort);
// handle serial errors:
serial.on("requesterror", portError);
// handle any incoming serial data:
serial.on("data", serialEvent);
serial.on("close", makePortButton);
}
function draw() {
// update the drawing:
background(255); // set background to white
push(); // begin object to draw
// variables for matrix translation:
let c1 = cos(radians(roll));
let s1 = sin(radians(roll));
let c2 = cos(radians(pitch));
let s2 = sin(radians(pitch));
let c3 = cos(radians(heading));
let s3 = sin(radians(heading));
applyMatrix(
c2 * c3,
s1 * s3 + c1 * c3 * s2,
c3 * s1 * s2 - c1 * s3,
0,
-s2,
c1 * c2,
c2 * s1,
0,
c2 * s3,
c1 * s2 * s3 - c3 * s1,
c1 * c3 + s1 * s2 * s3,
0,
0,
0,
0,
1
);
// draw arduino board:
drawArduino();
pop(); // end of object
}
// draws the Arduino Nano:
function drawArduino() {
// the base board:
stroke(0, 90, 90); // set outline color to darker teal
fill(0, 130, 130); // set fill color to lighter teal
box(300, 10, 120); // draw Arduino board base shape
// the CPU:
stroke(0); // set outline color to black
fill(80); // set fill color to dark grey
translate(30, -6, 0); // move to correct position
box(60, 0, 60); // draw box
// the radio module:
stroke(80); // set outline color to grey
fill(180); // set fill color to light grey
translate(80, 0, 0); // move to correct position
box(60, 15, 60); // draw box
// the USB connector:
translate(-245, 0, 0); // move to correct position
box(35, 15, 40); // draw box
}
// if there's no port selected,
// make a port select button appear:
function makePortButton() {
// create and position a port chooser button:
portButton = createButton("choose port");
portButton.position(10, 10);
// give the port button a mousepressed handler:
portButton.mousePressed(choosePort);
}
// make the port selector window appear:
function choosePort() {
serial.requestPort();
}
// open the selected port, and make the port
// button invisible:
function openPort() {
// wait for the serial.open promise to return,
// then call the initiateSerial function
serial.open().then(initiateSerial);
// once the port opens, let the user know:
function initiateSerial() {
console.log("port open");
serial.write("x");
}
// hide the port button once a port is chosen:
if (portButton) portButton.hide();
}
// read any incoming data:
function serialEvent() {
// read from port until new line:
let inString = serial.readStringUntil("\r\n");
if (inString != null) {
let list = split(trim(inString), ",");
if (list.length > 2) {
// conver list items to floats:
heading = float(list[0]);
pitch = float(list[2]);
roll = float(list[1]);
// send a byte to the microcontroller to get new data:
serial.write("x");
}
}
}
// pop up an alert if there's a port error:
function portError(err) {
alert("Serial port error: " + err);
}
// try to connect if a new serial port
// gets added (i.e. plugged in via USB):
function portConnect() {
console.log("port connected");
serial.getPorts();
}
// if a port is disconnected:
function portDisconnect() {
serial.close();
console.log("port disconnected");
}
/*
// Madgwick orientation calculation
// Uses Arduino MadgwickAHRS library to calculate heading, pitch, and roll
// on an Arduino Nano 33 IoT, using the onboard LSM6DS3 IMU.
// For big fun, connect this to p5.js sketch MadgwickVisualizer
// created 4 Aug 2019
// updated 25 Aug 2019
// by Tom Igoe
// original source code: https://github.com/ITPNYU/physcomp/blob/main/Labs/LabIMUs/Nano33Madgwick/Nano33Madgwick.ino
#include <Arduino_LSM6DS3.h>
#include <MadgwickAHRS.h>
// initialize a Madgwick filter:
Madgwick filter;
// sensor's sample rate is fixed at 104 Hz:
const float sensorRate = 104.00;
// values for orientation:
float roll = 0.0;
float pitch = 0.0;
float heading = 0.0;
void setup() {
Serial.begin(9600);
// attempt to start the IMU:
if (!IMU.begin()) {
Serial.println("Failed to initialize IMU");
// stop here if you can't access the IMU:
while (true)
;
}
// start the filter to run at the sample rate:
filter.begin(sensorRate);
}
void loop() {
// values for acceleration & rotation:
float xAcc, yAcc, zAcc;
float xGyro, yGyro, zGyro;
float xMag, yMag, zMag;
// check if the IMU is ready to read:
if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) {
// read accelerometer & gyrometer:
IMU.readAcceleration(xAcc, yAcc, zAcc);
IMU.readGyroscope(xGyro, yGyro, zGyro);
// update the filter, which computes orientation:
filter.updateIMU(xGyro, yGyro, zGyro, xAcc, yAcc, zAcc);
// print the heading, pitch and roll
roll = filter.getRoll();
pitch = filter.getPitch();
heading = filter.getYaw();
}
// if you get a byte in the serial port,
// send the latest heading, pitch, and roll:
if (Serial.available()) {
char input = Serial.read();
Serial.print(heading);
Serial.print(",");
Serial.print(pitch);
Serial.print(",");
Serial.println(roll);
}
}
*/
The question in this week
- what is the different between this p5.webserial library and this p5.webserial library?
- why ITP using the first p5.webserial? I’m just curious about the background to use that.