使用 MDK 下載程序時,我們可能需要在 Debug 設(shè)置的 Flash Download 子選項卡中,選擇編程算法,不過如果我們已經(jīng)安裝了芯片包(DFP 包)以后,就可以直接得到對應(yīng)的編程算法,并不需要我們?nèi)バ薷乃?。但是在面對以下某些情況時,我們依然要掌握這一技能,
①:當(dāng)你是一個芯片包的開發(fā)者時;
②:或者你有獨(dú)特的下載需求(比如在你的程序里加一些校驗信息,更改選項字節(jié));
③:或者某些芯片發(fā)布的過早,還沒有對應(yīng)的芯片包時;
這些時候就需要去了解學(xué)習(xí)如何制作 FLM 文件,如圖 1 所示(以億道 Emdoor 為例):

圖 1
二、制作FLM
(1)、如圖 2 所示,制作 FLM 所需要的 Keil 工程文件,可以在 Keil 的安裝目錄下找到,總共有 2 個地方可以找到,路徑如下所示:
①:F:Keil_MDK539ARMFlash_Template
②:F:Keil_MDK539PacksARMCMSIS5.9.0Device_Template_Flash


圖 2
(2)、進(jìn)入模版工程以后,如圖 3 可以看到,其實(shí)整個工程在結(jié)構(gòu)上只有 3 個主要文件:(如何理解并編寫 Flash 編程算法代碼請看本文最后部分)
①:FlashPrg.c
②:FlashDev.c
③:FlashOS.H
接下來,我們對這 3 個文件,逐個進(jìn)行解析。

圖 3
(3)、FlashPrg.c
在這個 C 文件里,需要我們包含一些必需的 Flash 編程函數(shù):Init, UnInit, EraseSector, 和 ProgramPage. 然后除了這些必需函數(shù),可以根據(jù)設(shè)備功能(或為了加快執(zhí)行速度),可以實(shí)現(xiàn) EraseChip、BlankCheck 和 Verify 功能。
這 7 個函數(shù)的功能,分別是:
Init 必需函數(shù) 初始化,并準(zhǔn)備設(shè)備以進(jìn)行 Flash 編程。
UnInit 必需函數(shù) 完成某個 Flash 編程步驟后,取消微控制器的初始化。
EraseSector 必需函數(shù) 刪除指定扇區(qū)的 Flash 內(nèi)存內(nèi)容。
ProgramPage 必需函數(shù) 將應(yīng)用程序?qū)懭氲?Flash 內(nèi)存中。
BlankCheck 可選函數(shù) 檢查并比較數(shù)據(jù)模式。
EraseChip 可選函數(shù) 刪除整個 Flash 內(nèi)存的內(nèi)容。
Verify 可選函數(shù) 將 Flash 內(nèi)存內(nèi)容與程序代碼進(jìn)行比較。
模版文件中的函數(shù)示例如圖 4 所示,可以看到,有 4 個必需函數(shù),和 1 個 EraseChip 函數(shù),留給我們往其中增加一些操作代碼:

圖 4
(4)、FlashDev.c
在這個 C 文件里,如圖 5 所示,里面定義了一個 FlashDevice 結(jié)構(gòu)體,這個結(jié)構(gòu)體里面的參數(shù)用途,是我們必須要知道,這是非常重要的,涉及到后續(xù)很多的操作步驟,每一行的代碼描述如下所示:

圖 5
第 29 行:FLASH_DRV_VERS, //代表驅(qū)動版本,不要輕易修改
第 30 行:"New Device 256kB Flash", //設(shè)備名稱,這里其實(shí)就是在勾選 Flash 燒寫算法時,會被軟件調(diào)用并顯示的內(nèi)容,如下圖 6 所示:

圖 6
第 31 行:ONCHIP, //設(shè)備類型,片上 Flash,也有外部 Flash 選項
第 32 行:0x00000000, //設(shè)備起始地址,stm32 在這里一般就是 0x08000000
第 33 行:0x00040000, //設(shè)備大小,以字節(jié)為單位,這里是 256kB,根據(jù)實(shí)際容量填寫即可
第 34 行:1024, //編程頁面大小,因為 Flash 存儲是按照 頁(Page) 進(jìn)行編程的,頁面太小會增加管理復(fù)雜度,浪費(fèi)存儲空間。頁面太大則會導(dǎo)致小數(shù)據(jù)寫入時增加不必要的操作時間。1024 字節(jié)大小可以在寫速率和耗時之間達(dá)到良好的平衡。而且頁面越大,寫入過程中發(fā)生斷電或干擾導(dǎo)致數(shù)據(jù)出錯的概率越高;而 1024 字節(jié)大小已被證實(shí)在實(shí)際應(yīng)用中具有較高的可靠性。
第 35 行: 0, //保留位,必須為 0
第 36 行:0xFF, //擦除后內(nèi)存的初始內(nèi)容,即所有位均為 1,主要是由 Flash 的工作原理和硬件設(shè)計決定的。
第 37 行: 100, //編程頁面超時時間(單位:毫秒)防止程序陷入死循環(huán),因為編程和擦除操作通常需要通過輪詢硬件狀態(tài),如 Flash 的 BSY 標(biāo)志位,來判斷操作是否完成。(后續(xù)講解代碼時會有體現(xiàn))
第 38 行: 3000, // 擦除扇區(qū)超時時間(單位:毫秒)防止程序陷入死循環(huán),因為編程和擦除操作通常需要通過輪詢硬件狀態(tài),如 Flash 的 BSY 標(biāo)志位,來判斷操作是否完成。(后續(xù)講解代碼時會有體現(xiàn))
第 41 行:0x002000, 0x000000, //扇區(qū)大小 8kB(8 個扇區(qū)),第一個參數(shù)代表 8KB,第二個參數(shù)代表起始地址為 0x000000,因為第 42 行的代碼規(guī)定了 64KB 的扇區(qū)是從 0x010000 地址開始的,所以留給 8KB 的扇區(qū)只有地址為 0x000000 從 0x00FFFF 之間的空間,即 64KB,所以就有 8 個 8KB 的扇區(qū)。
第 42 行:0x010000, 0x010000, //扇區(qū)大小 64kB (2 個扇區(qū)),第一個參數(shù)代表 64KB,第二個參數(shù)代表起始地址為 0x010000,因為第 43 行的代碼規(guī)定了 8KB 的扇區(qū)是從 0x030000 地址開始的,所以留給 64KB 的扇區(qū)只有地址為 0x010000 從 0x02FFFF 之間的空間,即 128KB,所以就有 2 個 64KB 的扇區(qū)。
第 43 行:0x002000, 0x030000, //扇區(qū)大小 8kB (8 個扇區(qū)),第一個參數(shù)代表 8KB,第二個參數(shù)代表起始地址為 0x030000,因為第 33 行的代碼規(guī)定了整個設(shè)備的大小為 0x040000,所以留給 8KB 的扇區(qū)只有地址為 0x030000 從 0x03FFFF 之間的空間,即 64KB,所以就有 8 個 8KB 的扇區(qū)。
第 44 行:SECTOR_END//標(biāo)識符,用于標(biāo)記扇區(qū)配置表的結(jié)束,通常出現(xiàn)在定義 Flash 扇區(qū)大小和地址的數(shù)組或結(jié)構(gòu)體的最后一項,用于通知程序或驅(qū)動解析到此處時不再繼續(xù)解析后續(xù)數(shù)據(jù)。
(5)、FlashOS.H
這個頭文件中,如圖 7 所示,其實(shí)只是一些宏定義和函數(shù)的聲明之類,沒有什么介紹的必要了,讀懂前面兩個 C 文件以后,這個頭文件里的內(nèi)容自然就會明白了。

圖 7
(6)、了解完以上這些文件內(nèi)的代碼后,就可以著手自己編寫一個 Flash 編譯算法了,不過在編寫修改 FlashPrg,F(xiàn)lashDev 之前,我們可能需要將處理器架構(gòu)選擇并配置好,如下圖,圖 8 所示:(多目標(biāo)+ifdef 的方式即可)

圖 8
以及還要在配置中,設(shè)置位置獨(dú)立代碼,如下圖圖 9 所示:

圖 9
(7)、之后就可以調(diào)整 FlashPrg 中的編程算法,F(xiàn)lashDev 中的設(shè)備參數(shù)了。調(diào)整完算法和參數(shù)之后,可以如圖 10 所示一樣,將輸出文件的名字更改一下,方便在目錄中查找最終的文件:

圖 10
(8)、點(diǎn)擊編譯后,keil 就會在編譯完成后,調(diào)用 user 欄中的 cmd.exe /C copy "Objects%L" ".@L.FLM"命令,在項目目錄下,創(chuàng)建一個 MyDevice.FLM 文件,如圖 11 所示:

圖 11
(9)、接著把我們做好的 FLM 文件,放到 DFP 包的 Flash 路徑下,(這里直接就放在 stm32f1 系列下面了),如圖 12 所示:

圖 12
(10)、接著打開 DFP 包中的 PDSC 文件(這個文件的屬性默認(rèn)是只讀的,記得去掉勾選),路徑如下圖 13 所示:

圖 13
(11)、在 PDSC 文件中,我們可以看到所有 F1 系列芯片的 Flash 燒寫算法文件,我們?nèi)绻胍砑右粋€自己的芯片進(jìn)去,就可以在如圖 14 這里,添加并命名我們的設(shè)備,附上對應(yīng)的 FLM 文件,即可在 keil 軟件重啟后,看到我們添加的芯片了,對應(yīng)的,也可以找到我們這個芯片的 Flash 編程算法了,如圖 15 所示:

圖 14

圖 15
(12)、本文再往后面的內(nèi)容,是官網(wǎng)提供的一個簡單的編程算法示例(stm32f1 系列的 flash 編程算法,基本 90%和官網(wǎng)提供的相似,可以對照著一起看),我將對這個代碼進(jìn)行一些解讀,方便大家快速學(xué)會一個簡單的基礎(chǔ),并借此編寫出具有更多功能的 Flash 編譯算法。
Init函數(shù)
Init 函數(shù)用于初始化微控制器以進(jìn)行 Flash 編程。當(dāng)嘗試將程序下載到 Flash 時,將調(diào)用此函數(shù)。

參數(shù):
adr:設(shè)備基地址——clk:時鐘頻率 (以 Hz 為單位)——fnc:功能代碼
含義:
第 4 行:FLASH->ACR = 0x00000000;
FLASH->ACR 是 Flash 訪問控制寄存器(Access Control Register),用于配置 Flash 訪問的等待周期。設(shè)置為 0x00000000 表示配置 零等待狀態(tài),即 CPU 訪問 Flash 時無需額外的等待周期。
第 7 行:FLASH->KEYR = FLASH_KEY1;
第 8 行:FLASH->KEYR = FLASH_KEY2;
寫入兩個解鎖密鑰 FLASH_KEY1 和 FLASH_KEY2 到 FLASH->KEYR(Flash 密鑰寄存器)。FLASH_KEY1 和 FLASH_KEY2 是特定的解鎖值,用于解除 Flash 寫保護(hù);解鎖后可以對 Flash 進(jìn)行寫入或擦除操作。如果沒有正確解鎖,后續(xù)的編程或擦除操作將無法執(zhí)行。如圖 16 所示,F(xiàn)1 系列的 FLM 文件中已給出密鑰的宏定義,同樣,在 STM32F10xxx 閃存編程參考手冊中,也有記錄。


圖 16
第 11 行:if ((FLASH->OBR & 0x04) == 0x00)
FLASH->OBR 是 Flash 的選項字節(jié)寄存器(Option Byte Register),其中位 2 用于指示獨(dú)立看門狗(IWDG)的運(yùn)行模式:
如果位 2 為 0,表示 IWDG 處于硬件模式(HW mode),始終運(yùn)行。
如果位 2 為 1,表示 IWDG 處于軟件模式(SW mode),可以通過軟件控制使能或禁用。
這里主要檢查是否 IWDG 在硬件模式下運(yùn)行,如下圖 17 所示:

圖 17
第 13 行: IWDG->KR = 0x5555; // Enable write access to IWDG_PR and IWDG_RLR
第 14 行: IWDG->PR = 0x06; // Set prescaler to 256
第 15 行: IWDG->RLR = 4095; // Set reload value to 4095
這里是在配置獨(dú)立看門狗(IWDG)超時時間,如果 IWDG 在硬件模式下運(yùn)行,配置其超時時間為 約 26.2144 秒:配置了較長的超時時間以適應(yīng) Flash 編程可能需要較長時間完成。防止 IWDG 在 Flash 編程時誤觸發(fā)系統(tǒng)復(fù)位;
IWDG->KR 寫入 0x5555,是表示允許對 IWDG 的預(yù)分頻寄存器(IWDG_PR)和重裝載寄存器(IWDG_RLR)進(jìn)行寫操作。如圖 18 所示:

圖 18
IWDG->PR 寫入 0x06 是為了配置預(yù)分頻器值為 256(分頻系數(shù)為 2^8),將時鐘周期放大到較低的頻率。如圖 19 所示。
IWDG->RLR 寫入 4095 配置重裝載值為最大值 4095。如圖 20 所示。
設(shè)置完成以后,就可以根據(jù)公式,計算出 IWDG 的超時時間為:256*4096/40000=26.2144s,具體計算可以參考圖 21 所示。

圖 19

圖 20

圖 21
UnInit函數(shù)

參數(shù):
fnc:功能代碼
含義:
第 4 行:FLASH->CR |= FLASH_LOCK;
反初始化,它的實(shí)現(xiàn)很簡單,主要完成以下任務(wù):FLASH->CR |= FLASH_LOCK,其實(shí)就是鎖定 Flash 模塊,因為 FLASH_LOCK 是 Flash 控制寄存器(FLASH->CR)中的一個位,它的作用是鎖定 Flash 模塊,以防止未經(jīng)授權(quán)的寫操作。在鎖定狀態(tài)下,F(xiàn)lash 不允許進(jìn)行寫操作(如擦除或編程),只能進(jìn)行讀取操作。如圖 22 所示:

圖 22
EraseSector函數(shù)
函數(shù) EraseSector 刪除從參數(shù) adr 指定的地址開始的扇區(qū)的內(nèi)容。每當(dāng)使用 uVision 菜單 Flash - Erase 時,或者每當(dāng)嘗試將程序下載到 Flash 并且已在 Flash 下載設(shè)置對話框中設(shè)置了選項 Erase Sectors 時,都會調(diào)用該函數(shù)。

參數(shù):
adr:扇區(qū)地址
含義:
第 3 行: FLASH->CR |= FLASH_PER;
啟用頁擦除功能,如圖 25 所示,F(xiàn)LASH_PER 的值在圖 23 中。
第 4 行:FLASH->AR = adr;
FLASH->AR 是一個地址寄存器,用于指定要擦除的 Flash 頁面(或扇區(qū))的起始地址。如圖 24 所示。
第 5 行:FLASH->CR |= FLASH_STRT;
啟動擦除操作,F(xiàn)LASH_STRT 是控制寄存器中的啟動位。當(dāng)該位被置位時,F(xiàn)lash 模塊開始執(zhí)行擦除操作。Flash 硬件會根據(jù)地址寄存器的值自動確定擦除的范圍。如圖 25 所示。

圖 23

圖 24

圖 25
第 7 行: while (FLASH->SR & FLASH_BSY) {
等待擦除完成,F(xiàn)LASH_BSY 是 Flash 狀態(tài)寄存器(FLASH->SR)中的一位,表示 Flash 當(dāng)前正在執(zhí)行擦除或編程操作。通過循環(huán)檢查該位,程序可以等待擦除操作完成。如圖 26 所示。
圖 26
第 8 行: IWDG->KR = 0xAAAA;
在循環(huán)中,使用 IWDG->KR = 0xAAAA 喂狗,避免擦除時間過長觸發(fā)看門狗復(fù)位(IWDG 是獨(dú)立看門狗模塊)。如圖 27 所示。


圖 27
第 11 行:FLASH->CR &= ~FLASH_PER;
關(guān)閉頁擦除功能,如圖 25 所示,擦除操作完成后,將 FLASH_PER 位清零,關(guān)閉頁面擦除模式,防止后續(xù)誤操作。FLASH_PER 的值在圖 23 中。
ProgramPage函數(shù)
ProgramPage 函數(shù)用于將數(shù)據(jù)寫入 Flash 存儲器。它在下載程序到 Flash 時被調(diào)用。由于 Flash 存儲器通常按塊或頁組織,因此函數(shù) ProgramPage 的參數(shù)不能跨越這些 Flash 頁的對齊邊界。頁大小由結(jié)構(gòu)體 FlashDevice 的 ProgramPageSize 值指定。
參數(shù):
adr 頁的起始地址——sz 頁的大小——buf 待寫入的數(shù)據(jù)
含義:
第 3 行:sz = (sz + 1) & ~1;
Flash 的編程單元是 半字(16 位,2 字節(jié)),因此寫入的大小 sz 必須是偶數(shù)。如果輸入的大小是奇數(shù),通過 (sz + 1) & ~1 向上對齊為偶數(shù)。
第 5 行: while (sz) {
循環(huán)寫入數(shù)據(jù),循環(huán)處理輸入緩沖區(qū)的每個半字,直到所有數(shù)據(jù)寫入完成。
第 7 行:FLASH->CR |= FLASH_PG;
啟用編程功能 ,F(xiàn)LASH_PG 是控制寄存器中的一位,用于啟用 Flash 編程模式。在寫入數(shù)據(jù)之前,必須啟用此功能。如圖 28 所示。

圖 28
第 9 行:M16(adr) = *((unsigned short *)buf);
M16(adr) 是一個宏,用于訪問指定地址處的 16 位(半字)數(shù)據(jù),如圖 29 所示。等號右邊將緩沖區(qū)中的數(shù)據(jù)(2 字節(jié),前面提到過)強(qiáng)制轉(zhuǎn)換為 unsigned short 類型,然后寫入到目標(biāo)地址。寫入操作會自動觸發(fā) Flash 的編程機(jī)制。

圖 29
第 10 行:while (FLASH->SR & FLASH_BSY);
等待編程完成(不再多說,前面有類似的提到過)
第 12 行:FLASH->CR &= ~FLASH_PG;
禁用編程功能(不再多說,前面有類似的提到過)
第 15 行:if (FLASH->SR & (FLASH_PGERR | FLASH_WRPRTERR)) {
第 16 行:FLASH->SR |= FLASH_PGERR | FLASH_WRPRTERR;
第 17 行:return (1);
如果狀態(tài)寄存器中存在以下錯誤標(biāo)志,則說明寫入失?。喝鐖D 30 和 31 所示
FLASH_PGERR:編程錯誤,可能是寫入地址不對齊。
FLASH_WRPRTERR:寫保護(hù)錯誤,目標(biāo)區(qū)域被保護(hù),無法寫入。
即:如果發(fā)生錯誤,清除錯誤標(biāo)志并返回 1 表示失敗。

圖 30

圖 31
第 21 行: adr += 2;
第 22 行: buf += 2;
第 23 行: sz -= 2;
這一段其實(shí)就是循環(huán)移動到下一個半字地址,更新目標(biāo)地址 adr,緩沖區(qū)指針 buf,以及剩余大小 sz:
即:地址加 2 字節(jié)(一個半字)、緩沖區(qū)指針移動到下一個 2 字節(jié)數(shù)據(jù)、寫入大小減少 2 字節(jié)。
三、討論分析
問 ①:任何版本的 MDK 都可以制作 FLM 文件嗎?
答 ①:MDK-Lite 不支持創(chuàng)建 Flash 編程算法。如圖 32 所示:

圖 32
四、結(jié)論
本文詳細(xì)的介紹了 Flash 編程算法,主要講解了如何制作和使用 FLM 文件。以及詳細(xì)的算法代碼解析,通過這篇文檔,芯片開發(fā)者可以更好地理解如何進(jìn)行 Flash 編程操作。對應(yīng)本文開頭,可以方便想要制作 pack 包的開發(fā)人員,做一些獨(dú)特下載的開發(fā)人員,和盡快上手調(diào)試未上市的高新芯片,都有極大的幫助。