开发环境:
IDE:MounRiver Studio
MCU:CH585
首先实现I2C的协议。
/**
* [url=home.php?mod=space&uid=2666770]@Brief[/url] I2C_Delay, I2C总线位延迟,最快400KHz
* [url=home.php?mod=space&uid=3142012]@param[/url] None
* @retval None
*/
static void I2C_Delay(void)
{
uint8_t i;
for (i = 0; i < 10; i++);
}
/**
* @brief I2C_Start, CPU发起I2C总线启动信号
* @param None
* @retval None
*/
void I2C_Start(void)
{
/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
I2C_SDA_1();
I2C_SCL_1();
I2C_Delay();
I2C_SDA_0();//START:when CLK is high,DATA change form high to low
I2C_Delay();
I2C_SCL_0();
I2C_Delay();
}
/**
* @brief I2C_Stop, CPU发起I2C总线停止信号
* @param None
* @retval None
*/
void I2C_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
I2C_SDA_0();//STOP:when CLK is high DATA change form low to high
I2C_SCL_1();
I2C_Delay();
I2C_SDA_1();
}
/**
* @brief I2C_SendByte, CPU向I2C总线设备发送8bit数据
* @param _ucByte : 等待发送的字节
* @retval None
*/
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();
}
}
/**
* @brief I2C_ReadByte, CPU从I2C总线设备读取8bit数据
* @param None
* @retval 读到的数据
*/
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;
}
/**
* @brief I2C_WaitAck, CPU产生一个时钟,并读取器件的ACK应答信号
* @param None
* @retval 返回0表示正确应答,1表示无器件响应
*/
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;
}
/**
* @brief I2C_Ack, CPU产生一个ACK信号
* @param None
* @retval None
*/
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总线 */
}
/**
* @brief iI2C_NAck, CPU产生1个NACK信号
* @param None
* @retval None
*/
void I2C_NAck(void)
{
I2C_SDA_1(); /* CPU驱动SDA = 1 */
I2C_Delay();
I2C_SCL_1(); /* CPU产生1个时钟 */
I2C_Delay();
I2C_SCL_0();
I2C_Delay();
}
/**
* @brief I2C_Cfg_GPIO, 配置I2C总线的GPIO,采用模拟IO的方式实现
* @param None
* @retval None
*/
static void I2C_Cfg_GPIO(void)
{
GPIOB_ModeCfg(I2C_SCL_PIN, GPIO_ModeOut_PP_5mA);
GPIOB_ModeCfg(I2C_SDA_PIN, GPIO_ModeOut_PP_5mA);
GPIOB_SetBits(I2C_SCL_PIN);
GPIOB_SetBits(I2C_SDA_PIN);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
I2C_Stop();
}
/**
* @brief i2c_CheckDevice, 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* @param _Address:设备的I2C总线地址
* @retval 返回值 0 表示正确, 返回1表示未探测到
*/
uint8_t I2C_CheckDevice(uint8_t _Address)
{
uint8_t ucAck;
I2C_Cfg_GPIO(); /* 配置GPIO */
I2C_Start(); /* 发送启动信号 */
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
I2C_SendByte(_Address | I2C_WR);
ucAck = I2C_WaitAck(); /* 检测设备的ACK应答 */
I2C_Stop(); /* 发送停止信号 */
return ucAck;
}
注释很清楚,对照I2C的协议看就行。
接着就是实现AT2C02的读写操作。
/**
* @brief EEPROM_CheckOk, 判断串行EERPOM是否正常
* @param None
* @retval 1 表示正常, 0 表示不正常
*/
uint8_t EEPROM_CheckOk(void)
{
if (I2C_CheckDevice(EEPROM_DEV_ADDR) == 0)
{
return 1;
}
else
{
/* 失败后,切记发送I2C总线停止信号 */
I2C_Stop();
return 0;
}
}
/**
* @brief EEPROM_ReadBytes, 从串行EEPROM指定地址处开始读取若干数据
* @param _usAddress : 起始地址
* _usSize : 数据长度,单位为字节
* _pReadBuf : 存放读到的数据的缓冲区指针
* @retval 0 表示失败,1表示成功
*/
uint8_t EEPROM_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(EEPROM_DEV_ADDR | I2C_WR); /* 此处是写指令 */
/* 第3步:发送ACK */
if (I2C_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
I2C_SendByte((uint8_t)_usAddress);
/* 第5步:发送ACK */
if (I2C_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件无应答 */
}
/* 第6步:重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
I2C_Start();
/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
I2C_SendByte(EEPROM_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;
}
/**
* @brief EEPROM_WriteBytes, 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
* @param _usAddress : 起始地址
* _usSize : 数据长度,单位为字节
* _pWriteBuf : 存放读到的数据的缓冲区指针
* @retval 0 表示失败,1表示成功
*/
uint8_t EEPROM_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i,m;
uint16_t usAddr;
/*
写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。
对于24xx02,page size = 8
简单的处理方法为:按字节写操作模式,没写1个字节,都发送地址
为了提高连续写的效率: 本函数采用page wirte操作。
*/
usAddr = _usAddress;
for (i = 0; i < _usSize; i++)
{
/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
{
/* 第0步:发停止信号,启动内部写操作 */
I2C_Stop();
/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
CLK频率为200KHz时,查询次数为30次左右
*/
for (m = 0; m < 100; m++)
{
/* 第1步:发起I2C总线启动信号 */
I2C_Start();
/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
I2C_SendByte(EEPROM_DEV_ADDR | I2C_WR); /* 此处是写指令 */
/* 第3步:发送一个时钟,判断器件是否正确应答 */
if (I2C_WaitAck() == 0)
{
break;
}
}
if (m == 1000)
{
goto cmd_fail; /* EEPROM器件写超时 */
}
/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
I2C_SendByte((uint8_t)usAddr);
/* 第5步:发送ACK */
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();
return 1;
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
I2C_Stop();
return 0;
}
/**
* @brief EEPROM_Erase
* @param None
* @retval None
*/
void EEPROM_Erase(void)
{
uint16_t i;
uint8_t buf[EEPROM_SIZE];
/* 填充缓冲区 */
for (i = 0; i < EEPROM_SIZE; i++)
{
buf[i] = 0xFF;
}
/* 写EEPROM, 起始地址 = 0,数据长度为 256 */
if (EEPROM_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
{
printf("擦除eeprom出错!\r\n");
return;
}
else
{
printf("擦除eeprom成功!\r\n");
}
}
/**
* @brief EE_Delay
* @param nCount
* @retval None
*/
static void EEPROM_Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
/**
* @brief AT24C02 读写测试
* @param None
* @retval None
*/
void EEPROM_Test(void)
{
uint16_t i;
uint8_t write_buf[EEPROM_SIZE];
uint8_t read_buf[EEPROM_SIZE];
/*-----------------------------------------------------------------------------------*/
if (EEPROM_CheckOk() == 0)
{
/* 没有检测到EEPROM */
printf("Serial EEPROM not detected!\r\n");
while (1); /* 停机 */
}
/*------------------------------------------------------------------------------------*/
/* 填充测试缓冲区 */
for (i = 0; i < EEPROM_SIZE; i++)
{
write_buf[i] = i;
}
/*------------------------------------------------------------------------------------*/
if (EEPROM_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
{
printf("Error writing eeprom!\r\n");
return;
}
else
{
printf("Successfully written eeprom!\r\n");
}
/*写完之后需要适当的延时再去读,不然会出错*/
EEPROM_Delay(0x0FFFFF);
/*-----------------------------------------------------------------------------------*/
if (EEPROM_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
{
printf("Error reading eeprom!\r\n");
return;
}
else
{
printf("Read eeprom successfully, with the following data: \r\n");
}
/*-----------------------------------------------------------------------------------*/
for (i = 0; i < EEPROM_SIZE; i++)
{
if(read_buf[i] != write_buf[i])
{
printf("0x%02X ", read_buf[i]);
printf("Error: Inconsistent data between I2C EEPROM writing and reading\n\r");
return;
}
printf(" %02X", read_buf[i]);
if ((i & 15) == 15)
{
printf("\r\n");
}
}
printf("I2C(AT24C02) Read and write test successful\r\n");
while(1);
}
代码很简单,和使用硬件I2C的逻辑是一样的。
最后看下主函数吧。
/*********************************************************************
* @fn main
*
* @brief 主函数
*
* [url=home.php?mod=space&uid=1141835]@Return[/url] none
*/
int main()
{
HSECFG_Capacitance(HSECap_18p);
SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);
BSP_UART_Init();
printf("I2C Test\r\n");
EEPROM_Test();
while(1)
{
}
}
主函数中重点关注EEPROM_Test()函数,这就是对AT24C02的读写操作。
下载程序,连接串口打印信息如下。

更多回帖