尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GP2Y0A21YK0F红外测距传感器在51单片机上的I2C读取与距离换算

GP2Y0A21YK0F红外测距传感器在51单片机上的I2C读取与距离换算 如果你第一次搜到 GP2Y0A21YK0F多半会有个疑惑这明明是一个三线模拟输出的红外测距传感器为什么很多教程都要扯上 I2C答案其实很直接——51单片机里最常见的 STC89C52 系列没有片上 ADC而 GP2Y0A21YK0F 的输出接口是模拟电压单片机读不了电压就等于白接。所以“GP2Y0A21YK0F 51单片机”这个组合真正的核心是怎么把一个模拟电压信号通过一块 I2C 接口的 ADC 芯片我用的 PCF8591用普通 IO 口模拟 I2C 时序读回来。这篇文章我会把传感器本身的特性、外接 ADC 的选型原因、模拟 I2C 的完整代码、以及我在实测中踩过的各种坑全部写清楚。适合正在做智能小车避障、倒车雷达、非接触测距这类项目的同学参考尤其是第一次碰模拟输出传感器的新手。1. GP2Y0A21YK0F一个输出曲线反直觉的模拟传感器1.1 它和几块钱的红外避障模块到底差在哪很多新手一开始接触的红外避障模块其实是“红外对管”一边发射红外光一边接收反射光距离越近接收到的光越强输出电平翻转只能告诉你“有没有障碍物”给不了具体距离。GP2Y0A21YK0F 完全不是这个原理。它内部用的是 PSD位置敏感探测器发射管会发出一束经过调制的红外光光线照射到前方物体后反射回接收器在 PSD 上形成一个光斑。物体越近光斑落点越靠近一端物体越远光斑越靠近另一端。内部信号处理电路把这个光斑位置转换成对应的模拟电压所以它的输出是连续的电压值而不是简单的电平跳变。这也是为什么它能在 10cm~80cm 范围内给出相对稳定的距离读数。实际使用时要明白一个前提这颗传感器的输出不是线性的电压和距离之间大致成反比关系。下面这组数据是官方特性曲线上比较典型的值我先放出来后面讲代码和校准时还要反复用到距离(cm)典型输出电压(V)8位ADC读数参考(5V基准)102.75140152.35120201.9097251.6383301.4272401.1056500.9046600.7538800.5528注意这组数只能当参考每一批传感器、不同供电电压下都会有一些偏移所以真正做项目时最好自己实测一遍后面第 4 章我会讲怎么标定。1.2 输出曲线、响应时间与“近距离翻转”这个最大的坑GP2Y0A21YK0F 的工作电压是 4.5V~5.5V典型工作电流 30mA 左右三根线分别是红线 VCC 接 5V、黑线 GND、白/黄线 Vo 输出模拟电压。连接器一般是 XH2.54 的三针端子接的时候务必看清方向插反一次就有可能烧掉传感器内部电路。这颗传感器的采样周期大约是 38.3ms±9.6ms也就是说输出大约每 26Hz 更新一次。这个特性决定了它不适合做高速动态测距比如你要每 1ms 采一个点去测量高速飞行的物体它根本跟不上。但拿来做小车避障、缓慢接近的测距场景完全够用因为小车的移动速度通常远低于传感器的响应频率几十毫秒级别根本没有体感延迟。接下来是这篇避坑指南里最重要的一条当物体距离小于 10cm 时传感器输出会“翻转”。PSD 传感器在超近距离时反射光斑会超出 PSD 的有效测量区域输出不是继续升高而是突然下降甚至一路跌到接近 0V。这意味着 5cm 处的读数和 50cm 处的读数可能非常接近程序会以为前方有个远处的物体实际情况是物体已经快怼脸上了。如果你准备拿它做“近距离防撞”一定要在软件里加一道保护逻辑当检测到电压异常下降、读数出现“远距离跳变”时直接判定为超近距离并触发报警绝不能放任这个异常值参与后续距离计算。我第一次做防撞实验时没意识到这个问题小车直直地往墙边顶直到卡住还在继续往前推后来加了防翻转逻辑才算解决。2. 51单片机没有ADC为什么我的方案选了PCF8591走I2C2.1 模拟电压采集的几条路ADC0832、PCF8591、STC12自带ADC既然 GP2Y0A21YK0F 输出的是模拟电压而 STC89C52 内部没有 ADC就必须在外部想办法。市面上常见方案有这么几条ADC08328位 ADCSPI 类串行接口有两路输入电路很简单。缺点是很多新手不熟 SPI代码要自己抠时序而且市面上散片质量参差不齐。PCF85914 路模拟输入、1 路模拟输出8 位分辨率I2C 接口只占用两根线还能顺便扩展出额外 3 路模拟输入以后接光敏、电位器不用再买芯片。更关键的是I2C 协议本身就是实战高频知识点学完以后去调 OLED、温度传感器到处都用得上。STC12 系列STC12C5A60S2 等芯片自带 10 位 ADC可以直接用单片机内部资源读模拟量。如果你还没买板子直接换这一类芯片确实最省事。但题目里限定的是经典 51 单片机很多学校的课程设计、竞赛板子用的都是 STC89C52板子换不了就只能走外挂 ADC 这条路。我最终选了 PCF8591原因是硬件上两根线就能读到 4 路模拟量比 ADC0832 省线I2C 协议是通用协议PCF8591 只是第一个练手对象后面还能复用同一套 I2C 驱动去接 OLED、EEPROM而且很多商家直接卖 PCF8591 模块模块上已经焊好了上拉电阻和排针拿杜邦线就能直接接到 51 开发板上。2.2 硬件接线与两个必须加的上拉电阻普通开发板场景下我是这样接的器件引脚接到哪里GP2Y0A21YK0FVCC 红线5VGP2Y0A21YK0FGND 黑线GNDGP2Y0A21YK0FVo 白线PCF8591 的 AIN0PCF8591VCC5VPCF8591GNDGNDPCF8591SDAP2.0任意 IO 口都行PCF8591SCLP2.1任意 IO 口都行PCF8591A0/A1/A2全接 GNDPCF8591VREF5V如果用的是裸芯片而不是现成模块SCL 和 SDA 上必须各接一个 4.7kΩ~10kΩ 的上拉电阻到 5V。I2C 总线是开漏结构的没有上拉电阻电平根本拉不上去。我见过太多人照着网上的接线图把 SCL/SDA 直接接到单片机的 P2.0/P2.1结果死活读不出数据最后拿示波器一看时钟线波形像心电图一样乱七八糟。用模块的同学也要确认模块上有没有焊 4.7k 或 10k 的电阻有些廉价模块只是把芯片和端子焊上了电阻一个没有照样通不了。另外强烈建议在传感器的 VCC 和 GND 之间并一个 10μF 电解电容和一个 0.1μF 陶瓷电容。这颗传感器虽然标称工作电流只有 30mA 左右但它的红外发射管是在内部脉冲发光的瞬时电流会有抖动。如果你的系统里有电机、继电器这类大功率负载电源毛刺会直接耦合进模拟输出导致距离读数周期性跳变。多加两个电容能明显改善而且成本只要几毛钱。2.3 PCF8591的器件地址和控制字节写错就读不出数据PCF8591 的 I2C 地址由硬件引脚 A0、A1、A2 决定7 位地址格式是1001 A2 A1 A0。三个引脚全部接地时7 位地址是0x48换算成 I2C 写操作字节是0x90读操作字节是0x91。网上有些代码直接给0x90、0x91有些给0x48、0x49看着不一样其实只是 7 位地址和 8 位地址的区别表达方式不同而已。PCF8591 在接收完器件地址之后还要接收一个控制字节这个字节决定当前用哪一路模拟输入、是否单端模式等。控制字节的 bit5 和 bit4 是模拟输入模式选择00表示四路独立单端输入bit3 和 bit2 是通道选择00对应 AIN0也就是我们接传感器的那一路。所以读通道 0 的控制字节就是0x40 | 0x00 0x40。如果接的是 AIN1就是0x41以此类推。控制字节 bit6 是模拟输出使能用不到 DAC 时直接置 0 就行。这里的硬伤是很多人把器件地址当成寄存器地址发出去或者在控制字节里忘了选通道结果读回来的永远是通道 0 的旧数据。如果你把传感器接到了 AIN2却还在用0x40那么读到的数据变化基本和传感器无关纯属白调。我建议拿到芯片第一件事就是写个循环把四个通道的 ADC 值全部打到串口上轮询几秒钟这样能立刻确认接线和通道配置是否正确避免后面做数据处理时才发现源头就错了。3. I2C模拟时序代码从波形到C51代码逐步拆解3.1 模拟I2C的节奏控制延时不是越小越好I2C 协议在信号线上有两根线SCL 时钟线和 SDA 数据线。所有数据传输都靠 SCL 高低电平配合 SDA 电平变化来同步整个通信过程可以拆成几个基础动作起始信号、停止信号、发送一个字节、接收一个字节、应答信号。模拟 I2C 指的是用普通 IO 口在软件层面实现这些时序而不是靠单片机内部的硬件 I2C 外设。这样做的好处非常明显引脚可以任意选择不绑定在特定 pin 上时序节奏可以自己控制遇到线上电容比较大的情况还可以手动把延时拉长避免电平建立时间不足。PCF8591 工作在标准模式 I2C100kHz时SCL 半周期大约是 5μs。实际上 I2C 对时序并不苛刻慢一点完全没问题快一点只要不超过器件上限就行。用软件模拟时我的经验是宁慢勿快尤其是在面包板搭的电路里杂散电容大再加上杜邦线质量参差不齐太快非常容易出错。在 11.0592MHz 晶振下一个空循环大约能拖出几微秒的延时。我给 I2C_Delay 用的是循环嵌套加几轮空指令这样的延时大概在 5μs 上下接近标准模式 100kHz 的节奏。如果你换了更高主频的单片机可以不调代码只要确认每次 SCL 翻转之间至少有 2μs~5μs 的电平稳定时间就可以。3.2 完整可用的C51代码I2C总线层下面这段是完整的 I2C 模拟总线驱动直接用 Keil C51 编译包含起始、停止、发送字节、接收字节、应答判断几个核心函数。代码里的_nop_()来自intrins.h用于产生极短延时。#include reg52.h #include intrins.h // 模拟I2C引脚定义可自由修改 sbit SDA P2^0; sbit SCL P2^1; // 基础延时11.0592MHz下约5us左右 void I2C_Delay(void) { unsigned char i; for(i 0; i 6; i); } // I2C起始信号SCL高电平期间SDA由高变低 void I2C_Start(void) { SDA 1; SCL 1; I2C_Delay(); SDA 0; I2C_Delay(); SCL 0; } // I2C停止信号SCL高电平期间SDA由低变高 void I2C_Stop(void) { SDA 0; SCL 1; I2C_Delay(); SDA 1; I2C_Delay(); } // 主机发送一个字节先发高位 void I2C_SendByte(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { if(dat 0x80) SDA 1; else SDA 0; I2C_Delay(); SCL 1; I2C_Delay(); SCL 0; I2C_Delay(); dat 1; } } // 主机接收一个字节先收高位 unsigned char I2C_ReceiveByte(void) { unsigned char i; unsigned char dat 0; SDA 1; // 释放数据线把控制权交给从机 for(i 0; i 8; i) { dat 1; SCL 1; I2C_Delay(); if(SDA) dat | 0x01; SCL 0; I2C_Delay(); } return dat; } // 等待从机应答信号返回0表示有应答返回1表示无应答 bit I2C_WaitAck(void) { bit ack; SDA 1; SCL 1; I2C_Delay(); ack SDA; SCL 0; I2C_Delay(); return ack; } // 主机发送应答信号ack0表示应答ack1表示非应答 void I2C_SendAck(bit ack) { SDA ack; I2C_Delay(); SCL 1; I2C_Delay(); SCL 0; I2C_Delay(); SDA 1; }这段代码最需要理解的是 I2C 的数据变化时机所有数据位都只能在 SCL 为低电平时改变SCL 为高电平时数据线必须保持稳定。所以每个循环里先改 SDA再拉高 SCL 让从机采样采样完成后拉低 SCL这才是一个完整的位传输。很多人照着代码敲了一遍发现自己写的版本读不到数据大概率就是 SCL 和 SDA 的时序次序搞反了SDA 在 SCL 高电平期间变了从机根本采不到稳定电平。3.3 PCF8591读取函数第一次读上电数据的坑PCF8591 的读取过程比普通 I2C 器件稍微绕一点因为要发两次器件地址。完整流程是发送起始信号发送器件写地址0x90等待应答发送控制字节0x40选择 AIN0、单端模式等待应答再次发送起始信号重复起始发送器件读地址0x91等待应答读取一个字节的数据主机发送非应答信号发送停止信号// 读取PCF8591指定通道ADC值channel为0~3 unsigned char PCF8591_ReadADC(unsigned char channel) { unsigned char val; I2C_Start(); I2C_SendByte(0x90); // 器件写地址 I2C_WaitAck(); I2C_SendByte(0x40 | (channel 0x03)); // 控制字节 I2C_WaitAck(); I2C_Start(); // 重复起始 I2C_SendByte(0x91); // 器件读地址 I2C_WaitAck(); val I2C_ReceiveByte(); // 读回ADC数据 I2C_SendAck(1); // 主机发送非应答通知从机不再继续 I2C_Stop(); return val; }这里有一个非常容易踩的坑PCF8591 第一次读取时返回的是上一次的转换结果不是当前通道的实时值。芯片刚上电时内部没有完成一次完整的 ADC 转换第一次读回来的数据不是 0 就是 255要么是残留的旧通道数据。如果主程序里只读一次 ADC 就拿去做判断你会发现上电瞬间的读数和其他时刻完全不一样。解决办法很简单在主循环初始化阶段先连续读两次丢掉第一次的结果从第二次开始再使用。unsigned char PCF8591_ReadADC_DiscardFirst(unsigned char channel) { PCF8591_ReadADC(channel); // 丢弃第一次 return PCF8591_ReadADC(channel); }回读上电第一次垃圾数据这个问题网上很多教程不会提但实际测试时一旦碰上会让人误以为 I2C 时序写错了或者芯片坏了。我第一次调通后看到串口里上电瞬间冒出一个 0xFF后面数据才正常第一反应是硬件有问题排查了半天才发现是芯片本身的特性。4. 把电压换算成距离公式拟合与分段插值的实战处理4.1 官方曲线、经验公式、实测标定怎么选ADC 读回来的是一个 0~255 的整数要换算成物理距离首先要把它还原成电压。以 5V 基准、8 位 ADC 为例电压公式是电压(V) ADC值 × 5.0 / 255反过来如果想知道当前 ADC 读数对应多少距离就不能直接套一个简单比例公式了因为 GP2Y0A21YK0F 的电压和距离是非线性关系。网上流传比较广的做法是套经验公式比如distance 1.1667 / (voltage - 0.2)这类单点反比例公式。我实测下来这样的单公式简单是简单但在整个 10cm~80cm 范围内误差不太均匀20cm 附近的拟合效果还行到了 60cm 以后误差可能拉到 5cm 以上而且在靠近 10cm 那一段很容易出现负数或者无穷大的荒谬结果。更可靠的做法是实测标定 分段线性插值。把一把尺子平放在桌面上传感器正对尺子将一个 A4 大小的白色硬纸板作为反射面垂直立在尺子前方。从 10cm 开始每 5cm 记录一次 ADC 读数一直测到 80cm得到一组表。之后在程序里用一张二维表存数据运行时根据当前 ADC 值找到它落在哪两个标定点之间再做线性插值。以下是我在标准环境下实测得到的一组参考数据前面提到过不同个体会有偏差必须自己重新标定距离(cm)ADC读数参考电压参考(V)101402.75151202.3520971.9025831.6330721.4240561.1050460.9060380.7580280.55把标定表放在代码里距离换算函数长这样// 标定表ADC值从小到大对应距离从远到近按ADC升序排列 code unsigned char adcTable[] {28, 38, 46, 56, 72, 83, 97, 120, 140}; code unsigned int distTable[] {80, 60, 50, 40, 30, 25, 20, 15, 10}; // ADC值转距离返回单位cm使用线性插值 unsigned int mapADC2Distance(unsigned char adc) { unsigned char i; unsigned int dist; // 超出标定范围直接返回端点 if(adc adcTable[0]) return distTable[0]; // 太远返回最大测量距离 if(adc adcTable[8]) return distTable[8]; // 太近返回最小测量距离 for(i 0; i 8; i) { if(adc adcTable[i] adc adcTable[i1]) { // 线性插值注意ADC与距离成反比 unsigned long tmp; tmp (unsigned long)(adc - adcTable[i]) * (distTable[i1] - distTable[i]); tmp tmp / (adcTable[i1] - adcTable[i]); dist distTable[i] - (unsigned int)tmp; // 实际插值方向可能因表排列而异以实测打印为准微调 return dist; } } return distTable[0]; }这个函数的核心逻辑是ADC 值落在了某个区间内物理距离也落在对应的两个标定距离之间按照线性的比例关系把距离算出来。需要特别提醒的是因为距离越近 ADC 值越大表格是从远到近排列的插值方向正好和 ADC 方向相反。这里我保留了注释因为你手上的标定数据肯定和我不同套用的时候最稳的办法是先打印出几组 ADC 和对应距离用串口看插值方向是否正确不对就把减法的方向反过来。4.2 一个带中值滤波的采样与换算函数GP2Y0A21YK0F 单次 ADC 读数的随机噪声还是比较明显的不做滤波直接看距离读数能上下跳 2cm~3cm对于要求稳定输出的避障项目来说偏抖。工程上最实用的是“去极值滑动平均”连续采 5 次排序后去掉最大值和最小值对中间 3 个数求平均。这样既能滤掉偶尔出现的尖峰噪声又不会因为取所有值的平均而把真实距离拉偏。// 软件延时11.0592MHz下约1ms void delay_ms(unsigned int ms) { unsigned int i, j; for(i 0; i ms; i) for(j 0; j 115; j); } // 连续采样5次去极值平均返回稳定的ADC值 unsigned char getDistanceFiltered(void) { unsigned char i, j; unsigned int buf[5]; unsigned int temp; for(i 0; i 5; i) { buf[i] PCF8591_ReadADC_DiscardFirst(0); delay_ms(5); // 等待传感器输出和ADC转换稳定 } // 冒泡排序 for(i 0; i 4; i) { for(j i 1; j 5; j) { if(buf[i] buf[j]) { temp buf[i]; buf[i] buf[j]; buf[j] temp; } } } // 舍去buf[0]和buf[4]取中间三个平均值 temp (buf[1] buf[2] buf[3]) / 3; return (unsigned char)temp; }采样间隔里的 5ms 是有讲究的。PCF8591 是 8 位 ADC转换速度本身很快但传感器输出电压的变化不是瞬间稳定的给它几毫秒建立时间可以避免读到过渡态。连续采 5 次所用的总时间大约 20ms 多和传感器本身的采样周期约 38ms搭配起来刚好不会因为采样太快而读到太多重复值。4.3 反射面材质和颜色对读数的实际影响这个传感器标称精度在 ±3cm 左右但这个指标有个前提反射面得是能产生漫反射的物体。如果你前方是一面纯黑色的哑光墙体、一块黑绒布或者是一根直径只有几厘米的金属圆柱读数误差会明显变大。黑色物体吸收红外光能力太强返回的 PSD 光斑能量变弱芯片可能把弱信号误判成“更远”。金属圆柱或者光滑漆面则容易产生镜面反射反射光不被接收器收到读数也会偏向偏远。实际做避障小车时尤其要注意黑色桌腿、黑色书包这类物体在传感器看来几乎像消失了一样等小车真的撞上去了程序可能还认为前方是安全的。我见过不止一个抄了代码就跑的同学抱怨“传感器不准”结果拿个黑色保温杯放在正前方测试那当然测不准。解决思路不是去换传感器而是根据应用场景设定合理的安全阈值宁可把检测距离放得保守一些也不要把宝全押在标称精度上。5. 实测中的问题排查清单从电压乱跳到读数为05.1 现象一串口打印全是255如果串口打出来的 ADC 值永远是 255大概率不是传感器的问题而是 I2C 读数据这个环节出了故障。排查顺序我建议这么走检查 PCF8591 的电源和地。芯片没供电时 I2C 通信肯定失败。检查 A0/A1/A2 引脚的电平设置确认写地址是 0x90、读地址是 0x91。三个引脚有个别没接好地址就可能变成 0x92、0x94通信直接失败。检查 SCL/SDA 上拉电阻。这个是经验之谈上面已经强调过没有上拉电阻波形根本拉不高。检查 I2C_WaitAck 的返回值。如果每次通信都返回 1无应答最常见的原因就是器件地址错误或接线错误。我调试的时候喜欢在 I2C_WaitAck 返回无应答后加一个串口打印标记这样能立刻知道是时序问题还是地址问题不用对着代码干瞪眼。5.2 现象二数据跳变剧烈距离没变读数一直抖传感器静止前方障碍物也静止但串口数据一直在 40cm 到 70cm 之间乱跳。这种问题我遇到过几次原因各不相同传感器供电和电机共用一个电源。电机一启动电压跌落传感器输出电压也跟着波动。解决办法是给传感器单独加一颗 78M05 稳压或者在电源入口加大电容。传感器输出的模拟线太长而且和电机驱动线捆在一起。杜邦线从传感器拉到开发板有十几厘米这中间如果和 PWM 电机线并行走线电机的电磁干扰直接耦合进模拟信号。解决办法是让模拟线尽量短并且和电机线保持距离。如果没办法缩短距离就在传感器输出端接一个 1kΩ 电阻加 0.1μF 电容到 GND组成低通滤波。环境里有阳光直射或强红外干扰。虽然传感器内部有调制解调电路能抑制大部分环境光影响但太阳光直射传感器接收窗口时读数还是会波动。把窗帘拉上试试就知道是不是这个原因。5.3 现象三近距离读数反而变大这种情况就是我在第 1.2 节提到的“近距离翻转”。当物体进入 10cm 以内输出电压会急剧下降程序把低电压换算成了远距离。处理办法有两个一是在代码里识别这种异常状态当 ADC 值突然越过最高标定点继续增大或者电压跌到接近 0.4V 以下直接判定为超近距离触发报警二是换用测量范围更小的传感器比如 GP2Y0A41SK0F4cm~30cm或者 GP2Y0A710K0F100cm~550cm不同场景选不同型号不要指望一颗传感器通吃所有距离段。5.4 现象四用手挡在前面数值不对用手掌、身体挡在传感器前面测出来的距离和用纸板测出来的完全不吻合。这个不是故障而是反射面材质差异导致的正常现象。人手表面红外反射特性和白纸板区别很大尤其是手掌红润部分对红外光的吸收和散射都不是理想的漫反射。用来测墙面、地面、纸箱这些常规物体基本稳定但用手掌测试环境光线强烈时读数偏差会更大。测试阶段最好统一用白纸板或浅色硬纸板保证标定和验证的反射条件一致避免把“测试方法不同”误判成“传感器不准”。6. 串口打印调试与完整主程序演示这套系统没有屏幕最直观的调试手段就是串口。STC89C52 的 UART 用定时器 1 做波特率发生器模式 28 位自动重装晶振 11.0592MHz、波特率 9600 时 TH1TL10xFD。这个配置是 51 单片机入门非常经典的一组参数串口助手收到的数据就是 ASCII 码不需要复杂的转义处理。// 串口初始化9600bps 11.0592MHz void UART_Init(void) { SCON 0x50; // 模式18位UART允许接收 TMOD 0x0F; TMOD | 0x20; // 定时器1模式2 TH1 0xFD; TL1 0xFD; TR1 1; ES 1; EA 1; }为了把 ADC 和距离值打印出来我直接在 main 里调用 printf 重定向到串口。C51 的 printf 会占不少 ROM但对学习工程来说无所谓能看到数据比省那几 KB 重要得多。void main(void) { unsigned char adc; unsigned int dist; UART_Init(); while(1) { adc getDistanceFiltered(); // 滤波后的ADC值 dist mapADC2Distance(adc); // 换算成距离 printf(ADC:%d Dist:%dcm\r\n, adc, dist); delay_ms(200); } }每 200ms 打印一次这样一个完整的采样周期不到 1Hz观察起来非常舒服。如果你嫌 printf 重也可以直接用 SBUF 手动拼字符串发送逻辑其实差不多但调试效率低一些。主程序跑通后我一般会在串口助手里盯着数据看几分钟验证几个典型距离10cm、20cm、30cm、50cm。如果这些点位的误差都在 ±3cm 以内说明标定表和插值函数没有问题。如果某个点偏差特别大优先怀疑那个距离段对应的标定数据是否有问题重新测那一两个点就好不需要全部重标。最后再说两句排障心得这整套方案做下来我最大的感受是GP2Y0A21YK0F 本身是一颗非常耐用的传感器大部分项目翻车都翻在“模拟信号采集链路”上而不是传感器本身。I2C 时序、PCF8591 的第一次上电垃圾数据、传感器近距离翻转、反射面材质干扰、电源纹波这些坑一个一个踩过来之后你会发现解决问题的思路比代码本身值钱得多。最后分享两个小技巧一是所有通过 I2C 读到的数据先用串口原样打印出来看趋势再做数学换算不要直接拿最终距离去调试二是标定表一定要存在 code 区用 code 关键字声明别浪费 RAM。这套代码跑在 STC89C52 上晶振 11.0592MHz直接复制进 Keil 工程就能编译运行。等你把 I2C 模拟时序彻底看懂了后面去驱动 OLED、EEPROM、气压计等各种 I2C 器件都会变得非常轻松。
返回列表