I2C软件模拟(亲测可用)

I2C软件模拟时序

  • 引脚模式的配置
    • SDA引脚配置
      • 如果外部有接上拉电阻,那么直接开漏输出就可以了
      • 如果没有上拉电阻,那在编程时就需要考虑在不同的阶段,设置不同的输入输出模式

数据有效性

在这里插入图片描述

起始信号

  • 时序图:

在这里插入图片描述

  • SCL 线是高电平时 ,SDA 线从高电平向低电平切换表示起始信号

  • 程序如下:

    void A_I2C_Start(void)
    {
    	SCL_High; //先拉高时钟线
    	delay_us(2); //延时函数很有必要,延时是微秒级,因为使用的是stm32f4,可以使用SysTick来实现,后面给出
    	
    	SDA_High; //拉高数据线
    	delay_us(2);
    	SDA_LOW;
    	
    	delay_us(2);
    	SCL_LOW;  //拉低时钟线,使SDA可以进行数据的切换
    	delay_us(2);
    	
    }
    
    void delay_us(uint32_t nus)
    {
        uint32_t ticks;
        uint32_t told,tnow,reload,tcnt=0;
    
        reload = SysTick->LOAD;                     
        ticks = nus * (SystemCoreClock / 1000000);  
        told=SysTick->VAL;                          
    
        while(1)
        {
              tnow=SysTick->VAL;          
              if(tnow!=told)              
              {         
    
                     if(tnow<told)             
                          tcnt+=told-tnow;     
    
                     else                  
                            tcnt+=reload-tnow+told;   
                                                    
                     told=tnow;                
                     if(tcnt>=ticks)break;     
              } 
        }     
    }
    

停止信号

  • 时序图见上图

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

  • 程序如下:

    void A_I2C_Stop(void)
    {
    	SDA_LOW;
    	SCL_High;
    	delay_us(2);
    	SDA_High;
    	delay_us(2);
    }
    

应答信号

  • 应答信号时序图:

    在这里插入图片描述

  • 作为数据接收端时,当接收到一个字节也就是8位数据后,在第九位需要发送应答或者非应答信号,如果想继续接收则发送应答信号,否则发送非应答信号;

  • 此时的数据发送端会释放SDA线,将SDA线拉成高电平;

  • 由应答信号的时序图可知,高电平为非应答,低电平为应答。

  • 程序如下:

    void A_I2C_Ack(void)
    {
    	SCL_LOW;//先拉低SCL线,使SDA进行数据的变换
    	
    	SDA_LOW;  //拉低SDA,产生应答信号
    	delay_us(2);
    	
    	SCL_High;  //拉高SCL使SDA数据有效
    	delay_us(2);
    	SCL_LOW;  //先拉低SCL线,使SDA进行数据的变换
    	SDA_High; //释放SDA	
    }
    

非应答信号

  • 非应答信号的原理和应答信号的差不多,代码如下:

    void A_I2C_NAck(void)
    {
    	SCL_LOW;//先拉低SCL线,使SDA进行数据的变换
    	
    	SDA_High;//拉高SDA,产生非应答信号
    	delay_us(2);
    	
    	SCL_High;
    	delay_us(2);
    	SCL_LOW;
    	SDA_High;
    }
    

等待应答信号

  • 等待应答信号是发送端发送完数据后,等待接收端给出的应答信号,如果在一定的时间内没有等到应答信号,则通信失败

  • 代码如下:

    uint8_t I2C_Wait_Ack(void) 
    {
    	uint8_t times = 0;
    	SCL_LOW;//先拉低SCL线,使SDA进行数据的变换
    	delay_us(1);
    	SCL_High;//拉高SCL使SDA数据有效
    	delay_us(1);
    	while (HAL_GPIO_ReadPin(OLED_SDA_GPIO_Port,OLED_SDA_Pin))
    	{
    		if (++times > 250) 
    		{
    			A_I2C_Stop();
    		    return 1;
    		}
    	}
    	SCL_LOW;//先拉低SCL线,使SDA进行数据的变换
    	delay_us(2);
    	return 0;
    }
    

写一个字节

  • SCL的每一个高电平,SDA发送一个比特的数据,所以一个字节需要循环发送8次

    void I2C_write(uint8_t date) 
    {
    	uint8_t i, temp;
    	temp = date;	
    	SCL_LOW;
    	delay_us(2);
    	for(i = 0; i < 8; i++) 
    	{	
            /*移位发送*/
            if (((temp << i) & 0x80) == 0 ) 
                SDA_LOW;
            else
    			SDA_High;
    		SCL_High;  //拉高SCL使SDA数据有效
    		delay_us(2);
    		SCL_LOW;  //先拉低SCL线,使SDA进行数据的变换
    		delay_us(2);
    	}
    }
    

读一个字节

  • 原理和发送字节差不多,需要在定义一个变量来存取读来的数据

    uint8_t I2C_read(void) 
    {
    	uint8_t i, temp = 0;	
    	for(i = 0; i < 8; i++) 
    	{	
    	
    		SCL_High;
    		delay_us(2);
            temp <<= 1;
            /*stm32在输出模式下是可以正确读取引脚的数值的,这和GPIO的内部寄存器结构有关*/
    		if(HAL_GPIO_ReadPin(OLED_SDA_GPIO_Port,OLED_SDA_Pin) == 1) 
    		{
    			temp |= 0x01;
    		}
    		SCL_LOW;
    		delay_us(5);
    	}
    	return temp;
    }
    

向寄存器写命令

  • 实质就是发送寄存器的地址,再发送命令,本质还是发送数据

    void A_I2C_WriteByte(uint8_t slaveaddr,uint8_t registeraddr,uint8_t data)
    {
    	A_I2C_Start();
    	I2C_write(slaveaddr);
    	I2C_Wait_Ack();
    	I2C_write(registeraddr);
    	I2C_Wait_Ack();
    	I2C_write(data);
    	I2C_Wait_Ack();
    }
    

向其他设备发送一串字节

  • 里面会涉及到I2C的报文格式,可以看看这一篇I2C的协议层

    void A_I2C_WriteByteS(uint8_t slaveaddr,uint8_t registeraddr,uint8_t *pbuffer,uint16_t num)
    {
    	uint16_t t;
    	A_I2C_Start();
    	I2C_write(slaveaddr);
    	I2C_Wait_Ack();
    	I2C_write(registeraddr);
    	I2C_Wait_Ack();
    	
    	for(t=0;t<num;t++)
    	{
    		I2C_write(*(pbuffer)+t);
    		I2C_Wait_Ack();
    	}
    	A_I2C_Stop();
    }
    

从其他设备读取一串字节

  • 原理和上面差不多

    void A_I2C_ReadBytes(uint8_t slaveaddr,uint8_t registeraddr,uint8_t *pbuffer,uint16_t num )
    {
    	uint16_t t;
    	A_I2C_Start();
    	I2C_write(slaveaddr);
    	I2C_Wait_Ack();
    	I2C_write(registeraddr);
    	I2C_Wait_Ack();
    	
    	A_I2C_Start();
    	I2C_write(slaveaddr+1);
    	I2C_Wait_Ack();
    	
    	for(t=0;t<num;t++)
    	{
    		*(pbuffer+t)=I2C_read();
    		if (t== num-1)
    	 	{
    	   	 	A_I2C_Ack();
    		}
    		else
    		{
    	    	A_I2C_NAck();
    		}
    	}
    	A_I2C_Stop();
    }
    

如有错误的地方,还望大家多多批评指正!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值