第四十二章 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所示:
表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的解鎖序列為:
- 寫0X45670123到FLASH_KEYR
- 寫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所示:
-
圖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所示:
-
圖42.1.3.2 FLASH_KEYR1寄存器
該寄存器主要用來解鎖FLASH_CR,必須在該寄存器寫入特定的序列(KEY1和KEY2)解鎖後,才能對FLASH_CR寄存器進行寫操作。
- 存儲區1的FLASH控制寄存器(FLASH_CR1)存儲區1的FLASH控制寄存器描述如圖42.1.3.3所示:
-
圖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所示:
圖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 程式流程圖
圖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實驗的例程,然後如下圖操作:
圖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的指定位址寫入指定長度的資料,有幾個要注意的點:
- 寫入位址必須是在BOOT_FLASH_SIZE以後。
- 寫入位址必須是32的倍數。
- 單次寫入長度必須是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所示:
圖42.4.1程式運作效果圖
通過先按KEY1按鍵寫入資料,然後按KEY0讀取資料,得到如圖42.4.2所示: