IIC之原理---(基础/理解)

目录

前言

基础

 一.  I2C基础

1.1 I2C物理层

1.1.1 什么是I2C

1.2 SCL和SDA信号线

二. I2C总线协议简介

2.1 空闲状态

2.2 数据有效性

2.3 START和STOP条件

2.4 字节格式

三.IIC协议流程

3.1初始化

3.2开始信号(START)

3.3结束信号(STOP)

3.4应答信号

3.4.1应答信号(ACK)

3.4.2非应答信号(NACK)

3.4.3等待应答信号(Wait_Ack)

3.5发送1字节

3.6读取1字节

3.6 myI2c代码 

四.AT24C01/2案例

4.1 AT24C01/2基础

4.2 AT24C01/2写数据

 4.2.1写一个字节

 4.3 AT24C02读数据

4.3.1 读1个字节

理解 

五.NAU8822 软件IIC

5.1 软件IIC

5.1.1 NAU8822 写数据

5.1.2 NAU8822 读数据

5.2 逻辑分析仪(软件)IIC

5.2.1 写数据时序

5.2.2 读数据时序

5.2.3 写读时序对比

5.1.4 注:myi2c代码 

 六.NAU8822硬件IIC

6.1 硬件IIC

6.1.1 I2C功能描述

6.1.2 模式选择 

6.2 I2C主模式选择 

6.2.1 主发送器模式

6.2.2 硬件IIC 写数据 

6.2.3 主接收器模式

6.2.4 硬件IIC 读数据

6.3 逻辑分析仪(硬件)IIC

6.3.1 写时序

6.3.2 读时序 


前言

IIC与AT24C02通信原理;

使用(软件/硬件)IIC与NAU8822进行通信;

基础(7位地址;8位数据)

一 .理解I2C协议基础

二.理解AT24C02模拟IIC通信原理

理解(7地址 ;9位数据)

一.使用STM32与NAU8822(软件IIC)进行通信

二.使用STM32与NAU8822(硬件IIC)进行通信

三.使用逻辑分析仪进行时序分析

基础

初级(7位地址;8位数据)

一 .理解I2C协议基础

二.理解AT24C02模拟IIC通信原理

 一.  I2C基础

1.1 I2C物理层

1.1.1 什么是I2C

IIC(Inter-Integrated Circuit,集成电路互连)是一种串行通信协议,用于微控制器和外围设备之间的通信。它是由飞利浦公司(现NXP Semiconductors)在1980年代初期开发的。IIC协议的主要特点包括:

  1. 两线制:IIC通信只需要两根线,一根是串行时钟线(SCL),另一根是串行数据线(SDA)。

  2. 多主从架构:在IIC总线上可以有一个主设备和多个从设备。主设备控制通信的开始和结束,从设备则响应主设备的请求。

  3. 地址寻址:每个从设备都有一个唯一的地址,主设备通过发送这个地址来选择特定的从设备进行通信。

  4. 同步通信:数据传输是同步进行的,由SCL时钟信号控制数据位的发送和接收。

  5. 半双工通信:在任何给定时间,数据只能在一个方向上流动,要么是主设备到从设备,要么是从设备到主设备。

  6. 应答机制:从设备在接收到数据后会发送一个应答(ACK)或非应答(NACK)信号,以指示数据是否成功接收。

  7. 支持多种传输速率:IIC支持不同的传输速率,包括标准模式(最高100 kbit/s)、快速模式(最高400 kbit/s)、快速模式加(最高1 Mbit/s)、高速模式(最高3.4 Mbit/s)和超高速模式(最高5 Mbit/s)。

  8. 低功耗:IIC协议设计时考虑了低功耗的需求,适合于电池供电的便携式设备。

  9. 易于扩展:IIC总线可以很容易地扩展,增加更多的从设备,而不会显著增加系统的复杂性。

  10. 广泛支持:IIC是一种广泛支持的工业标准,许多微控制器和外围设备都内置了IIC接口。

IIC协议因其简单性、灵活性和低成本而在各种电子系统中得到广泛应用,特别是在需要连接多个低速外围设备的场景中。

1.2 SCL和SDA信号线

IIC(Inter-Integrated Circuit,也称为I2C)协议中的SCL和SDA是两条关键的信号线,它们定义了IIC总线的物理通信基础。下面是对这两条线的详细解释:

  1. SCL(串行时钟线 Serial Clock Line)

    • SCL是IIC总线上的时钟信号线,由总线上的主设备(Master)控制。
    • 主设备通过SCL线提供时钟信号,以同步数据传输过程,确保数据在发送和接收时能够正确对齐。
    • SCL信号通常在空闲时保持高电平状态,表示总线是空闲的。
  2. SDA(串行数据线 Serial Data Line)

    • SDA是IIC总线上的数据信号线,用于在主设备和从设备之间传输数据。
    • 数据通过SDA线在主设备和从设备之间双向传输,但每次传输的方向由当前的通信协议和控制信号决定。
    • 在数据传输过程中,SDA线上的数据在SCL的时钟信号控制下进行传输,通常在SCL的高电平期间保持稳定,以便接收设备可以读取数据。

IIC总线的通信过程包括以下步骤:

  • 起始条件:主设备通过将SDA从高电平拉低到低电平,同时SCL保持高电平来发起通信。
  • 地址和数据传输:主设备随后发送从设备的地址,并可能指示传输方向(读或写)。从设备根据接收到的地址和传输方向信号响应。
  • 数据交换:根据传输方向,主设备和从设备通过SDA线交换数据。数据传输在SCL的时钟信号下进行,每个时钟周期传输一个数据位。
  • 应答(ACK)和非应答(NACK):接收方在接收到每个字节的数据后,通过SDA线发送一个应答信号,表示数据已被成功接收。
  • 停止条件:主设备通过将SDA从低电平拉高到高电平,同时SCL保持高电平来结束通信。

SDA和SCL都是双向线,通过电流源或上拉电阻连接到正电源电压(见图3)。当母线空闲时, 两条线都是高电平。连接到母线上的器件的输出级必须具有开路漏极或开路集电极,才能执 行线与功能。i2c总线的数据传输速率在标准模式下最高可达100kbit /s,在快速模式下最高可 达400kbit /s,在快速模式下最高可达1mbit /s,在高速模式下最高可达3.4 Mbit/s。总线电容限 制了连接到总线上的接口数量。

二. I2C总线协议简介

2.1 空闲状态

SDA和SCL都是双向线,通过电流源或上拉电阻连接到正电源电压(见图3)。当母线空闲时, 两条线都是高电平。连接到母线上的器件的输出级必须具有开路漏极或开路集电极,才能执 行线与功能。i2c总线的数据传输速率在标准模式下最高可达100kbit /s,在快速模式下最高可 达400kbit /s,在快速模式下最高可达1mbit /s,在高速模式下最高可达3.4 Mbit/s。总线电容限 制了连接到总线上的接口数量。对于单个主应用程序,如果总线上没有会延长时钟的设备,主的SCL输出可以是推挽驱动器设计。

在只有一个主设备(Master)的I2C通信应用中,如果总线上没有其他设备会延长时钟信号(即没有时钟拉伸),那么主设备的SCL(串行时钟线)输出可以设计为推挽式驱动器(push-pull driver)。

  1. 单主设备应用:指的是在I2C总线上只有一个主设备控制通信的情况。

  2. SCL输出:SCL是I2C总线上的时钟信号线,由主设备产生,用于同步数据传输。

  3. 推挽式驱动器设计:推挽式驱动器是一种可以同时提供推(驱动高电平)和拉(驱动低电平)能力的电路设计。在I2C总线上,这意味着主设备可以有效地控制SCL线上的电平状态。

  4. 没有设备会拉伸时钟:在I2C总线上,从设备可以通过保持SCL线低电平来“拉伸”时钟周期,这通常发生在从设备需要额外时间来处理数据时。如果总线上没有这样的设备,即没有设备会进行时钟拉伸,那么主设备可以更自由地控制SCL线。

  5. 含义:如果I2C总线上没有会进行时钟拉伸的从设备,主设备可以使用推挽式驱动器来控制SCL线,因为不需要考虑从设备对时钟信号的控制需求。这可以简化设计,因为不需要实现复杂的时钟同步或冲突检测机制。

2.2 数据有效性

在SCL时钟的HIGH时段,SDA线上的数据必须是稳定的。只有当SCL线上的时钟信号为LOW 时,数据线的HIGH或LOW状态才能改变(见图4)。每传输一个数据位产生一个时钟脉冲。

2.3 START和STOP条件

所有事务都以START(S)开始,并以STOP(P)结束(参见图5)。SDA线上的HIGH到LOW转换, 而SCL为HIGH定义了START条件。SDA线上的LOW到HIGH转换,而SCL是HIGH,定义 STOP条件。

2.4 字节格式

放在SDA线上的每个字节必须是8位长每次传输可以传输的字节数是不受限制的每个字 节后面必须跟一个确认位。数据首先以最高有效位(MSB)传输(见图6)。如果从机在完成其他 功能(例如服务内部中断)之前不能接收或传输另一个完整字节的数据,则它可以保持时钟线 SCL 低电平以迫使主机进入等待状态。当从服务器准备好接收另一个字节的数据并释放时钟 线SCL时,数据传输继续进行。

字节格式

在I2C总线上,每个字节的数据必须是8位长。每次传输可以传输的字节数没有限制。在每个字节的确认周期之后,主设备会在确认周期内将数据线(SDA)拉高。数据传输时,首先是最高有效位(MSB)被发送(见图25)。如果从设备不能接收另一个完整的字节数据,或者它正在执行其他功能,例如处理内部中断,它不允许将时钟线(SCL)保持在低电平

三.IIC协议流程

3.1初始化

空闲状态:拉高SDA和SCL,也就是这个下图的两根线;

这里初始化GPIOB的PB6和PB7作为IIC的时钟线和数据线,后面宏定义的函数是为了更好的去理解代码组成。

这里的代码是参考正点原子的

//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    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA	 
#define READ_SDA   PBin(7)  //输入SDA 

这里宏定义的:IO方向设置、IO口的操作函数。是为了提高代码可读性:使用宏定义可以提高代码的可读性,例如后面提到的代码,后面会详细介绍。

//初始化IIC
void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOB时钟
	   
	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_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); 	//PB6,PB7 输出高
}

3.2开始信号(START)

SDA线上的切换, 而SCL为高则定义了START条件。

前面提到过:IIC数据有效性的规则,在SCL为高的情况下,SDA必须是稳定的。

所以,想让数据稳定(能准确识别到START开始信号),最好先让SDA为高电平。

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

3.3结束信号(STOP)

SDA线上的低电平到高电平的切换,而SCL为高电平,定义 STOP条件。

先把SCL拉低(SDA数据无效状态),再拉低SDA,拉高SCL,拉高SDA。(SCL高电平的时候,检测到SDA上升沿)

//产生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();
	IIC_SCL=1; 
	IIC_SDA=1;//发送I2C总线结束信号
	//delay_us();							   	
}

3.4应答信号
3.4.1应答信号(ACK)

应答信号(ACK)

  • 应答信号是一个低电平信号,由从设备在接收到一个字节数据后发送给主设备。
  • 当从设备成功接收并准备好了接收下一个字节,它会在SCL的第9个时钟脉冲期间将SDA线拉低,发送ACK信号。
  • ACK信号表示数据被正确接收,并且从设备准备好了继续通信

//产生ACK应答
void IIC_Ack(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	//delay_us();
	IIC_SCL=1;
	//delay_us();
	IIC_SCL=0;
}

代码解释:

这段代码是I2C通信中用于生成应答(ACK)信号的函数实现。在I2C协议中,主设备(Master)在接收到从设备(Slave)发送的数据后,会发送一个应答信号,表示数据已被成功接收。以下是代码的逐行解释:

  1. IIC_SCL=0;:将SCL(Serial Clock Line,串行时钟线)设置为低电平。在I2C通信中,SCL线由主设备控制,用于同步数据传输。

  2. SDA_OUT();:这个函数的目的是将SDA(Serial Data Line,串行数据线)配置为输出模式。在不同的硬件平台上,可能需要特定的配置来设置GPIO(通用输入输出)引脚为输出模式。

  3. IIC_SDA=0;:将SDA线设置为低电平。在I2C协议中,应答(ACK)信号是在数据传输期间,主设备(Master)将SDA线拉低,以通知从设备(Slave)它已经成功接收到数据。

  4. IIC_SCL=1;:将SCL线设置为高电平。这一步是在SDA线已经被设置为低电平之后进行的,以确保在时钟线上的上升沿时,SDA线上的低电平可以被从设备检测到,从而识别出应答信号。

  5. IIC_SCL=0;:将SCL线重新设置为低电平,为下一次通信做准备。

3.4.2非应答信号(NACK)
  • 非应答信号(NACK)

    • 非应答信号是一个高电平信号,也是由从设备发送的,但表示数据没有被接收从设备不希望继续接收数据。
    • 这可能发生在从设备需要结束通信时,例如当它已经接收了预期数量的数据字节,或者当它需要主设备停止发送数据。
    • 在发送完最后一个数据字节后,主设备会等待NACK信号来确认通信的结束。

//不产生ACK应答		    
void IIC_NAck(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	//delay_us();
	IIC_SCL=1;
	//delay_us();
	IIC_SCL=0;
}	

代码解释:

这段代码是I2C通信中用于生成非应答(NACK)信号的函数实现。在I2C协议中,应答(ACK)和非应答(NACK)信号是通过SDA线上的电平变化来实现的。以下是代码的逐行解释:

  1. IIC_SCL=0;:将SCL(Serial Clock Line,串行时钟线)设置为低电平。在I2C通信中,SCL线由主设备控制,用于同步数据传输。

  2. SDA_OUT();:这个函数的目的是将SDA(Serial Data Line,串行数据线)配置为输出模式。在不同的硬件平台上,可能需要特定的配置来设置GPIO(通用输入输出)引脚为输出模式。

  3. IIC_SDA=1;:将SDA线设置为高电平。在I2C协议中,非应答(NACK)信号是在数据传输期间,从设备(Slave)将SDA线拉高,以通知主设备(Master)它不想接收更多的数据。

  4. IIC_SCL=1;:将SCL线设置为高电平。这一步是在SDA线已经被设置为高电平之后进行的,以确保在时钟线上的上升沿时,SDA线上的高电平可以被主设备检测到,从而识别出非应答信号。

  5. IIC_SCL=0;:将SCL线重新设置为低电平,为下一次通信做准备。

3.4.3等待应答信号(Wait_Ack)

"等待应答信号"(Wait for Acknowledge)是指主设备在发送数据字节后,进入的一种等待状态,以期望从设备通过SDA线返回一个应答信号。这个过程是I2C数据交换中的关键环节,确保数据被正确接收和确认。以下是等待应答信号的详细步骤:

//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN();      //SDA设置为输入  
	IIC_SDA=1;//delay_us();	   
	IIC_SCL=1;//delay_us();	 
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0;//时钟输出0 	   
	return 0;  
}

代码解释:

  1. u8 ucErrTime=0;:声明一个无符号8位变量ucErrTime并初始化为0,用于计数等待应答信号时的超时时间。

  2. SDA_IN();:调用一个宏或函数将SDA(串行数据线)设置为输入模式,以便能够读取从设备通过SDA线发送的应答信号。

  3. IIC_SDA=1;:释放SDA,这里参考前面讲的应答信号那里

  4. IIC_SCL=1;:释放SCL,这标志着开始读取应答信号的时钟周期。

  5. while(READ_SDA):使用一个循环等待SDA线变为低电平,这表示从设备已经发送了应答信号。READ_SDA是一个宏或函数,用于读取SDA线上的电平状态。

  6. ucErrTime++;:如果SDA线没有在预期的时间内变为低电平,错误计数器将增加。

  7. if(ucErrTime>250):如果错误计数超过250次,认为没有收到应答信号,这是一个超时条件。

  8. IIC_Stop();:如果超时,调用IIC_Stop函数发送I2C停止条件,以结束当前的通信序列。

  9. return 1;:返回1表示接收应答失败。

  10. IIC_SCL=0;:将SCL线置为低电平。这通常是为了在读取完应答信号后,为下一个通信周期做准备。

3.5发送1字节

在前面的字节格式提到过,每个字节的数据必须是8位长。每次传输可以传输的字节数没有限制。所以在发送数据时,8bit逐个发送。下图可以看出,发送数据和发送地址都是以字节单位(8bit)进行发送的。

发送(ADDRESS+W/R)(1字节)数据或DATA(1字节)数据

//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;
		if((txd&0x80)>>7)
			IIC_SDA=1;
		else
			IIC_SDA=0;
		txd<<=1; 	  
		//delay_us();   //对TEA5767这三个延时都是必须的
		IIC_SCL=1;
		//delay_us(); 
		IIC_SCL=0;	
		//delay_us();
    }	 
} 	

代码解释:

  1. SDA_OUT(); 将数据线(SDA)设置为输出模式,以便可以驱动数据线。

  2. IIC_SCL=0; 将时钟线拉低,表示开始一个数据传输周期。在I2C协议中,数据是在SCL的上升沿被采样,在下降沿进行改变。

  3. 接下来的循环通过8次迭代来发送一个字节的8个位。每次迭代发送一个字节的最高有效位(MSB),然后是次高有效位,依此类推,直到最低有效位。

  4. if((txd&0x80)>>7) 检查当前要发送的位是否为1。txd 是要发送的字节,0x80 是一个掩码,用于检查最高位。如果最高位是1,(txd&0x80) 的结果将非零,右移7位后仍然为1,然后设置 IIC_SDA=1; 将数据线设置为高电平。如果最高位是0,则设置 IIC_SDA=0; 将数据线设置为低电平。

  5. txd<<=1; 将 txd 左移一位,准备发送下一个位。

  6. IIC_SCL=1; 将时钟线拉高,表示当前位已经稳定在数据线上,从设备可以在此时采样这个位。

  7. IIC_SCL=0; 这行代码再次将时钟线拉低,准备发送下一个位。

3.6读取1字节

这段代码通过控制时钟线和数据线来同步数据的接收,并根据 ack 参数的值来决定发送ACK还是NACK信号。

//读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();
		IIC_SCL=1;
        receive<<=1;
        if(READ_SDA)receive++;   
		//delay_us(); 
    }					 
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive;
}

代码解释:

u8 IIC_Read_Byte(unsigned char ack):
    定义了一个函数 IIC_Read_Byte,它接收一个无符号字符参数 ack,这个参数用于决定在读取完数据后发送ACK(应答)还是NACK(非应答)。

unsigned char i,receive=0;:
    声明了两个无符号字符变量 i 和 receive。i 用作循环计数器,receive 用来存储接收到的数据。

SDA_IN();//SDA设置为输入:
    将数据线(SDA)设置为输入模式,以便可以读取从设备发送的数据。

for(i=0;i<8;i++):
    开始一个循环,用于读取8位数据。

IIC_SCL=0;:
    将时钟线(SCL)拉低,准备开始数据传输。

delay_us(2);:
    等待一段时间,确保数据线稳定。

IIC_SCL=1;:
    将时钟线拉高,从设备在这个时钟的上升沿发送数据。

receive<<=1;:
    将 receive 变量左移一位,为接收新的位做准备。

if(READ_SDA)receive++;:
    如果读取到SDA线上是高电平,则将 receive 的最高位设置为1。

delay_us(1);:
    再次等待一段时间,确保数据稳定。

if (!ack) IIC_NAck();//发送nACK:
    如果 ack 参数为0,发送NACK信号,表示这是最后一个接收的字节。

else IIC_Ack(); //发送ACK:
    如果 ack 参数非0,发送ACK信号,表示可以继续接收更多的数据。

return receive;:
    返回接收到的数据字节。

3.6 myI2c代码 

可以参考正点原子IIC实验的myi2c.c

#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    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA	 
#define READ_SDA   PBin(7)  //输入SDA 

//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);	 






//初始化IIC
void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOB时钟
	   
	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_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); 	//PB6,PB7 输出高
}

//产生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总线,准备发送或接收数据 
}	  

//产生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,接收应答失败
//        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;
}					 				     
//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;
		if((txd&0x80)>>7)
			IIC_SDA=1;
		else
			IIC_SDA=0;
		txd<<=1; 	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;	
		delay_us(2);
    }	 
} 	    
//读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;

四.AT24C01/2案例

4.1 AT24C01/2基础

AT24C01A/02/04/08A/16A 是一种存储芯片,有不同的存储容量,从1024位到16384位。它们被分成多个8位一组的数据块。也就是串行电可擦可编程只读存储器(EEPROM)

A0,A1,A2:硬件地址引脚。这里默认接地所以引脚都为0
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线
下图为设备地址1010000 0/1

4.2 AT24C01/2写数据

些数据详细图解

写入流程

  1. 发送开始信号 (IIC_Start()):通过SDA和SCL信号发出I2C总线开始通信的信号。

  2. 发送设备地址和写入标志:向总线上发送AT24C02的写入地址。

  3. 等待应答 (IIC_Wait_Ack()):等待EEPROM设备发送应答信号,确认地址接收成功。

  4. 发送内部地址:发送要写入数据的内部地址。由于AT24C02的地址只有8位,所以直接发送8位地址即可。

  5. 再次等待应答:发送内部地址后,再次等待EEPROM设备的应答。

  6. 发送数据:发送要写入EEPROM的数据字节。

  7. 再次等待应答:写入数据后,再次等待EEPROM设备的应答。

  8. 发送停止信号 (IIC_Stop()):通过SDA和SCL信号发出I2C总线停止通信的信号。

  9. 等待写入完成:EEPROM设备在接收到停止信号后,还需要一定的时间来完成数据的写入。通常需要等待几毫秒到几十毫秒不等,这个时间长度可以在AT24C02的数据手册中找到。

 4.2.1写一个字节

写1个字节数据

,这里使用等待应答来判断是否接收到数据

void AT24CXX_WriteOneByte(u8 WriteAddr, u8 DataToWrite)
{
    IIC_Start();  
    IIC_Send_Byte(0xA0); // 发送写命令到AT24C02,
    IIC_Wait_Ack();
    IIC_Send_Byte(WriteAddr); // 直接发送8位地址
    IIC_Wait_Ack();
    IIC_Send_Byte(DataToWrite); // 发送要写入的数据
    IIC_Wait_Ack();
    IIC_Stop(); // 产生一个停止条件
    delay_ms(10); // 等待操作完成
}
 4.3 AT24C02读数据

  1. 发送开始信号 (IIC_Start()):通过SDA和SCL信号发出I2C总线开始通信的信号。

  2. 发送设备地址和写入标志:向总线上发送AT24C02的写入地址。

  3. 等待应答 (IIC_Wait_Ack()):等待EEPROM设备发送应答信号,确认地址接收成功。

  4. 发送内部地址:发送要写入数据的内部地址。由于AT24C02的地址只有8位,所以直接发送8位地址即可。

  5. 再次等待应答:发送内部地址后,再次等待EEPROM设备的应答。(这5步操作,就是前面的写入操作)

  6. 发送重复开始信号 (IIC_Restart()):在发送完内部地址并接收到应答之后,发送重复开始信号来重新初始化总线,但这次是为了读取数据。

  7. 发送设备地址和读取标志:发送AT24C02的读取地址,这里是0xA1

  8. 等待应答:再次等待EEPROM设备发送应答信号,确认准备接收读取命令。

  9. 读取数据:从EEPROM读取所需的数据字节。

  10. 等待应答:主机向EEPROM设备发送非应答信号

  11. 发送停止信号 (IIC_Stop()):通过SDA和SCL信号发出I2C总线停止通信的信号。

  12. 返回读取的数据:将读取到的数据作为函数的返回值。

4.3.1 读1个字节

读1个字节

#include <stdint.h> // 包含标准整数类型定义

// 假设这些函数已经在您的系统中定义
void IIC_Start();
void IIC_Send_Byte(uint8_t data);
uint8_t IIC_Read_Byte(int ack); // 接收一个字节并可选择发送ACK
void IIC_Wait_Ack();
void IIC_Stop();

// 从AT24C02读取一个字节的函数
uint8_t AT24CXX_ReadOneByte(uint8_t ReadAddr)
{
    uint8_t temp = 0; // 用于存储读取的数据

    IIC_Start(); // 发送开始信号
    IIC_Send_Byte(0xA0); // 发送写入命令到AT24C02的写入地址
    IIC_Wait_Ack(); // 等待应答
    IIC_Send_Byte(ReadAddr); // 发送8位地址
    IIC_Wait_Ack(); // 等待应答

    IIC_Start(); // 重新发送开始信号,准备进入接收模式
    IIC_Send_Byte(0xA1); // 发送读取命令到AT24C02的读取地址
    IIC_Wait_Ack(); // 等待应答

    temp = IIC_Read_Byte(0); // 读取一个字节并发送NACK
    IIC_Stop(); // 发送停止条件

    return temp; // 返回读取的数据
}

理解 

进阶(7地址 ;9位数据)

一.使用STM32与NAU8822芯片(软件IIC)进行通信

二.使用STM32与NAU8822芯片(硬件IIC)进行通信

三.使用逻辑分析仪进行时序分析

五.NAU8822 软件IIC

NAU8822是一款由Nuvoton Technology Corporation生产的24位立体声音频编解码器(codec),具备扬声器驱动器。这款芯片专为便携式和通用音频应用设计,有兴趣可以了解下。这里的SCLK和SDIO默认在stm32开发板已经接上引脚了。

5.1 软件IIC

软件IIC(Inter-Integrated Circuit)指的是通过软件编程来模拟I2C通信协议的过程。

I2C是一种广泛使用的串行通信协议,允许多个设备通过两根线(数据线SDA和时钟线SCL)在同一总线上进行通信。软件IIC不需要特定的硬件I2C接口,而是通过GPIO(通用输入输出)引脚来手动控制这些通信线,实现数据的发送和接收。

5.1.1 NAU8822 写数据

简单翻译一下:一个写操作包括一个两字节的指令,后面跟着一个或多个数据字节。写操作需要一个START条件,然后是 一个R/W=0的有效设备地址字节、一个有效的控制地址字节、一个数据字节和一个STOP条件。 NAU8822被永久编程为“0011010”作为设备地址。如果设备地址匹配此值,NAU8822将响应预期的ACK 信号,因为它接受正在传输到它的数据。

NAU8822与AT24C02跟STM32进行通信的原理是差不多的,都是以字节位单位(8bit)进行传输再接应答。

注意:NAU8822它传输的是9位数据,所以控制寄存器地址值需要左移一位,否则会出问题。

以下是NAU8822的写数据

步骤:3、5、7主机等待应答 (注意:写数据和后面介绍的读数据有区别)

void NAU88C22_Write(uint8_t address,uint16_t data)
{
	
	// address是控制寄存器一共7位;data是9位数据
	//把address左移1位和data的最高位进行拼接,传送1字节(8位)数据
	uint8_t regaddress = (address<<1)|((data>>8)&0x01);
	
	
	// 1.发送起始信号
	MyI2C_Start();
	
	// 2.主机发送设备地址写入标志
	MyI2C_SendByte(NAU88C22_ADDRESS_W);
	
	// 3.主机等待从机的应答
	MyI2C_ReceiveAck();
	
	// 4.主机发送控制寄存器地址+data的最高1位数据
	MyI2C_SendByte(regaddress);
	
	// 5.主机等待从机的应答
	MyI2C_ReceiveAck();
	
	// 6.主机发送data的8位数据
	MyI2C_SendByte((uint8_t)data);
	
	// 7.主机等待从机的应答
	MyI2C_ReceiveAck();
	
	// 8.发送停止信号
	MyI2C_Stop();
	
	
	
}

5.1.2 NAU8822 读数据

        一个Read操作由一个三字节的Write指令和一个或多个数据字节的Read指令组成。总线主机启动操作,发出以下 序列:START条件、R/W位设置为“0”的设备地址字节和控制寄存器地址字节。这指示从设备要访问其控制寄存器中的哪一个。

        NAU8822以“0011010”作为其设备地址进行永久编程。如果设备地址与此值匹配,NAU8822将响应预期的 ACK信号,因为它接受传输到它的控制寄存器地址。在此之后,主机发送第二个START条件,以及相同设备地址的第二个实例化,但现在R/W=1。

        在再次识别其设备地址后,NAU8822发送一个ACK,随后是一个两个字节的值,其中包含来自NAU8822内所选控制寄存器的9位数据。包含来自NAU8822的MSB信息的字节中未使用的位由NAU8822输出为零。

        在此阶段,主机使用从NAU8822传输的每个字节生成ACK信令。如果没有来自主控的STOP信号,NAU8822将 在内部自动增加目标控制寄存器地址,然后为序列中的下一个寄存器输出两个数据字节。

        只要主服务器继续发出ACK信令,这个过程就会继续。如果NAU8822内部索引的控制寄存器地址达到0x7F值 (十六进制),并且该寄存器的值被输出,则索引将滚动到0x00。数据字节将继续输出,直到主服务器通过发出 STOP条件终止读取操作。

以下是NAU8822的读数据

注意:以下步骤

步骤:3 、5 、8 主机等待应答

步骤:10 主机发送应答

步骤12 主机发送非应答(不应答)

uint16_t NAU88C22_Read(uint8_t address)
{
	uint8_t hightdata;
	uint8_t lowdata;
	uint16_t data;
	
	// 1.发送起始信号
	MyI2C_Start();
	
	// 2.主机发送设备地址写入标志
	MyI2C_SendByte(NAU88C22_ADDRESS_W);
	
	// 3.主机等待从机的应答
	MyI2C_ReceiveAck();
	
	// 4.发送控制寄存器地址,左移一位
	MyI2C_SendByte(address<<1);
	
	// 5.主机等待从机的应答
	MyI2C_ReceiveAck();
	
	// 6.发送起始信号
	MyI2C_Start();
	
	// 7.发送设备地址读取标志
	MyI2C_SendByte(NAU88C22_ADDRESS_R);
	
	// 8.主机等待从机的应答
	MyI2C_ReceiveAck();
	
	// 9.发送一个字节(8位数据)
	hightdata = MyI2C_ReceiveByte();
	
	// 10.主机发送应答
	MyI2C_SendAck(0);
	
	// 11.读取一个字节(8位数据)
	lowdata = MyI2C_ReceiveByte();
	
	// 12.主机不应答(主机发送非应答)
	MyI2C_SendAck(1);
	
	// 13.发送停止信号
	MyI2C_Stop();
	
	//高8位和低8位拼接,返回一个16位的数据
	//(这里面有效的数据是低9位,高7位被NAU8822自动清0了)
	data = (hightdata<<8)|lowdata;
  return data;
}

5.2 逻辑分析仪(软件)IIC

使用逻辑分析仪测试PB7与PB6端口。

步骤:

1.先使用复位寄存器,初始化一下NAU8822;

2.接着循环写入和读取NAU88C22_ADC_Control寄存器的值。

3.查看读写时序,验证IIC时序是否正确,这里使用ADC_Control的寄存器进行测试

ADC_Control的寄存器地址为:0X0E

ADC_Control它的数据9位(8---0)我们通过控制它不同的位,来调整不同的功能:

ADC_Control寄存器的第2位不能赋值,赋值1会失败,因为它是保留位:为0.        

int main(void)
{
	
	//初始化
   NAU88C22_Init();
	//复位,向NAU88C22_Software_Reset输入任意值,即可完成NAU8822芯片复位
   NAU88C22_Write(NAU88C22_Software_Reset,0x11);
	
	while (1)
	{
		NAU88C22_Write(NAU88C22_ADC_Control,0xFFFF);
		Delay_ms(1);
		NAU88C22_Read(NAU88C22_ADC_Control);
		Delay_ms(3);
		

	}
}
5.2.1 写数据时序

写数据时序如下:

向控制寄存器ADC_Control写入0xFFFF(虽然写入了32位,但只有低9位:0x1FF有效)

以下是该写数据函数的时序图:

NAU88C22_Write(NAU88C22_ADC_Control,0xFFFF);

5.2.2 读数据时序

读数据时序

读数据函数时序如下:

NAU88C22_Read(NAU88C22_ADC_Control);

这里不好放大

5.2.3 写读时序对比

写数据和读数据进行对比:

写数据:

读数据:

5.1.4 注:myi2c代码 

可以参考江协科技的IIC实验中的myi2c

void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);




/*引脚配置层*/

/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_6, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C写SDA引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
  */
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_7, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);		//读取SDA电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}

/**
  * 函    数:I2C初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
  */
void MyI2C_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//将PB10和PB11引脚初始化为开漏输出
	
	/*设置默认电平*/
	GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

/*协议层*/

/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);							//拉低SDA,确保SDA为低电平
	MyI2C_W_SCL(1);							//释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));	//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		MyI2C_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDA
		MyI2C_W_SCL(0);						//拉低SCL,主机开始发送下一位数据
	}
}

/**
  * 函    数:I2C接收一个字节
  * 参    数:无
  * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位
	{
		MyI2C_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDA
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}	//读取SDA数据,并存储到Byte变量
														//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
		MyI2C_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte;							//返回接收到的一个字节数据
}

/**
  * 函    数:I2C发送应答位
  * 参    数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
  * 返 回 值:无
  */
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);					//主机把应答位数据放到SDA线
	MyI2C_W_SCL(1);							//释放SCL,从机在SCL高电平期间,读取应答位
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
}

/**
  * 函    数:I2C接收应答位
  * 参    数:无
  * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
  */
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;							//定义应答位变量
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	MyI2C_W_SCL(1);							//释放SCL,主机机在SCL高电平期间读取SDA
	AckBit = MyI2C_R_SDA();					//将应答位存储到变量里
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
	return AckBit;							//返回定义应答位变量
}

 六.NAU8822硬件IIC

6.1 硬件IIC

硬件IIC:由微控制器或特定芯片内置的I2C硬件模块实现。这些硬件模块通常包含有专用的寄存器和逻辑电路,能够自动处理I2C协议中的时钟同步、数据传输、应答和错误检测等功能。

6.1.1 I2C功能描述

I2C模块接收和发送数据,并将数据从串行转换成并行,或并行转换成串行。可以开启或禁止中 断。接口通过数据引脚(SDA)和时钟引脚(SCL)连接到I 2 C总线。允许连接到标准(高达100kHz)或 快速(高达400kHz)的I 2 C总线。

6.1.2 模式选择 

接口可以下述4种模式中的一种运行:

● 从发送器模式

● 从接收器模式

● 主发送器模式

● 主接收器模式

6.2 I2C主模式选择 

在主模式时,I 2 C接口启动数据传输并产生时钟信号。串行数据传输总是以起始条件开始并以停 止条件结束。当通过START位在总线上产生了起始条件,设备就进入了主模式。

6.2.1 主发送器模式

写操作,相当于前面软件IIC中的NAU88C22_Write

在发送了地址和清除了ADDR位后, 主设备通过内部移位寄存器将字节从DR寄存器发送到SDA 线上。

主设备等待,直到TxE被清除,(见图245的EV8)。

当收到应答脉冲时:

● TxE位被硬件置位,如果设置了INEVFEN和ITBUFEN位,则产生一个中断。 如果TxE被置位并且在上一次数据发送结束之前没有写新的数据字节到DR寄存器,则BTF被硬 件置位,在清除BTF之前I 2 C接口将保持SCL为低电平;读出I2C_SR1之后再写入I2C_DR寄存器 将清除BTF位。

关闭通信

在DR寄存器中写入最后一个字节后,通过设置STOP位产生一个停止条件(见图245的EV8_2), 然后I 2 C接口将自动回到从模式(M/S位清除)。

注: 当TxE或BTF位置位时,停止条件应安排在出现EV8_2事件时。

6.2.2 硬件IIC 写数据 

主发送器模式:

以下是NAU8822的写时序图

主发送模式




void NAU88C22_Hardware_Write(uint8_t address,uint16_t data)
{

	
	uint8_t addressdata = (address<<1)|((data>>8)&0x01);
	
	I2C_GenerateSTART(I2C1, ENABLE);										//硬件I2C生成起始条件
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C1, NAU88C22_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C1, addressdata);											//硬件I2C发送寄存器地址
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING);			//等待EV8
	

	
	I2C_SendData(I2C1, (uint8_t)data);												//硬件I2C发送数据
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2
	
	I2C_GenerateSTOP(I2C1, ENABLE);											//硬件I2C生成终止条件
	
	
}


void NAU8822_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;									//给定超时计数时间
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)	//循环等待指定事件
	{
		Timeout --;										//等待时,计数值自减
		if (Timeout == 0)								//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;										//跳出等待,不等了
		}
	}
}
6.2.3 主接收器模式

读操作,相当于前面软件IIC中的NAU88C22_Read

在发送地址和清除ADDR之后,I 2 C接口进入主接收器模式。在此模式下,I 2 C接口从SDA线接收 数据字节,并通过内部移位寄存器送至DR寄存器。在每个字节后,I 2 C接口依次执行以下操作:

● 如果ACK位被置位,发出一个应答脉冲。

● 硬件设置RxNE=1,如果设置了INEVFEN和ITBUFEN位,则会产生一个中断(见图246的 EV7)。

如果RxNE位被置位,并且在接收新数据结束前,DR寄存器中的数据没有被读走,硬件将设置 BTF=1,在清除BTF之前I 2 C接口将保持SCL为低电平;读出I2C_SR1之后再读出I2C_DR寄存 器将清除BTF位。

关闭通信

主设备在从从设备接收到最后一个字节后发送一个NACK。接收到NACK后,从设备释放对SCL 和SDA线的控制;主设备就可以发送一个停止/重起始条件。

● 为了在收到最后一个字节后产生一个NACK脉冲,在读倒数第二个数据字节之后(在倒数第 二个RxNE事件之后)必须清除ACK位。

● 为了产生一个停止/重起始条件,软件必须在读倒数第二个数据字节之后(在倒数第二个 RxNE事件之后)设置STOP/START位。

● 只接收一个字节时,刚好在EV6之后(EV6_1时,清除ADDR之后)要关闭应答和停止条件的 产生位。 在产生了停止条件后,I 2 C接口自动回到从模式(M/SL位被清除)。

6.2.4 硬件IIC 读数据

主接收模式:

下图是NAU8822的读时序图

主接收

这里的主接收器传送要配合前面的主发送器传送才是能把完整的读操作

uint16_t NAU88C22_Hardware_Read(uint8_t address)
{
	uint8_t hightdata;
	uint8_t lowdata;
	uint16_t data;
	

	
	I2C_GenerateSTART(I2C1, ENABLE);										//硬件I2C生成起始条件
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C1, NAU88C22_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C1, address<<1);											//硬件I2C发送寄存器地址
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2EV8
	
	
	I2C_GenerateSTART(I2C1, ENABLE);										//硬件I2C生成起始条件
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C1,NAU88C22_ADDRESS,I2C_Direction_Receiver);//硬件I2C发送从机地址,方向为接收
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);		//等待EV6
	
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED);				//等待EV7
	hightdata = I2C_ReceiveData(I2C1);											//接收数据寄存器
	
	
	I2C_AcknowledgeConfig(I2C1, DISABLE);									//在接收最后一个字节之前提前将应答失能
	I2C_GenerateSTOP(I2C1, ENABLE);											//在接收最后一个字节之前提前申请停止条件
	
	NAU8822_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED);				//等待EV7
	lowdata = I2C_ReceiveData(I2C1);											//接收数据寄存器
	
	
	I2C_AcknowledgeConfig(I2C1, ENABLE);									//将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
	
	
	data = (hightdata<<8)|lowdata;

   return data;
}



void NAU8822_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;									//给定超时计数时间
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)	//循环等待指定事件
	{
		Timeout --;										//等待时,计数值自减
		if (Timeout == 0)								//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;										//跳出等待,不等了
		}
	}
}

6.3 逻辑分析仪(硬件)IIC

6.3.1 写时序

写时序

NAU88C22_Hardware_Write(NAU88C22_ADC_Control,0xFFFF);

6.3.2 读时序 

读时序

NAU88C22_Hardware_Read(NAU88C22_ADC_Control);

 以上就是本期补齐的内容,欢迎参考指正,如有不懂,欢迎评论或私信出下期!!! 

  • 22
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

锻炼²

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值