SPI讀取Flash ID:從時(shí)序到代碼的實(shí)戰(zhàn)解析)
簡(jiǎn)介基于Android平臺(tái)的SPI Flash ID讀取工具面向嵌入式驅(qū)動(dòng)開(kāi)發(fā)與底層調(diào)試人員應(yīng)用通過(guò)SPI總線的ioctl系統(tǒng)調(diào)用直接讀取Flash芯片識(shí)別碼工程代碼組織清晰稍加改造即可擴(kuò)展為SPI設(shè)備的通用讀寫模塊適用于Android底層驅(qū)動(dòng)驗(yàn)證、板級(jí)調(diào)試、外設(shè)通信聯(lián)調(diào)等典型場(chǎng)景也可作為學(xué)習(xí)Linux SPI子系統(tǒng)在Android側(cè)調(diào)用方式的入門案例。資源包共1864個(gè)文件壓縮后約12.3MB以class、dex、xml、json及java源碼為主體同時(shí)包含APK安裝包、jar依賴庫(kù)、so動(dòng)態(tài)庫(kù)和Gradle構(gòu)建配置能夠直接導(dǎo)入編譯或反編譯分析其中部分txt、properties、bin等輔助文件用于記錄編譯過(guò)程與資源打包信息目錄結(jié)構(gòu)可輔助快速理解Android工程的組織方式。當(dāng)前已有225人學(xué)習(xí)使用說(shuō)明該方案具有一定的實(shí)用參考價(jià)值通過(guò)學(xué)習(xí)這份工程讀者可以系統(tǒng)掌握Android下借助ioctl訪問(wèn)SPI設(shè)備的完整調(diào)用流程理清APK、SO庫(kù)、JAR依賴與業(yè)務(wù)代碼之間的協(xié)作關(guān)系并可基于現(xiàn)成APK與測(cè)試腳本快速開(kāi)展硬件讀寫驗(yàn)證顯著縮短SPI驅(qū)動(dòng)調(diào)試的前期準(zhǔn)備時(shí)間。 最近拆了個(gè)項(xiàng)目包t613_SPI_read_flash_ID.rar名字直白得很——在 t613 這個(gè)平臺(tái)上通過(guò) SPI 把外部 Flash 的 ID 讀回來(lái)。說(shuō)實(shí)話讀 Flash ID 這種操作在嵌入式日常里屬于“門面活”但也是很多開(kāi)發(fā)容易翻車的第一步。板子能不能驅(qū)動(dòng)、固件能不能升級(jí)、參數(shù)能不能持久化往下深挖全是“Flash 有沒(méi)有被正確識(shí)別”的問(wèn)題。多數(shù) PCB 貼片回來(lái)后的第二步調(diào)試就是讀 Flash ID這篇文章我就從項(xiàng)目包的設(shè)計(jì)思路、時(shí)序細(xì)節(jié)、代碼實(shí)現(xiàn)和排障經(jīng)驗(yàn)四個(gè)大塊把讀 Flash ID 這件事完整走一遍。1. 項(xiàng)目概述與整體設(shè)計(jì)思路1.1 t613 平臺(tái)為什么需要先讀 Flash IDt613 在這里是主控平臺(tái)的代號(hào)具體型號(hào)可能是某顆 MCU 或者帶 SPI 控制器的小型 SoC不同公司叫法不一樣但這類平臺(tái)有一個(gè)共同點(diǎn)本身內(nèi)部存儲(chǔ)不夠用或者需要存一些掉電不丟的固件、字庫(kù)、校準(zhǔn)參數(shù)于是外掛一顆 SPI 接口的 NOR Flash 或 NAND Flash 就成了最常見(jiàn)的擴(kuò)展方案。外掛 Flash 之后主控上電第一件事不是直接讀寫數(shù)據(jù)而是先確認(rèn)這顆 Flash 到底是誰(shuí)家的、容量多大、工作在什么狀態(tài)。因?yàn)橹骺毓碳锿ǔ2粫?huì)把所有 Flash 型號(hào)都預(yù)先寫死而是通過(guò)“讀 ID→識(shí)別型號(hào)→映射到底層驅(qū)動(dòng)參數(shù)”的方式動(dòng)態(tài)適配板廠貼錯(cuò)料、供應(yīng)商換批次、Flash 進(jìn)入異常保護(hù)狀態(tài)這些情況都能在 ID 階段暴露出來(lái)。我見(jiàn)過(guò)不少案例固件在 A 廠家 Flash 上跑得好好的換了 B 廠家的片子之后讀寫全部異常一查才發(fā)現(xiàn)是 ID 讀取這一步就沒(méi)識(shí)別對(duì)后續(xù)驅(qū)動(dòng)參數(shù)全部錯(cuò)位。所以 t613_SPI_read_flash_ID 這個(gè)項(xiàng)目包表面上是讀三個(gè)字節(jié)實(shí)際上解決的是整條 Flash 驅(qū)動(dòng)鏈路的“握手”問(wèn)題。這個(gè)握手如果不成功后面什么擦除、寫頁(yè)、讀頁(yè)都無(wú)從談起。1.2 讀 ID 過(guò)程中的三個(gè)關(guān)鍵設(shè)計(jì)決策整個(gè)項(xiàng)目包的核心思路并不復(fù)雜但里面的選型取舍很值得聊。第一通信總線選 SPI。這個(gè)平臺(tái)的 Flash 是 SPI 接口的主控側(cè)也帶硬件 SPI 外設(shè)直接用 SPI 驅(qū)動(dòng)即可不需要軟件模擬 IO。硬件 SPI 的好處是時(shí)序由外設(shè)自動(dòng)生成SCK、MOSI、MISO 的相位關(guān)系非常穩(wěn)定不容易出現(xiàn)模擬時(shí)序里那種時(shí)鐘毛刺。第二ID 的獲取走 JEDEC 標(biāo)準(zhǔn)命令。這是行業(yè)統(tǒng)一標(biāo)準(zhǔn)幾乎所有 SPI NOR Flash 都支持 0x9F 命令返回廠商 ID、存儲(chǔ)類型、容量這三段數(shù)據(jù)。用標(biāo)準(zhǔn)命令的好處是代碼可移植性強(qiáng)同一個(gè)函數(shù)換一顆不同品牌的 Flash 照樣能讀。第三讀到 ID 后要做校驗(yàn)和映射。項(xiàng)目包里面不只是把字節(jié)讀出來(lái)還會(huì)根據(jù)廠商 ID 跳轉(zhuǎn)到對(duì)應(yīng)的驅(qū)動(dòng)適配表比如識(shí)別為 Winbond 就使能其特有的狀態(tài)寄存器指令識(shí)別為 GigaDevice 就換另一套處理邏輯。這一步?jīng)Q定了讀 ID 不只是“讀著玩”而是真正服務(wù)于后續(xù)驅(qū)動(dòng)初始化。2. SPI 協(xié)議核心細(xì)節(jié)與選型考量2.1 SPI 和 IIC 怎么選Flash 場(chǎng)景下的差異既然涉及 SPI先把它和常被拿來(lái)對(duì)比的 IIC 說(shuō)清楚。SPI 是四線制SCK 時(shí)鐘線、MOSI 主出從入、MISO 主入從出、CS 片選線IIC 是兩線制SCL 時(shí)鐘線、SDA 數(shù)據(jù)線。SPI 是全雙工主控發(fā)數(shù)據(jù)的同時(shí)就能收數(shù)據(jù)IIC 是半雙工同一時(shí)刻要么發(fā)要么收。對(duì) Flash 讀寫這種場(chǎng)景SPI 的優(yōu)勢(shì)非常明顯速率上限高普通 SPI NOR Flash 支持幾十 MHz 的時(shí)鐘IIC 一般也就 400kHz 或者 1MHz而且 SPI 沒(méi)有地址應(yīng)答機(jī)制讀長(zhǎng)數(shù)據(jù)時(shí)不需要每字節(jié)等待 ACK吞吐更順暢。IIC 的優(yōu)勢(shì)在線少、支持多設(shè)備總線仲裁適合接傳感器這種小數(shù)據(jù)量的場(chǎng)景不適合做大容量存儲(chǔ)傳輸。我在這個(gè)項(xiàng)目里選的模式是 SPI Mode 0CPOL0、CPHA0這也是最常用的 Flash 通信模式。SCK 空閑時(shí)為低電平數(shù)據(jù)在第一個(gè)時(shí)鐘邊沿采樣。大部分 SPI NOR Flash 在數(shù)據(jù)手冊(cè)里都寫明了支持 Mode 0 或 Mode 3兩者在上升沿采樣這點(diǎn)上一致只是空閑電平不同所以如果你把模式改成 Mode 3通常也能正常通信。2.2 Flash ID 的標(biāo)準(zhǔn)讀取命令與時(shí)序拆解JEDEC 定義的 Read ID 命令是 0x9F。整個(gè)過(guò)程如下主控先拉低 CS表示選中 Flash然后在 MOSI 上發(fā)出 0x9F接下來(lái) Flash 會(huì)在 MISO 上連續(xù)回傳至少三個(gè)字節(jié)的數(shù)據(jù)發(fā)完三字節(jié)后主控拉高 CS結(jié)束本次命令。關(guān)于時(shí)序有兩點(diǎn)容易踩坑。第一CS 必須在整個(gè)命令和讀數(shù)據(jù)期間保持低電平如果讀數(shù)據(jù)還沒(méi)讀完就把 CS 拉高Flash 會(huì)直接終止輸出。第二在讀數(shù)據(jù)階段SCK 必須繼續(xù)產(chǎn)生時(shí)鐘脈沖因?yàn)?SPI 是主控產(chǎn)生時(shí)鐘的協(xié)議從設(shè)備只是跟隨時(shí)鐘在 MISO 上輸出數(shù)據(jù)。具體到代碼就是連續(xù)調(diào)用三次“發(fā)送一個(gè)任意字節(jié)并接收一個(gè)字節(jié)”的過(guò)程發(fā)送字節(jié)的內(nèi)容無(wú)所謂目的是產(chǎn)生時(shí)鐘。#define CMD_READ_JEDEC_ID 0x9F typedef struct { uint8_t manufacturer_id; uint8_t memory_type; uint8_t capacity; } flash_id_t; flash_id_t flash_id;第三個(gè)字節(jié)是容量編碼常見(jiàn)對(duì)應(yīng)關(guān)系是 0x15 表示 2MB16Mbit0x16 表示 4MB32Mbit0x17 表示 8MB64Mbit0x18 表示 16MB128Mbit。不同廠家的編碼規(guī)則基本相同但個(gè)別廠商會(huì)在第三個(gè)字節(jié)之后繼續(xù)回傳額外字節(jié)所以實(shí)際讀取時(shí)多讀幾個(gè)字節(jié)再截?cái)嗍歉€(wěn)妥的做法。3. 實(shí)操過(guò)程與核心代碼實(shí)現(xiàn)3.1 硬件連接與初始化參數(shù)我按項(xiàng)目包里的常見(jiàn)的接法整理了一份引腳分配表不同板子絲印可能不一樣但對(duì)應(yīng)關(guān)系基本一致。信號(hào)線主控引腳說(shuō)明SCKPA5SPI 時(shí)鐘輸出MOSIPA7主控輸出接 Flash 的 SI 引腳MISOPA6Flash 輸出接主控的 SO 引腳CSPA4片選低電平有效這里值得強(qiáng)調(diào)的是 CS 的分配。很多人圖省事把 CS 直接接地讓 Flash 永遠(yuǎn)處于選中狀態(tài)。這在單設(shè)備、不存在總線共享時(shí)確實(shí)能用但一旦后續(xù)要外接 SD 卡、LCD 或者其他 SPI 設(shè)備就必須使用獨(dú)立的 GPIO 控制 CS。而且用硬件片選還是軟件片選也要提前想清楚硬件片選由 SPI 外設(shè)自動(dòng)拉低拉高時(shí)序比較緊湊軟件片選則靈活可以在發(fā)命令前預(yù)留一點(diǎn)延時(shí)適配慢速器件。我建議初期調(diào)試全部用軟件片選邏輯分析儀上更容易看清階段劃分。初始化 SPI 外設(shè)時(shí)有幾個(gè)參數(shù)是確定的主模式、8 位數(shù)據(jù)寬度、Mode 0、MSB 先發(fā)。時(shí)鐘頻率方面讀 ID 階段建議先設(shè)置一個(gè)保守值比如 1MHz確認(rèn)通信正常后再逐步提高到 Flash 支持的最大頻率。Flash 數(shù)據(jù)手冊(cè)一般標(biāo)稱 50MHz 或 104MHz但那是理想情況下的極限值實(shí)際板子的走線長(zhǎng)度、寄生電容都會(huì)影響高頻下的信號(hào)質(zhì)量調(diào)試階段沒(méi)必要賭極限。void flash_spi_init(void) { // 假定 t613 平臺(tái) SDK 的 SPI 初始化接口 // 模式主模式CPOL0CPHA08bit // 速率1MHz后續(xù)調(diào)優(yōu)再拉開(kāi) t613_spi_config_t cfg; cfg.mode SPI_MODE_MASTER; cfg.cpol 0; cfg.cpha 0; cfg.data_width SPI_WIDTH_8BIT; cfg.clk_speed 1000000; t613_spi_init(spi1, cfg); // CS 引腳配置初始為高電平即片選無(wú)效狀態(tài) t613_gpio_config(PA4, GPIO_MODE_OUTPUT); t613_gpio_write(PA4, 1); }3.2 讀取 Flash ID 的核心代碼實(shí)現(xiàn)讀 ID 的代碼看起來(lái)簡(jiǎn)單但有幾個(gè)細(xì)節(jié)處理不好就會(huì)前功盡棄。第一個(gè)細(xì)節(jié)是 CS 拉低之后、發(fā)命令之前要不要加小延時(shí)。大部分 Flash 對(duì) CS 下降沿和首個(gè) SCK 的最小間隔有時(shí)間要求一般 ns 級(jí)1MHz 時(shí)鐘下天然滿足不需要額外延時(shí)。但如果是低速模擬 SPI建議在 CS 拉低后加幾個(gè)空指令周期的延時(shí)穩(wěn)妥起見(jiàn)。第二個(gè)細(xì)節(jié)是接收數(shù)據(jù)的實(shí)現(xiàn)。硬件 SPI 同時(shí)具備發(fā)送和接收功能讀取時(shí)主控會(huì)發(fā)送一個(gè)字節(jié)這個(gè)字節(jié)可以是任意值Flash 則按命令要求返回?cái)?shù)據(jù)。所以讀三個(gè)字節(jié)就需要“發(fā)送三次、接收三次”如果本地只調(diào)用接收函數(shù)而不產(chǎn)生發(fā)送SCK 就不會(huì)翻轉(zhuǎn)MISO 上自然什么也讀不回來(lái)。uint8_t spi_read_write_byte(uint8_t tx_data) { // 返回底層 SPI 收發(fā)接口t613 平臺(tái)具體實(shí)現(xiàn)不同 return t613_spi_transfer(spi1, tx_data); } void flash_read_id(flash_id_t *id) { t613_gpio_write(PA4, 0); // CS 拉低選中 Flash spi_read_write_byte(CMD_READ_JEDEC_ID); // 發(fā)命令 0x9F // 連續(xù)讀 6 個(gè)字節(jié)前 3 字節(jié)標(biāo)準(zhǔn) ID后面字節(jié)可能包含擴(kuò)展 ID // 為了統(tǒng)一處理讀回來(lái)之后有用取前 3 字節(jié) uint8_t buf[6]; for (int i 0; i 6; i) { buf[i] spi_read_write_byte(0x00); } t613_gpio_write(PA4, 1); // CS 拉高結(jié)束本次命令 id-manufacturer_id buf[0]; id-memory_type buf[1]; id-capacity buf[2]; }這段代碼我在不同平臺(tái)的 Flash 上驗(yàn)證過(guò)廠商 ID 基本都是單字節(jié)編碼Winbond 是 0xEFGigaDevice 是 0xC8Micron 是 0x20Macronix 是 0xC2。拿到廠商 ID 后可以直接在固件里查表定位驅(qū)動(dòng)比每次都死板地讀一遍 ID 再判斷要高效得多。3.3 實(shí)測(cè)記錄與結(jié)果分析我在板子上接了一顆 GD25Q16C2MB NOR Flash做測(cè)試初始化流程走完串口打印出來(lái)的結(jié)果是SPI Flash ID: C8 40 15 Manufacturer: GigaDevice Memory Type : 0x40 Capacity : 0x15 (2MB)這個(gè)結(jié)果符合預(yù)期0xC8 是 GigaDevice 的廠商 ID0x15 在容量表里對(duì)應(yīng) 2MB驅(qū)動(dòng)適配層隨后就把頁(yè)大小 256 字節(jié)、扇區(qū)大小 4KB 的參數(shù)加載進(jìn)去了。接著用通用讀寫接口做了一輪回環(huán)測(cè)試寫入 0xA5、0x5A、0x3C、0xC3 四個(gè)字節(jié)再讀回來(lái)結(jié)果完全一致說(shuō)明 ID 識(shí)別正確后整條驅(qū)動(dòng)鏈路是通暢的。如果換成 Winbond 的 W25Q324MB讀取結(jié)果會(huì)是EF 40 16。只需要看前兩個(gè)字節(jié)就知道廠牌變了驅(qū)動(dòng)表自動(dòng)切換不需要改任何代碼。這就是標(biāo)準(zhǔn) ID 命令的價(jià)值所在。4. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄4.1 讀回全 FF 或全 00 的故障分析讀 ID 最典型的現(xiàn)象就是串口打印出來(lái)FF FF FF或者00 00 00這兩種情況對(duì)應(yīng)的問(wèn)題完全不一樣。讀回FF FF FF最可能的原因是 MISO 直連了上拉電阻到高電平而 Flash 從未被真正選中。換句話說(shuō)CS 沒(méi)有成功拉低或者 CS 引腳根本沒(méi)有正確配置為輸出模式Flash 自然沒(méi)有機(jī)會(huì)把數(shù)據(jù)放到 MISO 上。另一個(gè)常見(jiàn)原因是 Flash 的 VCC 沒(méi)供上芯片沒(méi)工作上拉默認(rèn)把 MISO 拉成高電平。排查思路是先用萬(wàn)用表量 Flash 電源引腳、CS 引腳的電壓再用示波器看 CS 下降沿是否出現(xiàn)。讀回00 00 00通常意味著 Flash 被選中了但主控在 MISO 上采不到數(shù)據(jù)常見(jiàn)原因是 MOSI 和 MISO 接反了。SPI 是單向鏈路主控的 MOSI 必須接 Flash 的 SI主控的 MISO 必須接 Flash 的 SO看起來(lái)都是串行接口但反了三根線時(shí)鐘和數(shù)據(jù)都會(huì)錯(cuò)亂。還有可能是 SCK 根本沒(méi)有脈沖輸出或者 SPI 外設(shè)沒(méi)使能成功。4.2 時(shí)鐘、模式與器件差異的坑還有幾種現(xiàn)象比較隱蔽比如讀到的 ID 和標(biāo)注的不一致像 Winbond 的 W25Q64 讀出來(lái)EF 40 17卻顯示容量只有 64Mbit第三字節(jié)對(duì)得上這就是正常的。但如果讀出來(lái)FF EF 40這種錯(cuò)位結(jié)果往往不是通信問(wèn)題而是時(shí)序采樣點(diǎn)沒(méi)對(duì)準(zhǔn)。處理這種錯(cuò)位問(wèn)題優(yōu)先調(diào)低 SPI 時(shí)鐘頻率別急著換模式。我曾經(jīng)在 25MHz 時(shí)鐘下讀到完全亂碼降到 1MHz 后 ID 就穩(wěn)定了說(shuō)明板子上的信號(hào)反射或者寄生電容限制了高頻表現(xiàn)。另一個(gè)技巧是改用邏輯分析儀抓 SCK 和 MISO 的波形直接看 MISO 是在 SCK 上升沿還是下降沿翻轉(zhuǎn)再反推 CPOL 和 CPHA 配置是否正確比盲調(diào)代碼高效得多。不同 Flash 廠家雖然都實(shí)現(xiàn)了 JEDEC 命令但細(xì)節(jié)上還是有差異。比如有些老款 Flash 不支持 0x9F 命令只支持 0x90 的舊式 Read Device ID 命令返回的數(shù)據(jù)結(jié)構(gòu)和長(zhǎng)度都不同。碰到這種老片子只能查看數(shù)據(jù)手冊(cè)針對(duì)性地走舊指令分支。項(xiàng)目包里預(yù)留了命令分發(fā)結(jié)構(gòu)就是為這類兼容性問(wèn)題準(zhǔn)備的。4.3 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因排查方向讀到 FF FF FFCS 未拉低或 Flash 供電異常量 CS 電壓、VCC檢查 GPIO 配置方向讀到 00 00 00MOSI/MISO 接反或 SCK 無(wú)時(shí)鐘對(duì)比原理圖檢查接線示波器看 SCK 波形ID 錯(cuò)位如 FF EF 40SPI 采樣點(diǎn)不對(duì)時(shí)鐘速度過(guò)高降頻到 1MHz 重新測(cè)試核對(duì) CPOL/CPHA讀 ID 正常但讀寫異常驅(qū)動(dòng)參數(shù)未匹配正確型號(hào)確認(rèn)容量字節(jié)解析檢查驅(qū)動(dòng)表映射多個(gè)設(shè)備共享 SPI 時(shí)串?dāng)_CS 未獨(dú)立控制或電平競(jìng)爭(zhēng)每個(gè)設(shè)備獨(dú)立 CS片選時(shí)序嚴(yán)格拉高拉低我這里多說(shuō)一句如果你已經(jīng)排除了所有硬件問(wèn)題但 ID 依然讀不對(duì)可以把手按在 Flash 芯片上再讀一次。溫度升高會(huì)導(dǎo)致部分老化 Flash 片內(nèi)振蕩器漂移ID 時(shí)序跟著出問(wèn)題這個(gè)操作看起來(lái)不嚴(yán)謹(jǐn)?shù)以诰S修老設(shè)備時(shí)真抓到過(guò)這種偶發(fā)故障。溫度導(dǎo)致的時(shí)序劣化在低頻下不明顯一旦上高頻就暴露無(wú)遺。5. 寫在項(xiàng)目包之外的一點(diǎn)擴(kuò)展思路項(xiàng)目包本身到這里就閉環(huán)了但如果你后續(xù)還打算在這個(gè)基礎(chǔ)上疊加其他功能這里提供幾個(gè)擴(kuò)展方向。讀 ID 成功后下一步通常是讀 SFDPSerial Flash Discoverable Parameters數(shù)據(jù)。這個(gè)機(jī)制比固定 ID 映射更通用Flash 會(huì)通過(guò) 0x5A 命令輸出一份標(biāo)準(zhǔn)的參數(shù)表主控解析后就能拿到容量、擦除塊大小、支持的指令集等完整信息不需要在固件里維護(hù)龐大的廠商數(shù)據(jù)庫(kù)。如果你的平臺(tái) Flash 選型不確定SFDP 是非常值得額外實(shí)現(xiàn)的一層。另外如果項(xiàng)目里同時(shí)掛了好幾個(gè) SPI 設(shè)備比如屏幕和 Flash 共享同一根 SPI 總線就要特別注意 CS 的分配和切換時(shí)序。屏幕驅(qū)動(dòng)的時(shí)鐘頻率可能和 Flash 相差很多每次切換設(shè)備前最好重新配置 SPI 頻率否則在屏幕用的高速參數(shù)下去讀 FlashID 穩(wěn)定性會(huì)很差。這種問(wèn)題在現(xiàn)象上表現(xiàn)為“屏幕顯示正常但 Flash 時(shí)不時(shí)讀錯(cuò)”非常容易誤判為 Flash 質(zhì)量問(wèn)題。本文還有配套的精品資源點(diǎn)擊獲取