二、STM32用HAL库写I2C底层时序

发布于:2023-01-04 ⋅ 阅读:(873) ⋅ 点赞:(0)

一、应用场景

        I2C在单片机开发中应用特别广泛,比如EEPROM中AT24CXX芯片使用的就是I2C总线。在STM32中,HAL库提供了I2C底层驱动,但小编在使用中用不了,不知是哪的问题。所以还是自己写底层时序吧,这种引脚使用上也更加的灵活。

二、原理图

(1)这里以AT24C128芯片和STM32F103RET6为例。

(2)EEPROM部分原理图

上图中A0、A1和A2为地址引脚;

WP为写保护引脚;

EEPROM_SCL为时钟引脚;

EEPROM_SDA为数据引脚。

 (3)STM32F103RET6部分电路图

 WP引脚接PB5引脚;EEPROM_SCL接PB6引脚;EEPROM_SDA接PB7引脚。

三、STM32CubeMX配置

1、时钟配置。略

2、使用定时器完成延时uS

一、STM32用HAL库实现uS级延时_朱嘉鼎的博客-CSDN博客

四、KeilMDK中代码的编写

1、新建drv_i2c.c文件和drv_i2c.h文件。

2、drv_i2c.h代码

#ifndef __DRV_I2C_H__
#define __DRV_I2C_H__

/*头文件包含*/
#include "tim.h"
#include "drv_gpio.h"

/*置位与清零SCL管脚*/
#define __AT24CXX_I2C_SCL_H()       HAL_GPIO_WritePin(EP_SCL_GPIO_Port,EP_SCL_Pin,GPIO_PIN_SET)    
#define __AT24CXX_I2C_SCL_L()       HAL_GPIO_WritePin(EP_SCL_GPIO_Port,EP_SCL_Pin,GPIO_PIN_RESET)
/*置位与清零SDA管脚*/
#define __AT24CXX_I2C_SDA_H()       HAL_GPIO_WritePin(EP_SDA_GPIO_Port,EP_SDA_Pin,GPIO_PIN_SET) 
#define __AT24CXX_I2C_SDA_L()       HAL_GPIO_WritePin(EP_SDA_GPIO_Port,EP_SDA_Pin,GPIO_PIN_RESET)
/*读取SDA管脚状态*/
#define __AT24CXX_READ_I2C_SDA()    HAL_GPIO_ReadPin(EP_SDA_GPIO_Port,EP_SDA_Pin)

/*设置SDA管脚为输出模式*/
#define __AT24CXX_I2C_SDA_OUT()     SDA_OutputMode()
/*设置SDA管脚为输入模式*/
#define __AT24CXX_I2C_SDA_INPUT()   SDA_InputMode()


/*I2C通信时的应答信号*/
typedef enum
{
	ACK	 = GPIO_PIN_RESET,
	NACK = GPIO_PIN_SET
}I2C_ACK_TypeDef;


typedef enum
{
	DEVICE_AT24CXX	= 0x00U
}I2C_DeviceTypeDef;


/*函数声明*/
void I2C_Delay_us(uint16_t t);
void I2C_Start(I2C_DeviceTypeDef Device);       													 /*I2C开始*/
void I2C_Stop(I2C_DeviceTypeDef Device);        													 /*I2C停止*/
I2C_ACK_TypeDef I2C_Write_Byte(I2C_DeviceTypeDef Device, uint8_t Byte);    /*I2C写一个字节*/   
uint8_t I2C_Read_Byte(I2C_DeviceTypeDef Device, I2C_ACK_TypeDef AckValue); /*I2C读一个字节*/


#endif

3、drv_i2c.c代码

#include "drv_i2c.h"  


/*
 *功能:定时器I2C延时
 *参数:延时的us数,us 范围-0~65535us
 *注意:使用定时器2产生延时
 *返回值:无
 */
void I2C_Delay_us(uint16_t t)
{
	uint16_t counter = 0;
	__HAL_TIM_SET_AUTORELOAD(&htim2, t); 	// 设置定时器自动加载值,到该值后重新计数   
	__HAL_TIM_SET_COUNTER(&htim2, 0);     // 设置定时器初始值
	HAL_TIM_Base_Start(&htim2);           // 启动定时器
	while(counter != t)                   // 直到定时器计数从 0 计数到 us 结束循环,刚好  us
	{
		counter = __HAL_TIM_GET_COUNTER(&htim2); // 获取定时器当前计数
	}
	HAL_TIM_Base_Stop(&htim2);   // 停止定时器
}


/*
 *功能:设置SDA管脚为输出模式
 *参数:无
 *返回值:无
 */
void SDA_OutputMode(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	GPIO_InitStruct.Pin = EP_SDA_Pin;                    
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;    /*输出模式*/
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}


/*
 *功能:设置SDA管脚为输入模式
 *参数:无
 *返回值:无
 */
void SDA_InputMode(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	GPIO_InitStruct.Pin = EP_SDA_Pin;                    
	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;      /*输入模式*/
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}


/*
 *功能:SDA和SCL引脚电平初始化
 *参数:无
 *返回值:无
 *测试:OK
 */
static void AT24CXX_I2C_Init(void)
{
	__AT24CXX_I2C_SCL_H();
	__AT24CXX_I2C_SDA_H();
}


/*
 *功能:I2C总线起始信号时序
 *参数:无
 *返回值:无
 *测试:OK
 */
static void AT24CXX_I2C_Start(void)
{
	__AT24CXX_I2C_SDA_OUT();     /*SDA配置为输出模式*/

	/*SCL为高电平,SDA的下降沿为I2C起始信号*/
	__AT24CXX_I2C_SDA_H();
	__AT24CXX_I2C_SCL_H();
	I2C_Delay_us(1);
	
	__AT24CXX_I2C_SDA_L();
	I2C_Delay_us(10);
	
	__AT24CXX_I2C_SCL_L();
	I2C_Delay_us(1);
}


/*
 *功能:I2C总线停止信号时序
 *参数:无
 *返回值:无
 *测试:OK
 */
static void AT24CXX_I2C_Stop(void)
{
  __AT24CXX_I2C_SDA_OUT();

	/*SCL为高电平,SDA的上升沿为I2C停止信号*/
	__AT24CXX_I2C_SDA_L();
	__AT24CXX_I2C_SCL_H();
	I2C_Delay_us(1);
		
	I2C_Delay_us(10);
	__AT24CXX_I2C_SDA_H();
}


/*
 *功能:I2C总线写字节时序
 *参数:Byte:写入的内容
 *返回值:I2C_ACK_TypeDef:应答信号是高电平还是低电平
 *测试:OK
 */
static I2C_ACK_TypeDef AT24CXX_I2C_Write_Byte(uint8_t Byte)
{
	I2C_ACK_TypeDef ack_value;
	__AT24CXX_I2C_SDA_OUT();     /*SDA配置为输出模式*/
	
	/*SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据*/
	for (uint8_t i = 0; i < 8; i++)
	{
		/*SCL清零,主机SDA准备数据*/
		__AT24CXX_I2C_SCL_L();
		I2C_Delay_us(1);
		if((Byte & (1 << 7)) == (1 << 7))
		{
			__AT24CXX_I2C_SDA_H();
		}
		else
		{
			__AT24CXX_I2C_SDA_L();
		}
		I2C_Delay_us(1);
		/*SCL置高,传输数据*/
		__AT24CXX_I2C_SCL_H();
		I2C_Delay_us(10);
		/*准备发送下一比特位*/
		Byte <<= 1;
	}
	__AT24CXX_I2C_SCL_L();	

	__AT24CXX_I2C_SDA_INPUT();

	/*释放SDA,等待从机应答*/
	__AT24CXX_I2C_SDA_H();
	I2C_Delay_us(1);

	__AT24CXX_I2C_SCL_H();
	I2C_Delay_us(10);

	ack_value = (I2C_ACK_TypeDef)__AT24CXX_READ_I2C_SDA();
 
	__AT24CXX_I2C_SCL_L();
	I2C_Delay_us(1);
	/*返回从机的应答信号*/
	return ack_value;
}


/*
 *功能:I2C总线读字节时序
 *参数:
 *返回值:读取到的数据
 *测试:OK
 */
static uint8_t AT24CXX_I2C_Read_Byte(I2C_ACK_TypeDef AckValue)
{
	uint8_t byte = 0;
	
  __AT24CXX_I2C_SDA_INPUT();

	/*SCL为低电平时,SDA准备数据,接着SCL为高电平,读取SDA数据*/
	for (uint8_t i = 0; i < 8; i++)
	{
		/*准备接收下一比特位*/
		byte <<= 1;
		/*SCL清零,从机SDA准备数据*/
		__AT24CXX_I2C_SCL_L();
		I2C_Delay_us(10);
		/*SCL置高,获取数据*/
		__AT24CXX_I2C_SCL_H();
		I2C_Delay_us(10);	
		byte |= __AT24CXX_READ_I2C_SDA();	
	}
	/*SCL清零,主机准备应答信号*/
	__AT24CXX_I2C_SCL_L();
	I2C_Delay_us(1);

  __AT24CXX_I2C_SDA_OUT();

	/*主机发送应答信号*/	
	if(AckValue == ACK)
	{
		__AT24CXX_I2C_SDA_L();
	}
	else
	{
		__AT24CXX_I2C_SDA_H();	
	}
	I2C_Delay_us(1);
	__AT24CXX_I2C_SCL_H(); 	
	I2C_Delay_us(10);
	/*SCL先清零,再释放SDA,防止连续传输数据时,从机错将SDA释放信号当成NACk信号*/
	__AT24CXX_I2C_SCL_L();
	__AT24CXX_I2C_SDA_H(); 	
	I2C_Delay_us(1);
	
	/*返回数据*/
	return byte;
}


/*
 *功能:I2C初始化
 *测试:OK
 */
void I2C_Init(I2C_DeviceTypeDef Device)
{
	switch(Device)
	{
		case DEVICE_AT24CXX: AT24CXX_I2C_Init(); break;
		default : break;
	}
}


/*
 *功能:I2C开始信号,针对不同型号设备的封装
 *测试:OK
 */
void I2C_Start(I2C_DeviceTypeDef Device)
{
	switch(Device)
	{
		case DEVICE_AT24CXX: AT24CXX_I2C_Start(); break;
		default : break;
	}
}


/*
 *功能:I2C停止信号,针对不同型号设备的封装
 *测试:OK
 */
void I2C_Stop(I2C_DeviceTypeDef Device)
{
	switch(Device)
	{
		case DEVICE_AT24CXX: AT24CXX_I2C_Stop(); break;
		default : break;
	}
}


/*
 *功能:I2C写一个字节
 *参数:Device:写入的设备
 *      Byte: 需要写入的内容
 *返回值:应答信号,正确应答信号是将SDA拉低
 */
I2C_ACK_TypeDef I2C_Write_Byte(I2C_DeviceTypeDef Device, uint8_t Byte)
{
	switch(Device)
	{
		case DEVICE_AT24CXX: return AT24CXX_I2C_Write_Byte(Byte); break;
		default : return NACK;    break;
	}
}


/*
 *功能:I2C读一个字节
 *参数:Device:读出的设备
 *      AckValue: 主机发送的应答信号
 *返回值:读到的内容
 *测试:OK
 */
uint8_t I2C_Read_Byte(I2C_DeviceTypeDef Device, I2C_ACK_TypeDef AckValue)
{
	switch(Device)
	{
		case DEVICE_AT24CXX: return AT24CXX_I2C_Read_Byte(AckValue);
		default : return 0;
	}
}





3、mai.c文件中测试代码

(1)包含头文件

#include "drv_uart.h"
#include "drv_at24cxx.h"

(2)main函数中while中代码

/*Test*/
printf("Hello World! \r\n");
HAL_Delay(500);
__EEPROM_UNLOCK();
AT24CXX_WriteOneByte(0X00, 88);
__EEPROM_LOCK();	
HAL_Delay(500);
printf("AT24CXX Test: %d\r\n", AT24CXX_ReadOneByte(0X00));

(3)下载程序后,串口打印信息如下

---------------------------------------------------------------------------------------------------------------------------------如有不足,请忽略。


网站公告

今日签到

点亮在社区的每一天
去签到