发新帖本帖赏金 200.00元(功能说明)我要提问
返回列表
打印
[N32G43x]

国民技术G435大容量FLASH让你储存离线数据不必节衣缩食!!

[复制链接]
8381|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 呐咯密密 于 2022-4-23 15:34 编辑
[url=home.php?mod=space&uid=760190]@21小跑堂 #申请原创#[/url]
前言:

国民技术刚来二姨家那会有幸获得一块开发板,当时没时间,一篇简短测评便敷衍了事,但是作为一款性价比较高的国产MCU,我的做法显然不适合,恰逢项目遇到需要掉电保存数据,国民的N32G435的大FLASH理应参与一下,毕竟128K的FLASH在一般应用中可以为用户储存很多的数据,那么今天就测评一下N32G435的FLASH读写性能。

测试工具:
国民技术N32G43XCL-STB开发板----主控为N32G435CB

keil5


创芯工坊PW200加密离线烧录器

PowerWriter上位机,配合PW200查看FLASH数据。

测试背景:

现在很多的应用中都需要保存离线数据,例如一些传感器的校正数据,每一块成片中的数据可能都不相同,不能每一次掉电后重启都重新进行一次校准,那样用户的体验感会非常不好,此时就需要掉电不丢失数据。一般的方式是在MCU外置一颗EEPROM芯片,通过MCU的I2C与其通信(例如AT24C02),此方法好处在于不占用片内FLASH,而且外置EEPROM大小灵活,对于大数据的掉电储存较为合适,毕竟MCU的FLASH容量有限,如果项目过大,可能没有太多的空闲FLASH预留给用户储存数据。但是缺点也很明显,占用PCB的版面,对于小尺寸的PCB很不友好,工作时也增加功耗,同时目前的EEPROM期间也存在溢价严重的问题,增加产品的成本。如果是少量的数据保存,优先还是片内FLASH。

FLASH操作注意事项:
1.每一款单片机的FLASH的大小不尽相同,在操作FLASH之前一定要根据手册确定手里的单片的FLASH的大小,超出FLASH容量的写操作是不被允许的,也无法成功完成数据写入。
2.计算好程序的内存,程序也是保存在FLASH中,如果没有计算好程序的大小,将写FLASH写入程序占用的内存中,会导致程序奔溃。
3.写数据之前必须先对页进行擦除,因为FLASH不能写1,只能写0,所以写之前要通过擦除操作将FLASH页中的数据全部恢复为FF,才能进行写操作,如果该FLASH中存在需要的数据,必须要先将数据读出来存在缓存区,再将页擦除,再进行写数据。
4.数据不超过一页,可连续写入。
5.注意FLASH的操作单位,每次最少写4个字节,可通过手册查询页的大小,因为一般采用的是整页的擦除和写入,不可随意擦写。

6.操作FLASH时会占用总线,会打断你的中断操作,且写FLASH时间一般较长,所以在操作FLASH时要保证单片机预留出足够的时间。

针对上述的注意事项,这里以N32G435CB为例,进行一次FLASH的操作流程讲解(此过程对其他单片机同样适用):

1.根据我们的单片机型号,从手册中可查询到片内FLASH为128K。


2.根据储存器映射图,可以了解到FLASH的地址是0x08000000-0x0801FFFF。那我们可操作的FLASH地址只能限制在该范围内,但是代码本身也是要占用FLASH空间的,那我们就需要去计算代码所占空间。



3. 查询代码所占空间可以找到工程中的.bin文件,看他的大小。



上图的.bin文件的大小为9KB,根据手册查询FLASH页面大小是2K,那么就需要5页,对应到FLASH的地址为0x08000000-0x08002800,该地址内不能去读写数据,否则会造成代码奔溃。



此处还有个直观的方式查询代码所占FLASH,就是用创芯工坊的烧录器进行FLASH页面的读取,在上位机进行显示,可以直观的看到代码所占地址,以及在地址内的数据分布,同时基于代码安全性,该烧录器还可以进行代码的读保护和代码的高度加密,即使代码被反读也无法运行。实现代码和MCU的绑定。创芯工坊也与国民技术达成合作,基本支持目前国民所有MCU的加密离线烧录,本人在去年论坛的测评有幸获得一块,操作方面好用,有代码加密,离线烧录和远程代码交付的可以试一下。目前还有蓝牙无线烧录,但是本人还未拥有,以后拿到了给大家分享一下。

官网链接:点此跳转


以下为PW200读取MCU的FLASH页面:



可以直观看到代码的存放位置,右边是每一页的大小和地址,在测试FLASH时非常方便。


4. 通过代码进行一次FLASH的擦除和读写操作。此处我们参见一下官方的demo。


<font size="4" face="黑体">#include "main.h"
#include <stdio.h>
/**
*  Flash_Program
*/

#define FLASH_PAGE_SIZE        ((uint16_t)0x800)
#define FLASH_WRITE_START_ADDR ((uint32_t)0x08010000)
#define FLASH_WRITE_END_ADDR   ((uint32_t)0x08018000)

/**
* [url=home.php?mod=space&uid=247401]@brief[/url]  Main program.
*/
int main(void)
{
    uint32_t Counter_Num = 0;
    uint32_t Erase_Data  = 0x12345678;
    /* USART Init */
    USART_Config();
    printf("Flash Program Test Start\r\n");
    /* Program FLASH */

    /* Configures the Internal High Speed oscillator */
    if(FLASH_HSICLOCK_DISABLE == FLASH_ClockInit())
    {
        printf("HSI oscillator not yet ready\r\n");
        while(1);
    }

    /* Unlocks the FLASH Program Erase Controller */
    FLASH_Unlock();

    /* Erase */
    if (FLASH_COMPL != FLASH_EraseOnePage(FLASH_WRITE_START_ADDR))
    {
        while(1)
        {
            printf("Flash EraseOnePage Error. Please Deal With This Error Promptly\r\n");
        }
    }

    /* Program */
    for (Counter_Num = 0; Counter_Num < FLASH_PAGE_SIZE; Counter_Num += 4)
    {
        if (FLASH_COMPL != FLASH_ProgramWord(FLASH_WRITE_START_ADDR + Counter_Num, Erase_Data))
        {
            while(1)
            {
                printf("Flash ProgramWord Error. Please Deal With This Error Promptly\r\n");
            }
        }
    }

    /* Locks the FLASH Program Erase Controller */
    FLASH_Lock();

    /* Check */
    for (Counter_Num = 0; Counter_Num < FLASH_PAGE_SIZE; Counter_Num += 4)
    {
        if (Erase_Data != (*(__IO uint32_t*)(FLASH_WRITE_START_ADDR + Counter_Num)))
        {
            printf("Flash Program Test Failed\r\n");
            break;
        }
    }

    printf("Flash Program Test End\r\n");

    while (1)
    {
    }
}

/**
* [url=home.php?mod=space&uid=247401]@brief[/url]  USART_Config.
*/
void USART_Config(void)
{
    GPIO_InitType GPIO_InitStructure;
    USART_InitType USART_InitStructure;

    GPIO_InitStruct(&GPIO_InitStructure);
   
    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_USART1, ENABLE);

    GPIO_InitStructure.Pin        = GPIO_PIN_9;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Alternate  = GPIO_AF4_USART1;
    GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.Pin       = GPIO_PIN_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Input;
    GPIO_InitStructure.GPIO_Alternate  = GPIO_AF4_USART1;
    GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure);

    USART_InitStructure.BaudRate            = 115200;
    USART_InitStructure.WordLength          = USART_WL_8B;
    USART_InitStructure.StopBits            = USART_STPB_1;
    USART_InitStructure.Parity              = USART_PE_NO;
    USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
    USART_InitStructure.Mode                = USART_MODE_RX | USART_MODE_TX;
    USART_Init(USART1, &USART_InitStructure);

    USART_Enable(USART1, ENABLE);
}</font>


因为官方的库函数是有状态返回的,为了测试FLASH擦写成功,代码中加了大量的USART状态打印,方便用户通过串口查看状态,在使用中屏蔽,几个重要的函数:

FLASH_ClockInit()


FLASH时钟的初始化。


FLASH_Unlock();///解锁FLASH



FLASH_Lock();///锁FLASH


这两个函数成对存在,操作FLASH前需解除锁定,完成后需锁定,否则可能因为程序的复位造成数据丢失。

FLASH_EraseOnePage();//擦除一页


FLASH_ProgramWord(uint32_t Address, uint32_t Data);//编程FLASH,一次操作4个字节。


重要的宏定义:

#define FLASH_PAGE_SIZE        ((uint16_t)0x800)


#define FLASH_WRITE_START_ADDR ((uint32_t)0x08010000)


#define FLASH_WRITE_END_ADDR   ((uint32_t)0x08018000)


FLASH_PAGE_SIZE:每页的大小,因为此MCU的一页是2K,所以这里是0x800,如果是1K的改为0x400,


FLASH_WRITE_START_ADDR:写FLASH的首地址,改地址不可随意定义,要对照手册计算每一页的首地址,或者使用PW200读取FLASH后从FLASH映射中获取。



FLASH_WRITE_END_ADDR:页结束地址,对应开始地址,结束地址和开始地址都要选取整页的地址,可以是一页,可以是几页,但不能超出最大地址。


这样在例程中会计算需要写入多少个数据,并把整个页写满。


烧录复位后读取页面如下图所示:




此处可以看到FLASH 是以小端的形式写入,低位会写到FLASH的最前面,高位放在后面。注意:如果想保留该次数据,再下次烧写新的代码时不擦除该数据,在KEIL里面要设置只擦除所选:



5. 取出FLASH的数据很简单,直接对地址进行寻址就可:(*(__IO uint32_t*)address。


6. 单片机的FLASH读写操作很简单,每家都有相应的库函数开放给用户使用,重点在于用户要清晰的了解单片机的FLASH结构,在使用时要正确的了解FLASH的每页地址,错误的写地址会造成不可估计的错误,本人在之前的项目中因不了解结构,直接套用函数,导致程序总是异常奔溃。



FLASH擦写速度测试:

在固件库的加持下,FLASH擦写异常方便,但是操作过FLASH的都知道,FLASH的操作十分耗时且占用总线,在程序中需要清晰的了解FLASH操作时的耗时问题,才可方便用户在编辑代码时预留出足够的时间用于FLASH的擦写。这里就测试一下N32G435的FLASH擦写速度。


首先将官方的demo中所有串口输出和状态检测的代码全部清除,只保留和FLASH相关的代码,同时初始化两个GPIO用于方便示波器捕捉时间。


PB0用于监测整个FLASH擦写操作的时间,PB1用于监测写FLASH的时间。GPIO的电平的操作均采用寄存器方式实现。

<font size="4" face="黑体">    while (1)

    {

GPIOB->PBSC = GPIO_PIN_0;

FLASH_Unlock();

/* Erase */

FLASH_EraseOnePage(FLASH_WRITE_START_ADDR);

GPIOB->PBSC = GPIO_PIN_1;

/* Program */

for (Counter_Num = 0; Counter_Num < FLASH_PAGE_SIZE; Counter_Num += 4)

{

FLASH_ProgramWord(FLASH_WRITE_START_ADDR + Counter_Num, Erase_Data);

}

/* Locks the FLASH Program Erase Controller */

GPIOB->PBC = GPIO_PIN_1;

FLASH_Lock();

GPIOB->PBC = GPIO_PIN_0;

Delay(50000);

}</font>

通过示波器查看波形:

上述过程皆为一页FLASH(2K)的擦写。通过左一可见一个完整的擦写过程,包括解锁,擦除一页,写一页,锁定四个步骤所用时间为54.6ms,只写一页所用时间为53ms。为了和手册对应,我又单独测试了只擦除一页所用的时间:

擦除的速度挺快的,只有834us(相对于写操作来说)。然后我们再找到官方的手册:

手册描述32位编程的时间典型值是100us,如果换算成一页,应该是512*100us=51.2ms。
考虑代码均采用库函数和测试环境不同,所测时间与手册还算吻合。
一页的擦除时间典型值是2ms。实际测试快了一倍还多,这个还是可以的。总体说来,手册的数据参考价值很高,后续有FLASH操作的用户了根据手册进行估算大概时间。
这次的**就到这里,因本人后续的项目可能会使用国民技术的MCU,以后的相关开发经验会逐步上传,以此共勉,如果有需要其他外设的开发测试的朋友可留言,小密有时间会给大家更新,希望坛友一起努力,共创国产单片机的开发生态。
如果觉得帖子还不错,麻烦大家点击头像给个关注呗。感谢大家!

   

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 200.00 元 2022-03-28
理由:恭喜通过原创奖文章审核!请多多加油哦!

沙发
Gavin3389| | 2022-3-29 08:58 | 只看该作者
技术密~

使用特权

评论回复
板凳
呐咯密密|  楼主 | 2022-3-29 09:01 | 只看该作者

大佬谬赞

使用特权

评论回复
地板
九型人格| | 2022-3-30 16:41 | 只看该作者
我经常节衣缩食。技术大拿。666

使用特权

评论回复
5
九型人格| | 2022-3-30 16:45 | 只看该作者
作为一款性价比较高的国产MCU,敷衍做法显然不适合,这真是一篇好**。

使用特权

评论回复
6
sy12138| | 2022-9-7 15:06 | 只看该作者
顶一下大佬的帖子

使用特权

评论回复
发新帖 本帖赏金 200.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

504

主题

3896

帖子

47

粉丝