文章目录
- 一、简介
- 二、模拟IIC时序
- 三、读写流程
- 四、完整代码
- 五、测试验证
一、简介
FM24C04D,4K串行EEPROM:内部32页,每个16字节,4K需要一个11位的数据字地址进行随机字寻址。FM24C04D提供4096位串行电可擦除和可编程只读存储器(EEPROM),大小为512字,每个8位,具有128位UID和16字节安全扇区。该设备被优化用于许多工业和商业应用中,其中低功率和低压操作是必不可少的。
芯片优点:FM24C04芯片手册
- 低工作电压: VCC = 1.7V到5.5V
- 2-线串行接口,施密特触发器,滤波输入噪声抑制,双向数据传输协议
- 1 MHz(2.5V~5.5V)和400 kHz (1.7V)兼容性
- 写保护针硬件数据保护
安全扇区
:FM24C04D提供了16字节的安全扇区,可以写入和(以后)永久锁定在只读模式。这些寄存器可以被系统制造商用来从存储主内存阵列分开存储安全性和其他重要信息
唯一ID
:FM24C04D使用了一个单独的内存块,其中包含一个工厂编程的128位唯一ID。通过以“1011”(Bh)序列开始设备地址字来访问此内存位置
数据存储器:
(1)第七位是内存页面地址位(P0),第八位是读/写操作选择位。
(2)地址位P0和<7:4>定义页面地址,A<3:0>定义字节地址。总共32页,每页16个字节。设备地址P0为0(0xA0)时,地址从第0页开始;设备地址为1(0xA2)时,地址从第16页开始写数据。
安全扇区:
(3)第七位并不在乎,第八位是读/写操作选择位。
(4)地址位<7:6>必须是00”,A<3:0>定义字节地址,其他位不在乎。
唯一性标识:
(5)第七位并不在乎,第八位是读/写操作选择位,它必须是“1”。
(6)地址位<7:6>必须是10”,A<3:0>定义字节地址,其他位不在乎。
二、模拟IIC时序
1.起始信号: 在SCL为高电平期间,SDA由高变低;
/*** @brief 产生I2C起始信号* @note* 请参考I2C通信协议,I2C起始信号:当SCL为高电平时,SDA由高变低* 如下图所示:方框部分表示I2C起始信号* _____ |* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \ /* | | | \___/\___/\___/\___/\___/\___/\___/\___/\___/* __|_____|_ | _ _ _ _ _ _ _ _ _* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_* |_____| |* start D7 D6 D5 D4 D3 D2 D1 D0 ACK*/
void I2C_Start(void)
{uint32_t i2c_delay = i2c_speed;SDA_OUT(); // SDA设置为输出I2C_SDA_HIGH; // SDA: 高I2C_SCL_HIGH; // SCL: 高BSP_I2C_Delay(i2c_delay); // 延时>4.7usI2C_SDA_LOW; // 当SCL为高电平时,SDA由高变低BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW; // SCL变低,钳住I2C总线,准备发送或接收数据
}
2.停止信号: 在SCL为高电平期间,SDA由低变高。
/*** @brief 产生I2C停止信号* @note* 请参考I2C通信协议,I2C停止信号:当SCL为高电平时,SDA由低变高* 发送完STOP信号后,SCL和SDA都为高电平,即释放了I2C总线* 如下图所示:方框部分表示I2C起始信号* _____* ___ ___ ___ ___ | __|_* SDA: / \/ \/ \/ \ | / |* \___/\___/\___/\___/\______|_/ |* _ _ _ _ _ _|_____|_* SCL: / \__/ \__/ \__/ \__/ \__/ | |* |_____|* D3 D2 D1 D0 ACK stop*/
void I2C_Stop(void)
{uint32_t i2c_delay = i2c_speed;SDA_OUT(); // SDA设置为输出I2C_SDA_LOW; // SDA低电平I2C_SCL_HIGH; // SCL高电平BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SDA_HIGH; // STOP:当SCL为高电平时,SDA由低变高BSP_I2C_Delay(i2c_delay); // 延时>4.7us
}
3.等待ACK应答信号: 在8位数据信号传输完成之后,第9位就是应答信号。应答信号分为应答和非应答,应答为低电平,非应答为高电平。
/*** @brief 等待ACK应答信号* @retval 1 - 未接收到应答信号ACK;0 - 接收到应答信号ACK* @note* 请参考I2C通信协议,检测ACK应答信号:当SCL为高电平时,读取SDA为低电平* 如下图所示:方框部分表示I2C起始信号* ________ _____* ___ ___ ___ ___ | _ | | __|_* SDA: / \/ \/ \/ \|/ \ | | / |* \___/\___/\___/\___/| \____|___|_/ |* _ _ _ _ | _____ | _|_____|* SCL: / \__/ \__/ \__/ \__|/ \_|_/ | |* |________| |_____|* D3 D2 D1 D0 ACK stop*/
uint8_t I2C_Wait_ACK(void)
{uint32_t i2c_delay = i2c_speed;uint8_t timeout = 0;SDA_IN(); // SDA设置为输入I2C_SDA_HIGH; // SDA上拉输入I2C_SCL_HIGH; // SCL设置为高电平BSP_I2C_Delay(i2c_delay);while (I2C_SDA_READ() == 1) // 等待ACK{if (timeout++ > 250){I2C_Stop();return 1;}}I2C_SCL_LOW; // 钳住I2C总线:时钟信号设为低电平return 0;
}/*** @brief 产生ACK应答信号* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为低电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号* _____ _____* ___ ___ ___ ___ | | | __|_* SDA: / \/ \/ \/ \| | | / |* \___/\___/\___/\___/|\____|___|_/ |* _ _ _ _ | _ | _|_____|_* SCL: / \__/ \__/ \__/ \__|_/ \_|_/ | |* |_____| |_____|* D3 D2 D1 D0 ACK stop*/
void I2C_ACK(void)
{uint32_t i2c_delay = i2c_speed;I2C_SCL_LOW; // 低电平SDA_OUT(); // 设置SDA为输出I2C_SDA_LOW; // ACK信号BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_HIGH; // 高电平BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW; // 钳住I2C总线:时钟信号设为低电平
}/*** @brief 产生非应答信号NACK* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为高电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号* _____ ______* ___ ___ ___ ___ | ____|_ | __|_* SDA: / \/ \/ \/ \|/ | \ | / |* \___/\___/\___/\___/| | \_|__/ |* _ _ _ _ | _ | __|______|_* SCL: / \__/ \__/ \__/ \__|_/ \_|_/ | |* |_____| |______|* D3 D2 D1 D0 NACK stop*/
void I2C_NACK(void)
{uint32_t i2c_delay = i2c_speed;I2C_SCL_LOW; // 低电平SDA_OUT(); // SDA设置为输出I2C_SDA_HIGH; // NACK信号BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_HIGH; // 高电平BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW; // 钳住I2C总线:时钟信号设为低电平
}
4.读写一个字节数据
/*** @brief I2C发送一个字节* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为高电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号** _____ |<------------I2C数据发送周期------------>|* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___ | _* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \|/* | | | \___/\___/\___/\___/\___/\___/\___/\___/|\_* __|_____|_ | _ _ _ _ _ _ _ _ |* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_|_* |_____| | |* start | D7 D6 D5 D4 D3 D2 D1 D0 |*/
void I2C_Send_Byte(uint8_t data)
{uint8_t i = 0;uint32_t i2c_delay = i2c_speed;SDA_OUT(); // SDA设为输出I2C_SCL_LOW; // 钳住I2C总线:SCL设为低电平for (i = 0; i < 8; i++){if (data & 0x80)I2C_SDA_HIGH; // 高位先传elseI2C_SDA_LOW;BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_HIGH; // 在SCL上产生一个正脉冲BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW;BSP_I2C_Delay(i2c_delay / 3); // 延时>1usdata <<= 1; // 右移一位}
}/*** @brief 从I2C读取一个字节* @param ack : 0 - NACK; 1 - ACK* @retval 接收到的数据* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为高电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号** _____ |<------------I2C数据读取周期(ACK)------------>|* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___ |* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \ |* | | | \___/\___/\___/\___/\___/\___/\___/\___/\____|_* __|_____|_ | _ _ _ _ _ _ _ _ _ |* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_|_* |_____| | |* start | D7 D6 D5 D4 D3 D2 D1 D0 ACK** _____ |<------------I2C数据读取周期(NACK)----------->|* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___ ____|_* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \/ |* | | | \___/\___/\___/\___/\___/\___/\___/\___/ |* __|_____|_ | _ _ _ _ _ _ _ _ _ |* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_|_* |_____| | |* start | D7 D6 D5 D4 D3 D2 D1 D0 NACK*/
uint8_t I2C_Read_Byte(uint8_t ack)
{uint8_t i, receive = 0x00;uint32_t i2c_delay = i2c_speed;I2C_SCL_LOW; // SCL低电平SDA_IN(); // SDA设置为输入for (i = 0; i < 8; i++){BSP_I2C_Delay(i2c_delay);I2C_SCL_HIGH; // 高电平BSP_I2C_Delay(i2c_delay);receive <<= 1;if (I2C_SDA_READ())receive |= 1; // 高位在前I2C_SCL_LOW;}if (ack == 0)I2C_NACK(); // 发送NACKelseI2C_ACK(); // 发送ACKreturn receive; // 返回接收到的数据
}
三、读写流程
1.写数据流程
START–>DEVICE ADDRESS(写)–>ACK–>WORD ADDRESS–>ACK–>DATA–>ACK–>STOP
- START:起始信号
- DEVICE-ADDRESS:设备地址,以及页地址设置
- ACK:从设备发送的应答信号(低电平),主机接收应答。
- WORD-ADDRESS:写的数据存入的地址,即页地址
- DATA:写入的数据
- STOP:结束信号
2.读数据流程
START–>DEVICE ADDRESS(写)–>ACK–>WORD ADDRESS–>START–>DEVICE ADDRESS(读)–>ACK–>DATA–>NACK–>STOP。
- START:起始信号
- DEVICE-ADDRESS:设备地址,以及页地址设置
- ACK:从设备发送的应答信号(低电平),主机接收应答。
- WORD-ADDRESS:写的数据存入的地址,即页地址
- DATA:写入的数据
- NACK:主机发送的非应答信号,高电平有效。
- STOP:结束信号
四、完整代码
1.fm24c04.h
主要是定义IIC通信的引脚,以及引脚高低电平变化,设置数据线的模式。
#ifndef APPLICATIONS_FM24C04_H_
#define APPLICATIONS_FM24C04_H_#include <drv_common.h>
#include <string.h>/**====================================================###### 宏定义 ######==================================================*/
/* 定义读写SCL和SDA的宏 */
#define I2C_SDA_PIN GET_PIN(B, 11)
#define I2C_SCL_PIN GET_PIN(B, 10)#define I2C_SCL_HIGH rt_pin_write(I2C_SCL_PIN, 1)
#define I2C_SCL_LOW rt_pin_write(I2C_SCL_PIN, 0)#define I2C_SDA_HIGH rt_pin_write(I2C_SDA_PIN, 1)
#define I2C_SDA_LOW rt_pin_write(I2C_SDA_PIN, 0)#define I2C_SDA_READ() rt_pin_read(I2C_SDA_PIN)#define SDA_IN() rt_pin_mode(I2C_SDA_PIN, PIN_MODE_INPUT);
#define SDA_OUT() rt_pin_mode(I2C_SDA_PIN, PIN_MODE_OUTPUT);/* 根据处理器速度设置,这里处理器速度是72MHz */
#define I2C_SPEED_1K 5000/* 状态枚举 */
typedef enum
{I2C_SUCCESS = 0,I2C_TIMEOUT,I2C_ERROR,} I2C_StatusTypeDef;uint32_t i2c_speed; // I2C访问速度 = I2C_SPEED_1K / i2c_speed
/**====================================================####### END #######=================================================*//**=========================================##### 依照I2C协议编写的时序函数 #####=========================================*/
extern void BSP_I2C_Init(void); // 初始化I2C的IO口
extern void I2C_Start(void); // 发送I2C开始信号
extern void I2C_Stop(void); // 发送I2C停止信号
extern uint8_t I2C_Wait_ACK(void); // I2C等待ACK信号
extern void I2C_ACK(void); // I2C发送ACK信号
extern void I2C_NACK(void); // I2C不发送ACK信号
extern void I2C_Send_Byte(uint8_t data); // I2C发送一个字节
extern uint8_t I2C_Read_Byte(uint8_t ack); // I2C读取一个字节
extern uint16_t I2C_SetSpeed(uint16_t speed); // 设置I2C速度(1Kbps~400Kbps,speed单位,Kbps)
/**================================================####### END #######=====================================================*//**============================================##### 封装好的I2C读写函数 #####===========================================*/
//具体到某一个器件,请仔细阅读器件规格书关于I2C部分的说明,因为某些器件在I2C的读写操作会
//有一些差异,下面的代码我们在绝大多数的I2C器件中,都是验证OK的!
I2C_StatusTypeDef I2C_WriteOneByte(uint8_t DevAddr, uint8_t DataAddr, uint8_t Data); // 向I2C从设备写入一个字节
I2C_StatusTypeDef I2C_WriteBurst(uint8_t DevAddr, uint8_t DataAddr, uint8_t* pData, uint32_t Num);// 向I2C从设备连续写入Num个字节
I2C_StatusTypeDef I2C_ReadOneByte(uint8_t DevAddr, uint8_t DataAddr, uint8_t* Data); // 从I2C从设备读取一个字节
I2C_StatusTypeDef I2C_ReadBurst(uint8_t DevAddr, uint8_t DataAddr, uint8_t* pData, uint32_t Num); // 从I2C设备连续读取Num个字节
I2C_StatusTypeDef I2C_WriteBit(uint8_t DevAddr, uint8_t DataAddr, uint8_t Bitx, uint8_t BitSet); // 设置数据的某一位
/**================================================####### END #######=====================================================*/#endif /* _bsp_FRAM_H */
2.fm24c04.c
主要实现IIC的模式时序,编写发送和接收字节的函数,同时根据读取和写入的流程,编写读取和写入的函数,便于在存储的时候直接使用。
#include "fm24c04.h"/*==========================================##### 依照I2C协议编写的时序函数 #####=========================================*/
/*** @brief 延时函数*/
static void BSP_I2C_Delay(uint32_t us)
{while (us--);
}/*** @brief 设置I2C速度* @param speed : I2C速度,单位Kbps* @retval 返回设置前的I2C速度* @note I2C速度设置范围是: 1Kbps ~ 400Kbps*/
uint16_t I2C_SetSpeed(uint16_t speed)
{uint16_t temp;// I2C速度必须小于400Kbps,大于 1Kbpsif ((speed > 400) || (speed < 1))return 0;temp = I2C_SPEED_1K / i2c_speed; //备份原来的i2c速度i2c_speed = I2C_SPEED_1K / speed; //设置新的i2c速度return temp; //返回设置前的i2c速度
}/*** @brief 产生I2C起始信号* @note* 请参考I2C通信协议,I2C起始信号:当SCL为高电平时,SDA由高变低* 如下图所示:方框部分表示I2C起始信号* _____ |* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \ /* | | | \___/\___/\___/\___/\___/\___/\___/\___/\___/* __|_____|_ | _ _ _ _ _ _ _ _ _* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_* |_____| |* start D7 D6 D5 D4 D3 D2 D1 D0 ACK*/
void I2C_Start(void)
{uint32_t i2c_delay = i2c_speed;SDA_OUT(); // SDA设置为输出I2C_SDA_HIGH; // SDA: 高I2C_SCL_HIGH; // SCL: 高BSP_I2C_Delay(i2c_delay); // 延时>4.7usI2C_SDA_LOW; // 当SCL为高电平时,SDA由高变低BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW; // SCL变低,钳住I2C总线,准备发送或接收数据
}/*** @brief 产生I2C停止信号* @note* 请参考I2C通信协议,I2C停止信号:当SCL为高电平时,SDA由低变高* 发送完STOP信号后,SCL和SDA都为高电平,即释放了I2C总线* 如下图所示:方框部分表示I2C起始信号* _____* ___ ___ ___ ___ | __|_* SDA: / \/ \/ \/ \ | / |* \___/\___/\___/\___/\______|_/ |* _ _ _ _ _ _|_____|_* SCL: / \__/ \__/ \__/ \__/ \__/ | |* |_____|* D3 D2 D1 D0 ACK stop*/
void I2C_Stop(void)
{uint32_t i2c_delay = i2c_speed;SDA_OUT(); // SDA设置为输出I2C_SDA_LOW; // SDA低电平I2C_SCL_HIGH; // SCL高电平BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SDA_HIGH; // STOP:当SCL为高电平时,SDA由低变高BSP_I2C_Delay(i2c_delay); // 延时>4.7us
}/*** @brief 等待ACK应答信号* @retval 1 - 未接收到应答信号ACK;0 - 接收到应答信号ACK* @note* 请参考I2C通信协议,检测ACK应答信号:当SCL为高电平时,读取SDA为低电平* 如下图所示:方框部分表示I2C起始信号* ________ _____* ___ ___ ___ ___ | _ | | __|_* SDA: / \/ \/ \/ \|/ \ | | / |* \___/\___/\___/\___/| \____|___|_/ |* _ _ _ _ | _____ | _|_____|* SCL: / \__/ \__/ \__/ \__|/ \_|_/ | |* |________| |_____|* D3 D2 D1 D0 ACK stop*/
uint8_t I2C_Wait_ACK(void)
{uint32_t i2c_delay = i2c_speed;uint8_t timeout = 0;SDA_IN(); // SDA设置为输入I2C_SDA_HIGH; // SDA上拉输入I2C_SCL_HIGH; // SCL设置为高电平BSP_I2C_Delay(i2c_delay);while (I2C_SDA_READ() == 1) // 等待ACK{if (timeout++ > 250){I2C_Stop();return 1;}}I2C_SCL_LOW; // 钳住I2C总线:时钟信号设为低电平return 0;
}/*** @brief 产生ACK应答信号* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为低电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号* _____ _____* ___ ___ ___ ___ | | | __|_* SDA: / \/ \/ \/ \| | | / |* \___/\___/\___/\___/|\____|___|_/ |* _ _ _ _ | _ | _|_____|_* SCL: / \__/ \__/ \__/ \__|_/ \_|_/ | |* |_____| |_____|* D3 D2 D1 D0 ACK stop*/
void I2C_ACK(void)
{uint32_t i2c_delay = i2c_speed;I2C_SCL_LOW; // 低电平SDA_OUT(); // 设置SDA为输出I2C_SDA_LOW; // ACK信号BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_HIGH; // 高电平BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW; // 钳住I2C总线:时钟信号设为低电平
}/*** @brief 产生非应答信号NACK* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为高电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号* _____ ______* ___ ___ ___ ___ | ____|_ | __|_* SDA: / \/ \/ \/ \|/ | \ | / |* \___/\___/\___/\___/| | \_|__/ |* _ _ _ _ | _ | __|______|_* SCL: / \__/ \__/ \__/ \__|_/ \_|_/ | |* |_____| |______|* D3 D2 D1 D0 NACK stop*/
void I2C_NACK(void)
{uint32_t i2c_delay = i2c_speed;I2C_SCL_LOW; // 低电平SDA_OUT(); // SDA设置为输出I2C_SDA_HIGH; // NACK信号BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_HIGH; // 高电平BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW; // 钳住I2C总线:时钟信号设为低电平
}/*** @brief I2C发送一个字节* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为高电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号** _____ |<------------I2C数据发送周期------------>|* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___ | _* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \|/* | | | \___/\___/\___/\___/\___/\___/\___/\___/|\_* __|_____|_ | _ _ _ _ _ _ _ _ |* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_|_* |_____| | |* start | D7 D6 D5 D4 D3 D2 D1 D0 |*/
void I2C_Send_Byte(uint8_t data)
{uint8_t i = 0;uint32_t i2c_delay = i2c_speed;SDA_OUT(); // SDA设为输出I2C_SCL_LOW; // 钳住I2C总线:SCL设为低电平for (i = 0; i < 8; i++){if (data & 0x80)I2C_SDA_HIGH; // 高位先传elseI2C_SDA_LOW;BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_HIGH; // 在SCL上产生一个正脉冲BSP_I2C_Delay(i2c_delay); // 延时>4usI2C_SCL_LOW;BSP_I2C_Delay(i2c_delay / 3); // 延时>1usdata <<= 1; // 右移一位}
}/*** @brief 从I2C读取一个字节* @param ack : 0 - NACK; 1 - ACK* @retval 接收到的数据* @note* 请参考I2C通信协议,产生ACK应答信号: 在SDA为高电平时,SCL产生一个正脉冲* 如下图所示:方框部分表示I2C起始信号** _____ |<------------I2C数据读取周期(ACK)------------>|* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___ |* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \ |* | | | \___/\___/\___/\___/\___/\___/\___/\___/\____|_* __|_____|_ | _ _ _ _ _ _ _ _ _ |* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_|_* |_____| | |* start | D7 D6 D5 D4 D3 D2 D1 D0 ACK** _____ |<------------I2C数据读取周期(NACK)----------->|* __|__ | | ___ ___ ___ ___ ___ ___ ___ ___ ____|_* SDA: | \__|____|_/ \/ \/ \/ \/ \/ \/ \/ \/ |* | | | \___/\___/\___/\___/\___/\___/\___/\___/ |* __|_____|_ | _ _ _ _ _ _ _ _ _ |* SCL: | | \__|__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \_|_* |_____| | |* start | D7 D6 D5 D4 D3 D2 D1 D0 NACK*/
uint8_t I2C_Read_Byte(uint8_t ack)
{uint8_t i, receive = 0x00;uint32_t i2c_delay = i2c_speed;I2C_SCL_LOW; // SCL低电平SDA_IN(); // SDA设置为输入for (i = 0; i < 8; i++){BSP_I2C_Delay(i2c_delay);I2C_SCL_HIGH; // 高电平BSP_I2C_Delay(i2c_delay);receive <<= 1;if (I2C_SDA_READ())receive |= 1; // 高位在前I2C_SCL_LOW;}if (ack == 0)I2C_NACK(); // 发送NACKelseI2C_ACK(); // 发送ACKreturn receive; // 返回接收到的数据
}
/*=================================================####### END #######=====================================================*//*=============================================##### 封装好的I2C读写函数 #####===========================================*/
/*** @brief 模拟I2C接口初始化* @note* SCL: PB10* SDA: PB11*/
void BSP_I2C_Init(void)
{I2C_SetSpeed(100); //设置I2C访问速度为100Kbpsrt_pin_mode(I2C_SCL_PIN, PIN_MODE_OUTPUT);rt_pin_mode(I2C_SDA_PIN, PIN_MODE_OUTPUT);I2C_SCL_HIGH;I2C_SDA_HIGH;
}/*** @brief 向设备指定地址写入单一Byte数据* @param DevAddr : I2C从设备地址* @param DataAddr: 需要访问的设备内地址(如寄存器地址,EEPROM地址等)* @param Data : 写入的数据* @retval I2C访问的结果: I2C_SUCCESS / I2C_TIMEOUT / I2C_ERROR* @note* 1 - 设备地址DevAddr高7bit是固定的,最低位是读/写(R/W)位,1为读,0为写* 2 - 时序:* _______________________________________* | | | | | | | | |* Master:|S|DevAddr+W| |DataAddr| |Data| |P|* |_|_________|___|________|___|____|___|_|* _______________________________________* | | | | | | | | |* Slave: | | |ACK| |ACK| |ACK| |* |_|_________|___|________|___|____|___|_|*/
I2C_StatusTypeDef I2C_WriteOneByte(uint8_t DevAddr, uint8_t DataAddr, uint8_t Data)
{I2C_Start(); // Master发送起始信号I2C_Send_Byte(DevAddr); // Master发送从设备地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误#ifdef FM24CL64I2C_Send_Byte(DataAddr >> 8); // 发送高8位数据地址if(I2C_Wait_ACK()){return I2C_TIMEOUT; // 等待ACK超时错误}I2C_Send_Byte(DataAddr); // 发送低八位数据地址if(I2C_Wait_ACK()){return I2C_TIMEOUT; // 等待ACK超时错误}
#elseI2C_Send_Byte(DataAddr); // 发送低八位数据地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误
#endifI2C_Send_Byte(Data); // 发送数据if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误I2C_Stop(); // 发送停止信号return I2C_SUCCESS;
}/*** @brief 向设备指定地址连续写入数据(Burst写模式)* @param DevAddr : I2C从设备地址* @param DataAddr: 需要访问的设备内地址(如寄存器地址,EEPROM地址等)* 对于Burst模式,DataAddr一般是设备的FIFO,缓存,或存储设备的数据地址* @param *pData : 写入的数据首地址* @param Num : 连续写入的数据个数* @retval I2C访问的结果: I2C_SUCCESS / I2C_TIMEOUT / I2C_ERROR* @note* 1 - 设备地址DevAddr高7bit是固定的,最低为是读/写(R/W)位,1为读,0为写* 2 - 时序:* ____________________________________________________* | | | | | | | | | | | |* Master:|S|DevAddr+W| |DataAddr| |Data| |...|Data| |P|* |_|_________|___|________|___|____|___|___|____|___|_|* ____________________________________________________* | | | | | | | | | | | |* Slave: | | |ACK| |ACK| |ACK|...| |ACK| |* |_|_________|___|________|___|____|___|___|____|___|_|*/
I2C_StatusTypeDef I2C_WriteBurst(uint8_t DevAddr, uint8_t DataAddr, uint8_t* pData, uint32_t Num)
{uint32_t i = 0;I2C_Start(); // Master发送起始信号I2C_Send_Byte(DevAddr); // Master发送从设备地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误#ifdef FM24CL64I2C_Send_Byte(DataAddr >> 8); // 发送高8位数据地址if(I2C_Wait_ACK()) return I2C_TIMEOUT; // 等待ACK超时错误I2C_Send_Byte(DataAddr); // 发送低八位数据地址if(I2C_Wait_ACK()) return I2C_TIMEOUT; // 等待ACK超时错误
#elseI2C_Send_Byte(DataAddr); // 发送低八位数据地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误
#endiffor (i = 0; i < Num; i++){I2C_Send_Byte(*(pData + i)); // 发送数据if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误}I2C_Stop(); // 发送停止信号return I2C_SUCCESS;
}/*** @brief 从指定设备读取1Byte数据* @param DevAddr : I2C从设备地址* @param DataAddr: 需要访问的设备内地址(如寄存器地址,EEPROM地址等)* @param *Data : 数据的存放地址* @retval I2C访问的结果: I2C_SUCCESS / I2C_TIMEOUT / I2C_ERROR* @note* 1 - 设备地址DevAddr高7bit是固定的,最低为是读/写(R/W)位,1为读,0为写* 2 - 时序:* _________________________________________________________* | | | | | | | | | | | |* Master:|S|DevAddr+W| |DataAddr| |S|DevAddr+R| | |NACK|P|* |_|_________|___|________|____|_|_________|___|____|____|_|* _________________________________________________________* | | | | | | | | | | | |* Slave: | | |ACK| |ACK | | |ACK|Data| | |* |_|_________|___|________|____|_|_________|___|____|____|_|*/
I2C_StatusTypeDef I2C_ReadOneByte(uint8_t DevAddr, uint8_t DataAddr, uint8_t* Data)
{I2C_Start(); // Master发送起始信号I2C_Send_Byte(DevAddr); // Master发送从设备地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误#ifdef FM24CL64I2C_Send_Byte(DataAddr >> 8); // 发送高8位数据地址if(I2C_Wait_ACK()) return I2C_TIMEOUT; // 等待ACK超时错误I2C_Send_Byte(DataAddr); // 发送低八位数据地址if(I2C_Wait_ACK()) return I2C_TIMEOUT; // 等待ACK超时错误
#elseI2C_Send_Byte(DataAddr); // 发送低八位数据地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误
#endifI2C_Start(); // Master发送起始信号I2C_Send_Byte(DevAddr + 1); // Master发送从设备读地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误*Data = I2C_Read_Byte(0); // 读数据,NACKI2C_Stop(); // 发送停止信号return I2C_SUCCESS;
}/*** @brief 向设备指定地址连续读取数据(Burst写模式)* @param DevAddr : I2C从设备地址* @param DataAddr: 需要访问的设备内地址(如寄存器地址,EEPROM地址等)* 对于Burst模式,DataAddr一般是设备的FIFO,缓存,或存储设备的数据地址* @param *pData : 写入的数据首地址* @param Num : 连续写入的数据个数* @retval I2C访问的结果: I2C_SUCCESS / I2C_TIMEOUT / I2C_ERROR* @note* 1 - 设备地址DevAddr高7bit是固定的,最低为是读/写(R/W)位,1为读,0为写* 2 - 时序:* _____________________________________________________________________* | | | | | | | | | | | | | | |* Master:|S|DevAddr+W| |DataAddr| |S|DevAddr+R| | |ACK|...| |NACK|P|* |_|_________|___|________|___|_|_________|___|____|___|___|____|____|_|* _____________________________________________________________________* | | | | | | | | | | | | | | |* Slave: | | |ACK| |ACK| | |ACK|Data| |...|Data| | |* |_|_________|___|________|___|_|_________|___|____|___|___|____|____|_|*/
I2C_StatusTypeDef I2C_ReadBurst(uint8_t DevAddr, uint8_t DataAddr, uint8_t* pData, uint32_t Num)
{uint32_t i = 0;I2C_Start(); // Master发送起始信号I2C_Send_Byte(DevAddr); // Master发送从设备地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误#ifdef FM24CL64I2C_Send_Byte(DataAddr >> 8); // 发送高8位数据地址if(I2C_Wait_ACK()) return I2C_TIMEOUT; // 等待ACK超时错误I2C_Send_Byte(DataAddr); // 发送低八位数据地址if(I2C_Wait_ACK()) return I2C_TIMEOUT; // 等待ACK超时错误
#elseI2C_Send_Byte(DataAddr); // 发送低八位数据地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误
#endifI2C_Start(); // Master发送起始信号I2C_Send_Byte(DevAddr + 1); // Master发送从设备读地址if (I2C_Wait_ACK())return I2C_TIMEOUT; // 等待ACK超时错误for (i = 0; i < (Num - 1); i++){*(pData + i) = I2C_Read_Byte(1); // 读数据,ACK}*(pData + i) = I2C_Read_Byte(0); // 读数据,NACKI2C_Stop(); // 发送停止信号return I2C_SUCCESS;
}/*** @brief 设置数据的某一位* @param DevAddr : I2C从设备地址* @param DataAddr: 需要访问的设备内地址(如寄存器地址,EEPROM地址等)* @param Bitx : 第几位* @param BitSet: 需要设置的值* @retval I2C访问的结果: I2C_SUCCESS / I2C_TIMEOUT / I2C_ERROR* @note*/
I2C_StatusTypeDef I2C_WriteBit(uint8_t DevAddr, uint8_t DataAddr, uint8_t Bitx, uint8_t BitSet)
{I2C_StatusTypeDef status = I2C_ERROR;uint8_t tempdata = 0;status = I2C_ReadOneByte(DevAddr, DataAddr, &tempdata); // 获取原有数据if (status != I2C_SUCCESS)return status; // I2C错误,则返回tempdata &= ~(1 << Bitx); // 将要设定的位清零tempdata |= (BitSet << Bitx); // 设置指定的bitstatus = I2C_WriteOneByte(DevAddr, DataAddr, tempdata); // 写入数据return status; // 返回状态
}
/*=================================================####### END #######=====================================================*/
3.main.c
主要是使用函数来进行数据的读取和写入。
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "fm24c04.h"I2C_StatusTypeDef eeStatus = I2C_ERROR;
uint8_t wrEE[5] = {0x10, 0x11, 0x12, 0x13, 0x14};
uint8_t rdEE[5] = {0x00};int main(void)
{rt_pin_mode(GET_PIN(D, 13), PIN_MODE_OUTPUT);rt_pin_write(GET_PIN(D, 13), 1);BSP_I2C_Init();#if 1// 使用多字节写数据I2C_ReadBurst(0xA0, 0, rdEE, 5);I2C_WriteBurst(0xA0, 0, wrEE, 5);I2C_ReadBurst(0xA0, 0, rdEE, 5);#else// 使用单个字节写数据for (int i = 0; i < 5; i++){eeStatus = I2C_ReadOneByte(0xA0, i, &rdEE[i]);}for (int i = 0; i < 5; i++){eeStatus = I2C_WriteOneByte(0xA0, i, wrEE[i]);}eeStatus = I2C_ERROR;HAL_Delay(10);for (int i = 0; i < 5; i++){eeStatus = I2C_ReadOneByte(0xA0, i, &rdEE[i]);}#endifwhile (1){rt_thread_mdelay(1000);}
}
五、测试验证
通过将测试代码下载到控制板中,先读取存储芯片中的数据,并打印出存储的数据,将接收到的数据用于实际参数中即可。在存储数据的时候,一般可以使用自动存储的方式,即定时进行存储或者当数据稳定后,将需要存储的数据进行存储,存储的时候,可以自己定义存储的位置,根据上述的内容,进行数据的分区。
本次实验的IIC模拟时序函数,可以应用于大部分的模拟IIC时序,并且封装的读写函数也可以使用于大部分模拟IIC通信。