I2C模块简介

I2C模块

IO引脚的初始化

在这里插入图片描述
I2C的IO引脚配置为什么是复用输出开漏模式
通用:由CPU直接控制寄存器写0/1,高低电平的翻转
复用:不由CPU直接控制,将控制权交给片上外设。
此处,引脚交给片上硬件I2C外设控制,自动输出标准时序,不需要CPU手动翻转SCL/SDA、加延时,减轻CPU负担,速度更稳定

1.如果整个过程所有动作全靠 CPU
CPU 手动改写寄存器翻转 SCL、SDA 高低电平;
每次电平切换都要调用延时函数,CPU 原地等待,不能做别的事
发 8 个 bit、起始、停止、ACK 都要几十次 IO 操作 + 延时;
CPU 一旦被中断、跑别的任务,延时不准,时序乱掉,传感器读不出数据。
所以,如果使用CPU去控制,CPU 全程包办每一步时序,占用大量运算资源,时序不稳定

2.使用片上外设硬件I2C自动依次执行:
自动拉 SCL/SDA 产生起始信号;
自动按标准时序移位输出 8 位数据,自动控制 SCL 高低切换;
自动释放 SDA,采样读取从机的 ACK 应答;
全部字节发完后,自动生成停止信号;
发送结束后只给 CPU 一个完成中断 / 标志位通知。
整个过程 CPU 不用写任何翻转引脚代码、不用加任何延时,CPU 写完数据寄存器就能去运行其他程序,不用原地等待

I2C总线硬件标准规定:SDA、SCL全部为逻辑线与结构,所有设备引脚只能拉低总线,不能主动拉高
此处我们对比一下推挽输出与开漏输出:
推挽输出(PP):引脚既可以输出高电平,也可以输出低电平。
如果一个设备输出高电平,另一个设备输出低电平拉低总线,也就是一端强制 VCC、一端强制 GND → 电源对地直通短路,烧毁 IO
开漏输出(OD):内部MOS管只能把引脚拉到GND,无法主动输出高电平。
总线的高电平完全靠上拉电阻实现,多设备并联时,任意设备拉低SDA/SCL,总线就变低;全部释放也就是设备的SDA/SCL引脚与总线断开连接,则由上拉电阻拉至高电平,完美实现逻辑线与,不会发生短路
此外,还因为:
因为SDA线是双向的,既发数据,也读数据。开漏写 1 = 让出总线,可读外部信号。而推挽引脚置 1 是强制强拉高,根本读不了别人的信号。
多主机同时通信时,开漏的线与特性可以自动识别总线冲突,推挽会直接短路损坏芯片

//#1.初始化IO引脚
//默认IO引脚 PB6,PB7
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);

如果进行重映射:

//对I2C1进行重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
//初始化PB8,PB9
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);

连接电路

我们的OLED模块是通过I2C与单片机建立通信的。
OLED屏
在这里插入图片描述

I2C的速度模式

在这里插入图片描述
I2C的波特率定义与串口的一样,每个时钟周期传输一个比特位。波特率越大,传输速度越大
I2C速度模式
我们使用的这款单片机仅支持SmFm两种模式。

时钟信号的占空比

快速模式下的时钟占空比
SCL 为时钟信号线,通过周期性高低电平切换输出时钟信号(图中高低变换的电压),决定总线通信传输速率;SDA 为数据线,负责收发通信数据。
占空比定义:一个完整时钟周期内,高电平持续时间占总周期时长的比值。
该单片机 I2C 外设提供2种时钟高低电平时序配比(低电平时长:高电平时长)可选:2:1、16:9
配比 2:1:单时钟周期内,低电平时长占 2 份,高电平时长占 1 份;
配比 16:9:单时钟周期内,低电平时长占 16 份,高电平时长占 9 份。

I2C模块的初始化

I2C模块初始化的编程接口
在这里插入图片描述

//#2.初始化I2C1模块
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);//开启I2C1的时钟
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);//施加复位信号
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);//释放复位信号
	
I2C_InitTypeDef I2C_InitStruct={0};
2C_InitStruct.I2C_ClockSpeed=400000;
I2C_InitStruct.I2C_Mode=I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle=I2C_DutyCycle_2;
I2C_Init(I2C1,&I2C_InitStruct);

I2C_Cmd(I2C1,ENABLE);//闭合I2C1总开关,使能I2C1模块

施加复位和释放复位的原因:
外设上电可能残留异常状态先复位再释放,能把 I2C 恢复初始干净状态,避免通信出错

在这里插入图片描述
完整代码:

#include "stm32f10x.h"
void My_I2C_Init(void);

int main(void)
{
	My_I2C_Init();
	while(1)
	{
	}
}
void My_I2C_Init(void)
{
//#1.初始化IO引脚

//默认IO引脚 PB6,PB7
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//	GPIO_InitTypeDef GPIO_InitStruct={0};
//	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
//	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_OD;
//	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;
//	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
//对I2C1进行重映射
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_I2C1,ENABLE);
	
//初始化PB8,PB9
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct={0};
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
	
//#2.初始化I2C1模块
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);//开启I2C1的时钟
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);//施加复位信号
RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);//释放复位信号
	
I2C_InitTypeDef I2C_InitStruct={0};
I2C_InitStruct.I2C_ClockSpeed=400000;
I2C_InitStruct.I2C_Mode=I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle=I2C_DutyCycle_2;
I2C_Init(I2C1,&I2C_InitStruct);
	
I2C_Cmd(I2C1,ENABLE);//闭合I2C1总开关,使能I2C1模块
}



Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐