Funpack5-2 基于 PIC32CM LS00 和 VL6180 实现 ToF 测距仪
该项目使用了PIC32CM LS00 Curiosity Nano+ Touch Evaluation Kit 和 VL6180 ToF 传感器,实现了TOF(Time-of-Flight)测距仪的设计,它的主要功能为:通过 VL6180 测距传感器进行非接触式距离测量,通过 I2C 总线与 MCU 通信,测量结果实时显示在 SSD1306 OLED 屏幕上。用户通过板载 PTC 触摸按键可在多个显示页面间切换,并支持长按重置统计数据及 180 度翻转屏幕。
标签
嵌入式系统
Funpack活动
ToF
EV41C56A
学嵌入式的Momo
更新2026-06-17
4

项目介绍

本项目基于 Microchip PIC32CM5164LS00048 微控制器,开发了一款 TOF(Time-of-Flight)测距仪。系统采用 ST VL6180 测距传感器进行非接触式距离测量,通过 I2C 总线与 MCU 通信,测量结果实时显示在 SSD1306 OLED 屏幕上。用户通过板载 PTC 触摸按键可在多个显示页面间切换,并支持长按重置统计数据及 180 度翻转屏幕。


项目实现了以下核心功能:

  1. VL6180 激光测距:通过 I2C 总线读取距离数据,范围 0-255mm,精度 ±2mm
  2. 指数移动平均(EMA)滤波器:平滑传感器噪声,提高读数稳定性
  3. 三页面 OLED 显示:大字距离、数据详情、历史趋势图
  4. PTC 触控交互:短按切换页面,长按 2 秒清除历史数据,长按 5 秒 180 度翻转屏幕
  5. UART 串口调试输出:实时打印距离值和状态码

演示

显示距离:

短按切换到数据详情,包括当前距离,最大最小值和传感器状态:

再次短按切换到历史趋势图:

长按 2s 可以清空历史数据:

长按 5s 可以翻转屏幕,方便不同角度使用:

硬件介绍

本项目使用 PIC32CM LS00 Curiosity Nano+ Touch Evaluation Kit,该评估套件专为评估 PIC32CM5164LS00048 微控制器而设计,基于安全且超低功耗的 ARM Cortex-M23 内核,集成了丰富的安全功能和触摸控制能力。

核心特性:

  • 安全 Boot:支持安全启动,通过硬件信任根保护固件完整性
  • TrustZone 技术:ARMv8-M 安全扩展,实现 Secure/Non-Secure 分区隔离
  • 加密加速器:硬件 AES、SHA、TRNG 加速加密运算
  • PTC 触摸控制:片上外设触摸控制器,支持自电容和互电容检测
  • 板载资源:PTC 触摸按键、LED (PA15)、32.768kHz 晶振

该套件将安全启动、TrustZone 技术、加密加速器与增强型触摸控制完美融合,是开发安全触摸应用的理想选择。

硬件清单

器件

型号

接口

地址

开发板

PIC32CM LS00 Curiosity Nano+ Touch Evaluation Kit

Cortex-M23, 48MHz

TOF传感器

VL6180X

I2C (SERCOM0)

0x29

OLED屏幕

SSD1306 128×32

I2C (SERCOM0)

0x3C

触摸按键

PTC 自电容

Y-line (Y23/Y16)

LED指示灯

PA15

GPIO输出

引脚连接

I2C 总线(SERCOM0)共用 SDA/SCL:

  • SCL → PA12(SERCOM0 PAD0)
  • SDA → PA13(SERCOM0 PAD1)

VL6180 和 SSD1306 共享同一 I2C 总线,通过不同的 7-bit 地址区分:

  • VL6180: 0x29
  • SSD1306: 0x3C


I2C 总线配置为 400kHz Fast-mode Plus,总线扫描确认两个设备均正常响应。PTC 触摸按键使用自电容模式,RTC 定时器触发 20ms 周期测量。触摸阈值设为 20,带滞回和抗干扰滤波。

软件介绍

软件架构

项目代码全部运行在 Secure 域,采用模块化设计:

  • main.c — 主循环入口,编排所有模块
    • 触摸处理:1ms 轮询,短按/长按事件分发
    • 传感器采样:50ms 间隔,EMA 滤波
    • 页面渲染:3 种显示模式切换
    • UART 调试输出
  • VL6180 驱动 (vl6180.c/h)
    • I2C 读写封装(SERCOM0_I2C_Write / WriteRead)
    • 初始化序列(参考 SparkFun / AN4545 寄存器配置)
    • 距离读取(单字节,10ms 延迟,中断清除)
    • EMA 滤波器(α=25%,抗尖峰,错误丢弃)
    • 偏移校准(VL6180_SetOffset,±127mm)
  • SSD1306 驱动 (ssd1306.c/h)
    • I2C 命令/数据写入(控制字节 0x00/0x40)
    • Page 寻址模式(0xB0-0xB7,批次 129 字节写入)
    • 帧缓冲(512 bytes,128×32)
    • 5×7 字体渲染(含 2×/3× 缩放)
    • 矩形填充(ssd1306_fill_rect)
    • 屏幕 180° 翻转(硬件 seg/COM 重映射)

主循环流程

主循环以 1ms 为节拍运行:每次迭代调用 touch_process() 处理触摸,然后通过 1ms 延迟控制节奏。每累计 50 次(约 50ms)触发一次传感器采样和显示刷新。这种设计保证了触摸响应延迟不超过 1ms,同时显示以 20Hz 流畅刷新。

触摸处理流程

触摸处理依赖 PTC 的measurement_done_touch 标志,该标志在触发采集完成后置位。PTC 驱动内部完成消抖和漂移补偿,上层只需读取 get_sensor_state(0) & KEY_TOUCHED_MASK 判断是否触摸。长按计时以 PTC 测量完成次数为基准(非真实毫秒),经实测标定为 60 次 ≈ 2s、150 次 ≈ 5s。两个长按操作独立触发:2s 时清除统计数据并保留屏幕方向,5s 时翻转屏幕。释放时若未触发任何长按操作则执行短按页面切换。

static bool cleared_data = false;
static bool flipped_screen = false;

/* 触摸按下:重置所有标志 */
if (state && !touch_pressed) {
touch_pressed = true;
press_ticks = 0;
cleared_data = false;
flipped_screen = false;
LED_Set();
}
/* 按住期间:分级触发 */
else if (state && touch_pressed) {
press_ticks++;
if (press_ticks >= 60 && !cleared_data) { /* ~2s */
reset_stats(); /* 清除 min/max/历史/滤波 */
cleared_data = true;
}
if (press_ticks >= 150 && !flipped_screen) { /* ~5s */
display_flipped = !display_flipped;
ssd1306_flip(display_flipped);
flipped_screen = true;
}
}
/* 松手:仅当未触发任何长按时切换页面 */
else if (!state && touch_pressed) {
if (!cleared_data && !flipped_screen)
current_page = (current_page + 1) % 3;
touch_pressed = false;
LED_Clear();
}

VL6180 采样 + 显示刷新流程

传感器采样通过 VL6180_ReadRangeFiltered()` 获取 EMA 滤波后的距离值。滤波器在状态码为 0x03(有效测量)且距离大于 0 时介入,误差读数直接丢弃并返回上一个有效值。有效数据存入 64 样本的环形缓冲区供趋势图使用,同时更新 min/max 统计。显示部分先清空帧缓冲,根据 current_page 渲染对应的页面内容,最后通过 ssd1306_update 批量写入 OLED 并输出串口调试信息。

VL6180 初始化序列

参考 SparkFun VL6180 驱动库和 ST AN4545 应用笔记,初始化共写入 ~45 个寄存器:

  1. 必选私有寄存器(28 个):配置 I2C、模拟前端、VCSEL 驱动等
  2. 推荐公共寄存器(6 个):GPIO 模式、平均采样周期、ALS 增益、自动校准、积分时间、温度校准
  3. 可选寄存器(3 个):测量间隔、中断配置
  4. 校准寄存器:ECE 收敛估计 (0x7B)、偏移校准 (23mm)、串扰补偿 (0x00)

EMA 指数移动平均滤波

#define FILTER_ALPHA 64  /* 25% 新权重 */

uint16_t VL6180_ReadRangeFiltered(void)
{
uint16_t raw = VL6180_ReadRange();

/* 丢弃无效读数 */
if (VL6180_ReadRangeStatus() != 0x03)
return filtered_value;

/* 首次或为零时直接初始化 */
if (!filter_inited || raw == 0) {
filtered_value = raw;
filter_inited = true;
return raw;
}

/* 跳变超过 50% 时快速跟踪 */
uint16_t delta = (raw > filtered_value)
? (raw - filtered_value) : (filtered_value - raw);
if (delta > filtered_value / 2) {
filtered_value = raw;
return raw;
}

/* EMA: (旧值 × 192 + 新值 × 64) / 256 */
filtered_value = (uint16_t)(((uint32_t)filtered_value * (256 - FILTER_ALPHA)
+ (uint32_t)raw * FILTER_ALPHA) >> 8);
return filtered_value;
}


遇到的问题和解决方法

配置 48Mhz 时钟后无法正常运行

问题:时钟配置成 48Mhz 之后,MCU 无法正常启动

原因:配置时钟之后,MCC 不会自动设置 wait state,需要手工设置

解决:MCC 中配置 wait state = 2

VL6180 驱动移植遇到的 I2C 写入格式问题

问题:Rust VL6180 驱动使用 4 字节 I2C 写入(2字节寄存器地址 + 2字节数据值)。移植到 C 后,初始化成功但无法读取有效距离值。

原因:VL6180 使用 8-bit 寄存器,I2C 写入应为 3 字节(地址 2 字节 + 数据 1 字节)。4 字节写入导致 VL6180 的 I2C 自动增量将额外字节写入相邻寄存器,破坏了配置。

解决:改为 3 字节 I2C 写入格式,匹配 SparkFun 参考驱动的实现。


VL6180 距离读数不准确

距离校准

改为正确的 I2C 格式后,传感器开始输出距离值,但与实际距离存在固定偏差。实测时实际距离 60mm,传感器显示 85mm,偏差达 25mm。同时读数跳变剧烈,稳定性差。

原因

  1. 私有寄存器未正确配置:VL6180 上电后需要写入约 28 个强制私有寄存器(I2C 焊盘配置、模拟前端、VCSEL 驱动等),参考 ST AN4545 应用笔记第 24 页。初始移植仅写入了 3 个寄存器,大量默认值未修正。
  2. 缺少偏移校准:VL6180 的 SYSRANGE_PART_TO_PART_OFFSET(0x0024)寄存器提供 ±127mm 的硬件偏移补偿,初始未设置。
  3. 公共寄存器缺失:GPIO 模式、平均采样周期、自动校准周期、温度校准等推荐设置均未写入,影响测量质量。

解决

  1. 完整移植 SparkFun 参考驱动的初始化序列,包括全部 28 个私有寄存器、6 个推荐公共寄存器和 5 个社区补充寄存器(ECE 收敛估计 0x7B、最大收敛时间 0x32、范围检查使能 0x11 等)。
  2. 通过 VL6180_SetOffset() 设置硬件偏移补偿,使传感器读数与真实距离匹配。经过多次测试标定,最终偏移值确定为 23mm。
  3. 增加 VL6180_ReadRangeStatus() 状态码检查,错误读数(status ≠ 0x03)不参与统计和显示。

SSD1306 OLED 128×64 无法点亮

问题:128×64 OLED 屏幕 I2C 地址确认正确、所有初始化命令收到 ACK("OLED OK"),但屏幕始终黑屏。尝试了多种方案:修改 I2C 速度、更换初始化序列、切换寻址模式、强制全屏点亮命令(0xA5)均无效。

排查过程

  1. I2C 总线扫描确认两个设备正常响应
  2. 尝试了 SSD1315 兼容配置— 无效
  3. 怀疑是 I2C 自动增量失效导致多字节读取返回重复数据

最终方案:更换为 128×32 OLED(SSD1306,地址 0x3C),使用 Page 寻址模式 + 129 字节批量 I2C 写入。屏幕立即正常工作。原 128×64 模块疑似为硬件故障。

触摸按键长按时间不准与多级长按设计

问题:长按阈值最初设为 2000 次测量循环,但实际需要远超 2 秒才触发复位。后续实现长按 2 秒清空数据 + 5 秒翻转屏幕的两级操作时,发现无法稳定触发。

原因分析

  1. press_ticks 仅在 measurement_done_touch==1 时递增(PTC 测量周期约 20ms),但主循环中 OLED 刷新(~45ms)和 VL6180 读取(~10ms)会阻塞循环,导致实际测量频率降至约 30Hz。
  2. 最初使用单一 long_press_done 标志同时控制清空数据和翻转屏幕,两个操作无法独立触发。

解决方案

  1. 校准阈值:通过实际测试标定,60 次测量循环约等于 2 秒,150 次测量循环约等于 5 秒。
  2. 独立标志位:引入 cleared_data  flipped_screen 两个独立布尔标志,分别追踪两个长按操作是否已触发。
  3. 分级触发
    • press_ticks >= 60 && !cleared_data:执行 reset_stats()(清除 min/max、历史记录、EMA 滤波器)
    • press_ticks >= 150 && !flipped_screen:执行 ssd1306_flip()(180° 翻转 OLED 显示)
  4. 释放处理:松手时只有当两个标志均为 false(即未触发任何长按操作)时,才执行短按页面切换。任一长按操作触发后,松手不再切换页面。

I2C 总线共享时序问题

问题:VL6180 和 SSD1306 共享 SERCOM0 I2C 总线,切换设备时可能出现总线繁忙导致写入失败。

解决:在每个 I2C 操作前调用 while(SERCOM0_I2C_IsBusy()) 等待总线空闲,确保前一个设备的传输完成后再发起新的传输。SSD1306 使用 129 字节批量写入(控制字节 + 128 列数据),减少 I2C 事务次数。

总结

本实项目实现了基于 VL6180 的 TOF 测距仪,集成 OLED 显示、触摸交互和数据滤波。项目涵盖嵌入式系统开发的典型挑战:I2C 协议调试、显示驱动适配、实时数据处理和用户交互设计。通过参考开源库、系统化排查硬件/软件问题,最终交付了一套功能完整的测距仪系统。

附件下载
distance_sensor_final.zip
代码
团队介绍
DDL Fighter
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号