STM32 IIC硬件 读取AT24C04[标准库]

IIC介绍

IIC协议和原理这里就不多解释了,在使用AT24C0x系列的存储器时,其官方的数据手册有给出时序图。简单贴出几张图。在使用IIC或学习IIC时,不需要将IIC和器件手册分开读,连起来看更容易理解。
页写
随机读取

在STM32F103官方手册IIC章节中也有贴出。
stm32手册图

硬件驱动代码

这是我主要写这个博客的原因。参考野火的书籍也参考STM32参考手册的建议编程流程,对应下来逻辑是对的,但是时序上却很容易出错,比如上电后能正常读写一次,再次读写时会卡在发送设备地址后EV6事件未就绪中,要么就是发送起始信号EV5事件未就绪。
EV5指起始信号发送后SR1寄存器的SB置1的事件
EV6指发送设备地址后SR1寄存器ADDR在收到从机的ACK后置1事件

个人猜测是I2C_CheckEvent库函数中直接读取SR1,和SR2导致某些标志被清空了,根据下图STM32手册中的序列图做了些修改,比如将野火的I2C_CheckEvent监测事件修改成I2C_GetFlagStatus,对读SR1、SR2用I2C_ReadRegister去操作。
下图是一个STM32 IIC硬件主机发送的序列图,在写代码过程中要严格参考该序列顺序,比如发送起始位后等待EV5事件就绪,才可以发送地址等后续命令,因为STM32主时钟速度是72Mhz的,如果不进行等待事件发生话,DR寄存连续被写入时,其时序是非常不正常的,因为IIC的速率仅在400Khz以下。
发送序列
下图为STM32硬件IIC的接收序列图,读取AT24Cxx时的时序代码要参考如图,当有对应的事件响应后再执行下一步的操作。
接受序列

在手册中需要注意的点

发送起始条件
起始条件
成功发送起始位后读取SR1寄存器可以清空该寄存器位。
sb概述

从机的设备地址发送后,需要读SR1然后再读SR2清空该事件,在上面的发送序列图中的EV6解释中。
从地址发送
在发送从机设备地址只有收到ACK响应后才会被置1
ADDR位
后面的EV8和EV8_1就是监测TXE数据寄存器是否为空,当DR寄存器被写入数据后,在硬件电路的控制下配合时钟一位一位的向外移数据,当数据移完也就是空了,空了才能写入下一个数据。
BTF就是最后一个数据发送完,没有新的DR数据写进去了。
BTF
当了解这些寄存器的功能和清除顺序后,就可以直接贴代码了。

硬件驱动代码

源文件

#include "AT24Cxx.h"

/**
 * @brief I2C外设初始化
 * 
 */
void AT24_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

    GPIO_InitTypeDef AT24GPIO_GPIOStructure;
    AT24GPIO_GPIOStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    AT24GPIO_GPIOStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    AT24GPIO_GPIOStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&AT24GPIO_GPIOStructure);

    I2C_InitTypeDef AT24IIC_I2CStructure;
    AT24IIC_I2CStructure.I2C_Mode = I2C_Mode_I2C;
    AT24IIC_I2CStructure.I2C_ClockSpeed = 100000;
    AT24IIC_I2CStructure.I2C_DutyCycle = I2C_DutyCycle_16_9;
    AT24IIC_I2CStructure.I2C_Ack = I2C_Ack_Enable;
    AT24IIC_I2CStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    AT24IIC_I2CStructure.I2C_OwnAddress1 = 0x0A;
    I2C_Init(I2C1,&AT24IIC_I2CStructure);
    I2C_Cmd(I2C1,ENABLE);
}

/**
 * @brief 查询AT24是否就绪
 * 
 */
void I2C_AT24_Wait(void)
{
    uint16_t SR1_Tmp;
    do
    {
        I2C_GenerateSTART(I2C1,ENABLE);
        /* 手册图245的EV5,起始条件后读SR1再写地址 */
        /* 发送起始位SB置1,读取SR1寄存器,写数据寄存器清除SB位 */
        SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);
        I2C_Send7bitAddress(I2C1,AT24C04_ADDRESS,I2C_Direction_Transmitter);
        /* 发送地址然后收到ACK ADDR置位 */
    }while(!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));
    /* 清除应答失败 */
    I2C_ClearFlag(I2C1, I2C_FLAG_AF);
    /* 读SR1,SR2 */
    SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);
    SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR2);
    I2C_GenerateSTOP(I2C1,ENABLE);
}


/**
 * @brief 单字节写入
 * 
 * @param ucAddr EEPROM地址
 * @param pucBuf 数据
 * 
 */
void I2C_AT24_WriteByte(uint8_t ucAddr, uint8_t * pucBuf)
{
    I2C_GenerateSTART(I2C1,ENABLE);
    /* 开始信号等待SB起始位就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_SB));

    I2C_Send7bitAddress(I2C1,AT24C04_ADDRESS,I2C_Direction_Transmitter);
    /* 发送设备地址后等待ADDR就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR));
    /* ADDR硬件置位后读取SR1,SR2寄存器清空 */
    I2C_ReadRegister(I2C1,I2C_Register_SR1);
    I2C_ReadRegister(I2C1,I2C_Register_SR2);

    /* 等待移位寄存器、数据寄存器空 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));

    I2C_SendData(I2C1,ucAddr);
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));
    
    I2C_SendData(I2C1,*pucBuf);
    /* 发送数据后等待EV8_2事件 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_BTF));
    
    I2C_GenerateSTOP(I2C1,ENABLE);
}

/**
 * @brief 多字节写入
 * 
 * @param ucAddr 起始地址
 * @param pucBuf 数据
 * @param uiNum  写入数量
 */
void I2C_AT24_WriteBytes(uint8_t ucAddr, uint8_t * pucBuf,uint16_t uiNum)
{
    uint16_t i;
    for(i = 0; i < uiNum; i++)
    {
        //Delay_ms(1);
        I2C_AT24_WriteByte(ucAddr++,pucBuf++);
        /* 稍微等待一下 */
        Delay_ms(50);
        //I2C_AT24_Wait();
    }
}

/**
 * @brief 页写入,数量小于页大小
 * 
 * @param ucAddr 地址
 * @param pucBuf 数据
 * @param uiNum  数量
 */
void I2C_AT24_PageWrite(uint8_t ucAddr,uint8_t * pucBuf,uint16_t uiNum)
{
    uint16_t i;
    while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
    I2C_GenerateSTART(I2C1,ENABLE);
    /* 开始信号等待SB起始位就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_SB));

    I2C_Send7bitAddress(I2C1,AT24C04_ADDRESS,I2C_Direction_Transmitter);
    /* 发送设备地址后等待ADDR就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR));
    /* ADDR硬件置位后读取SR1,SR2寄存器清空 */
    I2C_ReadRegister(I2C1,I2C_Register_SR1);
    I2C_ReadRegister(I2C1,I2C_Register_SR2);
    /* 等待移位寄存器、数据寄存器空 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));

    I2C_SendData(I2C1,ucAddr);
    /* 发送内存地址后等待EV8事件 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));
    /* 发送数据 */
    for(i = 0; i < uiNum; i ++)
    {
        I2C_SendData(I2C1,*pucBuf++);
        /* 发送数据后等待EV8事件 */
        while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));
    }
    /* 发送数据后等待EV8_2事件 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_BTF));
    I2C_GenerateSTOP(I2C1,ENABLE);
}

/**
 * @brief 顺序读指定数量的数据
 * 
 * @param ucAddr 地址
 * @param pucBuf 存储数据指针
 * @param uiNum  读取数量
 */
void I2C_AT24_ReadBuf(uint8_t ucAddr,uint8_t *pucBuf, uint16_t uiNum)
{
    uint16_t i;
    while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
    I2C_GenerateSTART(I2C1,ENABLE);
    /* 开始信号等待SB起始位就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_SB));

    I2C_Send7bitAddress(I2C1,AT24C04_ADDRESS,I2C_Direction_Transmitter);
    /* 发送设备地址后等待ADDR就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR));
    /* ADDR硬件置位后读取SR1,SR2寄存器清空 */
    I2C_ReadRegister(I2C1,I2C_Register_SR1);
    I2C_ReadRegister(I2C1,I2C_Register_SR2);
    /* 等待移位寄存器、数据寄存器空 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));
    
    I2C_SendData(I2C1,ucAddr);
    /* 发送内存地址后等待EV8事件 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_TXE));

    I2C_GenerateSTART(I2C1,ENABLE);
    /* 开始信号等待SB起始位就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_SB));

    I2C_Send7bitAddress(I2C1,AT24C04_ADDRESS,I2C_Direction_Receiver);
    /* 发送设备地址后等待ADDR就绪 */
    while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_ADDR));
    /* ADDR硬件置位后读取SR1,SR2寄存器清空 */
    I2C_ReadRegister(I2C1,I2C_Register_SR1);
    I2C_ReadRegister(I2C1,I2C_Register_SR2);

    /* 读取数据,参考手册图246 */
    for(i = 0; i < uiNum; i ++)
    {
/**
 * @brief 在最后一个数据位关闭非应答
 * todo:最后一个数据不设置非应答位时,
 * i2c寄存器的SR1->busy位会置1导致后续的起始位不成功
 */
        if(i == (uiNum - 1))
        {
            /* 发送非应答 */
            I2C_AcknowledgeConfig(I2C1,DISABLE);
        }
        /* 等待EV7后读取数据 */
        while(!I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE));
        *pucBuf++ = I2C_ReceiveData(I2C1);
        
    }
    /* 停止 */
    I2C_GenerateSTOP(I2C1,ENABLE);

    /* 最后使能应答 */
    I2C_AcknowledgeConfig(I2C1,ENABLE);
    //I2C_AT24_Wait();
}

头文件

#ifndef __AT24CXX_H
#define __AT24CXX_H
#include "main.h"

#define AT24C04_ADDRESS             0xA0

#define AT24C04_Page1               0x1
#define AT24C04_ADDRESS_Write       0xA0
#define AT24C04_Read_Read           0xA1

/* 每页16字节 */
#define AT24C04_PageSize            16
/* 一共32页 */
#define AT24C04_PageSum             32

#define I2C_TimeOut                 1000

void AT24_Init(void);
void I2C_AT24_Wait(void);
void I2C_AT24_WriteByte(uint8_t ucAddr, uint8_t *pucBuf);
void I2C_AT24_WriteBytes(uint8_t ucAddr, uint8_t *pucBuf, uint16_t uiNum);
void I2C_AT24_PageWrite(uint8_t ucAddr, uint8_t *pucBuf, uint16_t uiNum);
void I2C_AT24_ReadBuf(uint8_t ucAddr, uint8_t *pucBuf, uint16_t uiNum);

#endif

应用代码示例,经测试,在while中可以无限的读取也不会卡死了。

uint8_t a[5]= {0x2,0x3,0x4,0x5,0x6};
uint8_t b[5];

AT24_Init();
I2C_AT24_WriteBytes(0x0000,a,5);
I2C_AT24_ReadBuf(0x0000,b,5);
	while(1)
	{
		
        GPIO_SetBits(GPIOA,GPIO_Pin_8);
		Delay_ms(100);
		GPIO_ResetBits(GPIOA,GPIO_Pin_8);
		Delay_ms(100);
		I2C_AT24_WriteBytes(0x0000,a,5);
		I2C_AT24_ReadBuf(0x0000,b,5);

		// OLED_Drawpoint(55,55,memory,0);
		// OLED_FullRefresh(memory);
		// USART1_Printf("哈喽NUM = %d",123);
		// printf("hello\r\n");
		// USART1_SendString("123");
	}
  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先简单的说明以下I2C总线,I2C总线是一种串行数据总线,只有二根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。在 I2C总线上传送的一个数据字节由八位组成。总线对每次传送的字节数没有限制,但每个字节后必须跟一位应答位。数据传送首先传送最高位(MSB),数据传送按图1所示格式进行。首先由主机发出启动信号“S”(SDA在SCL高电平期间由高电平跳变为低电平),然后由主机发送一个字节的数据。启动信号后的第一个字节数据具有特殊含义:高七位是从机的地址,第八位是传送方向位,0表示主机发送数据(写),1表示主机接收数据(读)。被寻址到的从机设备按传送方向位设置为对应工作方式。标准I2C总线的设备都有一个七位地址,所有连接在I2C总线上的设备都接收启动信号后的第一个字节,并将接收到的地址与自己的地址进行比较,如果地址相符则为主机要寻访的从机,应在第九位答时钟脉冲时向SDA线送出低电平作为应答。除了第一字节是通用呼叫地址或十位从机地址之外第二字节开始即数据字节。数据传送完毕,由主机发出停止信号“P”(SDA在SCL高电平期间由低电平跳变为高电平)。   AT24C系列串行E2PROM具有I2C总线接口功能,功耗小,宽电源电压(根据不同型号2.5V~6.0V),工作电流约为3mA,静态电流随电源电压不同为30μA~110μA,AT24C系列串行E2PROM参数如下 型 号 容 量 器件寻址字节(8位) 一次装载字节数 AT24C01 128×8 1010A2A1A0 R/W 4 AT24C02 256×8 1010A2A1A0 R/W 8 AT24C04 512×8 1010A2A1P0 R/W 16 AT24C08 1024×8 1010A2P1P0 R/W 16 AT24C16 2048×8 1010P2P1P0 R/W 16   由于I2C总线可挂接多个串行接口器件,在I2C总线中每个器件应有唯一的器件地址,按I2C总线规则,器件地址为7位数据(即一个I2C总线系统中理论上可挂接128个不同地址的器件),它和1位数据方向位构成一个器件寻址字节,最低位D0为方向位(读/写)。器件寻址字节中的最高4位(D7~D4)为器件型号地址,不同的I2C总线接口器件的型号地址是厂家给定的,如AT24C系列E2PROM的型号地址皆为1010,器件地址中的低3位为引脚地址A2 A1 A0,对应器件寻址字节中的D3、D2、D1位,在硬件设计时由连接的引脚电平给定。   对AT24C系列 E2PROM的读写操作完全遵守I2C总线的主收从发和主发从收的规则。
以下是使用STM32软件IIC读取AT24C02的示例代码: ```c #include "stm32f10x.h" #define SDA_GPIO GPIOB #define SDA_PIN GPIO_Pin_7 #define SCL_GPIO GPIOB #define SCL_PIN GPIO_Pin_6 #define I2C_READ_ADDR 0xA1 #define I2C_WRITE_ADDR 0xA0 void i2c_start(void); void i2c_stop(void); void i2c_send_byte(uint8_t data); uint8_t i2c_read_byte(void); void i2c_ack(void); void i2c_nack(void); void i2c_wait(void); int main(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = SDA_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; GPIO_Init(SDA_GPIO, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = SCL_PIN; GPIO_Init(SCL_GPIO, &GPIO_InitStructure); while(1) { uint8_t data = 0; uint8_t addr = 0; // 打开I2C总线 i2c_start(); // 发送器件地址和读命令 i2c_send_byte(I2C_WRITE_ADDR); // 发送要读取的地址 i2c_send_byte(addr); // 重新启动I2C总线 i2c_start(); // 发送器件地址和读命令 i2c_send_byte(I2C_READ_ADDR); // 读取数据并发送NACK data = i2c_read_byte(); i2c_nack(); // 关闭I2C总线 i2c_stop(); // 显示读取到的数据 printf("Read data: %d\n", data); // 延时 for(int i = 0; i < 1000000; i++); } } void i2c_start(void) { GPIO_SetBits(SDA_GPIO, SDA_PIN); GPIO_SetBits(SCL_GPIO, SCL_PIN); GPIO_ResetBits(SDA_GPIO, SDA_PIN); GPIO_ResetBits(SCL_GPIO, SCL_PIN); } void i2c_stop(void) { GPIO_ResetBits(SDA_GPIO, SDA_PIN); GPIO_SetBits(SCL_GPIO, SCL_PIN); GPIO_SetBits(SDA_GPIO, SDA_PIN); } void i2c_send_byte(uint8_t data) { for(int i = 0; i < 8; i++) { if(data & 0x80) GPIO_SetBits(SDA_GPIO, SDA_PIN); else GPIO_ResetBits(SDA_GPIO, SDA_PIN); GPIO_SetBits(SCL_GPIO, SCL_PIN); GPIO_ResetBits(SCL_GPIO, SCL_PIN); data <<= 1; } } uint8_t i2c_read_byte(void) { uint8_t data = 0; for(int i = 0; i < 8; i++) { data <<= 1; GPIO_SetBits(SCL_GPIO, SCL_PIN); if(GPIO_ReadInputDataBit(SDA_GPIO, SDA_PIN)) data |= 0x01; GPIO_ResetBits(SCL_GPIO, SCL_PIN); } return data; } void i2c_ack(void) { GPIO_ResetBits(SDA_GPIO, SDA_PIN); GPIO_SetBits(SCL_GPIO, SCL_PIN); GPIO_ResetBits(SCL_GPIO, SCL_PIN); } void i2c_nack(void) { GPIO_SetBits(SDA_GPIO, SDA_PIN); GPIO_SetBits(SCL_GPIO, SCL_PIN); GPIO_ResetBits(SCL_GPIO, SCL_PIN); } void i2c_wait(void) { for(int i = 0; i < 100; i++); } ``` 注意:本示例代码仅作为参考使用,实际应用中需根据具体情况进行修改和适配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值