一、先了解一下硬件的连接,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,波形上升沿不好看!!!!!!!!!!!