STM32F103 + QMC5883LSSD1306 OLED 电子罗盘基于开源硬件与STM32Cube HAL的完整设计方案
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卡)
更多推荐

所有评论(0)