stm32—IIC

1. IIC (I2C:Inter-Integrated Circuit 集成电路总线)

IIC也是属于通信的一种,并且也是串行通信 (按 bit 收发数据)

半双工**:任意时刻只有一个设备可以发送数据,每个设备都有一个唯一的 I2C 地址,用来标识 I2C 设备**

同步**:有一个时钟信号**

总线**:可以连接多个设备**

IIC属于串行总线通信:

只有两根线

一根数据线 SDA:Serial DAta 串行数据线

一根时钟线 SCL:Serial CLock 串行时钟线

SDA:串行数据线

数据传输按 bit 位,1 bit 接 1 bit 的在 SDA 线上串行传输

先传送最高 bit (MSB)

SCL:串行时钟线

传递时钟信号

为什么需要时钟线? ===> 用来同步信号用的

同步:约定好发送数据只能在时钟线低电平时,接收(采样)数据只能在时钟线高电平时

所以,IIC是半双工通信:因为只有一根数据线SDA,在发送数据的时候就不能接收数据,否则收的数据就是自己发出去的

IIC通信设备都会挂载在 SDA 和 SCL 总线上,或者说在 SDA 和 SCL 总线上会挂载很多 IIC 设置。那么任意时刻,只能有一个设备向总线上发送数据,但是接收没有限制,都可以收

为了让数据精准到达 (而不是广播的形式发送),我们给 IIC 总线上的每一个设备都给一个唯一的地址,这个地址就是设备地址,用来区分不同的 IIC 设备

2. IIC物理特点

  • 它是一个支持设备的总线。"总线" 指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机
  • 一个 I2C 总线只使用两条总线线路,一条双向串行数据线 (SDA) ,一条串行时钟线 (SCL),数据线即用来表示数据,时钟线用于数据收发同步
  • 每个连接到总线的设备都有一个独立的地址 (7 / 10bit),主机可以利用这个地址进行不同设备之间的访问
  • 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态 (开漏),而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平
  • 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线
  • 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式
  • 连接到相同总线的 I2C 数量受到总线的最大电容 400pF 限制

总线仲裁:
I2C总线上可能在某一时刻有两个主控设备要同时向总线发送START信号,这种情况叫做总线竞争,I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁,决定谁的信号有效,其他的设备就立刻处于一个"监听模式"
其仲裁原则是这样的:

节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。SDA线的仲裁可以保证 I2C 总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线 (建立在线与逻辑上实现的,当总线上只要有一个设备输出低电平,整条总线便处于低电平状态)

3. IIC协议 (IIC时序图)

IIC 数据通信的大概流程 (时序)图:

  1. 总线空闲 (空闲指没有数据通信是总线的状态) 我们约定:IIC 总线在空闲 (ldle)时,SDA 和 SCL 都处于高电平 (通过在总线上 接一个上拉电阻来实现) 接下来如果有一个设备需要给另一个设备发送数据的话, 就需要一个起始信号

  2. 起始信号:用来表示我要往总线上发送数据啦!SCL 时钟线保持高电平 SDA 数据线从高到低的跳变例子:模拟 IIC 的起始信号(在没有I2C控制器的情况下,如C51): /* 空闲 */ SCL = 1; SDA= 1; delay(); /* 起始信号 */ SDA = 0; delay(); 有没有可能两个或两个以上设备同时发送起始信号呢? 有可能,所以需要总线仲裁:决定谁的信号有效 如果有两个或两个以上的设备同时发送START信号,这个时候,就需要 "总线仲裁",它会决定谁的 START 信号是有效的,其他的设备就立刻处于一个 "监听" 模式****比如:在发送起始信号前,判断 IIC 总线是否空闲 怎么做: time_out = SCL_T;// 超时时间为一个 SCL 周期 while (SCL == 1 && SDA == 1 && time_out--); 解析:如果一个 SCL 周期内,SCL 和 SDA 都是高电平,那么说明 就没有人往总线上发送数据

  3. 发送数据:user data,device data这个数据包含用户真正发送的数据,也包括设备地址 (指定通信方)因为总线上有多个设备,其中一个发起一个起始信号,表示它要跟总线上的某个设备或多个设备通信 它到底跟谁通信呢?如果不指定,总线上所有设备都可以收到数据 所以 IIC 协议规定,每个 IIC 总线上的设备都必须有一个 IIC 设备地址 (7bits / 10bits),并且,同一个 IIC 总线上的设备地址必须不一样 IIC中数据 (包括设备地址)的发送都是按 8bits 进行发送设备的地址 = 7bits + R / W (读写位,占最低 1bits ---> bit0) bit0:0 W 表示"我"要给指定地址的设备写入数据 bit0:1 R 表示"我"要从指定地址的设备里读取数据例如:设备 B 的地址是 101 0001,CPU 要发送数据 0x55 给设备A CPU:START 1010 0001 0101 0101 1010 001:地址 0:写 (发送) 0101 0101:发送的数据 发送完一个字节(8bits)数据后,对方(接收方)必须要返回一个 ACK (应答位) ACK:在 SDA 数据线上的第 9 个时钟周期,接收方给 SDA 一个低电平 但是这里存在一个问题,就是如果数据的最后一个 bit 本身就是一个低电平,那么 SDA 线此时的电平状态就是 0,这个时候,不管接收方应答还是不应答,发送方可能都会认为对方应答啦。怎么解决: 发送方在发送完 8bits 数据后,一般都会释放 SDA 数据线 (SDA = 1) 在第9 个时钟周期时,接收方就会给 SDA 一个低电平表示应答 (表示我已经收到了) 例如: CPU(发送方):STATR 1010 0010 0101 0101 A(接收方): ACK ACK

    总结数据的发送规则:

    数据发送其实就是根据要发送的数据的 bit位 的情况给 SDA 线低电平或高电平, 先发送 MSB(最高位)

    发送数据时,更改数据线的要求如下:

    **IIC 协议定:**在 SCL 时钟线低跳变的时候,可以改变 SDA 数据线电平 所以发送是下降沿触发,每个下降沿可以发送 1bits 数据 在 SCL 时钟线高电平的时候,SDA 数据线保持稳定 所以接收是上升沿触发,每个上升沿到来,就会去 SDA 上采集 1bits 数据

  4. 停止信号:STOP****SCL 保持高电平 SDA 从低电平到高电平跳变

所以一帧 IIC 数据如下:

发送:START + data(7bit addr + 1bit 0W) + data(8bit data) + ... + STOP

1bit ACK 1bit ACK

接收:START + data(7bit addr + 1bit 1R) + data(8bit data) + ... + STOP

1bit ACK 1bit ACK

有另外一个问题:

SDA 线一般是谁要发送数据出去就由谁来控制,那么 SCL 时钟线应该由谁来控制?

谁控制都可以,只要不同时控制,但是很多设备,不具备控制时钟的能力

因为它可能没有时钟单元(没有时钟输出功能)。所以,在STM32中一般是由CPU作为时钟输出(控制者)

所以通过谁控制 SCL 线我们为 IIC 通信设备区分不同的角色:

IIC 主设备:Master

在一次 IIC 通讯过程中,产生 IIC 时钟输出的设备,它控制 IIC 总线的传输速率

IIC 从设备:Slave

在一次 IIC 通讯过程中,被动接收 IIC 时钟的设备

细分的话就会有:Master-send 主发 Master-Receive 主收

就是说时钟提供者既可以收也可以发

Slave-Send 从发 Slave-Receive 从发

IIC 总线上的时钟频率一般在 几十K hz --- 400K hz,频率越低通信速度越慢,但是越稳定,"就低不就高"

IIC 时序图如下:

4. IIC 模拟时序

在一些芯片上(如:C51)它没有 IIC 总线,没有 IIC 控制器,那么它能不能和一个 IIC 接口的模块进行通信呢?

当然可以,我们只需要用两个 GPIO 口来模拟 SDA 和 SCL 即可

cpp 复制代码
/*
    IIC_Send_Start:发送 IIC 起始信号
*/
void IIC_Send_Start(void) {
    /* 空闲 */
    SCL = 1;
    SDA = 1;
    delay(IIC_T); // IIC_T:IIC时钟信号的周期,delay(IIC_T):延时一个时钟周期
    /* 起始 */
    SDA = 0;
    delay(IIC_T);
}

/*
    IIC_Send_Stop:发送IIC停止信号
*/
void IIC_Send_Stop(void) {
    
    SCL = 1;
    SDA = 0;
    delay(IIC_T);
    SDA = 1;
    delay(IIC_T);
}

/*
    "Master-Send":主发
    
    IIC_Send_Byte:	将一个字节的数据发送出去
    @ch:要发送的数据,1个字节
    @返回值:
        发送并成功返回1(表示接收方收到回了一个ACK)
        失败返回0(表示接收方没有收到,没有返回ACK)
*/
int IIC_Send_Byte(unsigned char ch) {
    
    /* MSB(最高位先发),并且是在SCL下降沿时发送1bit */
    int i;
    for (i = 7; i >= 0; i--) { // 8个SCL时钟周期发送8bits
        SCL = 0; // 周期开始,下降沿发送
        SDA = (ch >> i) & 0x01;	// 发送一个bit过去
        delay(IIC_T / 2); // 等待约半个时钟周期
        SCL = 1; // 对方在上升沿采集
        delay(IIC_T / 2); // 延时等待一会让对方有时间接收
    }

    /* 发送方在发送完8bits数据后,一般都会释放SDA数据线 */
    /* 同时第9个时钟周期,等待接受方回应一个ACK低电平 */
    SCL = 0; // 第9个周期开始
    SDA = 1; // 释放SDA数据线
    delay(IIC_T / 2); // 等待接收方应答,接收方回复ack(SDA -> 0)
    SCL = 1;
    delay(IIC_T / 2); // SCL高电平时,才能采集SDA的信号
    if (SDA) {
        return 0; // 代表无人应答,发送失败
    } else {
        return 1; // 代表有人应答,发送成功
    }
}

/*
     "Master-Receive":主收
    
    IIC_Recv_Byte:从IIC总线上接收一个字节
    @返回值:将接收到的字节返回
*/
unsigned char IIC_Recv_Byte(void) {
    // 接收:先接收到的是最高bit,陆续收到 8个bit,在第九个周期发送ACK
    unsigned char ch = 0;
    int i = 0;
    for (i = 7; i >= 0; i--) {
        SCL = 0; // 给半个周期的低电平,让对方发送数据
        delay(IIC_T / 2);
        SCL = 1; // 拉高准备去采集数据
        if (SDA) {
            ch |= 1 << i; // 如果接收到的是高电平,则将对应bit位置为1
        }
        delay(IIC_T / 2);
    }
    /* 接收到8个bit后,应该要回复应答 */
    SCL = 0;
    delay(); // 延时一段非常短的时间让对方释放数据线
    SDA = 0; // 回复应答信号ACK
    delay(IIC_T / 2); // 以上操作总共花掉半个时钟周期
    SCL = 1; // 让对方采集应答信号
    delay(IIC_T / 2);
    return ch;
}

/*
    IIC_Write_Data:向指定的IIC设备写入数据
    @addr:7bit的目标IIC设备的地址
    @str:要发送的数据字符串
    @len:要发送的数据字符串的长度
    @返回值:发送成功返回1,失败返回0
*/
int IIC_Write_Data(unsigned char addr, char *str, int len) {
    
    /* 发送起始信号 */
    IIC_Send_Start();					
    /* 发送设备地址 */	
    // bit0->0表示写入数据   bit1-bit7:IIC设备地址
    int ret = IIC_Send_Byte((addr << 1) | 0);
    if (ret == 0) {	// 代表没有应答
        IIC_Send_Stop(); // 发送停止信号
        return 0;
    }

    /* 发送数据 */
    int i;
    for (i = 0; i < len; i++) {
        ret = IIC_Send_Byte(str[i]);
        if (ret == 0) { // 代表没有应答
            IIC_Send_Stop(); // 发送停止信号
            return 0;
        }
    }
    /* 发送停止信号 */
    IIC_Send_Stop();
    return 1;
}

5. STM32F4xx IIC 控制器

STM32F4xx 有三个 IIC 控制器,有三条 IIC 总线

IIC 控制器是一个 IIC 的设备,它负责产生 IIC 的时序以及协议逻辑

在STM32F4xx中,CPU 与 IIC 是通过系统总线通信的

如果没有 IIC 控制器,那么 CPU 只能通过 GPIO 口来模拟,即 IIC 的时序、协议逻辑等等都需要软件代码去模拟

比如:8051 单片机就没有 IIC 控制器,所以,C51 的 IIC都是通过软件代码模拟实现的

IIC 控制器原理,大概如图:

CR:Control Register

SR:Status Register


STM32F4xx 的 IIC 控制器既可以作为主模式又可以作为从模式:

作为主模式的主发时序图(7bit):

作为主模式的主收时序图(7bit):

6. STM32F4xx I2C固件库函数

7. 疑问

相关推荐
Mortal_hhh11 分钟前
VScode的C/C++点击转到定义,不是跳转定义而是跳转声明怎么办?(内附详细做法)
ide·vscode·stm32·编辑器
密码小丑1 小时前
11月4日(内网横向移动(一))
笔记
Mr.谢尔比1 小时前
电赛入门之软件stm32keil+cubemx
stm32·单片机·嵌入式硬件·mcu·信息与通信·信号处理
LightningJie1 小时前
STM32中ARR(自动重装寄存器)为什么要减1
stm32·单片机·嵌入式硬件
鹿屿二向箔1 小时前
STM32外设之SPI的介绍
stm32
鸭鸭梨吖2 小时前
产品经理笔记
笔记·产品经理
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
西瓜籽@2 小时前
STM32——毕设基于单片机的多功能节能窗控制系统
stm32·单片机·课程设计
丫头,冲鸭!!!3 小时前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法
听忆.3 小时前
手机屏幕上进行OCR识别方案
笔记