1. 程式人生 > >STM32操作訪問flash,包括寫入資料到flash和從flash讀取資料

STM32操作訪問flash,包括寫入資料到flash和從flash讀取資料

STM32中儲存區分為:隨機存取儲存器RAM只讀儲存器ROM
其中:

  • RAM為常說的記憶體,比如手機的2G記憶體4G記憶體等,就是程式跑起來的時候所佔用的儲存空間,特點是掉電資料丟失
  • ROM為常說的硬碟,比如手機的64G和128G等,可以簡單的理解為硬碟的儲存空間,特點是掉電資料不丟失,所以又叫“非易失性儲存器件”。
    • ROM又包含:EEPROM和flash。

畫個嵌入式產品儲存器件的思維導圖如下(如有什麼地方不對,懇請大神們進行指正):
嵌入式裝置儲存器件思維導圖

作為ROM的一份子,flash的特點自然是掉電資料不丟失。但是,flash在STM32中比較重要,程式也是儲存在這個地方,所以輕易不讓使用者進行隨意的讀寫,以避免不必要的問題。

而這篇部落格就先簡單記錄一下flash的訪問流程和方法(讀和寫),具體原理以後理解深刻了再做補充。

1、STM32 FLASH操作流程

Flash操作已經屬於嵌入式裝置中很底層的操作了,直接對地址進行存取,簡單描述,Flash操作大致需要以下流程:

  • 1、確定要寫入Flash的首地址(稍後介紹確定地址的方法)
  • 2、解鎖Flash
  • 3、對Flash進行操作(寫入資料)
  • 4、對Flash重新上鎖

1.1 如何查詢並選定要寫入Flash十六進位制地址值的方法

要想選定安全的Flash地址進行讀寫,可以根據自己的STM32 MCU型號,查詢資料手冊,確定FLASH的地址區段,因為起始段會儲存程式碼,所以一定要避開起始段,以避免資料錯誤。(我一般是根據Flash大小計算Flash的最末尾地址,往前推一段地址空間,在這裡一般不會對程式碼中的資料產生覆蓋等影響)

我此次操作Flash使用的MCU是STM32103C8T6,所以以該型號MCU為例進行描述:

  • 在資料手冊中,可以看到STM32103C8T6的flash起始地址是0x0800 0000(如下圖所示),而STM32103C8T6的Flash大小為64K,可以計算出STM32103C8T6的Flash地址範圍是:0x0800 0000——0x0800 FFFF(計算方法參考另一篇部落格:STM32記憶體大小與地址的對應關係以及計算方法)。這裡選取0x0800 F000作為讀寫操作的起始地址,對於C8T6這款MCU,操作這個起始地址應該算是很安全的範圍了。
    STM32103C8T6 Flash地址

2、Flash基本知識點

2.1 Flash容量

Flash根據容量大小可以分為以下三種:

  • 1、小容量產品:Flash大小為1-32KB(STM32F10X_LD)
  • 2、中容量產品:Flash大小為64-128KB(STM32F10X_MD)
  • 3、大容量產品:Flash大小為256KB以上(STM32F10X_HD)

2.2 ST庫對Flash操作的支援

ST庫中對Flash操作主要提供了以下幾類操作API函式:

  • 1、Flash解鎖、鎖定函式
    • void FLASH_Unlock(void);//解鎖函式:在對Flash操作之前必須解鎖
    • void FLASH_Lock(void);//鎖定函式:同理,操作完Flash之後必須重新上鎖
  • 2、Flash寫操作函式
    • FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);//32位字寫入函式
    • FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);//16位半字寫入函式
    • FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);//使用者選擇位元組寫入函式
      注:這裡需要說明,32 位位元組寫入實際上是寫入的兩次 16 位資料,寫完第一次後地址+2,這與我們前面講解的 STM32 快閃記憶體的程式設計每次必須寫入 16 位並不矛盾。寫入 8位實際也是佔用的兩個地址了,跟寫入 16 位基本上沒啥區別。
  • 3、Flash擦除函式
    • FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
    • FLASH_Status FLASH_EraseAllPages(void);
    • FLASH_Status FLASH_EraseOptionBytes(void);
  • 4、獲取Flash狀態
    • FLASH_Status FLASH_GetStatus(void);
      獲取Flash狀態函式,主要是為了獲取Flash的狀態,以便於根據狀態對Flash進行操作。該函式返回值是通過列舉型別定義的,在程式碼中可以看到FLASH_Status型別定義如下(具體含義看註釋即可):
    •                                                  typedef enum
                                                       {
                                                          FLASH_BUSY = 1,        //忙
                                                          FLASH_ERROR_PG,      //程式設計錯誤
                                                          FLASH_ERROR_WRP,   //防寫錯誤
                                                          FLASH_COMPLETE,      //操作完成
                                                          FLASH_TIMEOUT         //操作超時
                                                        }FLASH_Status;
  • 5、等待操作完成函式
    • FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);
      注:在執行快閃記憶體寫操作時,任何對快閃記憶體的讀操作都會鎖住匯流排,在寫操作完成後讀操作才能正確地進行;既在進行寫或擦除操作時,不能進行程式碼或資料的讀取操作。所以在每次操作之前,我們都要等待上一次操作完成這次操作才能開始。

3、OK,上乾貨,上程式碼

根據ST庫提供的上述函式,我們可以自己編寫Flash的讀寫操作程式碼如下:

3.1 先定義一個Flash操作的起始地址巨集定義Flash狀態指示標誌位

#define STARTADDR 0x0800F000 //STM32F103C8T6適用

volatile FLASH_Status FLASHStatus = FLASH_BUSY; //Flash操作狀態變數

3.2 編寫各個讀寫函式

//////////////////////////////////////////////////////////////////////////////////
// Name:        WriteFlashOneWord
//
// Function:    向內部Flash寫入32位資料
//
// Input:       WriteAddress:資料要寫入的目標地址(偏移地址)
//              WriteData:   寫入的資料
//////////////////////////////////////////////////////////////////////////////////
void WriteFlashOneWord(uint32_t WriteAddress, uint32_t WriteData)
{   
    FLASH_UnlockBank1();
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);

    FLASHStatus = 1;    //清空狀態指示標誌位
    FLASHStatus = FLASH_ErasePage(STARTADDR);  
    if(FLASHStatus == FLASH_COMPLETE)   
    {  
        FLASHStatus = 1;    //清空狀態指示標誌位
        FLASHStatus = FLASH_ProgramWord(STARTADDR+WriteAddress, WriteData); //flash.c 中API函式
    }

    FLASHStatus = 1;    //清空狀態指示標誌位
    FLASH_LockBank1();    
}

//////////////////////////////////////////////////////////////////////////////////
// Name:        WriteFlashData
//
// Function:    向內部Flash寫入資料
//
// Input:       WriteAddress:資料要寫入的目標地址(偏移地址)
//              data[]:      寫入的資料首地址
//              num:         寫入資料的個數
//////////////////////////////////////////////////////////////////////////////////
void WriteFlashData(uint32_t WriteAddress, uint8_t data[], uint32_t num)
{
    uint32_t i = 0;
    uint16_t temp = 0;

    FLASH_UnlockBank1();    //解鎖flash
    FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); 

    FLASHStatus = 1;        //清空狀態指示標誌位
    FLASHStatus = FLASH_ErasePage(STARTADDR);//擦除整頁
    if(FLASHStatus == FLASH_COMPLETE)//flash操作完成
    {
        FLASHStatus = 1;    //清空狀態指示標誌位
        for(i=0; i<num; i++)
        {
            temp = (uint16_t)data[i];
            FLASHStatus = FLASH_ProgramHalfWord(STARTADDR+WriteAddress+i*2, temp);//寫入資料
        }
    }

    FLASHStatus = 1;    //清空狀態指示標誌位

    FLASH_LockBank1();  //鎖定flash
} 
//////////////////////////////////////////////////////////////////////////////////
// Name:        ReadFlashNBtye
//
// Function:    從內部Flash讀取N位元組資料
//
// Input:       ReadAddress:資料地址(偏移地址)
//              ReadBuf:讀取到的資料存放位置指標
//              ReadNum:讀取位元組個數
//
// Output:      讀取的位元組數
//////////////////////////////////////////////////////////////////////////////////
int ReadFlashNBtye(uint32_t ReadAddress, uint8_t *ReadBuf, int32_t ReadNum)
{   
    int DataNum = 0;

    ReadAddress = (uint32_t)STARTADDR + ReadAddress;  
    while(DataNum < ReadNum)   
    {        
        *(ReadBuf + DataNum) = *(__IO uint8_t*) ReadAddress++;  
        DataNum++;     
    }

    return DataNum;    
}

//////////////////////////////////////////////////////////////////////////////////
// Name:        ReadFlashData
//
// Function:    從內部Flash讀取num位元組資料
//
// Input:       ReadAddress:資料地址(偏移地址)
//              dest_Data:  讀取到的資料存放位置指標
//              num:        讀取位元組個數
//////////////////////////////////////////////////////////////////////////////////
void ReadFlashData(uint32_t ReadAddress, uint8_t *dest_Data, uint32_t num)
{
    int i = 0;
    ReadAddress = (uint32_t)STARTADDR + ReadAddress; 
    while(i < num) 
    {
        *(dest_Data+i) = *(__IO uint16_t*) ReadAddress;
        ReadAddress += 2;

        i++;
    }
}