M0S10系列内部存储器及Flash编程
1. 内部存储器概述
ABOV M0S10系列单片机(MCU)内部存储器主要分为两类:RAM(随机存取存储器)和Flash存储器。RAM用于存储运行时的数据,而Flash存储器用于存储程序代码和静态数据。了解这两类存储器的特点和使用方法对于开发高效、可靠的嵌入式系统至关重要。
1.1 RAM存储器
RAM存储器是易失性存储器,断电后数据会丢失。M0S10系列MCU通常配备一定量的SRAM(静态RAM),用于存储变量、栈和堆等数据。SRAM的特点是访问速度快,适合频繁读写的场景。
1.1.1 SRAM容量
M0S10系列MCU的SRAM容量因型号而异,常见的容量有8KB、16KB和32KB。具体容量可以参考数据手册中的技术规格。
1.1.2 SRAM地址范围
SRAM的地址范围通常从0x20000000开始,具体地址范围也因型号而异。例如,对于32KB SRAM的MCU,其地址范围为0x20000000至0x20007FFF。
1.1.3 SRAM使用示例
以下是一个简单的C代码示例,展示了如何使用SRAM来存储和操作数据:
#include "stm32f0xx.h"
// 定义一个全局变量,存储在SRAM中
volatile uint32_t globalVar = 0x12345678;
int main(void) {
// 初始化系统时钟
SystemInit();
// 使用SRAM中的变量
uint32_t localVar = globalVar;
localVar += 0x55AA55AA;
// 将局部变量的值写回全局变量
globalVar = localVar;
// 无限循环
while (1) {
// 可以在此处添加其他代码
}
}
2. Flash存储器概述
Flash存储器是非易失性存储器,断电后数据不会丢失。M0S10系列MCU的Flash存储器用于存储程序代码和静态数据。Flash存储器的特点是容量较大,但写入和擦除速度较慢,且有写入次数的限制。
2.1 Flash容量
M0S10系列MCU的Flash容量因型号而异,常见的容量有64KB、128KB和256KB。具体容量可以参考数据手册中的技术规格。
2.2 Flash地址范围
Flash存储器的地址范围通常从0x08000000开始,具体地址范围也因型号而异。例如,对于128KB Flash的MCU,其地址范围为0x08000000至0x0801FFFF。
2.3 Flash编程
Flash编程是将程序代码和静态数据写入Flash存储器的过程。M0S10系列MCU提供了多种编程方法,包括通过编程器编程、通过串行接口编程和通过软件编程。
2.3.1 通过编程器编程
通过编程器编程是最常见的方法。编程器通常通过JTAG或SWD接口与MCU连接,将编译后的二进制文件烧写到Flash存储器中。常用的编程器有ST-Link、J-Link等。
2.3.2 通过串行接口编程
通过串行接口编程(如UART、I2C、SPI等)也是一种常见的方法,适用于远程或在线编程。这种方法通常需要编写一个引导程序(Bootloader),通过引导程序将数据写入Flash存储器。
2.3.3 通过软件编程
通过软件编程是指在运行时将数据写入Flash存储器。M0S10系列MCU提供了Flash编程接口,可以通过API函数进行编程。以下是一个使用STM32F0xx HAL库的示例代码,展示了如何在运行时将数据写入Flash存储器:
#include "stm32f0xx_hal.h"
// 定义Flash编程的地址
#define FLASH_USER_START_ADDR ADDR_FLASH_PAGE_25 /* Start @ of user Flash area */
#define FLASH_USER_END_ADDR (ADDR_FLASH_PAGE_26 + FLASH_PAGE_SIZE - 1) /* End @ of user Flash area */
// 定义要写入的数据
uint32_t dataToWrite = 0x12345678;
void SystemClock_Config(void);
static void Error_Handler(void);
int main(void) {
// 初始化HAL库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 解锁Flash编程
HAL_FLASH_Unlock();
// 清除Flash编程的错误标志
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);
// 写入数据到Flash
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_USER_START_ADDR, dataToWrite) != HAL_OK) {
Error_Handler();
}
// 锁定Flash编程
HAL_FLASH_Lock();
// 无限循环
while (1) {
// 可以在此处添加其他代码
}
}
void SystemClock_Config(void) {
// 配置系统时钟
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 初始化振荡器
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12;
RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
// 初始化时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) {
Error_Handler();
}
}
static void Error_Handler(void) {
// 错误处理
while (1) {
// 可以在此处添加错误处理代码
}
}
3. Flash编程注意事项
在进行Flash编程时,需要注意以下几点以确保编程的可靠性和安全性:
3.1 写入次数限制
Flash存储器的每个扇区或页都有写入次数的限制。通常情况下,每个扇区或页可以写入10,000次。超过这个次数可能会导致存储器损坏,因此在设计时应尽量减少写入操作的频率。
3.2 擦除操作
在写入新的数据之前,需要先擦除目标地址的Flash存储器。擦除操作会将目标地址的数据全部清零,因此在擦除时需要特别小心,避免误擦除其他重要的数据。
3.3 锁定保护
为了防止意外写入或擦除Flash存储器,M0S10系列MCU提供了锁定和解锁机制。在进行Flash编程之前,需要先解锁Flash编程,编程完成后应立即锁定Flash编程。
3.4 编程错误处理
在Flash编程过程中,可能会遇到各种错误,如编程错误(PGERR)、写保护错误(WRPERR)等。因此,需要在编程代码中加入错误处理机制,确保在发生错误时能够及时处理。
4. Flash编程实例
以下是一个完整的Flash编程实例,展示了如何在运行时将数据写入Flash存储器,并在重启后读取这些数据。
4.1 硬件准备
- ABOV M0S10系列MCU开发板
- ST-Link编程器
- 串口调试工具(如TeraTerm、Putty等)
4.2 软件准备
- STM32CubeMX
- STM32 HAL库
- Keil uVision或STM32CubeIDE
4.3 代码实现
-
初始化Flash编程环境
首先,需要初始化Flash编程环境,包括解锁Flash编程、清除错误标志等。
#include "stm32f0xx_hal.h" // 定义Flash编程的地址 #define FLASH_USER_START_ADDR ADDR_FLASH_PAGE_25 /* Start @ of user Flash area */ #define FLASH_USER_END_ADDR (ADDR_FLASH_PAGE_26 + FLASH_PAGE_SIZE - 1) /* End @ of user Flash area */ void SystemClock_Config(void); static void Error_Handler(void); void Flash_Init(void) { // 解锁Flash编程 HAL_FLASH_Unlock(); // 清除Flash编程的错误标志 __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR); } void Flash_Lock(void) { // 锁定Flash编程 HAL_FLASH_Lock(); }
-
擦除Flash存储器
在写入新的数据之前,需要先擦除目标地址的Flash存储器。
void Flash_Erase(uint32_t startAddress, uint32_t endAddress) { FLASH_EraseInitTypeDef EraseInitStruct; uint32_t PageError = 0; // 配置擦除参数 EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; EraseInitStruct.PageAddress = startAddress; EraseInitStruct.NbPages = (endAddress - startAddress + 1) / FLASH_PAGE_SIZE; // 擦除Flash存储器 if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK) { Error_Handler(); } }
-
写入数据到Flash存储器
使用
HAL_FLASH_Program
函数将数据写入Flash存储器。void Flash_Write(uint32_t address, uint32_t data) { // 写入数据到Flash if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) { Error_Handler(); } }
-
读取Flash存储器中的数据
使用简单的读操作从Flash存储器中读取数据。
uint32_t Flash_Read(uint32_t address) { // 从Flash读取数据 return *(__IO uint32_t*)address; }
-
主函数
在主函数中,调用上述函数实现数据的写入和读取,并在重启后验证数据是否正确。
int main(void) { // 初始化HAL库 HAL_Init(); // 配置系统时钟 SystemClock_Config(); // 初始化Flash编程环境 Flash_Init(); // 擦除Flash存储器 Flash_Erase(FLASH_USER_START_ADDR, FLASH_USER_END_ADDR); // 写入数据到Flash Flash_Write(FLASH_USER_START_ADDR, 0x12345678); // 读取Flash存储器中的数据 uint32_t readData = Flash_Read(FLASH_USER_START_ADDR); // 输出读取的数据 if (readData == 0x12345678) { // 数据正确 // 可以在此处添加其他代码 } else { // 数据错误 Error_Handler(); } // 锁定Flash编程环境 Flash_Lock(); // 无限循环 while (1) { // 可以在此处添加其他代码 } } void SystemClock_Config(void) { // 配置系统时钟 RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // 初始化振荡器 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12; RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } // 初始化时钟 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) { Error_Handler(); } } static void Error_Handler(void) { // 错误处理 while (1) { // 可以在此处添加错误处理代码 } }
5. Flash存储器的高级使用
5.1 使用Flash存储器存储配置参数
在嵌入式系统中,经常需要存储一些配置参数,如设备ID、校准数据等。这些参数可以在系统初始化时从Flash存储器中读取,并在需要时进行更新。通过这种方式,可以实现系统的灵活配置和远程更新。
5.1.1 存储配置参数
以下是一个示例代码,展示了如何将配置参数存储到Flash存储器中。
#include "stm32f0xx_hal.h"
// 定义Flash编程的地址
#define FLASH_CONFIG_START_ADDR ADDR_FLASH_PAGE_25 /* Start @ of user Flash area */
#define FLASH_CONFIG_END_ADDR (ADDR_FLASH_PAGE_25 + FLASH_PAGE_SIZE - 1) /* End @ of user Flash area */
void SystemClock_Config(void);
static void Error_Handler(void);
void Flash_Init(void) {
// 解锁Flash编程
HAL_FLASH_Unlock();
// 清除Flash编程的错误标志
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);
}
void Flash_Lock(void) {
// 锁定Flash编程
HAL_FLASH_Lock();
}
void Flash_Erase(uint32_t startAddress, uint32_t endAddress) {
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t PageError = 0;
// 配置擦除参数
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = startAddress;
EraseInitStruct.NbPages = (endAddress - startAddress + 1) / FLASH_PAGE_SIZE;
// 擦除Flash存储器
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK) {
Error_Handler();
}
}
void Flash_Write(uint32_t address, uint32_t data) {
// 写入数据到Flash
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {
Error_Handler();
}
}
uint32_t Flash_Read(uint32_t address) {
// 从Flash读取数据
return *(__IO uint32_t*)address;
}
void SaveConfig(uint32_t configData) {
// 擦除Flash存储器
Flash_Erase(FLASH_CONFIG_START_ADDR, FLASH_CONFIG_END_ADDR);
// 写入配置参数
Flash_Write(FLASH_CONFIG_START_ADDR, configData);
// 锁定Flash编程环境
Flash_Lock();
}
uint32_t ReadConfig(void) {
// 读取配置参数
return Flash_Read(FLASH_CONFIG_START_ADDR);
}
int main(void) {
// 初始化HAL库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化Flash编程环境
Flash_Init();
// 读取配置参数
uint32_t configData = ReadConfig();
// 如果配置参数为0,写入默认值
if (configData == 0) {
configData = 0x12345678;
SaveConfig(configData);
}
// 输出读取的配置参数
if (configData == 0x12345678) {
// 配置参数正确
// 可以在此处添加其他代码
} else {
// 配置参数错误
Error_Handler();
}
// 锁定Flash编程环境
Flash_Lock();
// 无限循环
while (1) {
// 可以在此处添加其他代码
}
}
void SystemClock_Config(void) {
// 配置系统时钟
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 初始化振荡器
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12;
RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
// 初始化时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) {
Error_Handler();
}
}
static void Error_Handler(void) {
// 错误处理
while (1) {
// 可以在此处添加错误处理代码
}
}
5.2 使用Flash存储器存储日志数据
在某些嵌入式应用中,记录系统运行日志是非常重要的。这些日志数据可以用于故障诊断、系统调试等。通过将日志数据存储在Flash存储器中,可以确保数据在断电后不会丢失。
5.2.1 存储日志数据
以下是一个示例代码,展示了如何将日志数据存储到Flash存储器中。
#include "stm32f0xx_hal.h"
// 定义Flash编程的地址
#define FLASH_LOG_START_ADDR ADDR_FLASH_PAGE_26 /* Start @ of user Flash area */
#define FLASH_LOG_END_ADDR (ADDR_FLASH_PAGE_27 + FLASH_PAGE_SIZE - 1) /* End @ of user Flash area */
void SystemClock_Config(void);
static void Error_Handler(void);
void Flash_Init(void) {
// 解锁Flash编程
HAL_FLASH_Unlock();
// 清除Flash编程的错误标志
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_WRPERR | FLASH_FLAG_PGERR);
}
void Flash_Lock(void) {
// 锁定Flash编程
HAL_FLASH_Lock();
}
void Flash_Erase(uint32_t startAddress, uint32_t endAddress) {
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t PageError = 0;
// 配置擦除参数
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = startAddress;
EraseInitStruct.NbPages = (endAddress - startAddress + 1) / FLASH_PAGE_SIZE;
// 擦除Flash存储器
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK) {
Error_Handler();
}
}
void Flash_Write(uint32_t address, uint32_t data) {
// 写入数据到Flash
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, data) != HAL_OK) {
Error_Handler();
}
}
uint32_t Flash_Read(uint32_t address) {
// 从Flash读取数据
return *(__IO uint32_t*)address;
}
void SaveLog(uint32_t logData) {
static uint32_t logIndex = 0;
// 计算日志数据的存储地址
uint32_t logAddress = FLASH_LOG_START_ADDR + logIndex * sizeof(uint32_t);
// 检查是否超出存储范围
if (logAddress > FLASH_LOG_END_ADDR) {
// 擦除日志存储区
Flash_Erase(FLASH_LOG_START_ADDR, FLASH_LOG_END_ADDR);
logIndex = 0;
logAddress = FLASH_LOG_START_ADDR;
}
// 写入日志数据
Flash_Write(logAddress, logData);
// 更新日志索引
logIndex++;
// 锁定Flash编程环境
Flash_Lock();
}
uint32_t ReadLog(uint32_t index) {
// 计算日志数据的读取地址
uint32_t logAddress = FLASH_LOG_START_ADDR + index * sizeof(uint32_t);
// 读取日志数据
return Flash_Read(logAddress);
}
int main(void) {
// 初始化HAL库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化Flash编程环境
Flash_Init();
// 擦除日志存储区
Flash_Erase(FLASH_LOG_START_ADDR, FLASH_LOG_END_ADDR);
// 写入日志数据
SaveLog(0x12345678);
SaveLog(0x9ABCDEF0);
// 读取日志数据
uint32_t logData1 = ReadLog(0);
uint32_t logData2 = ReadLog(1);
// 输出读取的日志数据
if (logData1 == 0x12345678 && logData2 == 0x9ABCDEF0) {
// 日志数据正确
// 可以在此处添加其他代码
} else {
// 日志数据错误
Error_Handler();
}
// 锁定Flash编程环境
Flash_Lock();
// 无限循环
while (1) {
// 可以在此处添加其他代码
}
}
void SystemClock_Config(void) {
// 配置系统时钟
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 初始化振荡器
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12;
RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
// 初始化时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) {
Error_Handler();
}
}
static void Error_Handler(void) {
// 错误处理
while (1) {
// 可以在此处添加错误处理代码
}
}
6. Flash编程的优化技巧
为了提高Flash编程的效率和可靠性,可以采用以下几种优化技巧:
6.1 批量擦除
在擦除Flash存储器时,可以批量擦除多个扇区或页,而不是逐个擦除。这可以减少擦除操作的次数,提高编程速度。
6.2 数据缓存
在写入数据之前,可以先将数据缓存到RAM中,然后再批量写入Flash存储器。这样可以减少Flash存储器的写入次数,延长其使用寿命。
6.3 错误重试机制
在编程过程中,如果遇到错误,可以设计一个重试机制,尝试多次编程,直到成功或达到最大重试次数。这样可以提高编程的可靠性。
7. 结论
M0S10系列MCU的内部存储器和Flash编程是嵌入式系统开发中的重要环节。通过合理使用RAM和Flash存储器,可以提高系统的性能和可靠性。了解Flash编程的注意事项和优化技巧,有助于开发高效、稳定的嵌入式系统。希望本文档能为您的开发工作提供帮助。
8. 参考资料
- ABOV M0S10系列MCU数据手册
- STM32F0xx HAL库用户手册
- ST-Link编程器用户手册
- STM32CubeMX用户手册
通过上述内容,您可以更深入地了解M0S10系列MCU的内部存储器和Flash编程方法,从而在实际项目中更好地应用这些知识。