I2C学习24C64
https://blog.csdn.net/hzb15195948039/article/details/86673431?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EESLANDING%7Edefault-2-86673431-blog-126924091.pc_relevant_landingrelevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EESLANDING%7Edefault-2-86673431-blog-126924091.pc_relevant_landingrelevant&utm_relevant_index=5
前言:
常用的IIC类存储设备是以EEPROM和铁电为主。如:AT24C0xBN(x=2,4,8),这是ATMEL公司的EEPROM;FM24CL64B或者FM24CL16B,这是CYPRESS公司的铁电(F-RAM)。后续章节IIC程序是基于AT24C04BN和FM24CL64B这两款设备。
IIC时序简介:
起始信号:在SCL为高电平期间,SDA由高变低;
停止信号:在SCL为高电平期间,SDA由低变高。如下图所示:
应答信号:在8位数据信号传输完成之后,第9位就是应答信号。应答信号分为应答和非应答,应答为低电平,非应答为高电平,如下图所示:
IIC总线数据传输时序如下:
对于AT24C04BN与FM24CL64B在读写操作时,存在点差别。主要是因为FM24CL64B的内存64KB,寻址空间是0x0000~0xFFFFH,访问时需要先传输地址高字节,再传输地址低字节。
AT24C04BN写数据流程:START-->DEVICE ADDRESS(写)-->ACK-->WORD ADDRESS-->ACK-->DATA-->ACK-->STOP,其中ACK为从设备发送的应答信号(低电平),主机接收应答。
AT24C04BN读数据流程:START-->DEVICE ADDRESS(写)-->ACK-->WORD ADDRESS-->START-->DEVICE ADDRESS(读)-->ACK-->DATA-->NACK-->STOP。其中ACK是从设备发送的应答信号,低电平有效,NACK是主机发送的非应答信号,高电平有效。
FM24CL64B写数据流程:START-->Slave Address(写)-->ACK-->Address MSB-->ACK-->Address LSB-->ACK-->Data Byte-->ACK-->STOP。其中ACK是从机发送的应答信号,低电平有效。
FM24CL64B读数据流程:START-->Slave Address(写)-->ACK-->Address MSB-->ACK-->Address LSB-->ACK-->START-->Slave Address(读)-->ACK-->Data Byte-->NACK-->STOP。其中,ACK是从设备发送的应答信号,低电平有效,NACK是主机发送的非应答信号,高电平有效。
试验中,我使用单片机是STM32F091VC,程序中通过宏定义来兼容AT24C04BN和FM24CL64B两款I2C设备。#if 0默认使用AT24C04BN,#if 1则使用FM24CL64B。
#if 1
#define FM24CL64 1
#endif
以下是bsp_fram.h头文件代码:
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef _BSP_FRAM_H
#define _BSP_FRAM_H
#include "main.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 定义读写SCL和SDA的宏,已增加代码的可移植性和可阅读性 */
/* 定义I2C总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
#define GPIO_PORT_I2C GPIOB /* GPIO端口 */
#define RCC_I2C_PORT RCC_AHBPeriph_GPIOB /* GPIO端口时钟 */
#define I2C_SCL_PIN GPIO_Pin_8 /* 连接到SCL时钟线的GPIO */
#define I2C_SDA_PIN GPIO_Pin_9 /* 连接到SDA数据线的GPIO */
#define I2C_SCL_H GPIO_SetBits(GPIO_PORT_I2C, I2C_SCL_PIN) /* SCL = 1 */
#define I2C_SCL_L GPIO_ResetBits(GPIO_PORT_I2C, I2C_SCL_PIN) /* SCL = 0 */
#define I2C_SDA_H GPIO_SetBits(GPIO_PORT_I2C, I2C_SDA_PIN) /* SDA = 1 */
#define I2C_SDA_L GPIO_ResetBits(GPIO_PORT_I2C, I2C_SDA_PIN) /* SDA = 0 */
#define I2C_SDA_READ() GPIO_ReadInputDataBit(GPIO_PORT_I2C, I2C_SDA_PIN)/* 读SDA口线状态 */
#define I2C_SPEED_1K 5000 //根据处理器速度设置,这里处理器速度是72MHz
typedef enum
{
I2C_SUCCESS = 0,
I2C_TIMEOUT,
I2C_ERROR,
}I2C_StatusTypeDef;
extern uint32_t i2c_speed; //I2C访问速度 = I2C_SPEED_1K / i2c_speed
/* ---------------------------依照I2C协议编写的时序函数------------------------------*/
void BSP_I2C_Init(void); //初始化I2C的IO口
void I2C_Start(void); //发送I2C开始信号
void I2C_Stop(void); //发送I2C停止信号
uint8_t I2C_Wait_ACK(void); //I2C等待ACK信号
void I2C_ACK(void); //I2C发送ACK信号
void I2C_NACK(void); //I2C不发送ACK信号
void I2C_Send_Byte(uint8_t data); //I2C发送一个字节
uint8_t I2C_Read_Byte(uint8_t ack); //I2C读取一个字节
uint16_t I2C_SetSpeed(uint16_t speed);//设置I2C速度(1Kbps~400Kbps,speed单位,Kbps)
/* ---------------------------以下部分是封装好的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);
#endif /* _bsp_FRAM_H */
以下是bsp_fram.c的c文件代码:
/* Includes ------------------------------------------------------------------*/
#include "stm32f0xx.h"
#include "main.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#if 1
#define FM24CL64 1
#endif
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
// I2C访问速度 = I2C_SPEED_1K / i2c_speed
uint32_t i2c_speed;
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief 模拟I2C接口初始化
* @param None
* @retval None
* @note
* SCL: PB8
* SDA: PB9
*/
void BSP_I2C_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_I2C_PORT|RCC_AHBPeriph_GPIOD|RCC_AHBPeriph_GPIOE,ENABLE); /* 打开GPIO时钟 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; /* 输出 */
GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN|I2C_SDA_PIN;
GPIO_Init(GPIO_PORT_I2C, &GPIO_InitStructure);
I2C_SetSpeed(100);//设置I2C访问速度为100Kbps
}
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_PORT_I2C, &GPIO_InitStructure);
}
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_PORT_I2C, &GPIO_InitStructure);
}
/**
* @brief 产生I2C起始信号
* @param None
* @retval None
* @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_H; //SDA: 高
I2C_SCL_H; //SCL: 高
i2c_delay = i2c_speed;//延时>4.7us
while(i2c_delay--){}
I2C_SDA_L; //当SCL为高电平时,SDA由高变低
i2c_delay = i2c_speed;//延时>4us
while(i2c_delay--){}
I2C_SCL_L; //SCL变低,钳住I2C总线,准备发送或接收数据
}
/**
* @brief 产生I2C停止信号
* @param None
* @retval None
* @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_L; //SDA低电平
I2C_SCL_H; //SCL高电平
i2c_delay = i2c_speed;//延时>4us
while(i2c_delay--){}
I2C_SDA_H; //STOP:当SCL为高电平时,SDA由低变高
i2c_delay = i2c_speed;
while(i2c_delay--){}//延时>4.7us
}
/**
* @brief 等待ACK应答信号
* @param None
* @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_H; //SDA上拉输入
I2C_SCL_H; //SCL设置为高电平
i2c_delay = i2c_speed;
while(i2c_delay--){}
while(I2C_SDA_READ() == 1)//等待ACK
{
if(timeout++ > 250)
{
I2C_Stop();
return 1;
}
}
I2C_SCL_L;//钳住I2C总线:时钟信号设为低电平
return 0;
}
/**
* @brief 产生ACK应答信号
* @param None
* @retval None
* @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_L; //低电平
SDA_OUT(); //设置SDA为输出
I2C_SDA_L; //ACK信号
i2c_delay = i2c_speed;
while(i2c_delay--){}//延时>4us
I2C_SCL_H; //高电平
i2c_delay = i2c_speed;
while(i2c_delay--){}//延时>4us
I2C_SCL_L; //钳住I2C总线:时钟信号设为低电平
}
/**
* @brief 产生非应答信号NACK
* @param None
* @retval None
* @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_L; //低电平
SDA_OUT(); //SDA设置为输出
I2C_SDA_H; //NACK信号
i2c_delay = i2c_speed;
while(i2c_delay--){}//延时>4us
I2C_SCL_H; //高电平
i2c_delay = i2c_speed;
while(i2c_delay--){}//延时>4us
I2C_SCL_L; //钳住I2C总线:时钟信号设为低电平
}
/**
* @brief I2C发送一个字节
* @param None
* @retval None
* @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_L; //钳住I2C总线:SCL设为低电平
for(i = 0; i < 8; i++)
{
if(data&0x80)I2C_SDA_H; //高位先传
else I2C_SDA_L;
i2c_delay = i2c_speed;
while(i2c_delay--){} //延时>4us
I2C_SCL_H; //在SCL上产生一个正脉冲
i2c_delay = i2c_speed;
while(i2c_delay--){} //延时>4us
I2C_SCL_L;
i2c_delay = i2c_speed/3;
while(i2c_delay--){} //延时>1us
data <<= 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_L; //SCL低电平
SDA_IN(); //SDA设置为输入
for(i = 0; i < 8; i++)
{
i2c_delay = i2c_speed;
while(i2c_delay--);
I2C_SCL_H; //高电平
i2c_delay = i2c_speed;
while(i2c_delay--);
receive <<= 1;
if(I2C_SDA_READ()) receive |= 1; //高位在前
I2C_SCL_L;
}
if (ack == 0) I2C_NACK(); //发送NACK
else I2C_ACK(); //发送ACK
return receive; //返回接收到的数据
}
/**
* @brief 设置I2C速度
* @param speed : I2C速度,单位Kbps
* @retval 返回设置前的I2C速度
* @note I2C速度设置范围是: 1Kbps ~ 400Kbps
*/
uint16_t I2C_SetSpeed(uint16_t speed)
{
uint16_t temp;
//I2C速度必须小于400Kbps,大于 1Kbps
if((speed > 400)|| (speed < 1)) return 0;
temp = I2C_SPEED_1K / i2c_speed; //备份原来的i2c速度
i2c_speed = I2C_SPEED_1K / speed; //设置新的i2c速度
return temp; //返回设置前的i2c速度
}
/* ---------------------------以下部分是封装好的I2C读写函数--------------------------- */
//具体到某一个器件,请仔细阅读器件规格书关于I2C部分的说明,因为某些器件I2C的读写操作会
//有一些差异,下面的代码我们在绝大多数的I2C器件中,都是验证OK的!
/**
* @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 FM24CL64
I2C_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超时错误
#else
I2C_Send_Byte(DataAddr); //发送低八位数据地址
if(I2C_Wait_ACK()) return I2C_TIMEOUT;//等待ACK超时错误
#endif
I2C_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 FM24CL64
I2C_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超时错误
#else
I2C_Send_Byte(DataAddr); //发送低八位数据地址
if(I2C_Wait_ACK()) return I2C_TIMEOUT;//等待ACK超时错误
#endif
for(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 FM24CL64
I2C_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超时错误
#else
I2C_Send_Byte(DataAddr); //发送低八位数据地址
if(I2C_Wait_ACK()) return I2C_TIMEOUT;//等待ACK超时错误
#endif
I2C_Start(); //Master发送起始信号
I2C_Send_Byte(DevAddr+1); //Master发送从设备读地址
if(I2C_Wait_ACK()) return I2C_TIMEOUT;//等待ACK超时错误
*Data = I2C_Read_Byte(0); //读数据,NACK
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| |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 FM24CL64
I2C_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超时错误
#else
I2C_Send_Byte(DataAddr); //发送低八位数据地址
if(I2C_Wait_ACK()) return I2C_TIMEOUT;//等待ACK超时错误
#endif
I2C_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); //读数据,NACK
I2C_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); //设置指定的bit
status = I2C_WriteOneByte(DevAddr, DataAddr, tempdata); //写入数据
return status; //返回状态
}
————————————————
版权声明:本文为CSDN博主「劲风草」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hzb15195948039/article/details/86673431