有时候用户参数在掉电后需要保存,对于内部不带有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):
- Byte Write;Word Write;BlcokWrite;
- Segment Erase;Byet Read;
- 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
- 通过InfoC可以看出,flash information memory的存储地址是unsigned int型的。所以FlashReadBlock(unsigned int StartAddress,unsigned int EndAddress)的参数均定义成了unsigned int型。
- 虽然我们这段代码是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内执行块写入函数。