Physical Computing: Class 8 and 9

Serial communication

Asynchronous communication

https://itp.nyu.edu/physcomp/labs/labs-serial-communication/lab-two-way-duplex-webserial-communication

After doing the week’s lab, I found some more interesting code in GA class that I tried and blogged about.

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.