SST公司的SST89c554、SST89c564芯片的FLASH分为BLOCK0和BLOCK1两大块。其中BLOCK0为32K (564为64K),BLOCK0为8K(564为8K、58为4K)。芯片内部集成了可以对Flash进行操作的功能模块,通过对Flash的分区切放实现在应用中可编程的功能,达到保存程序临时数据目的,从而取代EEPROM。 IAP是指在用户的应用程序中对Flash块、Flash寄存器、加密位等实现擦降和编程等操作。SST89C54/58通过对SFCF.6的置位和清0而启动和介绍IAP功能。程序在block0时可以启动IAP对block1进行编程,也可以在block1对block0进行编程,但是每均都不能对自己进行编程。 SST89C54/58有256字节的内部数据存储器,其中低128字节RAM(00H~FFH)与8052完全相同;在高128字节中增加了一些针对IAP功能的特殊功能寄存器(SFR)。新增加的SFR的定义及设置如表1所列。
标识符 名 称 地 址 初始化值 SFST Flash状态 B6H xxx00000b SFCF Flash定义 B1H 000000xxb SFCM Flash命令 B2H 00h SFDT Flash数据 B5H 00h SFAL Flash地址低字节 B3H 00h SFAH Flash地址高字节 B4H 00h
SFCF IAP定义寄存器 SFCF^6 IAP 使能位 0 开启IAP功能 1 关闭IAP功能
SFCM IAP命令寄存器 0000 0001 芯片擦除命令 0000 1011 扇区擦除命令 0000 1101 块擦除命令 0000 1100 字节读取命令 0000 1110 字节写命令
SFST IAP状态寄存器 SFST^2 IAP状态标志位
0 IAP空闲状态 1 IAP执行状态
IAP功能的实现具体分为以下步骤 1. 开启IAP功能 2. 把地址传给 高字节传给SFAH,低字节传给SFAL 3. 把待执行的命令传给SFCM 4. 等到命令执行完后关闭IAP功能 按以上步骤严格执行,绝对OK!
难道IAP功能就这么容易,取掉EEPROM就这么简单?也许有人会发问。非也!非也!!以上只是提供了IAP功能的实现,而要具体应用中去就不那么简单了。值得注意的是我们的字节写命令 ,仅能把地址单元中的1改写成0,而不能把地址单元中的0改写成1(芯片出厂时默认全为FF)。按这样说芯片在出厂时是能够被改写的,但地址单元在使用过一次以后就不能再用?这个问题提得相当好。如果按常规地址单元在使用过一次以后就不能再用,但是我们可爱的554芯片提供了擦除这个命令,这样在你再次想改写地址单元的内容时只要先擦除就能改写数据了。不知各位有没有发现?在众多的IAP命令中,是没有提供 字节擦除 命令的,仅提供了扇区擦除命令。怎么办?这样岂不在改写一个数据的同时把其他数据给丢失了?这就需要我们在写数据的时候程序进行处理了。我们是这样对它进行处理的:在写数据之前,先把要改写的地址单元所在的扇区的数据128个字节全部读到一缓冲区中,然后把数据写入相对应的缓冲区,在擦除该扇区后最后把缓冲区的内容全部写入该扇区。听起来好象很复杂,但用起来还是能够达到要求的,而且还可以连续写一串数据,不知各位有没有其他更好的算法。
现在虽然能实现IAP功能,但有几个地方是值得注意的:
1. 一般FLASH的擦写寿命是有限的,554芯片是保证10万次不被擦坏。但我做过实验,我连续足足擦了200万次才把它擦坏(564擦166万就擦坏了),而且,在它擦坏一段时间后,让它休息两三天,它竟能自行恢复过来,虽然外部编程器已不能再擦除它,但芯片内部擦除指令是能够擦除的,也就是说,如果不是连续擦的话,擦写次数应该是能够超过我做实验的数据的。
2.如果在擦除或擦除后写数据的过程中临时掉电,那么整个扇区的数据都得丢失。我在程序中写数据较多的变量采用地址偏移法保存数据,因为在扇区擦除后整个扇区都能够进行字节写命令,我在写数据时只要把数据写在紧跟上次该变量的地址的空白单元,每写一次,自动跳到下一空白单元就可以了。这样就减少了擦除扇区的次数,不仅减少了掉电数据丢失的概率,而且延长了FLASH的使用寿命。另外,我还做过一个实验,精确地算出:用FLASH字节写一个数据仅需0.25ms,而写一个扇区要3ms,这样擦写全过程时间仅为以前的十分之一,进一步减少了因掉电而丢失数据的风险。但此法有一致命弱点,因为这是以牺牲空间为代价的,这样,一个扇区仅能存储一个变量的值。因此,只能用在少数的需要经常改写又非常重要的变量数据上。
3.因为擦除命令的操作对象是以扇区为单位的,所以在写一组数据时务必保证该组数据的存储地址在同一扇区,而且最好是连续的。
附录
IAP原代码
sfr16 DPTR = 0x82;
/* 新增特殊功能寄存器定义 */
sfr SFCF = 0xb1; /* SFCF DATA 0B1H; 汇编程序 */
sfr SFCM = 0xb2; /* SFCM DATA 0B2H; 汇编程序 */
sfr SFAL = 0xb3; /* SFAL DATA 0B3H; 汇编程序 */
sfr SFAH = 0xb4; /* SFAH DATA 0B4H; 汇编程序 */
sfr SFDT = 0xb5; /* SFDT DATA 0B5H; 汇编程序 */
sfr SFST = 0xb6; /* SFST DATA 0B6H; 汇编程序 */
/* 调试控制项 */
//#define DEBUG_SST89E_V564RD
#define DEBUG_SST89E_V554RC
#ifdef DEBUG_SST89E_V564RD
#define DEBUG_base_addr 0x10000
#endif
#ifdef DEBUG_SST89E_V554RC
#define DEBUG_base_addr 0xe000
#endif
/* 定义命令 */
#define SFCM_Sector_Erase 0x0b /* 扇区擦除 IAP command */
#define SFCM_Byte_Program 0x0e /* 字节编程 IAP command */
#define SFCM_Byte_Verify 0x0c /* 字节校验 IAP command */
typedef unsigned char INT8U; /* 8 bit 无符号整型 */
typedef unsigned int INT16U; /* 16 bit 无符号整型 */
INT8U xdata protect_buffer[128];
/* 关闭 IAP 功能 */
void iap_disable(void)
{
SFCF = SFCF & 0xbf; /* 1011,1111 */
SFDT = 0x00;
}
/* 扇区擦除 */
INT8U sector_erase(INT16U sector_addr)
{
SFCF = SFCF | 0x40; /* 打开 IAP 功能 */
DPTR = sector_addr;
SFAH = DPH;
SFAL = DPL;
SFCM = SFCM_Sector_Erase;
while(((ACC = SFST) & 0x04) != 0); // 0000,0100
iap_disable();
return OK;
}
/* 字节编程 */
INT8U byte_program(INT16U byte_addr, INT8U original_data)
{
SFCF = SFCF | 0x40; /* 打开 IAP 功能 */
DPTR = byte_addr;
SFAH = DPH;
SFAL = DPL;
SFDT = original_data;
SFCM = SFCM_Byte_Program;
while(((ACC = SFST) & 0x04) != 0); // 0000,0100
iap_disable();
return OK;
}
/* 字节校验 */
INT8U byte_verify(INT16U byte_addr)
{
INT8U verify_data = 0;
SFCF = SFCF | 0x40;
DPTR = byte_addr;
SFAH = DPH;
SFAL = DPL;
SFCM = SFCM_Byte_Verify;
_nop_();
verify_data = SFDT;
iap_disable();
return (verify_data);
}
/* 写数据进 Flash存储器 而不破坏同一扇区中不需修改的数据 */
INT8U write_flash_with_protect(INT16U begin_addr, INT8U counter, INT8U array[])
{
INT8U i = 0, safety_begin_addr = 0;
INT8U addr_high = 0x10, addr_low = 0x00;
INT16U sector_addr = 0;
/* 判是否是有效范围,此函数不允许跨扇区操作 */
if(counter > 128)
return ERROR;
safety_begin_addr = (INT8U)(begin_addr & 0x007f); /* 0000,0000,0111,1111 */
if( (safety_begin_addr + counter) > 128 )
return ERROR;
/* 将该扇区数据128字节数据读入缓冲区保护 */
sector_addr = (begin_addr & 0xff80); /* 1111,1111,1000,0000; 取扇区地址 */
SFCF = SFCF | 0x40; /* iap_enable */
addr_high = (INT8U)(sector_addr >> 8);
addr_low = (INT8U)(sector_addr & 0x00ff);
SFAH = addr_high;
for(i = 0; i < 128; i++)
{
SFAL = addr_low;
SFCM = SFCM_Byte_Verify;
_nop_();
protect_buffer = SFDT;
addr_low++;
}
// iap_disable();
/* 将要写入的数据写入缓冲区的相应区域,其余部分保护 */
for(i = 0; i < counter; i++)
{
protect_buffer[safety_begin_addr] = array;
safety_begin_addr++;
}
/* 檫除 要修改/写入 的扇区 */
sector_erase(sector_addr);
/* 将缓冲区的数据写入 要修改/写入 的扇区 */
SFCF = SFCF | 0x40;
addr_high = (INT8U)(sector_addr >> 8);
addr_low = (INT8U)(sector_addr & 0x00ff);
SFAH = addr_high;
for(i = 0; i < 128; i++)
{
SFAL = addr_low;
SFDT = protect_buffer;
SFCM = SFCM_Byte_Program;
addr_low++;
while(((ACC = SFST) & 0x04) != 0); // 0000,0100
}
iap_disable();
return OK;
}
|