IIC介绍
IIC协议和原理这里就不多解释了,在使用AT24C0x系列的存储器时,其官方的数据手册有给出时序图。简单贴出几张图。在使用IIC或学习IIC时,不需要将IIC和器件手册分开读,连起来看更容易理解。
在STM32F103官方手册IIC章节中也有贴出。
硬件驱动代码
这是我主要写这个博客的原因。参考野火的书籍也参考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寄存器可以清空该寄存器位。
从机的设备地址发送后,需要读SR1然后再读SR2清空该事件,在上面的发送序列图中的EV6解释中。
在发送从机设备地址只有收到ACK响应后才会被置1
后面的EV8和EV8_1就是监测TXE数据寄存器是否为空,当DR寄存器被写入数据后,在硬件电路的控制下配合时钟一位一位的向外移数据,当数据移完也就是空了,空了才能写入下一个数据。
BTF就是最后一个数据发送完,没有新的DR数据写进去了。
当了解这些寄存器的功能和清除顺序后,就可以直接贴代码了。
硬件驱动代码
源文件
#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");
}