国民技术N32G031系列Flash数据存储

最近在做一个电源项目软件,客户要求能够在掉电后保存一些数据,重新上电后能加载这些数据。数据内容只有一个字节,但每次写入时是4个字节。

具体的内容如下:

电源带有4路输出,上位机通过RS485能够控制这4路的通断,1:导通,0:断开,当上位机执行一次导通或关断操作时都要保存输出状态到Flash,如果相同的操作执行多次则只保存一次,例如:多次发送导通操作被视为是导通一次操作。

具体思路如下:

1、由于N32G031系列没有EEPROM,也没有其他外部存储器,只能使用ROM的一部分。N32G031总共有64K字节ROM,运行的程序只有十多K,空间还有很多;

2、使用片内Flash的一页来动态存储数据。Flash每页有512个字节,每4个字节写一次,则一页可以写入128次,整页写完,继续再写时才擦除页,重新从页的起始位置写入数据,如此反复,这样可以大大减小Flash页的擦除次数;

3、初始时从Flash页的指定位置读取数据(位置根据计算得到,就是找到第一个连续4个0xFF的位置),并存储在两个变量中A和B中,此时A和B中保存的数据是相同的,运行时如果上位机执行了导通和断开操作会改变B变量相关成员值,当while大循环检测到A和B的值不同时,则用B的值设置A变量,并根据计算的地址写入Flash页中。

程序主要代码如下:

结构体和联合体定义

struct STRUCT_OUTPUT_STATE{
	unsigned P1		:1;
	unsigned P2		:1;
	unsigned P3		:1;
	unsigned P4		:1;
	unsigned AUX	:1;
	unsigned 		:1;
	unsigned 		:1;
	unsigned 		:1;
};

//used to save states of all the four channels
union OUTPUT_UNION{
	struct STRUCT_OUTPUT_STATE port_state;
	uint8_t allbits;
	
	//uint8_t P1;
	//uint8_t P2;
	//uint8_t P3;
	//uint8_t P4;
	//uint32_t allbits;
};

typedef union OUTPUT_UNION Output_State;

结构体STRUCT_OUTPUT_STATE的成员P1-P4分别保存1-4路输出状态,AUX成员默认设置为0,是个辅助标志位,用于区别当所有输出都打开时(P1-P4都为1)和从Flash读取的连续4字节都是0xFF的情况。

主程序初始时读取逻辑:

page_offset = Find_Empty_Data();
	if(page_offset <= 128)
	{
        if(page_offset > 0)
        {
            _OutputState_Old.allbits = (*(__IO uint32_t*)(FLASH_WRITE_START_ADDR + (page_offset - 1) * 4));//get the status
            _OutputState_New.allbits = _OutputState_Old.allbits;
            _OutputState_Old.port_state.AUX = 0;
            _OutputState_New.port_state.AUX = 0;
        }
        else
        {
            _OutputState_Old.allbits = (*(__IO uint32_t*)(FLASH_WRITE_START_ADDR));//get the status
            _OutputState_New.allbits = _OutputState_Old.allbits;
            _OutputState_Old.port_state.AUX = 0;
            _OutputState_New.port_state.AUX = 0;
        }
	}
	else //can't find the address that contains the valid data
	{
		_OutputState_Old.allbits = (*(__IO uint32_t*)(FLASH_WRITE_START_ADDR));//get the status
		_OutputState_New.allbits = _OutputState_Old.allbits;
        _OutputState_Old.port_state.AUX = 0;
        _OutputState_New.port_state.AUX = 0;
	}

两个全局变量及Flash页起始地址定义如下:

//Flash information
#define 	FLASH_PAGE_SIZE        ((uint16_t)0x200)
#define 	FLASH_WRITE_START_ADDR ((uint32_t)0x08008000)
#define 	FLASH_WRITE_END_ADDR   ((uint32_t)0x08010000)

volatile	Output_State _OutputState_Old;
volatile	Output_State _OutputState_New;
//uint32_t 	flash_rw_delay = 0;
volatile	uint16_t off_set = 0;

Find_Empty_Data函数用于查找Flash页中连续4字节都为0xFF的位置,也就是我们下一个要写入数据的位置。函数如下:

/**
*@name: Find_Empty_Data
*@description: Find the location that has not been written.
			   The reason why we do this is because we need to avoid erasing flash.
*@params: none
*@return: none
*/
uint16_t Find_Empty_Data(void)
{
	uint16_t page_offset;
	for (page_offset = 0; page_offset < 128;/*(FLASH_PAGE_SIZE/4)*/ page_offset++)
	{
		if (*((uint32_t*)FLASH_WRITE_START_ADDR + page_offset) == 0xFFFFFFFF) break;
	}
	
	return page_offset;
}

当变量A和B的值不同时,执行以下代码,将更新后的数据写入Flash页中指定位置:

/**
*@name: Save_Output_State
*@description: If any of the output state has been changed, we write the new value to the flash.
*@params: none
*@return: none
*/
void Save_Output_State(void)
{
	if(_OutputState_Old.allbits != _OutputState_New.allbits)
	{
		uint32_t write_addr = 0;
		off_set = Find_Empty_Data();		
		
        FLASH_Unlock();
        
		//if(off_set>=127 && buf32 != 0xFFFFFFFF)//the last four bytes of the current page
		if(off_set <= 127)
		{			
			write_addr = FLASH_WRITE_START_ADDR + off_set * 4;
		}
		else	//We can't find the four bytes that are all 0xFF, which means the whole page is programmed.
		{
			//Erase
			if(FLASH_COMPL != FLASH_EraseOnePage(FLASH_WRITE_START_ADDR))
			{
				//TODO
			}
			write_addr = FLASH_WRITE_START_ADDR;
		}
		//flash_rw_delay = 480000;//about 10ms if FOSC = 48MHz
		_OutputState_Old.allbits = _OutputState_New.allbits;
		
		//Program
		if (FLASH_COMPL != FLASH_ProgramWord(write_addr, _OutputState_New.allbits))
		{
			//TODO
		}		

		/* Locks the FLASH Program Erase Controller */
		FLASH_Lock();
		//GPIO_WriteBit(GPIOF, GPIO_PIN_7, (Bit_OperateType)(1 - GPIO_ReadOutputDataBit(GPIOF, GPIO_PIN_7)));
	}
}

这样从理论上可以支持输出状态1280万次以上的更改(Flash擦除次数以10万次计算),可以大大减少Flash页擦除的次数,延长Flash使用寿命。客户那边也说过,用上位机导通和断开输出的次数不会很频繁,所以是够用的。

之前也考虑过连续高频率执行导通和关断操作的问题,例如:如果在一段时间内连续导通和关断很多次则忽略这次操作,等稳定后才执行写入操作,后经客户沟通不用担心这个问题,所以也没有写入这个机制了。

欢迎大家发表你们的看法,还有什么更好的方案大家一起分享。

你可能感兴趣的:(单片机,c语言,嵌入式硬件)