stm32g070 Flash HAL读写操作

一、stm32g070 Flash

由于没有外挂Flash,内存又比较小,所以在此使用内置Flash作为缓存,
stmG070CBT6整体flash位128K,为flash规划分区,分区表如下。

在这里插入图片描述

从STM32G070寄存器手册可以看到,内部flash是2K对齐总共有64个页,同时写操作flash时要注意地址为4字节对齐。
我这里把flash,分成几个区域,如下图
在这里插入图片描述

二、驱动方法

1、G0系列的驱动需要注意Flash需要进行64位或快速双32位写入,不支持单字节写入,这是和F103系列最大区别。读写时候是一样的!
这里是 stmflash.h。flash的头文件

/**
 ****************************************************************************************************
 * @file        stmflash.h
 * @author      935848559@qq.com
 * @version     V1.0
 * @date        2024-03-12
 * @brief       STM32G0内部FLASH读写 驱动代码
 * @license     Copyright (c) 
 ****************************************************************************************************
 * @attention
 *
 *
 ****************************************************************************************************
 */

#ifndef __STMFLASH_H
#define __STMFLASH_H


#include "main.h"


#define UPDATE_FLAG     0x123456        //升级标志
#define UPDATE_OVER     0xffffff       //升级完成标志

#define CONFIG_ADDR     0X8004800       //升级标志位存储分区
#define APP1_ADDR       0x8005000       //APP1 跳转地址
#define OTA_ADDR        0x8012800       //OTA  升级地址
#define ERASE_LEN       54              //擦除长度,单位kb



typedef enum
{
    FLASH_OK =0x00U,
    FLASH_ERROR,    
}FLASH_SATAE_T;


FLASH_SATAE_T Write_Flash(uint32_t address, uint8_t *buf, uint32_t length);
FLASH_SATAE_T Write_Config(uint64_t data);



#endif

stmflash.c文件

/**
 ****************************************************************************************************
 * @file        stmflash.c
 * @author      935848559@qq.com
 * @version     V1.0
 * @date        2024-03-12
 * @brief       STM32G0内部FLASH读写 驱动代码
 * @license     Copyright (c) 
 ****************************************************************************************************
 * @attention
 *
 *
 ****************************************************************************************************
 */


#include "./BSP/STMFLASH/stmflash.h"

uint32_t addr = OTA_ADDR;   //最新的Flash地址


/**
 * @brief       获取 地址Addr 在 Flash中式第几页。根据每页大小2k 
 * @param       Addr    开头地址
 * @retval      FLASH页数
 */
static uint32_t GetPage(uint32_t Addr)
{
    uint32_t page = 0;
    page = (Addr-FLASH_BASE) / FLASH_PAGE_SIZE;
    return page;
}

/**
 * @brief       擦除指定位置和大小的Flash,擦除flash之前必须解锁flash。
 * @param       addr    开头地址
 * @param       num    内存大小/kb
 * @retval      FLASH_SATAE_T state
 */
static FLASH_SATAE_T Erase_Flash(uint32_t addr,uint8_t num) 
{
    uint32_t SectorError=0;
    FLASH_EraseInitTypeDef UPDATE_FLASH;
    
    UPDATE_FLASH.TypeErase = FLASH_TYPEERASE_PAGES;
    UPDATE_FLASH.Page = GetPage(addr);
    UPDATE_FLASH.NbPages = num;
    if(HAL_FLASHEx_Erase(&UPDATE_FLASH,&SectorError) != HAL_OK) {
        printf("ERASE ERROR %x\n",SectorError);
        goto ERROR;
    }
    printf("ERASE SUCCESS %x\n",SectorError);
    return FLASH_OK;
ERROR:
    return FLASH_ERROR;
}

/**
 * @brief       写config Flash,升级标志位
 * @param       data     64位双字节
 * @retval      FLASH_SATAE_T state
 */
FLASH_SATAE_T Write_Config(uint64_t data) 
{
    HAL_FLASH_Unlock();//解锁flash
    if(Erase_Flash(CONFIG_ADDR,1) != FLASH_OK) { //擦除flash
        goto ERROR;
    }
    if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,CONFIG_ADDR,data) != HAL_OK) {
        printf("Write_Config ERROR\n");
        goto ERROR;
    } 
    HAL_FLASH_Lock();//上锁
    printf("Write_Config SUCCESS\n");
    return FLASH_OK;
ERROR:
    HAL_FLASH_Lock();//上锁    
    return FLASH_ERROR;
}

/**
 * @brief       写Flash,这里包括擦除flash,每页Flash是2Kb
 * @param       address    开头地址
 * @param       *buf       写入数据
 * @param       length     写入字节数据长度
 * @retval      FLASH_SATAE_T state
 */
FLASH_SATAE_T Write_Flash(uint32_t start_address, uint8_t *buf, uint32_t length)
{
    uint32_t i,j,len =0;  
    uint64_t data = 0;
    uint8_t num = 1;
    
    if(length == 0)     //长度为0时直接返回
    {
        goto ERROR;
    }
	if(length % 8)       //长度非8字节倍数时则补齐新的8字节
        len = length/8+1; 
    else
        len = length/8;
    
    while(length > num * 2048) num++;   //得出需要擦除几页Flash
    
    HAL_FLASH_Unlock();//解锁flash	 
    if(Erase_Flash(start_address , num) != FLASH_OK)   //擦除Flash
    {
        printf("Erase_Flash ERROR\n");
        goto ERROR;
    }

	for(i=0;i<len;i++)//这个是要转多少组
	{
        data = 0;
        for(j=0;j<8;j++)//这个是转8个字节,64位数据
        {
            data|=(uint64_t)buf[j+i*8]<<(j*8);
        }
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,start_address,data) != HAL_OK) 
        {
            goto ERROR;
        }            
        start_address += 8;
	}    
    HAL_FLASH_Lock();//上锁
    addr = start_address;   //记录最新的地址
    printf("Write_Flash_Data SUCCESS\n");
    return FLASH_OK;
ERROR:
    HAL_FLASH_Lock();//上锁
    printf("Write_Flash_Data ERROR\n");
    return FLASH_ERROR;    
}


这里面flash写入数据是uint_t64 数据类型,是64位,我们的8位的数组,需要进行预处理,转换成64位写入flash。

/**这里就是写入数据转换   ***/
	for(i=0;i<len;i++)//这个是要转多少组
	{
        data = 0;
        for(j=0;j<8;j++)//这个是转8个字节,64位数据
        {
            data|=(uint64_t)buf[j+i*8]<<(j*8);
        }
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,start_address,data) != HAL_OK) 
        {
            goto ERROR;
        }            
        start_address += 8;
	} 

三、测试结果

在stmflash.c下添加测试程序,然后在main函数下调用

/**
 * @brief       flash 测试
 * @param       address    开头地址
 * @param       *buf       写入数据
 * @param       length     写入字节数据长度
 * @retval      void
 */
void Flash_test(uint32_t start_address, uint8_t *buf, uint32_t length)
{
    Write_Flash(OTA_ADDR,buf,length);       //写大数据进入
    
    if(Write_Config(UPDATE_FLAG) != FLASH_OK)   //写标志位数据
        printf("Write_APP ERROR %x\n",addr);
}

main.c 函数下面的while循环下添加测试代码,用串口发送数据,看看对应地址数据是否写入。在keil仿真下,看地址数据!

  /* USER CODE BEGIN WHILE */
  while (1)
  {
       if (debug_rxStC.rx_flag)
        {
            debug_rxStC.rx_flag = 0;
            applenth = debug_rxStC.rx_cnt;
            debug_rxStC.rx_cnt = 0;
            printf("用户程序接收完成!\r\n");
            printf("代码长度:%dBytes\r\n", applenth);
            printf("用户程序开始升级!\r\n");
            
            Flash_test(debug_rxStC.rx_buf, applenth); 
            
           LED0_TOGGLE();
           HAL_Delay(100);
      
        
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
四、测试结果

这里可以看到#define CONFIG_ADDR 0X8004800 //升级标志位存储分区
config标志位写入成功。
在这里插入图片描述
在看大存储部分#define OTA_ADDR 0x8012800 //OTA 升级地址
写入数据,和串口对比数据对比,没有问题,说明代码测试成功!!
在这里插入图片描述

五、读取flash

读取的话就简单了,直接使用地址即可 。
注意:读取可以是1字节,也可以是2字节、4字节或者8字节形式。我这里用的是单字节读取,连续读取是用uint8_t 数组存储!

/**
 * @brief       从指定地址读取一个字节 (8位数据)stm32是小端模式
 * @param       faddr   : 读取地址 
 * @retval      读取到的数据 (8位)
 */
uint8_t stmflash_read_byte(uint32_t faddr)
{
     /* 读取的话就简单了,直接使用地址即可 */
    return *(volatile uint8_t *)faddr;
}

   
/**
 * @brief       从指定地址读取一个字 (32位数据)stm32是小端模式
 * @param       faddr   : 读取地址 
 * @retval      读取到的数据 (32位)
 */
uint32_t stmflash_read_word(uint32_t faddr)
{
     /* 读取的话就简单了,直接使用地址即可 */
    return *(volatile uint32_t *)faddr;
}

/**
 * @brief       从指定地址开始读出指定长度的数据
 * @param       ReadAddr : 起始地址
 * @param       pBuffer  : 数据指针
 * @param       NumToRead: 要读取的字(8位)数,即1个字节
 * @retval      FLASH_SATAE_T state
 */
FLASH_SATAE_T STMFLASH_Read(uint32_t ReadAddr,uint8_t *pBuffer,uint32_t NumToRead)   		//连续读取
{
	uint32_t i;
    
    if(NumToRead == 0) //数据长度为0时,直接返回
        goto ERROR;

	for(i = 0;i < NumToRead;i++)
	{
		pBuffer[i] = stmflash_read_byte(ReadAddr);	//读取4个字节.
		ReadAddr += 1;								//偏移4个字节.	
	}
    return FLASH_OK;  
    
ERROR:
    return FLASH_ERROR;    
}

测试代码如下

/**
 * @brief       flash 测试
 * @param       address    开头地址
 * @param       *buf       写入数据
 * @param       length     写入字节数据长度
 * @retval      void
 */
void Flash_test(uint8_t *buf, uint32_t length)
{
    Write_Flash(OTA_ADDR,buf,length);       //写大数据进入
    
    if(Write_Config(UPDATE_FLAG) != FLASH_OK)   //写标志位数据
        printf("Write_APP ERROR %x\n",addr);
    
    uint64_t data = UPDATE_FLAG;
    Write_Flash(CONFIG_ADDR,(uint8_t *)&data,4);//正确,取uint64_t数据的地址,然后强制转成(uint8_t *)
    HAL_Delay(5000);
     printf("read config\n");
     printf("CONFIG_ADDR  = %x\n",stmflash_read_byte(CONFIG_ADDR));
     printf("CONFIG_ADDR+1  = %x\n",stmflash_read_byte(CONFIG_ADDR+1));
     printf("CONFIG_ADDR+2  = %x\n",stmflash_read_byte(CONFIG_ADDR+2));
     printf("CONFIG_ADDR+3  = %x\n",stmflash_read_byte(CONFIG_ADDR+3));
     printf("CONFIG_ADDR+4  = %x\n",stmflash_read_byte(CONFIG_ADDR+4));
     printf("CONFIG_ADDR+5  = %x\n",stmflash_read_byte(CONFIG_ADDR+5));

}

测试结果,上图:
在这里插入图片描述
单字节测试读取成功,没有用其他人例程的32位读取或者16位读取。读取时候,不用在乎数据大小!!

六、总结

1、G0系列的Flash是需要64位(8字节)写入的,不支持单字(32位)和半字(16位)写入。
2、全系统每页Flash都是2kb大小。
3、擦除flash只有2种方法:1是页擦除;2是全部擦除。
4、flash的擦除和写入 都需要先解除flash锁,操作完就需要上锁。不上锁可能导致flash数据不安全!
5、flash读取不分数据形式的,可以是1字节,也可以是2字节、4字节或者8字节形式。

后续:
后续在写如何进行STM32G070 在线升级IAP的教程!!!

对于STM32G070微控制器,您可以使用它的FLASH存储器进行读写操作。以下是一个简单的示例代码,用于在STM32G070上进行FLASH读写操作。 首先,您需要包含适当的头文件和定义相关的宏: ```c #include "stm32g0xx_hal.h" #define FLASH_START_ADDR 0x08000000 // Flash起始地址 #define FLASH_PAGE_SIZE 2048 // Flash页大小 ``` 然后,您可以编写两个函数来执行读写操作。一个是用于从FLASH中读取数据的函数,另一个是用于将数据写入FLASH的函数: ```c // 从FLASH中读取数据 void readFromFlash(uint32_t address, uint32_t *data, uint32_t count) { for (uint32_t i = 0; i < count; i++) { data[i] = *(volatile uint32_t *)(address + (4 * i)); } } // 将数据写入FLASH HAL_StatusTypeDef writeToFlash(uint32_t address, uint32_t *data, uint32_t count) { FLASH_EraseInitTypeDef eraseInitStruct; uint32_t PageError = 0; // 解锁FLASH HAL_FLASH_Unlock(); // 擦除指定地址的FLASH页 eraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; eraseInitStruct.PageAddress = address; eraseInitStruct.NbPages = 1; if (HAL_FLASHEx_Erase(&eraseInitStruct, &PageError) != HAL_OK) { return HAL_ERROR; } // 写入数据到FLASH for (uint32_t i = 0; i < count; i++) { if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address + (4 * i), data[i]) != HAL_OK) { return HAL_ERROR; } } // 锁定FLASH HAL_FLASH_Lock(); return HAL_OK; } ``` 请注意,这只是一个简单的示例代码,并且没有包含错误处理和其他边界条件。在实际使用中,您可能需要添加适当的错误处理和边界检查。 希望这能帮助到您开始在STM32G070上进行FLASH读写操作。如有任何进一步的问题,请随时提问!
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值