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 | #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 | 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() |