从0开始点亮OLED屏幕(一)IIC时序篇

发布时间:2026/6/29 0:42:31

从0开始点亮OLED屏幕(一)IIC时序篇 1. IIC通信协议基础第一次接触OLED屏幕开发时我被IIC时序折腾得够呛。记得当时用STM32F103调试SSD1306屏幕屏幕死活不亮最后发现是起始信号时序不对。这种两线制的通信协议看似简单实际藏着不少魔鬼细节。IICInter-Integrated Circuit是飞利浦在1982年推出的串行通信协议用两根线就能连接多个设备。SDA负责数据传输SCL提供时钟信号这种设计特别适合传感器、EEPROM这类低速设备。你可能不知道现在市面上80%的0.96寸OLED模块都采用IIC接口因为它比SPI节省引脚资源。空闲状态下两根线都保持高电平这让我想起第一次用逻辑分析仪抓波形时的困惑——为什么示波器上总是两条直线原来需要主设备主动发起通信。协议规定标准模式速率100kHz快速模式400kHz高速模式3.4MHz但实际使用中我发现很多国产OLED在超过200kHz时就会丢数据。2. 硬件连接与初始化我的开发板上SCL接PA11SDA接PA12这个组合看似随意其实有讲究。STM32的GPIO口有复用功能但做IIC模拟时序时最好避开I2C1/I2C2默认引脚避免冲突。有次我把SDA接到PB7I2C1的SDA结果死活调不通后来才发现是硬件I2C模块影响了电平。初始化GPIO时要特别注意#define GPIO_SCL_PORT GPIOA #define GPIO_SCL_PIN GPIO_Pin_11 #define GPIO_SDA_PORT GPIOA #define GPIO_SDA_PIN GPIO_Pin_12 void GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCL推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_SCL_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIO_SCL_PORT, GPIO_InitStructure); // SDA初始化为开漏输出 GPIO_InitStructure.GPIO_Pin GPIO_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; GPIO_Init(GPIO_SDA_PORT, GPIO_InitStructure); // 初始置高 GPIO_SetBits(GPIO_SCL_PORT, GPIO_SCL_PIN); GPIO_SetBits(GPIO_SDA_PORT, GPIO_SDA_PIN); }这里有个坑SDA必须配置为开漏输出OD因为IIC协议允许多主设备抢占总线。如果配置成推挽输出PP当两个设备同时输出不同电平时会短路。我曾因此烧过一个OLED模块现在想起来还心疼。3. 关键时序实现3.1 起始与停止信号起始信号就像打电话时的拨号动作SCL高电平期间SDA从高跳低。代码实现要注意时序间隔void I2C_Start(void) { SDA_OUT(); OLED_SDA(1); OLED_SCL(1); delay_us(5); // 保持时间4.7us OLED_SDA(0); delay_us(5); OLED_SCL(0); // 准备发送数据 }停止信号则是挂电话SCL高电平时SDA从低跳高。有次我忘记加延时导致从设备没识别到停止信号void I2C_Stop(void) { SDA_OUT(); OLED_SDA(0); delay_us(5); OLED_SCL(1); delay_us(5); OLED_SDA(1); // 停止条件建立时间4us delay_us(5); }3.2 数据发送与应答发送字节时要遵循数据在SCL低电平变化高电平稳定的原则。我最开始犯的错误是没处理高位在前void Send_Byte(uint8_t dat) { SDA_OUT(); for(uint8_t i0; i8; i) { OLED_SCL(0); OLED_SDA((dat 0x80) ? 1 : 0); // 先发最高位 delay_us(2); OLED_SCL(1); delay_us(2); dat 1; OLED_SCL(0); } Wait_Ack(); }应答检测是个容易忽略的环节。有次调试发现屏幕偶尔花屏最后发现是没正确处理NACKuint8_t Wait_Ack(void) { uint8_t ack 0; SDA_IN(); OLED_SCL(1); delay_us(2); if(READ_SDA() 0) ack 1; OLED_SCL(0); SDA_OUT(); return ack; }4. 实战调试技巧4.1 时序优化不同OLED模块对时序要求差异很大。我用过的某款国产屏要求SCL高电平至少600ns而SSD1306手册只要求400ns。建议用示波器测量实际波形我通常这样调整延时#define IIC_DELAY() \ do { \ volatile int i3; \ while(i--); \ } while(0)这个简易延时在72MHz主频下约产生1us延时。如果通信不稳定可以尝试增加SCL高电平时间检查上拉电阻通常4.7K-10K缩短总线长度最好30cm4.2 错误排查当屏幕无反应时建议按以下步骤排查用万用表测量VCC/GND电压3.3V或5V检查上拉电阻是否接好用逻辑分析仪抓取起始信号确认设备地址0x3C或0x3D有次我遇到屏幕闪烁问题最后发现是电源滤波电容不足。建议在VCC和GND之间加个100nF电容这是我踩过坑后的经验。5. 进阶应用5.1 多设备通信IIC支持多从设备架构通过地址区分。比如同时连接OLED和BMP280气压传感器#define OLED_ADDRESS 0x3C #define BMP280_ADDRESS 0x76 void Write_Device(uint8_t addr, uint8_t reg, uint8_t dat) { I2C_Start(); Send_Byte(addr 1); // 写模式 Send_Byte(reg); Send_Byte(dat); I2C_Stop(); }注意地址左移一位最低位表示读写模式0写/1读。我曾因忘记移位导致设备无响应。5.2 速率优化标准模式下每个字节传输约100us刷新整屏128x64需要约8ms。通过优化延时函数可以提升刷新率// 快速模式下的延时 #define FAST_DELAY() \ __asm__ volatile(nop;nop;nop;nop)但要注意过快的速率可能导致屏幕显示异常。建议从标准模式开始逐步提高速率测试稳定性。

相关新闻