零知开源——基于STM32F407VET6与GY-271三轴地磁传感器的高精度电子罗盘设计与实现
✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
www.lingzhilab.com
目录
一、硬件设计部分
1.1 硬件清单
1.2 接线方案
1.3 硬件连接图
1.4 实物连接图
二、软件设计
2.1 系统初始化与传感器检测
2.2 主循环与数据处理
2.3 方位角计算与磁偏角校正
2.4 指南针显示与指针更新
2.5 传感器诊断与错误处理
2.6 完整代码
三、操作结果展示
3.1 与手机指南针对比
3.2 串口打印输出
3.3 视频演示
四、地磁传感器技术知识
4.1 工作原理
4.2 校准原理
五、常见问题解答
Q1: 指南针指向不准确怎么办?
Q2: 串口无输出怎么办?
Q3: 如何提高测量精度?
(1)项目概述
本项目基于STM32F407VET6主控芯片的零知增强板,结合GY-271(HMC5883L)三轴地磁传感器和ST7789显示屏,实现了一个高精度的数字指南针系统。系统能够实时测量地球磁场强度,计算方位角,并通过直观的UI界面展示方向信息。项目融合了硬件设计、传感器数据处理和用户界面开发,创造了一个功能完备的电子罗盘解决方案。
(2)项目亮点
>实现0.1°的方向测量精度
>根据地理位置自动校正磁偏角
>平滑指针动画和方向指示
>同时显示磁场强度和方位角
>自动检测传感器故障并提供详细诊断
(3)项目难点及解决方案
问题描述:指针移动需要平滑过渡
解决方案:
>指针位置追踪
>局部刷新技术
>贝塞尔曲线插值
一、硬件设计部分
1.1 硬件清单
组件 | 型号 | 数量 |
---|---|---|
主控板 | 零知增强板(STM32F407VET6) | 1 |
地磁传感器 | GY-271 (HMC5883L) | 1 |
显示屏 | ST7789 (240x320) | 1 |
杜邦线 | 20cm | 若干 |
1.2 接线方案
零知增强板(STM32F407VET6) | GY-271(I2C) | ST7789(SPI) | 引脚功能说明 |
---|---|---|---|
3.3V | / | VCC | 3.3V电源 |
5V | VCC | / | 5V电源 |
GND | GND | GND | 接地 |
SCL/21 | SCL | / | I2C时钟 |
SDA/20 | SDA | / | I2C数据 |
53 | / | CS | 片选 |
49 | / | DC | 数据/命令选择 |
51 | / | SDA | 主出从入 |
52 | / | SCL | 时钟 |
47 | / | RES | 复位 |
1.3 硬件连接图
1.4 实物连接图
二、软件设计
2.1 系统初始化与传感器检测
void setup() {Serial.begin(115200);tft.init(240, 320); // 初始化显示屏showSplashScreen(); // 显示启动界面Wire.begin(); // 初始化I2Cbool sensorReady = initCompass(); // 初始化地磁传感器if(sensorReady) {showMainUI(); // 显示主界面} else {showErrorScreen(); // 显示错误界面}
}
通过传感器初始化的结果选择界面展示
2.2 主循环与数据处理
void loop() {static uint32_t lastUpdate = 0;if(millis() - lastUpdate >= 150) { // 每150ms更新一次Vector norm = compass.readNormalize(); // 读取标准化数据float heading = calculateHeading(norm); // 计算方位角// 串口输出方位角信息(新增)Serial.print("Heading: ");Serial.print(heading, 1);Serial.println("°");updateSensorData(norm, heading); // 更新显示lastUpdate = millis();}
}
2.3 方位角计算与磁偏角校正
float calculateHeading(Vector norm) {// 计算原始方位角float heading = atan2(norm.YAxis, norm.XAxis);// 应用磁偏角校正(关键步骤)heading += declinationAngle;// 角度标准化(0-360°)if(heading < 0) heading += 2*M_PI;if(heading > 2*M_PI) heading -= 2*M_PI;return heading * 180/M_PI; // 弧度转角度
}
使用磁偏角校准提高方位角计算的精度
2.4 指南针显示与指针更新
void updateCompassNeedle(float heading) {int centerX = 160, centerY = 175; // 指南针中心int radius = 45; // 指针长度// 清除旧指针if(lastHeading >= 0) {float lastRad = lastHeading * M_PI / 180.0;int lastX = centerX + (radius - 10) * sin(lastRad);int lastY = centerY - (radius - 10) * cos(lastRad);tft.drawLine(centerX, centerY, lastX, lastY, PANEL_COLOR);}// 绘制新指针float rad = heading * M_PI / 180.0;int x2 = centerX + (radius - 10) * sin(rad);int y2 = centerY - (radius - 10) * cos(rad);tft.drawLine(centerX, centerY, x2, y2, NEEDLE_COLOR);lastHeading = heading; // 保存当前角度
}
2.5 传感器诊断与错误处理
bool initCompass() {// I2C设备扫描byte error, address;bool found = false;for(address = 1; address < 127; address++) {Wire.beginTransmission(address);error = Wire.endTransmission();if(error == 0 && address == 0x1E) {found = true;break;}}if(!found) {tft.setTextColor(ERROR_COLOR);tft.print("HMC5883L not found!");return false;}// 传感器初始化与配置if (!compass.begin()) {tft.print("Sensor init failed!");return false;}// 高级配置compass.setRange(HMC5883L_RANGE_1_3GA);compass.setMeasurementMode(HMC5883L_CONTINOUS);compass.setDataRate(HMC5883L_DATARATE_15HZ);compass.setSamples(HMC5883L_SAMPLES_1);return true;
}
2.6 完整代码
系统流程图:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <HMC5883L.h>
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans9pt7b.h>#define TFT_CS 53
#define TFT_DC 49
#define TFT_RST 47Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
HMC5883L compass;// 科技感黑色主题
#define BACKGROUND 0x0000
#define TITLE_COLOR 0x07FF
#define TEXT_COLOR 0xFFFF
#define DATA_COLOR 0x07E0
#define WARNING_COLOR 0xFD20
#define ERROR_COLOR 0xF800
#define COMPASS_COLOR 0x07FF
#define NEEDLE_COLOR 0xF800
#define PANEL_COLOR 0x18E3
#define ACCENT_COLOR 0x07FFconst char* directions[] = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"};
const float declinationAngle = (3.0 + (18.0 / 60.0)) * (M_PI / 180.0);
float lastHeading = -1;void setup() {Serial.begin(115200);tft.init(240, 320);tft.setRotation(3);tft.fillScreen(BACKGROUND);showSplashScreen();delay(2000);Wire.begin();bool sensorReady = initCompass();if(sensorReady) {showMainUI();} else {showErrorScreen();}
}void loop() {static uint32_t lastUpdate = 0;if(millis() - lastUpdate >= 150) {Vector norm = compass.readNormalize();float heading = calculateHeading(norm);// 串口输出方位角信息Serial.print("Heading: ");Serial.print(heading, 1);Serial.println("°");Serial.print("X: ");Serial.print(norm.XAxis, 2);Serial.print(" uT, Y: ");Serial.print(norm.YAxis, 2);Serial.print(" uT, Z: ");Serial.print(norm.ZAxis, 2);Serial.println(" uT");updateSensorData(norm, heading);lastUpdate = millis();}
}void showSplashScreen() {tft.fillScreen(BACKGROUND);tft.setFont(&FreeSans12pt7b);tft.setTextColor(TITLE_COLOR);tft.setCursor(60, 40);tft.print("DIGITAL COMPASS");tft.setFont(&FreeSans9pt7b);tft.setTextColor(ACCENT_COLOR);tft.setCursor(100, 90);tft.print("HMC5883L");tft.setTextColor(TEXT_COLOR);tft.setCursor(70, 140);tft.print("Initializing sensor...");
}bool initCompass() {tft.setFont(&FreeSans9pt7b);tft.setTextColor(TEXT_COLOR);tft.setCursor(30, 180);tft.print("Scanning I2C devices...");delay(500);byte error, address;bool found = false;for(address = 1; address < 127; address++) {Wire.beginTransmission(address);error = Wire.endTransmission();if(error == 0 && address == 0x1E) {found = true;break;}}if(!found) {tft.fillRect(20, 200, 280, 40, BACKGROUND);tft.setCursor(30, 220);tft.setTextColor(ERROR_COLOR);tft.print("HMC5883L not found!");delay(3000);return false;}tft.fillRect(20, 160, 280, 30, BACKGROUND);tft.setCursor(30, 180);tft.setTextColor(TEXT_COLOR);tft.print("Initializing HMC5883L...");if (!compass.begin()) {tft.fillRect(20, 200, 280, 40, BACKGROUND);tft.setCursor(30, 220);tft.setTextColor(ERROR_COLOR);tft.print("Sensor init failed!");delay(3000);return false;}compass.setRange(HMC5883L_RANGE_1_3GA);compass.setMeasurementMode(HMC5883L_CONTINOUS);compass.setDataRate(HMC5883L_DATARATE_15HZ);compass.setSamples(HMC5883L_SAMPLES_1);tft.fillRect(20, 200, 280, 40, BACKGROUND);tft.setCursor(30, 220);tft.setTextColor(DATA_COLOR);tft.print("Sensor initialized!");delay(1000);return true;
}void showMainUI() {tft.fillScreen(BACKGROUND);tft.fillRect(0, 0, 320, 30, PANEL_COLOR);tft.setFont(&FreeSans12pt7b);tft.setTextColor(TITLE_COLOR);tft.setCursor(110, 25);tft.print("COMPASS");tft.fillRoundRect(10, 40, 150, 70, 5, PANEL_COLOR);tft.setFont(&FreeSans9pt7b);tft.setTextColor(TEXT_COLOR);tft.setCursor(20, 60);tft.print("X:");tft.setCursor(20, 85);tft.print("Y:");tft.setCursor(20, 110);tft.print("Z:");tft.fillRoundRect(170, 40, 140, 70, 5, PANEL_COLOR);tft.setCursor(180, 60);tft.print("Heading:");tft.fillRoundRect(10, 120, 300, 180, 10, PANEL_COLOR);drawCompassBackground();
}void drawCompassBackground() {int centerX = 160, centerY = 175;int outerRadius = 55, innerRadius = 45;tft.drawCircle(centerX, centerY, outerRadius, COMPASS_COLOR);tft.drawCircle(centerX, centerY, innerRadius, COMPASS_COLOR);for (int i = 0; i < 360; i += 45) {float angle = i * M_PI / 180.0;int x1 = centerX + (outerRadius + 2) * sin(angle);int y1 = centerY - (outerRadius + 2) * cos(angle);int x2 = centerX + (outerRadius + 10) * sin(angle);int y2 = centerY - (outerRadius + 10) * cos(angle);tft.drawLine(x1, y1, x2, y2, COMPASS_COLOR);int labelX = centerX + (outerRadius + 5) * sin(angle);int labelY = centerY - (outerRadius + 5) * cos(angle);tft.setFont(&FreeSans9pt7b);tft.setTextColor(TEXT_COLOR);tft.setCursor(labelX-5, labelY-5);tft.print(directions[i/45]);}tft.fillCircle(centerX, centerY, 3, COMPASS_COLOR);
}float calculateHeading(Vector norm) {float heading = atan2(norm.YAxis, norm.XAxis);heading += declinationAngle;if(heading < 0) heading += 2*M_PI;if(heading > 2*M_PI) heading -= 2*M_PI;return heading * 180/M_PI;
}void updateSensorData(Vector norm, float heading) {tft.setFont(&FreeSans9pt7b);updateDataField(50, 60, norm.XAxis, "uT", 100, 20);updateDataField(50, 85, norm.YAxis, "uT", 100, 20);updateDataField(50, 110, norm.ZAxis, "uT", 100, 20);updateHeadingDisplay(heading);updateCompassNeedle(heading);
}void updateDataField(int x, int y, float value, const char* unit, int w, int h) {tft.fillRect(x, y-15, w, h, PANEL_COLOR);tft.setTextColor(DATA_COLOR);tft.setCursor(x, y);tft.print(value, 2);tft.print(" ");tft.print(unit);
}void updateHeadingDisplay(float heading) {tft.fillRect(180, 70, 30, 30, PANEL_COLOR);tft.setTextColor(DATA_COLOR);tft.setFont(&FreeSans9pt7b);tft.setCursor(180, 85);tft.print(heading, 0);tft.print("°");
}void updateCompassNeedle(float heading) {int centerX = 160, centerY = 175;int radius = 45;if(lastHeading >= 0) {float lastRad = lastHeading * M_PI / 180.0;int lastX = centerX + (radius - 10) * sin(lastRad);int lastY = centerY - (radius - 10) * cos(lastRad);tft.drawLine(centerX, centerY, lastX, lastY, PANEL_COLOR);}float rad = heading * M_PI / 180.0;int x2 = centerX + (radius - 10) * sin(rad);int y2 = centerY - (radius - 10) * cos(rad);tft.drawLine(centerX, centerY, x2, y2, NEEDLE_COLOR);tft.fillCircle(centerX, centerY, 5, NEEDLE_COLOR);tft.fillCircle(centerX, centerY, 2, PANEL_COLOR);lastHeading = heading;
}void showErrorScreen() {tft.fillScreen(BACKGROUND);tft.setFont(&FreeSans12pt7b);tft.setTextColor(ERROR_COLOR);tft.setCursor(80, 40);tft.print("SENSOR ERROR");tft.setFont(&FreeSans9pt7b);tft.setTextColor(TEXT_COLOR);tft.setCursor(70, 80);tft.print("HMC5883L not detected");tft.setCursor(70, 110);tft.print("Expected I2C: 0x1E");tft.setCursor(70, 130);tft.print("Devices found:");byte error, address;int foundCount = 0;for(address = 1; address < 127; address++) {Wire.beginTransmission(address);error = Wire.endTransmission();if(error == 0) {tft.setCursor(70 + (foundCount % 4) * 60, 150 + (foundCount / 4) * 20);tft.print("0x");tft.print(address, HEX);foundCount++;}}if(foundCount == 0) {tft.setCursor(70, 150);tft.print("No devices found!");}tft.setFont(&FreeSans12pt7b);tft.setTextColor(WARNING_COLOR);tft.setCursor(50, 200);tft.print("RESTART REQUIRED");while(true) {static bool blinkState = false;tft.fillRect(50, 180, 240, 50, BACKGROUND);if(blinkState) {tft.setCursor(50, 200);tft.setTextColor(WARNING_COLOR);tft.print("RESTART REQUIRED");}blinkState = !blinkState;delay(500);}
}
三、操作结果展示
3.1 与手机指南针对比
手机朝向和GY-271模块的X轴指向方向一致,获取到的方向角为68°
显示屏信息区域分布
>顶部标题栏:显示"COMPASS"标题
>磁场数据区:显示X/Y/Z三轴磁场强度
>方向角显示区:显示当前方位角度数
>指南针主区域:动态指南针显示
>方向标记:八个主要方向标记
3.2 串口打印输出
3.3 视频演示
GY-271三轴地磁传感器的高精度电子罗盘设计与验证
使用GY-271地磁传感器和显示屏验证精度
四、地磁传感器技术知识
4.1 工作原理
GY-271基于霍尼韦尔HMC5883L芯片,利用磁阻效应测量磁场:
>三轴磁阻传感器:分别测量X/Y/Z轴磁场分量
>信号调理电路:放大和滤波原始信号
>ADC转换:16位模数转换
>I2C接口:输出数字信号
4.2 校准原理
硬铁校准消除固定磁场干扰
软铁校准消除环境磁场畸变
比例校准调整各轴灵敏度差异
偏移校准消除零位误差
五、常见问题解答
Q1: 指南针指向不准确怎么办?
A: 可能原因及解决方案:
进行8字校准法(手持设备画8字)
远离电子设备、金属物体
根据地理位置调整declinationAngle
使用水平仪确保设备水平
Q2: 串口无输出怎么办?
A: 排查步骤:
检查USB数据线连接
确认波特率设置为115200
检查代码中Serial.begin(115200)是否存在
验证串口引脚(TX/RX)是否正常
Q3: 如何提高测量精度?
A: 优化建议:
增加采样次数(修改setSamples参数)
降低数据输出率(提高单次测量质量)
使用校准算法补偿环境干扰
保持设备静止3秒后再读数
项目资源:
>设置地区磁偏角:磁偏角计算
>显示屏驱动:ST7789驱动库
提示:本项目的磁偏角默认设置为深圳地区(3°18'),其他地区用户请根据实际位置调整
declinationAngle
参数。Magnetic Declination参数替换到(deg + (min / 60.0)) / (180 / M_PI)公式中:
比如,数值为-3° 19'说明将deg替换为-3,min替换为19
点击了解更多零知开发教程:https://www.lingzhilab.com/freesources.html