Flash

实现代码:

https://gitee.com/synwit-co-ltd/EEPROM_Emulation 

工作原理

Flash 和 EEPROM 的主要区别在于可擦单元大小,EEPROM 可以字节擦写,而 Flash 只能块(扇区)擦除。这意味着如果在一个 Flash 扇区上存储多个参数时,扇区的擦除次数是该扇区上所有存储参数修改次数的总和,因此用作参数存储器时,Flash 相比 EEPROM 更容易写坏。

解决这个问题的方法是,不再将参数存储在 Flash 的固定地址,而是给每个参数分配一个唯一的数字用作虚拟地址,将参数写入 Flash 时同时将虚拟地址写入 Flash。

举例说明,执行如下的伪代码,将产生下图所示的存储效果:

1.jpg

2.jpg

注:上图中空格表示未写入,其值实为 0xFF

Flash 存储器被划分为 2 个字一组的存储单元,执行 EE_Write 时,将参数的值写入存储单元的前一个字,参数的虚拟地址写入存储单元的后一个字。

3.png

读取参数时,不再去固定的 Flash 地址读取,而是从存储页的尾部向前逐个存储单元读取,检查该单元中存储的虚拟地址是否等于 EE_Read 调用中指定的虚拟地址 EE_ADDR_Speed。若不等,则继续向前搜索;若相等,则说明找到了参数 Speed 在存储器中存储的最新设定值。

由于参数不必存储在 Flash 中的固定地址,因此不必每次修改参数的值时都擦除 Flash,而是只要 Flash 扇区未写满,就可以直接在后续空白地址写入参数的新值。这样将大幅减少 Flash 的擦除次数,降低 Flash 被写坏的风险。

页拷贝

当存储页写满时,可以将所有所有参数读入 RAM,擦除存储页,然后再将所有参数写入 Flash中。但若在此过程中发生掉电,就会导致所有参数丢失。

为解决这个问题,可以使用两个存储页,存储页的第一个存储单元存储该页的状态。如下图所示第一个存储页已满:

4.jpg

若此时执行 EE_Write(EE_ADDR_Speed, 0x1900),可将第二个存储页状态改为 Receiving,表示正在从 Active 页拷贝数据。然后将本次要写的数据写入。

5.jpg

接下来,搜索 Active 页中的所有存储参数,将每个参数的最新值拷贝到 Receiving 页中。等所有参数拷贝完成后,将第一个页擦除(页状态自动变为 Erased),最后将第二个页状态改为 Active。

6.jpg

后面,所有的参数读写操作均在第二页上即可。

这样操作,可以保证即使在过程中发生突然掉电,也不会损坏存储的参数值:

l 若在将第二页标记为 Receiving 时突然掉电,则只是丢失参数 Speed 的最新设定值,其他参数不受影响,读取参数 Speed 会读取到它的前一个设定值。

ll 若在第二页标记为 Receiving 之后,从 Active 页向 Receiving 页拷贝参数的过程中突然掉电,则下次上电后会发现同时存在 Active 页和 Receiving 页,重新执行参数拷贝即可。

lll 若在参数拷贝完,Active 页擦除后,Receiving 页还未被修改为 Active 时突然掉电,则下次上电后会发现有一个 Receiving 页,但没有 Active 页,只需要将 Receiving 页状态改为 Active 即可。

来源:华芯微特32位MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 43

CKS32F4xx系列芯片内部嵌入一个FLASH,若FLASH存储了用户的应用程序后仍留有空间,剩余空间可被当作EEPROM使用,这比访问外部FLASH速度优势更为明显。在本章节中,将会向大家简要介绍内部FLASH,并通过一个例程来演示FLASH模拟EEPROM的操作。

内部FLASH简介

FLASH结合了ROM和RAM的优点,不仅具备了EEPROM的可擦写性能,还类似于失性随机存储器(NVRAM),不会因断电而丢失数据,且能以较快的速度读取数据。对于CKS32F4xx系列的FLASH接口,它负责管理CPU通过AHB I-Code和D-Code总线对FLASH进行访问,这个接口可以执行擦除和编程操作,同时实施了一些读写保护机制,以确保数据的安全性。此外,FLASH接口还通过指令预取和缓存机制来提高代码执行的速度。下图为系统架构内的FLASH接口连接图:

1.png

针对不同型号的CKS32F4xx系列,其FLASH容量在128K至1024K字节之间。本章中以开发板上搭载的CKS32F407VGT6为例,它的FLASH容量为1024K字节,下图是CKS32F40xx/41xx的闪存模块组织图:

2.png

由上图可知,CKS32F4的存储区主要是由主存储器、系统存储器、OTP区域和选项字节构成,各存储区简述如下:

①主存储器:该部分用来存放代码和数据常数,分为12个大小不同的扇区,主存储器的起始地址是0x08000000;

②系统存储器:主要用来存放CKS32F4的bootloader代码,此代码是出厂的时候就固化在芯片内部了,例如用串口下载程序时的bootloader(ISP下载),它专门用来给主存储器下载代码;

③OTP区域:即一次性可编程区域,一次性的,写完一次,永远不能擦除;

④选项字节:用于配置读保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位,相当于一些寄存器位。

内部FLASH读写操作

FLASH是以字节为最小单位进行操作,每个存储单元存放一个字节。以下是对内部FLASH读写操作的概述:

---内部FLASH指定地址读出数据

例如,从地址addr读取一个字节,可通过语句data = *(vu8*)addr来实现,若是读取一个半字或一个字,只将上面的vu8改vu16或vu32即可。

---内部FLASH的写入过程

(1)解锁:CKS32F4xx系列复位后,FLASH编程操作被写保护,只有向FLASH_KEYR寄存器写入特定序列0x45670123和0xCDEF89AB,方可解除写保护,进而操作其它相关寄存器;

(2)设置闪存操作位数:CKS32F4xx系列电源电压会影响数据的最大操作位数,由于本例中使用的开发板电压为3.3V,所以根据下述配置表,必须将FLASH_CR的PSIZE字段配置为10b,即32位并行位数,这也决定后续的擦除或编程操作必须以32位为基础进行。

3.png

(3)擦除扇区:在写入数据前,必须先擦除存储区域,CKS32标准库提供了扇区擦除和批量擦除的指令,批量擦除指令仅针对主存储区。

(4)写入数据:擦除完毕后才可写入数据,CKS32F4xx系列标准库提供了字节、半字、字和双字写入函数供用户调用,封装了对寄存器FLASH_CR和FLASH_SR的操作,具体步骤可查阅CKS32F4xx的嵌入式FLASH章节。

---其它注意事项:

①CPU时钟频率(HCLK)不能低于1MHz,不能在FLASH操作期间进行器件复位;

②FLASH执行写入或擦除操作期间,不能进行读操作,否则会导致总线阻塞。因此,FLASH写操作时,有必要写FLASH_DataCacheCmd(DISABLE),来禁止数据缓存,写完后再打开;

③写入地址必须是用户代码区以外的地址,不能覆盖用户代码,否则程序会出错,可以查看map文件,选择合适的存储空间;

④由于数据是以32bit写入的,占用4个地址位,所以写入地址必须是是4的倍数。

采用内部FLASH模拟EEPROM实验

CKS32F4xx系列有关FLASH的函数分布在文件cks32f4xx_flash.c以及cks32F4xx_flash.h中,本例中通过cks_flash_test函数演示内部FLASH的读写,该函数的执行过程如下:

① 调用FLASH_Unlock 解锁;

② 调用FLASH_DataCacheCmd禁止数据缓存;

③ 调用FLASH_EraseSector擦除待写入地址所在扇区,可由上述闪存模块组织图得知,擦除时是按字为单位进行操作,并等待FLASH操作结束进入下一步;

④ 本例中是调用FLASH_ProgramWord函数向指定地址写入指定数据,并等待FLASH操作结束进入下一步,CKS32F4xx系列官方库提供FLASH_ProgramHalfWord、FLASH_ProgramByte函数,用户可根据需求选用;

⑤ 调用FLASH_DataCacheCmd开启数据缓存;

⑥ 调用FLASH_Lock 上锁。

代码如下:

int main(void)
{
    cks_flash_test();
    while (1)
    {
    }
}
void cks_flash_test(void)
{
    FLASH_Unlock();
    FLASH_DataCacheCmd(DISABLE);
    if(FLASH_EraseSector(FLASH_Sector_1, VoltageRange_3) != FLASH_COMPLETE)
    {
        return;
    }
    if(FLASH_ProgramWord(0x08004000, 0x44332211) != FLASH_COMPLETE)
    {
        return;
    }
    FLASH_DataCacheCmd(ENABLE);
    FLASH_Lock();
}

4.png

图1

5.png

图2

主函数对cks_flash_test函数调用,cks_flash_test函数实现对指定地址0x08004000进行擦除,写入操作。通过断点调试,如图1当程序运行至FLASH_EraseSector后,0x08004000起始处的四个字节先被擦除成0xFF;如图2,当程序运行运行至FLASH_ProgramWord之后,执行向0x08004000写入0x44332211后,0x08004000起始处数据变成0x11、0x22、0x33、0x44。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 44

BD网盘链接:

https://pan.baidu.com/s/1dmtMWcK1TII-vutsS8X0Og?pwd=5wwy 
提取码:5wwy

概述

CW32L052内部集成了64KB嵌入式FLASH供用户使用,可用来存储应用程序和用户数据。

芯片支持对 FLASH 存储器的读、擦除和写操作,支持擦写保护和读保护。

芯片内置 FLASH 编程所需的高压 BOOST 电路,无须额外提供编程电压。

FLASH存储器组织

  • 总容量64KB,分页管理

  • 每页 512 字节

  • 共 128 页

FLASH存储器保护

FLASH 存储器具有擦写保护和读保护功能。

  • 擦写保护

包括锁定页擦写保护和PC 地址页擦写保护,处于保护状态的页面不能被擦写,可避免 FLASH 内容被意外改写。

  • 读保护

  • 以整片 FLASH 为保护对象,不支持单页保护,可避免用户代码被非法读取。

FLASH存储器操作

FLASH 存储器操作包括:读操作、擦除、写(编程)操作。

页擦除

FLASH 的页擦除操作的最小单位为 1 页,即 512 字节。页擦除操作完成后,该页所有地址空间的数据内容均为 0xFF。

如果对未解锁的 FLASH 页面进行页擦除操作,或者对*正在运行的程序[^1]*进行擦除操作,会操作失败,产生错误中断标志。

CW32L052 内部 FLASH 存储器被划分为 128 页,每 8 页对应擦写锁定寄存器的1 个锁定位。擦写锁定寄存器的各位域与 FLASH 锁定页面的对应关系如下表所示:

1.png

写操作

基于嵌入式 FLASH 的特性,写操作只能将 FLASH 存储器中位数据由‘1’改写为‘0’,不能由‘0’改写为‘1’, 因此在写数据之前先要对对应地址所在页进行擦除操作。

基于以上陈述,总结出以下三个原则:

  • 不可对数据位内容为‘0’的地址写入

  • 不可对锁定区域内的地址写入

  • 不可对 PC(程序指针)所在的页的地址写入

读操作

CW32L052 对 FLASH 的读操作支持 3 种不同位宽,可采用直接访问绝对地址方式读取,读取的数据位宽必 须和对应地址边界对齐。

核心代码

//单片机头文件
#include "main.h"
//硬件驱动
#include "gpio.h"
#include "delay.h"

//子程序
void LCD_Configuration(void);       //段式LCD配置函数
void LCD_Display(uint16_t dispdata);     //段式LCD显示函数
uint8_t FLASH_Erase(void);         //FLASH页擦除函数
uint8_t FLASH_Write(uint8_t *ByteData,uint16_t amount); //FLASH写操作函数

int main(void)
{
 int i;
 int temp8;
 uint8_t cnt=0;
 uint8_t WriteBuf[256];
 
    LED_Init();    //初始化程序运行情况指示灯
 LCD_Configuration();    //配置LCD液晶显示屏
 
 FLASH_Erase();          //页擦除操作
 for(i=0;i<256;i++)      //验证是否擦除成功
 {
  temp8=*((volatile uint8_t*)(512*127+i));
  if(temp8!=0xff)
  {
   while(1)
   {
    LED2_ON();       //LED2闪烁
    Delay_ms(300);
    LED2_OFF();
    Delay_ms(300);
   }
  }
 }
 
 for(i=0;i<256;i++)      //准备写入FLASH存储器的数据
 {
  WriteBuf[i]=i;
 }
 FLASH_Write(WriteBuf,256); //写操作
 for(i=0;i<255;i++)           //验证是否写入正确
 {
  temp8=*((volatile uint8_t*)(512*127+i));
  if(temp8!=i)
  {
   while(1)
   {
    LED1_ON();     //LED1、LED2同时闪烁指示写入失败
    LED2_ON();
    Delay_ms(300);
    LED1_OFF();
    LED2_OFF();
    Delay_ms(300);
   }
  }
 }
 
 LED1_ON();       //指示擦除、读、写均成功
 LED2_ON();
    while(1)
    {
  LCD_Display(*((volatile uint8_t*)(512*127+cnt)));  //LCD上依次显示写入的数据
  Delay_ms(500);
  cnt++;
    }
}

uint8_t FLASH_Erase(void)    //页擦除
{
 int flag=1;
 
 FLASH_UnlockPages(512*127,512*127);
 flag=FLASH_ErasePages(512*127,512*127);
 FLASH_LockAllPages();
 if(flag!=0)
 {
  while(1)
  {
   LED1_ON();
   Delay_ms(300);
   LED1_OFF();
   Delay_ms(300);
  }
 }
 
 return 0;
}

uint8_t FLASH_Write(uint8_t *ByteData,uint16_t amount)  //写操作
{
 int flag=1;
 
 FLASH_UnlockPages(512*127,512*127);
 flag=FLASH_WriteBytes(512*127,ByteData,amount);
 FLASH_LockAllPages();
 if(flag!=0)
 {
  while(1)
  {
   LED2_ON();
   Delay_ms(300);
   LED2_OFF();
   Delay_ms(300);
  }
 }
 
 return 0;
}

void LCD_Configuration(void)      //段式LCD配置
{
    __RCC_LCD_CLK_ENABLE();
 RCC_LSI_Enable();
 
    LCD_InitTypeDef LCD_InitStruct = {0};

    LCD_InitStruct.LCD_Bias = LCD_Bias_1_3;
    LCD_InitStruct.LCD_ClockSource = LCD_CLOCK_SOURCE_LSI;
    LCD_InitStruct.LCD_Duty = LCD_Duty_1_4;
    LCD_InitStruct.LCD_ScanFreq = LCD_SCAN_FREQ_256HZ;
    LCD_InitStruct.LCD_VoltageSource = LCD_VoltageSource_Internal;

    LCD_Init(&LCD_InitStruct); 
 LCD_COMConfig(LCD_COM0 | LCD_COM1 | LCD_COM2 | LCD_COM3, ENABLE);
    LCD_SEG0to23Config(LCD_SEG0|LCD_SEG1|LCD_SEG2|LCD_SEG3|LCD_SEG4|LCD_SEG5|LCD_SEG6|LCD_SEG7, ENABLE);
   
 LCD_Cmd(ENABLE);
}

void LCD_Display(uint16_t dispdata)   //LCD显示
{
 uint16_t DisBuf[10]={NUM0,NUM1,NUM2,NUM3,NUM4,NUM5,NUM6,NUM7,NUM8,NUM9};
 
 LCD_Write(LCD_RAMRegister_0,0x00000000);
 LCD_Write(LCD_RAMRegister_1,0x00000000);
 
 if(dispdata<10)
  LCD_Write(LCD_RAMRegister_0,DisBuf[dispdata]);
 else if(dispdata<100)
  LCD_Write(LCD_RAMRegister_0,DisBuf[dispdata/10]|DisBuf[dispdata%10]<<16);
 else if(dispdata<1000)
 {
  LCD_Write(LCD_RAMRegister_0,DisBuf[dispdata/100]|DisBuf[dispdata/10%10]<<16);
  LCD_Write(LCD_RAMRegister_1,DisBuf[dispdata%10]);
 }
 else
 {
  LCD_Write(LCD_RAMRegister_0,0xffffffff);
  LCD_Write(LCD_RAMRegister_1,0xffffffff);
 }
 
}

视频演示

2.gif

补充

FLASH存储器和EEPROM存储器对比

一般性的总结: 

3.png

使用场景侧重:

  • EEPROM:频繁的擦写操作,如存储计数器、传感器数据等

  • FLASH:大容量、高速读写,如存储程序代码和固件等

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 6

01、问题现象与分析

客户项目中使用的 MCU 型号是 STM32G0B1, 他们反馈在代码中尝试擦除并编程 FLASH时, 发现 FLASH 的状态寄存器显示编程错误(如图 1 所示). 问题是当前代码还没有开始擦除和编程, 怎么就有了编程错误标志了呢 ? 如果不将此错误标志清除, 后续的编程操作无法继续.客户对于每次想要操作 FLASH 之前这个清除动作既感觉多余也感觉别扭, 且还不得不做, 且做了也不知对整个产品的稳定性会有什么样的影响 ?

1.jpg

图1.Flash 编程错误标志

访问客户时, 客户也曾私下里反馈, 经常在网络论坛上获取类似这种问题, 客户怀疑是不是STM32 本身就存在某些未曾公开的问题 ? 其实, STM32 的所有问题都已公开在勘误手册中, 如果客户的问题在勘误手册中没找到, 那么极有可能是自己代码哪里出了问题。 

问题分析及测试 

查看客户的工程, 由于客户的工程相当庞大, 各个模块和任务相互交叉, 一时半刻是很难从如此庞大的工程中找出问题, 更麻烦地是, 客户的电脑是有加密系统的, 导致在工程内查找任何字符和函数都相当痛苦. 好在是, 问题能够稳定地复现。 

于是尽量精简客户的代码, 将所有不相关的任务,模块统统移除掉, 并且保持问题能够重现. 并使其能够在 ST 官方的 NUCLEO 板上重现. 这样一来, 就完全可以脱离客户原来的硬件环境进行测试. 由于客户的环境非常不利于查找问题, 效率事倍功半. 于是, 将客户的最小化工程提取出来(与软件泄密无关), 并拿到办公室进行测试. 很快就找到了问题所在。 

原来客户的工程中有用到两个串口, 串口 2 和串口 3, 都是使用的 DMA 模式。客户不同的软件人员负责不同的模块, 最终在整合代码时, 串口 2 并没有使用, 所以串口 2 对应的初始化代码是删除掉的, 但由于串口 2 和串口 3 的 DMA 中断是共用一条中断线, 是相同的中断入口, 在中断处理时,串口 2 的 DMA 处理函数和串口 3 的处理函数都会一起处理. 问题就出在串口 2 的 DMA 中断处理并没有移除 。如 stm32g0xx_it.c 文件 :

2.jpg

如上图,DMA 的通道 4~7 以及 DAM2 的通道 1~5 都是共用一个中断入口的。在这个中断处理函数内, 串口 2 并没有使用到, 但其对应处理代码由于疏忽仍然保留了下来。句柄hdma_usart2_rx, 和 hdma_usart2_tx 内的数据成员很多都是不定内容或为 0. 当代码运行到函数内部, 如下图所示出问题的代码行:

3.jpg

如上面代码所示, 代码运行到上图 866 行代码 hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << (hdma->ChannelIndex & 0x1CU));时, 实际上是给错误地址 0x0800 4109 赋值了, 此地址是内部 FLASH 地址, 这样相当于直接写 FLASH, 肯定会出错, 这也是为什么FLASH->SR.PGSERR 置位的原因. 我们都知道, 写内部 FLASH, 必须先擦除, 才可以写入, 而且写也是调用对应的 HAL API 函数, 且还需要先写 key 解锁 FLASH 等操作, 有一套写操作流程. 并不是直接用赋值语句, 这样操作出现问题一点也不奇怪。

当在中断中将串口 2 的 DMA 对应处理函数移除掉后功能就恢复正常, 这也佐证了结论的准确性。 

另外, 客户反映, 这个最小化工程, 相同的代码, 使用 IAR 时测试会出错, 但使用 KEIL 时并没有出错. 这个很奇怪. 这就引出的另外一个问题. 相同代码, 不同编译器运行结果不一致的问题。于是继续找原因, 对比 IAR 和 KEIL 的调试情况, 发现当代码运行到图 2 中 857 行代码 if 语句时其判断结果不相同. IAR 调试环境会进入到 if 语句内容, 从而导致错误的给内部 FLASH 地址赋值, 进行导致问题. 而 KEIL 调试环境并没有进入到 if 语句内部, 因此并没有触发问题. 那么为什么if 语句的判断结果不一样呢?

为了方便并避免不同编译器对长语句的执行顺序的差异, 将这个 if 长语句拆开:

4.jpg

如上红色代码, 用它替换原来的 if 判断语句. 结果发现 tmp1 在 IAR 和 KEIL 两个编译器环境中的值是一样的, 但是 tmp2 的值却不一样, 正是由于 tmp2 值的不一样, 导致 if 语句的最终判断结果不同。进一步发现, tmp2 的值主要是由于 flag_it 的值在两种编译器环境不一样所致。

5.jpg

如上 IAR 编译器环境, flag_it 的值为 0x2000 10f8。

6.jpg

如上 KEIL 编译器环境, flag_it 的值却是 0x2000 14F0。 

那么 flag_it 的值又是如何来的呢? 从如下代码:

7.jpg

如上所示, flag_it 的值来自 hdma->DmaBaseAddress->ISR, 原来是 DMA 相关 ISR 寄存器的值, 但实际调试如下:

8.jpg

如上 IAR 调试环境下, 出错时, hdma->DmaBaseAddress 实际指向的是地址 0, 其成员 ISR为其第一个成员, 实际也就是地址 0 上的数据. 我们都知道, 在默认情况下, MCU 的地址 0 默认是映射到内部 FLASH 的首地址 0x0800 0000 上的, 而此地址一般保存的是栈顶.。也就是说, IAR 编译环境下, 地址 0 指向栈顶地址 0x2000 10f8。 

对应地, 在 KEIL 调试环境下:

9.jpg

如上 KEIL 调试环境, hdma->DmaBaseAddress 同样地实际指向的是地址 0, 而地址 0 的上对应的数据为栈顶地址: 0x2000 14F0。 


也就是说, 在不同的 编译器 IAR 和 KEIL 环境下, 地址 0 指向栈顶地址是未必相同的, 进而导致两种编译环境下运行相同的代码结果不一样。 

我们知道, 通常栈地址是由编译器来指定的, 在默认情况下, IAR 和 KEIL 都会将栈放在内存的所有静态变量之后来分配. 其具体的分配地址这两个编译器都会默认按自动填充地方式来. 实际分配的地址具有不确定性, 当然, 我们也可以通过链接配置文件(IAR 的.icf 文件, KEIL 的.sct 文件)来将栈地址指定某一固定地址, 但我们通常不会这么做, 且完全没有必要.

02、小结

至此,将问题稍作小结。给变量 flag_it 实际赋值栈顶地址, 不同的编译器环境下, 此栈顶地址的不一致导致变量 flag_it 的值不一致, 进而导致 if 语句的判断结果不同, 最终导致 IAR 和 KEIL 这两个编译器环境下运行相同代码而结果不一样的情形。

03、后记

有时会听到某某客户反馈说, 在网络上看到 STM32 某款 MCU 存在某某问题, 然后问是不是 ST 故意隐瞒 ? 

不存在故意隐瞒的说法,芯片终究是要经过终端验证的。 

正常来讲, 任何芯片存在应用局限是正常的。对于 ST,一方面会正式地将所有已知 bug或应用局限放入到勘误手册中公示, 大家需要注意使用最新版勘误手册;另一方面,对于 ST 量产芯片,因本身缺陷导致的问题的概率非常低。事实上,绝大多数问题都来自我们自身的应用,遇到问题若简单的基于芯片品质来回猜疑非常不利于开发者静下心来查找问题原因。其实,面对问题时,我们很多人欠缺的并不是多么高深的水平,而是一颗冷静、自信并富有条理的心。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 48

概 述

在MCU的使用中,经常遇到需要存储参数或掉电保持数据等功能。其中,Flash和EEPROM是常见的非易失性存储器,都可以做到设备掉电重启后,数据还会保留。但二者有明显的区别:EEPROM可以被编程和电擦除,而且大多数的EEPROM可以被编程和电擦除,大多数串行EEPROM允许逐字节程序或擦除操作。与EEPROM相比,闪存具有更高的密度,这允许在芯片上实现更大的内存阵列(扇区)。通过对每个单元施加时间控制的电压来执行闪存擦除和写入周期。典型的Flash写时间是50µs/8位字;然而,EEPROM通常需要5到10 ms。EEPROM不需要进行页面(扇区)擦除操作,可以擦除一个需要指定时间的特定字节。与EEPROM相比,Flash具有更高的密度和更低的价格。

先楫产品可以外接大容量Flash芯片,支持可达256Mbyte程序或数据存储;部分产品如HPM6754、HPM6364、HPM6284内置4Mbyte Flash,HPM53XX系列全系支持1Mbyte Flash。在使用Flash模拟EEPROM时,最重要的挑战是满足Flash程序/擦除持久性和数据保留方面的可靠性目标。

其次,在应用程序的控制下,需要满足更新和读取数据的实时应用要求。请注意,在Flash擦写期间,它不能执行Flash应用程序,因为在此时间内不能执行在Flash中的程序,通常程序是将Flash擦写程序拷贝到RAM中执行。先楫半导体为了方便客户程序应用,已经将Flash驱动程序集成到ROM中,减少了系统对RAM的需求,用户使用时更加灵活方便。

由于Flash的块擦除要求,必须完全为模拟的EEPROM保留至少一个Flash扇区。例如,一个4K x 8bit大小的Flash扇区可以分为16页,每个页的大小为256 x 8bit。这使得每个页面相当于一个256 x 8字节的EEPROM。要保存的数据首先写入RAM中的缓冲区中,每个部分RAM可以模拟EEPROM的存储的数据。

如何实现?

根据Flash扇区和模拟的EEPROM的大小,划分相应的Flash和RAM空间。

功能:

• 读取片内或片外flash信息。

• 批量读取flash中数据到RAM缓存中。

• 用户可以自由读写RAM缓存中数据。

• 用户可以将RAM缓存中数据写入flash。

• 用户可以根据自己的需要定制存储空间大小和存储地址。

由于先楫半导体MCU已经集成了Flash驱动,用户可以不再需要把精力放到繁琐的底层Flash驱动部分。

为了实现此功能,需要8个函数来进行编程、读取和擦除,3个宏定义确定存储空间和位置。

/* Sector size */ 
#define SECTOR_SIZE (uint32_t) (0x1000)
 /* Sectors 0 and 1 base and end addresses */ 
#define Flash_base 0x80000000L 
#define SECTOR1_BASE_ADDRESS (Flash_base+0x3FE000) 
#define SECTOR1_END_ADDRESS  (SECTOR1_BASE_ADDRESS+SECTOR_SIZE*2-1)

其中,SECTOR_SIZE定义了flash扇区大小,单位是byte。若不确定flash扇区大小可以在Initial_EEProm函数中获取flash信息。Flash_base定义flash起始地址,具体可以参考user guider中系统内存映射 System Memory Map地址。SECTOR1_BASE_ADDRESS和SECTOR1_END_ADDRESS为数据存放起始地址,SECTOR1_BASE_ADDRESS必须是特定扇区起始地址。

ATTR_PLACE_AT_WITH_ALIGNMENT(".ahb_sram",8) uint8_t EEPROM_data[SECTOR1_END_ADDRESS-SECTOR1_BASE_ADDRESS+1];

Flash模拟EEPROM时需在RAM中开辟缓存用于常态数据读写,开辟数据时应注意RAM区数据应放到ahb_sram或noncacheable区域。

if defined(FLASH_XIP) && FLASH_XIP 
ATTR_RAMFUNC hpm_stat_t Initial_EEProm(void)
#else hpm_stat_t Initial_EEProm(void) 
#endif 
{ 
    xpi_nor_config_option_t option; 
    option.header.U = BOARD_APP_XPI_NOR_CFG_OPT_HDR; 
    option.option0.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT0; 
    option.option1.U = BOARD_APP_XPI_NOR_CFG_OPT_OPT1; 
    hpm_stat_t status = rom_xpi_nor_auto_config(HPM_XPI0, &s_xpi_nor_config, &option); 
    if (status != status_success) 
    { 
        return status; 
    } 
    rom_xpi_nor_get_property(HPM_XPI0, &s_xpi_nor_config, xpi_nor_property_total_size, &flash_size); 
    rom_xpi_nor_get_property(HPM_XPI0, &s_xpi_nor_config, xpi_nor_property_sector_size, &sector_size); 
    rom_xpi_nor_get_property(HPM_XPI0, &s_xpi_nor_config, xpi_nor_property_page_size, &page_size); 
    printf("Flash Size:%dMBytes\nFlash Sector Size:%dKBytes\nFlash Page Size:%dBytes\n", flash_size / 1024U / 1024U, sector_size / 1024U, page_size); 
    EEProm_Flush(); 
} /* End  Initial_EEProm */

通过调用rom_xpi_nor_auto_config()、rom_xpi_nor_get_property()获取flash信息。

/******************************************************************************* * Routine:  EEPromFlush 
* Purpose:  Refresh data from flash to buffer.
 *******************************************************************************/ 
inline void EEProm_Flush(void) 
{ 
     memcpy((void*)EEPROM_data,(const void*)SECTOR1_BASE_ADDRESS,(SECTOR1_END_ADDRESS-SECTOR1_BASE_ADDRESS)); 
} /* End EEProm_Flush */

从flash中读取数据无需单独调用API函数,直接寻址读取效率更高,文中通过memcpy()函数直接从flash中读取数据到RAM缓存中,后面读写参数直接读写RAM缓存即可。

如果需要将参数写入flash中,需将整块flash擦写,由于数据已经存在RAM缓存,不会存在flash擦写时数据丢失的问题。

/******************************************************************************* * Routine:  writeEEProm_withflush 
* Purpose:  Writes variable to EEPROM and flush flash later. 
* Input  :  none 
* Output:    None. 
* Return:   Returns 0 
*******************************************************************************/ 
#if defined(FLASH_XIP) && FLASH_XIP 
ATTR_RAMFUNC hpm_stat_t writeEEProm_withflush(uint16_t index, uint8_t *data, uint16_t size) 
#else 
hpm_stat_t writeEEProm_withflush(uint16_t index, uint8_t *data, uint16_t size) 
#endif 
{ 
    hpm_stat_t status; 
    if(flash_size==0) return status_fail; 
    memcpy((void*)&EEPROM_data[index],(const void*)data,size); 
    status = rom_xpi_nor_erase(HPM_XPI0, xpi_xfer_channel_auto, &s_xpi_nor_config, SECTOR1_BASE_ADDRESS-Flash_base, SECTOR1_END_ADDRESS-SECTOR1_BASE_ADDRESS); 
    if (status != status_success) 
    { 
        return status; 
    } 
    status = rom_xpi_nor_program(HPM_XPI0, xpi_xfer_channel_auto, &s_xpi_nor_config, (const uint32_t *)EEPROM_data, SECTOR1_BASE_ADDRESS-Flash_base, SECTOR1_END_ADDRESS-SECTOR1_BASE_ADDRESS); 
    if (status != status_success) 
    { 
        return status; 
    } 
}

考虑到flash擦写期间不能读取flash,flash擦写函数需放置在RAM执行的程序存储空间。先楫SDK中已经定义好了ram运行区域,并在HPM_COMMON.H文件中将函数和数字放置属性重新封装,通过ATTR_RAMFUNC等效定义__attribute__((section(“.fast”)))。为确保擦写flash期间不会被中断打断从而调用其他flash中的程序,需在运行中关闭中断。

//disable all interrupt before programming flash 
CSR_reg = disable_global_irq(CSR_MSTATUS_MIE_MASK); 
disable_global_irq(CSR_MSTATUS_SIE_MASK); 
disable_global_irq(CSR_MSTATUS_UIE_MASK); 
writeEEProm_withflush(0,(uint8_t*)s_write_buf,0x1000);//update eeprom with flash 
//restore interrupt 
restore_global_irq(CSR_reg);

小 结

本文首先介绍了基于HPM6000系列芯片如何使用Flash模拟EEPROM存储参数。由于先楫SDK中已经提供了强大的驱动库,用户可以方便地通过Flash存储数据,降低成本和提高使用灵活性。

来源: 先楫半导体HPMicro

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 154

一、概述

在操作相关flash器件的时候,需要先发指令再读数据,或者先发指令再发地址再发dummy再读相关数据。而先楫的SPI控制器中,SPI传输包括了命令、地址和数据字段,提供了专用的寄存器来存储这些字段,不需要开发者自行去填充。本文使用hpm6200evk开发板,flash器件是华邦的W25Q64JV。使用hpm_sdk进行开发。

SPI四线模式,统称也就QSPI。

本文是作者在使用先楫的SPI调试flash器件的心得,仅仅作为参考。

二、开发流程

(一)外设引脚初始化

需要初始化下SPI时钟,由于SDK使用的是单线常规模式的SPI,所以引脚上我们还需要初始化IO2和IO3两个引脚。

1.png

2.png

(二)SPI模式初始化

华邦的W25Q64JV使用的SPI模式是mode0或者mode3。这里使用mode0。可以使用sdk的api接口spi_format_init进行初始化

3.png

4.png

(三)SPI频率

先楫的SPI SCLK可以达到80M,这里由于是杜邦线接的flash模块,波形会存在失真,使用该flash可以达到50M的QSPI速度。

使用SDK的spi_master_timing_init api接口进行SPI频率调整。

(四)指令操作(单线模式SPI操作)

1. 华邦相关flash都会有手册,这里使用了SDK的spi_transfer api接口封装了一个指令操作的API。下面根据这个api配合flash器件的手册命令进行说明。

比如使用90命令读取制造商设备ID的时候,使用单线模式,需要先发指令,再发两个dummy,之后就是读取。

5.png

那么使用spi_transfer赋值以下结构体

6.png

cmd_enable:使能命令段传输

addr_enable:使能地址段传输

addr_pahase_fmt:选择是单线模式还是四线模式传输地址

trans_mode:选择的传输模式,比如同时读写,仅写,仅读,写读,读写,写填充读,读填充写等

dmmy_cnt:填充的数量

依靠上述说明,可以使用单线模式,传输模式为填充再读。填充数量为2

7.png

通过波形查看,是没什么问题的。    8.png

从以下可知,单线模式收发指令是没什么问题的。

9.png

(五)读写操作(四线模式QSPI操作)

这里举例读操作,读操作有好几条指令。这里举例使用Fast Read Quad I/O指令,也就是EBh指令。这里需要先发指令,再发地址(地址使用四线模式),再发三个填充dummy,之后再读。

10.png

11.png

擦除指令,可写入指令跟以上类似,这里测试sector0的0页地址。

12.png

对0页的256字节进行1到256赋值,然后再读取,这时候会是0~255 0变化。查看波形可以知道,读写正常。

13.png

三、总结

先楫的spi外设支持常规单线single spi,双线dual spi,四线quad spi。有着专用的操作flash的寄存器。极大方便开发相关flash器件。

以上内容来自先楫开发者的原创分享。

来源:先楫半导体HPMicro

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 143

市面上很多基于单片机的产品都具有在线或离线升级功能,为了防止升级过程出现意外,一般我们都会对Flash程序数据进行校验,常见的就是添加 CRC 校验信息。

本文给大家讲述一下Keil和IAR中计算CRC值的方法。

Flash自检的流程

Flash的自检一般分为启动时自检和程序运行时自检两个阶段。不管是哪种自检,其思路都是:

在程序编译完成后,计算整个程序的CRC值,然后将这个CRC值添加到可执行文件末尾,再将带有CRC校验值的可执行文件烧录到MCU中。程序启动后,由程序中的自检代码重新根据当前Flash内容(不包括预存的CRC校验值)计算一次CRC值,再与之前预先计算并烧录到Flash中的CRC校验值进行比较,如果一致就通过检测。

这两个自检阶段的区别就是:

程序启动自检是一次性对整个实际Flash代码范围计算出最终的CRC值;而运行时的自检,为了不影响其他程序模块的运行,计算CRC的过程是分步进行的,每次计算一部分,分多次计算出最终的CRC值。围绕Flash的自检所发生的问题可以归为两大类,一类是预先计算CRC值时和上电后计算CRC值的Flash范围设置是否一致;第二类就是预先计算CRC时和上电后计算CRC采用的CRC算法是否一致。

如何添加CRC值

下面我们主要介绍如何添加CRC校验值到可执行文件。

1、基于IAR环境

如果你使用IAR,那么添加CRC值的配置相对比较简单。通过配置IAR的CRC计算参数,自动对整个FLASH空间进行CRC计算,并将计算结果放到FLASH的末尾。

1)修改Link文件,指定CRC值的存放位置

在Link文件中增加下面语句,指定checksum在FLASH中的存储位置。

1.jpg

该语句指定将CRC的值放在FLASH的末尾位置,是整个FLASH空间的末尾,不是应用程序的代码末尾。这样,CRC值的位置就是固定的。不会随代码大小而变化。

在自检代码中,可以通过__checksum读取Flash中保存的CRC校验值来与重新计算的CRC值进行比较。

2)配置Checksum页面的参数

在link文件中指定了checksum的存储位置后,还要在工程配置菜单中,配置计算CRC值的范围和参数。见下图:

2.jpg

IAR的checksum页面分为两个部分。

第一部分,也就是红线圈出的部分。定义了FLASH中需要计算CRC的范围和空闲字节填充值。这里注意要留出flash末尾存储CRC值的位置。

剩下的部分,就是对checksum计算参数的设定部分。

Checksum size:选择checksum的大小(字节数)Alignment:指定checksum的对齐方式。一般,处理器不支持非对齐访问时有用,不填的话默认1字节对齐。

Algorithm:选择checksum的算法。

Complement:是否需要进行补码计算。选择“As is”就是不进行补码计算。

Bit order:位输出的顺序。

MSB first,每个字节的高位在前。LSB first,每个字节的低位在前。

Reverse byte order within word: 对于输入数据,在一个字内反转各个字节的顺序。

Initial value: checksum计算的初始化值。

Checksum unit size:选择进行迭代的单元大小,按8-bit,16-bit还是32-bit进行迭代。

3)STM32CRC外设的配置

与上图IARchecksum的配置对应,STM32 CRC外设可以按以下配置:

POLY= 0x4C11DB7(CRC32)

Initial_Crc = 0Xffffffff

输入/输出数据不反转

输入数据:根据实际Flash范围设定,留出CRC校验值的位置

CRC外设初始化及计算代码:

3.jpg

2、基于Keil环境

KEIL没有提供直接生成CRC值的功能,所以需要借助外部的工具计算CRC值,然后添加到可执行文件的末尾。在X-CUBE-CLASSB软件中提供了bat文件,它会利用外部工具Srecord来生成整个Flash的CRC校验码并放在文件末尾。这个工具同样也可以和标准外设库的ClassB库一起用。下面我们就来看看如何在KEIL工程中利用Srecord工具来添加CRC值。

1)安装Srecord工具

下载Srecord 工具(http://srecord.sourceforge.net )。将srec_cat.exe,srec_cmp.exe,srec_info.exe拷贝到C:\SREC(自己新建)目录下。

2)添加crc_gen_keil.bat及crc_load.ini文件到KEIL工程同级目录下

打开X-CUBE-CLASSB软件包中的任意KEIL工程目录,将其中crc_gen_keil.bat及crc_load.ini文件拷贝到自己的KEIL工程目录下。

crc_gen_keil.bat:利用外部工具Srecord来生成整个Flash的CRC校验码并放在文件末尾。

crc_load.ini:这个文件调试时有用,用来配置调试时导入带CRC校验码的HEX,避免对FLASH检测失败导致程序无法正常运行。

4.jpg

这两个文件中的内容也需要根据新工程路径进行修改:

  • 将crc_gen_keil.bat中的TARGET_NAME和TARGET_PATH改成跟新工程一致。否则不能成功的自动生成带CRC校验值的HEX文件。

5.jpg

  • Crc_load.ini文件中的路径和文件也要和实际的一致

6.jpg


3)添加定义CRC校验码存储区域

7.jpg

在分散加载文件中将CHECKSUM指定在代码的末尾。和IAR不同的是,通过在分散加载文件中+last指定checksum的位置,它不是将其固定放在整个flash地址的末尾,而是放在实际代码的末尾。

8.jpg

4)添加外部命令让KEIL在编译结束后,自动生成一个带CRC校验值的HEX文件

9.jpg

定义debug和flash download使用的HEX文件路径,使用带CRC校验值的HEX文件。

10.jpg

11.jpg

5)STM32CRC外设的配置

这里需要注意,从X-CUBE-CLASSB的软件包里拷贝出的crc_gen_keil.bat文件,里面的BYTE_SWAP设为1,也就是它在计算CRC值的时候,输入的数据,在一个字内按字节颠倒顺序。

12.jpg

所以直接用HAL_CRC_Calculate函数进行计算结果是不对的。可以参考下面的代码来初始化及进行计算:

13.jpg

或者,将crc_gen_keil.bat文件,里面的BYTE_SWAP改为0, 就可以直接调用HAL_CRC_Calculate函数进行计算了。

总结

本文介绍了基于IAR及ARM KEIL中如何添加CRC校验值的过程。在X-CUBE-CLASSB软件包中,也都可以找到对应的例程。如果在调试中,遇到FLASH CRC校验出错,也不用急。

可以从计算CRC值的范围设置是否一致和采用的CRC算法是否一致这两个方面去找原因。一定要调试看看代码实际执行的情况,比如要测试的地址范围和实际代码执行时计算的地址范围是否一样,防止因为coding错误造成检测不通过。

直接来源:strongerHuang

素材来源 | 网络

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 654

相关阅读:

mm32-2nd-bootloader技术白皮书(1)——配置软硬件环境

mm32-2nd-bootloader技术白皮书(2)——QSPI外设简介

mm32-2nd-bootloader技术白皮书(3)——设计实现QSPI Flash的下载算法

mm32-2nd-bootloader技术白皮书(4)——设计实现简单的2nd bootloader

引言

在前文中已经实现了一个能够在 MDK 平台进行下载代码到 QSPI Flash 中的下载算法,以及一个能跳转执行应用程序的 2nd Bootloader,但若想将代码下载到 QSPI Flash上并运行,仍需对所需执行的代码文件进行少量的修改,使其能够在 QSPI Flash 上运行。

修改Linker文件

本文将 MindSDK 的 PLUS-F5270 hello_world 样例工程作为所需执行的文件,可通过 MindSDK 官网 (https://mindsdk.mindmotion.com.cn/) 获取该样例,如图 1 所示。

1.png

图1 通过官网获取 MindSDK PLUS-F5270 hello_world样例工程

打开 hello_world 样例工程,在 Options for Target 选项的Linker页面下,找到 Scatter File 选项,该选项中内容为 Linker 所使用的 Scatter File 文件路径,点击该选项右侧的 Edit ,此时样例工程中会弹出对应的 Scatter File 文件界面,如图 2 所示。

2.png

图2 打开scatter file文件界面

在弹出的 mm32f5277e_flash.scf 文件中,需要根据  QSPI 的存储器映像配置 __ROM_BASE 的数值,QSPI 外设的基础地址为 0x90000000,这个地址值来自于微控制器的用户手册的 “地址映射” 章节,如图 3 所示。因此,__ROM_BASE的数值应该为 0x90000000。

3.png

图3 QSPI 外设的存储器映像编址范围

QSPI Flash 的大小为8MB,其中 8MB = 8 * 1024 *1024 = 8388608,换算为 16 进制是 0x00800000,因此,__ROM_SIZE 的数值应该为 0x00800000。

Scatter File文件的内容修改如下:

...    
/*--------------------- Flash Configuration ----------------------------------
; <h> Flash Configuration
;   <o0> Flash Base Address <0x0-0xFFFFFFFF:8>
;   <o1> Flash Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
 *----------------------------------------------------------------------------*/
#define __ROM_BASE      0x90000000
#define __ROM_SIZE      0x00800000
...

修改源代码

在配置好 Linker 所需的 Scatter File 文件后,需要对将要执行的文件进行检查,去除可能会影响到 QSPI Flash 运行的代码,需要检查的点如下:

  • 检查时钟初始化部分:

    是否存在复位 QSPI 模块的时钟或复位 QSPI 所使用的 GPIO 引脚时钟的情况

    若存在复位 QSPI 模块,则处理器将无法从 QSPI Flash 中读取下一个要执行的指令。

    若存在复位 QSPI 使用引脚的时钟,则运行到引脚时钟复位后,GPIO 复位,引脚不再作为 QSPI 的接口,因此将会影响到 QSPI 与 QSPI Flash 之间的通信。

    在 “hello_world” 工程中,以上代码可能会出现在 board 目录下的 “clock_init.c” 中。

  • 检查引脚初始化部分:

    是否占用 QSPI 所使用的引脚

    若引脚初始化时,占用 QSPI 所需引脚,将会影响与 QSPI Flash 的通信

    在 “hello_world” 工程中,以上代码可能会出现在 board 目录下的 “pin_init.c” 中。

  • 不能出现以间接模式操作 QSPI Flash 的代码

    若执行间接模式操作 QSPI Flash 的代码,会中断直接读模式,而直接读模式是执行存储在 QSPI Flash 的应用程序的基础,因此,执行间接模式操作 QSPI Flash 的代码会使应用程序跑飞。

    在 “hello_world” 工程中,没有执行间接模式操作 QSPI Flash 的代码。

添加下载算法到可执行文件

该可执行样例最终将在 QSPI Flash 中存储并运行,因此,需要添加根据实际所使用的 QSPI Flash芯片而配置的下载算法到样例工程中,以帮助调试器将指定位置的数据写入 Flash。

以配置完成的 PLUS-F5270 hello_world 样例工程为例,打开 Options for Target 的 Debug 页面,选择 Settings 选项进入 Cortex Jlink/JTrace Target Driver Setup 页面的 Flash Download ,选择 Add 选项,进入下载算法选择列表,选择前文中设置好的 MM32F5270 QSPI FlashLoader 下载算法,点击 Add 进行添加,如图 4 所示。

4.png

图4 添加下载算法到可执行文件中

此处需注意,若下载算法的大小大于 Flash Download 中 RAM for Algorithm 的 Size 选项中的大小,会导致在下载时出现 "Cannot Load Flash Programming Algorithm" 的问题,此时需适当将 Size 的大小调大一些,本文中设置 Size 为 0x2000。

验证

配置完成在 QSPI Flash 上运行的可执行文件后,可尝试使用下载算法将整个工程下载到 QSPI Flash 中并使用 2nd Bootloader 执行这个工程。

先将前文中实现运行在片内 Flash 的 2nd Bootloader 下载到 PLUS-F5270 开发板中,再将已经配置好的添加了下载算法的 hello_world 样例工程下载到开发板中的 QSPI Flash 中。下载成功后复位微控制器,通过串口调试器可看到输出字符 "hello_world"。

5.png

图5 使用QSPI Flash的hello_world样例工程运行结果

在调试模式下,可以从 Disassemby 窗口观察到进入main函数后地址处于 0x90000950,属于 QSPI Flash 的范围内,由此可见,整个 hello_world 下载到 QSPI Flash中并且正确运行。

6.png

图6 通过调试查看样例起始地址

至此,已经验证了下载算法可用, 2nd Bootloader 可用,修改后的样例工程也可用。

但仍需验证下中断是否可用,本文修改 hello_world 工程,验证 SysTick_Handler() 能否正确执行。

volatile uint32_t systime = 0u;

int main(void)
{
    uint8_t ch;

    BOARD_Init();

    printf("hello, world\r\n");

    SysTick_Config(CLOCK_SYSTICK_FREQ / 1000u);

    while (1)
    {
        if (systime > 1000)
        {
            systime = 0;
            putchar('*');
        }
    }
}

void SysTick_Handler()
{
    systime++;
}

将修改后的样例工程下载并运行在 QSPI Flash 上,其结果如图 7 所示。

7.png

图7 验证SysTick中断可用的样例结果

至此,也验证了中断也可正确执行。

总结

为了编译可在QSPI Flash上运行的可执行文件,需要作出以下两件事:

修改 Linker 文件,将 ROM 的位置和大小修改为 QSPI Flash 的映射地址和大小

修改源代码中所有可能会影响访问QSPI Flash的代码,例如 GPIO 的配置,时钟的配置等

可以发现编译可在 QSPI Flash上运行的可执行文件并不难,因此可以轻松将应用程序迁移到 QSPI Flash 里。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 29

串行外设接口(SPI)是一种同步串行数据通信接口,常用于 MCU 与外部设备之间进行同步串行通信。CW32L083 内部集成 2 个串行外设 SPI 接口,支持双向全双工、单线半双工和单工通信模式,可配置 MCU 作为 主机或从机,支持多主机通信模式,支持直接内存访问(DMA)。

一、主要功能

• 支持主机模式、从机模式 

• 支持全双工、单线半双工、单工 

• 可选的 4 位到 16 位数据帧宽度 

• 支持收发数据 LSB 或 MSB 在前 

• 可编程时钟极性和时钟相位

• 主机模式下通信速率高达 PCLK/2 

• 从机模式下通信速率高达 PCLK/4 

• 支持多机通信模式 

• 8 个带标志位的中断源 

• 支持直接内存访问 (DMA)

1.功能框图

1.png

SPI 一般通过 4 个引脚与外部设备相连:

• MOSI 主机输出 / 从机输入,用于主机模式下的数据发送和从机模式下的数据接收; 

• MISO 主机输入 / 从机输出,用于主机模式下的数据接收和从机模式下的数据发送; 

• SCK 同步串行时钟,主机时钟输出和从机时钟输入,发送或接收主机同步时钟; 

• CS 从机选择,也用于多机通信时总线冲突检测

CW32L083 支持用户灵活选择 GPIO 作为 SPI 通信引脚,通过 AFR 功能复用实现,具体 SPI 引脚请参考数据手册中的引脚定义。

2.全双工模式

SPI 支持全双工通信模式,在该模式下,主机和从机的移位寄存器通过 MOSI、MISO 两条单向数据线进行连接, 在主机提供的 SCK 时钟信号的控制下同时进行两个方向的数据传输。设置控制寄存器 SPIx_CR1 的 MODE 位域为 0x0,使 SPI 工作于全双工通信模式。

2.png

二、FLASH存储器及电路连接

3.png

W25Q64JVSSIQ为华邦NOR型FLASH,存储容量为8Mbytes,每页256bytes,总共有32768个可编程页,最大一次可编程大小为256bytes。一次擦除大小可以为4K、32K、64K字节(K=1024)或者全擦除。支持标准SPI、Dual SPI、Quad SPI,最大操作时钟为133MHz。

芯片SPI接口时序要求如下:

4.png

由接口时序图可知,空闲电平可高可低,数据在第1个时钟上升沿采样。对应的可以选择用模式0或者模式3和MCU进行对接。

三、实例演示

本实例演示MCU(CW32L083)芯片对外部SPI FLASH(W25Q64JVSSIQ)进行读写访问,SPI接口选择模式0。

1.配置RCC和GPIO

void RCC_Configuration(void)
{
  RCC_HSI_Enable(RCC_HSIOSC_DIV2); //SYSCLK = HSI = 24MHz = HCLK = PCLK
  //外设时钟使能
  RCC_AHBPeriphClk_Enable(DEBUG_UART_GPIO_CLK | RCC_AHB_PERIPH_GPIOC, ENABLE);
  DEBUG_UART_APBClkENx(DEBUG_UART_CLK, ENABLE);
}
             
void GPIO_Configuration(void)//包含UART TX RX 复用和LED开关
{
  GPIO_InitTypeDef GPIO_InitStructure = {0};
  DEBUG_UART_AFTX; //UART TX RX 复用
  DEBUG_UART_AFRX;
  GPIO_InitStructure.Pins = DEBUG_UART_TX_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_Init(DEBUG_UART_TX_GPIO_PORT, &GPIO_InitStructure);
  GPIO_InitStructure.Pins = DEBUG_UART_RX_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT_PULLUP;
  GPIO_Init(DEBUG_UART_RX_GPIO_PORT, &GPIO_InitStructure);
  GPIO_InitStructure.Pins = GPIO_PIN_3 | GPIO_PIN_2; //PC3 LED1 / PC2 LED2
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_Init(CW_GPIOC, &GPIO_InitStructure);
  PC03_SETLOW();//LED灭
  PC02_SETLOW();
}

2.配置UART 

void UART_Configuration(void)
{
   UART_InitTypeDef UART_InitStructure = {0};
   UART_InitStructure.UART_BaudRate = DEBUG_UART_BaudRate; // 波特率
   UART_InitStructure.UART_Over = UART_Over_16; // 采样方式
   UART_InitStructure.UART_Source = UART_Source_PCLK; // 传输时钟源UCLK
   UART_InitStructure.UART_UclkFreq = DEBUG_UART_UclkFreq; // 传输时钟UCLK频率
   UART_InitStructure.UART_StartBit = UART_StartBit_FE; // 起始位判定方式
   UART_InitStructure.UART_StopBits = UART_StopBits_1; // 停止位长度
   UART_InitStructure.UART_Parity = UART_Parity_No ; // 校验方式
   UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None; 
   //硬件流控
   UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx; // 发送/接收使能
   UART_Init(DEBUG_UARTx, &UART_InitStructure);
}

3.配置SPI接口

void SPI_FLASH_Init(void)
{
  SPI_InitTypeDef  SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  /************************GPIO Configuration***********************/
  RCC_AHBPeriphClk_Enable(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MISO_GPIO_CLK | FLASH_SPI_MOSI_GPIO_CLK | FLASH_SPI_CS_GPIO_CLK, ENABLE);
  FLASH_SPI_APBClkENx(FLASH_SPI_CLK, ENABLE);
  //SPI SCK MOSI MISO 复用
  FLASH_SPI_AF_SCK;
  FLASH_SPI_AF_MISO;  
  FLASH_SPI_AF_MOSI;     
  //CS
  GPIO_InitStructure.Pins = FLASH_SPI_CS_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
  GPIO_Init(FLASH_SPI_CS_GPIO_PORT, &GPIO_InitStructure);
  //SCK   
  GPIO_InitStructure.Pins = FLASH_SPI_SCK_GPIO_PIN;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
  //MOSI
  GPIO_InitStructure.Pins = FLASH_SPI_MOSI_GPIO_PIN;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
  //MISO
  GPIO_InitStructure.Pins = FLASH_SPI_MISO_GPIO_PIN;
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
  GPIO_InitStructure.IT = GPIO_IT_NONE;
  GPIO_Init(FLASH_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
   //拉高CS
  FLASH_SPI_CS_HIGH();
  /************************SPI Configuration***********************/
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;    // 双线全双工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;              // 主机模式
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   // 帧数据长度为8bit
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;       // 时钟空闲电平为低
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;     // 第1边沿采样
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;        // 片选信号由SSI寄存器控制
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; 
  // 波特率为PCLK的8分频
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    // 最高有效位 MSB 收发在前
  SPI_InitStructure.SPI_Speed = SPI_Speed_High;    // 低速SPI
  SPI_Init(FLASH_SPIx, &SPI_InitStructure);
}

4.比较两个缓冲区数据

TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{
  while(BufferLength--)
  {
     if(*pBuffer1 != *pBuffer2)
     {
       return FAILED;                 
     }
     pBuffer1++;
     pBuffer2++;               
  }
  return PASSED;
}

5.将C库printf函数重新定位到UART

PUTCHAR_PROTOTYPE
{
   UART_SendData_8bit(DEBUG_UARTx, (uint8_t)ch);// 通过UARTx发送一个数据(8bit)
   while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
   return ch;
}
size_t __write(int handle, const unsigned char * buffer, size_t size)
{
   size_t nChars = 0;     
                 
   if (buffer == 0)
   {//应该刷新内部缓冲区 "handle" == -1
     return 0;
   }
   for (size != 0; --size)
   {
     UART_SendData_8bit(DEBUG_UARTx, *buffer++);
     while (UART_GetFlagStatus(DEBUG_UARTx, UART_FLAG_TXE) == RESET);
     ++nChars;
   }
   return nChars;
}

6.主程序实现SPI对FLASH的读写(具体对FLASH读写可参考cw32_eval_spi_flash.c)

int32_t main(void)
{ 
  RCC_Configuration();//配置RCC
  GPIO_Configuration();//配置GPIO
  UART_Configuration();//配置UART
  SPI_FLASH_Init();//配置SPI
  printf("\r\nCW32L083 SPI FLASH Example\r\n");
  DeviceID = SPI_FLASH_DeviceID();//读取Device ID
  ManufactDeviceID = SPI_FLASH_ManufactDeviceID();//读取Manufacturer / Device ID
  JedecID = SPI_FLASH_JedecID();//读取JEDEC ID
  SPI_FLASH_UniqueID(UniqueID);// 读取Unique ID (64bit)
  printf("\r\nDeviceID = 0x%X\r\nManufactDeviceID = 0x%X\r\nJedecID = 0x%X", DeviceID, ManufactDeviceID, JedecID);
  printf("\r\nUniqueID = 0x ");
  for(uint8_t i = 0; i<8; i++)
  {
     printf("%X ", UniqueID[i]);
  }
  
  if(JedecID == sJedecID)  /* Check JEDEC ID */
  {
     printf("\r\n\nFLASH Detected\r\n");;
     SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress); //擦除扇区 4KB
     SPI_FLASH_BufferWrite(TxBuffer, FLASH_WriteAddress, BufferSize); //写数据
     printf("\r\n写入的数据为:%s\r\n", TxBuffer);
     SPI_FLASH_BufferRead(RxBuffer, FLASH_ReadAddress, BufferSize); //读数据
     printf("\r\n读出的数据为:%s\r\n", RxBuffer);
     //检查写入的数据与读出的数据是否一致
     TransferStatus = Buffercmp(TxBuffer, RxBuffer, BufferSize);
     if(TransferStatus == PASSED)
     {
        PC03_SETHIGH();
        printf("\r\nFLASH Success\r\n");
     }
     else
     {
        PC02_SETHIGH();
        printf("\r\nFLASH Error 1\r\n");
     }
     SPI_FLASH_SectorErase(FLASH_SectorToEraseAddress); //擦除扇区 4KB
     SPI_FLASH_BufferRead(RxBuffer, FLASH_ReadAddress, BufferSize); //读数据
     for(uint8_t j = 0; j < BufferSize; j++)
     {
        if(RxBuffer[j] != 0xFF)
        {
           PC02_SETHIGH();
           printf("\r\nFLASH Error 2\r\n");
        }
     }                
   }
   else// (FlashID != sFLASH_ID)
   {
     PC02_SETHIGH();
     printf("\r\nFLASH Error 3\r\n");
   }
   while(1)
   {
   }
}

7.演示说明

程序读外部FLASH的DeviceID、ManufactDeviceID、JedecID、UniqueID并打印读取到的芯片相关信息,然后依次进行写入数据、读取数据、比较数据、擦除SECTOR、读取数据等操作,当读写正确时,通过UART打印“FLASH Success”,LED1亮。当读取的JedecID不正确,写入读取内容不一致,擦除后读取数据不为全FF时,打印“FLASH Error 2”,LED2亮。如下图所示:

5.png

关于CW32更多详细信息,请访问官方网站www.whxy.com。

来源:武汉芯源半导体

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 281

相关阅读:

mm32-2nd-bootloader技术白皮书(1)——配置软硬件环境

mm32-2nd-bootloader技术白皮书(2)——QSPI外设简介

简介

前文中了解了微控制器可以使用 QSPI 接口连接 QSPI Flash,来实现扩展微控制器的 Flash 空间,存储较大 code size 的应用程序。

但通过 QSPI 接口连接的 QSPI Flash,无法直接使用已有的片内 Flash 的下载算法下载程序,例如在 MDK 平台上编译的程序,在没有下载算法的情况下,是不能直接点击 MDK 的下载按钮或调试按钮下载调试程序的,这样会对使用 QSPI Flash 存储执行程序带来很大的麻烦。

因此,本文将使用 MDK 平台,以 PLUS-F5270 开发板为例,讲解如何制作适用于 MM32F5270 系列 MCU 的 QSPI Flash 下载算法,让用户在 MDK 平台中下载应用程序就如同在片内 Flash 中那样方便。

生成下载算法文件

在 Keil-MDK 的安装目录下,包含一个下载算法的模板文件,例如:MDK 安装在 C 盘目录下,则工程模板的位置在 "C:\Keil_v5\ARM\Flash" ,模板名称为 "_Template",如图 1 所示。

1.png

图1 下载算法模板的目录与包含文件

复制整个 "_Template"文件到该目录下,并重命名为“MM32F5270_QSPI_FlashLoader”,当然也可以换成其它名字;进入该文件夹,将所有名为 "NewDevice" 的文件重命名,建议同文件夹名保持一致。

重命名文件后,打开 ".uvprojx" 后缀的工程文件,进入 MDK 工程,在 Project 栏中可以看到,除了 "Abstract.txt" 文件外,只有 "FlashPrg.c" 与 "FlashDev.c" 两个文件。

修改MDK工程选项配置

打开 Options for Target,进入 Device 页面,PLUS-F5270 使用的微控制器为 MM32F5277E9PV,在左下角的芯片选择中,选择 MindMotion -> MM32F5270 Series->MM32F5277E -> MM32F5277E9PV"。

由于 MM32F5270 系列芯片使用 STAR-MC1 内核处理器,不能使用 AC5 编译器编译代码,需选择 Target 页面,将 Code Generation 的 ARM Compiler 版本选择为 Use default compiler version 6。

选择 Output 页面,修改 Name of Executable 选项中的内容以表示设备,建议该修改名称与文件夹保持一致。修改页面如图 2 所示。

2.png

图2 MDK的工程选项配置界面

由于下载算法通常是在 Flash 目录下开发,使用下载算法时要将下载算法放到 Flash 目录下,避免调试下载算法时,来回将下载算法文件复制到前一级目录。因此,在工程选项配置中,将输出下载算法的路径配置到前一级目录下。选择 User 页面,修改 After Build/Rebuild 的Run #1为cmd.exe /C copy "Objects\%L" "..\@L.FLM",如图 3 所示。

3.png

图3 Keil-MDK的输出下载算法路径配置界面

Flash 编程算法使用 Read-Only Position Independent 与 Read-Write Position Independent。通过观察 "MM32F5270_QSPI_FlashLoader" 文件夹下的 "Target.lin" 文件可知,不管是代码还是数据,都被放入与地址无关的空间内,因此在编译时,要将代码和数据以“和地址无关”的方式编译。选择 C/C++(AC6) 页面,勾选 Read-Only Position Independent 与 Read-Write Position Independent;选择 Asm 页面,也勾选 Read-Only Position Independent 与  Read-Write Position Independent 选项,如图 4 所示。

4.png

图4 Keil-MDK的工程算法编译方式选项配置界面

此处需注意要将 C/C++(AC6) 页面下的 Warnings: 选项选为AC5-like Warnings,否则会产生路径名不同的 warning。

FlashDev.c(25): warning: non-portable path to file '"..\FlashOS.h"'; specified path differs in case from file name on disk [-Wnonportable-include-path]
#include "..\FlashOS.H"        // FlashOS Structures
         ^~~~~~~~~~~~~~
         "..\FlashOS.h"

查看整个 MM32F5270_QSPI_FlashLoader 工程文件,会发现当前工程中没有启动文件与主函数,默认只有几个功能函数,在这种情况下,编译会爆出下面的警告:

L6305W: Image does not have an entry point. (Not specified or not set due to multiple choices.)

但下载算法本身是不需要启动文件和主函数的,只在下载代码时由调试器调用下载算法,因此,需忽略该 warning。选择 Linker 界面,修改 Misc controls 中的内容为 --diag_suppress L6305 ,如图 5 所示。

5.png

图5 配置 MDK 工程禁用错误 L6305

修改FlashDev.c文件

在 MM32F5270_QSPI_FlashLoader工程文件中,FlashDev.c 文件的内容由实际所使用的 QSPI Flash 芯片决定,该文件中包含一个名为 FlashDevice 的结构体,本文以 W25Q128 芯片为例,作为与 MM32F5270 MCU 相连接的 QSPI Flash 芯片,该芯片容量为 16MB,扇区大小为 4KB,页大小为 256Byte。则结构体FlashDevice 的配置如下:

    struct FlashDevice const FlashDevice  =  {
   FLASH_DRV_VERS,              // Driver Version, do not modify!
   "MM32F5270 QSPI FlashLoader",// Device Name 
   EXTSPI,                      // Device Type
   0x90000000,                  // Device Start Address
   0x01000000,                  // Device Size in Bytes (16MB)
   256,                         // Programming Page Size
   0,                           // Reserved, must be 0
   0xFF,                        // Initial Content of Erased Memory
   100,                         // Program Page Timeout 100 mSec
   3000,                        // Erase Sector Timeout 3000 mSec

// Specify Size and Address of Sectors
   0x001000, 0x000000,          // Sector Size  4kB (1 Sectors)
   SECTOR_END
};

若使用 W25Q64 芯片,其芯片容量 8MB,Device Size in Bytes 行的值需修改为 0x00800000。

修改好后,先进行一次编译,然后在 MM32F5270 的任意工程中查看列表,以MM32F5270中的 hello_world 样例工程为例。

打开 hello_world 样例工程的 Options for Target 选项,到 Debug  页面下,打开调试器的 Settings, 到 Flash Download 页面下,进入 Add 选项,可看见列表中新增了一个名为 MM32F5270 QSPI FlashLoader 的下载算法,列表界面如图 6 所示。

6.png

图6 MDK 的下载算法添加列表

当然,这个下载算法还尚且不能使用,仍需要进行功能填充。若要让下载算法可用,首先,需要添加必要的文件,使用 MindSDK 生成任意一个 MM32F5270 的工程,提取其中的 device 目录到下载算法的目录下。

下载算法需要使用到的文件如图 7 所示:

7.png

图7 在工程中添加下载算法所需文件

添加完所需文件后,需添加头文件地址才能使用对应文件,如图 8 所示。

8.png

图8 在工程中添加头文件地址

为什么没有把所有的驱动文件都包含进去呢?这是由于 FlashLoader 需尽可能的小,如果把所有的驱动都添加进去的话,下载算法就会变得很大很大,要知道,下载算法没有启动文件和主函数,编译器不知道哪些函数没有用到,因此不会进行优化,将没有用到的函数删除。因此仅添加 GPIO,QSPI 和 RCC 三个必须使用到的驱动,如有必要,甚至可以写好下载算法后,将这三个驱动文件中没有用到的函数也进行删除。

QSPI Flash芯片的驱动开发

要想将代码下载到 QSPI Flash 中,就要根据 QSPI Flash 手册中的对应要求来操作 QSPI Flash,为此,需要为 QSPI Flash 芯片设计驱动,当然,为了减少代码量,仅实现必要的功能。

QSPI Flash 支持单线,双线和四线操作,本文为方便编程,仅描述实现单线模式的操作,且认为 QSPI Flash 没有对任何块实施写保护等操作,同时,QSPI Flash 没有进入所谓的 QPI 模式。当然,上述特殊需求也能够在 Flashloader 中实现,但本文不再详细赘述如何实现。

在 FlashPrg.c 文件中设计并添加 QspiFlash_Init()、QspiFlash_IsBusy()、QspiFlash_EnableWrite()、QspiFlash_EraseSector()、QspiFlash_WritePage() 函数作为 QSPI Flash 芯片的驱动函数。

QspiFlash_Init() 函数

设计函数QspiFlash_Init()用于初始化,该函数包括 GPIO 的初始化,QSPI 模块的初始化,QSPI 直接模式读的初始化,由于仅使用单线模式,故无需额外打开 QSPI Flash 的四线模式。以上初始化的需求如下:

  • GPIO 的初始化

    配置 GPIO 使其作为 QSPI 的信号线,供 QSPI 外设与 QSPI Flash 进行通信。

  • QSPI 模块的初始化

    使其能够与 QSPI Flash 进行通信,实现读数据和写数据的功能。

  • QSPI 直接模式读的初始化

    为了使系统能够通过访问地址的形式直接读取到 QSPI Flash 中的数据,调试器下载程序后,会通过访问地址的形式读取 QSPI Flash 中的数据,与下载的数据进行对比,实现校验功能。

需要注意的是,GPIO 要根据 QSPI 实际使用到的引脚进行配置,QSPI Flash 的初始化要根据 QSPI Flash 的数据手册进行调整,可能部分 QSPI Flash 在读写数据之前,会有一些针对 QSPI Flash 的初始化操作。

void QspiFlash_Init(void)
{
    /* enable periph clock. */
    RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOA
                        | RCC_AHB1_PERIPH_GPIOB
                        | RCC_AHB1_PERIPH_GPIOG
                        | RCC_AHB1_PERIPH_QSPI
                        , true);

    /* enable gpio. */
    GPIO_Init_Type gpio_init;
    gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
    gpio_init.Speed   = GPIO_Speed_50MHz;

    gpio_init.Pins    = GPIO_PIN_3; /* QSPI_DA1. */
    GPIO_Init(GPIOA, &gpio_init);
    GPIO_PinAFConf(GPIOA,gpio_init.Pins, GPIO_AF_10);

    gpio_init.Pins    = GPIO_PIN_3 | GPIO_PIN_10; /* QSPI_DA2 & QSPI_CS. */
    GPIO_Init(GPIOB, &gpio_init);
    GPIO_PinAFConf(GPIOB,gpio_init.Pins, GPIO_AF_10);

    gpio_init.Pins    = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8; /* QSPI_DA0, QSPI_SCK & QSPI_DA3. */
    GPIO_Init(GPIOG, &gpio_init);
    GPIO_PinAFConf(GPIOG,gpio_init.Pins, GPIO_AF_10);

    /* enable qspi. */
    QSPI_Init_Type qspi_init;
    qspi_init.SpiMode           = QSPI_SpiMode_3;
    qspi_init.SckDiv            = 8u;
    qspi_init.CsHighLevelCycles = 9u;
    qspi_init.RxSampleDelay     = 1u;

    QSPI_Init(QSPI, &qspi_init);

    /* enable qspi direct xfer mode. */
    QSPI_DirectXferConf_Type qspi_direct_conf;
    qspi_direct_conf.CmdBusWidth  = QSPI_BusWidth_1b;
    qspi_direct_conf.CmdValue     = 0x0B; /* fast read. */
    qspi_direct_conf.AddrBusWidth = QSPI_BusWidth_1b;
    qspi_direct_conf.AddrWordWidth    = QSPI_WordWidth_24b;
    qspi_direct_conf.AltBusWidth  = QSPI_BusWidth_None;
    qspi_direct_conf.DummyCycles  = 8u;
    qspi_direct_conf.DataBusWidth = QSPI_BusWidth_1b;

    QSPI_EnableDirectRead(QSPI, &qspi_direct_conf);
}

QspiFlash_IsBusy() 函数

QspiFlash_IsBusy() 函数通过读取 QSPI Flash 的寄存器值,确定 QSPI Flash 是否处于忙碌状态。在向 QSPI Flash 中写入数据之后,需要使用该函数等待 QSPI Flash 将写进去的数据完全烧写到 Flash 单元中。

判断本文使用的 QSPI Flash 是否忙碌的方法是:读取这个 QSPI Flash 寄存器 2 中的第一个比特位是否为1,若为1 则是忙碌状态。

具体的判断方法,还是需要根据使用的 QSPI Flash 手册来确定判断方法。

bool QspiFlash_IsBusy(void)
{
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    xfer_conf.CmdValue      = 0x05; /* get reg1 value. */
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_None;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
    xfer_conf.DataLen       = 1;

    QSPI_SetIndirectReadConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {}

    uint32_t reg1 = QSPI_GetIndirectData(QSPI);

    if (0 == (reg1 & 0x01) )
    {
        return false;
    }
    else
    {
        return true;
    }
}

QspiFlash_EnableWrite() 函数

QspiFlash_EnableWrite() 函数主要是为了进行写使能操作,保证数据可写入。

每次进行任何写操作时,都需要执行一次写使能操作。需要注意,每一次写操作完成后,写使能就会关闭(当然,还需视具体芯片而定)。

void QspiFlash_EnableWrite(bool enable)
{
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    if (true == enable)
    {
        xfer_conf.CmdValue      = 0x06; /* enbale write. */
    }
    else
    {
        xfer_conf.CmdValue      = 0x04; /* disable write. */
    }
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_None;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_None;

    QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {}
}

QspiFlash_EraseSector() 函数

QspiFlash_EraseSector() 函数执行擦除扇区操作。

void QspiFlash_EraseSector(uint32_t addr)
{
    QspiFlash_EnableWrite(true);
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    xfer_conf.CmdValue      = 0x20; /* sector erase. */
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
    xfer_conf.AddrValue     = addr & 0x00FFFFFF;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_None;

    QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {}

    while(QspiFlash_IsBusy() ) /* wait for erase done. */
    {}

    QspiFlash_EnableWrite(false);
}

QspiFlash_WritePage() 函数

QspiFlash_WritePage() 函数用于执行写数据操作。

注意,擦数据是按块(或者叫做扇区)擦除,写操作是按页进行写,页的大小是小于块的大小的。块大小和页大小已经在前面的 FlashDev.c 中定义好了,调试下载器会按照上面定义的大小进行块擦除和页写入。

void QspiFlash_WritePage(uint32_t addr, uint32_t size, uint8_t * data)
{
    QspiFlash_EnableWrite(true);
    QSPI_ClearStatus(QSPI, QSPI_STATUS_XFER_DONE);

    QSPI_IndirectXferConf_Type xfer_conf;
    xfer_conf.CmdBusWidth   = QSPI_BusWidth_1b;
    xfer_conf.CmdValue      = 0x02; /* sector erase. */
    xfer_conf.AddrBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.AddrWordWidth = QSPI_WordWidth_24b;
    xfer_conf.AddrValue     = addr & 0x00FFFFFF;
    xfer_conf.AltBusWidth   = QSPI_BusWidth_None;
    xfer_conf.DummyCycles   = 0u;
    xfer_conf.DataBusWidth  = QSPI_BusWidth_1b;
    xfer_conf.DataWordWidth = QSPI_WordWidth_8b;
    xfer_conf.DataLen       = size;

    QSPI_SetIndirectWriteConf(QSPI, &xfer_conf);

    while(0 == (QSPI_GetStatus(QSPI) & QSPI_STATUS_XFER_DONE) )
    {
        if (size > 0 && 0 != (QSPI_GetStatus(QSPI) & QSPI_STATUS_FIFO_EMPTY) )
        {
            QSPI_PutIndirectData(QSPI, *data++);
            size--;
        }
    }

    while(QspiFlash_IsBusy() ) /* wait for erase done. */
    {}

    QspiFlash_EnableWrite(false);
}

修改FlashPrg.c文件

在 MM32F5270_QSPI_FlashLoader 工程的 FlashPrg.c 文件中,根据用户手册对于 Flash 描述和 Flash 控制器的特性,适配编程算法。

使用上述QSPI Flash芯片驱动开发函数对FlashPrg.c文件中的模板函数进行填充。

Init()函数

初始化函数除了初始化 QSPI Flash 本身,还需要初始化 MCU 的时钟,使其工作在片内 RC 振荡器提供的 8M 时钟环境下。

int Init (unsigned long adr, unsigned long clk, unsigned long fnc) 
{
    /* Switch to HSI. */
    RCC->CR |= RCC_CR_HSION_MASK; /* Make sure the HSI is enabled. */
    while ( RCC_CR_HSIRDY_MASK != (RCC->CR & RCC_CR_HSIRDY_MASK) )
    {
    }
    RCC->CFGR = RCC_CFGR_SW(0u); /* Reset other clock sources and switch to HSI. */
    while ( RCC_CFGR_SWS(0u) != (RCC->CFGR & RCC_CFGR_SWS_MASK) ) /* Wait while the SYSCLK is switch to the HSI. */
    {
    }

    /* Reset all other clock sources. */
    RCC->CR = RCC_CR_HSION_MASK;

    /* Disable all interrupts and clear pending bits. */
    RCC->CIR = RCC->CIR; /* clear flags. */
    RCC->CIR = 0u; /* disable interrupts. */

    QspiFlash_Init();
    return (0);                                  /* Finished without Errors. */
}

UnInit()函数

无需任何操作。

UnInit() 函数在下载程序的操作完成后被调用,如果 Flashloader 操作的是片内 Flash,可能会在该函数内实现一个对片内 Flash 上锁的操作,保证 Flash 中的数据不会被意外修改,而本文使用的 QSPI Flash 不需要类似的操作,因此这个函数不用做任何改动,当然,也得视具体的 QSPI Flash 而定。

EraseChip()函数

QSPI Flash 本身具有擦除整颗芯片数据的指令,但为了减少代码量,可以使用循环调用擦除扇区的方法来擦除整颗芯片的数据。

int EraseChip (void) 
{
    for (uint32_t i = 0; i < 0x01000000; i+= 0x1000)
    {
        QspiFlash_EraseSector(i);
    }
    return (0);                                  /* Finished without Errors. */
}

EraseSector() 函数

此函数的目的是擦除扇区。

需要注意的是,Flash_EraseSector() 需要传入的参数是 QSPI Flash 芯片内的地址,而不是在 MCU 上映射的地址,因此需要使用 0x00FFFFFF 掩码取有效地址。

int EraseSector (unsigned long adr) 
{
    QspiFlash_EraseSector(adr & 0x00FFFFFF);
    return (0);                                  /* Finished without Errors. */
}

ProgramPage()函数

此函数的目的是将程序写入 Flash 中。

int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) 
{
    QspiFlash_WritePage(adr,sz,buf);
    return (0);                                /* Finished without Errors. */
}

总结

完成上述下载算法所需的函数后,下载算法也就制作完成了。使用这个下载算法,就可以实现下载程序到 QSPI Flash 的操作,但本文还没有讲该怎么实现这个可以存储在 QSPI Flash 中的应用程序,也没有讲解该怎么执行这个程序,所以暂时还没有办法验证这个下载算法是否可用,不过后续也将会讲解如何跳转执行存储在 QSPI Flash 中的应用程序,和如何让应用程序能够存储在 QSPI Flash 中。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 119

页面

订阅 RSS - Flash