当前位置: 首页 > news >正文

STM32HAL库 -- 9.IIC通信 软件IIC与硬件IIC驱动0.96寸OLED屏幕

目录

1.简介

2.IIC通信协议

2.1IIC简介

2.2IIC总线结构

2.3IIC的性能参数

2.4IIC基本时序单元

2.4.1起始条件

2.4.2停止条件

2.4.3发送或接收1字节数据

2.4.4应答信号

2.5IIC时序

2.5.1地址帧

2.5.2重复起始条件

2.5.3IIC通信时序示例 -- 指定地址写数据

2.5.4IIC通信时序示例 -- 当前地址读数据

 2.5.5IIC通信时序示例 -- 指定地址读

​编辑

3.软件IIC和硬件IIC

3.1软件IIC

3.2硬件IIC

4.软件IIC代码

4.1硬件连接

4.2程序编写

4.2.1宏定义

4.2.2GPIO的初始化

4.2.3起始条件

4.2.4停止条件

4.2.5检测应答信号

4.2.6发送应答信号和非应答信号

4.3测试代码

5.硬件IIC代码


1.简介

        这个文章会介绍IIC通信,然后编写IIC的代码。

        作者使用的开发板是正点原子的精英版,写文章用于记录学习和经验分享。

2.IIC通信协议

2.1IIC简介

        IIC(Inter-Integrated Circuit)通信协议,是一种由Philips公司(现为NXP Semiconductors)在1982年推出的多主机的、双向的、同步的串行通信协议。它主要用于集成电路之间的短距离通信,广泛应用于嵌入式系统和微控制器之间的通信。

2.2IIC总线结构

IIC 总线由两条信号线组成:

信号线含义功能
SDASerial Data Line数据传输线,用于主从设备之间发送和接收数据
SCLSerial Clock Line时钟线,由主设备生成,控制数据传输的时序

        开漏输出结构:IIC 的 SDA 和 SCL 引脚通常为开漏输出(Open-drain 或 Open-collector) ,这意味着它们只能将信号拉低(0V),不能主动驱动高电平(VCC)。

        上拉电阻:为了使信号恢复到高电平,必须在总线上添加外部上拉电阻(Pull-up Resistor) 连接到 VCC。典型值:1kΩ ~ 10kΩ(取决于总线速度和负载),高速模式下需要更小阻值以加快上升沿时间。

        总线拓扑结构:单主多从结构,即一个主设备控制多个从设备。多主多从结构,即支持多个主设备竞争总线使用权,通过仲裁机制避免冲突。

2.3IIC的性能参数

以下是标准 I²C 协议的主要性能参数:

参数描述
通信类型同步串行通信
数据线数量2 条:SDA(数据线)、SCL(时钟线)
通信方向半双工
通信模式主从模式(支持多主多从)
数据单位一个字节 = 8 位数据位(MSB先传)每个字节后必须有一个 ACK/NACK 应答位
最大传输速率标准模式下为 100 kbps,快速模式下为 400 kbps,高速模式下可达 3.4 Mbps
电压电平通常为 5V 或 3.3V,但可支持更低的电压(如 1.8V)
总线拓扑结构开漏输出 + 上拉电阻
地址位宽支持 7 位或 10 位寻址方式
最大从设备数量总线上可挂接的设备数量受总线的最大电容400pF限制。一般不超过8个。

2.4IIC基本时序单元

2.4.1起始条件

        起始条件(START Condition)。当 SCL 为高电平时,SDA 由高变低。通知所有从设备一次通信即将开始。

        特点是只有主设备可以发出起始信号。所有从设备进入监听状态。

2.4.2停止条件

        停止条件(STOP Condition)。当SCL 为高电平时,SDA 由低变高。表示本次通信结束。

2.4.3发送或接收1字节数据

        SCL低电平期间,发送端(主机或从机)将数据位依次放到SDA线上(高位先行),然后主机(或具有时钟控制权的一方)主动将 SCL 置为高电平,接收方(从机或主机)将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送或接收一个字节。

2.4.4应答信号

        每次 IIC 总线上传输一个字节(8位)后,接收方必须返回一个应答信号(ACK/NACK) ,作为对发送方的回应。发送方在此期间释放 SDA,允许接收方控制该信号线,并且发送方采样其状态以判断是 ACK 还是 NACK。

        应答信号占据第9个时钟周期(SCL周期),紧跟在每个字节之后。

类型含义SDA 状态
ACK(Acknowledgment)接收成功SDA = 低电平
NACK(Not Acknowledgment)接收失败或未准备好SDA = 高电平

        STOP信号之前的应答信号:

操作类型是否应在 STOP 前发送 NACK?说明
读操作✅ 是最后一个字节应答为 NACK
写操作❌ 否最后一个字节应答为 ACK 即可

2.5IIC时序

        一个完整的 IIC 时序 包含多个关键信号阶段:起始条件、地址帧传输、应答/非应答(ACK/NACK)、数据帧传输、重复起始条件(可选)、停止条件等。这些阶段构成了主设备与从设备之间的一次完整通信过程。

        起始条件、应答/非应答、数据帧传输和停止条件在之前一节已经讲过。

2.5.1地址帧

        地址帧是一个 8位的数据字节,用于标识将要通信的从设备地址和数据传输方向(读/写)。

        地址帧结构(7位地址模式下):

位号76543210
内容A6A5A4A3A2A1A0R/W#

        A6~A0(共7位) :表示从设备的地址。
        R/W#(第0位) :表示数据传输方向。0:写操作(主设备向从设备发送数据);1:读操作(主设备从从设备读取数据)。

        当主机发出起始条件之后紧跟着发送地址帧,此时所有从设备监听总线,比对收到的地址是否与自身匹配。如果匹配,则该从设备开始响应后续通信(如返回 ACK),并准备接收或发送数据。如果不匹配,从设备将继续监听下一次 START 条件。

2.5.2重复起始条件

        重复起始条件是指在一次 IIC 通信过程中,主设备在没有发出 STOP 条件的前提下,再次发出一个 START 条件,从而开启一个新的地址帧传输周期。这是 I²C 协议正式支持的标准操作之一。

作用描述
切换读写方向在连续读写操作中,用于从“写”切换为“读”
访问多个从设备在不释放总线的前提下,访问不同的从设备
提高通信效率避免多次发送 START 和 STOP,减少通信开销

        使用场景1:主设备先写地址和寄存器,然后切换为读数据

        [START] → [地址+写] → [ACK] → [寄存器地址] → [ACK] → [Repeated START] → [地址+读] → [ACK] → [读取数据字节] → [NACK] → [STOP]

        使用场景2:主设备访问多个从设备而不释放总线

        [START] → [地址A+写] → [ACK] → [数据1] → [ACK] → [Repeated START] → [地址B+写] → [ACK] → [数据2] → [ACK] → [STOP]

2.5.3IIC通信时序示例 -- 指定地址写数据

        [START] → [0xD0(从设备地址)+0(写)] → [ACK] → [0x19(寄存器地址)] → [ACK] → [0xAA(数据)] → [ACK] → [STOP]

2.5.4IIC通信时序示例 -- 当前地址读数据

        “当前地址读数据”是指主设备在不发送新的地址帧(如寄存器地址)的情况下,直接从从设备的当前地址指针位置开始读取数据。        

        在许多 IIC 从设备(如 EEPROM、ADC、传感器等)中,内部都有一个 地址指针寄存器(Address Pointer Register) ,用于指示下一次读或写操作的目标地址。 例如:主机写入地址 0x05 并写入一个字节后,下次写入将发生在地址 0x06。

        某些设备(如某些传感器)每次通信都需要重新发送地址。即使进行了写操作,地址指针也不会改变。OLED 显示屏 SSD1306(通常需要每次都写显存地址)。

        当主设备执行以下操作时,地址指针会发生变化:

操作类型地址指针行为
写地址帧 + 数据帧设置地址指针为指定值
写数据(带地址)设置地址指针,并写入数据后指针自动递增
读数据从当前地址读取数据后,地址指针通常自动递增

        [START] → [0xD0(从设备地址)+1(读)] → [ACK]  → [0x0F(数据)] → [NACK] → [STOP]

 2.5.5IIC通信时序示例 -- 指定地址读

       [START] → [0xD0(从设备地址)+0(写)] → [ACK] → [0x19(寄存器地址)] → [ACK] → [Repeated START] → [0xD0(从设备地址)+1(读)]  → [ACK]  → [0xAA(数据)] → [NACK] → [STOP]

3.软件IIC和硬件IIC

3.1软件IIC

        在嵌入式系统中,软件 IIC(Software IIC) 是一种通过 通用输入输出引脚(GPIO)模拟 IIC 总线协议 的方式。通过编写函数控制 SDA 和 SCL 的电平变化,并严格遵循 IIC 协议的时序规范,即可实现数据的发送和接收。

信号线GPIO 功能
SDA(Serial Data Line)数据线,双向传输
SCL(Serial Clock Line)时钟线,主设备控制

        软件IIC的优缺点:

优点描述
灵活可使用任意 GPIO 引脚作为 SDA 和 SCL
无需硬件支持即使没有硬件 I²C 模块也可通信
便于调试可插入打印语句观察每个步骤
兼容性强支持各种平台(如 STM32、ESP32、Arduino、51、AVR 等)
缺点描述
速度慢依赖 CPU 轮询,难以达到高速模式(>400kbps)
占用 CPU 资源实时性要求高时影响性能
代码复杂度高需处理 START/STOP、ACK/NACK、地址帧、数据帧等
抗干扰能力差容易受中断或延时影响导致通信失败

3.2硬件IIC

        硬件 IIC(Inter-Integrated Circuit) 是指由微控制器(MCU)或系统芯片(SoC)内部集成的专用外设模块,用于实现 高速、稳定、自动化的 IIC 总线通信,能够自动处理 I²C 协议的起始条件、地址帧、数据传输、ACK/NACK 应答、停止条件等操作。

       

4.软件IIC代码

        使用软件IIC的方式,操作oled屏幕。主要验证软件IIC的功能,对oled屏幕的驱动,不进行说明。

4.1硬件连接

        选用两个GPIO接口当作SCL、SDA。另外两根线分别接VCC、GND即可。

        作者选择的SCL 接在PD13、SDA接在PD15。

4.2程序编写

4.2.1宏定义


/*********宏定义********/
#define		OLED_SLAVE_ADDRESS					((unsigned char)(0x78))#define		OLED_IIC_SCL_CLK_ENABLE()			__HAL_RCC_GPIOD_CLK_ENABLE()
#define		OLED_IIC_SCL_PORT					GPIOD
#define		OLED_IIC_SCL_PIN					GPIO_PIN_13#define		OLED_IIC_SDA_CLK_ENABLE()			__HAL_RCC_GPIOD_CLK_ENABLE()
#define		OLED_IIC_SDA_PORT					GPIOD
#define		OLED_IIC_SDA_PIN					GPIO_PIN_15#define		OLED_WRITE_SCL(x)		do{HAL_GPIO_WritePin(OLED_IIC_SCL_PORT, OLED_IIC_SCL_PIN, (GPIO_PinState)(x));}while(0)
#define		OLED_WRITE_SDA(x)		do{HAL_GPIO_WritePin(OLED_IIC_SDA_PORT, OLED_IIC_SDA_PIN, (GPIO_PinState)(x));}while(0)

        定义了这个设备的从机地址,连接的端口和引脚,还有两个宏函数。

        OLED_WRITE_SCL(x)。当x为1时,即把SCL拉高,写0就拉低。SDA也同理。这样编写方便后续代码的使用。

4.2.2GPIO的初始化

        首先打开时钟,然后配置两个GPIO引脚,注意,要配置成上拉电阻模式和开漏输出模式。速度,作者使用的低速,可以正常使用。写入初始值,都是高电平就行。

/*** @brief       OLED引脚初始化函数* @param       无* @retval      无*/
void oled_gpio_init(void)
{/*	1.打开时钟	*/OLED_IIC_SCL_CLK_ENABLE();OLED_IIC_SDA_CLK_ENABLE();/*	2.配置GPIO	*/GPIO_InitTypeDef gpio_initure = {0};gpio_initure.Pin = OLED_IIC_SCL_PIN;			//SCL线gpio_initure.Mode = GPIO_MODE_OUTPUT_OD;		//开漏输出模式gpio_initure.Speed = GPIO_SPEED_FREQ_LOW;		//低速gpio_initure.Pull = GPIO_PULLUP;				//上拉电阻HAL_GPIO_Init(OLED_IIC_SCL_PORT, &gpio_initure);gpio_initure.Pin = OLED_IIC_SDA_PIN;			//SDA线HAL_GPIO_Init(OLED_IIC_SDA_PORT, &gpio_initure);/*	3.写入初始值	*/HAL_GPIO_WritePin(OLED_IIC_SCL_PORT, OLED_IIC_SCL_PIN, GPIO_PIN_SET);HAL_GPIO_WritePin(OLED_IIC_SDA_PORT, OLED_IIC_SDA_PIN, GPIO_PIN_SET);
}

4.2.3起始条件

        对应之前介绍的起始条件的时序图,编写代码就好。在SCL为高电平的时候,SDA从高电平置为低电平。这里也不用延时,因为每条代码在执行时也是要花费时间的,所以是有电平变化的过程的。无需担心。

/*** @brief       OLED的IIC开始信号* @param       无* @retval      无*/
void oled_iic_start(void)
{OLED_WRITE_SCL(1);   // 1. 拉高 SCL 时钟线OLED_WRITE_SDA(1);   // 2. 拉高 SDA 数据线OLED_WRITE_SDA(0);   // 3. 拉低 SDA 数据线(产生下降沿)OLED_WRITE_SCL(0);   // 4. 拉低 SCL 时钟线,释放总线以便后续操作
}

4.2.4停止条件

        拉高SCL,然后SDA从低电平置为高电平。

/*** @brief       OLED的IIC停止信号* @param       无* @retval      无*/
void oled_iic_stop(void)
{OLED_WRITE_SCL(1);   // 1. 拉高 SCL 时钟线OLED_WRITE_SDA(0);   // 2. 拉低 SDA 数据线OLED_WRITE_SDA(1);   // 3. 拉高 SDA 数据线(产生上升沿)
}

4.2.5检测应答信号

        当发送方发送了1字节数据之后,接受方要做出应答,发送方检测应答信号,就知道通信是否正常。

/*** @brief       检测从设备是否发送 ACK 应答* @param       无* @retval      0 表示收到 ACK,1 表示收到 NACK*/
unsigned char oled_iic_check_ack(void)
{unsigned char ack;OLED_WRITE_SDA(1);   // 释放 SDA,使其可以被从设备拉低OLED_WRITE_SCL(1);   // 拉高 SCL,进入第 9 个时钟周期ack = OLED_READ_SDA();  // 读取 SDA 状态OLED_WRITE_SCL(0);      // 拉低 SCL,完成 ACK 检测return ack;  // 返回 0 表示 ACK,1 表示 NACK
}

4.2.6发送应答信号和非应答信号

        当主机接收到1字节数据时要发送一个应答信号,告知从机,我收到了数据。当主设备要停止读取时,就发一个非应答信号,然后发送STOP结束这一次通信。

/*** @brief       主设备发送 ACK 应答(继续读取下一个字节)* @param       无* @retval      无*/
void oled_iic_send_ack(void)
{OLED_WRITE_SDA(0);   // 拉低 SDA,表示 ACKOLED_WRITE_SCL(1);   // 拉高 SCLOLED_WRITE_SCL(0);   // 拉低 SCL,结束 ACKOLED_WRITE_SDA(1);   // 释放 SDA(为下一次通信做准备)
}/*** @brief       主设备发送 NACK 应答(停止读取)* @param       无* @retval      无*/
void oled_iic_send_nack(void)
{OLED_WRITE_SDA(1);   // 拉高 SDA,表示 NACKOLED_WRITE_SCL(1);   // 拉高 SCLOLED_WRITE_SCL(0);   // 拉低 SCL,结束 NACKOLED_WRITE_SDA(1);   // 释放 SDA
}

4.3测试代码

        上面的程序代码不完整,但是经过作者的测试,可以成功驱动oled屏幕。我会附上完整的代码供学习参考。

5.硬件IIC代码

正在制作中,敬请期待.......

http://www.lryc.cn/news/574002.html

相关文章:

  • 【Linux指南】文件管理高级操作(复制、移动、查找)
  • GO 语言学习 之 代码风格
  • 时序数据库IoTDB数据导入与查询功能详解
  • 「ECG信号处理——(18)基于时空特征的心率变异性分析」2025年6月23日
  • IDEA中如何为 Spring Boot 项目添加 VM 参数?
  • 微服务架构下的分布式事务管理
  • CSS 中aspect - ratio属性的用途及应用
  • 【面板数据】上市公司投资者保护指数(2010-2023年)
  • 兆瓦闪充技术革命:解码新能源汽车补能赛道的技术跃迁与从业机会图谱
  • LNMP 一键部署脚本 shell脚本
  • Postgresql中不同数据类型的长度限制
  • 基于springboot+uniapp的“川味游”app的设计与实现7000字论文
  • HarmonyOS NEXT应用元服务布局优化ArkUI框架执行流程
  • Java性能优化权威指南-操作系统性能监控
  • RSYNC+IONTIFY数据实时同步
  • ISCSI存储
  • 从java角度理解io多路复用和redis为什么使用io多路复用
  • 品牌控价需要数据支撑与高效治理双驱动
  • 前端手写题(一)
  • MySQL基础函数篇
  • 黑马python(十三)
  • python高校教务管理系统
  • Rust智能指针演进:从堆分配到零复制的内存管理艺术
  • 算法与数据结构:动态规划DP
  • Windows11系统自定义关闭更新
  • 二刷苍穹外卖 day03
  • Unity2D 街机风太空射击游戏 学习记录 #12QFramework引入
  • 链接脚本基础语法
  • 国产12537穿甲弹侵彻仿真(显式动力学)
  • 抽象工厂设计模式