STM32实战总结:HAL之I2C

I2C基础知识参考:

嵌入式常见接口协议总结_路溪非溪的博客-CSDN博客

IIC读写数据的具体时序过程

参考:I2C通信时序解析_iic时序-CSDN博客

IIC

1. 起动

时钟线拉高,数据线下降沿触发。时钟线再拉低,为数据输入作好准备

2. 写数据

数据准备好,时钟线拉高,保持时间,再拉低为下一位数据作准备,共8位

3. 读ACK

释放数据线,时钟线拉高保持,读取数据线。时钟线拉低,准备好数据输入

4. 停止

时钟线拉高,数据线上升沿触发

驱动示例:

void i2c_start(void)//开始
{
	i2c_sda_output();//将SDA引脚配置成输出
	i2c_set_sda(1);//将SDA拉高
	i2c_set_scl(1);//将SDA拉高
	DelayUS(20);
	i2c_set_sda(0);//SCL高电平期间,将SDA拉低,SDA从高电平到低电平,表示传输开始
	DelayUS(2);
	i2c_set_scl(0);//此时,将SCL拉低,准备发送数据
    //只有当SCL为低电平时,才能将数据放到SDA上,因为如果是SCL高电平时来改变SDA的数据,就变成开始或者结束信号了
	DelayUS(2);
}

void i2c_stop(void)
{
	i2c_sda_output();//将SDA引脚配置成输出
	i2c_set_scl(0);//将SCL拉低
	i2c_set_sda(0);//将SDA拉低
	i2c_set_scl(1);//接着将SCL拉高
	DelayUS(2);
	i2c_set_sda(1);//SCL高电平期间,SDA从低电平到高电平,表示传输停止,此时SCL和SDA都处于高电平,即空闲状态
	DelayUS(2);
}

uint8_t i2c_write(uint8_t value)
{
	uint8_t i, j, ack;

	i2c_sda_output();//配置SDA为输出
	i2c_set_scl(0);//拉低SCL
    //只有当SCL为低电平时,才能将数据放到SDA上,因为如果是SCL高电平时来改变SDA的数据,就变成开始或者结束信号了
	for (i = 0; i < 8; i++)
	{
		if (value & 0x80)//将要发送的数据放到SDA上
			i2c_set_sda(1);
		else
			i2c_set_sda(0);

		DelayUS(2);
		i2c_set_scl(1);//拉高SCL电平
		DelayUS(3);//保持高电平一段时间,此时SDA的数据不能有变化
		i2c_set_scl(0);//发送完成,将SCL拉低,并不需要在发送每个位时都判断一次是否发送完成
		value = value << 1;//发送下一个bit
	}
	i2c_sda_input();//到这里,一个字节的数据就发送完成了,此时,开始接收应答信号ACK,故将SDA配置成输入,此时,SDA的主动权在从机那边
	DelayUS(20);
	i2c_set_scl(1);//上面发送完成后,SCL处于低电平,因此我们这里拉高SCL,准备接收ACK,因为需要在SCL高电平期间传输数据
	DelayUS(1);
	ack = i2c_get_sda();//SCL高电平期间,主机读取SDA的数据
	i2c_set_scl(0);//读取结束后,接着拉低SCL

	return ack;//返回ack,调用处判断是否写入成功
}

uint8_t i2c_read(uint8_t ack)//传递参数,标记是否需要在读取时发送ACK
{
	uint8_t i, j, u8Tmp;

	i2c_sda_input();//配置SDA为输入
	u8Tmp = 0x00;
	for (i = 0; i < 8; i++)
	{
		u8Tmp = u8Tmp << 1;

		i2c_set_scl(0);
		DelayUS(3);
		i2c_set_scl(1);//主机是SCL低电平时往SDA放数据,放好后,拉高SCL来传递数据;从机发送好数据时,主机只需要拉高SCL,让数据传递过来
		DelayUS(2);
		if (i2c_get_sda())//当数据传递过来时,读取SDA上的数据即可
			u8Tmp |= 0x01;
	}

	i2c_set_scl(0);//读完一个字节后,拉低SCL
	i2c_sda_output();//读完之后再将SDA配置成输出,准备发送ACK

	if (ack)//判断是否发送ACK,并根据情况往SDA放置不同的数据
		i2c_set_sda(0);
	else
		i2c_set_sda(1);

	DelayUS(20);
	i2c_set_scl(1);//拉高SCL以发送ACK
	DelayUS(2);
	i2c_set_scl(0);//发送完成后拉低即可

	return u8Tmp;
}//不管是主机还是从机,都是在SCL高电平期间传输数据,实际应该是上升沿吧?

void read_xy(void)
{
	uint8_t i;
	uint32_t crcVal;
	i2c_start();
	i2c_write(0x82);
	i2c_write(0x10);
	i2c_stop();

	i2c_start();
	i2c_write(0x82 + 1);

	for (i = 0; i < 31; i++)
	{
		IIC_Buf[i] = i2c_read(1);
	}

	i2c_read(0);
	i2c_stop();

	u32Tmp = IIC_Buf[1] & 0x7F;
	u32Tmp = (u32Tmp << 8) & 0xFF00;
	u32Tmp += IIC_Buf[2];
	tmpX = u32Tmp;

	u32Tmp = IIC_Buf[3] & 0x7F;
	u32Tmp = (u32Tmp << 8) & 0xFF00;
	u32Tmp += IIC_Buf[4];
	tmpY = u32Tmp;
}

电路图

扩展的I2C接口,可以连接支持I2C的设备。常见于传感器等。

参考手册

目前大部分MCU都带有IIC总线接口,STM32F1也不例外。但是这里我们不使用STM32F1的硬件IIC,而是通过软件模拟。原因是ST为了规避飞利浦IIC专利问题,将STM32的硬件IIC设计的比较复杂,且稳定性不怎么好。
用软件模拟IIC,最大的好处就是方便移植, 同一个代码兼容所有MCU,任何一个单片机只要有IO口,就可以很快的移植过去,而且不需要特定的IO口。而硬件IIC,则换一款 MCU,基本上就得重新搞一次,移植是比较麻烦的,这是我们推荐使用软件模拟IIC的另外一个原因。

自行阅读参考手册,大概了解下STM32的IIC。

此处通过SHT30的使用来学习IIC协议。

电路图如下:

注意,这里的引脚连接没有连到硬件IIC上。

所以,就算想用硬件IIC也用不起来。

STN30

STN30是一款温湿度传感器芯片,关键性能如下:

Wide supply voltage range, from 2.4 to 5.5 V

I2C Interface with communication speeds up to 1MHz and two user selectable addresses Typical accuracy of 2%RH and 0.3°C

The sensor shows best performance when operated within recommended normal temperature and humidity range of 5 – 60 °C and 20 – 80 %RH

饮片引脚如下:

 

设备地址:

上面的原理图中,ADDR接了地,所以这里的地址是0x44.

为了提高准确度,该芯片采用多次测量取均值的策略。所以,当重复度越高的时候,所用的时间会越长。

更多自行阅读手册。

GPIO配置

因为是用软件I2C,所以只用配置对应的两个GPIO口,即PG11和PG12。

要注意的一点是:

我们在配置时,需要将端口配置成输出或者输入,但是,在I2C工作过程中,既需要输出,又需要输入,通过半双工来完成整个过程。

那怎么办呢?怎么能让一个IO口既是输出也是输入呢?

当引脚配置成输出,并且是开漏模式时,引脚对输入的数据也能进行读取。

不过,读取时需要先输出一个高电平,关闭内部的NMOS管,才能作为输入使用。

这就是STM32的准双向口。

关键代码

I2C.c

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/
//置位与清零SCL管脚
#define	SET_SCL	HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_SET) 
#define	CLR_SCL	HAL_GPIO_WritePin(SHT30_SCL_GPIO_Port,SHT30_SCL_Pin,GPIO_PIN_RESET)
//置位与清零SDA管脚
#define	SET_SDA	HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_SET)
#define	CLR_SDA	HAL_GPIO_WritePin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin,GPIO_PIN_RESET)
//读SDA管脚状态
#define READ_SDA	HAL_GPIO_ReadPin(SHT30_SDA_GPIO_Port,SHT30_SDA_Pin)

/* Private variables----------------------------------------------------------*/
void Init(void);  //I2C初始化
void Start(void); //I2C起始信号
void Stop(void);  //I2C停止信号
ACK_Value_t Write_Byte(uint8_t);      //I2C写字节
uint8_t     Read_Byte (ACK_Value_t);  //I2C读字节

/* Public variables-----------------------------------------------------------*/
I2C_Soft_t I2C_Soft = 
{
	Init,
	Start,
	Stop,
	Write_Byte,
	Read_Byte
};

/* Private function prototypes------------------------------------------------*/      
static void I2C_Delay_us(uint8_t);

/*
	* @name   Init
	* @brief  I2C初始化
	* @param  None
	* @retval None      
*/
static void Init(void)
{
	SET_SCL;
	SET_SDA;
}

/*
	* @name   Start
	* @brief  I2C起始信号
	* @param  None
	* @retval None      
*/
static void Start(void)
{
	//SCL为高电平,SDA的下降沿为I2C起始信号
	SET_SDA;
	SET_SCL;
	I2C_Delay_us(1);
	
	CLR_SDA;
	I2C_Delay_us(10);
	
	CLR_SCL;
	I2C_Delay_us(1);
}

/*
	* @name   Stop
	* @brief  I2C停止信号
	* @param  None
	* @retval None      
*/
static void Stop(void)
{
	//SCL为高电平,SDA的上升沿为I2C停止信号
	CLR_SDA;
	SET_SCL;
	I2C_Delay_us(1);
		
	I2C_Delay_us(10);
	SET_SDA;
}

/*
	* @name   Write_Byte
	* @brief  I2C写字节
	* @param  WR_Byte -> 待写入数据
	* @retval ACK_Value_t -> 从机应答值      
*/
static ACK_Value_t Write_Byte(uint8_t WR_Byte)
{
	uint8_t i;
	ACK_Value_t  ACK_Rspond;
	
	//SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
	//数据按8位传输,高位在前,利用for循环逐个接收
	for(i=0;i<8;i++)
	{
		//SCL清零,主机SDA准备数据
		CLR_SCL;
		I2C_Delay_us(1);
		if((WR_Byte&BIT7) == BIT7)
		{
			SET_SDA;
		}
		else
		{
			CLR_SDA;
		}
		I2C_Delay_us(1);
		//SCL置高,传输数据
		SET_SCL;
		I2C_Delay_us(10);
		
		//准备发送下一比特位
		WR_Byte <<= 1;
	}
	
	CLR_SCL;	
	//释放SDA,等待从机应答
	SET_SDA;
	I2C_Delay_us(1);
	
	SET_SCL;
	I2C_Delay_us(10);
	
	ACK_Rspond = (ACK_Value_t)READ_SDA;
	
	CLR_SCL;
	I2C_Delay_us(1);
	
	//返回从机的应答信号
	return ACK_Rspond;
}

/*
	* @name   Write_Byte
	* @brief  I2C写字节
	* @param  ACK_Value -> 主机回应值
	* @retval 从机返回值      
*/
static uint8_t Read_Byte(ACK_Value_t ACK_Value)
{
	uint8_t RD_Byte = 0,i;
		
	接收数据
	//SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据
	//数据按8位传输,高位在前,利用for循环逐个接收
	for(i=0;i<8;i++)
	{
		//准备接收下一比特位
		RD_Byte <<= 1;
		
		//SCL清零,从机SDA准备数据
		CLR_SCL;
		I2C_Delay_us(10);
		
		//SCL置高,获取数据
		SET_SCL;
		I2C_Delay_us(10);	

		RD_Byte |= READ_SDA;		
	}
	
	
	//SCL清零,主机准备应答信号
	CLR_SCL;
	I2C_Delay_us(1);
	
	//主机发送应答信号	
	if(ACK_Value == ACK)
	{
		CLR_SDA;
	}
	else
	{
		SET_SDA;	
  }	
	I2C_Delay_us(1);
	
	
	SET_SCL; 	
	I2C_Delay_us(10);
	
	//Note:
  //释放SDA数据线
	//SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号
	CLR_SCL;
  SET_SDA; 	
	I2C_Delay_us(1);

	//返回数据
	return RD_Byte;
}

/*
	* @name   I2C_Delay
	* @brief  I2C延时
	* @param  None
	* @retval None      
*/
static void I2C_Delay_us(uint8_t us)
{
	uint8_t i = 0;
	
	//通过示波器测量进行校准
	while(us--)
	{
		for(i=0;i<7;i++);
	}
}
/********************************************************
  End Of File
********************************************************/

SHT30.c

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/
void Measure_Period_Mode(void);  //周期测量模式

/* Public variables-----------------------------------------------------------*/
SHT30_t SHT30 = 
{
	0.0,
	0,
	Measure_Period_Mode
};

/* Private function prototypes------------------------------------------------*/      
static uint8_t CRC_8(uint8_t *,uint8_t);


/*
	* @name   Measure_Period_Mode
	* @brief  周期测量模式
	* @param  None
	* @retval None      
*/
static void Measure_Period_Mode(void)
{
  uint8_t   temp_array[6] = {0};
	uint16_t  temp_uint     = 0;
	float     temp_float    = 0;

	//启动周期性测量
	I2C_Soft.Start();
	I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
	I2C_Soft.Write_Byte(0x27); //High repeat , mps = 10
	I2C_Soft.Write_Byte(0x37);
	
	Timer6.SHT30_Measure_Timeout = 0;
	//发送接收数据命令
	do
	{		
		if(Timer6.SHT30_Measure_Timeout >= TIMER6_2S) //2s内没获取到数据,退出等待
			break;
		
		I2C_Soft.Start();
		I2C_Soft.Write_Byte(SHT30_ADDR & Write_CMD);
		I2C_Soft.Write_Byte(0xE0);
		I2C_Soft.Write_Byte(0x00);
		
		I2C_Soft.Start();
	}
	while(I2C_Soft.Write_Byte(SHT30_ADDR | Read_CMD) ==NACK);
		
	//开始接收测量数据,并计算
	if(Timer6.SHT30_Measure_Timeout < TIMER6_2S)
	{
		temp_array[0] = I2C_Soft.Read_Byte(ACK);
		temp_array[1] = I2C_Soft.Read_Byte(ACK);
		temp_array[2] = I2C_Soft.Read_Byte(ACK);
		temp_array[3] = I2C_Soft.Read_Byte(ACK);
		temp_array[4] = I2C_Soft.Read_Byte(ACK);
		temp_array[5] = I2C_Soft.Read_Byte(NACK);
		I2C_Soft.Stop();
		
		//计算温度,精度0.1
		if(CRC_8(temp_array,2) == temp_array[2]) //CRC-8 校验
		{
			temp_uint         = temp_array[0]*256+temp_array[1];
			temp_float        = ((float)temp_uint)*0.267032-4500;
			SHT30.fTemperature = temp_float*0.01;
	  }
		
		//计算湿度,精度1%RH
		if(CRC_8(&temp_array[3],2) == temp_array[5]) //CRC-8 校验
		{
			temp_uint      = temp_array[3]*256+temp_array[4];
			temp_float     = ((float)temp_uint)*0.152590;
			temp_float     = temp_float*0.01;
			SHT30.ucHumidity = (unsigned char)temp_float;  
	  }
	}
}

/*
	* @name   CRC_8
	* @brief  CRC-8校验
	* @param  Crc_ptr -> 校验数据首地址
						LEN     -> 校验数据长度
	* @retval CRC_Value -> 校验值      
*/
static uint8_t CRC_8(uint8_t *Crc_ptr,uint8_t LEN)
{
	uint8_t CRC_Value = 0xFF;
	uint8_t i = 0,j = 0;

	for(i=0;i<LEN;i++)
	{
		CRC_Value ^= *(Crc_ptr+i);
		for(j=0;j<8;j++)
		{
			if(CRC_Value & 0x80)
				CRC_Value = (CRC_Value << 1) ^ 0x31;
			else
				CRC_Value = (CRC_Value << 1);
		}
	}
	return CRC_Value;
}

/********************************************************
  End Of File
********************************************************/

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32F103C8T6芯片上使用HAL库进行I2C配置需要以下步骤: 1. 首先,要在STM32CubeMX中配置I2C外设。打开STM32CubeMX工具,选择正确的芯片型号,然后在配置选项卡中找到I2C外设。添加一个I2C外设到你的项目中,并根据需要进行参数配置,例如选择所需的时钟速度、地址模式等。 2. 在生成的代码中,打开你选择的I2Cx.c文件(x表示I2C外设的编号,例如I2C1、I2C2等)。 3. 在I2Cx.c文件中,可以找到I2C的初始化函数,一般是以`HAL_I2C_Init()`开头的函数。在该函数中,会设置I2C外设的参数,例如时钟速度、地址等。根据你之前在STM32CubeMX中配置的选项,可以修改这些参数。 4. 需要注意的是,在配置I2C外设之前,还需要确认I2C引脚映射正确。打开I2Cx_Init函数的最上方,可以看到I2C引脚的定义。确保这些引脚与你实际连接的引脚相对应,如果有需要可以进行修改。 5. 在初始化I2C外设后,可以在其他函数中使用I2C进行数据传输。HAL库提供了一系列的I2C操作函数,例如`HAL_I2C_Master_Transmit()`用于主机发送数据,`HAL_I2C_Master_Receive()`用于主机接收数据,等等。根据你的需求,选择适合的函数,并传入对应的参数。 6. 在使用完I2C外设后,可以调用`HAL_I2C_DeInit()`函数来停止I2C外设的使用,释放资源。 以上就是在STM32F103C8T6芯片上使用HAL库进行I2C配置的步骤。希望对你有所帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值