Pozyx

Accurate positioning

Home > Documentation > Tutorials > Tutorial 3: Orientation 3D

Tutorial 3: Orientation 3D

Go to Arduino versionGo to Python version

In this example, the sensor data is read from the pozyx shield (either the shield mounted on the Arduino, or a remote shield). To visualize all the data, we use Processing. Below a screenshot of the Processing program showing all the sensor data graphically.

This example requires one or two pozyx shields and one Arduino. Open up the orientation 3D example in the Arduino IDE under File > Examples > Pozyx > orientation_3D.

This example requires one or two pozyx shields. If all tools are installed, open up orientation_3D.py in the Pozyx library’s tutorial folder. Probably, the path to this file will be "Downloads/Pozyx-Python-library/tutorials/orientation_3D.py". You can run the script from either command line or a text editor that allows running the scripts as well. If you're on Windows and have installed Python, you might be able to simply double-click it. If you you get an error, you likely forgot to install pythonosc, you can do this easily using pip install python-osc.

Open up the pozyx_orientation3D.pde sketch in Processing, as this example will be directly visualized.

Plug and play

In Processing, make sure serial is set to true, and that your serialPort matches the one used by your Arduino. Upload the Arduino sketch, and, when this is done, press the play button in Processing.

In Processing, make sure serial is set to false. Unless you need port 8888 for something else, you can leave both the Python script and Processing sketch intact. The data will automatically be sent using OSC on port 8888.

You should be able to physically rotate your Pozyx now and directly see it rotate in Processing as well. There are plots on the side and at the top, but what do they all mean?

Understanding the sensor data

  • Acceleration (g):
    the acceleration is measured along 3 axes (i.e., in 3 directions) which is shown by three different colors on the plot. The acceleration is expressed in g's (from gravity). 1g is exactly the acceleration from earth's gravitation pull (1g = 9.81m/s2). Because the acceleration from gravity is always present and equal to 1g it can be used to determine how the device is tilted or pitched. Try rotating the Pozyx device by 90 degrees (slowly) and you will see the the acceleration change from one axis to the other.
  • Magnetic field strength (µT):
    the magnetic field strength is also measured along 3 axes and is expressed in µT. It can be used to measure the earth's magnetic field which varies between 30 and 60µT over the earth's surface. The measured magnetic field strength can be used to estimate the magnetic north (similar to a compass). However, magnetic fields exist wherever electric current flows or magnetic materials are present. Because of this, they will influence the sensor data and it will become impossible to determine the magnetic north exactly. Try holding a magnet or something iron close to the pozyx device and you will see the magnetic field strength fluctuate.
  • Angular velocity (deg/s):
    The angular velocity is measured by the gyroscope and measures how fast the device rotates around itself. By integrating the angular velocity it is possible to obtain the angles of the device. However, due to drift this method does not give accurate results for a prolonged time. What is drift you wonder? Well if the device is standing still, the angular velocity should be exactly equal to zero. This is not the case however, it will be slightly different and this error will accumulate over time when integrating to angles.
  • 3D Orientation:
    The 3D orientation is shown by the 3D model in the middle. The orientation is computed by combining the sensor data from all three sensors together. By combining all sensors it is possible to overcome the limitations of each sensor separately. The 3D orientation can be expressed in Euler angles: yaw, pitch, roll or in quaternions. Quaternions are a mathematical representation using 4 numbers. In many situations, quaternions are preferred because they do not have singularity problems like the Euler angles.
  • Gravity vector (g):
    If the pozyx device is standing still, the acceleration is exactly equal to the gravity. The gravity vector is shown by the black line and is always pointing down. Notice that even when moving the device (which introduces an additional acceleration) the gravity vector still points down. This is due to the fusion algorithm that can separate gravity from an arbitrary acceleration;
  • Linear acceleration in body coordinates (g):
    The linear acceleration is the acceleration that remains after the gravity has been removed. When you hold the device horizontal, pointed forward, and shake it from left to right the circle will also move from left to right in the plot. However, if you rotate the device by 90 degrees and shake it again from left to right, the circle will now move in a different direction. This is because the linear acceleration is expressed in body coordinates, i.e., relative to the device. Note that all the above sensor data is expressed in body coordinates.
  • Linear acceleration in world coordinates (g):
    Once the orientation of the device is known, it is possible to express the acceleration in world coordinates. By doing this, the rotation of the device no longer affects the linear acceleration in the plot.

The code explained

We'll now go over the code that's needed to retrieve all sensor data. Looking at the code's parameters, we can see that we can once again use a remote Pozyx for this. This means that you could for example attach a Pozyx to a ball, and watch the ball's spin directly on your screen as well with the Pozyx.

Imports and setup

The imports are the default Pozyx library imports and Wire.h for the I²C functionality encountered in the previous tutorials.
The Pozyx and serial connection are then initialized, and the last measured time is set.

We import the pypozyx library and the IMU interrupt flag from its bitmasks, and pythonosc so that we can send the sensor data to Processing over OSC. The addition of the default time library import allows us to measure the time between different measurements.
We initialize the PozyxSerial and UDP socket over which we'll send the data, and the setup then sets the last measured time right.

#include <Pozyx.h>
#include <Pozyx_definitions.h>
#include <Wire.h>
from time import time

from pypozyx import *
from pypozyx.definitions.bitmasks import POZYX_INT_MASK_IMU
from pythonosc.osc_message_builder import OscMessageBuilder
from pythonosc.udp_client import SimpleUDPClient
void setup()
{
  Serial.begin(115200);

  if(Pozyx.begin(false, MODE_INTERRUPT, POZYX_INT_MASK_IMU) == POZYX_FAILURE){
    Serial.println("ERROR: Unable to connect to POZYX shield");
    Serial.println("Reset required");
    delay(100);
    abort();
  }

  if(!remote)
    remote_id = NULL;

  last_millis = millis();
  delay(10);
}
def setup(self):
    """There is no specific setup functionality"""
    self.current_time = time()

Loop

The main loop deserves to be elaborated on. The Pozyx's IMU sensors trigger an interrupt flag when there is new data available, and in the code we wait for this flag to trigger explicitly.

When it does, or when we read the sensor data remotely, the code will read out all sensor data and it's calibration status from the (remote) Pozyx, and then print it serially as comma separated values, which will be converted to standard units and interpreted by Processing. This is done in getAllRawSensorData, where the sensor registers are read directly and their values are stored in a sensor_raw_t struct. There is also a getAllSensorData function available, where this raw sensor data gets converted to standard units and is stored in a sensor_data_t struct, but we choose the former because we prefer this conversion to be done in Processing, on a powerful CPU, offsetting the Arduino's processing and allowing a faster retrieval rate of this IMU data.

When it doesn't, it tries again.

When checkForFlag returns POZYX_SUCCESS, meaning that POZYX_INT_MASK_IMU was raised and new IMU data is available, or when we're retrieving sensor data remotely, all sensor data and the calibration status will be read from the (remote) Pozyx and packed in a OSC message, which is then interpreted by the Processing sketch. You can see that there is a SensorData object available for this, which automatically converts the sensor data to its respective standard units.

void loop(){
  sensor_raw_t sensor_raw;
  uint8_t calibration_status = 0;
  int dt;
  int status;
  if(remote){
     status = Pozyx.getRawSensorData(&sensor_raw, remote_id);
     status &= Pozyx.getCalibrationStatus(&calibration_status, remote_id);
    if(status != POZYX_SUCCESS){
      return;
    }
  }else{
    if (Pozyx.waitForFlag(POZYX_INT_STATUS_IMU, 10)){
      Pozyx.getRawSensorData(&sensor_raw);
      Pozyx.getCalibrationStatus(&calibration_status);
    }else{
      uint8_t interrupt_status = 0;
      Pozyx.getInterruptStatus(&interrupt_status);
      return;
    }
  }

  dt = millis() - last_millis;
  last_millis += dt;
  // print time difference between last measurement in ms, sensor data, and calibration data
  Serial.print(dt, DEC);
  Serial.print(",");
  printRawSensorData(sensor_raw);
  Serial.print(",");
  // will be zeros for remote devices as unavailable remotely.
  printCalibrationStatus(calibration_status);
  Serial.println();
}
def loop(self):
    """Gets new IMU sensor data"""
    sensor_data = SensorData()
    calibration_status = SingleRegister()
    if self.remote_id is not None or self.pozyx.checkForFlag(POZYX_INT_MASK_IMU, 0.01) == POZYX_SUCCESS:
        status = self.pozyx.getAllSensorData(sensor_data, self.remote_id)
        status &= self.pozyx.getCalibrationStatus(calibration_status, self.remote_id)
        if status == POZYX_SUCCESS:
            self.publishSensorData(sensor_data, calibration_status)

The calibration status gives information about the quality of the sensor orientation data. When the system is not fully calibrated, the orientation estimation can be bad. The calibration status is a 8-bit variable where every 2 bits represent a different piece of calibration info.

What's next?

You're at the end of the standard tutorials that introduce you to all of Pozyx's basic functionality: ranging, positioning, and its IMU. If you haven't already done so and have the number of devices necessary, you can read the multitag tutorial. When you start work on your own prototype, don't be afraid to delve into our documentation which should suffice for all your needs.

There's also the chat room tutorial, for which you will need two Arduinos and two Pozyxs, in which you will build a small chat room where you use Pozyx to communicate between two computers. This opens the door to adding real communication and control to two Arduino-enabled Pozyxs, as you can send and interpret Arduino commands this way as well.

Home > Documentation > Tutorials > Tutorial 3: Orientation 3D