ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32红外避障模块+OLED显示实战:从接线到滤波完整教程

2026/9/9 9:47:17 拓冰建站 浏览量
STM32红外避障模块+OLED显示实战:从接线到滤波完整教程 1. 项目概述与学习目标一块STM32F103C8T6最小系统板一个十几块钱的红外避障模块再加一块0.96寸OLED屏幕这三样东西组合在一起几乎是我见过最“出活”的嵌入式入门方案。把红外避障模块的采样判断、OLED的I2C显示、软件滤波去抖、串口打印调试这些基本功全部过一遍后面再做智能小车避障、感应式智能家居、低成本人数统计这类项目你会发现底层逻辑全部相通只是换了个传感器而已。这篇教程面向正在学STM32的初学者也适合做课程设计、电子竞赛但时间紧需要快速出结果的朋友。你不需要有很深的电路基础只要会用Keil或者STM32CubeIDE知道怎么用CubeMX生成工程照着本文的接线和代码思路走一个下午就能把“障碍物检测状态显示”整套跑通。我用的HAL库开发环境是STM32CubeMX Keil MDK代码结构清晰后续想扩展也很方便。这个项目最让我看重的一点是它在硬件和软件两个维度上都包含了嵌入式开发最典型的痛点。硬件上要处理传感器信号与主控IO电平匹配、供电稳定性、引脚冲突软件上要处理传感器输出的抖动、实时显示、调试数据可视化。把这些理顺了你对“单片机怎么和真实世界打交道”这件事的理解会上一个台阶而不是停留在点灯阶段。1.1 这套组合能帮你打通哪些技能点很多人学STM32是从点灯开始的点完灯就不知道下一步该干什么。红外避障模块加OLED这个组合的好处是它把一条完整的“感知-处理-输出-调试”链路展示得非常清晰。第一感知端你要搞懂红外避障模块的工作原理搞清楚它的模拟信号和数字信号是怎么产生的为什么靠近障碍物时输出会变化。第二处理端你要学会对传感器输出做软件滤波因为红外模块受环境光、反射面材质影响很大不做滤波就会出现“明明前面没东西却乱报”的尴尬情况。第三输出端你要把结果实时显示在OLED上这涉及I2C通信、SSD1306驱动移植、字符显示和数据处理。第四调试端你要会用串口调试助手把原始采样值、滤波结果、模块状态实时打印出来对照现象做出判断。这四个技能点恰好是绝大多数嵌入式项目的基本循环。1.2 硬件选型与方案取舍STM32F103C8T6为什么经久不衰原因很简单价廉物美生态成熟。它是一颗Cortex-M3内核、主频72MHz的芯片Flash有64KBRAM有20KB片上外设丰富性价比极高。最典型的蓝色“最小系统板”只要几块钱到十几块钱板载8MHz晶振和复位电路有USB转串口芯片可以直接下载程序对初学者来说非常友好。红外避障模块我建议选三线制VCC、GND、OUT的数字量输出型模块内部集成红外发射管、接收管和LM393比较器。这种模块通过电位器调节灵敏度阈值输出0和1两种电平处理起来比模拟量输出的版本简单得多也不用经过ADC转换。如果你拿到的是带AO口的四线模块也没关系AO口悬空不接只用DO口就行。OLED显示模块优先选0.96寸、I2C接口、SSD1306控制器的版本。相比SPI接口的OLEDI2C版本只需要两根数据线SCL和SDA接线少、占用引脚少非常适合和传感器模块共用同一块板子。市面上有些OLED用的是SH1106控制器驱动代码稍微有点区别买的时候优先确认是SSD1306。这里有一个重要的选择逻辑为什么不用串口屏也不用LCD1602因为OLED体积小、功耗低、显示内容灵活I2C只占两个引脚而且自己写驱动代码能让你真正理解底层协议。用串口屏虽然开发速度快但价格贵、依赖上位机配置工具对学习理解帮助不大。LCD1602则需要8根数据线占引脚太多在这个小项目里性价比不高。2. 硬件准备、接线与自检这个项目的硬件成本非常低总价不超过三十块钱却能覆盖一个完整的智能感知系统。动手接线之前我建议你把每块板子的引脚定义先搞清楚再对着表一根一根接最后上电前做一遍自检。硬件接线这种事细心比聪明重要得多。2.1 三块硬件的核心参数和引脚定义STM32F103C8T6最小系统板的引脚非常多但咱们实际用到的就只有几个。需要注意的是这块板子有两种常见引脚标注方式一种是丝印直接标PA、PB这种端口名一种是只标数字比如PB6标成36脚第一次用容易搞混。建议以芯片上的丝印为准或者对照原理图确认引脚位置。红外避障模块的引脚一般有三个VCC接电源正极GND接电源负极OUT是信号输出脚。有的模块还会有两个固定螺丝孔方便安装在车架或支架上。模块正面有一个红外发射管和一个红外接收管旁边有一个蓝色的可调电位器或者是一个十字形可调电阻用来调节检测距离阈值。0.96寸OLED模块有四个引脚VCC接3.3V电源GND接地SCL接I2C时钟线SDA接I2C数据线。有些OLED模块的丝印上写的是D0和D1而不是SCL和SDA其实都是一回事D0对应时钟D1对应数据。少数版本会有RES和DC引脚的那种那是SPI接口或者带复位脚的版本接线方式不同买的时候注意看模块背后的丝印说明。2.2 完整接线表与供电注意事项我的推荐接线方案是OLED挂到STM32的I2C1接口上PB6做SCLPB7做SDA红外避障模块的OUT输出接到PA0全部供电走3.3V。具体对应关系如下模块引脚STM32引脚说明OLEDVCC3.3VOLED工作电压3.3VOLEDGNDGND共地OLEDSCLPB6I2C1时钟线OLEDSDAPB7I2C1数据线红外避障VCC3.3V模块供电红外避障GNDGND共地红外避障OUTPA0数字信号输入接线的时候有三件事要特别注意。第一电源必须共地OLED、红外模块、STM32三者的GND一定要连在一起否则信号参考电位不一致会导致读到的电平完全是乱的。第二关于红外模块供电很多模块标注支持3.3V到5V但如果你用的是5V供电OUT输出高电平就有5V直接进STM32引脚理论上虽然因为内部钳位二极管暂时不会烧芯片但长期用并不安全而且有些国产模块5V供电时输出特性并不可靠。我实测下来3.3V供电完全能正常工作检测距离也够用所以建议统一用3.3V省去电平转换的麻烦。第三如果需要同时给几个模块供电注意最小系统板的3.3V输出能力有限不能接太多大功率外设好在OLED和红外模块都是毫安级的功耗完全没问题。2.3 上电前的自检清单接好线之后千万别急着上电花两分钟做一遍目视检查。先检查有没有接错引脚特别是VCC和GND有没有反接反接轻则模块发热损坏重则烧坏主控。再检查有没有虚焊或杜邦线松动的情况用万用表蜂鸣档测一下每根线的导通性最稳妥。确认接线无误后先只接USB线给STM32供电用万用表量一下模块VCC引脚的实际电压应该在3.3V左右。如果能测到这里再下一步操作就非常安全了。如果电压不对先别往下走优先排查电源线路上的短路或者断路问题。这一步看似多余实际上能帮你省下大量排查问题的时间。我见过太多人焊完板子直接上电结果OLED发烫、STM32发热最后拆掉重来就是因为没有做上电前的自检。3. 原理拆解红外检测、OLED显示与滤波必要性3.1 红外避障模块的工作机制很多初学者以为红外避障模块像雷达一样主动发射信号再接收回波其实它的工作原理要简单粗暴得多。模块上有一个红外发射管持续发射特定频率的红外光红外接收管则负责接收被物体反射回来的红外光。当障碍物出现在模块正前方一定距离内时反射光强度增大接收管感受到的光信号变强LM393比较器就会输出翻转。这里涉及到模块的核心部件LM393比较器。它把接收管感应的电压和一个由电位器设定的基准电压做比较。当反射光强、感应电压高于基准电压时输出端翻转为低电平或高电平取决于模块设计反之输出相反电平。调节电位器就是在改变这个比较阈值相当于调节检测灵敏度。这个原理有个天然弱点如果障碍物表面是黑色它会吸收大量红外光如果是透明玻璃光会直接透过去而不是反射回来如果环境里有强烈的红外干扰源接收管可能误判。这些问题正是后面要做软件滤波和阈值校准的原因。理解了这一点你就能明白为什么同一个模块在白色墙面和黑色桌面前检测距离差异巨大——这不是模块坏了而是红外反射特性不同导致的。3.2 SSD1306与I2C显示流程0.96寸OLED的驱动核心是SSD1306控制器芯片内部有一块128x64像素的显存每一像素对应一个比特位1表示点亮0表示熄灭。单片机通过I2C接口向SSD1306发送命令和数据就能控制整个屏幕的显示内容。I2C协议简单来说就是一条时钟线SCL和一条数据线SDA通过时序将数据一位一位传过去。SSD1306的I2C地址通常是0x3C或0x3D由模块上的电阻配置决定大多数模块默认是0x3C。这里有个非常坑的细节实际操作时写入地址还要左移一位变成0x78或0x7A因为I2C通信的地址字节最低位是读写标志位。很多初学者在HAL库的HAL_I2C_Mem_Write函数里填地址时踩坑就是因为没搞清楚这个左移的关系。显示流程分三层底层是向SSD1306发送初始化序列配置显示时钟、分频比、显示模式等中间层是提供写命令和写数据的函数上层是根据你要显示的内容比如字符、数字、汉字往显存里填对应的点阵数据。对于显示一个字符常见做法是准备好ASCII字符的点阵字库然后逐列写入显存的对应区域。有一个概念我要特别强调一下SSD1306的显存是分页的Page从Page0到Page7共8页每页覆盖8行像素。往屏幕上画字符时你需要算清楚字符落在哪一页、哪一列然后按页写入数据。这个概念刚开始容易绕晕但只要对照数据手册的显存映射图看上十分钟基本就豁然开朗了。3.3 环境干扰下的滤波必要性红外避障模块的原始输出信号在实际环境中几乎不可能是稳定的。我实测的时候发现一个经典现象把手慢慢靠近模块在临界距离附近OUT引脚的电平会快速抖动像“010101”这样来回跳变。这是因为比较器在阈值附近对微弱信号的变化极其敏感再加上环境光干扰、供电噪声等因素输出信号就产生了毛刺。如果不做任何处理直接拿这个信号去控制电机或者做逻辑判断后果就是小车明明没有碰到障碍物却频繁触发避障动作或者计数系统乱跳。这也正是软件滤波存在的意义所在——不改变硬件电路而是在代码层面把信号处理平滑去掉抖动毛刺。软件滤波方案通常有几种思路。第一种是多次采样表决法连续采样N次取出现次数最多的电平作为有效值第二种是去抖延时法检测到电平变化后延时一段时间再确认第三种是状态机消抖法通过状态转移图实现稳定的电平确认逻辑。我在本项目中做了前两种的组合并且实测效果已经足够稳定。具体的实现方式后面代码部分细聊。4. CubeMX工程配置与代码实现这个项目的软件部分我建议用STM32CubeMX生成工程框架然后用HAL库写逻辑代码。相比标准外设库HAL库的代码抽象程度更高配置也更灵活网上资料和例程数量最多遇到问题容易搜到答案。STM32F103C8T6的Flash只有64KB但HAL库本身占用较多的Flash空间编译的时候需要留意优化等级设置。4.1 用CubeMX生成HAL工程打开CubeMX芯片型号选择STM32F103C8Tx。时钟配置成最高72MHz外部8MHz晶振作为HSEPLL倍频到72MHzAPB2总线时钟设为72MHzAPB1总线设为36MHz。STM32F103的I2C外设挂在APB1上如果APB1时钟配置不对I2C的通信时序就会出问题这是很多人OLED显示乱码的隐藏原因之一。引脚配置部分在Pinout视图中找到PB6和PB7把它们设置为I2C1_SCL和I2C1_SDA这样就会自动启用I2C1外设。PA0设置为GPIO_Input模式用作红外模块的数字信号输入。USART1的TXPA9和RXPA10设置为串口模式方便后面用串口调试助手查看数据。I2C1的参数配置里速度模式选择Standard Mode100kHz就够用Fast Mode 400kHz虽然也能跑但稳定性不如标准模式对新手来说没必要贪快。GPIO的输入模式可以选择上拉输入Pull-up因为红外模块输出是推挽式的选上拉输入更保险一些。USART1设置为115200-8-N-1这是串口调试助手的默认常见配置。生成工程的时候Toolchain选择MDK-ARM代码生成器配置里勾选“Generate peripheral initialization as a pair of .c/.h files per peripheral”这样每个外设的初始化代码会单独成文件结构更清晰。注意在Project Manager里把堆栈大小稍微调大一点默认的0x400可能不太够用建议把Heap和Stack都设置成0x800。4.2 OLED驱动移植要点OLED驱动网上流传的代码版本很多有中景园的、有正点原子的、有各种个人改写的选择标准只有一个代码结构清晰、注释完整、能看懂。我第一次做这个项目时用的是一份精简版驱动只有四个核心函数OLED_Init、OLED_Clear、OLED_ShowString、OLED_ShowNum足够应付绝大多数显示需求。移植的时候要改的核心地方是I2C底层函数。如果用的是HAL库的硬件I2C那么写数据和写命令函数的内部就是调用HAL_I2C_Mem_Write传输的是单字节命令或数据。这里有一个性能问题要特别注意硬件I2C每次传输都有一定的封装开销如果要刷新整屏128x64像素共1024字节速度会有点慢但显示几个字符和数字完全没压力不用做优化。我的OLED驱动核心写命令和写数据函数如下void OLED_WriteCmd(uint8_t cmd) { HAL_I2C_Mem_Write(hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT, cmd, 1, 100); } void OLED_WriteData(uint8_t data) { HAL_I2C_Mem_Write(hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT, data, 1, 100); }注意这里的0x78就是0x3C左移一位的结果控制字节0x00表示后续跟的是命令0x40表示后续跟的是数据。这个写法是基于SSD1306的I2C协议格式理解了这个后面想扩展显示内容就非常灵活。初始化函数里面是一长串配置命令包括关闭显示、设置显示时钟分频、设置复用比、设置显示偏移、开启显示等这些命令序列在SSD1306的数据手册里都有网上的例程也几乎都是同款序列直接用就行。4.3 红外采集与软件滤波核心代码红外模块的采样和滤波是整个项目最关键的部分。我采用“连续多次采样多数表决去抖确认”的组合方案原理很简单每次读取传感器状态时连续采样11次如果其中9次及以上是高电平就判定当前状态为高如果9次及以上是低电平就判定为低如果两种情况都不满足就保持上一次的判定结果不变。uint8_t IR_ReadFiltered(void) { uint8_t high_cnt 0; uint8_t low_cnt 0; uint8_t i; for (i 0; i 11; i) { if (HAL_GPIO_ReadPin(IR_GPIO_Port, IR_Pin) GPIO_PIN_SET) high_cnt; else low_cnt; } if (high_cnt 9) return 1; // 检测到障碍物 else if (low_cnt 9) return 0; // 无障碍物 else return ir_last_state; // 不确定则保持上一次状态 }这里ir_last_state是全局变量每次滤波结束后会更新。这种方法的原理就是真实信号的毛刺持续时间非常短在连续采样中不可能连续9次都出现同一个错误的电平值所以它能有效滤除短脉冲干扰。代价是每次读取都要循环11次如果每10毫秒调用一次差不多有十几微秒的额外开销对多数场景完全可接受。但这里还有一个小问题如果在障碍物临界区域传感器的输出本身就是“走走停停”的缓慢变化即便滤波之后输出的翻转也会让系统显得不稳定。所以我还加了一个“变化确认延时”机制只有当新状态和旧状态不一致并且持续一段时间仍然不一致时才真正更新状态。用定时器或者简单的计数变量实现都行。我选择在主循环里用一个递减计数器如果状态发生变化但没有持续足够多次循环就忽略这次变化。两种方法叠加之后实测在临界区域来回晃动的手输出也非常稳定不会再出现频繁跳变。5. 联调步骤串口打印OLED显示实测现象软件写完不代表万事大吉真正的调试才刚刚开始。整个联调流程我建议分三步走先只通过串口观察原始数据和滤波数据的区别确认传感器和滤波函数工作正常再把结果显示到OLED上验证I2C通信和显示函数没问题最后把两者结合起来对照实际物理现象做整体判断。5.1 用串口调试助手观察滤波效果代码里加上串口打印功能每100毫秒输出一次当前状态和原始采样值。初始测试阶段先不要接OLED专注观察串口输出即可。printf(IR_raw: %d, IR_filtered: %d\r\n, ir_raw_value, ir_filtered_value);这里有个小技巧HAL库默认没有开启printf重定向功能你需要在工程里添加fputc函数的实现把输出重定向到USART1。int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 100); return ch; }同时记得在Keil的Options for Target里勾选“Use MicroLIB”否则printf会占用大量Flash资源编译出来的固件体积明显变大。打开串口调试助手比如SSCOM选择正确的COM口波特率设置成115200观察数据。正常情况下模块前面没有障碍物时输出应该是IR_raw: 0, IR_filtered: 0。用手挡在模块前方10厘米左右的位置输出变为IR_raw: 1, IR_filtered: 1。最关键的是观察快速移动手或者把手放在临界位置时滤波后的数据不会乱跳而原始数据可能已经在疯狂抖动了。如果你能看到这个区别滤波效果就已经得到验证。5.2 OLED实时显示障碍物状态串口数据验证通过之后把OLED接上在初始化函数里调用OLED_Init()和OLED_Clear()然后在主循环里显示当前状态和信号强度。显示内容可以这样设计第一行显示“IR Sensor:”第二行显示“Status: OK”或“Status: BLOCK”第三行可以显示滤波后的数值方便直观对照。OLED_ShowString(0, 0, IR Sensor:); if (ir_filtered_value 1) { OLED_ShowString(0, 2, Status: BLOCK); } else { OLED_ShowString(0, 2, Status: OK ); }这里需要说明一下OLED坐标的含义第一个参数是列坐标单位像素第二个参数是页坐标单位页每页8像素。在0.96寸128x64的屏幕上页坐标从0到7我习惯把第0行放在页0第2行放在页2因为字符高度是8像素页0和页2之间不会重叠显示效果干净利落。如果你发现OLED显示的内容有残影就是上一帧的内容没清干净需要在刷新前调用OLED_Clear()或者对要更新的区域填充空格。我遇到过显示数字时长度变化导致尾数残留的情况比如从“123”变成“5”屏幕会显示“523”就是因为没有清掉旧数据。解决方法是更新时先输出几个空格或者干脆每次更新整行。OLED显示还有一个小缺陷需要留意SSD1306的功耗较低但长时间高亮度显示容易老化实际项目里可以通过降低对比度或定期休眠屏幕来延长寿命。5.3 关键现象判断与模块调节联调过程中最需要耐心的是红外模块灵敏度的调节。模块上的电位器顺时针旋转通常表示增大检测距离灵敏度提高逆时针旋转表示减小检测距离。但也有个别模块的电位器方向是反的最直接的判断方法就是一边旋转一边观察串口输出。我的调节建议是先逆时针拧到底然后把手放在你希望检测到的距离比如15厘米再慢慢顺时针旋转直到串口输出恰好变成1检测到障碍物。然后再把手拿开确认输出变回0。如果拿开后输出还是频繁跳变说明灵敏度调得太高了。反过来如果手在很近的距离也没有触发说明灵敏度太低。调节的目标就是让模块在“你希望检测的最近距离”处可靠触发而不误报更远的目标。这里有一个经验值可以参考三线制红外避障模块的检测距离一般可以在2厘米到30厘米之间调节超过30厘米后稳定性急剧下降。实际使用中白色或浅色物体反射效果好检测距离远黑色哑光物体和透明材料反射效果差哪怕很近也可能触发不了。如果你做的是黑色桌面上检测白纸这种场景效果很好但如果是检测黑色边框的小车可能要把模块灵敏度调到最高才行。OLED显示和串口打印可以同时工作主循环里大概每100毫秒刷新一次显示每200毫秒打印一次串口数据。实际的运行现象是手伸过去OLED瞬间从“OK”变成“BLOCK”串口同步吐出最新的状态变化。如果OLED的刷新和串口打印产生卡顿多半是因为printf阻塞时间太长可以把打印间隔拉长或者用DMA方式发送串口数据不过这个项目里数据量极小不太会遇到。6. 常见问题排查与避坑记录做这个项目的过程中我积累了一些踩坑经验整理成速查表分享给大家。这些问题如果是初学者自己摸索可能一个就要折腾好几个小时但实际上原因都非常简单。6.1 问题速查表现象可能原因解决方法OLED完全不亮接线错误、I2C地址不对检查VCC/GND/SCL/SDA接线用I2C扫描代码确认设备地址OLED显示乱码I2C速率太高、APB1时钟不对把I2C速度降到100kHz检查CubeMX里APB1时钟是否36MHzOLED有部分不显示显存地址映射理解错误查看数据手册中SSD1306的页地址映射确认写入位置红外模块输出一直为高灵敏度调太高、环境红外干扰逆时针调电位器降低灵敏度改变模块朝向避光红外模块输出一直为低灵敏度太低、障碍物太远、物体反射差顺时针调电位器测试距离缩短改用浅色障碍物输出频繁抖动传感器临界区域、环境光干扰采用本文的多次采样状态保持滤波方案串口输出乱码波特率配置不一致确认CubeMX和串口助手的波特率都是115200串口无输出printf重定向没配好、TX/RX接反实现fputc函数并勾选MicroLIB交换TX/RX接线程序能跑但一上电OLED白屏OLED供电不足、初始化序列有误单独给OLED供电复查初始化命令序列6.2 我踩过的几个坑第一个坑是I2C地址混淆。第一次写HAL_I2C_Mem_Write的地址参数时我填了0x3C结果OLED完全不响应。排查了好久才发现I2C从设备地址需要左移一位变成0x78因为最低位是读写标志位。这个坑很多人都会踩填地址之前先想清楚自己填的是“7位地址”还是“8位地址”。第二个坑是红外模块的检测距离判断错误。我在白色桌面上把模块调到能检测20厘米结果放到深色桌面上变成几厘米都检测不到一度以为是模块坏了。后来才明白红外反射率是决定性因素。如果你要在不同环境场景下使用同一套硬件每次换场景前用串口确认一下检测距离是有必要的不要想当然。第三个坑是电源问题。我最初用USB转TTL供电USB线质量差导致电压偏低OLED亮度忽明忽暗。换了一根粗线并且直接用USB直连最小系统板后问题就消失了。实际上情况就是电压不稳时I2C通信容易出乱码红外模块的临界触发也不稳定问题表现五花八门根源却在供电上。所以遇到诡异问题先量电压总没错。第四个坑是printf重定向没有配置好串口助手一直没数据我还以为是串口坏了。后来发现HAL库的fputc没写printf其实默认输出到标准输出通常是重定向失败的导致数据根本没进串口。加上fputc和MicroLIB之后一切正常。6.3 项目完成后还能往哪个方向扩展这个项目的可扩展性很强这也是我推荐大家做的原因之一。做完基础版本之后你可以在几个方向上继续深化。第一个方向是加电机做成完整的红外避障小车。把滤波后的障碍物检测结果作为决策输入通过PWM控制电机转速和转向当检测到前方有障碍物时左转或右转规避。这个方向会涉及到电机驱动模块比如L298N、PWM调速、运动逻辑设计是一整个新的知识体系。第二个方向是把OLED换成带触摸的屏幕或者加上几个物理按键做成一个带菜单的“传感器信息中心”。第三个方向是把红外避障模块用热释电人体红外传感器替代配合OLED和蜂鸣器做一个简易入侵报警器这个方向非常适合作业展示。第四个方向是增加一个无线模块比如蓝牙或者WiFi模块把检测到的状态实时发送到手机APP上这样传感器数据就能脱离本地屏幕实现远程监控。无论往哪个方向走本项目打下的基础都不会白费。红外检测的原理、I2C显示、软件滤波思想、串口调试方法这些都是嵌入式开发里复用率最高的核心技能。这套组合做完之后你会明显感觉以后再拿到一个新的传感器模块无论是超声波的、温湿度的还是加速度的接到STM32上跑通的思路和方法都清晰了很多。因为万变不离其宗无非就是看数据手册、配置引脚、初始化外设、读取数据、处理数据、显示或输出结果这么几步。如果你想进一步优化本项目的代码结构和可靠性可以尝试把这些功能拆分成模块化文件比如ir_sensor.c、oled_display.c、debug_uart.c各自的头文件里声明好接口函数。这样整个工程的层级更清晰后面做扩展的时候也不至于改一处崩一片。我自己在实际项目中的体会是越是小的项目越值得养成模块化组织的习惯这个收益在项目变大之后会体现得非常明显。