【stm32_8】IIC内部集成电路——IIC的时序、利用IO口模拟IIC的时序、IIC通信器件的读写使用、半导体存储器的基本概述

目录

内部集成电路

IIC的基本概念

引脚说明

通信速率

[通信过程 / 时序](#通信过程 / 时序)

(1)空闲状态

(2)开始信号

(3)数据传输

(4)应答信号

(5)停止信号

器件地址

利用IO口模拟IIC时序的程序设计

IIC初始化

IIC开始信号

IIC发送字节

判断从机是否应答

IIC读取字节

决定主机是否要应答

IIC的停止信号

半导体存储器

应用IIC使用AT24C02存储芯片

AT24C02的说明

AT24C02的基本说明

引脚接线、器件地址

存储地址

指令描述和指令编写

AT24C02字节写入的指令

AT24C02页写入指令

确认轮询

AT24C02的当前地址读指令

AT24C02的随机地址读指令

AT24C02顺序读指令

AT24C02程序设计


内部集成电路

IIC的基本概念

++++内部集成电路(Inter Integrated Circuit)的简称叫做IIC或者I++++ ++++2++++ ++++C++++ ,是一种半双工同步**** 通信的串行通信接口。

使用IIC的目的:给MCU和外围芯片提供更简单的交互方式。

作用:IIC总线接口用来和MCU,以及利用IIC接口通信的传感器进行通信。

引脚说明

IIC总线只需要两根引脚就可以实现通信,++++一根是数据线(SDA++++ Serial Data),另++++一根是时钟线(SCL++++ Serial Clock)。

所有通过IIC接口通信的外围器件都挂载在IIC总线上,通过这种机制就可以实现多机通信。

可见,++++外围器件的时钟线SCL和数据线SDA都挂载在IIC总线(由主控芯片提供)++++ ,并且**++++在空闲状++++** ++++下所有器件的时钟线(SCL)和数据线(SDA)都++++ ++++被总线的上拉电阻拉高++++ ,这样就可以把SDA引脚和SCL引脚设置为开漏模式即可 ,好处是防止短路(比较SDA输出一个高电平,外围器件输出一个低电平,就会造成短路)。

空闲时总线下的所有外围器件和主控提供的这两个IO口都是高电平。

上拉电阻在空闲着状态下把引脚电平拉高,而在使用IIC总线接口的时候把IO口设置为开漏。

通信速率

IIC总线支持不同的通信速率,但是一般常用的标准速率100KHZ,但是有的外围器件可以支持高达400KHZ的通信速率,而由于IIC总线是半双工通信,所以同一时刻只能接收或者发送,也就是说IIC总线一般是为了控制,不适合作为大量数据传输的接口。

IIC是半双工通信,数据收发很麻烦,所以IIC强调的是控制。

通信过程 / 时序

主器件和从器件建立通信的顺序:

1.主器件发送开始信号

2.主器件向从器件发送地址

3.主器件等待从器件应答

(1)空闲状态

空闲状态:SDA引脚和SCL引脚均为高电平。(3)

(2)开始信号

开始信号:在SCL引脚保持高电平期间,SDA引脚的电平被拉低。

(3)数据传输

主机发送开始信号后,需要发送从器件的地址(1byte 7bit + 1bit),遵循MSB高位先出

在脉冲信号的高电平期间,数据线SDA的电平是保持锁存的,此时允许收发。

在脉冲信号的低电平期间,数据线SDA的电平是正在变化的,此时不允许收发。

(4)应答信号

IIC接口具有应答机制,接收到数据的一方必须进行应答。

在第九个脉冲信号的高电平期间接收方才可以应答,SDA=0 表示应答 SDA=1 表示未应答。

(5)停止信号

停止信号:在SCL引脚保持高电平期间,把SDA引脚的电平拉高

器件地址

从器件地址一般7bit。

一个器件地址在发送的时候要发送1个字节,这个字节的最低位bit 0用来表示数据传输方向

bit 0 = 0,表示写方向(发送)。主器件 → 从器件

bit 0 = 1,表示读方向(接收)。从器件 → 主器件

利用IO口模拟IIC时序的程序设计

由于MCU内部只有3个IIC接口,所以想要使用IIC接口控制传感器,就必须使用IIC接口对应的引脚,把引脚复用为IIC功能,并且对硬件IIC进行配置和初始化,为了提高可移植性,用户也可以采用IO口模拟IIC时序的方案来控制传感器,该方案不受硬件的限制。

需求:IO口模拟IIC时序

IIC初始化

cpp 复制代码
void IIC_SCLConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN; 
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

void IIC_SDAConfig(GPIOMode_TypeDef GPIO_Mode)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN; 
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//IIC的初始化
void IIC_Config(void)
{
	//1.设置SDA和SCL为输出模式
	IIC_SCLConfig();
	IIC_SDAConfig(GPIO_Mode_OUT);

	//2.确保SDA和SCL处于空闲状态
	SDA_SET(1);
	SCL_SET(1);
	delay_us(5);
}

IIC开始信号

cpp 复制代码
void IIC_Start(void)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SDA_SET(1);
	SCL_SET(1);
	delay_us(5);

	//3.把SDA引脚电平拉低
	SDA_SET(0);
	delay_us(5);

	//4.把SCL引脚电平拉低,此时准备数据
	SCL_SET(0);
	//delay_us(5);

}

IIC发送字节

cpp 复制代码
//IIC的发送字节
void IIC_SendByte(uint8_t Byte)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SCL_SET(0);
	delay_us(5);

	for(int i = 0;i<8;i++)
	{
		if(Byte & 0x80)
		{
			SDA_SET(1);
		}
		else
		{
			SDA_SET(0);
		}
		Byte <<= 1;
		delay_us(5);	

		SCL_SET(1);
		delay_us(5);

		SCL_SET(0);
		delay_us(5);

	}
}

判断从机是否应答

cpp 复制代码
//判断从机是否应答
bool  IIC_IsSlaveACK(void)
{
	bool ack;

	//1.设置SDA引脚为输入模式
	IIC_SDAConfig(GPIO_Mode_IN);

	SCL_SET(0);
	delay_us(5);

	SCL_SET(1);
	delay_us(5);

	if(SDA_READ)
	{
		ack = false;
	}
	else
	{
		ack = true;
	}

	SCL_SET(0);
	delay_us(5);

	return ack;
}

IIC读取字节

cpp 复制代码
//IIC的读取字节
uint8_t IIC_ReadByte(void)
{
	uint8_t i,data = 0;

	//1.设置SDA引脚为输入模式
	IIC_SDAConfig(GPIO_Mode_IN);

	SCL_SET(0);
	delay_us(5);

	for(i = 0;i<8;i++)
	{
		SCL_SET(1);
		delay_us(5);

		data <<= 1;
		data |= SDA_READ;
		//delay_us(5);

		SCL_SET(0);
		delay_us(5);
	}
	return data;
}

决定主机是否要应答

cpp 复制代码
//ack=1 表示不应答  ack=0 表示要应答
void IIC_IsACK(uint8_t ack)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SCL_SET(0);
	delay_us(5);

	if(ack)
	{
		SDA_SET(1);
	}
	else
	{
		SDA_SET(0);
	}
	delay_us(5);

	SCL_SET(1);
	delay_us(5);

	SCL_SET(0);
	delay_us(5);
}

IIC的停止信号

cpp 复制代码
//IIC的停止信号
void IIC_Stop(void)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SDA_SET(0);
	SCL_SET(0);
	delay_us(5);

	SCL_SET(1);
	delay_us(5);

	SDA_SET(1);
	delay_us(5);
}

半导体存储器

|--------|-----------------------------------------|
| ROM | 只读存储器 |
| PROM | 可编程只读存储器,,采用熔丝工艺,只能写一次 |
| EPROM | 可擦除可编程只读存储器,可以多次擦除写入,擦除必须通过强烈紫外线照射 |
| EEPROM | 电可擦除可编程的只读存储器,擦除写入的次数变多,但是制作工艺复杂,所以容量小 |
| Flash | 快闪存储器,简称闪存,制作工艺简单,所以成本低,容量大,可靠性不如EERPOM |

应用IIC使用AT24C02存储芯片

AT24C02的说明

AT24C02的基本说明

  • 属于EEPROM(电可擦除可编程的只读存储器)
  • 采用3.3V或者5V
  • AT24C02存储IC的内部容量是2048bit,等于256字节
  • 双线制串行接口 IIC
  • 通信速率:400kHz
  • 具有写保护机制
  • 采用分页机制,1编程页=8Byte,所以AT24C02具有32页
  • 写入字节时需要等待5ms
  • 使用寿命:100万次,数据存储可以达到100年

可知,AT24C02 存储 IC一共有 2048bit,容量是 256 字节,存储IC是采用分页机制,所以 AT24C02 一共有 32 个可编程页,每个可编程页有 8个字节。

引脚接线、器件地址

存储地址

可知,AT4C02 一共有 256个字节的存储单元,每个存储单元都有独立的存储地址,存储 IC 的存储单元的地址范围是 0x00~0xFF,由于存储 IC采用了分页机制,所以把 256 个字节分为 32页,每页8个字节,并且AT24C02支持页写入指令,所以使用该指令时需要提供可编程页的地址,用户可以计算:0x00、0x08、0x10。

指令描述和指令编写

AT24C02字节写入的指令

cpp 复制代码
//AT24C02字节写入的指令
bool AT24C02_ByteWrite(uint8_t Addr,uint8_t Byte)
{
	//字节写入
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
		SendComputerString("Master write Byte is fail\r\n");
		return false;
	}
	
	//发送写入地址
	IIC_SendByte(Addr);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("Master write memory address is fail\r\n");
			return false;
	}
	
	//发送1字节
	IIC_SendByte(Byte);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("Master write deviceID is fail\r\n");
			return false;
	}
	
	//IIC的停止信号
	IIC_Stop();
			
	delay_ms(5);
	return true;
}

AT24C02页写入指令

cpp 复制代码
bool AT24C02_PageWrite(uint8_t Addr,char *str)
{
	uint32_t num = strlen((char*)str);
	if(num>8)
	{
		SendComputerString("num of write is so big\r\n");
		return false;
	}
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("CurrentAddressRead_Master write decive address is fail\r\n");
			return false;
	}
	
	//发送写入地址
	IIC_SendByte(Addr);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("CurrentAddressRead_Master write decive address is fail\r\n");
			return false;
	}
	
	while(num--)
	{
			//发送1字节
			IIC_SendByte(*str++);

			//判断从机是否应答
			if(IIC_IsSlaveACK() == false)
			{
				SendComputerString("Master write deviceID is fail\r\n");
				return false;
			}
	}
	
	//IIC的停止信号
	IIC_Stop();
	
	return true;
	
}

确认轮询

AT24C02的当前地址读指令

cpp 复制代码
uint8_t  AT24C02_CurrentAddressRead(void)
{
	uint8_t data = 0;
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA1);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("CurrentAddressRead_Master write decive address is fail\r\n");
			return false;
	}
	
	//IIC的读取字节
	data = IIC_ReadByte();
	
	//主机发送应答信号
	IIC_IsACK(0x01);
	
	//IIC的停止信号
	IIC_Stop();
	
	return data;
}

AT24C02的随机地址读指令

cpp 复制代码
//AT24C02的随机地址读指令
uint8_t  AT24C02_RandomAddressRead(uint8_t DestAddr)
{
	uint8_t data = 0;
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//发送内存地址
	IIC_SendByte(DestAddr);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA1);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//IIC的读取字节
	data = IIC_ReadByte();
	
	//主机发送应答信号
	IIC_IsACK(0x01);
	
	//IIC的停止信号
	IIC_Stop();
	
	return data;
}

AT24C02顺序读指令

可以在一个时序里写入多个字节,直到主机发送的响应是高电平来结束发送。

cpp 复制代码
void AT24C02_SequentialRead(uint8_t DestAddr,char* recvBuff,int num)
{
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//发送内存地址
	IIC_SendByte(DestAddr);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA1);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	while(num--)
	{
		//IIC的读取字节
		*recvBuff++ = IIC_ReadByte();

		//主机发送应答信号
		IIC_IsACK(0x00);
	}
	
	//IIC的停止信号
	IIC_Stop();
}

AT24C02程序设计

需求:利用2个读指令3个写指令对AT24C02存储器进行读写

cpp 复制代码
/**
   ******************************************************************************
   * @file    GPIO/GPIO_IOToggle/main.c 
   * @author  
   * @version 
   * @date    03-August-2026
   * @brief   利用IO口模拟IIC时序控制AT24C02
							USART1_TX -- PA9
							USART1_RX -- PA10
   ******************************************************************************
**/

#include "stm32f4xx.h"  //必须要包含的头文件
#include <string.h>
#include <stdio.h>
#include <stdbool.h>

#define  SDA_SET(n) 	((n)?(GPIO_SetBits(GPIOB, GPIO_Pin_9)) : (GPIO_ResetBits(GPIOB, GPIO_Pin_9)) )
#define  SDA_READ		 	 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9)
#define  SCL_SET(n) 	((n)?(GPIO_SetBits(GPIOB, GPIO_Pin_8)) : (GPIO_ResetBits(GPIOB, GPIO_Pin_8)) )

void delay_us(uint32_t nus);
void delay_ms(uint32_t nms);
void delay_s(uint32_t ns);
void USART1_Config(unsigned int baud);

void IIC_SCLConfig(void);
void IIC_SDAConfig(GPIOMode_TypeDef GPIO_Mode);
//IIC的初始化
void IIC_Config(void);
//IIC的开始信号
void IIC_Start(void);
//IIC的发送字节
void IIC_SendByte(uint8_t Byte);
//判断从机是否应答
bool  IIC_IsSlaveACK(void);
//IIC的读取字节
uint8_t IIC_ReadByte(void);
//ack=1 表示不应答  ack=0 表示要应答
void IIC_IsACK(uint8_t ack);
//IIC的停止信号
void IIC_Stop(void);




//向计算机的串口调试助手发送一个字符串,要求把程序封装为一个接口,该接口专门用于发送字符串。
void SendComputerString(char *string)	//参数里传入一个字符串
{
	while(*string != '\0')
	{
		USART_SendData(USART1, *string++ );
		while(USART_GetFlagStatus(USART1, USART_FLAG_TXE)==RESET);
	}
}

//AT24C02字节写入的指令
bool AT24C02_ByteWrite(uint8_t Addr,uint8_t Byte)
{
	//字节写入
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
		SendComputerString("Master write Byte is fail\r\n");
		return false;
	}
	
	//发送写入地址
	IIC_SendByte(Addr);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("Master write memory address is fail\r\n");
			return false;
	}
	
	//发送1字节
	IIC_SendByte(Byte);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("Master write deviceID is fail\r\n");
			return false;
	}
	
	//IIC的停止信号
	IIC_Stop();
			
	delay_ms(5);
	return true;
}


//AT24C02的随机地址读指令
uint8_t  AT24C02_RandomAddressRead(uint8_t DestAddr)
{
	uint8_t data = 0;
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//发送内存地址
	IIC_SendByte(DestAddr);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA1);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//IIC的读取字节
	data = IIC_ReadByte();
	
	//主机发送应答信号
	IIC_IsACK(0x01);
	
	//IIC的停止信号
	IIC_Stop();
	
	return data;
}

uint8_t  AT24C02_CurrentAddressRead(void)
{
	uint8_t data = 0;
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA1);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("CurrentAddressRead_Master write decive address is fail\r\n");
			return false;
	}
	
	//IIC的读取字节
	data = IIC_ReadByte();
	
	//主机发送应答信号
	IIC_IsACK(0x01);
	
	//IIC的停止信号
	IIC_Stop();
	
	return data;
}

bool AT24C02_PageWrite(uint8_t Addr,char *str)
{
	uint32_t num = strlen((char*)str);
	if(num>8)
	{
		SendComputerString("num of write is so big\r\n");
		return false;
	}
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("CurrentAddressRead_Master write decive address is fail\r\n");
			return false;
	}
	
	//发送写入地址
	IIC_SendByte(Addr);
	
	//判断从机是否应答
	if(IIC_IsSlaveACK() == false)
	{
			SendComputerString("CurrentAddressRead_Master write decive address is fail\r\n");
			return false;
	}
	
	while(num--)
	{
			//发送1字节
			IIC_SendByte(*str++);

			//判断从机是否应答
			if(IIC_IsSlaveACK() == false)
			{
				SendComputerString("Master write deviceID is fail\r\n");
				return false;
			}
	}
	
	//IIC的停止信号
	IIC_Stop();
	
	return true;
	
}

void AT24C02_SequentialRead(uint8_t DestAddr,char* recvBuff,int num)
{
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA0);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//发送内存地址
	IIC_SendByte(DestAddr);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	//IIC的开始信号
	IIC_Start();
	
	//发送设备地址
	IIC_SendByte(0xA1);
	
	//判断从机是否应答
	IIC_IsSlaveACK();
	
	while(num--)
	{
		//IIC的读取字节
		*recvBuff++ = IIC_ReadByte();

		//主机发送应答信号
		IIC_IsACK(0x00);
	}
	
	//IIC的停止信号
	IIC_Stop();
}

int main()
{
	uint8_t recv = 0;
	uint8_t buff[32]={0};
	char recvbuff[8]={0};
	
	USART1_Config(9600);
	IIC_Config();
	
	//AT24C02_ByteWrite(0x01,'B');
	//AT24C02_ByteWrite(0x00,'A');
	//AT24C02_ByteWrite(0xFF,'Z');
	//AT24C02_PageWrite(0x10,"lion");
	//recv = AT24C02_CurrentAddressRead();		//测试------当前地址访问(在写入的地址后面偏移一字节)
	
	//recv = AT24C02_RandomAddressRead(0x12);		测试------随机访问
	AT24C02_SequentialRead(0x10,recvbuff,4);
	
	sprintf((char*)buff,"read data:%s\r\n",recvbuff	);
	SendComputerString((char*)buff);
	SendComputerString((char*)&recvbuff);
	
	while(1)
	{
		
	}
}

void USART1_IRQHandler(void)
{
	/* USART in Receiver mode */	//收到数据的时候发生触发
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//串口的RX检测到数据
	{
			uint8_t data = 0;
			/* Receive Transaction data */
			data = USART_ReceiveData(USART1);//单片机接收数据
		
		//由于当前单片机没有屏幕显示接收到的数据,只能把接收到的数据返回给计算机
		USART_SendData(USART1, data);
	}
}

void IIC_SCLConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN; 
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

void IIC_SDAConfig(GPIOMode_TypeDef GPIO_Mode)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_DOWN; 
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//IIC的初始化
void IIC_Config(void)
{
	//1.设置SDA和SCL为输出模式
	IIC_SCLConfig();
	IIC_SDAConfig(GPIO_Mode_OUT);

	//2.确保SDA和SCL处于空闲状态
	SDA_SET(1);
	SCL_SET(1);
	delay_us(5);
}

void IIC_Start(void)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SDA_SET(1);
	SCL_SET(1);
	delay_us(5);

	//3.把SDA引脚电平拉低
	SDA_SET(0);
	delay_us(5);

	//4.把SCL引脚电平拉低,此时准备数据
	SCL_SET(0);
	//delay_us(5);

}

//IIC的发送字节
void IIC_SendByte(uint8_t Byte)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SCL_SET(0);
	delay_us(5);

	for(int i = 0;i<8;i++)
	{
		if(Byte & 0x80)
		{
			SDA_SET(1);
		}
		else
		{
			SDA_SET(0);
		}
		Byte <<= 1;
		delay_us(5);	

		SCL_SET(1);
		delay_us(5);

		SCL_SET(0);
		delay_us(5);

	}
}

//判断从机是否应答
bool  IIC_IsSlaveACK(void)
{
	bool ack;

	//1.设置SDA引脚为输入模式
	IIC_SDAConfig(GPIO_Mode_IN);

	SCL_SET(0);
	delay_us(5);

	SCL_SET(1);
	delay_us(5);

	if(SDA_READ)
	{
		ack = false;
	}
	else
	{
		ack = true;
	}

	SCL_SET(0);
	delay_us(5);

	return ack;
}


//IIC的读取字节
uint8_t IIC_ReadByte(void)
{
	uint8_t i,data = 0;

	//1.设置SDA引脚为输入模式
	IIC_SDAConfig(GPIO_Mode_IN);

	SCL_SET(0);
	delay_us(5);

	for(i = 0;i<8;i++)
	{
		SCL_SET(1);
		delay_us(5);

		data <<= 1;
		data |= SDA_READ;
		//delay_us(5);

		SCL_SET(0);
		delay_us(5);
	}
	return data;
}


//ack=1 表示不应答  ack=0 表示要应答
void IIC_IsACK(uint8_t ack)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SCL_SET(0);
	delay_us(5);

	if(ack)
	{
		SDA_SET(1);
	}
	else
	{
		SDA_SET(0);
	}
	delay_us(5);

	SCL_SET(1);
	delay_us(5);

	SCL_SET(0);
	delay_us(5);
}

//IIC的停止信号
void IIC_Stop(void)
{
	//1.设置SDA引脚为输出模式
	IIC_SDAConfig(GPIO_Mode_OUT);

	SDA_SET(0);
	SCL_SET(0);
	delay_us(5);

	SCL_SET(1);
	delay_us(5);

	SDA_SET(1);
	delay_us(5);
}

void delay_us(uint32_t nus)
{
	SysTick->CTRL = 0; 									// 关闭systick的控制状态寄存器------该寄存器的bit0置0
	SysTick->LOAD = ( 21 * nus - 1); 		// systick的重载寄存器------计数时长 = 计数周期 * 计数次数
	SysTick->VAL = 0; 									// 清空systick的当前数值寄存器
	SysTick->CTRL = 1; 									// 使能systick的控制状态寄存器------该寄存器的bit0置1
	while ((SysTick->CTRL & 0x00010000)==0);// 递减计数到了则控制状态寄存器的bit16为1,计数次数没到则bit16为0.
	SysTick->CTRL = 0; 									//关闭systick的控制状态寄存器
}

void delay_ms(uint32_t nms)
{
	SysTick->CTRL = 0; 									// 关闭systick的控制状态寄存器------该寄存器的bit0置0
	SysTick->LOAD = ( 21*1000*nms - 1); // systick的重载寄存器------计数时长 = 计数周期 * 计数次数
	SysTick->VAL = 0; 									// 清空systick的当前数值寄存器
	SysTick->CTRL = 1; 									// 使能systick的控制状态寄存器------该寄存器的bit0置1
	while ((SysTick->CTRL & 0x00010000)==0);// 递减计数到了则控制状态寄存器的bit16为1,计数次数没到则bit16为0.
	SysTick->CTRL = 0; 									//关闭systick的控制状态寄存器
}

void delay_s(uint32_t ns)
{
	while(ns--)
	{
		delay_ms(500);
		delay_ms(500);
	}
}

void USART1_Config(unsigned int baud)
{
		USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//Enable peripheral clock using the following functions
	/* Enable USART clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	//According to the USART mode, enable the GPIO clocks
	/* Enable GPIO clock */
	RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA , ENABLE);
	
	//Peripheral's alternate function:
	/* Connect USART pins to AF7 */
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	/* Program the Baud Rate, Word Length , Stop Bit, Parity, Hardware 
     flow control and Mode(Receiver/Transmitter) using the USART_Init()
     function. */
	USART_InitStructure.USART_BaudRate = baud;									//波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;			//停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;					//无校验
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无流控
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式
	USART_Init(USART1, &USART_InitStructure);
	
	//Enable the NVIC and the corresponding interrupt
	/* NVIC configuration */
	/* Configure the Priority Group to 2 bits */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	/* Enable the USARTx Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//接收缓冲区为空的时候使用中断
	
	// Enable the USART using the USART_Cmd() function.
	USART_Cmd(USART1, ENABLE);
}

IIC可以理解为IIC总线,

它的时序对应的叫IIC协议,使用这种协议进行通信的时候,可以叫IIC接口。

IIC接口属于MCU内部的一种硬件资源。

IIC采用半双工 、 同步通信。

⬇ ⬇

只用一根线来收发 有时钟线专门生成脉冲信号

根据上述,共用了2根线。==> 优点:节约资源。

==> 缺点:编写程序麻烦。

IIC中的SCL(时钟总线)和SDA(数据总线)是IIC的总线。

IIC总线是由 主机MCU/主器件 提供的。

传感器/从器件 提供的SDA和SCL不是总线。

从器件需要把自己的SDA和SCL挂载到主器件挂载到主器件提供的总线上。

总线:方便外设的挂载,和对外设进行控制。

①皇上要去谁那,总线把地址发出来,这个地址指令将被总线上挂载的所有从器件收到。

②每个从器件的地址都不一样。

==> 所以每个从器件收到该地址指令后都会做比较,比较 " 地址指令收发和自己的地址一致 "。

  • 不一致:不和主机通信
  • 一致:等待

一个IIC接口可以挂载多个从机,但无法同时和这些从机通信,只能和其一通信。==> 所以stm32f407有3个硬件IIC。

==> 所以主机可以同时使用3个IIC和3个从机通信。

半双工的数据线SDA只有一根,

主机在发送时 传感器是不会响应的。只有传感器收到数据才会响应主机;

而响应主机的时候,主机就不能再输出了。主机给传感器数据时,从机得拿到数据才行。

==> SDA引脚的电平会切换、

SDA引脚的模式会切换(输入/输出模式)

IIC的SCL、SDA引脚的输出类型是开漏!!!

MCU提供2个IO口,作为IIC的总线,把传感器挂载到这两根总线上 ==> 主器件MCU就可以利用SDA这根数据线/信道 和挂载到SDA的传感器通信。由于是同步通信,所以需要确保发送数据之后有能力去接收数据。

SCL对于MCU "主器件向从器件" 提供时钟信号用的。

IIC时序怎么算空闲:

  • SDA、SCL设置为为开漏模式(只能输出低电平),则外部的上拉电阻把他俩拉高。
  • SDA、SCL为推挽,则SDA、SCL需要自己给一个高电平。

谁接收谁就应答

做对比(eg.SPI和IIC)

  • 从引脚数量
  • 通信速率
  • 从器件现在(SPI用片选,IIC用地址)

IIC通信速率

  • 标准100kHz
  • 高速400kHz

SPI通信速率10MHz

==> IIC 强调控制,SPI 强调速率

MCU在主模式下才可以提供时钟。

Q:如何分辨IIC数据传输方向

A:通过发送的器件地址8个bit中bit 0来分析。

bit 0 = 0,表示发送。主器件 → 从器件

bit 0 = 1,表示接收。从器件 → 主器件

IIC中的程序设计使用的是AT24C02芯片,它是EEPROM存储器,不需要像"Flash芯片那样写入之前要先擦除"。

SPI和IIC的区别

SPI和IIC的区别

从引脚数量:SPI使用4个引脚,IIC用2个引脚。

通信速率

  • SPI几十M,所以很多容量比较大的存储IC是SPI接口。

    SPI收发使用2条信道,收发可以同时实现,是全双工通信。

    SPI强调的是效率。

  • IIC收发使用一根信道,半双工通信。IIC强调的是控制。

从器件选择的角度:SPI通过片选引脚选从机,IIC通过地址选从机。

相关推荐
代码地平线1 小时前
【排序】C语言实现八大排序算法(含完整源码与性能测试)
c语言·算法·排序算法
namas88481 小时前
APLC IDE 用户手册
ide·单片机·嵌入式硬件
少司府1 小时前
C++基础入门:vector深度解析(七千字深度剖析)
c语言·开发语言·数据结构·c++·容器·vector·顺序表
Dlrb12112 小时前
C语言-函数传参
c语言·数据结构·算法
草莓熊Lotso3 小时前
【Linux网络】UDP Socket 编程全解析:从回显服务到通用字典服务,从零实现工业级代码
linux·运维·服务器·数据库·c++·单片机·udp
InfinteJustice13 小时前
踩坑分享C 语言文件操作全攻略:从基础读写到随机访问与缓冲区原理
c语言·开发语言·microsoft
fengfuyao98515 小时前
利用 STM32 和 ADS1256 进行高精度数据采集
stm32·单片机·嵌入式硬件
黑白园15 小时前
ADC读取XY二轴操纵杆数据通过I2C_GPIO模拟 控制0.96寸OLED显示
stm32·单片机·嵌入式硬件
一个平凡而乐于分享的小比特16 小时前
还在手动挡写单片机?MicroPython 一脚油门踩进 Python 硬件世界
单片机·嵌入式硬件·micropython