STM32---IIC 协议(软件IIC)

一、IIC 简介

IIC(Inter-Integrated Circuit)是 IIC Bus 简称,中文叫集成电路总线。它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。
  🔔IIC使用两根信号线进行通信:一根时钟线SCL,一根数据线SDA

IIC将SCL处于高时SDA拉低的动作作为开始信号,

SCL处于高时SDA拉高的动作作为结束信号;

传输数据时,SDA在SCL低电平时改变数据在SCL高电平时保持数据,每个SCL脉冲的高电平传递1位数据。

PS: 这里要注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI

 

二、I2C最重要的功能包括:

  • 只需要两条总线---半双工
  • 没有严格的波特率要求,例如使用RS232,主设备生成总线时钟
  • 所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一的地址进行软件寻址
  • I2C是真正的多主设备总线,可提供仲裁和冲突检测
  • 传输速度
    • 标准模式:100kbit/s
    • 快速模式:400kbit/s
    • 高速模式:3.4Mbit/s
  • 最大主设备数:无限制
  • 最大从机数:理论上是127

IIC主要特点:

常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。**

  • IIC主设备功能:主要产生时钟,产生起始信号和停止信号

  • IIC从设备功能:可编程的IIC地址检测,停止位检测

  • IIC的一个优点是它支持多主控(multimastering), 其中任何一个能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

  • 支持不同速率的通讯速度,标准速度(最高速度100kHZ),快速(最高400kHZ)

  • SCL和SDA都需要接上拉电阻 (大小由速度和容性负载决定一般在3.3K-10K之间) 保证数据的稳定性,减少干扰。

  • IIC是半双工,而不是全双工 ,同一时间只可以单向通信

  • 为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。这一点在等下我们会讲解

🔮IIC的高阻态

漏极开路(Open Drain)即高阻状态,适用于输入/输出,其可独立输入/输出低电平和高阻状态,若需要产生高电平,则需使用外部上拉电阻

高阻状态:高阻状态是三态门电路的一种状态。逻辑门的输出除有高、低电平两种状态外,还有第三种状态——高阻状态的门电路。电路分析时高阻态可做开路理解

我们知道IIC的所有设备是接在一根总线上的,那么我们进行通信的时候往往只是几个设备进行通信,那么这时候其余的空闲设备可能会受到总线干扰,或者干扰到总线,怎么办呢?

为了避免总线信号的混乱,IIC的空闲状态只能有外部上拉, 而此时空闲设备被拉到了高阻态,也就是相当于断路, 整个IIC总线只有开启了的设备才会正常进行通信,而不会干扰到其他设备。

三、IIC的从地址

🍎IIC器件地址: 每一个IIC器件都有一个器件地址,有的器件地址在出厂时地址就设定好了,用户不可以更改,比如OV7670的地址为0x42。有的器件例如EEPROM,前四个地址已经确定为1010,后三个地址是由硬件链接确定的,所以一IIC总线最多能连8个EEPROM芯片。 

IIC从地址有3种类型:分别是7位,8位和10位。产生这么多类型的原因是厂商采用的不同的地址约定。 

这里主要讲解7位的寻址,

🍏7位寻址

在7位寻址过程中,从机地址在启动信号后的第一个字节开始传输,该字节的前7位为从机地址,第8位为读写位,其中0表示写1表示读。

上图:7位寻址。I2C总线规范规定,标准模式I2C,从机地址为7位长,其次是读/写位。 

  • 第一个字节的头7 位组成了从机地址, 最低位(LSB) 是第8 位, 它决定了传输的方向。
  • 第一个字节的第8位是“0” , 表示主机会写信息到被选中的从机;
  • “1” 表示主机会向从机读信息, 当发送了一个地址后, 系统中的每个器件都在起始条件后将
  • 头7 位与它自己的地址比较, 如果一样, 器件会判定它被主机寻址, 至于是从机接收器还是从机发送器, 都由R/W 位决定。
  • 任何I2C设备都必须遵循这个标准,USB2XXX传输的从机地址即为这7bit地址,不包含读写位,读写位会根据不同的函数自动添加进去。

四、I2C的协议层

🍒I2C 总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。

  • 开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
  • 结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
  • 应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲,表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。

数据格式 

📙传输流程 

  • 主芯片要发出一个start信号

  • 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0表示写,1表示读)

  • 从设备回应(用来确定这个设备是否存在),然后就可以传输数据

  • 主设备发送一个字节数据给从设备,并等待回应

  • 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。

  • 数据发送完之后,主芯片就会发送一个停止信号。

  • 上图:白色背景表示"主→从",灰色背景表示"从→主"

🍓通讯的起始和停止信号 

  • 当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。

  • 当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止

🍑应答 信号

        协议规定数据传输过程必须包含应答(ACK)。接收器通过应答告知发送的字节已被成功接收,之后发送器可以进行下一个字节的传输。主机产生数据传输过程中的所有时钟,包括用于应答的第9个时钟。发送器在应答时钟周期内释放对SDA总线的控制,这样接收器可以通过将SDA线拉低告知发送器:数据已被成功接收。
  应答信号分为两种:
    1)当第9位(应答位)为 低电平 时,为 ACK  (Acknowledge)   信号
    2)当第9位(应答位)为 高电平 时,为 NACK(Not Acknowledge)信号
  主机发送数据,从机接收时,ACK信号由从机发出。当在SCL第9位时钟高电平信号期间,如果SDA仍然保持高电平,则主机可以直接产生STOP条件终止以后的传输或者继续ReSTART开始一个新的传输
 从机发送数据,主机读取数据时,ACK信号由主机给出。主机响应ACK表示还需要再接收数据,而当主机接收完想要的数据后,通过发送NACK告诉从机读取数据结束、释放总线。随后主机发送STOP命令,将总线释放,结束读操作。

在起始信号后必须传送一个从机的地址(7位) 1~7位为7位接收器件地址,第8位为读写位,用“0”表示主机发送数据(W),“1”表示主机接收数据 (R), 第9位为ACK应答位,紧接着的为第一个数据字节,然后是一位应答位,后面继续第2个数据字节。 

🍍IIC发送数据 

Start: IIC开始信号,表示开始传输。
DEVICE_ADDRESS:: 从设备地址,就是7位从机地址
R/W: W(write)为写,R(read)为读
ACK: 应答信号
WORD_ADDRESS : 从机中对应的寄存器地址 比方说访问 OLED中的 某个寄存器
DATA发送的数据
STOP: 停止信号。结束IIC

主机要向从机写数据时: 

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

🔒IIC读数据: 

主机要从从机读数据时:

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

IIC分为软件IIC和硬件IIC

软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。

硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。

硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。

五、建议

IIC的基本时序是通用的,可以封装好IIC的文件进行适配,更改引脚即可。

附上两个代码,通用的

IIC.c

 /***********************************************************************************************************************************
 ***********************************************************************************************************************************
 **【文件名称】  bsp_IICSoft.c
 **【功能描述】  模拟IIC时序
 **              定义功能函数
 **            
 **【函数说明】  
 **
***********************************************************************************************************************************/
#include "bsp_IICSoft.h"



/************************************************
 ** 电平控制简化_宏定义
 ***********************************************/
#define SCL_L      (I2C_MONI_SCL_GPIO->BSRR = (I2C_MONI_SCL_PIN << 16))
#define SCL_H      (I2C_MONI_SCL_GPIO->BSRR =  I2C_MONI_SCL_PIN)
#define SDA_L      (I2C_MONI_SDA_GPIO->BSRR = (I2C_MONI_SDA_PIN << 16))
#define SDA_H      (I2C_MONI_SDA_GPIO->BSRR =  I2C_MONI_SDA_PIN)
#define SDA_READ   ((I2C_MONI_SDA_GPIO->IDR & I2C_MONI_SDA_PIN)?1:0)       // 不用改成输入模式就能读



/*****************************************************************************
 ** 内部函数
*****************************************************************************/
static void     delayUs(uint32_t time); // 简单延时,不准确

static void     isBusy (void);          // 检测总线是否空闲
static void     start  (void);          // 起始信号
static void     stop   (void);          // 停止信号
static void     ackYes (void);          // 发送应答信号
static void     ackNo  (void);          // 不发送应答信号
static uint8_t  waitForAck(void);       // 等待应答信号

static void     sendByte(uint8_t data);      // 发送一个字节
static uint8_t  readByte(uint8_t ack);       // 读取一个字节


/*****************************************************************************
*函  数:IICSoft_Init
*功  能:初始化模拟IIC引脚
*参  数:
*返回值: 
*备  注:
*****************************************************************************/
void IICSoft_Init(void)
{
    // 使能SCL引脚端口时钟
    if(I2C_MONI_SCL_GPIO == GPIOA)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
    if(I2C_MONI_SCL_GPIO == GPIOB)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
    if(I2C_MONI_SCL_GPIO == GPIOC)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);
    if(I2C_MONI_SCL_GPIO == GPIOD)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD , ENABLE);
    if(I2C_MONI_SCL_GPIO == GPIOE)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);
    if(I2C_MONI_SCL_GPIO == GPIOF)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF , ENABLE);
    if(I2C_MONI_SCL_GPIO == GPIOG)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG , ENABLE);
    // 使能SDA引脚端口时钟
    if(I2C_MONI_SDA_GPIO == GPIOA)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
    if(I2C_MONI_SDA_GPIO == GPIOB)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
    if(I2C_MONI_SDA_GPIO == GPIOC)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);
    if(I2C_MONI_SDA_GPIO == GPIOD)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD , ENABLE);
    if(I2C_MONI_SDA_GPIO == GPIOE)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE , ENABLE);
    if(I2C_MONI_SDA_GPIO == GPIOF)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF , ENABLE);
    if(I2C_MONI_SDA_GPIO == GPIOG)  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG , ENABLE);
    
    // 配置引脚工作模式: 开漏输出
    GPIO_InitTypeDef G;
    G.GPIO_Pin   = I2C_MONI_SCL_PIN;
    G.GPIO_Mode  = GPIO_Mode_Out_OD;
    G.GPIO_Speed = GPIO_Speed_50MHz ;
    GPIO_Init(I2C_MONI_SCL_GPIO, &G);
    delayUs(2);
    
    G.GPIO_Pin   = I2C_MONI_SDA_PIN;
    GPIO_Init(I2C_MONI_SDA_GPIO, &G);
    delayUs(2);    
    	
	SCL_L ;   
    delayUs(2);
    SDA_H ;    
}


// 粗略延时************************
static void delayUs( u32 times)
{
    times=times*7;    
	while(--times)  	   
        __nop();  
}



// 判断总线是否处于空闲状态,若不是,则循环的提供SCL时钟驱动,直到从机释放SDA线
static void isBusy(void)
{
    uint32_t i=500;                  // 大约3ms  
    //SDA_IN ();
    while((SDA_READ) == 0 && (--i))  // 读取sda线上的电平状态,低电平说明总线被从机控制,高电平则说明总线空闲,可以准备发送开始信号
    { 
        SCL_L;
        delayUs(3);        
        SCL_H ;
        delayUs(3);             
    }     
    if(i==0)    printf("\r\n IIC总线超时错误!! \r\n");   // 错误提示
}



/*****************************************************************************
 * 函  数:start
 * 功  能:开始信号 (SCL高电平期间,SDA由高向低 跳变)
*****************************************************************************/
static void start(void)
{     
    isBusy ();               // 判断总线是否处于空闲状态 
  
    SCL_L;   delayUs(3);   
    SDA_H;   delayUs(3);       

    SCL_H;   delayUs(3);      
    SDA_L;   delayUs(3);     // 在SCL高电平其间, SDA由高向低 跳变        
    SCL_L;   delayUs(3);     // 将SCL拉低,钳住SCL线,准备发送数据          
}



/*****************************************************************************
 * 函  数:stop
 * 功  能:产生停止信号 (在SCL高电平其间, SDA由低向高 跳变)
*****************************************************************************/
static void stop(void)
{
    SCL_L;   delayUs(3);       
    SDA_L;   delayUs(3);
    
    SCL_H;   delayUs(3);  
    SDA_H ;  delayUs(1);     // 在SCL高电平其间, SDA由低向高 跳变        
}



/*****************************************************************************
 * 函  数:ackYes
 * 功  能:主机产生应答信号 (在第9个时钟周期SAL高电平期间, SDA保持低电平)
 *         主机接收完一个字节数据后,主机产生ACK通知从机一个字节数据已正确接收
*****************************************************************************/
static void ackYes(void)
{
    SCL_L;    delayUs( 3); 
    SDA_L;    delayUs( 3);   
    
    SCL_H;    delayUs(10);   // 使SDA保持一个时钟的低电平    
    SCL_L;    delayUs( 3);   
    
    SDA_H ;   delayUs( 3);   // 很重要: 在这里释放SDA线,从机才能获取控制权 
}



/*****************************************************************************
 * 函  数:ackNo
 * 功  能:主机产生非应答信号 (在第9个时钟周期SAL高电平期间, SDA保持高电平)
 *         主机接收完一个字节数据后,主机产生NACK通知从机发送线束,以便主机产生停止信号
*****************************************************************************/
static void ackNo(void)
{
    SCL_L;    delayUs( 3); 
    SDA_H;    delayUs( 3); 
    
    SCL_H;    delayUs(10);   // 使SDA保持一个时钟的高电平   
    SCL_L;    delayUs( 3);  
    
    SDA_H;    delayUs( 3);   // 很重要: 在这里释放SDA线,从机才能获取控制权  
}


/*****************************************************************************
 * 函  数:waitForAck
 * 功  能:获取应答信号或者非应答信号
 *         有效应答信号:从机第9个SCL=0时,SDA被从机拉低,并且SCL=1时,SDA依然为低
 * 
 * 返回值: 0=收到应答信号,   1=收到非应答信号
 * 备  注:
*****************************************************************************/
static uint8_t waitForAck(void)
{    
    uint8_t  d=0;
    
    SCL_L;    delayUs( 3);
    SDA_H;    delayUs( 3);        // 主机释放SDA线,使总线空闲,以便从机能够响应信息
    
    SCL_H;    delayUs(10);        // 等待10us; 按标准模式100kbit/s 
    d=SDA_READ;
    
    SCL_L;    delayUs( 3);         
    SDA_H;    delayUs( 3);        // 主机释放SDA线,使总线空闲,以便从机能够响应信息
        
    if(d)
        return 1;                 // 返回非应答信号     
    else
        return 0;                 // 返回应答信号   
}



/*****************************************************************************
 * 函  数: sendByte
 * 功  能: 输出一个字节
 * 参  数:   
 * 返回值: 0=收到应答信号,   1=收到非应答信号
 * 备  注:
*****************************************************************************/
static void sendByte(uint8_t data)
{   
    for(uint8_t i=0; i<8; i++)
    {             
        SCL_L;     delayUs( 3);     
        
        (data&0x80)? SDA_H : SDA_L;      
        data<<=1;  delayUs( 3);    
        
        SCL_H;     delayUs( 3);        
    } 
    
    SCL_L;    delayUs( 3); 
    SDA_H;    delayUs( 3);      // 主机释放SDA线,使用总线空闲,以便从机能够发出响应信息,并钳信SCL线        
}

/*****************************************************************************
 * 函  数: readByte
 * 功  能: 读取一个字节
 * 参  数: ack=0:主机还没接收完, ack=1:主机数据已全部接收完成 
 * 返回值: 读取到的Byte值
 * 备  注: 读取从机发送的数据后,并决定发出应答信号还是非应答信号
*****************************************************************************/
static uint8_t readByte(uint8_t ack)
{
    uint8_t data=0;
    
    for(uint8_t i=0; i<8; i++)
    {            
        SCL_H;    delayUs( 3);
        data<<=1;
        if(SDA_READ)  data|=0x01;            
        SCL_L;    delayUs( 3);  
    }
    
    if(ack) ackNo ();               // 1, 不应答
    else    ackYes();               // 0, 应答
    
    return  data;       
}



/*****************************************************************************
 * 函  数: IICSoft_ReadByte
 * 功  能: 向指定从机设备,读取指定地址的一个值, 单位:字节
 * 参  数: slave: 从机地址
 *          addr: 数据地址
 *          *buf: 数据要存储的地址
 * 返回值: 0=成功, 1=失败
 * 备  注: 注意,时序有点特别
*****************************************************************************/
uint8_t IICSoft_ReadByte(uint8_t slave, uint8_t addr, uint8_t *buf)
{        
    start ();                     // 起始信号    
    sendByte (slave<<1 | 0);      // 从机地址, 写方向 , 0写1读    
    if(waitForAck())
    {
        stop();
        return 1;
    }
    
    sendByte(addr);               // 数据地址
    if(waitForAck())
    {
        stop();
        return 1;
    }
    
    // 写数据和读数据最大的时序区别, 写比较简单,读数据:再发一次起始信号、有读方向的从机地址
    start();                      // 起始信号
    sendByte(slave<<1 | 1);       // 从机地址, 读方向 ,  0写1读
    if(waitForAck())
    {
        stop();
        return 1;
    }
    
    *buf=readByte(1);             // 读值, 并发送ackNo;
    stop();
    
    return 0;
}      



/*****************************************************************************
 * 函  数: IICSoft_ReadBueffer
 * 功  能: 向指定从机设备,读取指定地址的多个值, 单位:字节
 * 参  数: uint8_t slave   从机地址
 *          uint8_t addr    数据地址
 *          uint8_t data    要写入的数据(1字节)
 * 返回值: 0=成功, 1=失败
 * 备  注: 注意,时序有点特别
*****************************************************************************/
uint8_t IICSoft_ReadBueffer(uint8_t slave, uint8_t addr, uint8_t *buf, uint8_t len)
{    
    start ();                     // 起始信号    
    sendByte (slave<<1 | 0);      // 从机地址, 写方向 , 0写1读    
    if(waitForAck())              // 等待ACK
    {
        stop();
        return 1;
    }
    
    sendByte(addr);               // 数据地址
    if(waitForAck())              // 等待ACK
    {
        stop();
        return 1;
    }
    
    // 写、读时序区别, 读数据:再发一次起始信号、有读方向的从机地址
    start();                      // 起始信号
    sendByte(slave<<1 | 1);       // 从机地址, 读方向 ,  0写1读
    if(waitForAck())              // 等待ACK
    {
        stop();
        return 1;
    }
    
  
    while(len)
    {      
        if(len==1)  *buf=readByte(1);   // 最后一个字节,读数据后产生NACK
        else        *buf=readByte(0);   // 读数据后产生ACK
    
        len--;
        buf++;
    }  
    
    stop ();                      // 产生一个停止信号
    return 0;   
}      


/*****************************************************************************
 * 函  数: IICSoft_WriteByte
 * 功  能: 往从机某地址写入一个字节
 * 参  数: uint8_t slave   从机地址
 *          uint8_t addr    数据地址
 *          uint8_t data    要写入的数据(1字节)
 * 返回值: 0=成功, 1=失败
 * 备  注: 时序与读是不同的
*****************************************************************************/
uint8_t IICSoft_WriteByte(uint8_t slave, uint8_t addr, uint8_t data)
{
    start ();                     // 起始信号    
    sendByte (slave<<1 | 0);      // 从机地址, 写方向 , 0写1读    
    if(waitForAck())
    {
        stop();
        return 1;                 // 若从机地址发送失败,就返回
    }
    
    sendByte(addr);               // 数据地址
    if(waitForAck())
    {
        stop();
        return 1;                 // 若发送失败,就返回
    }    
    
    sendByte(data);               // 发送数据, 写数据和读数据最大的时序区别在这一步前
    if(waitForAck())
    {
        stop ();
        return 1;                 // 数据写入失败
    }
    
    stop();                       // 写入完成,产生停止信号
    return 0;    
}

// 往从机写多个字节数据
uint8_t IICSoft_WriteBuffer(uint8_t slave, uint8_t addr, uint8_t *buf, uint8_t len)
{ 
    start ();                     // 起始信号    
    sendByte (slave<<1 | 0);      // 从机地址, 写方向 , 0写1读    
    if(waitForAck())
    {
        stop();
        return 1;                 // 若发送失败,就返回
    }  
    
    sendByte(addr);               // 数据地址
    if(waitForAck())
    {
        stop();
        return 1;                 // 若发送失败,就返回
    }  
    
    for(uint8_t i=0; i<len; i++)
    {
        sendByte(buf[i]);         // 数据
        if(waitForAck())          // 每一个字节都要等从机应答
        {
            stop ();
            return 1;             // 数据写入失败
        }
    }
    
    stop();                       // 写入完成,产生停止信号
    return 0;    
}             
        



 IIC .h

#ifndef __I2C_MONI_H
#define __I2C_MONI_H
/***********************************************************************************************************************************
 ***********************************************************************************************************************************
 **【文件名称】  i2c_moni.h
 **【功能描述】  模拟IIC时序
 **              定义引脚、定义全局结构体、声明全局函数
 **
 **【适用平台】  STM32F103 + 标准库v3.5 + keil5
 **                 
 **【移植说明】  引脚修改:在i2c_moni.h文件中修改,以方便IIC总线复用、代码复用
 **              器件地址:在各设备文件中修改
 ** 
 **【更新记录】  2020-03-05  创建
 **              2021-05-03  完善文件格式、注释格式
 **
***********************************************************************************************************************************/
#include <stm32f10x.h>  
#include <stdio.h>



/*****************************************************************************
 * 移植
 * 为配合多个设备共用一个模拟IIC, 外部引脚在本头文件内宏定义
****************************************************************************/
// SCL
#define I2C_MONI_SCL_GPIO      GPIOC
#define I2C_MONI_SCL_PIN       GPIO_Pin_12
// SDA
#define I2C_MONI_SDA_GPIO      GPIOC
#define I2C_MONI_SDA_PIN       GPIO_Pin_11



/*****************************************************************************
 * 声明  全局函数
 * 数量:1个初始化, 4个读写
****************************************************************************/
void IICSoft_Init(void);          // 初始化所用引脚

uint8_t   IICSoft_ReadByte    (uint8_t addr, uint8_t reg, uint8_t* byte);               // 向从机读一字节; 从机地址, 数据或者寄存器地址,数据
uint8_t   IICSoft_ReadBueffer (uint8_t addr, uint8_t reg, uint8_t* buf,  uint8_t len);  // 向从机读多字节; 从机地址, 数据或者寄存器地址,数据
uint8_t   IICSoft_WriteByte   (uint8_t addr, uint8_t reg, uint8_t  byte);               // 向从机写一字节; 从机地址, 数据或者寄存器地址,数据
uint8_t   IICSoft_WriteBuffer (uint8_t addr, uint8_t reg, uint8_t* buf,  uint8_t len);  // 向从机写多字节; 从机地址, 数据或者寄存器地址,数据



#endif

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值