zoukankan      html  css  js  c++  java
  • STM32掉电时存数据到FLASH

    开发板:STM32F103CBT6        开发环境:keil 4

    一、STM32FLASH简介

      不同的STM32它的FLASH大小也是不一样的,分为大、中、小容量,容量由16K到1024K不等。这次实验用的开发板FLASH容量大小为128K。

      STM32的闪存模块由:主存储器、信息块和闪存存储器接口寄存器三部分组成。

      主存储器:该部分主要是用来存放代码和数据常数,被划分为128页,每页1K字节(小容量产品也是每页1K字节,大容量为每页2K字节)。主存储器的起始地址就是0X08000000, B0、B1都接GND的时候,就是从0X08000000开始运行代码的。

      信息块:该部分分为2个小部分,其中启动程序代码,是用来存储ST自带的启动程序,用于串口下载代码,当B0接V3.3,B1接GND的时候,运行的就是这部分代码。用户选择字节,则一般用于配置写保护、读保护等功能。

      闪存存储器接口寄存器:该部分用于控制闪存读写等,是整个闪存模块的控制机构。 

      对主存储器和信息块的写入由内嵌的闪存编程/擦除控制器(FPEC)管理;编程与擦除的高电压由内部产生。

      在执行闪存写操作时,任何对闪存的读操作都会锁住总线,在写操作完成后读操作才能正确地进行;既在进行写或擦除操作时,不能进行代码或数据的读取操作

    闪存的读取 

      内置闪存模块可以在通用地址空间直接寻址,任何32位数据的读操作都能访问闪存模块的内容并得到相应的数据。读接口在闪存端包含一个读控制器,还包含一个AHB接口与CPU衔接。这个接口的主要工作是产生读闪存的控制信号并预取CPU要求的指令块,预取指令块仅用于在I-Code总线上的取指操作,数据常量是通过D-Code总线访问的。这两条总线的访问目标是相同的闪存模块,访问D-Code将比预取指令优先级高。

      这里要特别留意一个闪存等待时间,因为CPU运行速度比FLASH快得多,STM32F103的FLASH最快访问速度≤24Mhz,如果CPU频率超过这个速度,那么必须加入等待时间,比如我们一般使用72Mhz的主频,那么FLASH等待周期就必须设置为2,该设置通过FLASH_ACR寄存器设置。

    闪存的编程和擦除

      编程过程:

      ·检查FLASH_CR的LOCK是否解锁,如果没有则先解锁

      ·检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的编程操作

      ·设置FLASH_CR寄存器的PG位为’1’

      ·在指定的地址写入要编程的半字

      ·等待BSY位变为’0’

      ·读出写入的地址并验证数据

      擦除过程(页擦除)

      ·检查FLASH_CR的LOCK是否解锁,如果没有则先解锁

      ·检查FLASH_SR寄存器的BSY位,以确认没有其他正在进行的闪存操作

      ·设置FLASH_CR寄存器的PER位为’1’

      ·用FLASH_AR寄存器选择要擦除的页

      ·设置FLASH_CR寄存器的STRT位为’1’

      ·等待BSY位变为’0’

      ·读出被擦除的页并做验证

    二、软件实现

      flash.c文件

      

    #include "flash.h"
    #include "delay.h"
    #include "usart.h"
    
    /***flash解锁*****/
    void STMFLASH_Unlock(void)
    {
      FLASH->KEYR=FLASH_KEY1;                                        //写入解锁序列
      FLASH->KEYR=FLASH_KEY2;
    }
    
    //flash上锁
    void STMFLASH_Lock(void)
    {
      FLASH->CR|=1<<7;                                              //上锁
    }
    
    //得到FLASH状态
    u8 STMFLASH_GetStatus(void)
    {    
       u32 res;         
    
       res=FLASH->SR;
       if(res&(1<<0))return 1;                                          //
       else if(res&(1<<2))return 2;                                     //编程错误
       else if(res&(1<<4))return 3;                                     //写保护错误
       return 0;                                                        //操作完成
    }
    
    //等待操作完成
    //time:延时长短
    //返回值:状态.
    u8 STMFLASH_WaitDone(u16 time)
    {
       u8 res;
    
       do
       {
          res=STMFLASH_GetStatus();
          if(res!=1)break;                                            //非忙,无需等待,直接退出.
          Delay_us(1); time--;             
        }while(time);
        if(time==0)res=0xff;                                           //TIMEOUT
        return res;
    }
    
    //擦除页
    //paddr:页地址
    //返回值:执行情况
    u8 STMFLASH_ErasePage(u32 paddr)
    
    {
           u8 res=0;
    
           res=STMFLASH_WaitDone(0X5FFF);                              //等待上次操作结束,>20ms   
           if(res==0)
           {
                  FLASH->CR|=1<<1;                                      //页擦除
                  FLASH->AR=paddr;                                      //设置页地址
                  FLASH->CR|=1<<6;                                      //开始擦除           
                  res=STMFLASH_WaitDone(0X5FFF);                       //等待操作结束,>20ms 
                  if(res!=1)                                             //非忙
                  {
                         FLASH->CR&=~(1<<1);                           //清除页擦除标志.
                  }
           }
           return res;
    }
    
    //读出指定地址的半字(16位数据)
    //faddr:读地址(此地址必须为2的倍数!!)
    //返回值:对应数据.
    u16 STMFLASH_ReadHalfWord(u32 faddr)
    {
        return *(vu16*)faddr; 
    }
    #if STM32_FLASH_WREN                                               //如果使能了写   
    //不检查的写入
    //WriteAddr:起始地址
    //pBuffer:数据指针
    //NumToWrite:半字(16位)数   
    void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
    {                       
        u16 i;
        for(i=0;i<NumToWrite;i++)
        {
            FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
            WriteAddr+=2;                                           //地址增加2.
        }  
    } 
    //从指定地址开始写入指定长度的数据
    //WriteAddr:起始地址(此地址必须为2的倍数!!)
    //pBuffer:数据指针
    //NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
    #if STM32_FLASH_SIZE<256
    #define STM_SECTOR_SIZE 1024                                       //字节
    #else 
    #define STM_SECTOR_SIZE    2048
    #endif         
    u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];                               //最多是2K字节
    
    void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
    {
        u32 secpos;                                                     //扇区地址
        u16 secoff;                                                     //扇区内偏移地址(16位字计算)
        u16 secremain;                                                  //扇区内剩余地址(16位字计算)       
         u16 i;    
        u32 offaddr;                                                    //去掉0X08000000后的地址
        
        if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
        FLASH_Unlock();                                                 //解锁
        offaddr=WriteAddr-STM32_FLASH_BASE;                             //实际偏移地址.
        secpos=offaddr/STM_SECTOR_SIZE;                                 //扇区地址  0~127 for STM32F103RBT6
        secoff=(offaddr%STM_SECTOR_SIZE)/2;                             //在扇区内的偏移(2个字节为基本单位.)
        secremain=STM_SECTOR_SIZE/2-secoff;                             //扇区剩余空间大小  
        if(NumToWrite<=secremain)secremain=NumToWrite;                 //不大于该扇区范围
        while(1) 
        {    
            STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
            for(i=0;i<secremain;i++)                                   //校验数据
            {
                if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;              //需要擦除      
            }
            if(i<secremain)                                            //需要擦除
            {
                FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);               //擦除这个扇区
                for(i=0;i<secremain;i++)                              //复制
                {
                    STMFLASH_BUF[i+secoff]=pBuffer[i];      
                }
                STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
            }else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain); //写已经擦除了的,直接写入扇区剩余区间.                    
            if(NumToWrite==secremain)break;                            //写入结束了
            else                                                       //写入未结束
            {
                secpos++;                                              //扇区地址增1
                secoff=0;                                              //偏移位置为0      
                   pBuffer+=secremain;                                 //指针偏移
                WriteAddr+=secremain;                                  //写地址偏移       
                   NumToWrite-=secremain;                              //字节(16位)数递减
                if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
                else secremain=NumToWrite;                             //下一个扇区可以写完了
            }     
        };    
        FLASH_Lock();                                                  //上锁
    }
    #endif
    
    //从指定地址开始读取指定长度的数据
    //ReadAddr:起始地址
    //pBuffer:数据指针
    //NumToWrite:半字(16位)数
    void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
    {
        u16 i;
        for(i=0;i<NumToRead;i++)
        {
            pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);               //读取2个字节.
            ReadAddr+=2;                                              //偏移2个字节
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    //WriteAddr:起始地址
    //WriteData:要写入的数据
    void Test_Write(u32 WriteAddr,u16 WriteData)       
    {
        STMFLASH_Write(WriteAddr,&WriteData,1);                       //写入一个字 
    }

      flash.h文件

      

    #ifndef __FLASH_H__
    #define __FLASH_H__
    #include <stm32f10x.h>
    
    #define FLASH_KEY1               0X45670123
    #define FLASH_KEY2               0XCDEF89AB
    
    #define STM32_FLASH_SIZE 128                                 //所选STM32的FLASH容量大小(单位为K)
    #define STM32_FLASH_WREN 1                                       //使能FLASH写入(0,不使能;1,使能)
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    
    //FLASH起始地址
    #define STM32_FLASH_BASE 0x08000000                         //STM32 FLASH的起始地址
    
    
    void STMFLASH_Unlock(void);                                        //解锁
    void STMFLASH_Lock(void);                                       //上锁
    u8 STMFLASH_GetStatus(void);                                       //获得状态
    u8 STMFLASH_WaitDone(u16 time);                                  //等待操作结束
    u8 STMFLASH_ErasePage(u32 paddr);                                  //擦除页
    u16 STMFLASH_ReadHalfWord(u32 faddr);                               //读出半字  
    void STMFLASH_WriteLenByte(u32 WriteAddr,u32 DataToWrite,u16 Len);       //指定地址开始写入指定长度的数据
    u32 STMFLASH_ReadLenByte(u32 ReadAddr,u16 Len);                       //指定地址开始读取指定长度的数据
    void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite);          //从指定地址开始写入指定长度的数据
    void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead);             //从指定地址开始读取指定长度的数据
    
    //测试写入
    void Test_Write(u32 WriteAddr,u16 WriteData);                                   
    #endif

      main.c文件

    #include "flash.h"
    
    #define SIZE sizeof(TEXT_Buffer)                  ///数组长度
    #define FLASH_SAVE_ADDR  0X08070000   //设置FLASH 保存地址(必须为偶数,且其值要大于本代码所占用FLASH的大小+0X08000000)
    
    const u8 TEXT_Buffer[]={"STM32 FLASH TEST"};
    u8 datatemp[SIZE];
    
    int main(void)
    {
         while(1)
        {
            if(掉电)
         {
              STMFLASH_Write(FLASH_SAVE_ADDR,(u16*)TEXT_Buffer,SIZE);//flash写函数
         }  
               STMFLASH_Read(FLASH_SAVE_ADDR,(u16*)datatemp,SIZE);     //flash读函数   
       }
    }

      以上就是flash在掉电的时候进行读写的程序,项目用到了就记录一下,方便以后查阅学习。其中的大部分代码在库函数中都能够找到参考。

  • 相关阅读:
    年末反思
    Flink运行时架构
    Phoenix 启动报错:Error: ERROR 726 (43M10): Inconsistent namespace mapping properties. Cannot initiate connection as SYSTEM:CATALOG is found but client does not have phoenix.schema.
    Clickhouse学习
    Flink简单认识
    IDEA无法pull代码到本地,Can't Update No tracked branch configured for branch master or the branch doesn't exist.
    第1章 计算机系统漫游
    简单的 Shell 脚本入门教程
    开源≠免费 常见开源协议介绍
    MySQL 视图
  • 原文地址:https://www.cnblogs.com/lucky-3/p/11269510.html
Copyright © 2011-2022 走看看