前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >单片机内部FLASH的字节操作

单片机内部FLASH的字节操作

作者头像
知否知否应是绿肥红瘦
发布2025-02-19 21:18:58
发布2025-02-19 21:18:58
6600
代码可运行
举报
文章被收录于专栏:Linux知识
运行总次数:0
代码可运行

一般32位单片机的内部FALSH是不支持字节操作的,有的可以按字节读取,但是不能按字节写入。

而且,一般单片机内部FALSH擦除的最小单位都是页,如果向某页中的某个位置写入数据,恰好这个位置的前面存了其他数据,那么就必须把这页擦除,存的其他数据也会丢失。

实际上就是说内部的FALSH不好做改写的操作,如果有很多数据需要存放,最好是分页存储。这也是FALSH与E2PROM最大的区别,后者支持按字节操作且无需擦除,即使某一个地址写坏了,也不影响其他地址。

下面介绍一种方法让内部FLASH"支持"字节操作,且同一页的其他数据不受影响。

方法原理很简单,下面简单介绍下原理:

1.根据要写入地址,计算出该地址位于哪一页;

2.读出整个页,存入缓存BUF;

3.将要写入的数据按位置更新到BUF中;

4.擦除该页;

5.写入整个BUF。

可以看出这种方法弊端很明显:

1.耗时长  每次写都要读整个BUF,然后还要先把数据存到BUF里,然后再写入整个BUF;

2.FALSH擦写次数增加,降低使用寿命;

下面给出测试代码:

代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>//C语言标准库
#include "flash.h"

#define USER_FLASH_START_ADDR   0x01070000   //FLASH最后两个扇区  供用户使用


u32tou8 u32data;//定义一个联合体

//==================================================================================
// 获取某个地址所在的页首地址
// addr:FLASH地址
// 返回:该地址所在的页 共128页(0~127)
//==================================================================================
unsigned int FLASH_GetFlashPage(unsigned int addr)
{
	  if (IS_FLASH_ADDRESS(addr))
		{
		   return  (addr&(~0xFFF));//清0低12位就是该页的起始地址
  	} 
}
//==================================================================================
// 从FLASH中读取 一个字(32位)
// addr:读取地址
// 返回: 读到的字数据
//备注: 地址为4字节对齐
//==================================================================================
unsigned int FLSAH_ReadWord(unsigned int addr)
{
    return (*(unsigned int *)addr);
}


//==================================================================================
//从FLASH指定地址 读取数据
//备注: 读取数据类型为32位  读取地址为4字节对齐
//==================================================================================
void  FLASH_Read(unsigned int	ReadAddr,unsigned char *pBuffer,unsigned int NumToRead)
{
    unsigned int i;
	  u32tobyte cache;
    for(i=0; i<NumToRead; i+=4)
    {
            cache.u32data=FLSAH_ReadWord(ReadAddr+i);
				    pBuffer[i]=cache.buf[0];
   					pBuffer[i+1]=cache.buf[1];
					  pBuffer[i+2]=cache.buf[2];
					  pBuffer[i+3]=cache.buf[3];
    }
}

//==================================================================================
// 向FLASH指定地址 写入大量数据
// WriteAddr:写入首地址
// pBuffer:数据首地址
// NumToWrite:需要写入数据的大小
// 返回: 4=成功  1,2,3,5=失败
// 备注:
//==================================================================================
FLASH_Status  FLASH_Write(unsigned int	WriteAddr,unsigned char *pBuffer,unsigned int NumToWrite)
{

    FLASH_Status status = FLASH_COMPLETE;
	  u32tobyte cache;//联合体定义
    unsigned int startaddr,endaddr,pageaddr=0;
	  unsigned char buffer[4096];//4K缓冲区 对应FALSH 1页
	  unsigned int i;
	  unsigned int index,remain;
    startaddr = WriteAddr;
    endaddr = startaddr+NumToWrite;//结束地址
	
    FLASH_Unlock();
    FCU->RO = 0;//去掉所有扇区写保护
    //==================================================================================
    // 判断写入地址是否非法  起始地址或者结束地址不在FALSH范围内则退出
    //==================================================================================
    if(!(IS_FLASH_ADDRESS(startaddr)&& IS_FLASH_ADDRESS(endaddr))) return FLASH_ERROR_PG;
   
	   while(startaddr < endaddr)
		 {
			 
		//==================================================================================
    //1.计算起始地址在FALSH哪一页,并获取该页的首地址
		//2.计算起始地址在该页的偏移量
    //3.计算该页还剩余多少字节没写入数据			 
    //==================================================================================
			  pageaddr = FLASH_GetFlashPage(startaddr);//获取起始地址所在页的页首地址
			  index = startaddr-pageaddr;//4K缓冲区内偏移地址
			  remain=4096-index;//缓存区剩余大小
    //==================================================================================
    // 将该页数据读入4K缓冲数组,后面读写都是对该缓冲数组操作
    //==================================================================================			 
		    for(i=0;i<4096;i+=4)//读取一页到缓冲buff
			  {
				    cache.u32data=FLSAH_ReadWord(pageaddr+i);
				    buffer[i]=cache.buf[0];
   					buffer[i+1]=cache.buf[1];
					  buffer[i+2]=cache.buf[2];
					  buffer[i+3]=cache.buf[3];
				} 
    //==================================================================================
    // 擦除FALSH对应的页,FLASH只能按页擦除,
		// 这一页数据已经被读到缓冲数组中了 之前的数据也保留下来了 		
    //==================================================================================				
				status = FLASH_ErasePage(startaddr);
        if(status != FLASH_COMPLETE) return status;//擦除1页 4K字节					
    //==================================================================================
    //1.判断要写入的数据是否大于该页剩余容量(即计算写入的数据长度是否跨多页) 
		//2.将需要写入的数据转存到缓冲数据		
    //==================================================================================				
				if(NumToWrite > remain)//需要写入的数据量大于缓冲buf剩余字节数
				{
					for(i=index;i<4096;i++)//将需要写入FALSH的数据写入缓冲buff
					{
							 buffer[i]=*(pBuffer++);				
					}
					NumToWrite-=remain;//需要写入的数据长度-本次已经写入的数据长度	
          startaddr+=remain;//地址向后偏移本次写入的字节数					
			  }
				else
				{
				  for(i=index;i<NumToWrite+index;i++)//将需要写入FALSH的数据写入缓冲buff
					{
							 buffer[i]=*(pBuffer++);				
					} 
          startaddr+=NumToWrite;//地址向后偏移本次写入的字节数								
				}	
    //==================================================================================
    // 将缓冲数组(4K)写入FLASH 对应的页
		// 此处必须从页首写入,因为缓冲数组正好4K,对应FALSH 1页	
    //==================================================================================				
				for(i=0;i<4096;i+=4)//将缓冲buffer写入 FALSH
			  {
				    cache.buf[0]=buffer[i];
   					cache.buf[1]=buffer[i+1];
					  cache.buf[2]=buffer[i+2];
					  cache.buf[3]=buffer[i+3];
					
					  if((status=FLASH_ProgramWord(pageaddr+i,cache.u32data))!= FLASH_COMPLETE)
						{
								FLASH_Lock();
								return status;//写入失败 FLASH上锁 
						}	             						
				}			
		 }
    FLASH_Lock();
}

其中还有个联合体的定义: 

代码语言:javascript
代码运行次数:0
复制
typedef union
{
    unsigned int  data;
    unsigned char buf[4];
}
u32tou8;

FLASH_ErasePage、FLASH_ProgramWord、IS_FLASH_ADDRESS 这三个都是单片机FLASH的库函数

各家单片机不同,但功能基本相同,这里不再提供源码。

最后提供以下两个FLASH接口即可:

代码语言:javascript
代码运行次数:0
复制
FLASH_Write(unsigned int    WriteAddr,unsigned char *pBuffer,unsigned int NumToWrite);

FLASH_Read(unsigned int    ReadAddr,unsigned char *pBuffer,unsigned int NumToRead)

演示:

1.为方便查看结果,测试从0x1070FFC的位置开始写入数据,FLASH地址分布如下图所示:

这里展示了FLASH连续两页的地址,首先将这两页全部擦除。

2.接着从1070FFC的位置开始写入56个1,这样就保证了数据跨越了1页。 

代码语言:javascript
代码运行次数:0
复制
unsigned char write[]= {"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111"};
FLASH_Write(0x01070FFC,write,sizeof(write));

 注意:最后的00是因为字符串的结尾字符是“\0”

3.紧接着,在0x1070FFE位置写入新的字符串,也要保证写入长度跨越1页

代码语言:javascript
代码运行次数:0
复制
unsigned char write2[]={"23456789"};
FLASH_Write(0x01070FFE,write2,sizeof(write2));

可以看出,0x1070FFE~0x1071006的位置被写入了新的字节,但这两页的其他位置数据保持不变。

总结:

1.实际使用时,如果不是受限于成本或者FLASH大小,不建议这样读写内部FLASH,以为stm32内部FLASH也就

10W次寿命,这样频繁擦写会大大降低FLASH寿命。

2.如果保存的数据不多,建议每个数据都单独存1页,这样不用考虑擦除时会把其他数据也一并擦除。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 演示:
  • 总结:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档