浅析STM32内部FLASH读写

http://www.openedv.com/docs/index.html

这个是正点原子开发板的下载资料,您可以到这个网站下载STM32F103精英板资料,拿到具体例程(实验32 FLASH模拟EEPROM实验)。

此例程是基于STM32F103精英板(标准库)进行开发,对STM32内部的FLASH进行读写操作。

通过main函数,我们来解析这个函数的目的

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"     
#include "stmflash.h"
 
 
/************************************************
 ALIENTEK精英STM32开发板实验32
 FLASH模拟EEPROM 实验   
 技术支持:www.openedv.com
 淘宝店铺:http://eboard.taobao.com 
 关注微信公众平台微信号:"正点原子",免费获取STM32资料。
 广州市星翼电子科技有限公司  
 作者:正点原子 @ALIENTEK
************************************************/


//要写入到STM32 FLASH的字符串数组
const u8 TEXT_Buffer[]={"22STM32FLASH_TEXT22"};
#define SIZE sizeof(TEXT_Buffer)        //数组长度
#define FLASH_SAVE_ADDR  0X08070000        //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)

 int main(void)
 {     
    u8 key;
    u16 i=0;
    u8 datatemp[SIZE];

    delay_init();             //延时函数初始化      
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    uart_init(115200);         //串口初始化为115200
     LED_Init();                      //初始化与LED连接的硬件接口
    KEY_Init();                    //初始化按键
    LCD_Init();                       //初始化LCD  
     POINT_COLOR=RED;            //设置字体为红色 
    LCD_ShowString(30,50,200,16,16,"ELITE STM32");    
    LCD_ShowString(30,70,200,16,16,"FLASH EEPROM TEST");    
    LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,110,200,16,16,"2015/1/18"); 
    LCD_ShowString(30,130,200,16,16,"KEY1:Write  KEY0:Read");
    while(1)
    {
        key=KEY_Scan(0);
        if(key==KEY0_PRES)
        {
            LCD_Fill(0,170,239,400,WHITE);
            LCD_ShowString(30,170,200,16,16,"The Data Readed Is:");
            STMFLASH_Read(FLASH_SAVE_ADDR,(u16 *)datatemp,SIZE);
            LCD_ShowString(30,190,200,16,16,datatemp);
            
        }
        else if(key==KEY1_PRES)
        {
            LCD_Fill(0,170,239,400,WHITE);
            LCD_ShowString(30,170,200,16,16,"FLASH Write Finished!");
            STMFLASH_Write(FLASH_SAVE_ADDR,(u16 *)TEXT_Buffer,SIZE);
        }
        i++;
        delay_ms(10);  
        if(i==20)
        {
            LED0=!LED0;//提示系统正在运行    
            i=0;
        }           
    } 
}

mian函数大体是对各种外设进行初始化:

  uart_init(115200);         //串口初始化为115200               =========〉通过串口1把数据传输到LCD,LCD才能正常显示
     LED_Init();                      //初始化与LED连接的硬件接口     =========〉对流水灯轮流亮灭,确认工程是否在正常工作
    KEY_Init();                    //初始化按键              ==========>按键初始化,当某一按键按下时,执行对应操作
    LCD_Init();                       //初始化LCD             ==========>对LCD屏幕初始化,可以人机界面交流
如mian函数中红色部分,按键0(KEY0)按下对FLASH进行读取操作,并通过LCD屏幕显示出来“The Data Readed Is: 11STM32FLASH_TEXT11”
           按键1(KEY1)按下时对FLASH进行写入操作,并通过LCD显示“FLASH Write Finished!”



接下来就要来到今天的重头戏了,FLASH如何实现读写?
其中最重要的是STMFLASH_Read和STMFLASH_Write这两个函数。
提到FLASH的读写函数,我把读写函数的流程通过一个图说明一下


我们通过具体的代码来分析,其中注释基本已经清晰明了地说明了
#include "stmflash.h"

/*
函数名:STMFLASH_ReadHalfWord
功能:    读取半字底层函数
输入:  (u32*)fladdr(地址)
返回:  (u16)    (数据)
*/
u16 STMFLASH_ReadHalfWord(u32 fladdr)
{
    return *(u32*)fladdr;
}


/*
函数名:STMFLASH_WriteNoCheck
功能:    利用库函数(写半字进FLASH的底层函数)对多个数据进行写入
输入: (u32*)fladdr(写入地址) (u16*)WriteBuf(要写入的数据)   (u16)NumToWrite(写入数据的个数)
返回:  NULL
*/
//写入多个数据进入Flash
//这个FLAS有512K字节,但是这里把一次最多读取限制为(u16 0xFFFFFFFF=65536字节=64K字节)
void STMFLASH_WriteNoCheck(u32 fladdr,u16* Write_Buf,u16 NumToWrite)
{
    u16 i;
    for(i=0;i<NumToWrite;i++)
    {
        FLASH_ProgramHalfWord(fladdr,Write_Buf[i]);
        fladdr+=2;
    }
}



/*
函数名:STMFLASH_Read
功能:    对多个数据进行读取
输入: (u32*)fladdr(读取地址) (u16*)WriteBuf(要读取的数据)   (u16)NumToWrite(读取数据的个数)
返回:  NULL
*/
void STMFLASH_Read(u32 fladdr,u16* Read_Buf,u16 NumToRead)
{
    u16 i;
    for(i=0;i<NumToRead;i++)
    {
        Read_Buf[i]=STMFLASH_ReadHalfWord(fladdr);
        fladdr+=2;
    }
}



/*
函数名:STMFLASH_Write
功能:    分扇区对Flash进行写入操作
输入: (u32*)fladdr(写入地址) (u16*)WriteBuf(要写入的数据)   (u16)NumToWrite(写入数据的个数)
返回:  NULL
注意:  每次进行对Flash操作之间要进行解锁操作,之后再上锁,不然会造成数据写入失败
*/

#define STMFLASH_Sector_Size 2048
u16 FLASH_BUF[STMFLASH_Sector_Size/2];
void STMFLASH_Write(u32 fladdr,u16* Write_Buf,u16 NumToWrite)
{
    u16 i;
    u32 offaddr;
    u16 secpos,secoff,secremain;
    offaddr=fladdr-STM32_FLASH_BASE;                //去掉基地址之后,得出偏移量
    secpos=offaddr/STMFLASH_Sector_Size;                //偏移量/扇区大小=当前所在扇区块
    secoff=(fladdr%STMFLASH_Sector_Size)/2;                //(当前扇区区域)已经写入的次数
    secremain=STMFLASH_Sector_Size/2-secoff;            //(扇区剩余区域)可写的次数
    if(secremain>=NumToWrite) secremain=NumToWrite;
    FLASH_Unlock();                        //解锁
    while(1)
    {
        STMFLASH_WriteNoCheck(secpos*STMFLASH_Sector_Size+STM32_FLASH_BASE,FLASH_BUF,STMFLASH_Sector_Size/2);//读取要写入的扇区的数据
        
        for(i=0;i<secremain;i++)                    //判断该扇区的空余区域是否都是空的
        {
            if(FLASH_BUF[i+secoff]!=0xFFFF)
                break;
            
        }
        
        if(i<secremain)                            //非空情况,要先擦除,在进行写入
        {
            FLASH_ErasePage(secpos*STMFLASH_Sector_Size+STM32_FLASH_BASE);
            for(i=0;i<secremain;i++)
            {
                FLASH_BUF[secoff+i]=Write_Buf[i];
            }
            STMFLASH_WriteNoCheck(secpos*STMFLASH_Sector_Size+STM32_FLASH_BASE,FLASH_BUF,secremain);
        
        }
        else
        {
            STMFLASH_WriteNoCheck(fladdr,Write_Buf,NumToWrite);
        }
        
        if(secremain==NumToWrite) break;
        else                  //假如一个扇区还写不完,则进入下一个扇区
        {
            secpos++;          //扇区加一,可写入的次数清零
            secoff=0;
            
            fladdr+=(secremain*2);   //写入地址要加上已经写过的地址偏移量
            Write_Buf+=secremain;    //数组的起始地址要加上已经写入的次数
            NumToWrite-=secremain;   //写入的个数要减去已经写入的次数
            if(NumToWrite>=1024) secremain=1024;  //本个扇区要写入的次数
            else secremain=NumToWrite;
            
        }
        
    }
    FLASH_Lock();//上锁
    
}

 

  这上面毕竟值得注意的是在对内部FLASH进行写入操作时要记得解锁FLASH_Unlock();,写完之后要记得上锁FLASH_Lock();

  接着必须说明一下库函数FLASH_ErasePage

if(status == FLASH_COMPLETE)
    { 
      /* if the previous operation is completed, proceed to erase the page */
      FLASH->CR|= CR_PER_Set;                //PER:页擦除选择擦除页。
      FLASH->AR = Page_Address;         //当进行页擦除时,通过AR设置要擦除的页地址
      FLASH->CR|= CR_STRT_Set;            //STRT:开始当该位为’1’时将触发一次擦除操作。该位只可由软件置为’1’并在BSY变为’1’时清为’0’。
    
      /* Wait for last operation to be completed */
      status = FLASH_WaitForLastBank1Operation(EraseTimeout);        //延时等待擦除操作完成

      /* Disable the PER Bit */
      FLASH->CR &= CR_PER_Reset;
    }

 

  

  其中值得一提的是FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)函数

 该函数也是官方给出的,我们只需要用就好了。但要注意,这个是个半字的写操作,即uint16_t 的数据算半字呢,
因为单片机是32的,对于32位单片机系统来说,一个字是4个字节的,8位的比如51单片机系统一个字就是2位的,
64位单片机系统一个字就是8个字节,脱离单片机系统说字是多少个字节是没意义的。
所以这里写入/读出半字也就是一次写入2个字节,写完/读出一次地址会加2。
if(Address < FLASH_BANK1_END_ADDRESS)
  {
    if(status == FLASH_COMPLETE)
    {
      /* if the previous operation is completed, proceed to program the new data */
      FLASH->CR |= CR_PG_Set;                //PG:编程选择编程操作。        
  
      *(__IO uint16_t*)Address = Data;//对写入地址进行赋值
      /* Wait for last operation to be completed */
      status = FLASH_WaitForLastBank1Operation(ProgramTimeout);        //延时等待操作完成

      /* Disable the PG Bit */
      FLASH->CR &= CR_PG_Reset;                        //把PG位清零,即停止编写操作
    }
  }

 

 在下面将插入一张FLASH->CR(内存控制寄存器)的各个位的作用,各位请参考一下。





 

posted on 2020-07-19 17:36  ---MRZhu  阅读(3836)  评论(0编辑  收藏  举报