[I2C]I2C模块的使用方法(stm32学习)
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与单片机建立通信的。

I2C的速度模式

I2C的波特率定义与串口的一样,每个时钟周期传输一个比特位。波特率越大,传输速度越大。
我们使用的这款单片机仅支持Sm和Fm两种模式。
时钟信号的占空比

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

所有评论(0)