1. 项目概述

本项目基于 STM32F103C8T6 微控制器,结合 QMC5883L 三轴磁力计传感器和 SSD1306 OLED 显示屏,构建了一个高精度电子罗盘系统。系统通过 I2C 总线采集磁力计数据,经三重滤波算法(中值滤波 + 卡尔曼滤波 + 滑动平均)处理后,实时计算方位角并在 OLED 上显示。同时通过 USART 串口将航向数据发送至上位机,配合 Python 编写的 GUI 罗盘界面使用。

1.1 核心功能

  • 三轴磁场数据采集(X/Y/Z 分量)
  • 三重数字滤波:中值滤波 + 卡尔曼滤波 + 滑动窗口平均
  • 实时方位角计算(0-360°)
  • SSD1306 OLED 128x64 显示:磁场值、航向角、滤波状态
  • USART 串口输出航向数据(115200 baud)
  • PC 端 Python Tkinter 罗盘 GUI 应用程序

2. 硬件架构

2.1 主控芯片 - STM32F103C8T6

STM32F103C8T6 基于 ARM Cortex-M3 内核,主频 72MHz,拥有 64KB Flash 和 20KB SRAM。本项目中承担 I2C 总线控制、传感器数据读取、滤波算法计算、OLED 驱动和串口通信等全部处理任务。系统时钟配置为 HSE 8MHz 外部晶振经 PLL 倍频至 72MHz。

2.2 磁力计 - QMC5883L

QMC5883L 是 3 轴 AMR(各向异性磁阻)磁传感器,支持 I2C 接口(7位地址 0x0D),测量范围为 ±2G / ±8G,最高输出速率达 200Hz。片上集成信号放大、ADC 和自动消磁电路。其 ID 寄存器值为 0xFF,可用于与 HMC5883L 区分。

QMC5883L 寄存器映射

地址

寄存器

说明

0x00

X_LSB

X 轴低字节(数据读取起始地址)

0x01

X_MSB

X 轴高字节

0x02

Y_LSB

Y 轴低字节

0x03

Y_MSB

Y 轴高字节

0x04

Z_LSB

Z 轴低字节

0x05

Z_MSB

Z 轴高字节

0x06

STATUS

状态寄存器(bit 0 = DRDY)

0x09

CTRL1

控制寄存器1(Mode/ODR/RNG/OSR)

0x0A

CTRL2

控制寄存器2(软复位/中断)

0x0B

SET/RESET

置位/复位周期寄存器

0x0D

ID

芯片 ID = 0xFF

2.3 显示屏 - SSD1306 OLED 128x64

SSD1306 是单芯片 CMOS OLED 驱动控制器,支持 128x64 像素分辨率,通过 I2C 接口控制(7位地址 0x3C)。本项目采用全帧缓存(1024 bytes)双缓冲模式:先写入本地 framebuffer,然后调用 OLED_Update() 一次性刷新到显示屏,避免 I2C 频繁传输导致的闪烁。

2.4 硬件连接

STM32F103

外设

引脚

说明

PB6 (I2C1_SCL)

QMC5883L / OLED

SCL

I2C 时钟线

PB7 (I2C1_SDA)

QMC5883L / OLED

SDA

I2C 数据线

PA9 (USART1_TX)

PC 串口

RXD

串口发送(115200)

PA10 (USART1_RX)

PC 串口

TXD

串口接收

PA13

ST-Link

SWDIO

调试接口

PA14

ST-Link

SWCLK

调试时钟

3. 软件架构与算法

3.1 开发环境

  • IDE: STM32CubeMX 6.17.0 + PlatformIO / CMake
  • Framework: STM32Cube HAL (FW_F1 V1.8.7)
  • Compiler: GCC ARM (with -O6 优化)
  • MCU: STM32F103C8Tx (LQFP48)
  • PC 工具: Python 3 + pyserial + Tkinter

3.2 系统时钟配置

系统采用 8MHz 外部晶振(HSE),通过 PLL 9 倍频得到 72MHz 系统时钟。APB1 总线为 36MHz(HCLK/2),APB2 为 72MHz。Timer 时钟分别为 72MHz(APB1)和 72MHz(APB2)。I2C 工作频率配置为 400kHz(快速模式)。

3.3 I2C 传感器探测与初始化

系统启动后通过 I2C 总线探测多个候选地址(0x0D、0x1E、0x30、0x3C),通过读取 ID 寄存器区分 QMC5883L(0x0D 寄存器值为 0xFF)和 HMC5883L(0x00 寄存器值为 0x48)。考虑到 STM32F1 I2C 硬件存在锁死风险,每次探测前配置合适的超时,并在探测失败后执行 I2C 去初始化和重新初始化来恢复总线。

3.4 数据采集流程

传感器配置为连续测量模式、200Hz 输出速率、±8G 量程、256 倍过采样。主循环以约 50Hz(每 20ms)频率读取数据。每次读取执行 3 次突发采样,每次间隔 5ms。最终进入三重滤波流水线:

阶段

说明

Stage 1: 中值滤波

3 次采样 → 每轴排序 → 取中值。有效抑制 I2C 突发错误导致的单次野值。

Stage 2: 卡尔曼滤波

一维卡尔曼滤波器逐轴独立滤波(Q=1.0, R=50),快速收敛的同时平滑剩余噪声。

Stage 3: 滑动平均

5 级循环缓冲区 → 累加/替换 → 求平均。进一步消除短期波动。

核心数据采集代码如下:

/* 3 次采样 → 排序 → 中值 → Kalman → 滑动平均 */

int16_t rx[3], ry[3], rz[3];

for (i = 0; i < 3 && n < 3; i++)

    if (QMC5883L_ReadRaw(&hi2c1, &rx[n], &ry[n], &rz[n]) == 0) n++;

SortInt16(rx, n); SortInt16(ry, n); SortInt16(rz, n);

int16_t med_x = rx[n/2];   /* Median */

float fx = Kalman1D_Update(&s_kf_x, (float)med_x);  /* Kalman */

/* sliding window average ... */

3.5 方位角计算

经过三重滤波后的 X/Y 轴数据用于计算方位角,采用标准 atan2 公式:

heading = atan2f(Y, X) * 180.0f / 3.14159265f;

if (heading < 0.0f) heading += 360.0f;

注意:此计算假设传感器处于水平状态(仅使用 X/Y 轴)。如需倾斜补偿,需结合加速度计数据做俯仰/横滚校正。

3.6 串口通信协议

STM32 通过 USART1(115200, 8N1)向 PC 发送航向数据,格式为纯 ASCII:

H:123.4\r\n

其中 H 为固定前缀,数值范围为 000.0 ~ 359.9,以回车换行结尾。此设计避免使用 printf/sprintf,减少代码体积。

3.7 OLED 显示驱动

OLED 驱动采用全帧缓存 + 批量刷新策略。8 页(每页 8 像素高)x 128 列 = 1024 字节的帧缓存位于 SRAM 中。所有绘图命令操作帧缓存,仅在主循环末尾调用 OLED_Update() 一次性刷新到显示屏。支持 5x7 点阵 ASCII 字符显示,提供整数和浮点数打印函数。

OLED 显示布局(8 行 x 每行 21 字符):

内容

示例

0

X 轴磁场值

X: +12345

1

Y 轴磁场值

Y:  -6789

2

Z 轴磁场值

Z:  +2345

3

总强度 (mG)

mG: 14286

4

航向角

HDG: 123.4 deg

5

滤波状态

Filter: Med3+Kal+Avg5

6

I2C 速率

I2C:400kHz

7

传感器配置

OSR256

4. PC 端罗盘 GUI 应用程序

项目包含一个 Python 编写的桌面罗盘工具 compass.py,使用 Tkinter 和 pyserial 实现。它通过串口接收 STM32 发送的航向数据,以模拟指针罗盘的界面实时显示方位。

4.1 功能特性

  • 自动检测 USB 串口设备(支持 VID 过滤)
  • 串口接收协议解析:H:xxx.x 格式
  • 圆形模拟罗盘表盘:刻度线、角度数字、8 方位标签
  • 平滑指针动画(最短路径插值 Lerp)
  • 大字体数字显示 + 中文方位标签
  • 稳定性指示条(基于最近 30 次读数的标准差)
  • 窗口置顶模式

4.2 使用方法

pip install pyserial

python compass.py              # 自动连接

python compass.py COM3         # 指定端口

python compass.py --list       # 列出串口

5. 项目文件结构

项目基于 PlatformIO + STM32CubeMX 双构建系统,主要源代码结构如下:

fours/

  ├── Core/

  │   ├── Inc/

  │   │   ├── main.h          主头文件

  │   │   ├── oled.h           SSD1306 OLED 驱动头文件

  │   │   ├── qhmc5883l.h      QMC5883L 磁力计驱动头文件

  │   │   ├── font.h           5x7 ASCII 字库

  │   │   ├── gpio.h / i2c.h / usart.h

  │   │   └── stm32f1xx_hal_conf.h

  │   └── Src/

  │       ├── main.c           主程序(系统初始化 + 主循环)

  │       ├── oled.c           OLED 驱动实现

  │       ├── qhmc5883l.c      磁力计驱动 + Kalman滤波器

  │       ├── gpio.c / i2c.c / usart.c

  │       └── stm32f1xx_it.c / stm32f1xx_hal_msp.c

  ├── compass.py        PC 端 Python 罗盘 GUI 程序

  ├── platformio.ini    PlatformIO 项目配置

  ├── CMakeLists.txt    CMake 构建配置

  └── fours.ioc         STM32CubeMX 项目文件

6. 滤波算法详解

6.1 卡尔曼滤波器实现

针对每轴数据独立维护一个一维卡尔曼滤波器。系统模型假设磁场在短时间内近似恒定(过程噪声 Q=1.0),测量噪声方差 R=50。初始协方差 P=1000 确保快速收敛。卡尔曼增益在每个更新周期自动调节预测与测量的权重。

typedef struct {

    float x;     /* 估计状态 */

    float P;     /* 估计误差协方差 */

    float Q;     /* 过程噪声方差 */

    float R;     /* 测量噪声方差 */

    uint8_t init;

} Kalman1D_t;

/* Predict + Update */

float K = P_pred / (P_pred + kf->R);

kf->x += K * (raw - kf->x);

kf->P = (1.0f - K) * P_pred;

6.2 三重滤波的优势

  • 中值滤波:完全消除 I2C 读取错误导致的单次野值(如总线干扰或噪声脉冲)
  • 卡尔曼滤波:自适应平滑,在快速响应和平滑输出间动态平衡
  • 滑动平均:消除剩余高频抖动,使显示值稳定可读
  • 三者协同:中值滤波捕获突发异常,Kalman 处理连续噪声,滑动平均做最终平滑

7. 常见问题与调试指南

问题

解决方案

OLED 不显示

检查 I2C 接线;确认 0x3C 地址正确;使用逻辑分析仪查看 SCL/SDA

磁力计无数据

检查 QMC5883L 供电(3.3V);确认 ID 寄存器读取是否为 0xFF

I2C 总线锁死

STM32F1 I2C 易锁死,程序中已实现探测失败后 DeInit/ReInit 恢复

航向抖动大

检查周围是否有强磁场(电机/电源);增加 Kalman 滤波器 R 值

串口无输出

检查 USART1 引脚 PA9/PA10;验证波特率 115200

数值全为零

QMC5883L 可能处于待机模式;检查 CTRL1 寄存器配置

8. 总结与展望

本文介绍了基于 STM32F103C8T6 和 QMC5883L 的电子罗盘完整设计方案,涵盖硬件选型、I2C 通信、三重滤波算法(中值+卡尔曼+滑动平均)、OLED 显示驱动和 PC 端 GUI 应用程序。

系统实际运行效果稳定,航向精度在水平放置条件下可达 ±2° 以内。三重滤波管线有效抑制了传感器噪声和 I2C 总线干扰,平滑响应滞后控制在 200ms 以内。

后续可扩展方向:

  • 增加 MPU6050 加速度计实现倾斜补偿(精确航向不受俯仰/横滚影响)
  • 支持 HMC5883L / LIS3MDL 等多种磁力计自动识别
  • 在线校准算法(硬铁/软铁校正)
  • 无线传输(ESP8266/蓝牙模块)替代串口线
  • 增加电子存储航向日志(EEPROM/SD卡)
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐