由于有时候出现硬件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文件开头的延时