Magnetometer Calibration Wirelessly Over BLE

Calibration for the magnetometer is essential for any operation that involves using magnetometer data. Among the available PC tools for calibration, MotionCal by Paul Stoffregen is considered the best. It provides users with the ability to visualize data and check important statistical information such as gap, fit error, and variance. 

For more about magnetometer calibration process, you can check: “Magnetometer Soft Iron and Hard Iron Calibration: Why and How” article.

However, I have found it inconvenient to connect my board to a PCB every time I need to calibrate the magnetometer. This can be particularly problematic for PCBs inside closed enclosures or in mass production, where connecting via USB to a PC may become impossible. Due to this challenge, I have come up with a trick to reuse MotionCal over a BLE (Bluetooth Low Energy) connection. 

Steps to use this hack


  1. A firmware with 3 characteristics in one BLE service. One characteristic for Acc. and Gyro. data, one characteristic for Mag. data and one characteristic for receiving calibration results. A reference Arduino firmware is provided in the repo.
  2. NRF52 USB dongle with Python pc-ble-driver-py library installed. Flash the dongle with connectivity firmware (if needed). I use connectivity_4.1.4_usb_with_s132_5.1.0
  3. Linux/Ubuntu OS. Although, Windows can be supported in a future update with few tweaks related to virtual ports on Windows (contributions as a PR are welcomed). 


  1. Download the repository of my MotionCal fork. Run the provided MotionCal build.
  2. Run the following command inside the terminal to create the virtual ports. 
    sudo socat -d -d pty,link=/dev/ttyACM11,echo=0,perm=0777 pty,link=/dev/ttyACM10,echo=0,perm=0777
  3. In MotionCal, select ttyACM11 in port list. 
  4. Run the Python script after connecting the NRF52 USB dongle to PC.  Change ttyACM0 in the command according to your port assigned to the dongle. 
    python3 NRF52 /dev/ttyACM0
  5. After the calibration is caluclated, press ‘Send Cal’ button in MotionCal, the python script will read the message from the tool and send it back to the firmware via BLE.
A comparison of MotionCal before and after the tweak. In the tweak, a Python script receives magnetometer data over BLE using NRF USB dongle and passes it using a virtual port to MotionCal.

How it is possible to do this tweak? 

The MotionCal expect to get data over the serial port with the following example format: 


Where it contains in sequence, 3-axis accelerometer data, 3-axis gyroscope data, and 3-axis magnetometer data in uT. floating point should not be included in the message. That is why you can send the ADC digital value of the accelerometer and gyroscope, and to multiply the magnetometer data by 10 to get rid of the floating point. 

I thought it possible to send the same data using a virtual port (aka Psudo-tty). All what I need is a script that can receive the IMU data using BLE and send it over the virtual port to the MotionCal.

The first step was to test the original MotionCal’s build with the idea using terminal commands. First, to create the virtual ports the following Linux command is used: 

sudo socat -d -d pty,link=/dev/ttyACM11,echo=0,perm=0777 pty,link=/dev/ttyACM10,echo=0,perm=0777

This command utilizes the socat command to create two pseudo-tty terminals. One terminal, ttyACM11, used by MotionCal to transmit data, while the other terminal, ttyACM10, is used to transmit data to MotionCal from the script. To test the setup, I used minicom to connect to one of the ports manually, allowing me to send a single packet and verify if it can be successfully received in MotionCal. 

minicom -b 115200 -o -D /dev/ttyACM10 

However, I was surprised to find that MotionCal did not list the ttyACM10 and ttyACM11 ports at all. Thanks to Paul for providing the tool as open-source, I decided to investigate further. I went into the source code and discovered that the port list in the GUI had a check condition to filter out pseudo-terminals, which was causing the issue. To address this, I disabled the filter, allowing these ports to be visible (this commit).

To achieve this, I made a minimal change and built the MotionCal tool. The only requirement was to install wxWidgets, which is used to create the GUI. To do this, I followed the wxWidgets guide and downloaded version 3.0.2, which was the same version used by Paul during the development. After downloading the wxWidgets source files, I navigated to the folder and executed the following commands:

mkdir gtk-build 
../configure --with-gtk=3 --with-opengl 
make -j3 sudo 
make install 
make clean 
sudo ldconfig

I’ve made a small change in the Makefile (this commit) in order to use the system-wide installation of wxWidgets for building, rather than a custom folder.

I developed a Python script using pc-ble-driver-py Python library from Nordic Semiconductor for the nRF USB dongle. The script is found in my fork repo. Selecting an external BLE adapter other than the internal one in the laptops gives more portability and robust performance. 

Regarding the firmware side, any firmware capable to send and receive data through BLE should be fine.

The BLE Characteristics structure inside the reference firmware

One characteristic for sending accelerometer and gyroscope data in little-endian. The Python script expects to have the data in this order sent as an array of bytes.


And one characteristic for magnetometer data in little-endian:


Another characteristic is used to receive the calibration value from the PC side. You need to press ‘Send Cal’ button in MotionCal. The calibration results is sent as a string of UTF-8 using the following format: 


A reference Arduino sketch is provided in the fork repo as well. 

Exit mobile version