
简介本资源为2017年全国大学生电子设计竞赛电赛‘可见光室内定位’赛题的完整实战解决方案包面向电子信息类、自动化、计算机等专业本科生专为备赛训练、课程设计及嵌入式系统实践提供可运行参考。压缩包共280个文件涵盖67个头文件h、60个C源码c、33个编译中间文件o/d及14份PDF技术文档包含STM32F4平台下的TIM/RTC/RCC外设驱动、MPU姿态解算、LCD显示控制等核心模块代码辅以Keil工程配置uvprojx/uvoptx、调试脚本bat及HEX/AXF可执行镜像结构完整、即下即用。已有198人学习下载所有程序均经实测验证支持硬件快速部署与算法逻辑复现特别适合电赛新手理解光信号调制解调、多基站时间差定位TDOA实现及嵌入式实时系统协同开发全流程。1. 项目缘起一个被低估的经典赛题2017年的全国大学生电子设计竞赛有一个题目在当年和之后很长一段时间里都让很多参赛者和电子爱好者印象深刻那就是“可见光室内定位装置”。这个赛题的核心是要求参赛者利用可见光通常是白光LED作为信号载体在室内实现一个简单的定位系统。题目文件通常以“2017年电赛-可见光室内定位.zip”这样的压缩包形式流传里面包含了当年的赛题要求、评分标准等原始文档。乍一看这个题目有点“科幻”——用灯光就能知道自己在屋里的哪个位置但深入下去你会发现它巧妙地融合了通信、信号处理、单片机控制和几何算法等多个电子工程的核心领域。它不像一些纯算法或纯硬件的题目那样偏科而是要求参赛者具备系统级的整合能力。从LED驱动电路的设计到光信号的调制解发射再到接收端的光电转换、信号解调最后到定位算法的实现与显示一整条链路都需要自己打通。这正是电赛题目的魅力所在也是它成为经典的原因它模拟了一个真实、完整的小型产品开发流程。我之所以对这个项目念念不忘是因为它揭示了一个在物联网和智能家居背景下非常实际的需求低成本、高精度的室内定位。虽然我们现在有Wi-Fi、蓝牙信标等方案但在一些对电磁敏感、或者需要极高更新频率、极低成本的场景下可见光定位Visible Light Positioning, VLP依然有其独特的优势。通过复现和深化这个项目我们不仅能重温经典更能理解一种有别于无线电的定位技术思路这对于拓宽技术视野非常有帮助。2. 系统核心原理光如何成为“坐标尺”要理解可见光室内定位首先要抛开“照明”的固有印象把LED灯看作一个无线信号发射器。它的核心原理与无线电定位如GPS类似都是利用信号传播的一些特性来反推位置只不过载体从无线电波变成了光波。2.1 三种主流的定位原理在这个赛题语境下通常有三种基础原理可供选择它们各有优劣也直接决定了后续硬件和软件的复杂度。2.1.1 基于接收信号强度RSSI的定位这是最直观也是当年很多队伍首选的方法。其原理是光在空气中传播时强度会随着距离的增加而衰减遵循平方反比定律。如果我们已知发射灯的位置和其发射功率通过测量接收端如光电二极管收到的光强就能估算出接收端到该灯的距离。但这里有个巨大的坑理想很丰满现实很骨感。光强的衰减模型在理想均匀介质中才成立。在实际房间里墙壁、家具的反射多径效应人员走动造成的遮挡甚至LED灯本身的老化、供电电压的波动都会严重影响接收光强的读数。这就导致单纯靠RSSI算出的距离误差非常大可能达到米级很难用于精确定位。所以纯RSSI方案往往需要极其复杂的现场校准和补偿算法并不推荐作为唯一依据。2.1.2 基于到达时间差TDOA的定位这是一种更高精度的方法。其思路是让多个已知位置的LED灯同步发射带有时间戳的光信号。接收端会先后收到这些信号通过计算信号到达的时间差可以推算出接收端到每两个灯之间的距离差。这就在空间中形成了一条双曲线多条双曲线的交点就是接收端的位置。TDOA的精度理论上可以做到厘米级但它对硬件的要求极高。首先需要所有发射灯之间实现高精度的时间同步纳秒级这本身就是一个挑战。其次接收端需要能精确测量光信号到达的微小时间差这对接收电路的时间分辨率带宽和时钟稳定性提出了苛刻要求。在当年的比赛时限和常用单片机如STM32的性能下实现稳定可靠的TDOA非常困难。2.1.3 基于身份编码ID与三角测量的定位这是当年平衡了难度与精度之后最可行、最主流的方案也是我们今天重点讨论的。它的核心思想可以概括为“认灯”“测角”。身份编码认灯我们不在光信号里传递复杂的时间戳或强度信息而是让每个LED灯发射一个唯一的、简单的数字ID编码比如001 010 100。这个编码通过一种叫“开关键控OOK”的方式调制到LED的亮灭上。接收端的光电传感器接收到明暗变化的光信号后解调出这个ID从而知道自己“看到”的是哪一盏灯。角度感知测角如何通过“看到”了哪盏灯就知道位置呢这里的关键是接收端不是一个简单的“光强计”而是一个有方向性的“光学探测器”。通常我们会使用一个遮光筒套在光电二极管或光敏电阻外面。遮光筒就像一个望远镜的镜筒只有正对光源方向的光线才能直射进来侧面来的光大部分被筒壁遮挡。这样当接收端旋转到信号强度最大时遮光筒的轴向就大致指向了光源的方向。三角测量在室内固定位置安装至少两个最好三个已知坐标的LED信标灯。接收端通过旋转分别找到对准每个灯的方向即方位角。知道了两个发射点的位置和接收端看向它们的两个方向线这两条方向线的交点就是接收端自身的位置。这就是最简单的三角定位法。这个方案巧妙规避了测距不准和同步难的痛点将问题转化为相对容易实现的编码识别和方向寻找。精度主要取决于方向测量的精度而这可以通过机械结构旋转云台和软件算法峰值搜索来优化。2.2 系统整体架构设计基于ID编码三角测量的方案整个系统可以划分为发射端、接收端和显示端三大部分。发射端由多个通常2-4个固定在室内已知坐标点的LED灯组成。每个灯由一个单片机如51、STM32控制以不同的频率或编码循环发送自己唯一的ID。LED需要工作在一定的频率几百Hz到几KHz以避免与环境光50/60Hz工频干扰同时要让人眼不易察觉闪烁。接收端这是系统的核心。主要包括光学接收头光电二极管遮光筒负责将光信号转换为微弱的电信号。信号调理电路包括前置放大器、带通滤波器、比较器。用于放大微伏级的有用信号滤除环境光和其他噪声并将模拟信号整形成单片机可以识别的数字方波。主控单片机负责解码数字方波中的ID并控制一个舵机或步进电机旋转接收头。通过扫描找到信号最强的角度即为对准某个灯的方向。同时它还需要通过串口等通信方式将角度数据发送出去。显示端通常是一个上位机软件如Qt、LabVIEW编写或另一个带屏幕的单片机。它接收来自接收端的角度数据根据已知的信标灯坐标运行三角定位算法实时计算出接收端的位置X Y坐标并在平面图上动态显示出来。3. 硬件设计与选型从光电转换到信号整形硬件是实现一切算法的基础在这个项目中硬件设计的优劣直接决定了系统能否稳定工作。下面我们拆解几个关键模块。3.1 发射端如何让LED“说话”发射端的任务很简单让LED灯以特定的频率和编码规律闪烁。但“简单”背后有细节。3.1.1 LED驱动电路不能直接用单片机的IO口驱动大功率LED。IO口的驱动能力有限通常20mA左右且压降会导致LED亮度不足、不稳定。我们需要一个简单的驱动电路。 一个经典的方案是使用NPN三极管如S8050或MOS管如SI2302作为开关。单片机IO输出高电平时三极管导通电流从VCC流经LED和限流电阻、三极管到地LED点亮。限流电阻R的计算很关键R (VCC - Vf_led - Vce_sat) / I_led。其中Vf_led是LED正向压降白光约3.0-3.4VVce_sat是三极管饱和压降约0.2VI_led是期望的LED工作电流根据亮度需要可选20-100mA。例如VCC5V Vf3.2V Vce0.2V I50mA 则R (5-3.2-0.2)/0.05 32Ω 可选择33Ω的电阻。确保LED在调制频率下能快速亮灭。3.1.2 编码与调制为了让每个灯有唯一身份我们需要编码。最简单的就是用不同频率的方波。例如灯A以1kHz闪烁灯B以2kHz闪烁。接收端通过测量信号频率来区分它们。但频率容易受干扰且需要较复杂的测频电路。 更可靠的方法是数字编码。例如采用曼彻斯特编码或自定义的帧结构。一个简单的帧可以设计为同步头一段特定频率的方波如10个1kHz脉冲 数据位用脉冲宽度表示如短宽代表0长宽代表1 停止位。这样每个灯循环发送自己的ID如3位二进制。单片机使用一个定时器产生PWM或精确的延时即可实现。3.2 接收端如何从噪声中提取“声音”接收端是硬件设计的难点也是最容易出问题的地方。它的任务是把微弱的、淹没在强环境光噪声中的有用光信号变成干净的数字信号送给单片机。3.2.1 光电传感器选型常用有两种光电二极管和光敏电阻。光电二极管响应速度快可达微秒级非常适合我们这种几百Hz到KHz的调制信号。它工作在反向偏压模式时线性度好噪声低。是首选。光敏电阻成本低但响应速度慢毫秒级对于快速变化的信号会严重失真导致解码失败。不推荐用于此项目。我强烈建议使用光电二极管型号如BPW34硅PIN光电二极管它价格便宜响应快光谱范围也覆盖可见光。3.2.2 核心跨阻放大器TIA设计光电二极管输出的是微弱的电流信号几微安到几十微安。第一步就是把这个电流信号转换成电压信号并且要放大。这里绝对不能使用普通的同相或反相电压放大器因为光电二极管的高输出阻抗会导致信号严重衰减。 必须使用跨阻放大器。它的原理是将光电二极管接在运放的反相输入端和输出端之间利用负反馈将电流直接线性地转换为电压Vout -Iph * Rf。其中Iph是光电二极管产生的光电流Rf是反馈电阻。运放选型必须选择低偏置电流、低噪声的运放。通用运放如LM358的偏置电流太大纳安级会淹没微安级的光信号。推荐使用JFET输入型或CMOS输入的运放如TLV2462、OPA344、MCP6002等它们的偏置电流在皮安级。反馈电阻Rf决定放大倍数。假设光电流最大50uA我们希望输出电压最大2.5V则Rf 2.5V / 50uA 50kΩ。可以先用一个50kΩ的可调电阻调试确定最佳值后换为固定电阻。反馈电容Cf这是一个关键且易忽略的元件。它并联在Rf上用于抑制高频噪声和防止振荡。其值很小通常在几皮法到几十皮法。没有它电路可能自激振荡输出杂乱无章。可以通过实验确定用示波器观察输出调整Cf直到信号清晰稳定。3.2.3 滤波与整形电路TIA输出的电压信号仍然叠加着大量的噪声环境光、工频干扰、电路噪声。我们需要滤波。带通滤波器因为我们知道发射信号的频率范围比如500Hz-3kHz可以设计一个带通滤波器只让这个频段的信号通过极大地抑制带外噪声。可以使用一个高速滤波器截止频率500Hz和一个低通滤波器截止频率3kHz级联构成。运放可以复用TIA后的另一路。电压比较器经过放大滤波后的信号是模拟的正弦波或类正弦波。我们需要将其转换为单片机可以读取的数字方波0或3.3V。这就是比较器的作用。将滤波后的信号接入比较器同相端反相端接一个可调的参考电压通过电位器分压得到。当信号电压高于参考电压时输出高电平反之输出低电平。这样光信号的“有/无”或“强/弱”就被转化为了数字脉冲。比较器滞回施密特触发器非常重要如果比较器没有滞回当输入信号在参考电压附近有微小波动时输出会产生一连串抖动的毛刺导致解码错误。我们需要引入正反馈形成一个电压窗口滞回电压。例如当输出为高时比较阈值自动抬高一点输出为低时阈值自动降低一点。这样就能有效消除抖动。可以在比较器输出端和同相输入端之间连接一个较大的反馈电阻如10MΩ来实现。3.2.4 方向性接收与机械结构为了测量方向我们必须让接收头有方向性。这就是“遮光筒”的作用。材料使用不透光的材料如黑色亚克力管、涂黑的纸筒或金属管。尺寸长度和内径需要权衡。筒越长方向性越好波束越窄定位越准但接收到的光强也越弱可能影响信噪比。筒内径越小方向性也越好。一个常见的折中尺寸是长度5-10cm内径1-2cm。安装光电二极管必须安装在遮光筒的底部中心位置确保只有正前方的光能直射到感光面。整个接收头需要牢固安装在一个舵机的摆臂上由单片机控制其进行0-180度的扫描。4. 软件算法实现解码、寻向与定位硬件提供了干净的脉冲信号软件的任务是解读这些信号并计算出最终位置。4.1 发射端固件生成可靠编码发射端单片机程序相对简单核心是利用定时器中断产生精确的时序。// 伪代码示例 (以灯A ID001为例) #define ID_A 0b001 #define SYNC_PULSES 10 #define BIT_PERIOD_MS 5 // 每位持续5ms void Timer_IRQ_Handler() { static int state 0; static int bit_count 0; static int sync_count 0; switch(state) { case 0: // 发送同步头 if(sync_count SYNC_PULSES) { LED_TOGGLE(); // 以1kHz频率翻转LED sync_count; } else { state 1; sync_count 0; bit_count 0; } break; case 1: // 发送数据位 if(bit_count 3) { // 假设ID为3位 int bit_val (ID_A (2-bit_count)) 0x01; // 从最高位开始发 if(bit_val 1) { // 发“1”高电平持续时间长例如3ms高2ms低 if(当前时间 3ms) LED_ON(); else LED_OFF(); } else { // 发“0”高电平持续时间短例如1ms高4ms低 if(当前时间 1ms) LED_ON(); else LED_OFF(); } // 检查一位是否发完 if(当前时间 BIT_PERIOD_MS) { 重置计时 bit_count; } } else { // ID发完回到发送同步头状态 state 0; } break; } }关键在于定时精准和抗干扰。可以在代码中加入看门狗防止程序跑飞导致LED发送异常。4.2 接收端固件解码与角度测量接收端程序是核心主要包括信号解码和舵机控制扫描两大任务。4.2.1 解码算法比较器输出的数字脉冲信号接入单片机的外部中断引脚或带输入捕获功能的定时器引脚。方法一输入捕获脉冲宽度测量。利用定时器的输入捕获功能测量脉冲的高电平宽度。根据我们定义的编码规则例如同步头是固定数量的等宽脉冲数据位“1”是宽脉冲“0”是窄脉冲来解析数据。首先寻找同步头连续检测到多个如10个宽度在预期范围内的脉冲。同步头确认后开始解析后续的数据位测量每个脉冲周期内高电平的持续时间与阈值比较判断是“1”还是“0”。组合几位数据得到ID。进行简单的校验如奇偶校验通过后则认为成功解码一个灯的信号。方法二外部中断软件计时。如果没有足够的输入捕获通道可以用外部中断普通定时器实现。在中断服务程序里记录引脚电平跳变的时间戳然后在主循环中处理这些时间戳序列同样通过判断脉冲宽度来解码。解码程序必须非常健壮要考虑信号丢失、噪声毛刺等情况。我的经验是加入“超时重置”机制。如果在预期时间内没有收到完整的同步头或数据帧就将解码状态机重置到初始搜索状态避免“卡死”在某个错误状态。4.2.2 舵机控制与角度搜索解码成功后我们需要知道当前接收头指向哪个角度时信号最强即成功解码。这需要一个搜索过程。初始化舵机归零指向一个起始角度如0度。扫描单片机控制舵机以固定的步进角度如1度或2度逐步旋转。采样在每个角度停留一小段时间如100ms在这段时间内尝试解码信号。如果成功解码到某个ID则记录下当前角度和接收到的信号强度可以通过ADC采样比较器前的模拟信号电压或统计单位时间内解码成功的次数作为“强度”指标。峰值判断通常信号强度会随着指向角度偏离光源方向而衰减。我们可以记录下扫描整个范围内信号强度最大时对应的角度即为该光源的方位角。优化策略全范围0-180度扫描比较耗时。可以第一次粗扫步进5度找到信号大致区域第二次在该区域附近细扫步进1度提高效率和精度。4.3 上位机定位算法从角度到坐标接收端通过串口将测量到的角度数据例如ID:A, Angle:45.3发送给上位机。上位机或另一个负责显示的单片机进行三角运算。假设我们使用两个灯进行定位二维平面最少需要两个参考点。已知灯1坐标(x1, y1)灯2坐标(x2, y2)接收端测得的相对于正北或某个参考方向的方位角θ1指向灯1 θ2指向灯2。求接收端坐标 (x, y)。根据几何关系对于每个灯可以得到一条方向线方程tan(θ1) (y - y1) / (x - x1)tan(θ2) (y - y2) / (x - x2)这是一个二元一次方程组。理论上只要θ1不等于θ2即接收端不在两个灯的连线上就能解出唯一的(x, y)。在实际编程中直接解这个方程即可。使用三个灯时可以通过最小二乘法求最优解能有效降低单个角度测量误差的影响。一个重要的细节角度系统的统一。单片机测量的舵机角度是相对于其机械零点的。而上位机计算用的角度如θ1 θ2应该是相对于统一的坐标系比如房间的东北方向。因此在安装发射灯时需要记录下每个灯在房间坐标系中的坐标同时还需要通过一次校准确定接收头“零度”方向对应房间坐标系的那个方向并在角度数据发送前进行换算。5. 调试心得与避坑指南这个项目从电路焊接、程序编写到系统联调每一步都可能遇到意想不到的问题。下面分享几个我踩过的坑和总结的经验。5.1 硬件调试没有示波器寸步难行问题现象接收端无论如何都解不出码或者解码极不稳定。排查步骤先查发射用手机摄像头很多手机CMOS有卷帘快门对着LED灯看应该能看到明显的暗条纹说明LED在高速闪烁。这是最粗略的检查。关键点测量用示波器探头从后往前查。测比较器输出看是否有规则的方波脉冲如果没有或者全是噪声问题在前级。测运放输出带通滤波器后看是否有清晰的正弦波幅度是否足够如果这里信号很好但比较器输出不对调整比较器的参考电压或者检查是否忘了加滞回。测TIA输出这里应该能看到被放大但依然混杂着低频噪声的信号。如果信号非常微弱检查光电二极管是否接反光电二极管有正负极检查Rf阻值是否合适检查运放供电是否正常。测光电二极管两端电压在无光时反向偏压是否正常常见坑点电源噪声模拟电路部分尤其是TIA对电源噪声非常敏感。一定要用线性稳压电源如LM7805为模拟部分单独供电并与数字部分单片机、舵机的电源用磁珠或0Ω电阻隔离。舵机启停的瞬间会产生很大的电流毛刺足以让整个模拟前端失灵。虚焊与接触不良特别是运放、比较器这种多引脚器件以及反馈电阻、电容这类小元件一定要焊接牢固。有时问题就是某个引脚虚焊导致的。环境光干扰虽然带通滤波器能滤除大部分但强烈的日光灯或窗户自然光仍可能使光电二极管饱和。可以尝试在遮光筒前端加一个可见光透过、红外截止的滤光片因为很多白光LED含有蓝光芯片激发荧光粉其光谱与日光有一定区别滤光片能提升一些信噪比。5.2 软件调试逻辑与时序的陷阱问题现象角度测量跳动大定位点飘忽不定。排查步骤固定测试将接收头固定对准一个灯看解码是否连续稳定。如果不稳定检查解码程序的抗干扰处理如脉宽容差范围是否设得太小。检查角度数据让接收头缓慢匀速旋转通过串口打印角度和对应的信号强度值。观察信号强度随角度变化的曲线是否平滑峰值是否明显。如果曲线很平缓说明遮光筒方向性不好需要加长或减小内径。舵机控制舵机本身有回差且不同舵机的中位脉宽可能有细微差异。确保你发送的PWM脉宽与舵机角度是线性对应的。最好对每个舵机进行校准发送几个已知角度0 90 180用尺子测量实际转动角度是否吻合。经验技巧数字滤波对于解码得到的角度不要直接用单次测量值。可以采用滑动平均滤波或卡尔曼滤波。例如连续测量10次角度去掉最大最小值后取平均能有效平滑抖动。异常值剔除在三角定位计算时如果某个角度值明显偏离历史值或物理上不可能例如角度突变超过30度应该将其视为无效数据丢弃等待下一次有效测量。分时处理单片机同时要控制舵机、解码信号、通信任务繁重。好的程序结构很重要。建议使用状态机定时中断的方式。主循环负责调度定时中断负责精确定时如舵机PWM生成、解码时间窗口判断避免使用delay()这类阻塞函数。5.3 系统集成误差来源与精度提升即使每个模块单独工作都正常整个系统联调时精度可能还是不理想。误差主要来自几个方面机械误差舵机安装不水平遮光筒与舵机旋转轴不同心。标定误差发射灯在房间内的实际坐标测量不准接收头“零度”方向与房间坐标系的对齐不准。算法误差三角定位公式在角度接近90度时tan函数变化剧烈会放大测量误差。因此两个发射灯与待测点形成的夹角最好在30度到150度之间避免三点接近一条直线。提升精度的小技巧多点测量取平均让接收端在同一个位置多测几组角度数据上位机计算多个定位结果后取平均。引入高度信息如果房间高度已知且接收端高度固定例如放在桌子上可以将三维定位问题简化为二维有时能减少一个参考点。软件补偿通过大量测试建立一个“角度测量值-真实角度”的误差查找表在软件中进行补偿。回顾整个项目从理解原理、设计电路、编写代码到调试优化是一个完整的工程实践闭环。它锻炼的不仅仅是某个知识点而是解决一个复杂、跨学科问题的系统能力。虽然可见光定位目前还不是消费市场的主流但通过动手实现它你对信号处理、嵌入式系统、控制算法的理解会上一个实实在在的台阶。那些调试电路时对着示波器波形苦思冥想的夜晚那些修改代码后终于看到定位点稳定出现在屏幕上的瞬间才是这个项目带给一名工程师最宝贵的财富。本文还有配套的精品资源点击获取