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.
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
Pre-requests:
- 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.
- 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.
- 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).
Steps:
- Download the repository of my MotionCal fork. Run the provided MotionCal build.
- 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
- In MotionCal, select ttyACM11 in port list.
- 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 nRF_dongle_imu_cal.py NRF52 /dev/ttyACM0
- 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.
How it is possible to do this tweak?
The MotionCal expect to get data over the serial port with the following example format:
Raw:272,-304,16422,0,19,-3,-100,200,170
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 nRF_dongle_imu_cal.py 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.
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.
<B0..B1>Acc_x<B2..B3>acc_y<B4..B5>acc_z<B6..B7>gyro_x<B8..B9>gyro_y<B10..B11>gyro_z
And one characteristic for magnetometer data in little-endian:
<B0..B1>mag_x<B2..B3>mag_y<B4..B5>mag_z
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:
SoftI:S11,S12,S13,S21,S22,S23,S31,S32,S33
HardI:H1,H2,H3
A reference Arduino sketch is provided in the fork repo as well.