Flash存储器编程小结

时间:2024-02-24 20:53:48

  有时候用户参数在掉电后需要保存,对于内部不带有EEPROM的MCU,我考虑到是否可以使用它的flash来代替,如果flash控制器不支持数据改写,那么最后再考虑使用外部EEPROM芯片,毕竟方法越简单越好、成本越低越好。在全系列msp430单片机上,可以通过内置的flash控制器,擦除或改写内部任何一段flash内容。此外msp430的flash内部还专门开辟了一段information memory用于存储需要掉电后永久存储的数据。本次项目使用的芯片是msp430g2553,硬件电路已基本完成,软件工作也已经完成了大部分,由于之前并没有接触过如何利用flash存储器存储数据,这段世间用了几天时间狠狠啃了一下flash的相关内容。flash的结构不同于RAM,对flash的擦除和写操作,需要配合时序发生器和电压发生器,msp430单片机flash控制器自带内部编程电压发生器,写软件时不需要考虑编程电压的问题,不过时序发生器则需要用户软件设置(range from approximately 257kHz to approximately 476kHz)。

  msp430的flash支持以下操作(我列出来的都是常用到的操作,详见MSP430x2xxUserManual):

  1. Byte Write;Word Write;BlcokWrite;
  2. Segment Erase;Byet Read;
  3. Word Read;Segment Read

  我把它们分我3类,写操作、擦除操作、读操作。对Flash的读操作比较简单,MSP430单片机采用冯诺依曼结构,读取Flash的方法与读取RAM内数据的方法完全相同。

/**-----------------------------------------------------------------------------
  * @brief   读取flash中一段存储空间的内容
  * @param   StartAddress起始地址
  * @param   EndAddress结束地址
  * @retval  ReadFromFlash[i] -- 实现手法:全局变量
  * @example 读取0x1000~0x100A地址的内容
  *          FlashReadBlock(0x10000,0x100A) 
  */
void FlashReadBlock(unsigned int StartAddress,unsigned int EndAddress)
{   
    unsigned char i=0;
    /* 定义两个指向字符型变量的指针变量 */
    unsigned char *FlashPtrStartAddress;
    unsigned char *FlashPtrEndAddress;
    
    FlashPtrStartAddress = (unsigned char *)StartAddress;
    FlashPtrEndAddress = (unsigned char *)EndAddress;
    
    while(!(FlashPtrStartAddress > FlashPtrEndAddress))
    {
      ReadFromFlash[i] = *FlashPtrStartAddress++;
      i++;
    }
    i = 0;
}

  通过数据datasheet可知msp430x2xx单片机flash的information memory分为A、B、C、D四段,每段64Byte,其中InfoA比较特殊,因为关系到LOCKA;在使用Information memory时,如果可以尽量避免InfoA,我首选InfoC和InfoD段。为了方便使用,我对每段的起始地址作以下宏定义:

#define InfoA   (0x10C0)    // 信息存储器 InfoA
#define InfoB   (0x1080)    // 信息存储器 InfoB
#define InfoC   (0x1040)    // 信息存储器 InfoC
#define InfoD   (0x1000)    // 信息存储器 InfoD
  1. 通过InfoC可以看出,flash information memory的存储地址是unsigned int型的。所以FlashReadBlock(unsigned int StartAddress,unsigned int EndAddress)的参数均定义成了unsigned int型。
  2. 虽然我们这段代码是BlockRead,但我们也得一个字符一个字符地从flash中读取我们所需的数据,故我们需要定义指向字符型的指针变量,用于指向flash中的地址,在这里我使用强制类型转(unsigned char *)是换操作,将Flash中内存的地址转换成和指针变量相同的类型。 FlashPtrStartAddress = (unsigned char *)StartAddress; 

  上面对flash的BlockRead简单的说明了一下,至于擦除和写操作相对复杂,具体参见datasheet,我这里就只对几点要注意的事情说明一下:

  • 默认情况下,Flash所有比特位都为1,对Flash只能写0,不能写1
  • 对flash某一地址只能写入一次,若想改写改单元的内容必须先擦除该单元所在的段,之后,再把要该写的数据写进该地址。例如:FlashWriteByte(InfoC,0x25);该函数在0x1040地址处写入了一个字节0x25,如果想把该地址处的0x25改写成0x30,必选先对InfoC段做擦除操作,之后才能写入,不然得到的结果并不是第二次写入的值0x30,而是两次相与后的值0x20。

 

  FlashWriteByte(InfoC,0x25);  
  FlashErase(InfoC);
  FlashWriteByte(InfoC,0x30); 

 

  • flash中每次能擦除的最小单位为“段”(Segment),主Flash每个段512Byte,InfoFlash共四段,每个段64Byte (msp430x2xx)
  • 当通过FCTL1将Flash设为段擦除模式,并清除FCTL3内的锁存标志之后,只需要向待擦除的段内的任意存储单元写入数据0,即可执行擦除操作。擦除操作同样需要判段BUSY标志位结束后才能进行下一次操作。
/**-----------------------------------------------------------------------------
  * @brief  Segment Erase.擦除flash的一个段
  * @param  将要被擦除的数据段的首地址
  * @retval None
  */
void FlashErase(unsigned int FlashAddress)  
{ 
    /* 定义一个字符型指针变量 -- 用于指向Flash待擦除的段 */
    unsigned char *FlashPtr;
    /* 对Flash中的地址做强制类型转换 -- 与FlashPtr类型一致 */
    FlashPtr = (unsigned char *)FlashAddress; 
    
    FCTL1 = FWKEY + ERASE;    // Set Erase bit
    FCTL3 = FWKEY;            // Clear Lock bit and LOCKA bit
    _DINT();                  // Flash操作器件不允许中断
    /* 发出擦除指令的方法:向被擦出段内任意地址写0-dummy write */
    *FlashPtr = 0;   
    while((FCTL3 & BUSY) == BUSY); // 等待操作完成
    _EINT();
    FCTL1 = FWKEY;         // Flash退出擦除状态
    FCTL3 = FWKEY + LOCK;  // 恢复Flash的锁定位,保护数据  
}

 

  • 每次能连续写入的最大区块称为“块”(Block),Block = 64Byte;所以一次性往Flash中写入的数据为0~64Byte
  • BlockWrite模式效率较ByteWrite高一倍,批量写模式下没字节写完后用WAIT标志判忙,每块结束后在用BUSY判忙。但批量写入过程中,不允许在对Flash读写,甚至程序的执行(读存储于Flash中的程序指令)也会破坏Flash,所以BlockWrite的程序代码必须在RAM中执行。这需要将BlcokWrite相关的代码先复制到RAM内,再从RAM内执行块写入函数。