1. 从光敏电阻到OLED屏幕:一个完整的STM32 ADC采集链路拆解
光敏传感器这个东西,玩过单片机的基本都接触过。它便宜、好买、原理简单,但真要把它跑通并且把数据实时显示出来,中间涉及的知识点其实不少——ADC采样、通道配置、DMA搬运、I2C驱动OLED、数据格式化,每一环都有坑。我这次拿STM32F103C8T6这块经典板子做了一套完整的采集显示方案,从硬件接线到代码落地全部走了一遍,中间踩了不少坑,也总结了一些文档里不会写的经验。
这篇文章适合谁看?如果你已经点过LED、跑过串口,想找一个综合性强但又不太复杂的项目来练手,那这个ADC采集光敏传感器并实时显示的项目非常合适。它不像单纯的ADC实验那么枯燥,也不像RTOS项目那么复杂,刚好卡在“有点挑战但能搞定”的区间。整个链路是:光敏电阻分压产生模拟电压,STM32的ADC模块采集这个电压,MCU把原始值换算成光照强度的近似值,最后通过I2C协议推送到OLED屏幕上实时刷新。
我实测下来,从零开始到屏幕稳定显示,大概需要两三个小时,前提是你对GPIO和基本外设配置有概念。如果你完全没碰过STM32,建议先把GPIO输出和串口通信过一遍再来看这个。下面我会把整个项目的设计思路、硬件选型、代码实现、调试过程全部展开讲,尽量做到你照着做就能复现。
2. 项目整体设计与方案选型
2.1 为什么选光敏电阻而不是数字光照传感器
市面上测光照的方案大致分两类:一类是光敏电阻、光电二极管这类模拟器件,输出随光照变化的电压或电流;另一类是BH1750、TSL2561这类数字传感器,直接通过I2C输出光照强度的数字值。我这次选光敏电阻,原因有三个。
第一,成本极低。一个光敏电阻几毛钱,配合一个普通电阻就能搭出分压电路,而数字光照传感器模块通常要几块到十几块。对于学习ADC采集来说,光敏电阻的模拟输出恰好是ADC最典型的应用场景。
第二,原理透明。光敏电阻的阻值随光照变化,亮的时候阻值降到几K欧,暗的时候能到几百K欧甚至兆欧级别。这个变化过程是连续的、可观测的,你能用万用表直接量到电压变化,对理解ADC采样的本质非常有帮助。数字传感器把一切都封装好了,你只能看到最终数值,中间过程是黑盒。
第三,可调空间大。分压电阻的选型、ADC的采样时间、参考电压的稳定性,这些都会影响最终精度,你可以通过调整这些参数来观察效果变化,这对建立“模拟电路+数字采集”的直觉很有价值。
当然,光敏电阻也有明显缺点:线性度差、一致性差、受温度影响大。所以它测出来的值只能做相对比较,比如“现在比刚才亮”或者“遮住之后变暗了”,不能当作精确的光照度计来用。这一点在后面数据换算的时候我会详细说。
2.2 ADC通道与引脚分配的逻辑
STM32F103C8T6有2个ADC,每个ADC有多个通道,对应不同的GPIO引脚。我选的是ADC1的通道0,对应PA0引脚。为什么选PA0?因为它在板子上引出方便,而且PA0本身没有复用成其他必须的功能,冲突少。
这里有个细节要注意:STM32的ADC通道和引脚是固定映射的,不是你想用哪个引脚就能随便分配到哪个通道。比如ADC1_IN0固定就是PA0,ADC1_IN1固定是PA1,以此类推。所以在画原理图或者接线之前,一定要先查数据手册的引脚定义表,确认你用的引脚对应哪个ADC通道。
另外,PA0同时还兼着WKUP功能(唤醒待机模式),如果你后续要用待机唤醒,这个引脚就要留意。不过对于这个项目来说,只用ADC功能,不冲突。
2.3 I2C显示方案的选择理由
显示部分我用了0.96寸的OLED屏,驱动芯片是SSD1306,分辨率128x64,接口是I2C。为什么选它?对比几种常见方案:
| 显示方案 | 接口 | 优点 | 缺点 |
|---|---|---|---|
| OLED SSD1306 | I2C | 接线少、对比度高、功耗低 | 尺寸小、成本中等 |
| LCD1602 | 并口/I2C | 便宜、字符大 | 显示内容少、需要背光 |
| TFT彩屏 | SPI | 显示丰富 | 接线多、代码复杂 |
| 串口屏 | UART | 开发简单 | 成本高、体积大 |
OLED的I2C接口只需要两根线(SCL和SDA),加上电源和地一共四根线,接线非常清爽。而且SSD1306的驱动库很成熟,网上能找到大量现成的驱动代码,移植成本低。128x64的分辨率虽然不大,但显示几个数值和单位绰绰有余。
I2C的地址需要注意:SSD1306的默认地址通常是0x78(8位地址)或0x3C(7位地址),具体取决于模块背后的电阻配置。我手上这块模块的地址是0x78,如果你的屏幕不亮,第一件事就是用I2C扫描程序确认地址。
2.4 软件架构的分层设计
代码结构上我没有把所有东西塞进main函数,而是做了简单的分层:
- 硬件驱动层:ADC初始化、I2C初始化、OLED初始化,各自独立成模块
- 数据处理层:ADC原始值读取、滤波、电压换算、光照强度近似换算
- 应用层:主循环里定时采集、格式化字符串、刷新显示
这样分的好处是,如果后面要换传感器或者换屏幕,只需要改对应的驱动层,数据处理和应用逻辑基本不用动。对于学习项目来说,这种结构也不会太复杂,不至于过度设计。
3. 硬件电路与关键参数计算
3.1 光敏电阻分压电路的设计与计算
光敏电阻本身是阻值变化的器件,要把它变成电压信号,最常用的方法就是串联一个固定电阻组成分压电路。电路很简单:VCC接光敏电阻一端,光敏电阻另一端接固定电阻一端,固定电阻另一端接地,中间节点接到ADC输入引脚。
设光敏电阻阻值为R_photo,固定电阻阻值为R_fixed,供电电压为VCC,那么中间节点的电压为:
V_out = VCC × R_fixed / (R_photo + R_fixed)
关键问题是:R_fixed选多大?
光敏电阻的阻值范围通常在1K欧到1M欧之间变化。亮的时候可能只有1K到10K,暗的时候能到500K以上。为了让输出电压在亮暗两种状态下都有明显变化,R_fixed应该选在光敏电阻变化范围的中间值附近。我实测手上这个光敏电阻,室内正常光照下阻值约10K欧,遮住后约200K欧,强光下约2K欧。
如果R_fixed选10K欧:
- 强光时:V_out = 3.3 × 10K / (2K + 10K) = 2.75V
- 室内光时:V_out = 3.3 × 10K / (10K + 10K) = 1.65V
- 遮住时:V_out = 3.3 × 10K / (200K + 10K) = 0.157V
这个变化范围从0.157V到2.75V,跨度超过2.5V,ADC能分辨出明显的差异。如果R_fixed选1K欧,遮住时输出电压只有0.016V,接近地电位,分辨率会变差。如果选100K欧,强光时输出电压接近VCC,容易饱和。所以10K欧是一个比较均衡的选择。
注意:光敏电阻的响应速度较慢,通常几十毫秒到几百毫秒,所以ADC采样间隔不用太快,100ms到500ms读一次完全够用。读太快反而会因为响应跟不上而看到跳变。
3.2 STM32 ADC的采样时间与精度权衡
STM32F103的ADC是12位逐次逼近型,理论分辨率是3.3V/4096≈0.8mV。但实际精度受采样时间、参考电压稳定性、输入阻抗等因素影响。
采样时间决定了ADC内部采样保持电容充电的时间。如果采样时间太短,电容没充到输入电压就被切断了,读数会偏低。STM32的ADC采样时间可以配置为1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5个ADC时钟周期。
光敏电阻分压电路的输出阻抗等于R_photo和R_fixed的并联值。在最坏情况下(比如遮住时R_photo=200K,R_fixed=10K),并联阻抗约为9.5K欧。对于这种级别的源阻抗,采样时间建议选55.5个周期以上。我实际用的是71.5个周期,ADC时钟配置为12MHz(APB2的72MHz除以6),采样时间约6微秒,足够让采样电容充分充电。
这里有个经验公式可以参考:采样时间 > (R_source + R_adc) × C_adc × ln(2^12)。其中R_adc约1K欧,C_adc约8pF。代入R_source=10K欧,算出来约0.7微秒,看起来很短,但这是理论最小值,实际要留足余量,因为还要考虑寄生电容和噪声。
3.3 I2C上拉电阻的必要性与取值
I2C总线是开漏输出,SCL和SDA线必须接上拉电阻才能输出高电平。很多OLED模块背面已经自带了4.7K欧的上拉电阻,如果你的模块有,就不需要额外加。如果没有,必须在SCL和SDA上各接一个4.7K欧到10K欧的电阻到VCC。
上拉电阻的取值影响通信速度和信号质量。阻值太小,上升沿快但功耗大;阻值太大,上升沿慢,高速通信时可能来不及拉高。对于STM32的I2C标准模式(100KHz)或快速模式(400KHz),4.7K欧是通用选择。我实测用10K欧也能稳定通信,但如果你把I2C速度提到400KHz以上,建议还是用4.7K欧。
实操心得:如果你发现OLED偶尔不亮或者显示乱码,先检查上拉电阻。我遇到过一块模块没焊上拉电阻,结果I2C通信时好时坏,折腾了半天才发现是硬件问题。
4. 代码实现与核心环节拆解
4.1 ADC初始化的完整流程与寄存器配置
STM32的ADC初始化涉及多个步骤,用标准库的话大致流程如下。我先把关键代码列出来,然后逐段解释。
void ADC_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); ADC_DeInit(ADC1); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = DISABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_71Cycles5); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }几个关键点解释一下。第一,PA0必须配置为模拟输入模式(GPIO_Mode_AIN),不能配成浮空输入或者上拉输入,否则会引入额外的漏电流影响精度。第二,ADC时钟不能超过14MHz,所以72MHz的PCLK2要除以6得到12MHz。第三,ADC校准不能省,每次上电后都要执行一次复位校准和启动校准,否则读数偏差可能达到几十个LSB。
第四,连续转换模式我设的是DISABLE,因为我不需要ADC一直不停地转换,而是在主循环里按需触发单次转换。这样可以节省功耗,也避免数据更新太快导致显示闪烁。如果你要做高速采集,可以开启连续转换配合DMA。
4.2 单次转换与多次采样取平均的滤波策略
ADC读一次的结果往往有跳动,这是正常现象。电源纹波、热噪声、光敏电阻本身的噪声都会导致读数波动。我试过直接读一次就显示,数值末位一直在跳,看起来很不稳定。后来改成连续读8次取平均,显示就稳多了。
u16 Get_ADC_Average(u8 times) { u32 sum = 0; u8 i; for(i = 0; i < times; i++) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); sum += ADC_GetConversionValue(ADC1); } return (u16)(sum / times); }取多少次平均?我试过4次、8次、16次。4次效果一般,16次明显更稳但耗时增加。8次是一个比较好的平衡点,每次转换加采样大概7微秒,8次不到60微秒,对主循环影响可以忽略。
注意:取平均之前要确保每次转换之间留一点间隔,不要连续触发太密。虽然STM32的ADC支持连续触发,但采样保持电容需要时间恢复。我在两次转换之间加了一个微秒级的延时,实际测试对稳定性有帮助。
4.3 从ADC原始值到电压值的换算过程
ADC读出来的是0到4095的整数,要换算成电压,公式是:
V = ADC_Value × V_ref / 4096
V_ref是参考电压,STM32F103的V_ref通常接3.3V。但实际V_ref可能不是精确的3.3V,比如USB供电时可能是3.28V或3.32V。如果你要精确测量,最好用万用表量一下V_ref引脚的实际电压,代入计算。
float adc_to_voltage(u16 adc_value) { return (float)adc_value * 3.3f / 4096.0f; }这里用浮点运算是为了方便,如果你对性能敏感,可以用定点运算:先乘3300再除以4096,得到毫伏值。不过对于这个项目来说,浮点完全够用,STM32F103没有硬件浮点单元,但软件浮点算一次也就几微秒。
4.4 电压值到光照强度的近似换算
前面说过,光敏电阻的阻值和光照强度不是线性关系,而是近似对数关系。所以从电压反推光照强度,严格来说需要标定曲线。但对于这个项目,我们不需要精确的光照度值,只需要一个能反映亮暗变化的相对值。
我用的方法是这样:先根据分压公式反推光敏电阻的阻值:
R_photo = R_fixed × (VCC - V_out) / V_out
然后根据光敏电阻的数据手册,阻值和光照度的关系大致是:
R_photo = A × Lux^(-B)
其中A和B是常数,取决于具体型号。对于常见的GL5528,B约0.7到0.9。我们可以用两个标定点来估算A和B,但这样太麻烦。实际项目中,我直接用一个简单的映射:把电压值映射到0到100的亮度百分比。
u8 voltage_to_brightness(float voltage) { if(voltage >= 3.0f) return 100; if(voltage <= 0.1f) return 0; return (u8)((voltage - 0.1f) / 2.9f * 100.0f); }这个映射当然不精确,但显示效果直观,能看到遮住传感器时数值下降,强光照射时数值上升。如果你需要更准确的值,建议用标准光源做两点标定,拟合出A和B。
4.5 OLED显示驱动的移植与中文字符处理
OLED驱动我用的是一份常见的SSD1306驱动库,包含初始化、写命令、写数据、清屏、显示字符和字符串等函数。移植的时候主要改两个地方:I2C的读写函数和延时函数。
I2C读写我用的是软件模拟I2C,因为STM32F103的硬件I2C有些已知的稳定性问题,软件模拟更可控。软件I2C的时序很简单:起始条件、发送字节、等待应答、停止条件。关键是延时要匹配,太快了从机跟不上,太慢了刷新率低。
void I2C_Start(void) { SDA_H; SCL_H; delay_us(2); SDA_L; delay_us(2); SCL_L; delay_us(2); }显示字符串的时候,如果只显示英文和数字,直接用8x16的ASCII字库就行。如果要显示中文,需要自己取模。我这次显示的内容是“Light:”加数值加“%”,全是ASCII字符,不需要中文字库,省了不少事。
实操心得:OLED刷新的时候不要每次都全屏清空再重写,那样会闪。我用的方法是只更新数值所在的区域,用背景色覆盖旧数值再写新数值。这样刷新率高的时候也看不到闪烁。
4.6 主循环的时序安排与显示刷新策略
主循环的结构很简单:每隔一定时间采集一次ADC,换算,格式化字符串,刷新OLED。关键是这个“一定时间”设多少。
我试过10ms刷新一次,屏幕上的数值跳得太快,根本看不清。改成100ms一次,看起来就比较舒服。500ms又觉得反应太慢,遮住传感器后要等半秒才变。最终定在200ms,兼顾了实时性和可读性。
while(1) { adc_value = Get_ADC_Average(8); voltage = adc_to_voltage(adc_value); brightness = voltage_to_brightness(voltage); sprintf(display_buf, "Light: %3d%%", brightness); OLED_ShowString(0, 2, (u8*)display_buf, 16); delay_ms(200); }这里用sprintf格式化字符串很方便,但要注意缓冲区大小。我定义了20字节的缓冲区,足够放下“Light: 100%”加结束符。如果你要显示更多内容,记得加大缓冲区,否则会栈溢出。
5. 调试过程与常见问题排查
5.1 ADC读数始终为0或4095的排查思路
这是最常见的问题。读数始终为0,说明ADC输入引脚一直是低电平;始终为4095,说明一直是高电平。排查步骤:
第一步,用万用表量PA0引脚的电压。如果电压正常变化但ADC读数不变,说明是软件配置问题。如果电压本身就不变,说明是硬件问题。
第二步,检查GPIO模式是否配成了模拟输入。我遇到过有人配成了浮空输入,结果读数一直跳。模拟输入模式下,GPIO的施密特触发器是关闭的,不会影响模拟信号。
第三步,检查ADC通道配置是否正确。ADC_RegularChannelConfig里的通道号要和引脚对应。PA0对应ADC_Channel_0,如果你写成了ADC_Channel_1,读的就是PA1的电压。
第四步,检查ADC是否使能、校准是否完成。如果校准没完成就开始转换,读数可能不准或者不更新。
5.2 数值跳动严重的滤波与硬件优化
数值跳动的原因通常有三个:电源噪声、光敏电阻本身噪声、ADC量化噪声。
电源噪声可以通过加去耦电容来改善。我在光敏电阻分压电路的VCC和地之间并了一个100nF的陶瓷电容,效果很明显。另外,ADC的参考电压引脚也要加去耦电容,通常是一个10uF钽电容加一个100nF陶瓷电容。
光敏电阻本身的噪声是固有的,只能通过软件滤波来抑制。除了取平均,还可以用中值滤波:连续读多次,去掉最大和最小值,剩下的取平均。我试过中值滤波,效果比单纯取平均更好,但代码稍微复杂一点。
ADC量化噪声是原理性的,12位ADC的最小分辨力就是0.8mV,低于这个的波动是正常的。如果你需要更高精度,可以考虑过采样:读16次求和,相当于把分辨率提高到14位。
5.3 OLED不亮的几种原因与快速定位
OLED不亮是最让人头疼的问题,因为可能的原因很多。我整理了一个排查顺序:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 完全无显示 | 电源没接好 | 量VCC和GND之间电压 |
| 完全无显示 | I2C地址错误 | 用I2C扫描程序确认地址 |
| 完全无显示 | 初始化序列不对 | 对照数据手册检查初始化命令 |
| 显示但内容乱 | 上拉电阻缺失 | 检查SCL和SDA上拉 |
| 显示但内容乱 | 时序太快 | 增加I2C延时 |
| 部分区域不显示 | 显存地址计算错误 | 检查页地址和列地址设置 |
我遇到过一次OLED不亮,查了半天发现是I2C地址搞错了。模块背面标的是0x78,但实际用7位地址是0x3C,驱动库里用的是8位地址,需要左移一位。这种细节很容易忽略。
5.4 I2C通信失败的时序问题与解决
软件I2C最常见的问题是时序不匹配。不同的OLED模块对时序的要求略有差异,有的能跑很快,有的必须慢一点。
我的一般做法是:先用一个比较大的延时(比如10微秒),确保通信成功,然后再逐步减小延时,找到稳定工作的最小延时。这样既能保证兼容性,又能在稳定后提高刷新率。
另外,I2C的起始条件和停止条件要规范。起始条件是SCL高时SDA从高变低,停止条件是SCL高时SDA从低变高。如果这两个条件不标准,从机可能无法识别。
注意:软件I2C的SDA和SCL引脚要配置为开漏输出,并且外部要有上拉电阻。如果配置成推挽输出,可能会烧毁从机的I2C引脚。
5.5 常见问题速查表
| 问题 | 排查方向 | 解决方法 |
|---|---|---|
| ADC读数不变 | 硬件电压、GPIO模式、通道配置 | 逐项检查,用万用表辅助 |
| ADC读数跳动大 | 电源噪声、滤波不足 | 加去耦电容、增加平均次数 |
| OLED不亮 | 电源、地址、初始化 | 按顺序排查,先确认电源 |
| OLED显示乱码 | 上拉电阻、时序 | 检查上拉,增加延时 |
| 数值换算不对 | 参考电压、公式 | 实测V_ref,检查公式 |
| 刷新闪烁 | 全屏清空 | 局部刷新,只更新数值区域 |
6. 项目扩展与进阶方向
6.1 用DMA实现无阻塞ADC采集
现在的方案是在主循环里主动触发ADC转换然后等待完成,这会阻塞CPU。如果后面要加其他任务,比如按键扫描或者串口通信,阻塞式采集就不太合适了。
用DMA可以把ADC转换结果自动搬运到内存数组,CPU不需要干预。配置好DMA通道后,ADC每次转换完成都会触发DMA请求,数据自动存到指定地址。主循环只需要读取数组里的值就行,完全不阻塞。
STM32F103的ADC1对应DMA1的通道1。配置步骤:使能DMA时钟,配置DMA参数(外设地址为ADC数据寄存器,内存地址为数组,传输方向为外设到内存,循环模式使能),使能DMA通道,最后把ADC配置为连续转换模式并使能DMA请求。
6.2 加入滑动平均滤波提升显示稳定性
取平均滤波有个缺点:它对所有数据一视同仁,如果突然有一个异常值,会拉偏平均值。滑动平均滤波可以改善这个问题:维护一个固定长度的队列,每次新数据进来就挤掉最旧的数据,然后对队列里的数据取平均。
#define FILTER_LEN 10 u16 filter_buf[FILTER_LEN]; u8 filter_index = 0; u16 sliding_average(u16 new_value) { u32 sum = 0; u8 i; filter_buf[filter_index] = new_value; filter_index = (filter_index + 1) % FILTER_LEN; for(i = 0; i < FILTER_LEN; i++) { sum += filter_buf[i]; } return (u16)(sum / FILTER_LEN); }滑动平均的响应速度比简单平均慢一点,但平滑效果更好。队列长度可以根据需要调整,10到20比较合适。
6.3 通过串口输出数据做上位机可视化
OLED屏幕只能显示数值,看不到变化趋势。如果想观察光照随时间的变化曲线,可以把数据通过串口发送到电脑,用串口示波器或者Python脚本画出来。
串口配置很简单:波特率115200,8位数据位,1位停止位,无校验。每采集一次就发送一行数据,格式可以是“时间戳,ADC值,电压值,亮度值”。上位机用Python的pyserial读取,matplotlib实时绘图。
这个扩展对于调试滤波算法特别有用,你能直观看到滤波前后的波形对比,判断滤波参数是否合适。
6.4 低功耗场景下的采集策略优化
如果这个项目要用电池供电,功耗就需要考虑。STM32F103在运行模式下功耗约几十毫安,ADC和OLED也会消耗电流。降低功耗的方法:
第一,降低主频。把系统时钟从72MHz降到8MHz,功耗能降不少,ADC采集对速度要求不高,8MHz完全够用。
第二,间歇工作。不需要一直采集,可以每隔几秒采集一次,采集完就进入睡眠模式,用定时器唤醒。
第三,关闭OLED。如果不需要一直显示,可以采集几次后刷新一次屏幕,其他时间关闭OLED电源。
第四,降低ADC采样频率。采样时间选长一点,转换间隔大一点,都能降低功耗。
这些优化对于学习项目来说不是必须的,但如果你要做产品化的东西,低功耗设计是绕不开的。
7. 个人实操体会与踩坑记录
这个项目我从开始到稳定运行,大概花了三个晚上。第一个晚上卡在ADC读数不动,后来发现是GPIO模式配错了。第二个晚上卡在OLED不亮,查了半天是I2C地址问题。第三个晚上在调滤波参数和刷新率,试了好几种组合才找到比较满意的效果。
最大的体会是:硬件调试一定要用万用表。软件层面能查的东西有限,很多时候问题出在硬件上,比如虚焊、接错线、电阻选错。我遇到过一次光敏电阻分压电压正常但ADC读数偏低,后来发现是PA0引脚上还接了一个LED,LED的漏电流影响了模拟输入。把LED去掉就正常了。
另一个体会是:不要迷信现成的驱动库。网上的OLED驱动库很多,但质量参差不齐。有的初始化序列不全,有的时序太快,直接拿来用可能跑不起来。最好对照数据手册把初始化序列过一遍,确认每条命令的作用。
最后分享一个小技巧:如果你觉得OLED刷新太慢,可以只刷新变化的区域。比如数值从50变到51,只需要重写“51”这两个字符的位置,不需要重写整个“Light: 51%”。这样刷新率能提高不少,而且看起来更流畅。