【学习记录】软件iic

由于有时候出现硬件iic总线挂死的情况,所以记录一下软件iic的方法,代码来源网上资料整合
以下是.h文件

#ifndef _M_IIC_SOFT_H
#define _M_IIC_SOFT_H
#include "stdint.h" //基本数据结构
// #include "M_io.h"
#include "gd32f30x.h" //io 控制
/*******************************************
 *Soft I2C 

 ********************************************/
 
    /* connect PB6 to I2C_SCL */
    /* connect PB7 to I2C_SDA */

#define IIC_Soft   1    //是否启用软件

#define WP_EE_GPIO_PORT    GPIOB
#define WP_EE_PIN          GPIO_PIN_5
//#define IIC_SCL   I2C0_SCL_PIN
//#define IIC_SDA   I2C0_SDA_PIN

#define SDA_IN()    gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_7)//输入模式
#define SDA_OUT()   gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7)//输出模式

#define SCL_OUT()   gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6)//输出模式

#define I2C_WAIT_TIMEOUT     	(uint8_t)250	//等待时间
#define I2C_DELAY             	(uint8_t)5		//延时时间

#define I2C_N_ACK               (0U)   //不发ACK
#define I2C_ACK 				        (1U)   //发送ACK

#define SW_I2C_SCL_LOW             gpio_bit_reset(GPIOB, GPIO_PIN_6);
#define SW_I2C_SCL_HIGH            gpio_bit_set(GPIOB, GPIO_PIN_6);
#define SW_I2C_SDA_LOW             gpio_bit_reset(GPIOB, GPIO_PIN_7);
#define SW_I2C_SDA_HIGH            gpio_bit_set(GPIOB, GPIO_PIN_7);

#define I2C_SDA_STATUS          	 gpio_input_bit_get (GPIOB,GPIO_PIN_7)                      //读取SDA状态
#define I2C_SCL_STATUS             gpio_input_bit_get (GPIOB,GPIO_PIN_6)                      	//读取SCL状态

//#define SW_I2C_SDA_INPUT        sw_i2c_set_sda_input()		//设置SDA为输入模式
//#define SW_I2C_SDA_OUTPUT       sw_i2c_set_sda_output()		//设置SDA为输出模式
//#define SW_I2C_SDA_STATUS       sw_i2c_sda_status()			//设置SDA状态
//#define SW_I2C_BusInit          sw_i2c_busInit()			//总线初始化
  
//#define i2c_delay_us(a)         SysCtlDelayus(a)			//延时函数us
void Init_IIC(void);

void i2c_delay(void);


extern void sw_i2c_start(void);
extern void sw_i2c_stop(void);
extern void Send_Ack(void);
extern void Send_N_Ack(void);
extern uint8_t sw_i2c_wait_ack(void);
extern void I2C_Write_Byte(uint8_t aByte);
extern uint8_t I2C_Read_Byte(uint8_t Ack);

void i2c_Softwrite_reg_EEPROM(uint8_t SlaveID, uint16_t reg, uint8_t* Data,uint16_t number_of_byte);
void i2c_Softread_reg_EEPROM(uint8_t SlaveID, uint16_t reg,uint16_t num, uint8_t * addr);

#endif


.c文件如下

#include "M_iic_soft.h"
//#include "delay.h"

void Delay_Nus(uint16_t Nus)//需要测量
{
	uint8_t i;
     while(Nus --)
     {
       for(i =0;i<6;i++);
		 }
}


#define  i2c_delay_us(x)  Delay_Nus(x)
//-------------------------------------------------------------------------------------------------------------------
//  @brief      I2C通信结束后需要调用的函数函数
//  @return     void
//  @since      v2.0
//  @note		如果通信失败,可尝试增大此延时值,确认是否延时导致的
//-------------------------------------------------------------------------------------------------------------------
void i2c_delay(void)
{
    volatile uint16_t n = 150*3;     //注意,这个数据太小,会导致读取错误。

    while(n--);
}    


/************************************************************************************
*@fuction	:sw_i2c_start
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void sw_i2c_start(void)
{
    //I2C开始时序:SDL输出模式 SDL = 1 SCL = 1时,SDA由1变成0.
    SDA_OUT(); 
    SW_I2C_SDA_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SDA_LOW;
    i2c_delay_us(I2C_DELAY);
	  SW_I2C_SCL_LOW;
}

/************************************************************************************
*@fuction	:sw_i2c_stop
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void sw_i2c_stop(void)
{
    //I2C 停止时序:SDL输出模式 SDL = 0 SCL = 1时,SDA由0变成1.
    SDA_OUT();
    SW_I2C_SDA_LOW;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SDA_HIGH;
}
/************************************************************************************
*@fuction	:Send_Ack
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void Send_Ack(void)
{
		//发送ACK就是在SDA=0时,SCL由0变成1
    SDA_OUT();
  	SW_I2C_SCL_LOW;
    SW_I2C_SDA_LOW;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
}

/************************************************************************************
*@fuction	:Send_N_Ack
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void Send_N_Ack(void)
{
		//发送nACK就是在SDA=1时,SCL由0变成1
    SDA_OUT();
	SW_I2C_SCL_LOW;
    SW_I2C_SDA_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
}
/************************************************************************************
*@fuction	:sw_i2c_wait_ack
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
uint8_t sw_i2c_wait_ack(void)
{
	/*IIC总线上接收方在完成接收一个字节后(8bit),在第9个SCL时钟脉冲的上升沿期间,通过SDA响应ACK(SDA=0)或NACK(SDA=1)信号*/
    uint8_t wait_time = 0;
    uint8_t ack_nack  = 1;
		
	//SDA输入模式
    SDA_IN();
	
	  SW_I2C_SCL_HIGH;
	//等待SDA脚被从机拉低
    while(I2C_SDA_STATUS)
    {
        wait_time++;
        if(wait_time >= I2C_WAIT_TIMEOUT)
        {
			//如果等待时间超时,则退出等待
            ack_nack = 0;
            break;
        }    
    }
	//SCL由0变为1,读入ACK状态
    //如果此时SDA=0,则是ACK
    //如果此时SDA=1,则是NACK
    i2c_delay_us(I2C_DELAY);
    SW_I2C_SCL_HIGH;
    i2c_delay_us(I2C_DELAY);
	//再次将SCL=0,并且将SDA设置为输出
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
    SDA_OUT();
    i2c_delay_us(I2C_DELAY);
	//返回ACK状态
    return ack_nack;
}
/************************************************************************************
*@fuction	:I2C_Write_Byte
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
void I2C_Write_Byte(uint8_t aByte)
{		
	//写模式:在SCKL = 0时候,可改变SDA状态
    uint8_t i = 0;
	//将SDA设置为输出模式
    SDA_OUT();
    for(i = 0;i < 8;i++)
    {
		//先将SCL拉低,再改变SDA上的数据
        SW_I2C_SCL_LOW;
        i2c_delay_us(I2C_DELAY);
        if(aByte&0x80)
        {	
			//最高位为1
            SW_I2C_SDA_HIGH; 
        }
        else
        {	//最高位为2
            SW_I2C_SDA_LOW;
        }
        i2c_delay_us(I2C_DELAY);
		//将SCL拉高,数据稳定,等待读取
        SW_I2C_SCL_HIGH;
        i2c_delay_us(I2C_DELAY);
		//数据左移一位,下一循环将下一个bit发送出去
        aByte <<= 1;
    }
	//最后字节完成,将SCL拉低
    SW_I2C_SCL_LOW;
    i2c_delay_us(I2C_DELAY);
}

/************************************************************************************
*@fuction	:I2C_Read_Byte
*@brief		:
*@param		:--
*@return	:void
*@author	:_Awen
*@date		:2022-12-04
************************************************************************************/
uint8_t I2C_Read_Byte(uint8_t Ack)
{
	//读模式:在SCKL = 1时候,可读取SDA状态
    uint8_t i = 0 ,aByte;
	//SDA为输入模式
    SDA_IN();
    for (i = 0; i < 8; i++)
    {
        aByte <<= 1;
        SW_I2C_SCL_LOW;
        i2c_delay_us(I2C_DELAY);
		//将SCL拉高,读取数据稳定
        SW_I2C_SCL_HIGH;
        i2c_delay_us(I2C_DELAY);
        if(I2C_SDA_STATUS)
        {
            aByte |= 0x01;
        }
		//拉低SCL,解除锁定
        SW_I2C_SCL_LOW;
    }
	//读取一个字节,根据实际情况,是否发送ACK
    SDA_OUT();
    if(Ack)
    {
        Send_Ack();
    }
    else
    {
        Send_N_Ack();
    }
    return aByte;
}





//-------------------------------------------------------------------------------------------------------------------
//  @brief      写入一个字节数据到I2C设备指定寄存器地址
//  @param      SlaveID     从机地址(7位地址)
//  @param      reg         从机寄存器地址
//  @param      Data        数据
//  @return     void
//  @since      v2.0
//  Sample usage:       	i2c_write_reg(i2c0, 0x2D, 0x50,2);     //写入数据2到0x50地址,从机地址为0x2D
//-------------------------------------------------------------------------------------------------------------------
void i2c_Softwrite_reg_EEPROM(uint8_t SlaveID, uint16_t reg, uint8_t* Data,uint16_t number_of_byte)
{

    sw_i2c_start();                                    //发送启动信号

    I2C_Write_Byte(( SlaveID << 1 ) | 0x00);      //发送从机地址和写位
	 if(!sw_i2c_wait_ack())
	 {
			sw_i2c_stop();
		 return ;
	 }

    I2C_Write_Byte((uint8_t)(reg>>8));            //发送从机里的寄存器地址高位
	 	 if(!sw_i2c_wait_ack())
	 {
			sw_i2c_stop();
		 return ;
	 }
    I2C_Write_Byte((uint8_t)reg);                 //发送从机里的寄存器地址低位
	 	 if(!sw_i2c_wait_ack())
	 {
			sw_i2c_stop();
		 return ;
	 }

		while(number_of_byte--){
			 I2C_Write_Byte(*Data);                         //发送需要写入的数据
			 if(!sw_i2c_wait_ack())
			 {
					sw_i2c_stop();
					break;
			 }
			 i2c_delay();
		   Data++;
		}
    sw_i2c_stop();
		
    i2c_delay();                                        //延时太短的话,可能写出错
}
//-------------------------------------------------------------------------------------------------------------------
//  @brief      读取I2C设备指定地址寄存器的数据
//  @param      i2cn        I2C模块(i2c0、i2c1)
//  @param      SlaveID     从机地址(7位地址)
//  @param      reg         从机寄存器地址
//  @return                 读取的寄存器值
//  @since      v2.0
//  Sample usage:       	uint8 value = i2c_read_reg(i2c0, 0x2D, 0x50);//读取0x50地址的数据,从机地址为0x2D
//-------------------------------------------------------------------------------------------------------------------
void i2c_Softread_reg_EEPROM(uint8_t SlaveID, uint16_t reg,uint16_t num, uint8_t * addr)
{
    //先写入寄存器地址,再读取数据,因此此过程是 I2C 的复合格式,改变数据方向时需要重新启动
    //地址是低七位
    
    
    sw_i2c_start();                                      //发送启动信号

    I2C_Write_Byte(( SlaveID << 1 ) | 0x00);        //发送从机地址和写位
		 if(!sw_i2c_wait_ack())
	 {
			sw_i2c_stop();
		 return ;
	 }

    I2C_Write_Byte((uint8_t)(reg>>8));            //发送从机里的寄存器地址高位
	 	 if(!sw_i2c_wait_ack())
	 {
			sw_i2c_stop();
		 return ;
	 }
    I2C_Write_Byte((uint8_t)reg);                 //发送从机里的寄存器地址低位
	 	 if(!sw_i2c_wait_ack())
	 {
			sw_i2c_stop();
		 return ;
	 }

    sw_i2c_start();                             //复合格式,发送重新启动信号

    I2C_Write_Byte(( SlaveID << 1 ) | 0x01);      //发送从机地址和读位
   	 	 if(!sw_i2c_wait_ack())
	 {
			sw_i2c_stop();
		 return ;
	 }

    while(--num)
    {
        *addr=I2C_Read_Byte(1);                            //读取数据
        i2c_delay();                                    //必须延时一下,否则出错
        addr++;
    }
		*addr=I2C_Read_Byte(0);                            //读取最后一个字节,不应答
    i2c_delay(); 
    sw_i2c_stop();

    i2c_delay();                                        //必须延时一下,否则出错
}

void Init_IIC(void){
	#if IIC_Soft
	  rcu_periph_clock_enable(RCU_GPIOB);
	  gpio_init(WP_EE_GPIO_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, WP_EE_PIN);//输出模式
	  SCL_OUT();
	  SDA_OUT();
	#else
	  extern void i2c_config(void);//加这个避免编译器告警
    i2c_config();
	#endif
}

注: 移植主要对接.h文件相关io的宏 目前我使用的芯片平台主频为120Mhz。如果在其他平台使用 可能需要调试一下.c文件开头的延时

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值