1.单片机内存描述
1.1ROM(Read Only Memory)
ROM(Read Only Memory),只读存储器。用来存储和保存数据。ROM数据不能随意更新,但是在任何时候都可以读取。即使是断电,ROM也能够保留数据。
1.1.1ROM的分类
PROM:是可编程一次性(无法修改)的ROM;
EPROM:是紫外线可擦除可编程的ROM;
EEPROM:是电可擦除可编程的ROM,按字节进行删除和重写, 写入时间很长,写入很慢;现在多用作非易失的数据存储器。特点是可以随机访问和修改任何一个字节,可以往每个bit中写入0或者1。这是最传统的一种EEPROM,掉电后数据不丢失,可以保存100年,可以擦写100w次。具有较高的可靠性,但是电路复杂/成本也高。因此目前的EEPROM都是几十千字节到几百千字节的,绝少有超过512K的。
注意:我一直对ROM有一个误解,他叫只读存储器,但是由于科技的发展,现阶段使用的ROM主要指代EEPROM,他是可以通过程序编程的方式进行数据的增删改查。但是PROM和EPROM就不可以了,需要特别注意,当然还有很多单片机内置有Data FLASH,在汽车行业一般用于仿制EEPROM。
1.2栈区(Stack)
1、 临时创建的局部变量存放在栈区
2、 函数调用时,其入口参数存放在栈区。
3、 函数返回时,其返回值存放在栈区。
4、 const定义的局部变量存放在栈区。
1.3堆区(heap)
1、 堆区用于存放程序运行中被动态分布的内存段,可增可减。
2、 可以有malloc等函数实现动态分布内存。
3、 有malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。
1.4全局区(静态区)
全局区有.bss段和.data段组成,可读可写
1.5.bss段
未初始化的全局变量存放在.bss段。
初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
.bss段不占用可执行文件空间,其内容有操作系统初始化。
6.6.data段
已经初始化的全局变量存放在.data段。
静态变量存放在.data段。
.data段占用可执行文件空间,其内容有程序初始化。
const定义的全局变量存放在.rodata段。
1.7常量区
字符串存放在常量区。
常量区的内容不可以被修改。
1.8代码区
程序执行代码存放在代码区。
字符串常量也有可能存放在代码区。
通过上面的介绍,可能你对各个数据的存储位置还是很模糊,下面通过一个简单的程序,再来体会理解一下。
通过上面的介绍,可能你对各个数据的存储位置还是很模糊,下面通过一个简单的程序,再来体会理解一下【多余一段】
#include <stdio.h>
static unsigned int val1 = 1; //val1存放在.data段
unsigned int val2 = 1; //初始化的全局变量存放在.data段
unsigned int val3 ; //未初始化的全局变量存放在.bss段
const unsigned int val4 = 1; //val4存放在.rodata(只读数据段)
unsigned char Demo(unsigned int num) //num 存放在栈区
{
char var = "123456"; //var存放在栈区,"123456"存放在常量区
unsigned int num1 = 1 ; //num1存放在栈区
static unsigned int num2 = 0; //num2存放在.data段
const unsigned int num3 = 7; //num3存放在栈区
void *p;
p = malloc(8); //p存放在堆区
free(p);
return 1;
}
void main()
{
unsigned int num = 0 ;
num = Demo(num); //Demo()函数的返回值存放在栈区。
}
上面我们已经对堆、栈、全局区、常量区、代码区进行了全面的分析,也举例进行了说明。下面我们在对这些区存放在哪种介质上进行讨论。
1.9RAM和ROM、Flash Memory的物理特性
首先,我们需要明白RAM和ROM、Flash Memory的物理特性。
1.9.1RAM
RAM又称随机存取存储器,存储的内容可通过指令随机读写访问。RAM中的存储的数据在掉电是会丢失,因而只能在开机运行时存储数据。其中RAM又可以分为两种,一种是Dynamic RAM(DRAM动态随机存储器),另一种是Static RAM(SRAM,静态随机存储器)。
1.9.2ROM
ROM又称只读存储器,只能从里面读出数据而不能任意写入数据。ROM与RAM相比,具有读写速度慢的缺点。但由于其具有掉电后数据可保持不变的优点,因此常用也存放一次性写入的程序和数据,比如主版的BIOS程序的芯片就是ROM存储器。
1.9.3Flash Memory
由于ROM具有不易更改的特性,后面就发展了Flash Memory。Flash Memory不仅具有ROM掉电不丢失数据的特点,又可以在需要的时候对数据进行更改,不过价格比ROM要高。
1.10不同数据的存放位置
由前面的分析我们知道,代码区和常量区的内容是不允许被修改的,ROM(STM32就是Flash Memory)也是不允许被修改的,所以代码区和常量区的内容编译后存储在ROM中。
而栈、堆、全局区(.bss段、.data段)都是存放在RAM中。
至此,关于不同数据存放哪个区域已经全部介绍完了。下面还将介绍一下Keil 的Build Output窗口。
1.11Keil的Build Output窗口
如上图,存在Code、RO-data、RW-data、ZI-data四个代码段大小。
其中Code就是代码占用大小,RO-data是只读常量、RW-data是已初始化的可读可写变量,ZI-data是未初始化的可读可写变量。
有些时候,我们需要知道RAM和ROM的使用情况如何,那么我们就可以使用下面的公式计算。
观察map文件,可知
RAM = RW-data + ZI-data
ROM = Code + RO-data + RW-data
2.STM32单片机中如何代码操作FLASH
2.1Cubemx配置与代码实现
第一步:打开Cubemx然后选择好芯片
第二步:选择下载器模式
第三步:选择外部时钟
第四步:配置时钟树
第五步:给工程起名字
第六步:生成工程
第七步:打开工程
第八步:编写代码
/**
* 函 数:FLASH_Program是FLASH的写入
* 参 数:TypeProgram 写入的数据size eg:半字(16-bit) FLASH_TYPEPROGRAM_HALFWORD 全字(32-bit) FLASH_TYPEPROGRAM_WORD 双字(64-bit) FLASH_TYPEPROGRAM_DOUBLEWORD
:Address 需要写入的地址
:Data 需要写入的数据
* 返 回 值:HAL_StatusTypeDef
*/
HAL_StatusTypeDef FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
{
HAL_StatusTypeDef status = HAL_ERROR;
uint32_t Addr = 0x08000000 + (0x400*((Address - 0x08000000)/0x400));
static FLASH_EraseInitTypeDef EraseInitStruct = {
.TypeErase = FLASH_TYPEERASE_PAGES, //页擦除
// .PageAddress = FLASH_SAVE_ADDR, //擦除地址
.NbPages = 1 //擦除页数
};
EraseInitStruct.PageAddress = Addr;
HAL_FLASH_Unlock();
uint32_t PageError = 0;
__disable_irq(); //擦除前关闭中断
if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)
{
status = HAL_ERROR;
}
__enable_irq(); //擦除后打开中断
status = HAL_FLASH_Program(TypeProgram,Address, Data);
HAL_FLASH_Lock();
return status;
}
/**
* 函 数:FLASH_Program是FLASH的页写入
* 参 数:TypeProgram 写入的数据size eg:半字(16-bit) FLASH_TYPEPROGRAM_HALFWORD 全字(32-bit) FLASH_TYPEPROGRAM_WORD 双字(64-bit) FLASH_TYPEPROGRAM_DOUBLEWORD
:Address 需要写入页的首地址
:Data 需要写入的数据
* 返 回 值:HAL_StatusTypeDef
*/
HAL_StatusTypeDef FLASH_Page_Program(uint32_t TypeProgram, uint32_t Address, uint8_t *Data)
{
HAL_StatusTypeDef status = HAL_ERROR;
uint32_t Addr = 0x08000000 + (0x400*((Address - 0x08000000)/0x400));
int16_t index = 0;
uint64_t page_data = 0;
static FLASH_EraseInitTypeDef EraseInitStruct = {
.TypeErase = FLASH_TYPEERASE_PAGES, //页擦除
// .PageAddress = FLASH_SAVE_ADDR, //擦除地址
.NbPages = 1 //擦除页数
};
EraseInitStruct.PageAddress = Addr;
HAL_FLASH_Unlock();
uint32_t PageError = 0;
__disable_irq(); //擦除前关闭中断
if (HAL_FLASHEx_Erase(&EraseInitStruct,&PageError) == HAL_OK)
{
status = HAL_ERROR;
}
__enable_irq(); //擦除后打开中断
for(index = 0;index < 0x400;index = index + 8){
page_data = ((uint64_t)Data[index]<<56)|((uint64_t)Data[index+1]<<48)|((uint64_t)Data[index+2]<<40)|((uint64_t)Data[index+3]<<32)|((uint64_t)Data[index+4]<<24)|((uint64_t)Data[index+5]<<16)|((uint64_t)Data[index+6]<<8)|((uint64_t)Data[index+7]<<0);
status = HAL_FLASH_Program(TypeProgram,Address + index, page_data);
}
HAL_FLASH_Lock();
return status;
}
2.2测试
在这里找到看地址的窗口(超级超级超级超级超级友情提醒,我这里使用的是P FLASH ,因此在写好代码调用千万不要放在循环里面轮询调用,不然单片机运行超级快,很快就会超过FLASH的使用寿命,超过了片子可能会嘎,别问我咋知道的,我也是嘎了五块才豁然开朗!!!!)
查看写入的地址
写完了,你如果满意,请给个点赞,如果你有疑问,欢迎私信交流,大家一起学习进步!!!