目录
[通信过程 / 时序](#通信过程 / 时序)
内部集成电路
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通过地址选从机。