使用C语言编写STM32 IIC软件

最近在重构自己的平衡车代码,里面需要用到MPU6050的DMP,从中读取四元数进行欧拉角解算,但是看着软件IIC的代码实在是很变扭,因为之前不会C++,所以如果需要调用多个IIC设备,那么使用的时候就需要重复的去进行软件IIC底层代码的初始化,非常的麻烦,而且需要调整各个引脚,在学习过C++之后,发现类实在是太好用了,那么我就在想能不能通过类把软件IIC的底层进行封装,实现和arduino一样的编程效果,使用的时候只需要放入软件IIC的SCL和SDA对应的GPIO即可。

1.环境

软件环境:keil CubeMX
硬件环境:STM32F103C8T6 MPU6050
使用引脚:PB8–>SCL PB9–>SDA

2.注意事项

软件IIC,顾名思义通过操作IO口的输入和输出,即使得IO口高低电平变换输出模拟数据发送,读取IO口高低电平实现数据接收,关于IIC通讯协议的介绍,本篇文章就不赘述了,这里要说的有以下几点:

a)实现IIC输出高低电平有以下几种方式:

HAL库:
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET);			
			HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET);		
标准库:
			GPIO_SetBits(GPIOB,GPIO_Pin_9);		
			GPIO_ResetBits(GPIOB,GPIO_Pin_9);
寄存器:
			寄存器有很多
			正点原子封装好的,可以以操作51单片机IO口的方式去操作STM32IO输入或者 输出:
			PBout(9) = 1;
			PBout(9) = 0;		//相信大家正点原子用的应该是最多的
			直接操作寄存器:
			GPIOB -> BSRR = GPIO_PIN_9;		//输出高电平
			GPIOB-> BRR = GPIO_PIN_9;			//输出低电平
这里的效率对比,从慢往快说:
		HAL库 、标准库、仿51 、 直接操作寄存器
 从我个人角度来说,我喜欢使用最后一种,直接操作寄存器,效率最高,执行最快,玩单片机玩的熟的都喜欢
 追求极致的效率,个人习惯看不见底层代码很难受,所以喜欢直接对寄存器进行操作。

b)IO口方向设置:(相信大家在移植软件IIC的时候这应该是最头痛的一件事情)

#define SDA_IN()  {GPIOB->CRH&=0XFFFFFF0F;GPIOB->CRH|=8<<4;}
#define SDA_OUT() {GPIOB->CRH&=0XFFFFFF0F;GPIOB->CRH|=3<<4;}
相信大家对这个已经深恶痛绝,每次需要更换IO口去实现的时候,都需要重新算,这里咱们就不多说关于STM
IO寄存器映射问题了,简单说一下这个IO口怎么映射,和让哪一位归0,如果使用的IO口是8-15,那么就是就是
高八位即对应CRH,反之0-7对应CRL,CRx &= 0XFFFFFFFF代表要操作哪一位,如果是GPIO10,那么高八位
从右想左第一个是8,第三个就是10,此时就应该是10,移动多少位,用(10-8)*4,可以计算出来是8,为什么要
*4,因为STM32是32位的,高低八位,每位各对应4个,即每个IO口对应4所处高低八位中的32位中的四位。

之所以要说这件事情,是因为之前本人的博客是关于[F4情况下软件IIC的使用](https://blog.csdn.net/weixin_44080304/article/details/123480999?spm=1001.2014.3001.5502),
那个时候在做项目,时间比较着急,所以就直接用HAL库上了,当然功能上是并不影响,因为传感器的通讯协议
和功能都比较简单对通讯的要求并不高,而且F4的主频也比较快并没有什么大的影响。

在重构自己平衡车代码的时候发现,软件IIC驱动MPU6050的时候,不设定IO口方向,我可以读取到6050发送的角速度和加速度信息,但是无法正常使用DMP模式,即无法直接读取出来四元数,经过查看单片机说明书和其他资料,发现是因为IO口方向设置问题,所以趁着这次封装,就一鼓作气全部搞定。

3软件IIC类

其中最重要的就是:
#define setbit(x,y) x|=(1<<y) //将X的第Y位置1
#define clrbit(x,y) x&=~(1<<y) //将X的第Y位清0
这段代码的作用是用于设置IO口方向的,最重要也是最容易忽视的一部分

#ifndef _SOFT_I2C1_H__
#define _SOFT_I2C1_H__

#include "header.h"

#define setbit(x,y) x|=(1<<y) //将X的第Y位置1
#define clrbit(x,y) x&=~(1<<y) //将X的第Y位清0

class SOFTIIC{
	private:
		GPIO_TypeDef *SCL_GPIOx;
		uint16_t      SCL_PIN;
		GPIO_TypeDef *SDA_GPIOx;
		uint16_t      SDA_PIN;
	
		uint32_t 			sda_io_num;    //对应的SDA引脚位移之后的数字
		uint8_t       sda_move;			// 输出需要移动的位数			
		uint8_t       temp;
	private:
		void SDA_IN_SET(void);			//IO方向设置,将IO设置为输入
		void SDA_OUT_SET(void);			//IO方向设置,将IO设置为输出50Mhz
		void I2C_SCL_LOW(void);			//SCL总线输出低电平
		void I2C_SCL_HIGH(void);			//SCL总线输出高电平
		void I2C_SDA_LOW(void);			  //SCL总线输出低电平
		void I2C_SDA_HIGH(void);			//SCL总线输出高电平
	public:
		SOFTIIC(){};	//构造函数
		SOFTIIC(GPIO_TypeDef *scl_GPIOx, uint16_t scl_GPIO_Pin,
			GPIO_TypeDef *sda_GPIOx, uint16_t sda_GPIO_Pin)			//重载构造函数
		{
			this->SCL_GPIOx = scl_GPIOx;
			this->SCL_PIN = scl_GPIO_Pin;
			this->SDA_GPIOx = sda_GPIOx;
			this->SDA_PIN = sda_GPIO_Pin;
		}	
		
		~SOFTIIC(){};	//析构函数
		
	public:
		void I2C_Soft_Init(void);
		void I2C_Soft_Delay(void);
		int I2C_Soft_Start(void);
		void I2C_Soft_Stop(void);
		void I2C_Soft_Ack(void);
		void I2C_Soft_NoAck(void);
		int I2C_Soft_WaitAck(void); 	 //返回:=1有ACK,=0无ACK
		void I2C_Soft_SendByte(u8 SendByte);
		u8 I2C_Soft_ReadByte(void);

		int I2C_Soft_Single_Read(unsigned char I2C_Addr,unsigned char addr);
		u8 IICwriteBits(u8 dev,u8 reg,u8 bitStart,u8 length,u8 data);
		/**************************实现函数********************************************
		*函数原型:		u8 IICreadByte(u8 dev, u8 reg, u8 *data)
		*功  能:	    读取指定设备 指定寄存器的一个值
		输入	dev  目标设备地址
				reg	   寄存器地址
				*data  读出的数据将要存放的地址
		返回   1
		*******************************************************************************/ 
		u8 IICreadByte(u8 dev, u8 reg, u8 *data);
		/**************************实现函数********************************************
		*函数原型:		u8 IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8 data)
		*功  能:	    读 修改 写 指定设备 指定寄存器一个字节 中的1个位
		输入	dev  目标设备地址
				reg	   寄存器地址
				bitNum  要修改目标字节的bitNum位
				data  为0 时,目标位将被清0 否则将被置位
		返回   成功 为1 
				失败为0
		*******************************************************************************/ 
		u8 IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8 data);
		/**************************实现函数********************************************
		*函数原型:		bool i2cWrite(uint8_t addr, uint8_t reg, uint8_t data)
		*功  能:		
		*******************************************************************************/
		int i2cWrite(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *data);
		/**************************实现函数********************************************
		*函数原型:		u8 IIC_Read_Byte(unsigned char ack)
		*功  能:	    //读1个字节,ack=1时,发送ACK,ack=0,发送nACK 
		*******************************************************************************/
		u8 I2C_Soft_ReadByte(unsigned char ack);  //数据从高位到低位//
		/**************************实现函数********************************************
		*函数原型:		u8 IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data)
		*功  能:	    读取指定设备 指定寄存器的 length个值
		输入	dev  目标设备地址
				reg	  寄存器地址
				length 要读的字节数
				*data  读出的数据将要存放的指针
		返回   读出来的字节数量
		*******************************************************************************/ 
		u8 IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data);
		/**************************实现函数********************************************
		*函数原型:		u8 IICwriteBytes(u8 dev, u8 reg, u8 length, u8* data)
		*功  能:	    将多个字节写入指定设备 指定寄存器
		输入	dev  目标设备地址
				reg	  寄存器地址
				length 要写的字节数
				*data  将要写的数据的首地址
		返回   返回是否成功
		*******************************************************************************/
		u8 IICwriteBytes(u8 dev, u8 reg, u8 length, u8* data);
		/**************************实现函数********************************************
		*函数原型:		unsigned char IICwriteByte(unsigned char dev, unsigned char reg, unsigned char data)
		*功  能:	    写入指定设备 指定寄存器一个字节
		输入	dev  目标设备地址
				reg	   寄存器地址
				data  将要写入的字节
		返回   1
		*******************************************************************************/ 
		unsigned char IICwriteByte(unsigned char dev, unsigned char reg, unsigned char data);
		/**************************实现函数********************************************
		*函数原型:		bool i2cWrite(uint8_t addr, uint8_t reg, uint8_t data)
		*功  能:		
		*******************************************************************************/
		int i2cRead(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf);
};

#endif

具体实现:

#include "softiic.h"

//模拟IIC初始化
void SOFTIIC::I2C_Soft_Init(void)
{
	uint8_t clear;
	
	switch(SDA_PIN)
	{
		case GPIO_PIN_0:
			temp = 0;
			break;
		case GPIO_PIN_1:
			temp = 1;
			break;
		case GPIO_PIN_2:
			temp = 2;
			break;
		case GPIO_PIN_3:
			temp = 3;
			break;
		case GPIO_PIN_4:
			temp = 4;
			break;
		case GPIO_PIN_5:
			temp = 5;
			break;
		case GPIO_PIN_6:
			temp = 6;
			break;
		case GPIO_PIN_7:
			temp = 7;
			break;
		case GPIO_PIN_8:
			temp = 8;
			break;
		case GPIO_PIN_9:
			temp = 9;
			break;
		case GPIO_PIN_10:
			temp = 10;
			break;
		case GPIO_PIN_11:
			temp = 11;
			break;
		case GPIO_PIN_12:
			temp = 12;
			break;
		case GPIO_PIN_13:
			temp = 13;
			break;
		case GPIO_PIN_14:
			temp = 14;
			break;
		case GPIO_PIN_15:
			temp = 15;
			break;			
		default:
			break;
	}
	
	if(temp <= 7)
	{
		clear = temp;
	}
	else if(temp > 7)
	{
		clear = temp-8;
	}
	sda_move = clear*4;
	sda_io_num = (uint32_t)4294967295;
	clrbit(sda_io_num,(clear*4));
	clrbit(sda_io_num,(clear*4+1));
	clrbit(sda_io_num,(clear*4+2));
	clrbit(sda_io_num,(clear*4+3));
}

void SOFTIIC::SDA_IN_SET(void)		//IO口输入设置
{
	if(temp <= 7)
	{
		SDA_GPIOx->CRL &= sda_io_num;
		SDA_GPIOx->CRL |= 8<<sda_move;
	}
	else if(temp >7)
	{
		SDA_GPIOx->CRH &= sda_io_num;
		SDA_GPIOx->CRH |= 8<<sda_move;
	}
}

void SOFTIIC::SDA_OUT_SET(void)		// IO口输出设置
{
	if(temp <= 7)
	{
		SDA_GPIOx->CRL &= sda_io_num;
		SDA_GPIOx->CRL |= 3<<sda_move;
	}
	else if(temp >7)
	{
		SDA_GPIOx->CRH &= sda_io_num;
		SDA_GPIOx->CRH |= 3<<sda_move;
	}
}

void SOFTIIC::I2C_SCL_LOW(void)			//SCL总线输出低电平
{
	SCL_GPIOx->BRR = SCL_PIN;
}
void SOFTIIC::I2C_SCL_HIGH(void)			//SCL总线输出高电平
{
	SCL_GPIOx->BSRR = SCL_PIN;
}
void SOFTIIC::I2C_SDA_LOW(void)			  //SCL总线输出低电平
{
	SDA_GPIOx->BRR = SDA_PIN;
}
void SOFTIIC::I2C_SDA_HIGH(void)			//SCL总线输出高电平
{
	SDA_GPIOx->BSRR = SDA_PIN;
}

/*软件延迟*/
void SOFTIIC::I2C_Soft_Delay(void)
{
	delay_us(1);
}

/*开始信号,SCL为高时,SDA由高电平变为低电平*/
int SOFTIIC::I2C_Soft_Start(void)
{
//sda线输出
	SDA_OUT_SET();
	I2C_SDA_HIGH();
	if(!(SDA_GPIOx->IDR & SDA_PIN))return 0;	
	I2C_SCL_HIGH();
	delay_us(1);
 	I2C_SDA_LOW();//START:when CLK is high,DATA change form high to low 
	if((SDA_GPIOx->IDR & SDA_PIN))return 0;
	delay_us(1);
	I2C_SCL_LOW();//钳住I2C总线,准备发送或接收数据 
	return 1;
}
/*停止信号,SCL为高时,SDA由低电平变为高电平*/
void SOFTIIC::I2C_Soft_Stop(void)
{
//sda线输出
	SDA_OUT_SET();
	I2C_SCL_LOW();
	I2C_SDA_LOW();//STOP:when CLK is high DATA change form low to high
 	delay_us(1);
	I2C_SCL_HIGH(); 
	I2C_SDA_HIGH();//发送I2C总线结束信号
	delay_us(1);							
} 

/*应答信号,在第九个时钟脉冲来临前将SDA拉低,并在SCL=1期间,SDA稳定为低*/
void SOFTIIC::I2C_Soft_Ack(void)
{	
	I2C_SCL_LOW();
	SDA_OUT_SET();
	I2C_SDA_LOW();
	delay_us(1);
	I2C_SCL_HIGH();
	delay_us(1);
	I2C_SCL_LOW();
}   
/*不产生应答信号,第9个时钟脉冲来临前将SDA拉低,并在SCL=1期间,SDA稳定为高*/
void SOFTIIC::I2C_Soft_NoAck(void)
{	
	I2C_SCL_LOW();
	SDA_OUT_SET();
	I2C_SDA_HIGH();
	delay_us(1);
	I2C_SCL_HIGH();
	delay_us(1);
	I2C_SCL_LOW();
} 

/*等待应答*/
int SOFTIIC::I2C_Soft_WaitAck(void) 	 //返回为:=1有ACK,=0无ACK
{
	u8 ucErrTime=0;
	SDA_IN_SET();
	I2C_SDA_HIGH();
	delay_us(1);	   
	I2C_SCL_HIGH();
	delay_us(1);	 
	while((SDA_GPIOx->IDR & SDA_PIN))
	{
		ucErrTime++;
		if(ucErrTime>50)
		{
			I2C_Soft_Stop();
			return 0;
		}
	  delay_us(1);
	}
	I2C_SCL_LOW();//时钟输出0 	   
	return 1; 
}
/*字节发送函数*/
void SOFTIIC::I2C_Soft_SendByte(u8 SendByte)
{
    u8 t;    	   
		SDA_OUT_SET();	
    I2C_SCL_LOW();//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        if((SendByte&0x80)>>7 == 1){
					I2C_SDA_HIGH();
				}
				else{
					I2C_SDA_LOW();
				}
        SendByte<<=1; 	  
		delay_us(1);   
		I2C_SCL_HIGH();
		delay_us(1); 
		I2C_SCL_LOW();	
		delay_us(1);
    }	
}

/**************************实现函数********************************************
*函数原型:		bool i2cWrite(uint8_t addr, uint8_t reg, uint8_t data)
*功  能:		
*******************************************************************************/
int SOFTIIC::i2cWrite(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *data)
{
		int i;
    if (!I2C_Soft_Start())
        return 1;
    I2C_Soft_SendByte(addr << 1 );
    if (!I2C_Soft_WaitAck()) {
        I2C_Soft_Stop();
        return 1;
    }
    I2C_Soft_SendByte(reg);
    I2C_Soft_WaitAck();
		for (i = 0; i < len; i++) {
        I2C_Soft_SendByte(data[i]);
        if (!I2C_Soft_WaitAck()) {
            I2C_Soft_Stop();
            return 0;
        }
    }
    I2C_Soft_Stop();
    return 0;
}

/**************************实现函数********************************************
*函数原型:		bool i2cWrite(uint8_t addr, uint8_t reg, uint8_t data)
*功  能:		
*******************************************************************************/
int SOFTIIC::i2cRead(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf)
{
    if (!I2C_Soft_Start())
        return 1;
    I2C_Soft_SendByte(addr << 1);
    if (!I2C_Soft_WaitAck()) {
        I2C_Soft_Stop();
        return 1;
    }
    I2C_Soft_SendByte(reg);
    I2C_Soft_WaitAck();
    I2C_Soft_Start();
    I2C_Soft_SendByte((addr << 1)+1);
    I2C_Soft_WaitAck();
    while (len) {
        if (len == 1)
            *buf = I2C_Soft_ReadByte(0);
        else
            *buf = I2C_Soft_ReadByte(1);
        buf++;
        len--;
    }
    I2C_Soft_Stop();
    return 0;
}


///**************************实现函数********************************************
//*函数原型:		void IIC_Send_Byte(u8 txd)
//*功  能:	    IIC发送一个字节
//*******************************************************************************/		  
u8 SOFTIIC::I2C_Soft_ReadByte(unsigned char ack)  //数据从高位到低位//
{ 
	unsigned char i,receive=0;
	SDA_IN_SET();
    for(i=0;i<8;i++ )
	{
        I2C_SCL_LOW(); 
        delay_us(2);
		I2C_SCL_HIGH();
        receive<<=1;
        if((SDA_GPIOx->IDR & SDA_PIN))receive++;   
		delay_us(2); 
    }					 
    if (ack)
        I2C_Soft_Ack(); //发送ACK 
    else
        I2C_Soft_NoAck();//发送nACK  
    return receive;
} 

/**************************实现函数********************************************
*函数原型:		unsigned char I2C_ReadOneByte(unsigned char I2C_Addr,unsigned char addr)
*功  能:	    读取指定设备 指定寄存器的一个值
输入	I2C_Addr  目标设备地址
		addr	   寄存器地址
返回   读出来的值
*******************************************************************************/
int SOFTIIC::I2C_Soft_Single_Read(unsigned char I2C_Addr,unsigned char addr)
{   
	unsigned char res=0;
	
	I2C_Soft_Start();	
	I2C_Soft_SendByte(I2C_Addr);	   //发送写命令
	res++;
	I2C_Soft_WaitAck();
	I2C_Soft_SendByte(addr); res++;  //发送地址
	I2C_Soft_WaitAck();	  
	I2C_Soft_Start();
	I2C_Soft_SendByte(I2C_Addr+1); res++;          //进入接收模式			   
	I2C_Soft_WaitAck();
	res=I2C_Soft_ReadByte(0);	   
  I2C_Soft_Stop();//产生一个停止条件

	return res;
}

/**************************实现函数********************************************
*函数原型:		u8 IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data)
*功  能:	    读取指定设备 指定寄存器的 length个值
输入	dev  目标设备地址
		reg	  寄存器地址
		length 要读的字节数
		*data  读出的数据将要存放的指针
返回   读出来的字节数量
*******************************************************************************/ 
u8 SOFTIIC::IICreadBytes(u8 dev, u8 reg, u8 length, u8 *data)
{
    u8 count = 0;
	
	I2C_Soft_Start();
	I2C_Soft_SendByte(dev);	   //发送写命令
	I2C_Soft_WaitAck();
	I2C_Soft_SendByte(reg);   //发送地址
    I2C_Soft_WaitAck();	  
	I2C_Soft_Start();
	I2C_Soft_SendByte(dev+1);  //进入接收模式	
	I2C_Soft_WaitAck();
	
    for(count=0;count<length;count++){
		 
		 if(count!=length-1)data[count]=I2C_Soft_ReadByte(1);  //带ACK的读数据
		 	else  data[count]=I2C_Soft_ReadByte(0);	 //最后一个字节NACK
	}
    I2C_Soft_Stop();//产生一个停止条件
    return count;
}

/**************************实现函数********************************************
*函数原型:		u8 IICwriteBytes(u8 dev, u8 reg, u8 length, u8* data)
*功  能:	    将多个字节写入指定设备 指定寄存器
输入	dev  目标设备地址
		reg	  寄存器地址
		length 要写的字节数
		*data  将要写的数据的首地址
返回   返回是否成功
*******************************************************************************/ 
u8 SOFTIIC::IICwriteBytes(u8 dev, u8 reg, u8 length, u8* data){
  
 	u8 count = 0;
	I2C_Soft_Start();
	I2C_Soft_SendByte(dev);	   //发送写命令
	I2C_Soft_WaitAck();
	I2C_Soft_SendByte(reg);   //发送地址
    I2C_Soft_WaitAck();	  
	for(count=0;count<length;count++){
		I2C_Soft_SendByte(data[count]); 
		I2C_Soft_WaitAck(); 
	 }
	I2C_Soft_Stop();//产生一个停止条件

    return 1; //status == 0;
}

/**************************实现函数********************************************
*函数原型:		u8 IICreadByte(u8 dev, u8 reg, u8 *data)
*功  能:	    读取指定设备 指定寄存器的一个值  IIC读取一位的数值,输出要存放的地址
输入	dev  目标设备地址
		reg	   寄存器地址
		*data  读出的数据将要存放的地址
返回   1
*******************************************************************************/ 
u8 SOFTIIC::IICreadByte(u8 dev, u8 reg, u8 *data){
	*data=I2C_Soft_Single_Read(dev, reg);
    return 1;
}

/**************************实现函数********************************************
*函数原型:		unsigned char IICwriteByte(unsigned char dev, unsigned char reg, unsigned char data)
*功  能:	    写入指定设备 指定寄存器一个字节
输入	dev  目标设备地址
		reg	   寄存器地址
		data  将要写入的字节
返回   1
*******************************************************************************/ 
unsigned char SOFTIIC::IICwriteByte(unsigned char dev, unsigned char reg, unsigned char data){
    return IICwriteBytes(dev, reg, 1, &data);
}


/**************************实现函数********************************************
*函数原型:		u8 IICwriteBits(u8 dev,u8 reg,u8 bitStart,u8 length,u8 data)
*功  能:	    读 修改 写 指定设备 指定寄存器一个字节 中的多个位  多字节写入
输入	dev  目标设备地址
		reg	   寄存器地址
		bitStart  目标字节的起始位
		length   位长度
		data    存放改变目标字节位的值
返回   成功 为1 
 		失败为0
*******************************************************************************/
u8 SOFTIIC::IICwriteBits(u8 dev,u8 reg,u8 bitStart,u8 length,u8 data)
{
	u8 b;
	if (IICreadByte(dev, reg, &b) != 0) {
			u8 mask = (0xFF << (bitStart + 1)) | 0xFF >> ((8 - bitStart) + length - 1);
			data <<= (8 - length);
			data >>= (7 - bitStart);
			b &= mask;
			b |= data;
			return IICwriteByte(dev, reg, b);
	} else {
			return 0;
	}	
}

/**************************实现函数********************************************
*函数原型:		u8 IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8 data)
*功  能:	    读 修改 写 指定设备 指定寄存器一个字节 中的1个位
输入	dev  目标设备地址
		reg	   寄存器地址
		bitNum  要修改目标字节的bitNum位
		data  为0 时,目标位将被清0 否则将被置位
返回   成功 为1 
 		失败为0
*******************************************************************************/ 
u8 SOFTIIC::IICwriteBit(u8 dev, u8 reg, u8 bitNum, u8 data){
    u8 b;
    IICreadByte(dev, reg, &b);
    b = (data != 0) ? (b | (1 << bitNum)) : (b & ~(1 << bitNum));
    return IICwriteByte(dev, reg, b);
}

首先声明其中对于IO口初始化部分和输入输出设置的函数绝对本人原创,这其中需要重点说的一部分:
此部分的作用就是确认IO口输入输出方向设定的时候,应当进行如何移位,以及移几位,如果使用一定要认真查看此部分代码。

//模拟IIC初始化
void SOFTIIC::I2C_Soft_Init(void)
{
	uint8_t clear;
	
	switch(SDA_PIN)
	{
		case GPIO_PIN_0:
			temp = 0;
			break;
		case GPIO_PIN_1:
			temp = 1;
			break;
		case GPIO_PIN_2:
			temp = 2;
			break;
		case GPIO_PIN_3:
			temp = 3;
			break;
		case GPIO_PIN_4:
			temp = 4;
			break;
		case GPIO_PIN_5:
			temp = 5;
			break;
		case GPIO_PIN_6:
			temp = 6;
			break;
		case GPIO_PIN_7:
			temp = 7;
			break;
		case GPIO_PIN_8:
			temp = 8;
			break;
		case GPIO_PIN_9:
			temp = 9;
			break;
		case GPIO_PIN_10:
			temp = 10;
			break;
		case GPIO_PIN_11:
			temp = 11;
			break;
		case GPIO_PIN_12:
			temp = 12;
			break;
		case GPIO_PIN_13:
			temp = 13;
			break;
		case GPIO_PIN_14:
			temp = 14;
			break;
		case GPIO_PIN_15:
			temp = 15;
			break;			
		default:
			break;
	}
	
	if(temp <= 7)
	{
		clear = temp;
	}
	else if(temp > 7)
	{
		clear = temp-8;
	}
	sda_move = clear*4;
	sda_io_num = (uint32_t)4294967295;
	clrbit(sda_io_num,(clear*4));
	clrbit(sda_io_num,(clear*4+1));
	clrbit(sda_io_num,(clear*4+2));
	clrbit(sda_io_num,(clear*4+3));
}

4使用方式:

构造软件IIC类
SOFTIIC softiic1(GPIOB,GPIO_PIN_8,GPIOB,GPIO_PIN_9); // 前两个对应SCL引脚,后两个对应SDA引脚
如果需要用很多,那么根据自己需要进行构造即可
SOFTIIC softiic2(GPIOB,GPIO_PIN_10,GPIOB,GPIO_PIN_11);
SOFTIIC softiic3(GPIOA,GPIO_PIN_4,GPIOA,GPIO_PIN_5);
调用上,在需要使用软件IIC通讯的传感器,底层通讯协议上直接使用,这里的示例使用的是MPU6050的部分初始化

//MPU6050初始化,传入参数:采样率,低通滤波频率
void MPU6050::Mpu6050_Init(void)
{
	//设置时钟源
	softiic1.IICwriteBits(MPU6050_ADDRESS, MPU_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH, MPU6050_CLOCK_PLL_YGYRO);
	//设定陀螺仪采样的范围  陀螺仪最大量程 +-2000度每秒
	softiic1.IICwriteBits(MPU6050_ADDRESS, MPU_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH, MPU6050_GYRO_FS_1000);
	//加速度度最大量程 +-2G
	softiic1.IICwriteBits(MPU6050_ADDRESS, MPU_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH,MPU6050_ACCEL_FS_2);
	//设定工作模式 0工作 1睡眠  进入工作状态
	softiic1.IICwriteBit(MPU6050_ADDRESS,MPU_RA_PWR_MGMT_1,MPU6050_PWR1_SLEEP_BIT,0);
	//设置 MPU6050 是否为AUX I2C线的主机  0不让MPU6050 控制AUXI2C
	softiic1.IICwriteBit(MPU6050_ADDRESS,MPU_RA_USER_CTRL,MPU6050_USERCTRL_I2C_MST_EN_BIT,0);
	//设置 MPU6050 是否为AUX I2C线的主机 主控制器的I2C与	MPU6050的AUXI2C	直通控制器可以直接访问HMC5883L
	softiic1.IICwriteBit(MPU6050_ADDRESS, MPU_RA_INT_PIN_CFG, MPU6050_INTCFG_I2C_BYPASS_EN_BIT,0);
	/* DMP模式初始化 */
	DMP_Init();
}

5效果:

理论上适用于任何IIC通讯的传感器或者说设备,执行效率上达到最高,因为直接面向寄存器进行操作,虽然底层IO初始化使用的是HAL库,但是这并不会影响到以上代码的执行效率,个人通过以上代码是成功的可以对MPU6050的DMP模式进行初始化,并且能够正常使用。以上代码对应的是STM32F1系列单片机的寄存器,如果往其它系列的32中进行移植,需要自己修改。
后续可能会更新软件串口、软件SPI等IO口可以模拟的通讯协议的底层代码封装。

物联沃分享整理
物联沃-IOTWORD物联网 » 使用C语言编写STM32 IIC软件

发表评论