In this video lesson we show how to calibrate a QMC5883L 3-Axis magnetometer using python and PyQt5. The program presents the user with a visual representation of the magnetic vector, allowing more precise calibration of the sensors. We are using the GY-87 module, and it is connected like this:

This is the simple code on the Arduino side, sending the raw magnetometer data:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// ==================================================================== // DISCLAIMER: // This code is provided as-is for educational and experimental // purposes only. The author makes no representations or warranties of // any kind concerning the safety, suitability, or accuracy of this // code. Use at your own risk. The author assumes no liability for any // damages, system failures, security breaches, or network issues // resulting from the use or implementation of this script. // ==================================================================== #include <Adafruit_MPU6050.h> #include <Adafruit_Sensor.h> #include <Wire.h> #include <QMC5883LCompass.h> int x,y,z; Adafruit_MPU6050 mpu; QMC5883LCompass compass; void setup() { Serial.begin(115200); mpu.begin(); mpu.setI2CBypass(true); compass.init(); } void loop() { compass.read(); x = compass.getX(); y = compass.getY(); z = compass.getZ(); Serial.print(x); Serial.print(','); Serial.print(y); Serial.print(','); Serial.println(z); delay(100); } |
This is the calibration code, running on the Python side;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# ==================================================================== # DISCLAIMER: # This code is provided as-is for educational and experimental # purposes only. The author makes no representations or warranties of # any kind concerning the safety, suitability, or accuracy of this # code. Use at your own risk. The author assumes no liability for any # damages, system failures, security breaches, or network issues # resulting from the use or implementation of this script. # ==================================================================== import serial import numpy as np from PyQt5 import QtWidgets, QtCore import pyqtgraph as pg serialPort = 'COM8' baudRate =115200 to =.1 ser =serial.Serial(serialPort, baudRate, timeout=to) maxPoints = 2000 xRaw = np.empty(0) yRaw = np.empty(0) zRaw = np.empty(0) xCal = np.empty(0) yCal = np.empty(0) zCal = np.empty(0) calibrated = False offsets = np.zeros(3) scales = np.ones(3) app = QtWidgets.QApplication([]) mainWindow = QtWidgets.QWidget() mainWindow.setWindowTitle("Magnetometer Calibratioin") mainLayout =QtWidgets.QVBoxLayout() mainWindow.setLayout(mainLayout) statusLabel = QtWidgets.QLabel("Program Running: Not Calibrated") statusLabel.setAlignment(QtCore.Qt.AlignCenter) statusLabel.setStyleSheet("color: green; font-weight: bold; font-size: 20px;") mainLayout.addWidget(statusLabel) minMaxLabel=QtWidgets.QLabel("Min/Max: N/A") minMaxLabel.setAlignment(QtCore.Qt.AlignCenter) minMaxLabel.setStyleSheet("color: green; font-weight: bold; font-size: 16px;") mainLayout.addWidget(minMaxLabel) calibrateButton = QtWidgets.QPushButton("Start Calibration") calibrateButton.setCheckable(True) mainLayout.addWidget(calibrateButton) plotLayout =QtWidgets.QGridLayout() mainLayout.addLayout(plotLayout) xyPlot = pg.PlotWidget(title="XY Plane") yzPlot = pg.PlotWidget(title="YZ Plane") xzPlot = pg.PlotWidget(title="XZ Plane") for plot in [xyPlot, yzPlot, xzPlot]: plot.setAspectLocked(True) plot.showGrid(x=True, y=True) plot.setMinimumSize(300,300) plotLayout.addWidget(xyPlot, 0,0) plotLayout.addWidget(yzPlot, 0,1) plotLayout.addWidget(xzPlot, 1,0) xyScatter = pg.ScatterPlotItem(size=5) yzScatter = pg.ScatterPlotItem(size=5) xzScatter = pg.ScatterPlotItem(size=5) xyPlot.addItem(xyScatter) yzPlot.addItem(yzScatter) xzPlot.addItem(xzScatter) def toggleCalibration(): global calibrated, xRaw, yRaw, zRaw, xCal, yCal, zCal, offsets, scales if calibrateButton.isChecked(): calibrateButton.setText("Stop Calibration") statusLabel.setText("Mode: Calibration") statusLabel.setStyleSheet("color: red; font-weight: bold; font-size: 20px;") minMaxLabel.setText("Min/Max: collecting . . .") minMaxLabel.setStyleSheet("color: red; font-weight: bold; font-size: 16px;") calibrated = False xRaw = np.empty(0) yRaw = np.empty(0) zRaw = np.empty(0) if not calibrateButton.isChecked(): calibrateButton.setText("Start Calibration") statusLabel.setText("Mode: Calibrated") statusLabel.setStyleSheet("color: green; font-weight: bold; font-size: 20px;") rawData = np.column_stack((xRaw,yRaw,zRaw)) minVals = np.min(rawData, axis=0) maxVals = np.max(rawData, axis=0) offsets = (maxVals + minVals)/2 scales = 2.0/(maxVals-minVals) minMaxLabel.setStyleSheet("color: green; font-weight: bold; font-size: 16px;") minMaxLabel.setText("Min(x,y,z: "+str(minVals.round(2))+" Max(x,y,z): " +str(maxVals.round(2))+" Offsets(x,y,z): "+str(offsets.round(2)) +"Scales(x,y,z): "+str(scales.round(5))) print("Calibration Completed.") print("Offsets: ", offsets) print("Scales: ", scales) xCal = np.empty(0) yCal = np.empty(0) zCal = np.empty(0) calibrated = True calibrateButton.clicked.connect(toggleCalibration) def updatePlot(): global xRaw, yRaw, zRaw, xCal, yCal, zCal if ser.in_waiting > 0: try: line= ser.readline().decode('utf-8').strip() values = line.split(',') if len(values)==3: x = float(values[0]) y = float(values[1]) z = float(values[2]) if not calibrated: xRaw =np.append(xRaw, x)[-maxPoints:] yRaw =np.append(yRaw, y)[-maxPoints:] zRaw =np.append(zRaw, z)[-maxPoints:] xyScatter.setData(x=xRaw, y=yRaw, brush=pg.mkBrush(0,0,255,255)) yzScatter.setData(x=yRaw, y=zRaw, brush=pg.mkBrush(0,255,0,255)) xzScatter.setData(x=xRaw, y=zRaw, brush=pg.mkBrush(255,0,0,255)) if len(xRaw)>10: minVals = np.min(np.column_stack((xRaw,yRaw,zRaw)),axis=0) maxVals = np.max(np.column_stack((xRaw,yRaw,zRaw)),axis=0) minMaxLabel.setStyleSheet("color: red; font-weight: bold; font-size: 16px;") minMaxLabel.setText("Min(x,y,z: "+str(minVals.round(2))+" Max(x,y,z): " +str(maxVals.round(2))) if calibrated: xC= (x - offsets[0])*scales[0] yC= (y - offsets[1])*scales[1] zC= (z - offsets[2])*scales[2] xCal = np.append(xCal, xC)[-maxPoints:] yCal = np.append(yCal, yC)[-maxPoints:] zCal = np.append(zCal, zC)[-maxPoints:] xyScatter.setData(x=xCal, y=yCal, brush=pg.mkBrush(0,0,255,120)) yzScatter.setData(x=yCal, y=zCal, brush=pg.mkBrush(0,255,0,120)) xzScatter.setData(x=xCal, y=zCal, brush=pg.mkBrush(255,0,0,120)) except Exception as e: print("Parse Error: ", e) plotTimer =QtCore.QTimer() plotTimer.timeout.connect(updatePlot) plotTimer.start(50) mainWindow.show() try: app.exec_() finally: ser.close() |