天天看點

《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗

第四十二章 FLASH模拟EEPROM實驗​

STM32H750本身沒有自帶EEPROM,但是STM32H750具有IAP(在應用程式設計)功能,是以我們可以把它的FLASH當成EEPROM來使用。本章,我們将利用STM32H750内部的FLASH來實作第三十六章實驗類似的效果,不過這次我們是将資料直接存放在STM32H750内部,而不是存放在NOR FLASH。​

本章分為如下幾個小節:​

42.1 STM32H750 FLASH簡介 ​

42.2 硬體設計​

42.3 程式設計​

42.4 下載下傳驗證​

42.1 STM32H750 FLASH簡介​

STM32H750内部隻有128K FLASH,僅有1個扇區,STM32H750的閃存子產品組織如表42.1.1所示:​

塊​ 名稱​ FLASH起始位址​ 大小​
BANK1​ 扇區0​ 0X0800 0000-0X0801FFFF​ 128K​
系統存儲器​ 0X1FF0 0000-0X1FF1FFFF​ 128K​

表42.1.1 STM32H750閃存子產品組織​

STM32H750為了節省成本,内部僅包含1個使用者扇區(Sector0),大小為128K。另外,内部還有一個128K的系統存儲區,用于存儲ST自己的BootLoader程式等,不過這個128K使用者是無法通路的。​

關于STM32H750内部FLASH的詳細說明,詳見《STM32H7xx參考手冊_V7(英文版)》第4.3節相關内容。 ​

在執行閃存寫操作時,任何對閃存的讀操作都會鎖住總線,在寫操作完成後讀操作才能正确地進行。既在進行寫或擦除操作時,不能進行代碼或資料的讀取操作。​

42.1.1 閃存的讀取​

為了準确讀取 Flash 資料,必須根據ACLK時鐘rcc_aclk) 頻率和Vcore電壓範圍在 Flash 存取控制寄存器 (FLASH_ACR) 中正确地設定等待周期數 (LATENCY)。Flash 等待周期與ACLK時鐘頻率之間的對應關系,如表42.1.1.2所示:​

《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗

表42.1.1.2 ACLK時鐘(rcc_aclk)頻率對應的FLASH等待周期表​

等待周期通過FLASH_ACR寄存器的LATENCY[2:0]三個位設定。系統複位後,CPU時鐘頻率為内部64M RC振蕩器(HSI),LATENCY預設是0,即0個等待周期。為了得到更佳的FLASH通路性能,我們設定Vcore電壓範圍為VOS1級别(1.15V~1.26V)。rcc_aclk和rcc_hclk3的頻率是一樣的,都是來自RCC_D1CFGR的HPRE[3:0]分頻,rcc_hclk3我們一般設定的是240Mhz,這樣rcc_aclk也是240Mhz的頻率。我們設定等待周期為4(LATENCY[2:0]=4),否則FLASH讀寫可能出錯,導緻當機。寄存器的設定和HAL庫的是不一樣的,這個請大家注意一下。這個知識點我們在11.2.1小節講解過,請回顧。​

STM32H750的FLASH讀取是很簡單的。例如,我們要從位址addr,讀取一個字(一個字為32位),可以通過如下的語句讀取:​

Data = *(volatile uint32_t *)faddr;​

将faddr強制轉換為volatile uint32_t指針,然後取該指針所指向的位址的值,即得到了faddr位址的值。類似的,将上面的volatile uint32_t改為volatile uint8_t,即可讀取指定位址的一個位元組。相對FLASH讀取來說,STM32H750 FLASH的寫就複雜一點了,下面我們介紹STM32H750閃存的程式設計和擦除。​

42.1.2 閃存的程式設計和擦除​

在對 STM32H750的Flash執行寫入或擦除操作期間,任何讀取Flash的嘗試都會導緻總線阻塞。隻有在完成程式設計操作後,才能正确處理讀操作。這意味着,寫/擦除操作進行期間不能從Flash中執行代碼或資料擷取操作。​

特别注意:因為STM32H750内部僅有1個扇區,是以在執行對該扇區的擦除或者寫入操作時,是無法執行内部FLASH代碼的,是以,必須外擴QSPI FLASH,将擦除/程式設計内部扇區相關的代碼放到外部QSPI FLASH,這樣才可以實作對内部FLASH的正常擦除及寫入操作。​

STM32H750使用者閃存的程式設計一般由5個32位寄存器控制,他們分别是:​

  • FLASH通路控制寄存器(FLASH_ACR)​
  • FLASH秘鑰寄存器1(FLASH_KEYR1) ​
  • FLASH狀态寄存器1(FLASH_SR1) ​
  • FLASH控制寄存器1(FLASH_CR1) ​
  • FLASH清除與控制寄存器1(FLASH_CCR1) ​

注意:這裡的FLASH_KEYR1、FLASH_SR1、FLASH_CR1、FLASH_CCR1分别對應Bank1的相關寄存器,是以單個Bank的控制寄存器由:FLASH_KEYR、SR、CR和CCR等四個寄存器控制。下面,我們直接以FLASH_KEYR、FLASH_CR、FLASH_SR和FLASH_CCR來介紹相關操作。​

STM32H750複位後,FLASH程式設計操作是被保護的,不能寫入FLASH_CR寄存器;通過寫入特定的序列(0X45670123和0XCDEF89AB)到FLASH_KEYR寄存器才可解除寫保護,隻有在寫保護被解除後,我們才能操作相關寄存器。​

FLASH_CR的解鎖序列為:​

  1. 寫0X45670123到FLASH_KEYR​
  2. 寫0XCDEF89AB到FLASH_KEYR​

通過這兩個步驟,即可解鎖FLASH_CR,如果寫入錯誤,那麼FLASH_CR将被鎖定,直到下次複位後才可以再次解鎖。 ​

STM32H750閃存的程式設計位數固定為256位,也就是每次寫入資料必須為8個字(32位元組),如果不夠8個字,可以在後面進行補零寫入,否則後續的内容将不可預知。而且,寫入首位址必須是32的整數倍,否則會影響前後資料。​

舉例來說,假設我們要往:0X0810 0000這個位址寫入一個位元組的資料,則寫入一個位元組資料的同時,也會影響接下來31位元組的内容,是以,如果你隻需要寫入一個位元組,則可以将後面的31位元組全部填充成0,然後組成一個32位元組數組(256位),一次寫入。而且,如果你寫入資料的首位址不是32的倍數,比如往0X0810 0004這裡,寫入1個位元組資料,則會把位址:0X0810 0000 ~ 0X0810 0003的資料也損壞掉(擦除)。是以,記住STM32H7 FLASH寫入的規則:寫入首位址必須是32的倍數,寫入資料長度必須是32位元組的倍數。​

FLASH配置步驟​

STM32H750的FLASH在程式設計的時候,也必須要求其寫入位址的FLASH是被擦除了的(也就是其值必須是0XFFFFFFFF),否則無法寫入。STM32H750的标準程式設計步驟如下:​

1,檢查FLASH_CR的LOCK是否解鎖,如果沒有則先解鎖​

2,檢查FLASH_SR中的BSY位,確定目前未執行任何FLASH操作。​

3,設定FLASH_CR寄存器的PSIZE[1:0]為2,按字寫入(32位寫入)。​

4,将FLASH_CR寄存器中的PG位置1,激活FLASH程式設計。​

5,在指定的存儲器位址,寫入資料(一次寫入32位元組,不能超過32位元組)​

6,等待BSY位清零,完成一次程式設計。​

按以上六步操作,就可以完成一次FLASH程式設計。不過需要注意:程式設計前,要確定要寫如位址的FLASH已經擦除。​

在STM32H750的FLASH程式設計的時候,要先判斷縮寫位址是否被擦除了,是以,我們有必要再介紹一下STM32H750的閃存擦除,STM32H750的閃存擦除分為兩種:扇區擦除和塊擦除。​

扇區擦除步驟如下:​

1,檢查FLASH_CR的LOCK是否解鎖,如果沒有則先解鎖。​

2,檢查FLASH_SR寄存器中的BSY 位,確定目前未執行任何FLASH操作。​

3,在FLASH_CR寄存器中,将SER位置1,并設定SNB=0(隻有1個扇區,扇區0)。​

4,将FLASH_CR寄存器中的START位置1,觸發擦除操作。​

5,等待BSY位清零。​

經過以上五步,就可以擦除某個扇區。本章,我們隻用到了STM32H750的扇區擦除功能。塊擦除功能我們在這裡就不介紹了,想了解的朋友請看《STM32H7xx參考手冊_V7(英文版).pdf》的相關内容。​

42.1.3 FLASH寄存器​

  • Flash通路控制寄存器(FLASH_ACR)​Flash通路控制寄存器描述如圖42.1.3.1所示:​
  • 《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗
  • 圖42.1.3.1 FLASH_ACR寄存器​

    WRHIGHFREQ[1:0]位,用于控制FLASH程式設計操作時的延遲,必須根據FLASH操作頻率(rcc_aclk)進行正确的設定:00,rcc_aclk≤85Mhz;01,rcc_aclk≤185Mhz;10,rcc_aclk≤285Mhz;​

    11,rcc_aclk≤385Mhz;我們的rcc_aclk設定的是240Mhz,設定WRHIGHFREQ[1:0]=10即可。​

    LATENCY[2:0]位,用于控制FLASH讀延遲,必須根據我們MCU核心的工作電壓和頻率,來進行正确的設定,否則,可能當機,設定規則見表42.1.1.2。 ​

  • 存儲區1的FLASH密鑰寄存器(FLASH_KEYR1)​存儲區1的FLASH密鑰寄存器描述如圖42.1.3.2所示:​
  • 《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗
  • 圖42.1.3.2 FLASH_KEYR1寄存器​

    該寄存器主要用來解鎖FLASH_CR,必須在該寄存器寫入特定的序列(KEY1和KEY2)解鎖後,才能對FLASH_CR寄存器進行寫操作。​

  • 存儲區1的FLASH控制寄存器(FLASH_CR1)​存儲區1的FLASH控制寄存器描述如圖42.1.3.3所示:​
  • 《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗
  • 圖42.1.3.3 FLASH_CR1寄存器​

    LOCK位,該位用于訓示FLASH_CR寄存器是否被鎖住,該位在檢測到正确的解鎖序列後,硬體将其清零。在一次不成功的解鎖操作後,在下次系統複位之前,該位将不再改變。​

    PG位,該位用于選擇程式設計操作,在往FLASH寫資料的時候,該位需要置1。​

    SER位,該位用于選擇扇區擦除操作,在扇區擦除的時候,需要将該位置1。​

    PSIZE[1:0]位,用于設定程式設計寬度,我們一般設定PSIZE =2即可(32位)。​

    SATRT位,該位用于開始一次擦除操作。在該位寫入1 ,将執行一次擦除操作。​

    SNB[2:0]位,這3個位用于選擇要擦除的扇區編号,取值範圍為0~7,H750隻能設為0。​

    FLASH_CR的其他位,我們就不在這裡介紹了,請大家參考《STM32H7xx參考手冊_V7(英文版).pdf》 。​

  • 存儲區1的FLASH狀态寄存器(FLASH_SR1)​

存儲區1的FLASH狀态寄存器描述如圖42.1.3.4所示:​

《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗

圖42.1.3.4 FLASH_SR1寄存器​

BSY位:表示BANK目前正在執行程式設計操作,必須等待該位為0,才可以執行其他操作。​

WBNE位:表示BANK寫BUFFER是否為空。當該位為1時,表示寫BUFFER裡面還有資料待寫入FLASH,需要等待該位為0,才表示資料寫入全部完成了。​

QW位:表示操作序列裡面是否還有程式設計操作需要執行,需要等待該位為0,才表示所有的程式設計操作完成了。​

最後,FLASH清除控制寄存器FLASH_CCR用于清除相關錯誤,這裡我們就不做介紹了,詳見《STM32H7xx參考手冊_V7(英文版).pdf》第3.9.6節。 ​

42.2 硬體設計​

1. 例程功能​

按鍵KEY1控制寫入FLASH的操作,按鍵KEY0控制讀出操作,并在TFTLCD子產品上顯示相關資訊,還可以借助USMART進行讀取或者寫入操作。LED0閃爍用于提示程式正在運作。​

2. 硬體資源​

1)RGB燈​

RED :LED0 - PB4 ​

2)序列槽1(PA9/PA10連接配接在闆載USB轉序列槽晶片CH340上面)​

3)正點原子2.8/3.5/4.3/7/10寸TFTLCD子產品(僅限MCU屏,16位8080并口驅動)​

4)獨立按鍵 :KEY0 - PA1、KEY1 - PA15​

42.3 程式設計​

42.3.1 FLASH的HAL庫驅動​

FLASH在HAL庫中的驅動代碼在stm32h7xx_hal_flash.c和stm32h7xx_hal_flash_ex.c檔案(及其頭檔案)中。 ​

1. HAL_FLASH_Unlock函數​

解鎖閃存控制寄存器通路的函數,其聲明如下:​

HAL_StatusTypeDef HAL_FLASH_Unlock(void);      
  • 函數描述:​用于解鎖閃存控制寄存器的通路,在對FLASH進行寫操作前必須先解鎖,解鎖操作也就是必須在FLASH_KEYR寄存器寫入特定的序列(KEY1和KEY2)。​
  • 函數形參:無​
  • 函數傳回值:HAL_StatusTypeDef枚舉類型的值。​2. HAL_FLASH_Lock函數​鎖定閃存控制寄存器通路的函數,其聲明如下:​
HAL_StatusTypeDef HAL_FLASH_Lock (void);      
  • 函數描述:​用于鎖定閃存控制寄存器的通路。​
  • 函數形參:無​
  • 函數傳回值:HAL_StatusTypeDef枚舉類型的值。​3. HAL_FLASHEx_Erase函數​閃存擦除函數,其聲明如下:​
HAL_StatusTypeDef HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, ​
uint32_t *SectorError);      
  • 函數描述:​該函數用于大量擦除或擦除指定的閃存扇區。​
  • 函數形參:​形參1是FLASH_EraseInitTypeDef結構體類型指針變量。​

    形參2是uint32_t類型指針變量,存放錯誤碼,0xFFFFFFFF值表示扇區已被正确擦除,其它值表示擦除過程中的錯誤扇區。。 ​

  • 函數傳回值:HAL_StatusTypeDef枚舉類型的值。​4. FLASH_WaitForLastOperation函數​等待FLASH操作完成函數,其聲明如下:​
HAL_StatusTypeDef FLASH_WaitForLastOperation(uint32_t Timeout, uint32_t Bank);      
  • 函數描述:​該函數用于等待FLASH操作完成。​
  • 函數形參:​形參1是FLASH操作逾時時間。​

    形參2是等待BANK1還是BANK2操作完成,這裡我們用的H750隻能選擇BANK1。 ​

  • 函數傳回值:​

HAL_StatusTypeDef枚舉類型的值。​

42.3.2 程式流程圖​

《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗

圖42.3.2.1 FLASH模拟EEPROM實驗程式流程圖​

42.3.3 程式解析​

1. STMFLASH驅動代碼​

這裡我們隻講解核心代碼,詳細的源碼請大家參考CD光牒本實驗對應源碼。STMFLASH驅動源碼包括兩個檔案:stmflash.c和stmflash.h。 ​

stmflash.h頭檔案做了一些比較重要的宏定義,定義如下:​

/*****************************************************************************/​
/* FLASH起始位址 */​
#define STM32_FLASH_BASE 0x08000000  /* STM32 FLASH的起始位址 */​
#define STM32_FLASH_SIZE 0x20000  /* STM32 FLASH總大小 */​
#define BOOT_FLASH_SIZE 0x4000  /* 前16K FLASH用于儲存BootLoader */​
#define FLASH_WAITETIME 50000   /* FLASH等待逾時時間 */​

/* FLASH 扇區的起始位址,H750xx隻有BANK1的扇區0有效,共128KB */​
#define BANK1_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Bank1扇區0起始位址 */​
/*****************************************************************************/      

STM32_FLASH_BASE和STM32_FLASH_SIZE分别是FLASH的起始位址和FLASH總大小,這兩個宏定義随着晶片是固定的,我們開發闆的H750晶片的FLASH是128K位元組,是以STM32_FLASH_SIZE宏定義值為0x20000。​

比較重要的一個宏定義是BOOT_FLASH_SIZE,因為這個宏定義需要使用者根據自己工程的大小設定的。設定方法如下:首先編譯我們的工程,通過檢視.map檔案,查詢并計算出程式加載都STM32内部FLASH的指令記憶體的大小,最後根據這個大小來定義這個宏的值。這裡,我們直接編譯HAL庫實驗30 FLASH模拟EEPROM實驗的例程,然後如下圖操作:​

《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗

圖42.3.3 檢視程式加載占用FLASH的大小​

首先輕按兩下FLASH打開.map檔案,然後查詢到Execution Region ER_m_stmflash。如圖中所示,得到加載到STMFLASH的程式占用的記憶體大小約為14.2KB。​

是以,BOOT_FLASH_SIZE宏定義的值我們設定為0x4000(即16KB),表示預留STMFLASH的前16K的記憶體用于儲存BootLoader。在本工程中,BOOT_FLASH_SIZE宏定義的值要滿足幾個條件,一是必須大于14.2KB,二是必須是4的倍數,三是預留一定的空間給BootLoader執行過程的記憶體消耗。​

FLASH_WAITETIME是FLASH等待逾時時間的宏定義。​

BANK1_FLASH_SECTOR_0是Bank1扇區0起始位址,H750xx就隻有BANK1的扇區0有效,共128KB。​

下面我們開始介紹stmflash.c的程式,具體程式源碼如下:​

/**​
得到FLASH的錯誤狀态​
無​
錯誤代碼​
無錯誤​
其他, 錯誤編号​
 */​
static uint8_t stmflash_get_error_status(void)​
{​
 uint32_t res = 0;​
 res = FLASH->SR1;​

 if (res & (1 << 17)) return 1; /* WRPERR=1,寫保護錯誤 */​
 if (res & (1 << 18)) return 2; /* PGSERR=1,程式設計式列錯誤 */​
 if (res & (1 << 19)) return 3; /* STRBERR=1,複寫錯誤 */​
 if (res & (1 << 21)) return 4; /* INCERR=1,資料一緻性錯誤 */​
 if (res & (1 << 22)) return 5; /* OPERR=1,寫/擦除錯誤 */​
 if (res & (1 << 23)) return 6; /* RDPERR=1,讀保護錯誤 */​
 if (res & (1 << 24)) return 7; /* RDSERR=1,非法通路加密區錯誤 */​
 if (res & (1 << 25)) return 8; /* SNECCERR=1,1bit ecc校正錯誤 */​
 if (res & (1 << 26)) return 9; /* DBECCERR=1,2bit ecc錯誤 */​

 return 0; /* 沒有任何狀态/操作完成. */​
}​

/**​
等待操作完成​
要延時的長短​
錯誤代碼​
已完成​
錯誤代碼​
逾時​
 */​
static uint8_t stmflash_wait_done(uint32_t time)​
{​
 uint8_t res = 0;​
 uint32_t tempreg = 0;​

 while (1)​
 {​
 tempreg = FLASH->SR1;​

 if ((tempreg & 0X07) == 0)​
 {​
 break; /* BSY=0,WBNE=0,QW=0,則操作完成 */​
 }​

 time--;​

 if (time == 0)return 0XFF;​
 }​

 res = stmflash_get_error_status();​

 if (res)​
 {​
 FLASH->CCR1 = 0X07EE0000; /* 清所有錯誤标志 */​
 }​

 return res;​
}​

/**​
在FLASH指定位址寫8個字,即256bit​
必須以256bit為機關(32位元組)程式設計!!​
指定位址(此位址必須為4的倍數!!)​
要寫入的資料​
錯誤代碼​
寫入成功​
其他: 錯誤代碼​
 */​
static uint8_t stmflash_write_8word(uint32_t faddr, uint32_t *pdata)​
{​
 volatile uint8_t nword = 8; /* 每次寫8個字,256bit */​
 uint8_t res;​
 res = stmflash_wait_done(0XFFFF);​

 if (res == 0) /* OK */​
 {​
 FLASH->CR1 &= ~(3 << 4); /* PSIZE1[1:0]=0,清除原來的設定 */​
 FLASH->CR1 |= 2 << 4;  /* 設定為32bit寬 */​
 FLASH->CR1 |= 1 << 1;  /* PG1=1,程式設計使能 */​

 while (nword)​
 {​
 *(volatile uint32_t *)faddr = *pdata; /* 寫入資料 */​
 faddr += 4;  /* 寫位址+4 */​
 pdata++;  /* 偏移到下一個資料首位址 */​
 nword--;​
 }​
 __DSB();  /* 寫操作完成後,屏蔽資料同步,使CPU重新執行指令序列 */​
 res = stmflash_wait_done(0XFFFF); /* 等待操作完成,一個字程式設計,最多100us. */​
 FLASH->CR1 &= ~(1 << 1);/* PG1=0,清除扇區擦除标志 */​
 }​

 return res;​
}​

/**​
讀取指定位址的一個字(32位資料)​
要讀取的位址​
讀取到的資料​
 */​
uint32_t stmflash_read_word(uint32_t faddr)​
{​
 return *(volatile uint32_t *)faddr;​
}​

/**​
從指定位址開始寫入指定長度的資料​
特别注意:因為STM32H750隻有一個扇區(128K),是以我們規定:​
前16K留作BootLoader用​
後112K用作APP用,我們要做寫入測試,盡量使用16K以後的位址,否則容易出問題​
另外,由于寫資料時,必須是0XFF才可以寫入資料,是以不可避免的需要擦除扇區​
是以在擦除時需要先對前16K資料做備份儲存(讀取到RAM),然後再寫入,以保證​
前16K資料的完整性。且執行寫入操作的時候,不能發生任何中斷(凡是在寫入時執​
行内部FLASH代碼,必将導緻hardfault)。​
起始位址(此位址必須為32的倍數!!,否則寫入出錯!)​
資料指針​
字(32位)數(就是要寫入的32位資料的個數,一次至少寫入32位元組,即8個字)​
無​
 */​

/* FLASH 寫入資料緩存 */​
uint32_t g_flashbuf[BOOT_FLASH_SIZE / 4];​

void stmflash_write(uint32_t waddr, uint32_t *pbuf, uint32_t length)​
{​
 FLASH_EraseInitTypeDef flash_erase_init_handle;​
 HAL_StatusTypeDef hal_status = HAL_OK;​
 uint32_t SectorError = 0;​
 uint32_t addrx = 0;​
 uint32_t endaddr = 0;​
 uint16_t wbfcyc = BOOT_FLASH_SIZE/32;/* 寫bootflashbuf時,需要執行的循環數 */​
 uint32_t *wbfptr;​
 uint32_t wbfaddr;​
/* 寫入位址小于STM32_FLASH_BASE+BOOT_FLASH_SIZE,非法. */​
 if (waddr < (STM32_FLASH_BASE + BOOT_FLASH_SIZE))return; ​
 /* 寫入位址大于STM32總FLASH位址範圍,非法. */​
 if (waddr > (STM32_FLASH_BASE + STM32_FLASH_SIZE))return; ​

 if (waddr % 32)return; /* 寫入位址不是32位元組倍數,非法. */​

 HAL_FLASH_Unlock(); /* 解鎖 */​
 addrx = waddr; /* 寫入的起始位址 */​
 endaddr = waddr + length * 4; /* 寫入的結束位址 */​

 while (addrx < endaddr) /* 掃清一切障礙.(對非FFFFFFFF的地方,先擦除) */​
{​
/* 有非0XFFFFFFFF的地方,要擦除這個扇區 */​
 if (stmflash_read_word(addrx) != 0XFFFFFFFF) ​
 { ​
/* 讀出BOOT_FLASH_SIZE大小資料 */​
 stmflash_read(STM32_FLASH_BASE, g_flashbuf, BOOT_FLASH_SIZE / 4); ​
 INTX_DISABLE(); /* 禁止所有中斷 */​
/* 擦除類型,扇區擦除 */​
 flash_erase_init_handle.TypeErase = FLASH_TYPEERASE_SECTORS; ​
 flash_erase_init_handle.Sector = FLASH_SECTOR_0; /* 要擦除的扇區 */​
 flash_erase_init_handle.Banks = FLASH_BANK_1; /* 操作BANK1 */​
 flash_erase_init_handle.NbSectors = 1; /* 一次隻擦除一個扇區 */​
/* 電壓範圍,VCC=2.7~3.6V之間 */​
 flash_erase_init_handle.VoltageRange = FLASH_VOLTAGE_RANGE_3; ​
 hal_status =HAL_FLASHEx_Erase(&flash_erase_init_handle,&SectorError);​
 if (hal_status != HAL_OK)  /* 發生錯誤了 */​
 {​
 INTX_ENABLE();  /* 允許中斷 */​
 break;  /* 發生錯誤了 */​
 }​
 SCB_CleanInvalidateDCache();  /* 清除無效的D-Cache */​
 wbfptr = g_flashbuf;  /* 指向g_flashbuf首位址 */​
 wbfaddr = STM32_FLASH_BASE;  /* 指向STM32 FLASH首位址 */​
 while (wbfcyc)  /* 寫資料 */​
 {​
 if (stmflash_write_8word(wbfaddr, wbfptr)) /* 寫入資料 */​
 {​
 break;  /* 寫入異常 */​
 }​
 wbfaddr += 32;​
 wbfptr += 8;​
 wbfcyc--;​
 }​
 INTX_ENABLE();  /* 允許中斷 */​
 }​
 else​
 {​
 addrx += 4;  /* 偏移到下一個位置 */​
 }​
/* 等待上次操作完成 */​
 FLASH_WaitForLastOperation(FLASH_WAITETIME, FLASH_BANK_1);​
}​
/* 等待上次操作完成 */​
 hal_status = FLASH_WaitForLastOperation(FLASH_WAITETIME, FLASH_BANK_1); ​

 if (hal_status == HAL_OK)​
 {​
 while (waddr < endaddr) /* 寫資料 */​
 {​
 if (stmflash_write_8word(waddr, pbuf)) /* 寫入資料 */​
 {​
 break; /* 寫入異常 */​
 }​
 waddr += 32;​
 pbuf += 8;​
 }​
 }​
 HAL_FLASH_Lock(); /* 上鎖 */​
}​

/**​
從指定位址開始讀出指定長度的資料​
起始位址​
資料指針​
要讀取的字(32)數,即4個位元組的整數倍​
無​
 */​
void stmflash_read(uint32_t raddr, uint32_t *pbuf, uint32_t length)​
{​
 uint32_t i;​

 for (i = 0; i < length; i++)​
 {​
 pbuf[i] = stmflash_read_word(raddr); /* 讀取4個位元組. */​
 raddr += 4; /* 偏移4個位元組. */​
 }​
}​
/*****************************************************************************/​
/* 測試用代碼 */​

/**​
測試寫資料(寫1個字)​
起始位址​
要寫入的資料​
讀取到的資料​
 */​
void test_write(uint32_t waddr, uint32_t wdata)​
{​
 stmflash_write(waddr, &wdata, 1); /* 寫入一個字 */​
}      

該部分代碼,我們重點介紹一下stmflash_write函數,該函數用于在STM32H750的指定位址寫入指定長度的資料,有幾個要注意的點:​

  1. 寫入位址必須是在BOOT_FLASH_SIZE以後。​
  2. 寫入位址必須是32的倍數。​
  3. 單次寫入長度必須是32位元組的倍數(8個字)。​

第1點重點說明一下,BOOT_FLASH_SIZE是我們在stmflash.h裡面定義的一個宏定義,其值為:0X4000,即16K,也就是寫入資料必須在16K以後的位址(0X0800 0000 + 0X4000)寫入。因為STM32H750内部僅有1個扇區,為了友善做IAP應用,必須把這一個扇區分為2部分:IAP部分和APP部分,我們預留16K位址範圍給IAP,是以本例程寫入位址必須在16K以後,以便後面的IAP應用。​

另外,由于H750内部隻有1個扇區,在擦除扇區的時候,連帶所有資料都擦掉了(IAP也擦了),是以,為了能夠實作保留IAP的效果,我們在stmflash_write函數裡面,執行擦除扇區之前,會先備份前16K的資料,将前16K資料儲存到SRAM,然後再擦除扇區,擦除完了以後,再從SRAM裡面恢複這16K資料到扇區裡面去,這樣就可以實作類似擦除扇區但是仍保留前16K資料的效果。需要特别注意的是:在擦除扇區或寫入扇區資料的時候,不能執行任何内部FLASH上面的代碼!所有相關的代碼必須存放到外部QSPI FLASH。展現到本例程就是:stmflash.c的代碼,都應該存放到外部QSPI FLASH,而不能存放到H750内部FLASH。 ​

第2點和第3點則是由于STM32H7的FLASH特性,每次寫入必須是256位寬,也就是32位元組,是以寫入首位址必須是32位元組的倍數,且寫入資料長度必須是32位元組的倍數。 ​

另外,在STMFLASH_Write8Word函數裡面,有一個__DSB函數,該函數用于屏蔽資料同步,該函數在cmsis_armcc.h裡面定義,這裡在執行等待操作完成之前,必須調用該函數,否則将無法往FLASH寫入資料。 ​

由于我們使用了分散加載(qspi_code.scf),stmflash.c編譯後是自動存放到外部QSPI FLASH的,是以不需要做額外的設定。關于分散加載說明,詳見:8.2小節。​

2. main.c代碼​

在main.c裡面編寫如下代碼:​

/* 要寫入到STM32 FLASH的字元串數組 */​
const uint8_t g_text_buf[] = {"STM32 FLASH TEST"};​

#define TEXT_LENTH sizeof(g_text_buf) /* 數組長度 */​

/*SIZE表示字長(4位元組), 大小必須是4的整數倍, 如果不是的話, 強制對齊到4的整數倍 */​
#define SIZE TEXT_LENTH / 4 + ((TEXT_LENTH % 4) ? 1 : 0)​

/* 設定FLASH 儲存位址(必須大于使用者代碼區位址範圍,且為4的倍數) */​
#define FLASH_SAVE_ADDR 0X08004000 ​

int main(void)​
{​
 uint8_t key = 0;​
 uint16_t i = 0;​
 uint8_t datatemp[SIZE];​

 sys_cache_enable();  /* 打開L1-Cache */​
 HAL_Init();  /* 初始化HAL庫 */​
 sys_stm32_clock_init(240, 2, 2, 4);  /* 設定時鐘, 480Mhz */​
 delay_init(480);  /* 延時初始化 */​
 usart_init(115200);  /* 序列槽初始化為115200 */​
 usmart_dev.init(240);  /* 初始化USMART */​
 mpu_memory_protection();  /* 保護相關存儲區域 */​
 led_init();   /* 初始化LED */​
 lcd_init();   /* 初始化LCD */​
 key_init();   /* 初始化按鍵 */​

 lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);​
 lcd_show_string(30, 70, 200, 16, 16, "FLASH EEPROM TEST", RED);​
 lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);​
 lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write KEY0:Read", RED);​

 while (1)​
 {​
 key = key_scan(0);​

 if (key == KEY1_PRES) /* KEY1按下,寫入STM32 FLASH */​
 {​
 lcd_fill(0, 150, 239, 319, WHITE); /* 清除半屏 */​
 lcd_show_string(30, 150, 200, 16, 16, "Start Write FLASH....", RED);​
 stmflash_write(FLASH_SAVE_ADDR, (uint32_t *)g_text_buf, SIZE);​
/* 提示傳送完成 */​
 lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", RED); ​
 }​

 if (key == KEY0_PRES) /* KEY0按下,讀取字元串并顯示 */​
 {​
 lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH.... ", RED);​
 stmflash_read(FLASH_SAVE_ADDR, (uint32_t *)datatemp, SIZE);​
/* 提示傳送完成 */​
 lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is: ", RED); ​
/* 顯示讀到的字元串 */​
 lcd_show_string(30, 170, 200, 16, 16, (char*)datatemp, BLUE); ​
 }​

 i++;​
 delay_ms(10);​

 if (i == 20)​
 {​
 LED0_TOGGLE(); /* 提示系統正在運作 */​
 i = 0;​
 }​
 }​
}      

主函數代碼邏輯比較簡單,當檢測到按鍵KEY1按下後往FLASH指定位址開始的連續位址空間寫入一段資料,當檢測到按鍵KEY0按下後讀取FLASH指定位址開始的連續空間資料。最後,我們将stmflash_read_word和test_write函數加入USMART控制,這樣,我們就可以通過序列槽調試助手,調用STM32H750的FLASH讀寫函數,友善測試。​

42.4 下載下傳驗證​

将程式下載下傳到開發闆後,可以看到LED0不停的閃爍,提示程式已經在運作了。LCD顯示的内容如圖42.4.1所示:​

《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗

圖42.4.1程式運作效果圖​

通過先按KEY1按鍵寫入資料,然後按KEY0讀取資料,得到如圖42.4.2所示:​

《MiniPRO H750開發指南》第四十二章 FLASH模拟EEPROM實驗