STM32CubeMX GPIO模拟I2C读写M24C64

STM32CubeMX GPIO模拟I2C读写M24C64

一、先了解一下硬件的连接,I2C_SDA和I2C_SCL分别接STM32的PB9、PB6
在这里插入图片描述
二、粗阅一下M24C64的数据手册,得知器件地址和存储器地址,器件地址是8bit,而存储器地址是16bit
在这里插入图片描述
三、下面是M24C64的写时序
在这里插入图片描述
四、下面是M24C64的读时序
在这里插入图片描述
五、下面是程序编写流程
在这里插入图片描述
六、看看时序参数在这里插入图片描述在这里插入图片描述
七、好啦!需要的知识点差不多都提到了开始搬砖
1、用STM32CubeMX配置生成工程,并打开工程。(具体怎么用这个软件这里不讲)
2、在我的工程里是这样配置的
《1》配置USART3,用打印读出来的数据与写入的是否一致
《2》配置PB6、PB9为开漏输出模式,配置如下:

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_7, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_9, GPIO_PIN_RESET);

  /*Configure GPIO pin : PG7 */
  GPIO_InitStruct.Pin = GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

  /*Configure GPIO pins : PB6 PB9 */
  GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_9;						//PB6    PB9
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;				//开漏输出
  GPIO_InitStruct.Pull = GPIO_NOPULL;											//上下拉模式配置为既不上拉也不下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//IO口速度配置
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);									//初始化
}
在这里插入代码片

八、编写读程序,下面的代码是(安富莱电子 www.armfly.com)串行EEPROM 24xx驱动模块的代码,
代码如下:

/***********************************************************************************************
*
*
*
*
*
*
*
*
***************************************************************************************************/
#include "stm32f4xx_hal.h"


#define EE_MODEL_NAME		"AT24C64"
#define EE_DEV_ADDR			0xA0			/* 设备地址 */
#define EE_PAGE_SIZE		32				/* 页面大小(字节) */
#define EE_SIZE				(8*1024)		/* 总容量(字节) */
#define EE_ADDR_BYTES		2			 	/* 地址字节个数 */

// 定义I2C总线连接的GPIO端口, 用户只需要修改下面3行代码即可任意改变SCL和SDA的引脚 
#define GPIO_PORT_I2C	GPIOB			     // GPIO端口 
#define I2C_SCL_PIN		GPIO_PIN_6			 // 连接到SCL时钟线的GPIO 
#define I2C_SDA_PIN		GPIO_PIN_9			 // 连接到SDA数据线的GPIO 

/* 定义读写SCL和SDA的宏 */
#define I2C_SCL_1()  GPIO_PORT_I2C->BSRR = I2C_SCL_PIN							// SCL = 1 
#define I2C_SCL_0()  GPIO_PORT_I2C->BSRR = (uint32_t)I2C_SCL_PIN << 16U  		// SCL = 0 

#define I2C_SDA_1()  GPIO_PORT_I2C->BSRR = GPIO_PIN_9   						// SDA = 1 
#define I2C_SDA_0()  GPIO_PORT_I2C->BSRR = (uint32_t)GPIO_PIN_9 << 16U  		// SDA = 0 

#define I2C_SDA_READ()  (GPIO_PORT_I2C->IDR & GPIO_PIN_9)						// 读SDA口线状态 
#define I2C_SCL_READ()  (GPIO_PORT_I2C->IDR & I2C_SCL_PIN)						// 读SCL口线状态 


static void i2c_Delay(void)
{
	uint8_t i;
	for (i = 0; i < 40; i++);
}
void i2c_Start(void)
{
	// 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 
	
	I2C_SDA_1();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_0();
	i2c_Delay();
	
	I2C_SCL_0();
	i2c_Delay();
}

void i2c_Stop(void)
{
	// 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 
	I2C_SDA_0();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_1();
	i2c_Delay();
}
void i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;
	
	// 先发送字节的高位bit7 
	for (i = 0; i < 8; i++)
	{
		if (_ucByte & 0x80)
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		i2c_Delay();
		I2C_SCL_1();
		i2c_Delay();
		I2C_SCL_0();
		if (i == 7)
		{
			 I2C_SDA_1(); 	// 释放总线
		}
		_ucByte <<= 1;		// 左移一个bit 
		i2c_Delay();
	}
}
uint8_t i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value;

	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		I2C_SCL_1();
		i2c_Delay();
		if (I2C_SDA_READ())
		{
			value++;
		}
		I2C_SCL_0();
		i2c_Delay();
	}
	return value;
}


uint8_t i2c_WaitAck(void)
{
	uint8_t re;

	I2C_SDA_1();	/* CPU释放SDA总线 */
//	i2c_Delay();
	I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_Delay();

	if (I2C_SDA_READ())	/* CPU读取SDA口线状态 */
	{
		re = 1;
	}
	else
	{
		re = 0;
	}

	I2C_SCL_0();
	i2c_Delay();
	return re;
}

void i2c_Ack(void)
{
	I2C_SDA_0();	/* CPU驱动SDA = 0 */
	i2c_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
	I2C_SDA_1();	/* CPU释放SDA总线 */
}

void i2c_NAck(void)
{
	I2C_SDA_1();	/* CPU驱动SDA = 1 */
	i2c_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
}

uint8_t i2c_CheckDevice(uint8_t _Address)
{
	uint8_t ucAck;

	if (I2C_SDA_READ() && I2C_SCL_READ())
	{
		i2c_Start();		/* 发送启动信号 */

		/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
		i2c_SendByte(_Address | I2C_WR);
		ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */

		i2c_Stop();			/* 发送停止信号 */

		return ucAck;
	}
	return 1;	/* I2C总线异常 */
}

uint8_t ee_CheckOk(void)
{
	if (i2c_CheckDevice(EE_DEV_ADDR) == 0)
	{
		return 1;
	}
	else
	{
		/* 失败后,切记发送I2C总线停止信号 */
		i2c_Stop();
		return 0;
	}
}
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i,m;
	uint16_t usAddr;
	usAddr = _usAddress;
	for (i = 0; i < _usSize; i++)
	{
		/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
		if ((i == 0) || (usAddr & (EE_PAGE_SIZE - 1)) == 0)
		{
			/* 第0步:发停止信号,启动内部写操作 */
			i2c_Stop();

			/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
				CLK频率为200KHz时,查询次数为30次左右
			*/
			for (m = 0; m < 1000; m++)
			{
				/* 第1步:发起I2C总线启动信号 */
				i2c_Start();

				/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
				
//				#if EE_ADDR_A8 == 1
//					i2c_SendByte(EE_DEV_ADDR | I2C_WR | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
//				#else				
					i2c_SendByte(EE_DEV_ADDR | I2C_WR);
//				#endif
        
				/* 第3步:发送一个时钟,判断器件是否正确应答 */
				if (i2c_WaitAck() == 0)
				{
					break;
				}
			}
			if (m  == 1000)
			{
				goto cmd_fail;	/* EEPROM器件写超时 */
			}
			/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
			if (EE_ADDR_BYTES == 1)
			{
				i2c_SendByte((uint8_t)usAddr);
				if (i2c_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
			else
			{
				i2c_SendByte(usAddr >> 8);
				if (i2c_WaitAck()!= 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}

				i2c_SendByte(usAddr);
				if (i2c_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
		}

		/* 第6步:开始写入数据 */
		i2c_SendByte(_pWriteBuf[i]);

		/* 第7步:发送ACK */
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
		usAddr++;	/* 地址增1 */
	}

	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();

	/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
		CLK频率为200KHz时,查询次数为30次左右
	*/
	for (m = 0; m < 1000; m++)
	{
		/* 第1步:发起I2C总线启动信号 */
		i2c_Start();

		/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */	
		#if EE_ADDR_A8 == 1
			i2c_SendByte(EE_DEV_ADDR | I2C_WR | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
		#else		
			i2c_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */
		#endif

		/* 第3步:发送一个时钟,判断器件是否正确应答 */
		if (i2c_WaitAck() == 0)
		{
			break;
		}
	}
	if (m  == 1000)
	{
		goto cmd_fail;	/* EEPROM器件写超时 */
	}

	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();	

	return 1;

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;
}
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i;

	/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

	/* 第1步:发起I2C总线启动信号 */
	i2c_Start();

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	
	i2c_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */

	/* 第3步:发送ACK */
	if (i2c_WaitAck() != 0)
	{	
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
	if (EE_ADDR_BYTES == 1)
	{
		i2c_SendByte((uint8_t)_usAddress);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}
	else
	{
		i2c_SendByte(_usAddress >> 8);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}

		i2c_SendByte(_usAddress);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}

	/* 第6步:重新启动I2C总线。下面开始读取数据 */
	i2c_Start();

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */	
	
	i2c_SendByte(EE_DEV_ADDR | I2C_RD);	/* 此处是写指令 */

	/* 第8步:发送ACK */
	if (i2c_WaitAck() != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第9步:循环读取数据 */
	for (i = 0; i < _usSize; i++)
	{
		_pReadBuf[i] = i2c_ReadByte();	/* 读1个字节 */

		/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
		if (i != _usSize - 1)
		{
			i2c_Ack();	/* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
		}
		else
		{
			i2c_NAck();	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
		}
	}
	/* 发送I2C总线停止信号 */
	i2c_Stop();

	return 1;	/* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;
}

九、main函数

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_hal.h"
#include "usart.h"
#include "gpio.h"

/* USER CODE BEGIN Includes */
#include "bsp_eeprom_24xx.h"

/* USER CODE END Includes */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
#define EEPROM_WriteAddress1    0x00
#define EEPROM_ReadAddress1     0x00
#define BufferSize1             (countof(Tx1_Buffer)-1)

/* Private macro -------------------------------------------------------------*/
#define countof(a) (sizeof(a) / sizeof(*(a)))

/* Private variables ---------------------------------------------------------*/
uint8_t Tx1_Buffer[] = "123456789abcdef";
uint8_t Rx1_Buffer[BufferSize1] = "";

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);

/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */    
int fputc(int ch, FILE *f)
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);
  return ch;
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  *
  * @retval None
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  uint16_t  i, m;
  uint8_t xTbuffer[256],xRbuffer[256];
  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  /* USER CODE BEGIN 2 */
	HAL_GPIO_WritePin(GPIOG,GPIO_PIN_7,GPIO_PIN_SET); 
	HAL_Delay(100);
	HAL_GPIO_WritePin(GPIOG,GPIO_PIN_7,GPIO_PIN_RESET); 
	  
	for(i=0;i<200;i++)
	{
	xTbuffer[i]=i;
	xRbuffer[i]=0;
	}
	ee_WriteBytes((uint8_t *)xTbuffer, 0, 40);	
	HAL_Delay(2000);
	printf(" \r\n");
	printf("\r\n");
	ee_ReadBytes((uint8_t *)xRbuffer, 0, 40);
	for (i = 0; i < 40; i++)
	{
		printf("xRbuffer %d  %d \r\n",i,xRbuffer[i]);
	}
	HAL_Delay(2000);
	printf("\r\n");
	printf("\r\n");
	printf("I2C CESHI\r\n");

	m=ee_WriteBytes(Tx1_Buffer,0,BufferSize1);

	printf("I2C_WriteByte  %d   %s\r\n",m,(uint8_t*)&Tx1_Buffer[0]);

	m=ee_ReadBytes(Rx1_Buffer,0,BufferSize1);

	printf("I2C_ReadByte  %d  %s\r\n",m,(uint8_t*)&Rx1_Buffer[0]);

	printf(" Rx1_Buffer is:%s,\t the BufferSize1 is%d\r\n",Rx1_Buffer,BufferSize1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

  }
  /* USER CODE END 3 */

}

下面贴几个波形
1、向M24C64发送地址0xA0的时序图,图中的波形比好看;因为I2C总线的上拉电阻是1K,应答在第八个时钟后面,如图中红圈中蓝色的小波形
在这里插入图片描述
2.把应答位置的波形放大,如下图,那个绿色是小波形式怎么产生的呢?
在这里插入图片描述

	void i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;
	// 先发送字节的高位bit7 
	for (i = 0; i < 8; i++)
	{
		if (_ucByte & 0x80)
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		i2c_Delay();
		I2C_SCL_1();
		i2c_Delay();
		I2C_SCL_0();
		if (i == 7)
		{
			 I2C_SDA_1(); 	// 释放总线      这里产生的一个小波形
		}
		_ucByte <<= 1;		// 左移一个bit 
		i2c_Delay();
	}
}

产生的原因在发送完地址后释放总线是产生的,当I2C_SDA_1为高时,器件给出应答把I2C_SDA_1拉低,而产生的一个小波形,所以读到的应答应该是低电平。
在这里插入图片描述
因为I2C总线的上拉电阻是10K,波形上升沿不好看!!!!!!!!!!!
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值