一、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的教程!!!