• STM32的IIC


    IIC介绍

    IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线。

    通信距离

    在这里插入图片描述

    通信速度

    在这里插入图片描述
    在这里插入图片描述

    主从方式

    在这里插入图片描述

    通信方式

    在这里插入图片描述

    物理结构

    在这里插入图片描述
    在这里插入图片描述

    I2C总线上传输的每一位数据都有一个时钟脉冲相对应,即同步控制。数据位的传输是边沿触发。

    IIC协议

    空闲状态

    SDA和SCL两条线同时处于高电平为总线空闲状态。由两个上拉电阻拉高。

    开始信号、结束信号和应答信号

    SDA数据线,SCL时钟线

    • 开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。是一种电平跳变时序信号,而非一个电平信号。

    • 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
      在这里插入图片描述

    • 应答信号ACK:发送器每发送一个字节,接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据,即由接收器反馈的应答信号。将SDA拉低,并保证在该时钟的高电平前进为稳定的低电平。

    • 如果ACK为低电平为有效应答,如果是高电平就是非应答位NACK,也就是没有成功。

    • 如果接收器是主控器,收到最后一个字节后,发送NACK信号,以通知发送器数据发送结束,并释放SDA。
      在这里插入图片描述

    向从机发送数据的过程

    1. 主机发送start信号;总线处于占用状态。
    2. 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读
    3. 从机返回ACK响应信号;
    4. 主机发送要给从机写入数据的地址;(有的设备不用)
    5. 从机返回ACK响应信号;
    6. 主机发送数据;
    7. 从机返回ACK响应信号;
      重复第6和7步,直到从机返回一个NACK非响应信号;
      主机发送停止信号,结束数据传输。
      在这里插入图片描述

    读取从机数据的过程

    1. 主机发送start信号;
    2. 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读;
    3. 从机返回ACK响应信号;
    4. 主机发送要给从机读入数据的地址;(有的设备不用)
    5. 从机返回ACK响应信号;
    6. 重新启动IIC总线,发送start信号;(前面步骤的目的向从机传送地址,下面开始读取数据)
    7. 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读;
    8. 从机返回ACK响应信号;
    9. 主机接收数据;
    10. 从机返回ACK响应信号;
      重复第9和01步,直到从机返回一个NACK非响应信号;
      主机发送停止信号,结束数据传输。
      在这里插入图片描述

    数据有效性

    IIC总线数据传送时,时钟的高电平期间,数据线上的数据必须保持稳定,不允许变化。即SCL的上升沿到来前要准备好,下降沿到来前必须可靠
    SDA的数据在SCL高电平期间被写入从机。所以SDA的数据变化要发生在SCL低电平期间。
    在这里插入图片描述

    协议一帧构成

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    IIC实验目的

    开机的时候先检测 24C02 是否存在,然后在主循环里面用 1 个按键(KEY0)用来执行写入 24C02 的操作,另外一个按键(WK_UP)用来执行读出操作,在 TFTLCD模块上显示相关信息。

    24C02硬件连接图

    板载的EEPROM芯片型号为24C02。
    该芯片的总容量是256个字节,该芯片通过 IIC 总线与外部连接。

    • 24C02 的 SCL 和 SDA 分别连在 STM32 的 PB6 和 PB7 上的
      在这里插入图片描述
      WP:write Protect 写保护就不能操作
      A0-A2,地址线,默认接地,地址000,最高就是256

    地址如下:
    在这里插入图片描述

    • 器件地址:固定地址(4)+ 可编程地址(3)+ 读写为(0读1写)

    • 高四位是出厂时写好的地址,A1-3是可调整的硬件地址线,这七位就是地址,最后一位是读写控制,读是1,写是0
      读写时序
      在这里插入图片描述

    • 开始,外设地址,应答,写入位置,应答,数据,应答

    ST公司为了规避飞利浦IIC专利问题,将STM32 的硬件 IIC 非常复杂,更重要的是不稳定,故不推荐使用。所以通常就通过模拟来实现了。

    初始化IIC

    该段代码可以用在任何 IIC 设备上。

    引脚初始化

    #include "myiic.h"
    #include "delay.h"
    //初始化 IIC
    void IIC_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//PB 时钟使能
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化 GPIO
    GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这段代码是对GPIOB端口的一个初始化。
    为什么是GPIOB呢?前面我们说过24C02 的 SCL 和 SDA 分别连在 STM32 的 PB6 和 PB7 上。
    GPIOB挂载在APB2之下,这里可以参考APB2 外设时钟使能寄存器(RCC_APB2ENR)
    RCC_APB2Periph_GPIOB是宏定义,这里可以参考使能IO口时钟
    按理说I2C接口应该如下设置或者按照中文参考手册,这里却用了推挽,暂时不知为何,很可能是按照常规思想,没有特殊要求则推挽的思路。

    • SCL,SDA可以配置成推挽输出、开漏输出(上拉电阻输出1)

      • 开漏输出有线与特性,当多个器件通讯的时候,因为线与. 如果主设备A拉高SDA时, 已经有其他主设备将SDA拉低了. 由于 1 & 0 = 0 那么主设备A在检查SDA电平时, 会发现不是高电平, 而是低电平. 说明其他主设备抢占总线的时间比它早, 主设备A只能放弃占用总线. 如果是高电平, 则可以占用.
    • SCL,SDA也可以配置成开漏输出、开漏输出(开漏输出为防止多个器件存在短路)

    • SCL,SDA也可以配置成推挽输出、推挽输出与浮空输入(通过切换模式)

    中文参考手册中的推荐设置为
    在这里插入图片描述

    输出模式只有三种,最常用的就是50MHz,可以参考解析GPIO_Init()
    SetBits可以参考GPIO相关库函数
    开始都是高电平

    起始信号

    //产生 IIC 起始信号
    void IIC_Start(void)
    {
    	SDA_OUT(); //sda 线输出
    	IIC_SDA=1;
    	IIC_SCL=1;
    	delay_us(4);
    	IIC_SDA=0; //START:when CLK is high,DATA change form high to low
    	delay_us(4);
    	IIC_SCL=0; //钳住 I2C 总线,准备发送或接收数据
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    保持SCL为高电平,SDA产生一个下降沿,然后SCL拉低,一个START信号产生;

    停止信号

    
    //产生 IIC 停止信号
    void IIC_Stop(void)
    {
    	SDA_OUT(); //sda 线输出
    	IIC_SCL=0;
    	IIC_SDA=0; //STOP:when CLK is high DATA change form low to high
    	delay_us(4);
    	IIC_SCL=1;
    	IIC_SDA=1; //发送 I2C 总线结束信号
    	delay_us(4);
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    保持SCL为高电平,SDA产生一个上升沿,一个STOP信号产生。

    应答信号

    
    //等待应答信号到来
    //返回值:1,接收应答失败
    // 0,接收应答成功
    u8 IIC_Wait_Ack(void)
    {
    	u8 ucErrTime=0;
    	SDA_IN(); //SDA 设置为输入
    	IIC_SDA=1;delay_us(1);
    	IIC_SCL=1;delay_us(1);
    	while(READ_SDA)//一直等,等不到就停止
    	{ 
    		ucErrTime++;
    		if(ucErrTime>250)
    			{ IIC_Stop();
    			return 1;
    			}
    }
    IIC_SCL=0; //时钟输出 0
    return 0;
    }
    //产生 ACK 应答
    void IIC_Ack(void)
    { IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=0;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(2);
    	IIC_SCL=0;
    }
    //不产生 ACK 应答
    void IIC_NAck(void)
    { IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=1;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(2);
    	IIC_SCL=0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在大于一个时钟脉冲的时间保持SDA为低电平

    发送一个字节

    //IIC 发送一个字节
    //返回从机有无应答
    //1,有应答
    //0,无应答
    void IIC_Send_Byte(u8 txd)
    { u8 t;
    	SDA_OUT();
    	IIC_SCL=0;//拉低时钟开始数据传输
    	for(t=0;t<8;t++)
    	{ IIC_SDA=(txd&0x80)>>7;
    	从最高位开始发数据,最高位移到最低
    	txd<<=1;
    	delay_us(2); //对 TEA5767 这三个延时都是必须的
    	IIC_SCL=1;
    	delay_us(2);
    	IIC_SCL=0;
    	delay_us(2);
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    一个CLK对应发送一个bit,先将SCL拉低,然后准备好要发送的数据,然后将SCL拉高,产生一个CLK时钟脉冲,数据就能直接发往从机;从高位(MSB)开始发送直到最低位(LSB),每发送完一个位后左移一位,确保每一位的数据都能正确发送,最后将SCL重新拉低。

    读一个字节

    //读 1 个字节,ack=1 时,发送 ACK,ack=0,发送 nACK
    u8 IIC_Read_Byte(unsigned char ack)
    { unsigned char i,receive=0;
    SDA_IN(); //SDA 设置为输入
    for(i=0;i<8;i++ )
    { 
    	IIC_SCL=0;
    	delay_us(2);
    	IIC_SCL=1;
    	//上升沿的时候去读
    	receive<<=1;
    	if(READ_SDA)receive++;
    	delay_us(1);
    }
    //读完了之后要应答
    if (!ack)
    IIC_NAck(); //发送 nACK
    else
    IIC_Ack(); //发送 ACK
    return receive;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    该部分为 IIC 驱动代码,实现包括 IIC 的初始化(IO 口)、IIC 开始、IIC 结束、ACK、IIC读写等功能。
    在其他函数里面,只需要调用相关的 IIC 函数就可以和外部 IIC 器件通信了

    EEPROM-24c02

    硬件引脚

    什么是EEPROM?
    总容量是256(2K/8)个字节,也就是2的8次方个字节,每个字节八位,共2K个位,10次方。

    在这里插入图片描述
    引脚
    在这里插入图片描述
    硬件上设置了A0=A1=A2=0

    硬件地址

    在这里插入图片描述
    高四个位是固定的是10,即A,后边懂了吧。

    iic.h

    #ifndef __MYIIC_H
    #define __MYIIC_H
    #include "sys.h"
    //IO 方向设置
    #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
    #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}//IO 操作函数
    #define IIC_SCL 
    #define IIC_SDA //SCL
    #endif 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    该部分代码的 SDA_IN()和 SDA_OUT()分别用于设置 IIC_SDA 接口为输入和输出

    IIC 接口来操作 24Cxx 芯片

    接下来我们看看 24cxx.c 文件代码:

    #include “24cxx.h”
    #include “delay.h”
    //初始化 IIC 接口
    void AT24CXX_Init(void)
    {
    IIC_Init();
    }

    读一个字节

    //在 AT24CXX 指定地址读出一个数据
    //ReadAddr:开始读数的地址
    //返回值 :读到的数据
    u8 AT24CXX_ReadOneByte(u16 ReadAddr)
    {
    	u8 temp=0;
    	IIC_Start();
    if(EE_TYPE>AT24C16)
    { 
    	IIC_Send_Byte(0XA0); //发送写命令
    	IIC_Wait_Ack();
    	IIC_Send_Byte(ReadAddr>>8); //发送高地址
    }else 
    	IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址 0XA0,写数据
    	IIC_Wait_Ack();
    	IIC_Send_Byte(ReadAddr%256); //发送低地址
    	IIC_Wait_Ack();
    	IIC_Start();
    	IIC_Send_Byte(0XA1); //进入接收模式
    	IIC_Wait_Ack();
    	temp=IIC_Read_Byte(0);
    	IIC_Stop(); //产生一个停止条件
    	return temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    写一个字节

    //在 AT24CXX 指定地址写入一个数据
    //WriteAddr :写入数据的目的地址
    //DataToWrite:要写入的数据
    void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
    {
    	IIC_Start();
    	if(EE_TYPE>AT24C16)
    	{   IIC_Send_Byte(0XA0); //发送写命令
    		IIC_Wait_Ack();
    		IIC_Send_Byte(WriteAddr>>8);//发送高地址
    	}else IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址 0XA0,写数据
    	IIC_Wait_Ack();
    	IIC_Send_Byte(WriteAddr%256); //发送低地址
    	IIC_Wait_Ack();
    	IIC_Send_Byte(DataToWrite); //发送字节
    	IIC_Wait_Ack();
    	IIC_Stop(); //产生一个停止条件
    	delay_ms(10);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    写一定长度的字节

    //在 AT24CXX 里面的指定地址开始写入长度为 Len 的数据
    //该函数用于写入 16bit 或者 32bit 的数据.
    //WriteAddr :开始写入的地址
    //DataToWrite:数据数组首地址
    //Len :要写入数据的长度 2,4
    void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
    { u8 t;
    for(t=0;t<Len;t++)
    { AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
    }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    读一定长度的字节

    //在 AT24CXX 里面的指定地址开始读出长度为 Len 的数据
    //该函数用于读出 16bit 或者 32bit 的数据.
    //ReadAddr :开始读出的地址
    //返回值 :数据
    //Len :要读出数据的长度 2,4
    u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
    { u8 t;
    u32 temp=0;
    for(t=0;t<Len;t++)
    { temp<<=8;
    temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
    }
    return temp;
    }
    
    //检查 AT24CXX 是否正常,写一个值再读回来,看是不是这个值。
    //这里用了 24XX 的最后一个地址(255)来存储标志字.
    //如果用其他 24C 系列,这个地址要修改
    //返回 1:检测失败
    //返回 0:检测成功
    u8 AT24CXX_Check(void)
    { u8 temp;
    temp=AT24CXX_ReadOneByte(255); //避免每次开机都写 AT24CXX
    if(temp==0X55)return 0;
    else //排除第一次初始化的情况
    { AT24CXX_WriteOneByte(255,0X55);
    temp=AT24CXX_ReadOneByte(255);
    if(temp==0X55)return 0;
    }
    return 1;
    }
    
    //在 AT24CXX 里面的指定地址开始读出指定个数的数据
    //ReadAddr :开始读出的地址 对 24c02 为 0~255
    //pBuffer :数据数组首地址
    //NumToRead:要读出数据的个数
    void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
    { while(NumToRead)
    { *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
    NumToRead--;
    }
    }
    //在 AT24CXX 里面的指定地址开始写入指定个数的数据
    //WriteAddr :开始写入的地址 对 24c02 为 0~255
    //pBuffer :数据数组首地址
    //NumToWrite:要写入数据的个数
    void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
    { while(NumToWrite--)
    	{ AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
    		WriteAddr++;
    		pBuffer++;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    这部分代码实际就是通过 IIC 接口来操作 24Cxx 芯片,理论上是可以支持 24Cxx 所有系列的芯片的(地址引脚必须都设置为 0),24CXX 的型号定义在 24cxx.h 文件里面,通过 EE_TYPE 设置。

    IIC步骤总结

    • 开启GPIOB时钟
    • 设置GPIOB的参数,初始化GPIOB
    • 主机发送start信号;
    • 主机发送从机地址,高7bit是地址,bit0是读写控制位,0表示写,1表示读;
    • 从机返回ACK响应信号;
    • 主机发送要给从机写入数据的地址;(有的设备不用)
    • 从机返回ACK响应信号;
    • 主机发送数据;
    • 从机返回ACK响应信号;
  • 相关阅读:
    青少年python系列 26.turtle库绘制一个四叶草
    Flutter ☞ 变量
    对某钓鱼样本分析
    C/C++ Linux 开发环境
    在Ubuntu中设置中文输入法的步骤
    git图形化管理工具
    Three.js的渲染器:WebGLRenderer、CSS3DRenderer、SVGRenderer
    计算机网络面经八股-HTTP请求报文和响应报文的格式?
    C语言基础-结构体
    【精】alibaba-sentinel 管理控制台 啥都没有 ,一片空白解决。
  • 原文地址:https://blog.csdn.net/qq_45578181/article/details/126492667