重新开始学stm32(7)IIC通信实验

照上一期结束的时候所说的,今天开始我们就进入通信专题的学习了。通信这一专题的学习是非常重要的,我们在上一期的结尾也强调过了。所以,废话不多说,我们马上进入今天的正题。开始我们通信专题的第一个实验——IIC通信实验。今天我们的实验按照正点原子的实验来进行。内容有的多,文章有的长,请耐心观看!


IIC简介

IIC,英文全称是Inter-Integrated Circuit,是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器与从设备间的主从通信。

IIC总线的构成由数据线SDA时钟线SCL构成,可以发送和接收数据。注意:IIC是为了与低速设备通信而发明的,所以IIC的传输速率是不及我们后面学习的SPI通信的。

IIC总线在传输数据时共有三种类型的信号,分别是:开始信号结束信号应答信号


我们先来详细了解一下这三个信号。

1)开始信号

SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据

作用:开启IIC通信

代码的实现思路就是先把数据线SDA设置成输入模式,把SDA和SCL全部拉高,延时,再把SDA拉低,就实现了开始信号了。

void IIC_Start(void)
{
	SDA_OUT();
	IIC_SDA=1;	  	  
	IIC_SCL=1;
	delay_us(4);
 	IIC_SDA=0;
	delay_us(4);
	IIC_SCL=0;
}	

2)结束信号

SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据

作用:停止IIC通信

  •  开始信号和结束信号都是由主设备产生并发出的。

代码的实现思路同开始信号的思路相似,同样先将SDA和SCL全部拉低,延时,再将SCL和SDA先后拉高,就可以实现结束信号了。

void IIC_Stop(void)
{
	SDA_OUT();
	IIC_SCL=0;
	IIC_SDA=0;
 	delay_us(4);
	IIC_SCL=1;
	delay_us(4);
	IIC_SDA=1;
}

3)应答信号

主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答

  • 应答信号为低电平时,规定为有效应答位(ACK),表示接收器已经成功地接收了该字节
  • 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功在这里插入图片描述

一个字节(8bit)按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。

并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个非应答位。

作用:指示一个字节的数据传输完成

  • 应答信号是由从设备产生并发出的。

代码的实现思路就是先把数据线SDA设置为输出,把数据线SDA和时钟线SCL拉低,最后把SCL拉高一段时间后再拉低,就可以实现应答信号的产生了。

void IIC_Ack(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

如果是非应答信号就是先把数据线SDA设置为输出,把数据线SDA拉高,时钟线SCL拉低,最后把SCL拉高一段时间后再拉低,就可以实现非应答信号的产生了。

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初始化完成后所在的状态就是空闲状态。所以IIC总线在空闲状态下数据线SDA和时钟线SCL都是高电平

因此,我们在初始化完成GPIO后,要把数据线SDA和时钟线SCL相应的IO口设置为高电平。

void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	   
	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);
}

数据有效性

IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

  • 在数据传输中,数据是在时钟线SCL的高电平期间被写入从设备,所以数据的变化要在SCL的低电平期间在这里插入图片描述

数据传输格式和从设备地址选取

数据传送时,输出到SDA线上的每个字节必须是8位,从最高位(MSB)开始到最低位(LSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

IIC总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前要先指定从设备的地址。地址指定的过程和上面数据传输的过程一样, 从设备地址有7位的也有10位的,只不过大多数从设备的地址都是7位的。为了表示数据传输的方向,IIC协议规定再给地址添加一个最低位用来表示接下来数据传输的方向。
  • 0表示主设备向从设备写数据。
  • 1表示主设备向从设备读数据

实际上通常所说的8位地址其实就是7位地址后面加上了一个读写位。


 由图可以看到,在开始信号开启通信后,在传输数据前要先指定从设备的地址,然后再进行数据传输。数据在传输中是一个字节一个字节地传输的,即每8位一个数据包。最后就是结束信号停止通信。

数据格式是每一小包数据由9位bit组成,即每一帧数据由9bit组成。

  • 如果是发送数据,则9bit包含了8bit数据+1bit应答位
  • 如果是发送设备地址,则9bit包含了7bit地址+1bit读写位+1bit应答位

发送一个字节的数据
//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;    //获取相应的数据位,相应位为1则数据线拉高,否则相反
        txd<<=1;
		delay_us(2);   //延时是必须的
		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;
}

IIC发送数据

在这里插入图片描述

在这里插入图片描述

Start:起始信号
DEVICE_ADDRESS:从设备地址,就是7位从机地址
R/W:W(write)为写,R(read)为读
ACK:应答信号
WORD_ADDRESS:从机中对应的寄存器地址
DATA:发送的数据
STOP:终止信号

主机要向从机写数据时:

  1. 主机首先产生START信号,开启传输。
  2. 然后紧跟着发送一个从机地址,这个地址共有7位,紧接着的第8位是数据方 向位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)。
  3. 主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器。
  4. 主机等待从机的应答信号(ACK)。
  5. 当主机收到应答信号时,发送要访问从机的那个地址, 继续等待从机的应答信号。
  6. 当主机收到应答信号时,发送N个字节的数据,继续等待从机的N次应答信号。
  7. 主机产生终止信号,结束传输。

IIC读取数据 

在这里插入图片描述

主机要从从机读数据时: 

  1. 主机首先产生START信号,开启传输。
  2. 然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令。
  3. 主机等待从机的应答信号(ACK)。
  4. 当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号。
  5. 当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据
  6. 主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不在接收数据。
  7. 主机进而产生停止信号,结束传输。

以24C02为例

24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。

对于不同大小的24Cxx,具有不同的从器件地址。24C02为2k容量,也就是对应需要参考图中第一行的内容,就可以知道从器件地址。

在这里插入图片描述

 AT24CXX设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX的EEPROM Board模块中默认为接地。A2~A0为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。

因此

  • 读24C02时,从器件地址为10100000(0xA1)
  • 写20C02时,从器件地址为10100000(0xA0)

我们可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。在地址上对应地修改A2、A1、A0三位即可。


向24C02写数据
  1. 主机先发送一个开始信号(START)启动总线
  2. 接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)
  3. 等待应答信号(ACK)
  4. 发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。
  5. 发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。
  6. 发送结束信号(STOP)停止总线
 向24C02写入一个数据
//在AT24CXX指定地址写入一个数据
//WriteAddr  :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{				   	  	    																 
    IIC_Start();  
	IIC_Send_Byte(0XA0+((WriteAddr/256)<<1));   //发送器件地址和存储地址+写命令
	IIC_Wait_Ack();	   
    IIC_Send_Byte(WriteAddr%256);   //发送存储地址
	IIC_Wait_Ack(); 	 										  		   
	IIC_Send_Byte(DataToWrite);     //发送数据						   
	IIC_Wait_Ack();  		    	   
    IIC_Stop();                     //停止
	delay_ms(10);	 
}
向24C02连续写入数据
//在AT24CXX指定地址写入指定个数数据
//ReadAddr :开始写入的地址 对24c02为0~255
//pBuffer  :数据数组首地址
//NumToRead:要写入数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
	while(NumToWrite--)
	{
		AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
		WriteAddr++;
		pBuffer++;
	}
}

向24C02读数据 
  1. 主机先发送一个开始信号(START)启动总线
  2. 接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)              (注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据)。
  3. 发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。
  4. 重新发送开始信号(START)
  5. 发送设备读操作地址(DEVICE ADDRESS)对E2PROM进行读操作 (0xA1)
  6. E2PROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
  7. 如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线
向24C02读取一个数据
//在AT24CXX指定地址读取一个数据
//ReadAddr:开始读数的地址
//返回值  :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{				  
	u8 temp=0;
    IIC_Start();
	IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址和存储地址+写命令

	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;
}
 向24C02连续读取数据
//在AT24CXX指定地址读取指定个数数据
//ReadAddr :开始读取的地址 对24c02为0~255
//pBuffer  :数据数组首地址
//NumToRead:要读取数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
	while(NumToRead)
	{
		*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);	
		NumToRead--;
	}
} 

今天的内容就到这里,咱们下期SPI通信再见!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值