目录
(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(); // 初始化I2C
bool 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 47
Adafruit_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 0x07FF
const 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
✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
1万+

被折叠的 条评论
为什么被折叠?



