注意说明
网上的示例代码都基于arm等内核MCU,有些地方不适合用在低端MCU上,因为内存和堆栈的显示不允许这样使用,需要重新调整IIC通讯!这边只是参考,通讯部分还需要自己重头写!特别是 delayms(10),这个在产品中是不允许出现!整整10ms内MCU啥都不做,这容易触发卡顿或者看门狗复位!
一、应用场景?
有些单片机没有内部的EEPROM,或者说内部的EEPROM空间不够大,这时候,我们就想到了外挂一个EEPROM芯片,而AT24C32就是一种常用的串行EEPROM存储器,它适用于许多需要在系统掉电后保留数据的嵌入式应用场景。
二、AT24C32是什么?
AT24C32是一种常见的串行 EEPROM 存储器芯片,通常用于存储系统配置、用户数据、校准参数等。它具有以下特点,一般会在以下情况下被使用:
- 小容量数据存储:AT24C32的存储容量一般为32Kb,适合存储少量的配置信息、校准参数、设备标识等数据。
- 串行接口:AT24C32采用串行接口进行数据通信,这使得它在占用较少引脚的同时能够进行高效的数据读写操作,因此适合于资源受限的嵌入式系统或小型设备中使用。
- 数据保存与恢复:AT24C32通常被用于存储需要持久保存的数据,例如设备配置、校准参数等。它的非易失性存储特性意味着即使断电也能保持数据完整,可在下次上电时恢复。
- 嵌入式系统:由于其小巧的封装、低功耗和易于集成的特点,AT24C32常被嵌入式系统设计中用于管理系统配置、日志记录、用户个性化设置等数据。
芯片对比
型号 |
容量bit |
容量byte |
页数 |
字节/页 |
器件寻址位 |
可寻址器件数 |
WordAddress位数/字节数 |
备注 |
AT24C32 |
32k |
4k |
128 |
32 |
A2A1A0 |
8 |
12/2 |
|
AT24C64 |
64k |
8k |
256 |
32 |
A2A1A0 |
8 |
13/2 |
|
AT24C128 |
128k |
16k |
256 |
64 |
A1A0 |
4 |
14/2 |
|
AT24C256 |
256k |
32k |
512 |
64 |
A1A0 |
4 |
15/2 |
|
T24C512 |
512k |
64k |
512 |
128 |
A2A1A0 |
8 |
16/2 |
|
这5款芯片与x24C01~x24C16主要的不同是,WordAddress是两个字节,器件寻址位A2/A1/A0三位或者两位,不再有页选择位。例如,下图是AT24C128、AT24C256的引脚图,只有两个硬件地址引脚A0/A1,第3脚不连接。
三、使用步骤
1.引脚说明
1-3.A0~A2:地址输入引脚,共3个,用于设定芯片的I2C地址。
4.GND:地引脚,与VCC联合供电。
5.SDA:串行数据输入/输出引脚,用于与I2C总线进行通信。
6.SCL:串行时钟输入引脚,用于与I2C总线进行同步。
7.WP:写保护引脚,当WP引脚为高电平时,AT24C32将无法写入数据,起到了对AT24C32数据的保护作用。
8.VCC:供电引脚,连接正极电源。
其中,WP引脚是AT24C32的写保护引脚,用于控制AT24C32是否可以写入数据。当WP引脚处于高电平时,AT24C32将进入写保护模式,此时将无法写入数据。当WP引脚处于低电平时,AT24C32将退出写保护模式,此时可以向其写入数据。通常情况下,WP引脚需要连接到系统中的GPIO引脚上,以便在需要时能够动态控制AT24C32的写保护模式
1.2读写操作
1.2.1 写操作
Byte Write写一个字节
AT24C32和AT24C64手册中写单个字节的操作及时序,红框中可以看出,在器件地址、应答信号后,跟着的是两个字节的WordAddress,这就是与AT24C01~AT24C16芯片的主要区别。
Page Write写一页
页写操作也是没有多少新东西,除了WordAddress有两个字节,与其他容量芯片的操作类似。
1.2.2 写操作
读任意地址
与较小容量的芯片相比,读任意地址的时序,主要的区别是需要发送两个字节的WordAddress。
顺序读(页读)
以上为页读操作,也是由“读当前地址”或“读任意地址”操作开始,mcu收到一个数据后,应答,不发送停止信号,即可接收下一个字节数据。
1.2.3 示例代码(可参考)
宏定义
先对器件地址等信息进行宏定义,根据不同的器件进行条件编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #define READ_CMD 1 #define WRITE_CMD 0
#define x24C32 #define DEV_ADDR 0xA0
#ifdef x24C32 #define PAGE_NUM 128 #define PAGE_SIZE 32 #define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) #define ADDR_BYTE_NUM 2 #endif #ifdef x24C64 #define PAGE_NUM 256 #define PAGE_SIZE 32 #define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) #define ADDR_BYTE_NUM 2 #endif #ifdef x24C128 #define PAGE_NUM 256 #define PAGE_SIZE 64 #define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) #define ADDR_BYTE_NUM 2 #endif #ifdef x24C256 #define PAGE_NUM 512 #define PAGE_SIZE 64 #define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) #define ADDR_BYTE_NUM 2 #endif #ifdef x24C512 #define PAGE_NUM 512 #define PAGE_SIZE 128 #define CAPACITY_SIZE (PAGE_NUM * PAGE_SIZE) #define ADDR_BYTE_NUM 2 #endif
|
写单个字节(写任意地址)
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要写入数据的地址的高字节(8bit)–>收到应答–>发送需要写入数据的地址的低字节(8bit)–>收到应答–>发送需要写入的数据–>收到应答–>发送停止信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
void x24Cxx_WriteByte(uint16_t u16Addr, uint8_t u8Data) { x24Cxx_WriteEnable(); IIC_Start(); IIC_WriteByte(DEV_ADDR | WRITE_CMD); IIC_WaitAck(); IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF)); IIC_WaitAck(); IIC_WriteByte((uint8_t)(u16Addr & 0xFF)); IIC_WaitAck(); IIC_WriteByte(u8Data); IIC_WaitAck(); IIC_Stop(); x24Cxx_WriteDisble(); }
|
写一页
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要写入数据的首地址的高字节(8bit)–>收到应答–>发送需要写入数据的首地址的低字节(8bit)–>收到应答–>发送需要写入的第1个数据–>收到应答–>发送需要写入的第2个数据–>收到应答…–>发送需要写入的第n个数据–>收到应答–>发送停止信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
void x24Cxx_WritePage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pData) { uint8_t i; x24Cxx_WriteEnable(); IIC_Start(); IIC_WriteByte(DEV_ADDR | WRITE_CMD); IIC_WaitAck(); IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF)); IIC_WaitAck(); IIC_WriteByte((uint8_t)(u16Addr & 0xFF)); IIC_WaitAck(); if (u8Len > PAGE_SIZE) { u8Len = PAGE_SIZE; } if ((u16Addr + (uint16_t)u8Len) > CAPACITY_SIZE) { u8Len = (uint8_t)(CAPACITY_SIZE - u16Addr); } if (((u16Addr % PAGE_SIZE) + (uint16_t)u8Len) > PAGE_SIZE) { u8Len -= (uint8_t)((u16Addr + (uint16_t)u8Len) % PAGE_SIZE); } for (i = 0; i < u8Len; i++) { IIC_WriteByte(*(pData + i)); IIC_WaitAck(); } IIC_Stop(); x24Cxx_WriteDisble(); }
|
读单个字节(读任意地址)
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要读取数据的地址的高字节(8bit)–>收到应答–>发送需要读取数据的地址的低字节(8bit)–>收到应答–>发送起始信号–>发送器件地址(包含读取命令)–>收到应答–>读取数据–>不应答–>发送停止信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
uint8_t x24Cxx_ReadByte(uint16_t u16Addr) { uint8_t u8Data = 0; IIC_Start(); IIC_WriteByte(DEV_ADDR | WRITE_CMD); IIC_WaitAck(); IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF)); IIC_WaitAck(); IIC_WriteByte((uint8_t)(u16Addr & 0xFF)); IIC_WaitAck(); IIC_Start(); IIC_WriteByte(DEV_ADDR | READ_CMD); IIC_WaitAck(); u8Data = IIC_ReadByte(); IIC_NoAck(); IIC_Stop(); return u8Data; }
|
读一页(顺序读)
发送起始信号–>发送器件地址(包含写入命令)–>收到应答–>发送需要读取数据的首地址高字节–>收到应答–>发送需要读取数据的首地址低字节–>收到应答–>发送起始信号–>发送器件地址(包含读取命令)–>收到应答–>读取第1个数据–>发送应答–>读取第2个数据–>发送应答…–>读取第n个数据–>不应答–>发送停止信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
void x24Cxx_ReadPage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pBuff) { uint8_t i; IIC_Start(); IIC_WriteByte(DEV_ADDR | WRITE_CMD); IIC_WaitAck(); IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF)); IIC_WaitAck(); IIC_WriteByte((uint8_t)(u16Addr & 0xFF)); IIC_WaitAck(); IIC_Start(); IIC_WriteByte(DEV_ADDR | READ_CMD); IIC_WaitAck(); if (u8Len > PAGE_SIZE) { u8Len = PAGE_SIZE; } if ((u16Addr + (uint16_t)u8Len) > CAPACITY_SIZE) { u8Len = (uint8_t)(CAPACITY_SIZE - u16Addr); } if (((u16Addr % PAGE_SIZE) + (uint16_t)u8Len) > PAGE_SIZE) { u8Len -= (uint8_t)((u16Addr + (uint16_t)u8Len) % PAGE_SIZE); } for (i = 0; i < (u8Len - 1); i++) { *(pBuff + i) = IIC_ReadByte(); IIC_Ack(); } *(pBuff + u8Len - 1) = IIC_ReadByte(); IIC_NoAck(); IIC_Stop(); }
|
注意事项
- 仅适用于x24C32、x24C64、x24C128、x24C256、x24C512系列EEPROM芯片,其他容量芯片请参考第一节中链接的相关文章;
- 器件地址必须与A2/A1/A0引脚的硬件连接对应;
- 调用写入程序(无论是单字节写入还是页写),需要延时10ms(即twr,有的芯片手册说是5ms)后再对器件进行操作,否则这段时间内器件不响应命令;
2.示例代码
EEPROM设备都需要一个8位设备地址字,包含一个启动条件,以使芯片能够进行读或写操作。设备地址字前4位最高有效位为1010。这对所有串行EEPROM设备都是通用的。接下来的3位是EEPROM的A2、A1和A0设备地址位。设备地址的第8位是读写操作选择位。如果该位高,则进行读操作;如果该位低,则进行写操作。
综上,如果对AT24C32进行读操作,则设备地址为10100001B=A1H;如果对AT24C32进行写操作,则设备地址为10100000B=A0H.
2.1 读取一个字节数据代码如下(示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
uint8_t AT24CXX_ReadOneByte(uint16_t _usAddress) { uint8_t dat = 0; EE_IIC_Start(); if(EE_SIZE > ((128*16))) { EE_IIC_Send_Byte(EE_DEV_ADDR); EE_IIC_Wait_Ack(); EE_IIC_Send_Byte(_usAddress>>8); EE_IIC_Wait_Ack(); } else { EE_IIC_Send_Byte(EE_DEV_ADDR + ((_usAddress/256)<<1)); } EE_IIC_Send_Byte(_usAddress%256); EE_IIC_Wait_Ack(); EE_IIC_Start(); EE_IIC_Send_Byte(EE_DEV_ADDR | I2C_RD); EE_IIC_Wait_Ack(); dat = EE_IIC_Read_Byte(0); EE_IIC_Stop(); return dat; }
|
2.2 写入一个字节数据代码如下(示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
void AT24CXX_WriteOneByte(uint16_t _usAddress,uint8_t _usWrite) { EE_IIC_Start(); if(EE_SIZE > ((128*16))) { EE_IIC_Send_Byte(EE_DEV_ADDR); EE_IIC_Wait_Ack(); EE_IIC_Send_Byte(_usAddress>>8); } else { EE_IIC_Send_Byte(EE_DEV_ADDR + ((_usAddress/256)<<1)); } EE_IIC_Wait_Ack(); EE_IIC_Send_Byte(_usAddress%256); EE_IIC_Wait_Ack(); EE_IIC_Send_Byte(_usWrite); EE_IIC_Wait_Ack(); EE_IIC_Stop(); delay_syms(10); }
|
2.3 读取若干数据代码如下(示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
void AT24CXX_ReadBytes(uint16_t _usAddress, uint8_t *_pReadBuf, uint16_t _usSize) { while(_usSize) { *_pReadBuf++ = AT24CXX_ReadOneByte(_usAddress++); _usSize--; } }
|
2.4 写入若干数据代码如下(示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
void AT24CXX_WriteBytes(uint16_t _usAddress, uint8_t *_pWriteBuf, uint16_t _usSize) { while(_usSize--) { AT24CXX_WriteOneByte(_usAddress,*_pWriteBuf); _usAddress++; _pWriteBuf++; } }
|
2.5 测试代码如下(示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
void DEBUG_test_AT24C32(void) { uint8_t i,j; if(EE_CheckOk() == 1) { printf("Check OK \r\n"); for(i = 0; i < 16; i++) { test_buf1[i] = i; } AT24CXX_WriteBytes(0x20,test_buf1,16); AT24CXX_ReadBytes(0x20,test_buf2,16); for(j=0;j<16;j++) { printf("test_buf2 = %d\r\n",test_buf2[j]); } } else { printf("Check Fail \r\n"); } }
|
3.测试结果
和测试代码写的结果是一样,测试代码首先把0-15写入到test_buf1数组里面,再通过写函数把它写入到EEPROM芯片,最后通过读函数将它读取出来给到test_buf2数组,再打印出来。
其他示例代码一
IIC.H
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #ifndef __IIC_H #define __IIC_H #include "sys.h"
#define SDA_IN() {GPIOA->CRL&=0X0FFFFFFF;GPIOA->CRL|=8<<28;} #define SDA_OUT() {GPIOA->CRL&=0X0FFFFFFF;GPIOA->CRL|=3<<28;}
#define IIC_SCL PAout(6) #define IIC_SDA PAout(7) #define READ_SDA PAin(7)
void IIC_Init(void); void IIC_Start(void); void IIC_Stop(void); void IIC_Send_Byte(u8 txd); u8 IIC_Read_Byte(unsigned char ack); u8 IIC_Wait_Ack(void); void IIC_Ack(void); void IIC_NAck(void); void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data); u8 IIC_Read_One_Byte(u8 daddr,u8 addr); #endif
|
IIC.C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
| #include "iic.h" #include "delay.h"
void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); IIC_SCL=1; IIC_SDA=1; }
void IIC_Start(void) { SDA_OUT(); IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0; delay_us(4); IIC_SCL=0; }
void IIC_Stop(void) { SDA_OUT(); IIC_SCL=0; IIC_SDA=0; delay_us(4); IIC_SCL=1; delay_us(4); IIC_SDA=1; }
u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0; return 0; }
void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; }
void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; }
void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0; for(t=0;t<8;t++) { IIC_SDA=(txd&0x80)>>7; txd<<=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } }
u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN(); for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA) receive++; delay_us(1); } if (!ack) IIC_NAck(); else IIC_Ack(); return receive; }
|
24CXX.H
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #ifndef __24CXX_H #define __24CXX_H #include "iic.h"
#define AT24C01 127 #define AT24C02 255 #define AT24C04 511 #define AT24C08 1023 #define AT24C16 2047 #define AT24C32 4095 #define AT24C64 8191 #define AT24C128 16383 #define AT24C256 32767 #define AT24C512 65535
#define EE_TYPE AT24C512 u8 AT24CXX_ReadOneByte(u16 ReadAddr); void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite); void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len); u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len); void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite); void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead); u8 AT24CXX_Check(void); void AT24CXX_Init(void); #endif
|
24CXX.C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
| #include "24cxx.h" #include "delay.h"
void AT24CXX_Init(void) { IIC_Init(); }
u8 AT24CXX_ReadOneByte(u16 ReadAddr) { u8 temp=0; IIC_Start(); if(EE_TYPE>AT24C16) { IIC_Send_Byte(0XA0); IIC_Wait_Ack(); IIC_Send_Byte(ReadAddr>>8); IIC_Wait_Ack(); } else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); IIC_Send_Byte(ReadAddr%256); IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(0XA1); IIC_Wait_Ack(); temp=IIC_Read_Byte(0); IIC_Stop(); return temp; }
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite) { IIC_Start(); if(EE_TYPE>AT24C16) { IIC_Send_Byte(0XA0); IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr>>8); } else { IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); } IIC_Wait_Ack(); IIC_Send_Byte(WriteAddr%256); IIC_Wait_Ack(); IIC_Send_Byte(DataToWrite); IIC_Wait_Ack(); IIC_Stop(); delay_ms(10); }
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len) { u8 t; for(t=0;t<Len;t++) { AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff); } }
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len) { u8 t; u32 temp=0; for(t=0;t<Len;t++) { temp<<=8; temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); } return temp; }
u8 AT24CXX_Check(void) { u8 temp; temp=AT24CXX_ReadOneByte(65535); if(temp==0X55) return 0; else { AT24CXX_WriteOneByte(65535,0X55); temp=AT24CXX_ReadOneByte(65535); if(temp==0X55) return 0; } return 1; }
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead) { while(NumToRead) { *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++); NumToRead--; } }
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite) { while(NumToWrite--) { AT24CXX_WriteOneByte(WriteAddr,*pBuffer); WriteAddr++; pBuffer++; } }
|
主程序main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include "led.h" #include "delay.h" #include "sys.h" #include "usart.h"
#include "key.h" #include "24cxx.h" #include "iic.h"
const u8 TEXT_Buffer[]={"MiniSTM32 IIC TEST"}; #define SIZE sizeof(TEXT_Buffer) int main(void) { u8 key; u16 i=0; u8 datatemp[SIZE]; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); delay_init(); uart_init(9600); LED_Init(); KEY_Init(); AT24CXX_Init(); printf("开机成功\r\n"); while(AT24CXX_Check()) { printf("检测不到\r\n"); delay_ms(500); delay_ms(500); printf("请检查\r\n"); LED0=!LED0; } printf("检测存在\r\n"); printf("read \t %d \t.\r\n", AT24CXX_ReadOneByte(65535) ); while(1) { key=KEY_Scan(0); if(key==WKUP_PRES) { printf("Start Write 24C512....\r\n"); AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE); printf("24C512 Write Finished!\r\n"); } if(key==KEY0_PRES) { printf("Start Read 24C512.... \r\n"); AT24CXX_Read(0,datatemp,SIZE); printf("The Data Readed Is: %s\r\n",datatemp); } i++; delay_ms(10); if(i==20) { LED0=!LED0; i=0; } } }
|
AT24Cxx应用介绍
结论:
1、读写AT24CXX芯片,根据容量有多种方式:
一、容量为AT24C01AT24C16,首先发送设备地址(8位地址),再发送数据地址(8位地址),再发送或者接受数据。
二、AT24C32/AT24C64AT24C512,首先发送设备地址(8位地址),再发送高位数据地址,再发送地位数据地址,再发送或者接受数据。
三、容量AT24C1024的芯片,是把容量一和容量二的方法结合,设备地址中要用一位作为数据地址位,存储地址长度是17位。
2、它的设备地址根据容量不同有区别:
1)、AT24C01AT24C16:这一类又分为两类,分别为AT24C01/AT24C02和AT24C04AT24C16;他们的设备地址为高7位,低1位用来作为读写标示位,1为读,0为写。
1、AT24C01/AT24C02。AT24C01/AT24C02的A0、A1、A2引脚作为7位设备地址的低三位,高4为固定为1010B,低三位A0、A1、A2确定了AT24CXX的设备地址,所以一根I2C线上最大可以接8个AT24CXX,地址为1010000B1010111B。
2、AT24C04AT24C16的 A0、A1、A2只使用一部分,不用的悬空或者接地(数据手册中写的是悬空不接)。举例:AT24C04只用A2、A1引脚作为设备地址,另外一位A0不用悬空,发送地址中对应的这位(A0)用来写入页寻址的页面号,一根I2C线上最大可以接4个,地址为101000xB~101011xB
2)、AT24C32/AT24C64:和AT24C01/AT24C02一样,区别是,发送数据地址变成16位。
注意事项:对AT24C32来说,WP置高,则只有四分之一受保护,即0x0C00-0x0FFF。也就是说保护区为1KBytes。对于低地址的四分之三,则不保护。所以,如果数据较多时,可以有选择地存储。不重要的数据则放在低四分之三区域,重要的数据则放在高四分之一区域。
详细如下:
图AT24C01/02/04/08/16的外形级封装和引脚说明
AT24C系列为美国ATMEL公司推出的串行COMS型E2PROM,是典型的串行通信E2PROM 。
AT24CXX是IIC总线串行器件,具有工作电源宽(1.8~6.0 V),抗干扰能力强(输入引脚内置施密特触发器滤波抑制噪声),功耗低(写状态时最大工作电流3 mA),高可靠性(写次数100万次,数据保存100年),支持在线编程等特点.
从上面两张图片可以得知:
AT :ATMEL公司出品
24: 系列号
C :商业
XX : 存储容量 ,举例 01 –> 1K à 128 字节
02 à 2K à 256 字节
…………….
16à 16K à 2K 字节
I2C总线协议规定,任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器。
主器件控制串行时钟和起始、停止信号的发生。主器件任何期间都可以发送或接收数据,但是主器件控制数据传送模式(发送或者接收)。
WP写保护引脚:当该引脚连接到VCC,I2C器件内的内容被写保护(只能读)。如果允许对器件进行正常的读写,那么WP引脚需连接到地或者悬空。
通过器件地址输入端A0、A1、A2可以实现讲最多8个at24c01器件和at24c02器件、4个at24c04器件、2个at24c08器件、1个at24c16器件连接到总线上。当总线上只有一个器件时,A0、A1、A2可以连接到地或者悬空。
下面重点分析一下这句话:为什么 at24c01/at24c02 可以挂接8个器件呢?为什么到了 at24C16 却只能挂接1个器件呢???
WHY?
器件识别控制字节的作用
以at24c01/at24c02 和at24C16 举例:
I2C总线上所有外围器件都有唯一的地址,这个地址由器件地址和引脚地址两部分组成。共7位。
器件地址是I2C器件固有的地址编码,器件出厂时已经给定,不可更改。
引脚地址由I2C总线外围器件的地址引脚A0、A1、A2决定,根据其在电路中接电源正极、接地或者悬空的不同,形成不同的地址代码。引脚地址数也决定了同一器件可接入总线的最大数目。
此时于引脚地址无关,与 P2、P1、P0有关,即页地址有关,页地址高三位是器件识别控制字节的1-3位,器件上的A0,A1,A2,就无效了,所以只能接1个AT24C16器件。
页地址是什么乖乖,从何而来呢????
上图可知 AT24C16 存储容量 16K = 2K字节 = 128(页面数)* 16 (每页的字节数) = 2^11 (寻址地址位数 11位)。
AT24C16内部有2048*8位的存储容量,即可以存储2K字节的数据。这2K字节被放在128个页内,每页存放16个字节。所以对AT24C16内部的访问需要11位地址(0-7ff)。
举个实际的例子:
对AT24C16访问时,按照页地址和页偏移量的方式进行访问。
比如要访问第100页的第3个字节,则在发送寻址的时候,就要发送0X0643,其中页地址的高三位放在器件地址中。
第100页的第3个字节 == 0X0643
0643 = 6 * 256 + 4 * 16 + 3 = (616+4)16 + 3 = 1603
就是 100页的第3个字节。
所以在编写程序对AT24C16第100页的第3个字节进行写数据的时候,步骤如下:
1)发送起始信号;
2)发送器件地址0XAC(1010 1100,1010是固定地址,110是页地址的高三位,0表示写操作);
3)发送操作地址0X43(0100 0010,0100是页地址的低四位,0010是页地址偏移量,即第100页内的第三个字节;
4)发送要写的数据;
5)发送终止信号。
STM32 AT24CXX器件地址的理解(IIC通讯协议)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if (EE_TYPE > AT24C16) { iic_send_byte(0XA0); iic_wait_ack(); iic_send_byte(addr >> 8); } else { iic_send_byte(0XA0 + ((addr >> 8) << 1)); } iic_wait_ack(); iic_send_byte(addr % 256); iic_wait_ack();
|
EETYPE为宏定义,具体如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| #define AT24C01 127 #define AT24C02 255 #define AT24C04 511 #define AT24C08 1023 #define AT24C16 2047 #define AT24C32 4095 #define AT24C64 8191 #define AT24C128 16383 #define AT24C256 32767
#define EE_TYPE AT24C02
|
根据尼使用得AT24C型号得不同,给EE_TYPE初始化不同的值。先讨论C16以下的
* 对于24C01/02, 其器件地址格式(8bit)为: 1 0 1 0 A2 A1 A0 R/W
* 对于24C04, 其器件地址格式(8bit)为: 1 0 1 0 A2 A1 a8 R/W
* 对于24C08, 其器件地址格式(8bit)为: 1 0 1 0 A2 a9 a8 R/W
* 对于24C16, 其器件地址格式(8bit)为: 1 0 1 0 a10 a9 a8 R/W
R/W: 读/写控制位 0,表示写; 1,表示读;
A0/A1/A2: 对应器件的1,2,3引脚(只有24C01/02/04/8有这些脚,16以上的芯片没设计这些脚)
a8/a9/a10: 对应存储地址的高位。
比如24C04 有512byte(32page16byte),地址本质都是0 1这种二进制(经常我们看到的地址表示形式是0x00000008这种,是32位机下,四个二进制转换成一个十六进制形成的),一位二进制(2的一次方)能代表两个地址块,2位二进制(2的平方)能代表四个地址块,512个地址块就需要2的九次方,所以每个地址需要9位来表示来能把这521byte覆盖完。在发送时,只能一个字节一个字节的发送,你要一口气发九个字节,从机会领悟不到的,所以可以先把高位的1位弄成00000001发送,再发最后的八位,加上1 0 1 0 A2 A1 A0 R/W,一共要发三次才能确定要读或者写的地址,麻烦!!
所以我们把多出来的1位(第九位,这个最高位)放到1 0 1 0 A2 A1 A0 R/W的A0位去传输,这样只用传输两次即可。如果多了两位就把多的放在A1,和A0位去传输。
以上方法最多11bit地址,最多可以表示2048个位置,可以寻址24C16及以内的型号
针对C32以后的,不够用了,所以干脆就大大方方的先发1 0 1 0 A2 A1 A0 R/W,再分两次发送地址
iic_send_byte(0XA0 + ((addr >> 8) << 1));
这句代码的理解:addr >> 8左移八位,把八位之后的东西留下来再左移一位后与0XA0相加,左移这一位相当于是给R/W腾了个位置,通过这句代码,把地址的高x位取出来了,然后随着1 0 1 0 A2 A1 A0 R/W发送出去。
相关链接(侵删)
- AT24C32、AT24C64、AT24C128、AT24C256、AT24C512系列EEPROM芯片单片机读写驱动程序
- 串行 EEPROM 存储器芯片AT24C32(兼容同系列AT24CXX)
- AT24CXX系列芯片在STM32F103单片机下的读写程序
- EEPROM—AT24Cxx应用介绍
- STM32 AT24CXX器件地址的理解(IIC通讯协议)
=================我是分割线=================
欢迎到公众号来唠嗑: