Search code examples
processingportcommunication

How to display microbit's orientation as a 3d model in processing?


I am working on a project with my rocketry club. I want to have a microbit control the orientation of the fins to auto-stabilize the rocket. But first, I tried to make a processing code to display in real-time my micro-bit's orientation using its integrated gyroscope.

Here's my processing code :

import processing.serial.*;  // import the serial library

Serial myPort;  // create a serial object

float xRot = 0;  // variables to store the rotation angles
float yRot = 0;
float zRot = 0;
String occ[];
void setup() {
  size(400, 400, P3D);  // set the size of the window and enable 3D rendering

  String portName = Serial.list()[0];  // get the name of the first serial port
  myPort = new Serial(this, portName, 115200);  // open a connection to the serial port
  println((Object[]) Serial.list());
}

void draw() {
  background(255);  // clear the screen
  translate(width/2, height/2, 0);  // center the cube on the screen
  rotateX(xRot);  // apply the rotations
  rotateZ(yRot);
  rotateY(zRot);
  fill(200);  // set the fill color
  box(100);  // draw the cube
}

void serialEvent(Serial myPort) {
  // this function is called whenever new data is available
  // read the incoming data from the serial port
  String data = myPort.readStringUntil('\n');  // read the data as a string
  // print the incoming data to the console if it is not an empty string
  if (data != null) {
    println(data);
  }
  delay(10);
  if (data != null) {
    // split the string into separate values
    String[] values = split(data, ',');
    // convert the values to floats and store them in the rotation variables
    xRot = radians(float(values[0]));
    yRot = radians(float(values[1]));
    zRot = radians(float(values[2]));
  }
}

and here's the python code I have on my microbit

pitch = 0
roll = 0
x = 0
y = 0
z = 0

def on_forever():
    global pitch, roll, x, y, z
    pitch = input.rotation(Rotation.PITCH) + 90
    roll = input.rotation(Rotation.ROLL) + 90
    servos.P2.set_angle(pitch)
    servos.P1.set_angle(roll)
    x = input.rotation(Rotation.PITCH)
    y = input.rotation(Rotation.ROLL)
    z = 1
    serial.set_baud_rate(BaudRate.BAUD_RATE115200)
    serial.write_string(str(x) + "," + str(y) + "," + str(z) + "\n")
basic.forever(on_forever)

What happens when I run my code is that a cube appears and rotates weirdly for a short time, then, the cube stops and processing prints "Error, disabling serialEvent() for COM5 null".

Please help me out, I really need this code to be working!


Solution

  • Is this the documentation for the micro:bit Python API you're using ?

    input.rotation (as the reference mentions), returns accelerometer data:

    a number that means how much the micro:bit is tilted in the direction you ask for. This is a value in degrees between -180 to 180 in either the Rotation.Pitch or the Rotation.Roll direction of rotation.

    I'd start with a try/catch block to get more details on the actual error. e.g. is it the actual serial communication (e.g. resetting the baud rate over and over again (serial.set_baud_rate(BaudRate.BAUD_RATE115200)) instead of once) or optimistically assuming there will be 0 errors in serial communication and the string will always split to at least 3 values.

    Unfortunately I won't have the resources to test with an actual device, so the following code might contain errors, but hopefully it illustrates some of the ideas.

    I'd try simplifying/minimising the amount of data used on the micropython (and setting the baud rate once) and removing the need to read accelerometer data twice in the same iteration. If z is always 1 it can probably be ignored (you always rotate by 1 degree in Processing if necessary):

    pitch = 0
    roll = 0
    x = 0
    y = 0
    
    def on_forever():
        global pitch, roll, x, y
        x = input.rotation(Rotation.PITCH)
        y = input.rotation(Rotation.ROLL)
        pitch = x + 90
        roll  = y + 90
        servos.P2.set_angle(pitch)
        servos.P1.set_angle(roll)
        serial.write_string(str(x) + "," + str(y) + "\n")
    
    serial.set_baud_rate(BaudRate.BAUD_RATE115200)
    basic.forever(on_forever)
    

    On the processing side, I'd surround serial code with try/catch just in case anything went wrong and double check every step of the string parsing process:

    import processing.serial.*;  // import the serial library
    
    Serial myPort;  // create a serial object
    
    float xRot = 0;  // variables to store the rotation angles
    float yRot = 0;
    
    void setup() {
      size(400, 400, P3D);  // set the size of the window and enable 3D rendering
    
      String portNames = Serial.list();
      println(portNames);
    
      String portName = portNames[0];  // get the name of the first serial port
      try {
        myPort = new Serial(this, portName, 115200);  // open a connection to the serial port
        myPort.bufferUntil('\n');
      } catch (Exception e){
        println("error opening serial port " + portName + "\ndouble check USB is connected and the port isn't already open in another app")
        e.printStackTrace();
      }
      
    }
    
    void draw() {
      background(255);  // clear the screen
      translate(width/2, height/2, 0);  // center the cube on the screen
      rotateX(xRot);  // apply the rotations
      rotateZ(yRot);
      fill(200);  // set the fill color
      box(100);  // draw the cube
    }
    
    void serialEvent(Serial myPort) {
      try{
        // this function is called whenever new data is available
        // read the incoming data from the serial port
        String data = myPort.readString();  // read the data as a string
        // print the incoming data to the console if it is not an empty string
        if (data != null) {
          
          println(data);
          
          // cleanup / remove whitespace
          data = data.trim();
          // split the string into separate values
          String[] values = split(data, ',');
    
          if (values.length >= 2){
            // convert the values to floats and store them in the rotation variables
            xRot = radians(float(values[0]));
            yRot = radians(float(values[1]));
          }else{
            println("received unexpected number of values");
            printArray(values);
          }
          
        }
        
      } catch (Exception e){
        println("error reading serial:");
        e.printStackTrace();
      }
        
    }
    

    (If the above still produces serial errors I'd also write a separate test that doesn't use the servos, just in case internally some servo pwm timing/interrupts alongside accelerometer data polling interferes with serial communication for some reason.)

    (AFAIK there is no full IMU support on the micro::bit (e.g. accelerometer + gyroscope + magnetometer (+ ideally sensor fusion)), just accelerometer + magnetometer. If you want to get basic rotation data, but don't care for full 3D orientation of the device should suffice. Otherwise you'd need an IMU (e.g BNO055 or newer) which you can connect to the micro via I2C (but will also probably need to implement the communications protocol to the sensor if someone else hasn't done so already).(In theory I see there's Python support for the Nordic nRF52840 chipset, however microbit uses nRF51822 for v1 and nRF52833 for v2 :/). Depending on your application you might want to switch to a different microcontroller altogether. (for example the official Arduino 33 BLE has a built-in accelerometer (and Python support) (and even supports TensorFlow Lite))