文章涉及到的源码链接:点击这里
目录
1 AT21CS01/ AT21CS11
AT21CS01/11 是一种 2 引脚存储器(SI/O 信号和地),其从信号引脚获取电能,从而为集成电路供电。它提供 1024 位串行电可擦除的可编程只读存储器(Electrically-Erasable Programmable Read-Only Memory,EEPROM),该存储器划分为 128 个 8 位字。
该器件经过了优化,可使用两点机械连接(仅将一个信号(SI/O)和 GND 连接到无电配件)在无电配件中添加配置和使用信息。部分无电配件应用示例包括模拟传感器校准数据存储、打印机墨水/墨粉盒识别以及售后市场消耗品的管理。该器件的软件寻址方案允许最多 8 个器件共享一条通用单线总线。该器件提供多种节省空间的封装选项,SI/O 线的外部上拉电压范围为 1.7V 至 3.6V(AT21CS01)/2.7V 至 4.5V(AT21CS11)。
AT21CS01/ AT21CS11使用单线串行接口,采用 I2C 协议结构,通过单个 I/O 引脚来实现器件通信。
2 复位ack时序
在器件上电后或主器件复位器件(将 SI/O 线保持低电平并持续 tRESET或 tDSCHG)后,主器件必须释放SI/O 线,该线随后将通过外部上拉电阻拉为高电平。之后,主器件必须再等待一段最小 tRRT 时间,然后才能请求从器件获取发现响应应答。
在发现响应应答序列中,首先是主器件将 SI/O 线驱动为低电平,这将启动 AT21CS01/11 内部时序电路。主器件必须继续将此线驱动为低电平并持续 tDRR。
在 tDRR 时间内,AT21CS01/11 将以并发的方式将 SI/O 驱动为低电平来进行响应。器件会继续将 SI/O 线驱动为低电平,总时长为 tDACK。主器件应从 tDRR 开始的 tMSDR 后对 SI/O 线的状态进行采样。按照定义,最小 tDACK 时间应比最大 tMSDR 时间长,从而确保主器件始终能够正确地从 SI/O 线上采样到低于 VIL 的电压。在 tDACK 时间过后,AT21CS01/11 将释放 SI/O 线,该线随后通过外部上拉电阻拉为高电平。
代码:
Ack_t AT21CS01_Reset(void)
{
u8 res = 0;
AT21CS01_PowerOn();
delay_us(20);
/*for(int i = 0; i < 70 ; i++)*/delay_ms(100);
AT21CS01_IO_L();
delay_us(400); //RESET
AT21CS01_IO_H();
delay_us(10); //T_RRT
AT21CS01_IO_L();
delay_us(1); //T_MSDR 2~6us
AT21CS01_IO_Input();
delay_us(1);
res = GPIO_ReadInputDataBit(AT21CS01_IO_PORT, AT21CS01_IO_PIN); //read ACK
AT21CS01_IO_Output();
AT21CS01_IO_H();
return res ? NACK : ACK; //read 1 -> NACK
}
3 发送时序
主机逻辑0发送波形
主机逻辑1发送波形
代码:
Ack_t AT21CS01_SendByte(u8 data)
{
u8 i = 0, res = 0;
AT21CS01_IO_Output();
for(; i < 8; i++)
{
if(data & 0x80) //高位先发,高位为1
{
if(SPEED_MODE == HIGH_SPEED) //高速模式
{
AT21CS01_IO_L();
delay_us(TLOW_1_HighSpeed); //发送逻辑1中的低电平
AT21CS01_IO_H();
delay_us(THIGH_1_HighSpeed); //发送逻辑1中的高电平
}
else //标速模式
{
AT21CS01_IO_L();
delay_us(TLOW_1_StandardSpeed); //发送逻辑1中的低电平
AT21CS01_IO_H();
delay_us(THIGH_1_StandardSpeed);//发送逻辑1中的高电平
}
}
else //高位先发,高位为0
{
if(SPEED_MODE == HIGH_SPEED) //高速模式
{
AT21CS01_IO_L();
delay_us(TLOW_0_HighSpeed); //发送逻辑0中的低电平
AT21CS01_IO_H();
delay_us(THIGH_0_HighSpeed); //发送逻辑0中的高电平
}
else //标速模式
{
AT21CS01_IO_L();
delay_us(TLOW_0_StandardSpeed); //发送逻辑0中的低电平
AT21CS01_IO_H();
delay_us(THIGH_0_StandardSpeed); //发送逻辑0中的高电平
}
}
data <<= 1;
}
AT21CS01_IO_L(); //拉低准备接收ACK
if(SPEED_MODE == HIGH_SPEED)
{
delay_us(TWACK_HighSpeed);
}
else
{
delay_us(TWACK_StandardSpeed);
}
AT21CS01_IO_Input();
__nop();
res = GPIO_ReadInputDataBit(AT21CS01_IO_PORT, AT21CS01_IO_PIN); //read ACK
AT21CS01_IO_Output();
AT21CS01_IO_H(); //释放ACK结束
if(SPEED_MODE == HIGH_SPEED)
{
delay_us(TWACKHIGH_HighSpeed);
}
else
{
delay_us(TWACKHIGH_StandardSpeed);
}
return res ? NACK : ACK;
}
4 接收时序
从机逻辑0反馈波形
从机逻辑1反馈波形
代码:
u8 AT21CS01_ReadByte(Ack_t ack)
{
u8 i = 0, res = 0;
AT21CS01_IO_Output();
for(; i < 8; i++)
{
res <<= 1;
AT21CS01_IO_L();
if(SPEED_MODE == HIGH_SPEED)
{
delay_us(TRD_HighSpeed);
}
else
{
delay_us(TRD_StandardSpeed);
}
AT21CS01_IO_Input();
delay_us(1); //上拉时间
res |= GPIO_ReadInputDataBit(AT21CS01_IO_PORT, AT21CS01_IO_PIN);
AT21CS01_IO_Output();
AT21CS01_IO_H();
if(SPEED_MODE == HIGH_SPEED)
{
delay_us(TRDHIGH_HighSpeed);
}
else
{
delay_us(TRDHIGH_StandardSpeed);
}
}
if(SPEED_MODE == HIGH_SPEED)
{
if(ack == ACK)
{
MACK_HighSpeed();
}
else//if(ack == NACK)
{
MNACK_HighSpeed();
}
}
else
{
if(ack == ACK)
{
MACK_StandardSpeed();
}
else//if(ack == NACK)
{
MNACK_StandardSpeed();
}
}
return res;
}
5 连续发送/接收示例
//连续发送
void AT21CS01_SendData(u8* data, u16 len)
{
while(len--)
{
AT21CS01_SendByte(*data);
data++;
}
}
//连续接收
void AT21CS01_ReadData(u8* buffer, u16 len)
{
int i = 0;
for( ; i < len - 1; i++)
{
buffer[i] = AT21CS01_ReadByte(ACK);
}
buffer[i] = AT21CS01_ReadByte(NACK);
}
6 EEPROM访问
EEPROM区有多种读写方式,下面以常用的随机地址连续读取和页写操作为例。
6.1 EEPROM读取
随机读操作以与字节写操作相同的方式开始,也就是将新的 EEPROM 存储器地址装入地址指针。但是并不发送字节写操作的数据字节和停止条件,而是将重复的启动条件发送到器件。这一序列称为“虚拟写入”。在发送了“虚拟写入”的器件地址和存储器地址字节之后,AT21CS01/11 将返回 ACK 响应。主器件随后即可启动当前地址读操作(以新的启动条件开始),以从 EEPROM 读取数据。
代码:
void EEPROMRead(u8* buffer, u8 addr, u8 len)
{
DELAY_HTSS();
AT21CS01_SendByte(EEPROM_ACCESS | WRITE);//dummy write
AT21CS01_SendByte(addr);
delay_us(800);
AT21CS01_SendByte(EEPROM_ACCESS | READ);
AT21CS01_ReadData(buffer, len);
DELAY_HTSS();
}
6.2 EEPROM页写
页写操作允许在一个写周期内最多写入 8 个字节,前提是所有字节都在存储器阵列的同一行(地址位 A6到 A3 相同)。此外,还支持不足 8 字节的部分页写操作。
接收到每个数据字节后,EEPROM 都将以 ACK 进行响应。在发送了所有数据字节后,器件需要一个停止条件来开始写周期。但是,由于停止条件定义为空位帧(SI/O 拉为高电平),因此主器件无需驱动 SI/O线即可实现这一点。如果在任何其他时间发送停止条件,则写操作将中止。在停止条件完成后,内部自计时写周期将开始。SI/O 引脚在整个 tWR 周期内必须通过外部上拉电阻上拉到高电平。因此,在有多个从器件的环境下,如果有任何器件处于内部写周期,则不应尝试与总线上的其他单线器件通信。
接收到每个数据字节后,存储器地址的低三位在内部递增。高地址位不会递增,器件将保持存储器页位置。无论实际写入的字节数如何,页写操作都仅限于在单个物理页内写入字节。当递增的字地址达到页边界时,地址计数器将“计满返回”到当前页的开头。但是应避免创建“计满返回”,因为页中先前装入的数据可能会被意外更改。在经过了最大 tWR 时间之后,主器件可能会开始新的总线事务。
代码:
Ack_t EEPROMWritePage(u8* data, u8 addr, u16 len)
{
int i = 0;
Ack_t ack = ACK;
DELAY_HTSS();
ack &= AT21CS01_SendByte(EEPROM_ACCESS | WRITE);
ack &= AT21CS01_SendByte(addr);
for(i = 0; i < len; i++)
{
ack &= AT21CS01_SendByte(data[i]);
}
DELAY_HTSS();
delay_ms(5); //t_WR
return ack;
}
6.3 ROM区域寄存器写
ROM 区域寄存器只能写入逻辑 1,该操作会将相应的存储区域设置为 ROM 状态。一旦写入 ROM 区域寄存器,便无法再次更改。
代码:
u8 RomZoneRegWrite(u8 addr)
{
Ack_t ack = ACK;
if(addr != 1 && addr != 2 && addr != 4 && addr != 8)
{
return 0xEE; //ERROR
}
DELAY_HTSS();
ack &= AT21CS01_SendByte(ROM_ZONE_REG_ACCESS | WRITE);
ack &= AT21CS01_SendByte(addr);
ack &= AT21CS01_SendByte(0xFF);
DELAY_HTSS();
delay_ms(5); //t_WR
return (u8)ack;
}
6.4 ROM区域寄存器读
要检查 ROM 区域寄存器的当前状态,主器件必须对随机读操作序列进行仿真,但使用的是操作码 0111b(7h)。为了指定将要读取的 ROM 区域寄存器地址,需要使用随机读操作序列的虚拟写入部分。
代码:
u8 RomZoneRegWrite(u8 addr)
{
Ack_t ack = ACK;
if(addr != 1 && addr != 2 && addr != 4 && addr != 8)
{
return 0xEE; //ERROR
}
DELAY_HTSS();
ack &= AT21CS01_SendByte(ROM_ZONE_REG_ACCESS | WRITE);
ack &= AT21CS01_SendByte(addr);
ack &= AT21CS01_SendByte(0xFF);
DELAY_HTSS();
delay_ms(5); //t_WR
return (u8)ack;
}
6.5 冻结ROM区域寄存器
通过冻结当前 ROM 区域状态,可防止对 ROM 区域寄存器进行进一步修改。一旦冻结,便无法撤销此事件。
代码:
u8 RomZoneRegFreeze(void)
{
Ack_t ack = ACK;
DELAY_HTSS();
ack &= AT21CS01_SendByte(FREEZE_ROM_ZONE_STATE | WRITE);
if(ack == NACK) //可能是之前已经冻结
{
return ack;
}
ack &= AT21CS01_SendByte(0x55);
ack = AT21CS01_SendByte(0xAA); //NACK:已经锁定 ACK:锁定成功
DELAY_HTSS();
delay_ms(5); //t_WR
return ack;
}
7 安全寄存器访问
代码:
//安全寄存器页写
Ack_t SecRegWritePage(u8* data, u8 addr, u16 len)
{
int i = 0;
Ack_t ack = ACK;
DELAY_HTSS();
ack &= AT21CS01_SendByte(SECURITY_REG_ACCESS | WRITE);
ack &= AT21CS01_SendByte(addr);
for(i = 0; i < len; i++)
{
ack &= AT21CS01_SendByte(data[i]);
}
DELAY_HTSS();
delay_ms(5); //t_WR
return ack;
}
//读取安全寄存器中的序列号
void SecRegReadSN(u8* buffer)
{
DELAY_HTSS();
AT21CS01_SendByte(SECURITY_REG_ACCESS | WRITE);//dummy write
AT21CS01_SendByte(0x00);
delay_us(800);
AT21CS01_SendByte(SECURITY_REG_ACCESS | READ);
AT21CS01_ReadData(buffer, 8);
DELAY_HTSS();
}
锁定命令是一个不可逆序列,在此之后将永久阻止对 AT21CS01/11 上的安全寄存器高 16 字节的所有写操作。一旦执行了锁定命令,整个 32 字节的安全寄存器都变为只读。一旦锁定了安全寄存器,便无法将其解锁。
//锁定安全寄存器
Ack_t SecRegLock(void)
{
Ack_t ack = ACK;
DELAY_HTSS();
ack &= AT21CS01_SendByte(SECURITY_REG_LOCK | WRITE);
ack &= AT21CS01_SendByte(0x60); //NACK:已经锁定 ACK:锁定成功
if(ack == NACK)
{
return ack;
}
ack &= AT21CS01_SendByte(0x00);
DELAY_HTSS();
delay_ms(5); //t_WR
return ack;
}
8 制造商ID读取
AT21CS01/11 提供了查询器件制造商、容量和版本信息的功能。通过使用特定的操作码并遵循当前地址读操作的格式,器件将返回一个 24 位值,该值对应为 Microchip 保留的 I2C 标识符值以及用于表示 1 Kb 容量和器件版本的其他数据。
代码:
void MfrIDRead(u8* buffer)
{
DELAY_HTSS();
AT21CS01_SendByte(MFRID_READ | READ);//注意这个没有假写
AT21CS01_ReadData(buffer, 3);
DELAY_HTSS();
}
9 高速/标准速度模式
9.1 模式切换
可以使用 Eh 操作码将器件设置为高速模式或检查其是否处于高速模式。该事务只需要 8 位。AT21CS01/11 在上电后默认处于高速模式。
可以使用 Dh 操作码将 AT21CS01 设置为标准模式或检查其是否处于标准模式。该事务只需要 8 位。
代码:
void SpeedModeSet(u8 mode)
{
DELAY_HTSS();
if(mode == HIGH_SPEED_MODE || mode == HIGH_SPEED)
{
AT21CS01_SendByte(HIGH_SPEED_MODE | WRITE);
SPEED_MODE = HIGH_SPEED;
}
else if(mode == STANDARD_SPEED_MODE || mode == STANDARD_SPEED)
{
AT21CS01_SendByte(STANDARD_SPEED_MODE | WRITE);
SPEED_MODE = STANDARD_SPEED;
}
DELAY_HTSS();
}
9.2 模式检查
要确定器件是否已设置为标准模式,必须将器件地址字节(操作码为 1101b(Dh))连同相应的从器件地址组合一起发送到器件,并且将读/写 位设置为逻辑 1。如果器件已设置为标准速度模式,则将返回 ACK(逻辑 0)。如果器件当前未设置为标准速度模式,则将返回 NACK(逻辑 1)。
要确定器件是否已设置为高速模式,必须将器件地址字节(指定操作码 1110b(Eh))连同相应的从器件地址组合一起发送到器件,并且将读/写 位设置为逻辑 1。如果器件已设置为高速模式,则将返回 ACK(逻辑 0)。如果器件当前未设置为高速模式,则将返回 NACK(逻辑 1)。
代码:
Ack_t SpeedModeCheck(u8 mode)
{
Ack_t ack = ACK;
DELAY_HTSS();
if(mode == HIGH_SPEED_MODE || mode == HIGH_SPEED)
{
ack = AT21CS01_SendByte(HIGH_SPEED_MODE | READ);
}
else if(mode == STANDARD_SPEED_MODE || mode == STANDARD_SPEED)
{
ack = AT21CS01_SendByte(STANDARD_SPEED_MODE | READ);
}
DELAY_HTSS();
return ack;
}