戰(zhàn):U盤讀寫與MSC協(xié)議解析)
拿到DNESP32P4開發(fā)板之后很多人最先被板子上那一排Type-C口搞懵一個(gè)標(biāo)著UART一個(gè)標(biāo)著USB_OTG還有可能是電源口。第四十七章的USB U盤實(shí)驗(yàn)要是沒搞清楚哪個(gè)口能插U盤大概率會(huì)在日志里盯著“device descriptor read error”發(fā)呆一整個(gè)下午。這個(gè)實(shí)驗(yàn)本身不復(fù)雜但涉及的鏈路很長(zhǎng)ESP32-P4的USB控制器要跑主機(jī)模式要完成USB枚舉要識(shí)別出這是個(gè)大容量存儲(chǔ)設(shè)備要發(fā)SCSI命令讀取扇區(qū)最后還要掛上FAT文件系統(tǒng)才能讓應(yīng)用層用fopen/fread去操作U盤里的文件。任何一環(huán)出問題表現(xiàn)都是“U盤沒反應(yīng)”。所以這一章雖然叫U盤實(shí)驗(yàn)實(shí)際是把USB Host協(xié)議棧、MSC類驅(qū)動(dòng)、塊設(shè)備抽象和文件系統(tǒng)串起來的綜合實(shí)驗(yàn)。這篇文章我會(huì)按自己的實(shí)操經(jīng)驗(yàn)把這章實(shí)驗(yàn)背后的原理、代碼邏輯、踩坑點(diǎn)完整拆開講一遍。不管你是剛拿到DNESP32P4想跑通實(shí)驗(yàn)還是想在項(xiàng)目里用U盤做固件升級(jí)、數(shù)據(jù)導(dǎo)出都應(yīng)該能從里面找到有用的東西。1. 實(shí)驗(yàn)整體思路與硬件環(huán)境1.1 DNESP32P4開發(fā)板上的USB接口別認(rèn)錯(cuò)先花點(diǎn)時(shí)間把板子上的接口理清楚。DNESP32P4開發(fā)板通常會(huì)有兩個(gè)Type-C口其中一個(gè)通過板載的USB轉(zhuǎn)串口芯片接到ESP32-P4的UART主要用來下載固件和看日志這就是標(biāo)著UART的接口。另一個(gè)直接連到ESP32-P4芯片的USB控制器支持OTG功能這才是做U盤實(shí)驗(yàn)要用到的USB_OTG接口。第一次做這個(gè)實(shí)驗(yàn)的人最容易犯的錯(cuò)就是把U盤插到UART口上然后發(fā)現(xiàn)日志里一點(diǎn)反應(yīng)都沒有。因?yàn)閁ART口的USB控制器在芯片內(nèi)部是轉(zhuǎn)成串口的不是讓你接U盤的。還有一個(gè)細(xì)節(jié)容易被忽略USB_OTG接口的物理形態(tài)。開發(fā)板出廠一般是Type-C母座而標(biāo)準(zhǔn)U盤是USB-A公頭。你要么準(zhǔn)備一個(gè)Type-C轉(zhuǎn)USB-A的OTG轉(zhuǎn)接頭要么用一根OTG轉(zhuǎn)接線。這里提醒一句買轉(zhuǎn)接頭的時(shí)候盡量挑支持USB 2.0數(shù)據(jù)傳輸?shù)膭e貪便宜買那種只能充電的“假OTG線”否則插上去只有電源根本沒有數(shù)據(jù)線連通枚舉照樣失敗。我自己的習(xí)慣是準(zhǔn)備一個(gè)帶供電的USB HUB接在開發(fā)板和U盤中間。后面實(shí)測(cè)部分會(huì)詳細(xì)說原因這里先提一句ESP32-P4開發(fā)板的USB口供電能力有限遇到一些啟動(dòng)電流偏大的U盤或者老式機(jī)械移動(dòng)硬盤直接插板子很容易出現(xiàn)電壓跌落導(dǎo)致枚舉中斷。1.2 這個(gè)實(shí)驗(yàn)的目標(biāo)和整體流程正點(diǎn)原子這一章的實(shí)驗(yàn)?zāi)繕?biāo)很明確讓ESP32-P4作為USB主機(jī)識(shí)別插入的U盤并把U盤掛載成可讀寫的文件系統(tǒng)然后在應(yīng)用層實(shí)現(xiàn)對(duì)文件的讀和寫。整個(gè)流程可以分成五步USB Host控制器初始化啟動(dòng)主機(jī)協(xié)議棧。檢測(cè)到U盤插入觸發(fā)USB枚舉流程獲取設(shè)備描述符、配置描述符、接口描述符和端點(diǎn)描述符。識(shí)別出設(shè)備屬于Mass Storage Class大容量存儲(chǔ)類加載MSC類驅(qū)動(dòng)通過Bulk-Only Transport協(xié)議和SCSI命令讀寫扇區(qū)。將塊設(shè)備抽象成FATFS可識(shí)別的磁盤執(zhí)行掛載操作。應(yīng)用層調(diào)用標(biāo)準(zhǔn)文件API對(duì)U盤里的文件進(jìn)行讀寫驗(yàn)證。理解這五步很重要因?yàn)楹竺媾挪閱栴}基本就是沿著這條鏈路逐個(gè)環(huán)節(jié)檢查。如果枚舉沒過后面文件系統(tǒng)代碼寫得再好也沒用如果MSC握手失敗看到的現(xiàn)象可能是“能檢測(cè)到設(shè)備但一掛載就超時(shí)”。這個(gè)實(shí)驗(yàn)里的代碼結(jié)構(gòu)大致分三層底層是和USB MSC通信的驅(qū)動(dòng)層中間是FATFS文件系統(tǒng)適配層頂層是應(yīng)用邏輯。如果你打開工程發(fā)現(xiàn)有很多回調(diào)函數(shù)和事件注冊(cè)的代碼不要慌那是USB Host的典型寫法——USB是事件驅(qū)動(dòng)的插拔、枚舉完成、讀寫完成都是以事件形式通知上層。2. USB主機(jī)枚舉U盤的底層原理2.1 U盤在USB眼里只是一堆端點(diǎn)很多人寫代碼很熟練但對(duì)USB協(xié)議本身沒概念這里有必要補(bǔ)一下基礎(chǔ)。在USB的世界里主機(jī)和設(shè)備的通信是主從模式所有事務(wù)都由主機(jī)發(fā)起設(shè)備只能被動(dòng)響應(yīng)。U盤插上去之后主機(jī)要先給設(shè)備供電、復(fù)位總線然后通過默認(rèn)地址0向設(shè)備發(fā)送標(biāo)準(zhǔn)請(qǐng)求比如GET_DESCRIPTOR獲取設(shè)備描述符。設(shè)備描述符里包含設(shè)備的USB版本、設(shè)備類型、廠商IDVID、產(chǎn)品IDPID等信息。拿到設(shè)備描述符之后主機(jī)會(huì)給設(shè)備分配一個(gè)唯一的地址然后繼續(xù)獲取配置描述符。配置描述符下面還掛著接口描述符和端點(diǎn)描述符。U盤通常是一個(gè)接口包含兩個(gè)Bulk端點(diǎn)一個(gè)用于主機(jī)發(fā)數(shù)據(jù)一個(gè)用于設(shè)備回?cái)?shù)據(jù)。這一步做完主機(jī)才知道“這個(gè)設(shè)備有幾個(gè)接口、每個(gè)接口是什么類型、通過哪個(gè)端點(diǎn)和設(shè)備通信”。整個(gè)枚舉過程對(duì)外表現(xiàn)就是U盤燈閃一下、系統(tǒng)提示音“叮咚”。在嵌入式平臺(tái)上這個(gè)過程通常在幾十到幾百毫秒內(nèi)完成如果日志里能看到完整的枚舉流程說明物理鏈路和USB協(xié)議棧基本沒問題。2.2 MSC協(xié)議CBW、CSW和SCSI命令識(shí)別出U盤設(shè)備之后主機(jī)還不能直接讀文件。U盤在USB協(xié)議層面對(duì)應(yīng)的是Mass Storage Class簡(jiǎn)稱MSC。MSC協(xié)議規(guī)定了一種叫Bulk-Only TransportBOT的傳輸方式所有命令和數(shù)據(jù)都通過Bulk端點(diǎn)傳輸。BOT協(xié)議的核心是兩個(gè)結(jié)構(gòu)體CBWCommand Block Wrapper和CSWCommand Status Wrapper。CBW是主機(jī)發(fā)給設(shè)備的數(shù)據(jù)包31字節(jié)包含命令塊標(biāo)記、傳輸方向、傳輸長(zhǎng)度和一個(gè)16字節(jié)的SCSI命令描述塊。設(shè)備執(zhí)行完命令后會(huì)回一個(gè)13字節(jié)的CSW告訴主機(jī)命令執(zhí)行成功還是失敗。SCSI命令是真正讓U盤干活的指令比如INQUIRY查詢?cè)O(shè)備基本信息比如廠商名、產(chǎn)品名。READ CAPACITY讀取U盤總?cè)萘亢蜕葏^(qū)大小。READ(10)從指定邏輯塊地址讀取數(shù)據(jù)。WRITE(10)向指定邏輯塊地址寫入數(shù)據(jù)。所以一次最簡(jiǎn)單的U盤讀取操作實(shí)際上是主機(jī)通過CBW發(fā)送一個(gè)READ命令然后通過Bulk端點(diǎn)接收數(shù)據(jù)最后通過CSW確認(rèn)狀態(tài)。這一來一回就是底層驅(qū)動(dòng)一直在做的事情。2.3 ESP-IDF的USB Host棧分層清楚了USB協(xié)議之后再看軟件框架就順了。ESP-IDF從v5.x開始提供了完整的USB Host協(xié)議棧分層大概是這樣的HCDHost Controller Driver直接操作ESP32-P4的USB控制器寄存器管理傳輸描述符處理中斷。USB Host Library管理設(shè)備枚舉、地址分配、標(biāo)準(zhǔn)請(qǐng)求處理向上層提供設(shè)備連接/斷開的回調(diào)。Class Driver針對(duì)具體設(shè)備類別的驅(qū)動(dòng)。U盤對(duì)應(yīng)的是Mass Storage Class Driver也叫esp_msc_host。Application層調(diào)用類驅(qū)動(dòng)提供的API注冊(cè)事件回調(diào)處理業(yè)務(wù)邏輯。ESP-IDF的USB Host棧是事件驅(qū)動(dòng)架構(gòu)類驅(qū)動(dòng)和應(yīng)用層之間通過回調(diào)函數(shù)通信。比如MSC驅(qū)動(dòng)檢測(cè)到設(shè)備插入會(huì)回調(diào)應(yīng)用層注冊(cè)的on_event函數(shù)應(yīng)用層再根據(jù)事件類型決定掛載還是卸載文件系統(tǒng)。這種設(shè)計(jì)的優(yōu)點(diǎn)是協(xié)議棧解耦插拔事件可以安全處理缺點(diǎn)是你不能像寫單片機(jī)裸機(jī)代碼那樣在主循環(huán)里輪詢寄存器必須熟悉事件回調(diào)的編程方式。官方在examples/storage/fatfs_usb這個(gè)例程里把這套棧用得很典型。我建議你即使使用正點(diǎn)原子的例程也把官方這個(gè)fatfs_usb示例打開對(duì)照著看很多問題在對(duì)比中就會(huì)豁然開朗。3. 從零搭建USB U盤實(shí)驗(yàn)工程3.1 硬件準(zhǔn)備與接線先把硬件清單列出來這些都不難找DNESP32P4開發(fā)板一塊。標(biāo)準(zhǔn)USB 2.0 U盤一個(gè)建議8GB到32GB格式化為FAT32。Type-C轉(zhuǎn)USB-A的OTG轉(zhuǎn)接頭或者OTG線一根。備用帶供電的USB HUB一個(gè)劣質(zhì)U盤排查時(shí)會(huì)用到。USB轉(zhuǎn)串口模塊的Type-C線一根用于燒錄和查看日志。接線非常簡(jiǎn)單把OTG轉(zhuǎn)接頭插到開發(fā)板的USB_OTG口然后插上U盤。注意不要插到UART口。如果你用帶供電的USB HUB連接順序是開發(fā)板USB_OTG口接到HUB的上行口U盤插到HUB下行口。這樣U盤的供電由HUB提供排查很多莫名奇妙的復(fù)位問題會(huì)省心很多。3.2 創(chuàng)建工程與menuconfig關(guān)鍵配置我習(xí)慣用官方例程作為起點(diǎn)這樣能把不必要的變量排除掉。打開終端先拷貝官方fatfs_usb示例cd ~/esp cp -r $IDF_PATH/examples/storage/fatfs_usb dnese32p4_usb_demo cd dnese32p4_usb_demo idf.py set-target esp32p4 idf.py menuconfigmenuconfig里面有三個(gè)地方需要重點(diǎn)確認(rèn)。第一是USB Host總開關(guān)。在Component config - USB Host Stack里確保Enable USB Host Stack是勾選狀態(tài)同時(shí)把Mass Storage Class Driver也打開。這個(gè)是MSC驅(qū)動(dòng)加載的前提。第二是FATFS配置。在Component config - FAT Filesystem support里建議開啟Long filename support否則長(zhǎng)文件名文件會(huì)打不開。另外Max files數(shù)量可以根據(jù)需要調(diào)大默認(rèn)4個(gè)在簡(jiǎn)單實(shí)驗(yàn)里夠用但如果你的應(yīng)用要同時(shí)打開多個(gè)文件記得調(diào)高。第三是任務(wù)棧和優(yōu)先級(jí)。USB Host協(xié)議棧會(huì)創(chuàng)建一個(gè)后臺(tái)任務(wù)里面的task stack size要根據(jù)你的使用場(chǎng)景調(diào)整。如果操作U盤時(shí)頻繁報(bào)內(nèi)存不足或棧溢出把stack size從默認(rèn)值往上加。配置完保存退出。3.3 核心代碼邏輯拆解整個(gè)實(shí)驗(yàn)的代碼核心思路是先初始化USB Host棧和MSC類驅(qū)動(dòng)注冊(cè)事件回調(diào)然后在回調(diào)里處理設(shè)備連接和斷開事件連接時(shí)掛載文件系統(tǒng)斷開時(shí)卸載文件系統(tǒng)。關(guān)鍵代碼結(jié)構(gòu)大致如下#include esp_log.h #include esp_vfs_fat.h #include usb_disk.h static const char *TAG usb_demo; // MSC設(shè)備事件回調(diào) static void msc_event_handler(const esp_msc_host_event_t *event, void *arg) { switch (event-event) { case MSC_DEVICE_CONNECTED: ESP_LOGI(TAG, USB device connected); // 掛載U盤到 /usb 目錄 esp_vfs_fat_mount_config_t mount_cfg { .format_if_mount_failed false, .max_files 4, .allocation_unit_size CONFIG_WL_SECTOR_SIZE, }; esp_err_t ret esp_vfs_fat_rawflash_mount(/usb, storage); if (ret ESP_OK) { ESP_LOGI(TAG, Mount success); } break; case MSC_DEVICE_DISCONNECTED: ESP_LOGI(TAG, USB device disconnected); // 卸載文件系統(tǒng) esp_vfs_fat_rawflash_unmount(/usb, NULL); break; default: break; } } void app_main(void) { // 安裝MSC主機(jī)驅(qū)動(dòng) const esp_msc_host_config_t msc_cfg { .create_backround_task true, .task_priority 5, .stack_size 4096, .event_cb msc_event_handler, }; esp_msc_host_install(msc_cfg); }注意一點(diǎn)不同版本的ESP-IDFAPI函數(shù)名可能有變化。比如掛載接口有時(shí)候是esp_vfs_fat_spiflash_mount_rw_wl有時(shí)候是esp_vfs_fat_rawflash_mount。這不是你寫錯(cuò)了而是IDF版本迭代導(dǎo)致的接口調(diào)整。如果編譯報(bào)錯(cuò)先查一下當(dāng)前IDF版本對(duì)應(yīng)的API文檔別急著懷疑代碼邏輯。文件系統(tǒng)掛載成功后應(yīng)用層就可以用標(biāo)準(zhǔn)C庫(kù)函數(shù)操作U盤了。比如讀取U盤根目錄下名為hello.txt的文件FILE *fp fopen(/usb/hello.txt, r); if (fp ! NULL) { char buf[128]; size_t len fread(buf, 1, sizeof(buf), fp); buf[len] \0; ESP_LOGI(TAG, Read from file: %s, buf); fclose(fp); }寫入文件也是同樣的套路FILE *fp fopen(/usb/esp32p4_write.txt, w); if (fp ! NULL) { fputs(Hello from ESP32-P4 USB Host!\n, fp); fclose(fp); ESP_LOGI(TAG, Write done); }注意掛載路徑是/usb所以文件名前面的路徑必須帶上這個(gè)前綴。路徑不匹配會(huì)直接導(dǎo)致fopen返回NULL。3.4 編譯燒錄與驗(yàn)證代碼寫完之后編譯、燒錄、看日志三連idf.py build idf.py -p /dev/ttyUSB0 flash monitor注意燒錄口用UART口開發(fā)板USB_OTG口不要連電腦避免沖突。設(shè)備啟動(dòng)后把U盤插到OTG口正常日志應(yīng)該類似下面這樣I (1000) usb_demo: USB Host stack installed I (1200) usb_demo: MSC class driver registered I (3000) usb_demo: USB device connected I (3100) usb_demo: VID0x0930, PID0x6544 I (3200) usb_demo: SCSI inquiry: Kingston DataTraveler 3.0 I (3300) usb_demo: Disk capacity: 30528 MB, sector size: 512 I (3400) usb_demo: Mount success I (3500) usb_demo: Read from file: Hello from ESP32-P4!看到Mount success說明整個(gè)鏈路已經(jīng)跑通了。再往U盤里寫一個(gè)文件拔下來插電腦上看內(nèi)容是否一致這個(gè)實(shí)驗(yàn)就算完整驗(yàn)證通過。4. 常見問題與排查技巧實(shí)錄4.1 枚舉失敗與供電問題如果日志里一直停在USB device connected之后就沒了下文或者報(bào)類似“device descriptor read error”的錯(cuò)誤第一個(gè)要懷疑的是供電。USB枚舉的前幾步主機(jī)會(huì)給設(shè)備供電并復(fù)位總線此時(shí)設(shè)備要回復(fù)Set Address和Get Descriptor。如果供電不足設(shè)備可能剛上電就掉電重啟表現(xiàn)就是枚舉反復(fù)失敗或者日志里出現(xiàn)設(shè)備反復(fù)連接斷開。排查方法很簡(jiǎn)單用帶供電的USB HUB把U盤單獨(dú)供電再重新插拔一次。這個(gè)辦法我用了很多次解決了至少一半的U盤不識(shí)別問題。尤其是那種包裝上寫著“高速3.0”但實(shí)際做工很差的U盤啟動(dòng)瞬間電流能到幾百毫安板載USB口容易扛不住。另外注意OTG轉(zhuǎn)接頭的質(zhì)量。我買過一批很便宜的OTG短線拿萬用表一量里面只有電源線沒有數(shù)據(jù)線這種線插上去永遠(yuǎn)不可能枚舉成功。判斷方法也很簡(jiǎn)單同樣的轉(zhuǎn)接頭插到電腦的Type-C口上看電腦能不能識(shí)別U盤。電腦也識(shí)別不了趕緊換線。4.2 FATFS掛載失敗與U盤格式問題枚舉成功、但掛載失敗的典型日志是E (3400) vfs_fat_spiflash: f_mount failed (13) I (3400) usb_demo: Mount failFATFS的錯(cuò)誤碼13是FR_NO_FILESYSTEM意思是這個(gè)設(shè)備上沒有有效的FAT文件系統(tǒng)。最常見的原因就是U盤格式不對(duì)。很多出廠U盤是exFAT或者NTFS格式而嵌入式FATFS默認(rèn)只認(rèn)FAT16/FAT32。解決辦法是把U盤格式化成FAT32。Windows下右鍵格式化文件系統(tǒng)選FAT32分配單元大小默認(rèn)就行。注意U盤里的數(shù)據(jù)會(huì)清空格式化前先備份。如果你一定要支持exFAT需要在menuconfig里把FATFS的exFAT支持選項(xiàng)打開同時(shí)還需要一個(gè)支持exFAT的FATFS版本。ESP-IDF從v5.x開始已經(jīng)有條件支持exFAT但這個(gè)需求在U盤場(chǎng)景用得不多絕大多數(shù)情況下FAT32就夠了。還有一個(gè)容易踩的坑容量大于32GB的U盤。Windows默認(rèn)不讓你格式化成FAT32很多第三方工具又默認(rèn)做大扇區(qū)對(duì)齊。如果你用這種U盤建議先用磁盤工具確認(rèn)扇區(qū)大小是不是512字節(jié)。部分U盤是4K扇區(qū)FATFS默認(rèn)按512字節(jié)處理會(huì)導(dǎo)致讀寫數(shù)據(jù)錯(cuò)亂。4.3 數(shù)據(jù)錯(cuò)亂與熱插拔處理有讀者遇到過這種情況文件能寫進(jìn)去但拔下來插電腦上文件打不開或者內(nèi)容是亂碼。這個(gè)問題大概率出在熱插拔時(shí)機(jī)上。USB支持熱插拔但FATFS不希望你正在寫文件的時(shí)候突然拔掉U盤。應(yīng)用層在收到MSC_DEVICE_DISCONNECTED事件到真正卸載文件系統(tǒng)之間如果還有一個(gè)文件句柄是打開的數(shù)據(jù)可能還留在緩存里沒有真正下沉到閃存。處理辦法是在應(yīng)用層做操作保護(hù)比如用一個(gè)互斥鎖或狀態(tài)變量標(biāo)記當(dāng)前是否正在讀寫文件。收到斷開事件后先等待當(dāng)前文件操作結(jié)束再調(diào)用unmount。如果項(xiàng)目對(duì)數(shù)據(jù)可靠性要求高每次寫完文件主動(dòng)調(diào)用f_sync或者直接fclose把緩存刷下去。另外MSC驅(qū)動(dòng)本身在收到斷開事件后如果再執(zhí)行讀寫操作會(huì)直接報(bào)錯(cuò)。這是因?yàn)榈讓訅K設(shè)備已經(jīng)失效FATFS再往磁盤上寫數(shù)據(jù)相當(dāng)于寫空氣。所以驅(qū)動(dòng)回調(diào)里卸載文件系統(tǒng)之前一定不能繼續(xù)發(fā)起文件操作。4.4 USB抓包輔助定位當(dāng)你把供電和格式問題都排除掉U盤還是不工作這時(shí)候可以上USB抓包工具。最簡(jiǎn)單的辦法是邏輯分析儀。買一個(gè)采樣率在24MHz以上的邏輯分析儀把通道0接USB的D通道1接D-共地接好然后用PulseView軟件配置USB解碼協(xié)議。抓一次插拔過程就能看到主機(jī)發(fā)了哪些SETUP包、設(shè)備回了哪些數(shù)據(jù)。如果協(xié)議層沒有響應(yīng)日志和分析儀都能定位到具體是哪一步掛了。不過邏輯分析儀接USB信號(hào)線會(huì)影響信號(hào)質(zhì)量不適合長(zhǎng)時(shí)間掛著抓包的時(shí)候臨時(shí)接一下就好。如果手頭沒有邏輯分析儀也可以用USB分析儀比如帶USB解碼的示波器原理相同。還有個(gè)取巧的辦法在PC上用Wireshark配合USBPcap抓同一個(gè)U盤的枚舉過程對(duì)比PC和開發(fā)板枚舉出來的描述符是否有差異。如果PC上U盤枚舉正常但開發(fā)板枚舉中途斷掉基本可以判斷是開發(fā)板側(cè)供電或協(xié)議棧配置問題。這個(gè)方法不用額外買硬件排查問題時(shí)可以先試。5. 應(yīng)用場(chǎng)景與擴(kuò)展方向U盤實(shí)驗(yàn)跑通后能玩的東西就多了。最常見的場(chǎng)景是U盤升級(jí)固件。把新固件文件放到U盤根目錄嵌入式設(shè)備檢測(cè)到U盤插入自動(dòng)讀取固件文件并寫入OTA分區(qū)做完校驗(yàn)后重啟。相比通過網(wǎng)絡(luò)升級(jí)U盤升級(jí)在產(chǎn)線和售后場(chǎng)景下更可靠不需要網(wǎng)絡(luò)環(huán)境也不需要上位機(jī)軟件。另一個(gè)場(chǎng)景是數(shù)據(jù)導(dǎo)出。很多工業(yè)設(shè)備需要把運(yùn)行日志、采樣數(shù)據(jù)導(dǎo)出給用戶分析U盤是最通用的介質(zhì)。ESP32-P4有充足的內(nèi)存和計(jì)算能力可以在線把數(shù)據(jù)整理成CSV或者JSON寫入U(xiǎn)盤用戶拿回家插電腦就能打開。再擴(kuò)展一下ESP32-P4的USB Host能力不止支持U盤。官方已經(jīng)有USB攝像頭、USB鍵盤、USB轉(zhuǎn)串口等host例程的開發(fā)計(jì)劃或社區(qū)實(shí)現(xiàn)。如果你把這一章的MSC驅(qū)動(dòng)理解透了再去看其他類的USB設(shè)備驅(qū)動(dòng)會(huì)發(fā)現(xiàn)套路都是一樣的枚舉、配置接口、找到通信端點(diǎn)、按類協(xié)議發(fā)命令。區(qū)別只在類協(xié)議本身。6. 實(shí)際操作中的幾點(diǎn)體會(huì)這個(gè)實(shí)驗(yàn)做完之后我對(duì)USB Host的認(rèn)識(shí)比只看協(xié)議文檔時(shí)清晰太多了。紙上談兵時(shí)覺得USB很復(fù)雜真正跑一遍才發(fā)現(xiàn)其實(shí)主機(jī)側(cè)的協(xié)議棧已經(jīng)把90%的細(xì)節(jié)封裝好了你真正要關(guān)心的是設(shè)備的差異和供電的穩(wěn)定性。還有一點(diǎn)經(jīng)驗(yàn)是遇到問題先看日志日志里沒有信息再看硬件。很多新手一上來就懷疑代碼來回改配置其實(shí)U盤不識(shí)別九成是供電或格式問題代碼框架反而不容易出錯(cuò)。我現(xiàn)在的習(xí)慣是準(zhǔn)備一個(gè)固定的“測(cè)試U盤”2GB老U盤格式化成FAT32只在U盤實(shí)驗(yàn)和調(diào)試時(shí)用不裝其他數(shù)據(jù)。這樣一來每次出問題都能排除U盤本身的因素排查范圍一下子小了很多。