文章参照使用旺宝红龙开发板,STM32F407ZGT6 KIT V1.0。
一、接口和通信协议
开发板上有一个I2C接口的EEPROM芯片AT24C02,是ATMEL公司的产品。因为其他一些厂家的芯片与AT24C02引脚和功能完全兼容,一般就统称为24C02。24C02存储容量为256字节,它的电路连接如图所示。其SDA和SCL引脚连接STM32F407芯片的I2C1接口,使用引脚PF0和PF9。WP是写保护引脚,WP接地时,对24C02芯片可读可写。
1、设备地址
24C02的I2C设备地址组成如图所示。A2、A1、A0位由芯片的引脚A2、A1、A0的电平决定,原理图中这3个引脚都接地,所以都是0。最低位R/W是读写标志位,当主设备对24C02进行写操作时,R/W为0,进行读操作时,R/W为1。所以,24C02的写操作地址为0xA0(10100000),读操作地址为0xA1(10100001)。HAL驱动程序函数中需要传递I2C从设备的地址时,都使用写操作地址。
2、写1字节数据
24C02的读写操作比较简单,存储空间可反复读写。
MCU向24C02写入1字节数据的SDA传输内容和顺序如下图。
操作的顺序如下:
●主机发送起始信号,然后发送器件的写操作地址。
● 24C02应答ACK后,主机再发送8位字地址,这是24C02内部存储单元的地址。8位地址的范围是0~255,也就是24C02内256字节存储单元的地址。
● 24C02应答ACK后,主机再发送需要写入的1字节的数据。
●从机接收完数据后,应答ACK,主机发停止信号结束传输。
3、连续写多字节数据
24C02内部存储区域按页划分,每页8字节,所以256字节的存储单元分为32页,页的起始地址是8×N,其中,N=0,1,…,31。
可以在一次I2C通信过程(一个起始信号与一个停止信号限定的通信过程)中向24C02连续写入多个字节的数据,SDA传输内容和顺序如下图。图中的n是数据存储的起始地址,存储的数据字节数为1+x。24C02会自动将接收的数据从指定的起始地址开始存储,但是要注意,连续写入的数据的存储位置不能超过页的边界,否则,将自动从这页的开始位置继续存储。
所以,在连续写数据时,如果数据起始地址在页的起始位置,则一次最多可写8字节的数据。当然,数据存储起始地址也可以不在页的起始位置,这时要注意,一次写入的数据不要超过页的边界。
4、读1字节数据
可以从24C02的任何一个存储位置读取1字节的数据,读取1字节数据时,SDA传输的内容和顺序如下图。主设备先进行一次写操作,写入需要读取的存储单元的地址,然后再进行一次读操作,读取的1字节数据就是所指定的存储地址的存储内容。
5、连续读多字节数据
可以从24C02一次性连续读取多字节的数据,且读取数据时不受页边界的影响,也就是读取数据的长度可以超过8字节。连续读多个字节数据的SDA传输内容和顺序如下图。主设备先进行一次写操作,写入需要读取的存储单元的地址,然后再进行一次读操作,连续读取多字节,存储器内部将自动移动存储位置,且存储位置不受页边界的影响。
在使用I2C的HAL驱动函数进行24C02的数据读写时,以上各图描述的传输时序是由I2C硬件接口完成的,工程师不需要理睬这些时序。
二、驱动程序设计
使用I2C接口的HAL驱动函数,根据24C02的数据读写操作的定义,编写24C02的驱动程序,将24C02的一些常用操作封装为函数,以便其他项目调用。
24C02的驱动程序文件包括头文件24cxx.h和源程序文件24cxx.c,头文件24cxx.h是24C02驱动程序的接口定义,其完整代码如下:
1、24cxx.h
/*
* 文件名:24cxx.h
* 功能描述: 24C02 EEPROM驱动程序,使用HAL库
*
* (1)24C02是 256字节EEPROM,可以单个字节读写,连续读写时按页读写,每页8字节。所以,按页读写时最多8字节,页号0--31
*
* (2)连续写入数据时要注意不要超过页的边界,否则从页的开始处重新写,会覆盖原来的内容。
*
* (3)24C02地址是 0b1010xxxy, 其中xxx由芯片的地址引脚A2,A1,A0决定,一般接地,所以是0xA0, HAL库在读写时自动在最后一位写0或1进行读或写操作
*
* (4)24C02的地址数据长度是8位,使用宏定义符号I2C_MEMADD_SIZE_8BIT
*/
#ifndef __24cxx_H
#define __24cxx_H
#include "stm32f4xx_hal.h"
#include "i2c.h" //i2c.h中定义了hi2c2
/* 两个与硬件相关的定义, */
#define I2C_HANDLE hi2c2 //I2C外设对象变量,利用i2c.h中定义的hi2c2
#define DEV_ADDR_24CXX 0x00A0 //24C02的写地址
// EEPROM存储器参数
#define PAGE_SIZE_24CXX 0x0008 //24C02的Page大小为8字节
#define MEM_SIZE_24CXX (uint16_t)256 //24C02总共容量字节数,8*32=256字节
//检查设备是否准备好
HAL_StatusTypeDef EP24C_IsDeviceReady(void);
//在任意地址写入一个字节
HAL_StatusTypeDef EP24C_WriteOneByte(uint16_t memAddress, uint8_t byteData);
//在任意地址读出一个字节
HAL_StatusTypeDef EP24C_ReadOneByte(uint16_t memAddress, uint8_t *byteData);
//连续读取数据,任意地址,任意长度,不受页的限制
HAL_StatusTypeDef EP24C_ReadBytes(uint16_t memAddress, uint8_t *pBuffer, uint16_t bufferLen);
/*限定在一个页内写入连续数据,最多8字节。任意起始地址,
*但是起始地址+数据长度不能超过页边界,即不能超过地址8*N-1 */
HAL_StatusTypeDef EP24C_WriteInOnePage(uint16_t memAddress, uint8_t *pBuffer, uint16_t bufferLen);
//写任意长的数据,可以超过8字节,但数据地址必须从页首开始,即 8*N
HAL_StatusTypeDef EP24C_WriteLongData(uint16_t memAddress, uint8_t *pBuffer, uint16_t bufferLen);
#endif
为便于程序的移植,在文件24cxx.h中定义一个宏I2C_HANDLE替代hi2c2。如果使用了不同的I2C接口,只需修改这个宏定义即可,而I2C接口的外设初始化由CubeMX自动生成的函数完成。在24cxx.h中还定义了表示24C02地址的宏DEV_ADDR_24CXX,如果实际电路中的24C02的I2C地址被修改了,修改这个宏即可。
在文件24cxx.h中定义了5个函数,前面4个都是直接封装I2C的HAL传输函数实现的,最后1个函数EP24C_WriteLongData()能自动将一个长的数据拆分为多个页(每页8字节)写入,这5个函数都使用I2C的阻塞式存储器数据传输函数。
2、24cxx.c
/* 文件: 24cxx.c
* 描述: 24C02驱动程序源程序文件
*/
#include "24cxx.h"
#define EP24C_TIMEOUT 200 //超时等待时间,单位:ms
#define EP24C_MEMADD_SIZE I2C_MEMADD_SIZE_8BIT //存储器地址大小,8位地址
//检查设备是否准备好I2C通讯,返回HAL_OK 表示OK
HAL_StatusTypeDef EP24C_IsDeviceReady(void)
{
uint32_t Trials=10; //尝试次数
HAL_StatusTypeDef result=HAL_I2C_IsDeviceReady(&I2C_HANDLE,DEV_ADDR_24CXX,Trials,EP24C_TIMEOUT);
return result;
}
//向任意地址写入一个字节的数据, memAddr是存储器内部地址,byteData是需要写入的1个字节的数据
HAL_StatusTypeDef EP24C_WriteOneByte(uint16_t memAddress,uint8_t byteData)
{
HAL_StatusTypeDef result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
EP24C_MEMADD_SIZE,&byteData,1,EP24C_TIMEOUT);
return result;
}
//从任意地址读出一个字节的数据, memAddr是存储器内部地址,byteData是读出的1个字节的数据,若返回HAL_OK表示读取成功
HAL_StatusTypeDef EP24C_ReadOneByte(uint16_t memAddress,uint8_t *byteData)
{
HAL_StatusTypeDef result=HAL_I2C_Mem_Read(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
EP24C_MEMADD_SIZE,byteData,1,EP24C_TIMEOUT);
return result;
}
//连续读取数据,任意地址,任意长度,不受页的限制
HAL_StatusTypeDef EP24C_ReadBytes(uint16_t memAddress,uint8_t *pBuffer,uint16_t bufferLen)
{
if(bufferLen>MEM_SIZE_24CXX) //超过总存储容量
return HAL_ERROR;
HAL_StatusTypeDef result=HAL_I2C_Mem_Read(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT);
return result;
}
//限定在一个页内写入连续数据,最多8字节。任意起始地址,但是起始地址+数据长度不能超过页边界
HAL_StatusTypeDef EP24C_WriteInOnePage(uint16_t memAddress,uint8_t *pBuffer,uint16_t bufferLen)
{
if(bufferLen>PAGE_SIZE_24CXX) //数据长度不能大于页的大小
return HAL_ERROR;
HAL_StatusTypeDef result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT);
return result;
}
//写任意长的数据,可以超过8字节,但数据地址必须从页首开始,即 8*N。自动分解为多次写入
HAL_StatusTypeDef EP24C_WriteLongData(uint16_t memAddress,uint8_t *pBuffer,uint16_t bufferLen)
{
if(bufferLen>MEM_SIZE_24CXX) //超过总存储容量
return HAL_ERROR;
HAL_StatusTypeDef result=HAL_ERROR;
if(bufferLen<=PAGE_SIZE_24CXX) //不超过1个page,直接写入后退出
{
result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
EP24C_MEMADD_SIZE,pBuffer,bufferLen,EP24C_TIMEOUT);
return result;
}
uint8_t *pt=pBuffer; //临时指针,不能改变传入的指针
uint16_t pageCount=bufferLen/PAGE_SIZE_24CXX; //Page个数
for(uint16_t i=0; i<pageCount; i++) //一次写入一个page的数据
{
result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
EP24C_MEMADD_SIZE,pt,PAGE_SIZE_24CXX,EP24C_TIMEOUT);
pt += PAGE_SIZE_24CXX;
memAddress += PAGE_SIZE_24CXX;
HAL_Delay(5); //必须有延时,以等待页写完
if (result != HAL_OK)
return result;
}
uint16_t leftBytes=bufferLen % PAGE_SIZE_24CXX; //余数
if (leftBytes>0) //写入剩余的数据
result=HAL_I2C_Mem_Write(&I2C_HANDLE,DEV_ADDR_24CXX,memAddress,
EP24C_MEMADD_SIZE,pt,leftBytes,EP24C_TIMEOUT);
return result;
}
24C02是I2C接口的存储器,使用I2C的HAL驱动程序中的存储器数据传输函数进行数据读写更方便。这几个函数就是封装了函数HAL_I2C_Mem_Write()和HAL_I2C_Mem_Read(),只是函数的接口定义更简化,便于用户程序调用。
函数EP24C_WriteOneByte()用于在任意地址写入1字节数据。
函数EP24C_ReadOneByte()用于从任意地址读取1字节数据。
函数EP24C_ReadBytes()用于从任意地址开始读取任意长度的数据,不受每页8字节的限制。
函数EP24C_WriteInOnePage()限定在一个页内写数据,可以是任意起始地址,但一次最多写入8字节。
函数EP24C_WriteLongData()用于从一个页的起始地址开始写入超过8字节的数据,函数内部会将数据拆分为多个页,分为多次写入。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/wenchm/article/details/144736520
|
|