IIC简介
IIC物理层
用软件模拟IIC时序
一、空闲状态(初始化):SCL 和SDA都保持高电平
二、开始信号 :SCL为高电平期间,SDA由高电平变为低电平。
三、停止信号:SCL为高电平期间,SDA由低电平变为高电平
四、应答信号:
五、发送一个字节
六、读取一个字节
七、IIC发送数据
八、IIC读取数据
下面以EEPROM AT24C02为例
AT24C02相关知识:
向AT24C02中写数据
从AT24C02中读数据
软件模拟IIC时序过程中遇到的问题
注:
①此文章用来作为个人笔记使用,内容难免有不当之处,如有需要,仅供参考。
②以下内容基于正点原子精英版开发板(STM32F103ZET6)。
IIC简介
IIC(Inter-Integrated Circuit)是一种两线式串行总线(SDA:数据线,SCL:时钟线)。属于同步通信(带同步时钟信号,即发送数据的同时还要发送时钟信号),传送方式为半双工(数据可以双向传输,但在某一时刻只能单向传输)。标准IIC总线传输速率一般为100kbps,高速IIC总线传输速率一般可达400kbps以上,且传输速率比SPI低。 所有要进行IIC通讯的设备直接挂到两条总线上即可。
IIC物理层
-
SDA(Serial data):数据线,用于传输数据。
-
SCL(Serial clock line):时钟线,用于发送时钟信号。
-
上拉电阻:为了避免总线信号的混乱,各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出,如下图所示。那么设备只能输出低电平和高阻态两种状态,设备要想输出高电平必须接上拉电阻 。
-
IIC设备地址: 每一个IIC设备都有一个设备地址,来确保不同设备之间访问的准确性。有的器件地址在出厂时地址就设定好了,用户不可以更改。有的器件例如EEPROM,前四个地址已经确定为1010,后三个地址是由硬件链接确定的,所以一IIC总线最多能连8个EEPROM芯片。
-
IIC的高阻态:IIC设备一般是开漏输出(OD),这种方式能输出低电平和高阻态两种电平。好处是当设备需要读取IIC总线上的数据时,不需要切换到输入模式,在高阻态模式下就能读取IIC总线上的数据。并且开漏输出支持线与功能(可简单理解为支持多个输出口接到一起),而正好IIC总线上需要挂多个设备。
(对IIC总线上设备芯片开漏读写数据的理解:①芯片接收数据时,DATA1OUT和DATA2OUT为低电平,MOS管关闭(高阻态状态),此时DATA1IN和DATA2IN处读到的电平就是SDA线上的电平;②芯片发送数据时,通过DATA1OUT和DATA2OUT切换MOS管导通和关闭,配合上拉电阻,使SDA线切换为高低电平,即发出高低电平。当然,此时其他空闲设备都处于高阻态。)
IIC设备可分为主设备和从设备 ,主设备一般是产生时钟信号、开始信号和停止信号的一方,主设备有时可能不止一个。
用软件模拟IIC时序:
STM32F103ZET6 作为主设备,AT24C02 EEPROM作为从设备
注:依个人理解和总结,模拟IIC时序时其实只有三种情况:①开始信号:SCL为高电平期间,SDA由高电平变为低电平。②停止信号:SCL为高电平时,SDA由低电平变为高电平 ③除去前两种特殊信号,其他都可理解为是基于发送或接收一个位的数据:
单片机发送一个位的数据:发送数据都是在SCL为低电平时准备好数据,在SCL上升沿发送,并在SCL的高电平保持不变(一是防止产生停止信号或开始信号,二是使接受设备有足够时间读取到数据)。当然发送完意味着接收设备要接收一个位,接收设备也是收到一个SCL上升沿时认为发送设备发送了一位数据,因此会读取一次。
单片机接收一个位的数据:也就是从设备芯片需要发送一个位,因此需要提供给从设备芯片一个上升沿时钟信号。此时从设备芯片会主动发送一个位,我们用单片机读取总线上的电平作为一个位即可。对从设备而言,向主设备发送数据时需要收到主设备发送来的一个上升沿时钟信号,才发送一位数据。
数据有效性
IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。因为SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。
一、空闲状态(初始化):SCL 和SDA都保持高电平
//IIC总线 PB6:SCL PB7:SDA
//软件模拟IIC时序可以使用任意两个GPIO口,即使单片机没有IIC功能,也是可以的。
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
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);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);
}
用STM32软件模拟IIC时序时,用于模拟IIC总线的IO口可以配置成推挽输出或开漏输出两种情况。因原子例程中是配置成了推挽输出,因此在自己模拟时序时尝试配置成了开漏输出。两种配置方式的区别见文章末尾问题一。
二、开始信号 :SCL为高电平期间,SDA由高电平变为低电平。
//产生IIC开始信号
void IIC_Start(void)
{
IIC_SCL=1;
IIC_SDA=1;
delay_us(5);
IIC_SDA=0;
delay_us(5);
IIC_SCL=0;
}
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线。
有关开始信号的时序模拟遇到的问题见文章末尾问题三。
三、停止信号:SCL为高电平期间,SDA由低电平变为高电平
void IIC_Stop(void)
{
IIC_SCL=0;
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SDA=1;
delay_us(5);
}
在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。
四、应答信号:
应答信号有应答(ACK)和不应答(NACK)两种。按方向又可以分成单片机对设备芯片应答(或不应答)和设备芯片对单片机应答(或不应答)两种情况。但是设备芯片对单片机的应答信号是芯片本身主动发送的,不需要我们编写代码,因此我们只需要编写三个代码:单片机对设备芯片应答、单片机对设备芯片不应答、以及接收设备芯片对单片机的应答信号。
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
**每发送一个字节(8个bit)**在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。
//单片机对设备芯片的应答ACK信号:可以理解为单片机向从设备发送一位值为0的数据
void IIC_ACK(void)
{
IIC_SCL=0;
IIC_SDA=0; //在SCL为0时准备好数据
delay_us(5);
IIC_SCL=1; //SCL的上升沿发送一位数据
delay_us(5);//在SCL高电平期间保持不变
IIC_SCL=0;
}
//单片机对设备芯片的非应答NADCK信号:可以理解为单片机向从设备发送一位值为1的数据
void IIC_NACK(void)
{
IIC_SCL=0;
IIC_SDA=1; //在SCL为0时准备好数据
delay_us(2);
IIC_SCL=1; //SCL的上升沿发送一位数据
delay_us(5); //在SCL高电平期间保持不变
IIC_SCL=0;
}
//单片机等待接收设备芯片的应答:相当于从设备向单片机发送一位数据
u8 IIC_WaitACK(void)
{
IIC_SCL=0;
IIC_SDA=1; //此处是为了让单片机处于高阻态,以便于单片机能读取SDA线上的电平。
//但是会发现不设置成高阻态程序也能正常运行,原因是即使IIC_SDA=0时单片机不能
//读取到外部电平,此时单片机被MOS管拉低,读到的值会总是为0,单片机会误认为是从机
//发送的应答信号,因此程序也会正常运行。
delay_us(2);
IIC_SCL=1;
delay_us(2); //以上是为了提供给从机一个上升沿时钟信号,从机就会主动发送一位数据
if(READ_SDA==1) //下面判断从机发送过来的是1还是0,也就是判断从机发送的是不应答还是应答信号
{
IIC_Stop(); //如果从机发送过来的是不应答就停止传输数据
return 1;
}
else
{
IIC_SCL=0;
return 0; //如果从机发送过来的是应答信号就继续传输数据
}
}
五、发送一个字节
IIC协议要求数据传输是以字节为单位的(一次传输8位)。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(8位数据位加1位应答位称为一帧)。
//发送一个字节数据:相当于把8位数据一位一位发送过去
//此函数被用来发送设备芯片地址、数据在设备芯片中的存储地址、以及要发送的8位数据
void IIC_SendByte(u8 txd)
{
u8 i;
IIC_SCL=0;
for(i=0;i<8;i++) //先传送高位,再传送低位
{
if(txd&0x80) IIC_SDA=1;
else IIC_SDA=0;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
}
六、读取一个字节
//读取一个字节数据:从设备会在主设备每发送一个时钟上升沿时向主设备发送一位数据
u8 IIC_ReadByte(void)
{
u8 i;
u8 receive=0;
for(i=0;i<8;i++)
{
IIC_SCL=0;
delay_us(2);
IIC_SDA=1;
IIC_SCL=1;
receive<<=1;
if(READ_SDA==1) receive++;
}
return receive;
}
以上函数可以看做是IIC的底层函数,要实现某个功能直接调用以上函数即可。
七、IIC发送数据
流程:开始信号->设备地址和写(7+1位)->应答->数据存放地址->应答->8位要发送的数据->应答->停止信号
1、主机首先产生START信号
2、 然后紧跟着发送一个从机地址,这个地址共有7位,紧接着的第8位是数据方向位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)。这总共8位是作为一个字节一起发送的。
3、主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器
4、这时候主机等待从机的应答信号
5、当主机收到应答信号时,发送要访问从机的那个地址, 继续等待从机的应答信号
6、当主机收到应答信号时,发送1个字节的数据,继续等待从机应答信号(6''这里可以连续发送数据,即重复 “发送一个字节数据+等待从机应答信号”,就可以连续向后续字节地址写入数据(寄存器地址会自增),直到不再发送数据了就可以发送一个停止信号)
7、主机产生停止信号,结束传送过程。
//模拟完整发送一个字节数据时序
//write_addr:设备地址 write_reg:寄存器地址 write_data:要写入的数据
void IIC_SendOneByte(u8 write_addr,u8 write_reg,u8 write_data)
{
IIC_Start();
IIC_SendByte((write_addr<<1)|0);
IIC_WaitACK();
IIC_SendByte(write_reg);
IIC_WaitACK();
IIC_SendByte(write_data);
IIC_WaitACK();
IIC_Stop();
delay_ms(5);
}
//IIC连续发送数据
//addr:设备地址 reg:寄存器地址 len:写入长度 buf:数据区
//返回值:0,正常;其他,错误代码
u8 IIC_Write_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
u8 i;
IIC_Start();
IIC_SendByte((addr<<1)|0);//发送设备地址+写命令
IIC_WaitACK();
IIC_SendByte(reg); //写寄存器地址
IIC_WaitACK();(); //等待应答
for(i=0;i<len;i++)
{
IIC_SendByte(buf[i]); //发送数据
IIC_WaitACK();
}
IIC_Stop();
return 0;
}
八、IIC读取数据
流程:开始信号->设备地址和写->应答->数据存储地址-> 应答->开始信号->设备地址和读->应答->要发送的8位数据->不应答->停止信号
1、主机首先产生START信号
2、然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令,
3、这时候主机等待从机的应答信号(ACK)
4、主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号,
5、当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据,6、这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据。当接收完成后,若主机发送应答信号,就可以继续读数据,如此循环“接收数据+发送应答信号”,就可以连续从后续字节地址读取数据(寄存器地址自增)。直到主机发送非应答信号,表示不在接收数据
7、主机进而产生停止信号,结束传送过程。
//模拟完整读取一个数据时序
//read_addr:设备地址 read_reg:寄存器地址
u8 IIC_ReadOneByte(u8 read_addr,u8 read_reg)
{
u8 temp;
IIC_Start();
IIC_SendByte((addr<<1)|0);
IIC_WaitACK();
IIC_SendByte(read_reg);
IIC_WaitACK();
IIC_Start();
IIC_SendByte((addr<<1)|1);
IIC_WaitACK();
temp=IIC_ReadByte();
IIC_NACK();
IIC_Stop();
return temp;
}
//IIC连续读取数据
//addr:设备地址 reg:要读取的寄存器地址 len:要读取的长度 buf:读取到的数据存储区
//返回值:0,正常;其他,错误代码
u8 IIC_Read_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
IIC_Start();
IIC_SendByte((addr<<1)|0);//发送器件地址+写命令
IIC_WaitACK();
IIC_SendByte(reg); //写寄存器地址
IIC_WaitACK(); //等待应答
IIC_Start();
IIC_SendByte((addr<<1)|1);//发送器件地址+读命令
IIC_WaitACK(); //等待应答
while(len)
{
if(len==1)
{
*buf=IIC_ReadByte();
IIC_NACK();//读数据,发送nACK
}
else
{
*buf=IIC_ReadByte();
IIC_NCK();//读数据,发送ACK
}
len--;
buf++;
}
IIC_Stop(); //产生一个停止条件
return 0;
}
下面以EEPROM AT24C02为例
AT24C02相关知识:
24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。
A0,A1,A2:硬件地址引脚
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线VCC、GND:电源和地
可以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:
芯片的寻址:
AT24C设备地址为如下,前四位固定为1010,A2~A0由管脚电平决定。AT24CXX EEPROM Board模块中默认为接地。A2~A0为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。
也就是说如果是
写24C02的时候,从器件地址为10100000(0xA0);
读24C02的时候,从器件地址为10100001(0xA1)。
片内地址寻址:
芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
硬件连接
向AT24C02中写数据
操作时序:
1、MCU先发送一个开始信号(START)启动总线
2、接着跟上首字节,发送设备地址和写(即0xA0)
3、等待应答信号(ACK)4、发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储在哪
个位置,此刻写的就是哪个地址。
5、等待应答信号(ACK)
6、发送要存储的一个字节的数据,并等待应答信号(ACK)。
7、发送结束信号(STOP)停止总线
注:给EEPROM发送数据时,数据是先发送到页写缓冲区,等5ms之后,才会被转移到非易失区域;在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1
EEPROM的写数据分为字节写数据模式和页写数据模式。字节写就是一个地址一个数据的写(见下述代码段(1));页写是连续写数据,一个地址多个数据的写。但是页写不能跨页,跨页后超出的数据会覆盖原先写入的数据。
页写介绍:在向EEPROM写入多个字节的数据时,如果每写一个字节都要等5ms才能让数据从缓冲区转移到非易失区域,那效率会低太多。为此,出现了EEPROM的分页管理:24C01/02按8字节一个页,24C04/08/16按16个字节一页,24C32/64按32字节一页。这时在同一页连续写入多个字节后,再发送一个停止信号,EEPROM就会把一页的数据一次性从页缓冲区转移到非易失区域。这样就大大提高了效率。因此,如果在连续写数据过程中发生了跨页,就必须发送一个停止信号将一页的数据一次性转移到非易失区域。再重新进行后续数据的发送。
//(1)字节写数据
void IIC_SendString(u8 *pBuffer,u8 addr_send_start)
{
while(1)
{
IIC_SendOneByte(addr_send_start,*pBuffer);//每写入一个字节立马转移到非易失区域,并且需等
//待5ms(等待的5ms已经在此条函数中)。
addr_send_start++;
pBuffer++;
if(*pBuffer=='\0') break;
}
}
//(2)页写数据
void IIC_SendString(u8 *pBuffer,u8 addr_send_start)
{
u8 addr;
addr=addr_send_start;
IIC_Start();
IIC_SendByte(0xA0);
IIC_WaitACK();
IIC_SendByte(addr_send_start);
IIC_WaitACK();
while(1)
{
IIC_SendByte(*pBuffer);
IIC_WaitACK();
if(*pBuffer=='\0') break;
pBuffer++;
addr++;
if(addr%8==0) //判断要写入的地址是否跨页,如果跨页则需重新开始发送
{
IIC_Stop();
delay_ms(10); //发送停止信号,并等待10ms.(发送停止信号后EEPROM开始将页缓冲区数据转
//移到非易失区域,此过程需等待5ms以上时间)
IIC_Start();
IIC_SendByte(0xA0);
IIC_WaitACK();
IIC_SendByte(addr);
IIC_WaitACK();
}
}
IIC_Stop();
delay_ms(10);
}
从AT24C02中读数据
操作时序:
1、MCU先发送一个开始信号(START)启动总线
2、接着跟上首字节,发送设备地址和写(即0xA0)
注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。3、等待应答
4、发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。5、等待应答
6、重新发送开始信号(START)7、发送设备地址和读 (即0xA1)
8、等待应答
9、EEPROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU如果回应一个应答信号(ACK)后,EEPROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
如果不想读了,就发送一个非应答位NACK。发送结束信号(STOP)停止总线
与发送数据有点类似,从EEPROM读取数据页可以有两种方式。
//一个地址一个字节的读取
void IIC_ReadString(u8 *pBuffer,u8 addr_read_start)
{
while(1)
{
*pBuffer=IIC_ReadOneByte(addr_read_start);
if(*pBuffer=='\0') break;
pBuffer++;
addr_read_start++;
}
}
//(2)连续读取字符串,但不存在跨页问题,直接读取即可
void IIC_ReadString(u8 *pBuffer,u8 addr_read_start)
{
IIC_Start();
IIC_SendByte(0xA0);
IIC_WaitACK();
IIC_SendByte(addr_read_start);
IIC_WaitACK();
IIC_Start();
IIC_SendByte(0xA1);
IIC_WaitACK();
while(1)
{
*pBuffer=IIC_ReadByte();
if(*pBuffer=='\0') break;
IIC_ACK();
pBuffer++;
}
IIC_NACK();
IIC_Stop();
软件模拟IIC时序过程中遇到的问题
一、用于模拟IIC总线的IO口配置成推挽输出和开漏输出的区别:
上图为STM32 GPIO功能框图,黄色部分配置为推挽输出。③处有两个MOS管, 当上面的P-MOS管导通、下面的N-MOS关闭时,输出口被VDD拉成了高电平; 当上面的P-MOS管关闭、下面的N-MOS导通时,输出口被VSS拉低成低电平。因此GPIO输出模式中的推挽输出模式可以输出低电平和高电平两种电平。
上图黄色部分配置为开漏输出(③处只有下面的N-MOS起作用,上面的P-MOS管可以理解成总是处于关闭状态)。当③处N-MOS管导通时,输出口为低电平;当③处N-MOS管关闭是,输出口被悬空,称之为高阻态,此时输出口的电平状态不确定,电平状态由下一级电路决定(也就是输出口后边接了什么)。因此GPIO输出模式中的开漏输出模式只能输出低电平和高阻态,而不能输出高电平。若要输出高电平需要外接上拉电阻。
GPIO有一个特点是当配置为输出模式时,其输入通道仍然是打开的 ,TTL施密特触发器处于打开状态,此时可以通过输入数据寄存器读出输出口状态。但是当配置为推挽模式时,输出口状态由芯片内部的两个MOS管拉高或拉低,也就是输入数据寄存器中的值并非IO口外部电平状态;而配置为开漏输出的高阻态模式时,由于此时的IO口被悬空,当IO口输入高低电平时,输入数据寄存器中的值就是外部IO口的状态。也就是在开漏输出模式下的IO口也可同时作为输入口使用。
因此,当初始化IO口为推挽模式时,如果单片机要读取(接收)从机发来的数据,就要提前把SDA接口配置成输入模式(发送数据时又要改回推挽模式);而初始化IO口为开漏输出时,则不需要来回切换输入输出模式,只需要输出高阻态即可。
二、既然推挽模式下不支持线与,为什么单片机能配置成推挽模式?
首先来看为什么推挽模式下不支持线与:
可见,当两个推挽输出的IO接到一起时,若上面的IO输出1,下边输出0,则电流直接由正极经过两个MOS管(忽略MOS管导通电阻相当于直接讲正负极接到一起),流到负极,中间不经过电阻,这样会产生很大的电流,从而烧坏芯片;上面的IO输出0,下边输出1,也是同样的情况;只有当上下同时输出0或上下同时输出1时才不会烧坏芯片。
再来看看为什么开漏输出时支持线与:
当两个开漏输出接到一起时,只有当上下两个MOS管同时关闭时,输出口为高阻态(上图中外接了上拉电阻,所以时高电平);当任何一个MOS管导通时,输出口都被拉低成低电平。但不管怎么样,都不会产生大电流烧坏芯片。
那为什么在IIC总线中把单片机配置成推挽模式也是可以的呢:
①IIC总线中只有一个单片机(一个主设备)时:
对于SDA线:从设备芯片内部都是开漏输出(一个MOS管),其实也就是开漏和推挽接在了一起。并且单片机读取数据时(此时单片机配置成了输入模式,而非推挽模式),从设备芯片为写(开漏);单片机发送数据时(推挽模式),从设备芯片为读(开漏且为高阻态)。这两种情况都不会出现大电流烧坏芯片。
对于SCL线:单片机只发送(推挽),设备芯片只接收(开漏且高阻态)。也不会烧坏芯片。
②IIC总线有多个单片机(多个主设备)时:由于永远是一读一写,因此不会出现推挽与推挽接在一起的情况,也不会烧坏芯片。
综上,单片机配置成推挽输出模式也是可以的。
三、有关开始信号软件模拟遇到的问题如下:
按理来说只有在IIC_SCL=0期间才能修改SDA 的值,为什么代码段(1)中先使IIC_SCL=1,再改变IIC_SDA=1也是可以的呢(如果IIC_SDA在上一刻值为0不就产生了一个停止信号吗)?
原因: 开始信号函只出现在发送数据的开头或读取数据的开头和中间(发送数据地址函数后的等待应答信号函数之后)两个地方。经实验,即使是刻意在开始信号前加一个停止信号,也不会影响数据传输。文章来源:https://www.toymoban.com/news/detail-486950.html
本文有部分内容引用博主「Z小旋」的文章。
原文链接:https://blog.csdn.net/as480133937/article/details/105366932文章来源地址https://www.toymoban.com/news/detail-486950.html
到了这里,关于STM32软件模拟IIC时序实现与EEPROM的通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!