Flash燒錄難題:CubeProgrammer實(shí)戰(zhàn)排查)
做低功耗數(shù)據(jù)采集產(chǎn)品的時(shí)候選型定了 STM32U5G9NJH6Q外掛一片 64MB 的 octal NOR FlashMX25LM51245G當(dāng)日志存儲(chǔ)。方案在板子上跑得倒是挺順真正折磨人的是生產(chǎn)燒錄環(huán)節(jié)ST-Link 連上芯片STM32CubeProgrammer 里選好外部存儲(chǔ)器按下 Start直接給你甩一個(gè)錯(cuò)誤彈窗。配套的官方教程只能覆蓋到官方開發(fā)板遇到定制板上的 OCTOSPI 問題網(wǎng)上能直接參考的實(shí)戰(zhàn)記錄很少最后只能靠一條條報(bào)錯(cuò)信息去反推。折騰了快兩天總算把整套流程跑通。這篇文章就是那兩天的完整記錄寫給同樣在 STM32U5 上用 OCTOSPI 外擴(kuò) Flash、又在 STM32CubeProgrammer 里卡住的朋友新手可以參考這里面的使用入門路徑老手可以直接跳到問題排查章節(jié)。1. 先搞清楚 STM32CubeProgrammer 和 OCTOSPI 之間是怎么配合的1.1 External Loader 機(jī)制到底是怎么回事STM32CubeProgrammer 本身并不認(rèn)識(shí)任何外部 Flash 芯片。它燒寫外部存儲(chǔ)器的原理是通過 SWD 調(diào)試接口往 MCU 的 RAM 里加載一個(gè)叫 External Loader外部加載器的固件然后讓這個(gè)固件在目標(biāo)芯片上跑起來再由 CubeProgrammer 通過調(diào)試接口給固件下發(fā)讀、寫、擦除命令。這個(gè)固件的格式是 .stldrSTM32CubeProgrammer 安裝目錄下自帶一批對(duì)應(yīng)的是各家官方開發(fā)板的配置。這個(gè)機(jī)制設(shè)計(jì)得很巧妙把“復(fù)雜的 Flash 操作”下沉到芯片內(nèi)部執(zhí)行上位機(jī)只負(fù)責(zé)發(fā)指令和收數(shù)據(jù)。但它也帶來了一個(gè)非常現(xiàn)實(shí)的問題你在 CubeProgrammer 里選擇外部存儲(chǔ)器時(shí)選的是某一個(gè) .stldr 文件這個(gè)文件里的所有配置——GPIO 分配、OCTOSPI 時(shí)鐘、Flash 型號(hào)對(duì)應(yīng)的命令序列——都是按官方板子寫死的。你的板子只要有一處和官方板不同loader 初始化 OCTOSPI 的時(shí)候就可能直接失敗或者初始化成功了但讀寫的數(shù)據(jù)是錯(cuò)的。我在 STM32U5G9NJH6Q 上遇到的那些報(bào)錯(cuò)根源基本都是這個(gè)。注意官方 .stldr 文件位于 STM32CubeProgrammer 安裝目錄的 bin/ExternalLoader 文件夾下常見默認(rèn)路徑是 C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\ExternalLoader。凡是名稱里帶 STM32U5G9J-DK1 字樣的都是針對(duì)官方開發(fā)板的定制板只能拿來當(dāng)參考不能直接復(fù)用。1.2 STM32U5 的 OCTOSPI 有什么特殊性STM32U5G9NJH6Q 這顆料主核是 Cortex-M33內(nèi)部有 4MB Flash、2.5MB SRAM最大的亮點(diǎn)是帶兩個(gè)支持 Octal 的 OCTOSPI 外設(shè)可以外掛大容量 NOR Flash 做代碼 XIP 或者數(shù)據(jù)日志存儲(chǔ)。和老的 QUADSPI 相比OCTOSPI 支持 1/2/4/8 線寬支持 SDR/DDR 雙沿采樣還帶 DQS 引腳反饋時(shí)序上復(fù)雜了不少。有兩個(gè)點(diǎn)特別容易讓開發(fā)者翻車。第一U5 系列默認(rèn)開了 TrustZone。OCTOSPI 的寄存器和它用到的 RAM 區(qū)域都分 secure / non-secure 屬性而 external loader 默認(rèn)運(yùn)行在 non-secure 域。如果你的 option bytes 里 TZEN 是打開的loader 要加載的 RAM 區(qū)域恰好被劃成了 secure那 CubeProgrammer 把 loader 寫進(jìn)內(nèi)存之后它一執(zhí)行就崩表現(xiàn)出來就是各種莫名其妙的加載失敗。如果產(chǎn)品不需要 TrustZone最省事的做法是直接在 CubeProgrammer 的 Option Bytes 頁面里把 TZEN 關(guān)掉代價(jià)是觸發(fā)一次整片擦除數(shù)據(jù)記得提前備份。第二OCTOSPI 的時(shí)鐘源和分頻配置比較講究。U5 的 OCTOSPI 內(nèi)核時(shí)鐘可以從 PLL1Q、HCLK 等多個(gè)來源取具體走哪條路徑看 RCC 配置。如果 PLL 沒配好OCTOSPI 時(shí)鐘可能超出 Flash 支持的范圍。很多 8 線 Flash 標(biāo)稱支持 200MHz但在 1.8V VCC 下實(shí)際時(shí)序余量沒你想的那么大所以先把頻率降下來跑通再考慮拉高頻。還有一個(gè)容易被忽略的點(diǎn)U5G9NJH6Q 是 TQFP216 封裝IO 非常多但每個(gè)引腳的 AF復(fù)用功能映射和官方開發(fā)板并不完全一致。官方 loader 用的那幾個(gè) GPIO在你的板子上可能是別的功能、甚至沒引出來。所以拿到一塊定制板的第一步永遠(yuǎn)是核對(duì)原理圖里 OCTOSPI 總線的實(shí)際走線再去數(shù)據(jù)手冊(cè)的 AF 映射表里查對(duì)應(yīng)關(guān)系不要想當(dāng)然。2. 我在 STM32U5G9NJH6Q 上踩過的三類典型問題2.1 連接階段就失敗No STM32 target found第一次拿到新板子接上 ST-Link打開 STM32CubeProgrammer點(diǎn)連接彈出來 No STM32 target found。這種問題我見過太多次按下面的順序排查基本能覆蓋 90% 的情況。首先看 NRST 有沒有接到 ST-Link 的 NRST 腳。CubeProgrammer 左側(cè)設(shè)置里有 Connect under reset 選項(xiàng)勾上之后調(diào)試器會(huì)在復(fù)位狀態(tài)下建立連接。如果用戶代碼里已經(jīng)把 PA13/PA14SWDIO/SWCLK復(fù)用成普通 GPIO 了用這種方式才能連上。前提是 NRST 必須連到調(diào)試器否則無法控制復(fù)位時(shí)序。其次查 option bytes。如果之前燒過 RDP讀保護(hù)等級(jí)RDP 不是 Level 0調(diào)試口會(huì)被鎖掉。RDP Level 1 還能靠 connect under reset 連上但無法讀寫Level 2 就直接永久鎖死只能換芯片。排查方法連接失敗時(shí)切到 Option Bytes 頁看能不能讀出來。如果讀出來發(fā)現(xiàn) RDP 是 Level 1 或 Level 2先把讀保護(hù)降級(jí)再繼續(xù)操作。還有一個(gè)很少被注意的點(diǎn)U5 有多路電源域。VDD 要供電、VDDA 模擬電源也要還有 VDDUSB、VDDIO2 這些獨(dú)立電源域。如果某一腳沒上電芯片可能進(jìn)入異常狀態(tài)。同時(shí) ST-Link 的 VTREF 參考電壓要和板子的 VDD 一致否則電平不匹配也會(huì)連接失敗。我當(dāng)時(shí)實(shí)際的處理是把 NRST 連線補(bǔ)上勾選 connect under reset問題立刻解決。后來才發(fā)現(xiàn)這塊板子之前跑測(cè)試程序時(shí)把 SWD 引腳占用了新程序里沒來得及改回 AF 功能導(dǎo)致第二次連接失敗。這個(gè)坑非常典型尤其是量產(chǎn)返修板十有八九是這個(gè)問題。2.2 能連上芯片但選擇外部存儲(chǔ)器時(shí)報(bào)錯(cuò)External loader not found / Init error連接芯片這一步過了接下來在外部存儲(chǔ)器下拉框里選擇工作區(qū)會(huì)遇到兩類報(bào)錯(cuò)。第一類是 External loader not found。說明 bin/ExternalLoader 目錄下沒有匹配的 .stldr 文件。要確認(rèn)你選的存儲(chǔ)區(qū)比如 OCTOSPI1 起始地址 0x90000000對(duì)應(yīng)的 loader 是否存在。如果板子是定制的八成沒有現(xiàn)成 loader只能自己寫后面第 3 節(jié)會(huì)詳述。第二類是 Init error 或 Read error。說明 loader 文件加載成功了但 loader 里的 Init() 函數(shù)返回失敗。Init() 做的工作通常包括初始化 OCTOSPI GPIO、配置 OCTOSPI 控制器、向 Flash 發(fā)讀 ID 命令、比對(duì) ID 是否正確。任何一步失敗都會(huì)導(dǎo)致加載器初始化失敗。這里給一個(gè)特別管用的排查手段不要靠瞎猜先用一個(gè)最小的 STM32 測(cè)試工程直接用 OCTOSPI 的 SDR 模式、標(biāo)準(zhǔn) SPI 命令比如 0x9F 讀 JEDEC ID把 Flash 的 ID 讀出來。如果讀得出來說明硬件鏈路供電、IO、時(shí)鐘、Flash 本身都是通的問題就在 loader 的配置。如果讀不出來先查硬件Flash 的 VCC 供電、WP 引腳是不是被拉低、CS 上拉是否正常。這個(gè)“先讀 ID”的習(xí)慣能省掉大量無效時(shí)間。我當(dāng)時(shí)調(diào)試的時(shí)候loader 里怎么配都報(bào) Init error后來一查發(fā)現(xiàn)是 Flash 的 VCC 標(biāo)稱 1.8V而板子上的 LDO 壞了實(shí)際電壓只有 1.2VFlash 根本沒法工作。這屬于硬件問題軟件配置再對(duì)也白搭。2.3 擦除、編程或校驗(yàn)階段報(bào)錯(cuò)Memory programming error / Data mismatch能初始化、能讀數(shù)據(jù)但一擦除一寫入就報(bào)錯(cuò)或者校驗(yàn)不通過。這類問題是 OCTOSPI 場(chǎng)景里最磨人的我把它拆成三個(gè)最常見的根因。第一個(gè)是 DDR 模式配置問題。8 線 NOR Flash 普遍支持 DDRDTR雙沿傳輸時(shí)序參數(shù)非常敏感。loader 里開了 DDR但 DQS 反饋信號(hào)沒接或者 DQS 時(shí)序配置不對(duì)寫入的數(shù)據(jù)就會(huì)錯(cuò)位。校驗(yàn)的時(shí)候報(bào)的地址通常在 0x90000000 附近和數(shù)據(jù)偏移量相關(guān)看起來很隨機(jī)。最粗暴有效的方法先把 loader 改成 SDR 模式跑通再回頭調(diào) DDR。不要一上來就追求高性能先把鏈路打通。第二個(gè)是寫保護(hù)沒解鎖。很多 NOR Flash 上電后默認(rèn)開啟寫保護(hù)需要發(fā) Write Enable0x06才能擦寫。如果 loader 的 Write 和 SectorErase 流程里沒有先發(fā)寫使能命令擦除可能靜默失敗或者編程時(shí)寫進(jìn)去的依然是 0xFF。這個(gè)問題很隱蔽因?yàn)閳?bào)錯(cuò)不一定出現(xiàn)但數(shù)據(jù)校驗(yàn)會(huì)失敗。第三個(gè)是時(shí)鐘頻率過高。前面說過1.8V 供電下 Flash 的實(shí)際頻率上限會(huì)降低。如果 loader 里配置的 OCTOSPI 時(shí)鐘是 100MHz但 Flash 在這個(gè)電壓下只能穩(wěn)定跑 66MHz就會(huì)出現(xiàn)偶發(fā)性錯(cuò)誤。錯(cuò)誤可能不是每次都出現(xiàn)而是在某個(gè)地址段反復(fù)失敗。生產(chǎn)線上這種“一會(huì)兒好一會(huì)兒壞”的問題最可怕直接把頻率降到標(biāo)稱值的 50% 再試。心得OCTOSPI 相關(guān)問題調(diào)試優(yōu)先級(jí)永遠(yuǎn)是先 READ 后 WRITE先 SDR 后 DDR先低頻后高頻。按這個(gè)順序能把變量的維度從 5 個(gè)降到 1 個(gè)排查效率提升非常明顯。3. 從零適配一塊板載 OCTOSPI Flash 的完整記錄3.1 用 STM32CubeMX 生成 External Loader 基礎(chǔ)工程如果你的板子沒有現(xiàn)成 loader就得自己寫一個(gè)。ST 的 STM32CubeU5 固件包里通常自帶 External Loader 的參考工程位置一般在 Projects/STM32U5G9J-DK1/Applications/ExternalLoader 下不同版本固件包路徑略有差異。先把它拷貝出來作為藍(lán)本。這個(gè)工程的核心是幾個(gè)函數(shù)簽名由 STM32CubeProgrammer 的加載器協(xié)議固定int Init(void); // 初始化 OCTOSPI 和 Flash 芯片 uint32_t Read(uint32_t Address, uint32_t Size, uint8_t *Buffer); // 讀數(shù)據(jù) uint32_t Write(uint32_t Address, uint32_t Size, uint8_t *Buffer); // 頁編程 uint32_t SectorErase(uint32_t StartAddress, uint32_t Size); // 扇區(qū)擦除 uint32_t MassErase(void); // 全片擦除還有一個(gè)描述 Flash 特性的結(jié)構(gòu)體CubeProgrammer 會(huì)用它來規(guī)劃整個(gè)地址空間和操作粒度const LoaderS Loader { MY_BOARD_OCTOSPI_MX25LM51245G, // loader 名稱會(huì)顯示在 CubeProgrammer 下拉框里 0x90000000UL, // 外部存儲(chǔ)器起始地址 0x04000000UL, // 存儲(chǔ)器容量64MB 0x00000100UL, // 編程頁大小256 字節(jié) 0x00001000UL, // 擦除塊大小4KB ... Init, Read, Write, SectorErase, MassErase, };生成工程后第一件事是核對(duì)芯片型號(hào)和 RAM 起始地址。STM32U5 的 SRAM 在 TZEN 打開時(shí)loader 需要放在 non-secure 可訪問的區(qū)域典型地址是 0x30000000 起的 non-secure alias。如果 TZEN 關(guān)閉直接用 0x20000000 這個(gè)常規(guī)區(qū)域就行。另外CubeProgrammer 對(duì) loader 的 API 版本有檢查如果提示 Old API version多半是你 loader 工程里的 STM32_ExternalLoader.h 頭文件版本和當(dāng)前 CubeProgrammer 不匹配去固件包里找對(duì)應(yīng)版本替換即可。3.2 修改 GPIO、時(shí)鐘和 Flash 命令配置工程生成好之后重點(diǎn)改三塊配置。第一塊是 GPIO。打開 STM32U5G9NJH6Q 的數(shù)據(jù)手冊(cè)找到 OCTOSPI1 的 Alternate Function 映射表對(duì)照你的原理圖把 IO0~IO7、CLK、CS、DQS 對(duì)應(yīng)的 GPIO 引腳和 AF 號(hào)全部填到 CubeMX 里。每個(gè) IO 都要配置成高速 push-pull上下拉狀態(tài)按 Flash 手冊(cè)要求設(shè)置一般是默認(rèn)帶上拉更穩(wěn)。這里特別提醒一句網(wǎng)上很多教程直接抄官方板子的引腳放到自己的板子上未必成立務(wù)必以自己原理圖和 AF 表為準(zhǔn)。第二塊是時(shí)鐘。OCTOSPI 內(nèi)核時(shí)鐘建議先從 PLL1Q 取頻率先設(shè)保守一點(diǎn)比如 50MHz。等整條鏈路跑通了再根據(jù) Flash