FLASH閃存
程序現象:
1、讀寫內部FLASH
這個代碼的目的,就是利用內部flash程序存儲器的剩余空間,來存儲一些掉電不丟失的參數。所以這里的程序是按下K1變換一下測試數據,然后存儲到內部FLASH,按下K2把所有參數清0,最后OIED顯示一下。
這里OLED顯示Flag、Data 。Flag當做標志位,內容定義為:A5A5。Flag作用:判斷之前是不是存儲過數據,如果存儲過,直接讀取;如沒有存儲過,就先初始化。
Data:是要掉電存儲的數據。
2,讀取芯片ID
在stm32里,指定地址下,存儲有原廠寫入的ID號,使用指針的方式讀取,就可以得到ID號。
OLED可以看到,有2個ID號:第一個是F_SIZE,表示芯片FLASH容量。0040表示FLASH容量,單位是KB,換成十進制是64KB。
第二個是U_ID,是96位芯片唯一ID號,每個芯片ID號都不一樣。
STM32內部FLASH閃存簡介
讀寫FLASH的用途:
利用程序存儲器的剩余空間來保存掉電不丟失的用戶數據
通過在程序中編程(IAP),實現程序的自我更新
-
第一個用途,對于我們這個C8T6芯片來說,它的程序存儲器容量是64K,一般我們寫個簡單的程序,可能就只占前面的很小一部分空間,剩下的大片空余空間我們就可以加以利用,比如存儲一些我們自定義的數據,而且可以充分利用資源。不過這里要注意我們在選取存儲區域時,一定不要覆蓋了原有的程序,要不然程序自己把自己給破壞了,一般存儲少量的參數,我們就選最后幾頁存儲就行了、
第二個用途,通過在程序中編程IAP,實現程序的自我更新。我們在存儲用戶數據時要避開程序本身,以免破壞程序,但如果我們就非要修改程序本身,這會發生什么呢,在程序中編程,利用程序來修改程序本身,實現程序的自我更新,這個在程序中編程就是IAP,是用來實現程序升級的。
在線編程( ICP):這個JTAG SWD就是仿真器下載程序,每次下載都是把整個程序完全更新掉,那系統加載程序就是系統存儲器的BOOTLOADER,也就是串口下載,串口下載也是更新整個程序,這就是我們一直在用的ICP下載方式。
-
在程序中編程( IAP),它可以使用微控制器支持的任意一種通信接口下載程序。實現過程:
比如這是整個程序存儲器,我們首先需要自己寫一個BOOTLOADER程序,并且存放在程序更新時不會覆蓋的地方,然后需要更新程序時,我們控制程序跳轉到這BOOTLOADER這里來,在這里面我們就可以接收任意一種通訊接口傳過來的數據,比如串口、USB、藍牙轉串口、WIFI轉串口等等,這個傳過來的數據就是待更新的程序,然后我們控制flash讀寫,把收到的程序寫入到前面程序正常運行的地方,寫完之后再控制程序跳轉回正常運行的地方,或者直接復位,這樣程序就完成了自我升級。
區別:IAP就是和系統存儲器這個的BOOTLOADER一樣。系統存儲器的BOOTLOADER,只能配置boot引腳觸發啟動。而我們自己寫BOOTLOADER的話,就可以想怎么收怎么收,并且在整個升級過程,程序都可以自主完成。
回顧一下存儲器影像
閃存模塊組織
C8T6芯片的閃存容量是64K,屬于中容量產品。
-
這里分為了三個塊,第一個是主存儲器,也是程序存儲器,用來存放程序代碼的,這是最主要也是容量最大的一塊。
第二個是信息塊,里面又可以分為啟動程序代碼和用戶選擇字節,其中啟動程序代碼就是系統存儲器,存放的是原廠寫入BOOTLOADER用于串口下載。
第三塊是閃存存儲器接口寄存器,這一塊的存儲器實際上并不屬于閃存,地址都是40開頭的,說明這個存儲器接口寄存器就是一個普通的外設,和之前講的GPIO定時器、串口等等都是一個性質的東西,這些存儲器它們的存儲介質也都是SRAM。
這個閃存存儲器接口寄存器,就上面主存儲器和信息塊的管理員,閃存存儲器接口寄存器就是用來控制擦除和編程這個過程的。
-
對于主存儲器,這里對它進行了分頁,分頁是為了更好的管理閃存,擦除和寫保護都是以頁為單位的。這點和之前W25Q64芯片的閃存一樣,寫入前必須擦除,擦除必須以最小單位進行,擦除后數據位全變為1,數據只能1寫0,不能0寫1,擦除和寫入之后都需要等待忙,這些都是一樣的。
這里就比較簡單了,它只有一個基本單位就是頁,每一頁的大小都是1K,0到127總共128頁,總量就是128K,對于C8T6來說,它只有64K,所以C8T6的頁只有一半0~63總共64頁共64K,然后看一下頁的地址范圍。
所以地址只要以000、400、800、400,結尾的都一定是頁的起始地址。
FLASH基本結構
-
整個閃存分為程序存儲器、系統存儲器和選項字節三部分,這里程序存儲器為以C8T6為例,它是64K的,所以總共只有64頁,最后一頁的起始地址是0800FC00。
左邊這里是閃存存儲器接口,手冊里還有個名稱,閃存編程和擦除控制器LPEC。
然后閃存存儲器接口,可以對程序存儲器進行擦除和編程,也可以對選項字節進行擦除和編程,系統存儲器是不能擦除和編程的。
這個選項字節里面有很大一部分配置位,其實是配置主程序存儲器的讀寫保護的,所以右邊畫的寫入選項字節,可以配置程序存儲器的讀寫保護。
如何操作這個控制器FPEC,來對程序存儲器和選項字節進行擦除和編程。
FLASH解鎖
-
首先第一步是flash解鎖,這個flash操作之前需要解鎖,目的都是為了防止誤操作,是通過在鍵寄存器寫入指定的鍵值來實現,使用鍵寄存容器的好處就是更能防止誤操作,每一個指令必須輸密碼才能完成,叫密鑰寄存器。
首先FPEC共有三個鍵值,也就是三把開鎖的鑰匙,RDPRT鍵是解除讀保護的密鑰,值是0XA5這些值呢,實際上是隨便定義的,只要你定義的不是很簡單就行。
-
解鎖:第一個是復位后FPEC被保護,不能寫入FLASH_CR:也就是復位后flash默認是鎖著的。
在FLASH_KEYR鍵寄存器中,先寫入KEY1,再寫入KEY2解鎖,這個鎖是KEYR寄存器,所以這個有兩道鎖,解鎖的話,要先用K1鑰匙解,再用K2鑰匙解,最終才能解鎖成功,所以非人為情況下基本不可能解鎖。
然后,就是錯誤的操作序列會在下次復位前鎖死FPEC和FLASH_CR:于是他發現有程序在嘗試撬鎖時,一旦沒有先寫入KEY1,再寫入KEY2,整個模塊就會完全鎖死,除非復位,這是整個解鎖操作。
-
加鎖:我們操作完成之后,要盡快把flash重新加鎖,以防止意外情況,加鎖的操作是設置FLASH_CR中的LOCK位鎖住FPEC和FLASH_CR:就是控制寄存器里面有個LOCK位,我們在這一位寫1就能重新鎖住閃存。
使用指針訪問存儲器
uint16_t Data = *((__IO uint16_t *)(0x08000000));
第一步,讀取給定存儲器器的地址,比如0x08000000例,讀取這個地址下的數據。
第二步,在地址前面加上強制類型轉換,轉換成uint16_t * 。__IO 在stm32庫函數中,這是宏定義。
#define __IO volatile,這個宏定義對應C語言關鍵字, volatile。在uint16_t *數據類型前加volatile,起一個保障作用,目的是,防止編譯器優化。
第三步,使用*號,指針取內容,把這個指針指向的存儲器取出來了,這個值就是指定存儲器的值,取出來可以把它賦值給自定義變量Data,這樣就完成指定地址讀的任務。
如果你這個地址寫的是SRAM的地址,比如0X20000000,那可以直接寫入了,因為SRAM在程序運行時是可讀可寫的,這是使用指針訪問存儲器的C語言代碼。其中讀取可以直接讀,寫入需要解鎖,并且執行后面的流程。
程序存儲器全擦除
第一步是讀取lock位,看一下芯片鎖沒鎖,如果lock位=1鎖住了,就執行解鎖過程,解鎖過程就是:在KEYR寄存器先寫入KEY1,再寫入KEY2;如果lock位=0當前沒鎖住,就不用解鎖了。這是流程圖里給的解鎖步驟,如果鎖住了就解鎖,如果沒鎖住就不用解鎖。但是在庫函數中并沒有這個判斷,庫函數是直接執行解鎖過程,管你鎖沒鎖都執行解鎖。
第二步,然后繼續解鎖之后,首先置控制寄存器里的MER(Mass Erase)位為1,然后再置STRT(Start)位為1,其中STRT為1是觸發條件,STRT為1之后芯片開始干活,然后現在看到MER位是1,它就知道接下來要干的活就是全擦除,這樣內部電路就會自動執行全擦除的過程。
第三步,然后繼續擦除,擦除過程開始后,程序要執行等待,判斷狀態寄存器的BSY(Busy)位是否為1,BSY位表示芯片是否處于忙狀態,BSY位為1表示芯片忙,所以這里如果判斷BSY位等于1,就跳轉回來繼續循環判斷,直到BSY位=0跳出循環。
第四步:讀出并驗證所有頁的數據,這個是測試程序才要做的,如要全讀出來驗證一下,這個工作量太大了,所以這里的最后一步就不管了,正常情況下全刪除完成了,我們默認就成功了,這是全擦除的流程。
程序存儲器頁擦除
第一步,是解鎖的流程。
第二步,這個方框里的,置控寄存器的PER(Page Erase)位為1;在AR(Address Register)地址寄存器中選擇要擦除的頁,最后置控制寄存器的STRT位為1,置STRT位為1,也是觸發條件,芯片開始干活,然后芯片看到PER等于1,它就知道接下來要執行頁擦除。
然后閃存不止一頁,頁擦除芯片就要知道要具體擦哪一頁,所以它會繼續看AR寄存器的數據,AR寄存器我們要提前寫入一個頁的起始地址,這樣芯片就會把我們指定的一頁給擦除掉。
第三步,然后擦除開始之后,我們也要等待BSY位,最后讀出并驗證數據。
程序存儲器編程
閃存的寫入
閃存的寫入:擦除之后我們就可以執行寫入的流程了,另外說明一下,STM32的閃存,在寫入之前會檢查指定地址有沒有擦除,如果沒有擦除就寫入STM32則不執行寫入操作,除非寫入的全是0,這個數據是例外,因為不擦除就寫入,可能會寫入錯誤。
第一步,是解鎖。
第二步,置控制寄存器的PG(Programming)位為1,表示我們即將寫入數據。
第三步,在指定的地址寫入半字,這一步我們需要*((__IO uint16_t *)(0x08000000)) = 0x1234;
使用指針在指定地址寫入數據。
-
注意:寫入操作只能以半字的形式寫入,在STM32中有幾個術語,字、半字和字節,其中字word就是32位數據,半字half word就是16位數據,字節byte就是8位數據。那這里只能以半字寫入,意思就是只能以16位的形式寫入,一次性,寫入兩個字節,如果你要寫入32位,就分兩次完成。
如果你只要寫入8位,比較麻煩了,如果你想單獨寫入一個字節,還要保留另一個字節的原始數據的話,那只能把整頁數據都讀到SRAM,再隨意修改SRAM數據,修改全部完成之后,再把整頁都擦除,最后再把整頁都寫回去。
所以如果你想像SRAM一樣隨心所欲的讀寫,那最好的辦法就,先把閃存的一頁讀到SRAM中,讀寫完成后再擦除一頁,整體寫回去。
第四步,寫了半字之后,芯片會處于忙狀態,我們等待一下BUSY清0,這樣寫入數據的過程就完成。那每執行這樣一個流程,只能寫入一個半字,如果要寫出很多數據,要不斷循環調用這個流程就可以。
選項字節
圖里的起始地址,就是選項字節的起始地址,1FFF800,這塊的這些數據,就前面這里這個表的這一行,里面總共只有16個字節,把這些存儲器給展開,就這個圖。
-
這里是對應的16個字節,其中有一半的名稱前面都帶了個N,比如RDP和nRDP
,USER和nUSER等,意思,是你在寫入RDP數據時,要同時在NRDP寫入數據的反碼,要在帶N的對應的存儲器寫入反碼,這樣寫入操作才是有效的。如果芯片檢測到,這兩個存儲器不是反碼的關系,那就代表數據無效有錯誤,對應的功能就不執行,這是一個安全保障措施。但這個寫入反碼的過程,硬件會自動計算并寫入。
那然后每個存儲器的功能,去掉所有帶n的,剩下八個字節存儲器:
-
第一個RDP(Read Protect)是,讀保護配置位,解釋,在RDP存儲器寫入RDPRT鍵,然后解除讀保護,如果RDP不是A5,那閃存就是讀保護狀態,無法通過調試器讀取程序,避免程序被別人竊取。
第二個字節USER,可以配置硬件看門狗和進入停機待機模式是否產生復位。
第三個和第四個字節Data0/1,這個在芯片中沒有定義功能,用戶可自定義使用。
最后四個字節,WRP(WriteProtect)0、1、2、3這四個字節配置的是寫保護,在中容量產品里是每一個位對應保護四個存儲頁,四個字節總共32位,一位對應保護四頁,總共保護32×4等于128頁,正好對應中容量量的最大128頁。
小容量產品
手冊2.5選項字節說明
對于小容量產品,也是每一位對應保護四個存儲頁,但小容量產品最大只有32K,所以只需要一個字節WRP0就行,4×8=32,其他三個字節沒用到。
大容量產品
然而對于大容量產品,每一個位只能保護兩個存儲頁,這樣的話四個字節就不夠用了,所以這里規定WRP3的最高位,這一位直接把剩下的所有頁一起都保護了,這是寫保護的定義。
選項字節擦除
選項字節擦除:
第一步,其實也是解鎖閃存,這里文字并沒有寫。
第二步,這里文字版的流程多了一步,檢查SR的BSY位,以確認沒有其他正在進行的閃存操作,這個實際上就是事前等待,如果當前已經在忙了,我先等一下。
第三步,解鎖CR的OPTWRE(Option Write Enable)位,這一步是選項字節的解鎖,選項字節里面還有一個單獨的鎖,在解鎖閃存后,還需要再解鎖選項字節的鎖,之后才能操作選項字節。
解鎖選項字節:看(前面閃存模塊組織圖),整個閃存的鎖是KEYR,里面選項字節的小鎖是下面的OPTKEYR(Option Key Register),解鎖這個小鎖也是類似的流程,我們需要在OPTKEYR里先寫入KEY1,再寫入KEY2,這樣就能解鎖選項字節的小鎖了。
第四步,繼續解除小鎖之后,設置CR的OPTER(Option Erase)位為1,表示即將擦除選項字節。
第五步,設置CR的STRT位為1,觸發芯片開始干活,這樣芯片就會啟動擦除選項字節的工作。
第六步,之后等待BUSY位變為0,擦除選項字節就完成了。
選項字節編程
先檢測BSY,
然后解除小鎖,
之后設置CR的OPTPG(Option Programming)位為1,表示即將寫入選項字節,
再之后寫入要編程的半字到指定的地址,這個是指針寫入操作,
最后等待忙,這樣寫入選項字節就完成了。
器件電子簽名
電子簽名其實就是STM32的ID號,它的存放區域是系統存儲器,它不僅有BOOTLOADER程序,還有幾個字節的ID號,系統存儲器起始地址是1FFFF000。
-
第一個是閃存容量存儲器,基地址是1FFF
F7E0,通過地址也可以確定它的位置,就是系統存儲器,這個存儲器的大小是16位,它的值就是閃存的容量單位是KB。第二個是產品唯一身份標識寄存器,就是每個芯片的身份證號,這個數據存放的基地址是1FFFF7E8,大小是96位,每一個芯片的這96位數據,都是不一樣的。
使用這個唯一ID號,可以做一些加密的操作:比如你想寫入一段程序,只能在指定設備運行,那也可以在程序的多處,加入ID號判斷,如果不是指定設備的ID號,就不執行程序功能。這樣即使你的程序被盜,在別的設備上也難以運行,這是STM32的電子簽名。
閃存編程手冊
在編程過程中,任何讀寫閃存的操作都會使CPU暫停,直到此閃存編程結束。
這是讀寫內部閃存存儲數據的一個弊端,忙的時候代碼執行會暫停,因為執行代碼需要讀閃存,閃存在忙沒法讀,所以CPU也就沒法運行了,程序就會暫停。這會導致,假如你使用內部閃存存儲數據,同時你的中斷代碼又在頻繁執行,這樣讀寫閃存的時候,中斷代碼就無法執行了,這可能會導致中斷無法及時響應。
選項字節編程
閃存控制寄存器(FLASH_CR)