發(fā)配置全攻略)
先說(shuō)個(gè)真實(shí)場(chǎng)景某天我正坐在電腦前準(zhǔn)備把一塊 STM32 開(kāi)發(fā)板通過(guò) ST-LINK 調(diào)試器燒錄新寫的固件。Windows 下一切正常但為了統(tǒng)一整個(gè)團(tuán)隊(duì)的編譯環(huán)境我一直習(xí)慣在 WSLWindows Subsystem for Linux里敲命令、跑腳本。那天我打開(kāi) WSL 終端執(zhí)行了lsusb結(jié)果列表里空蕩蕩的完全沒(méi)有 ST-LINK 的影子。那一刻我腦子里就一句話“我板子呢”其實(shí)板子就插在 USB 口上Windows 的設(shè)備管理器也識(shí)別得清清楚楚。問(wèn)題出在我把 WSL 當(dāng)成了一個(gè)普通的 Linux 終端卻忘了 WSL 2 本質(zhì)上是個(gè)輕量虛擬機(jī)它默認(rèn)看不到 Windows 主機(jī)上的 USB 設(shè)備更別說(shuō)直接操作開(kāi)發(fā)板了。想直接在 WSL 里燒錄 STM32核心要解決的就是“讓 Linux 內(nèi)核認(rèn)識(shí)這塊板子”的鏈路問(wèn)題。這篇文章就記錄一下我怎么把這根鏈路徹底打通從安裝 USB 轉(zhuǎn)發(fā)工具到配置 udev 規(guī)則再到用 OpenOCD 完成燒錄中間踩過(guò)了哪些坑都會(huì)一并寫清楚。無(wú)論你是剛?cè)腴T嵌入式 C 開(kāi)發(fā)的新手還是被 WSL 各種設(shè)備識(shí)別問(wèn)題折騰過(guò)的老手這套流程應(yīng)該都能給你一個(gè)靠譜的參考。1. 為什么要在 WSL 里搞嵌入式燒錄一個(gè)懶人的解決方案你可能也會(huì)問(wèn)明明 Windows 下有那么多現(xiàn)成的圖形化燒錄工具點(diǎn)幾下鼠標(biāo)就能把固件寫進(jìn)芯片為什么非要跑到 WSL 里折騰命令行我的理由很簡(jiǎn)單因?yàn)槟憧赡芨静幌氪蜷_(kāi)那個(gè)圖形化工具。1.1 工作流的統(tǒng)一性遠(yuǎn)大于工具界面做嵌入式 C 開(kāi)發(fā)很多人會(huì)把代碼放在 Windows 的某個(gè)目錄里然后用 WSL 里的 GCC 工具鏈編譯。這樣做的最大好處是只要寫一個(gè) Makefile 或者 CMakeLists.txt無(wú)論誰(shuí)把倉(cāng)庫(kù)克隆到哪臺(tái)電腦上都能用幾乎一模一樣的命令編譯出來(lái)。編譯解決了燒錄怎么辦如果燒錄還得回到 Windows 圖形化界面那整個(gè)自動(dòng)化鏈路就斷了一截。更麻煩的是圖形化工具往往沒(méi)法輕易集成到你的 CI 流程、自定義腳本或一鍵部署方案里。讓我徹底轉(zhuǎn)向 WSL 燒錄的催化劑是某次我在編譯服務(wù)器上跑完測(cè)試想把生成好的固件直接燒到本地板子上驗(yàn)證。但編譯服務(wù)器是 Linux本機(jī)開(kāi)發(fā)板卻插在 Windows 上兩邊根本連不通。后來(lái)我想明白一件事如果 WSL 能直接訪問(wèn)那塊板子那它就相當(dāng)于把我的 Windows 主機(jī)變成了一臺(tái)“Linux 開(kāi)發(fā)機(jī)”燒錄、編譯、調(diào)試、GDB 全都能在同一套命令行體系里完成。這種統(tǒng)一性帶來(lái)的效率提升是任何圖形界面都替代不了的。并且WSL 里的命令可以直接引用 Windows 文件系統(tǒng)下的路徑比如/mnt/d/STM32Project/build/firmware.hex。這意味著你可以在 Windows 里用編輯器寫代碼在 WSL 里編譯和燒錄兩邊不需要任何文件拷貝天然無(wú)縫銜接。1.2 避開(kāi)圖形界面工具的“看不見(jiàn)的坑”還有一點(diǎn)比較現(xiàn)實(shí)很多圖形化燒錄工具雖然好用但當(dāng)你需要自動(dòng)化、批量生產(chǎn)、或者給測(cè)試團(tuán)隊(duì)配發(fā)燒錄腳本時(shí)它們就顯得非常笨重。命令行工具 OpenOCD 就沒(méi)有這種困擾——它本來(lái)就是為腳本化和遠(yuǎn)程調(diào)試設(shè)計(jì)的。所以我決定徹底在 WSL 里打通燒錄鏈路所有固件、腳本、配置文件都放進(jìn)倉(cāng)庫(kù)版本控制里。以后新同事上任拉下代碼、裝好工具鏈直接執(zhí)行make flash就能用根本不需要按鈕在哪兒、協(xié)議怎么選這些瑣碎經(jīng)驗(yàn)。2. 打通 USB 設(shè)備的“任督二脈”WSL 2 下的 USB 轉(zhuǎn)發(fā)配置先說(shuō)清楚一個(gè)概念WSL 2 默認(rèn)是不支持直接訪問(wèn) USB 設(shè)備的。因?yàn)閺募軜?gòu)上說(shuō)WSL 2 跑在一個(gè)輕量級(jí)虛擬化平臺(tái)上它有自己的 Linux 內(nèi)核但硬件資源包括 USB 控制器仍然由 Windows 主系統(tǒng)管理。所以在 Linux 子系統(tǒng)里根本看不到 Windows 下掛載的那些 USB 設(shè)備。解決辦法也很直接用一個(gè)開(kāi)源的 USB/IP 轉(zhuǎn)發(fā)工具把 Windows 上的物理 USB 設(shè)備“借給” WSL 2 內(nèi)核使用。這個(gè)工具一般叫usbipd-win它通過(guò) USB/IP 協(xié)議讓 WSL 里的 Linux 驅(qū)動(dòng)能夠訪問(wèn) Windows 側(cè)真實(shí)接入的硬件。實(shí)際配置過(guò)程分三步走每一步都有關(guān)鍵細(xì)節(jié)漏一個(gè)就可能前功盡棄。2.1 檢查你的 WSL 版本必須是 WSL 2USB 轉(zhuǎn)發(fā)只支持 WSL 2WSL 1 沒(méi)有完整的內(nèi)核沒(méi)法掛載這種虛擬 USB 驅(qū)動(dòng)。因此第一步確認(rèn)你的系統(tǒng)用的是 WSL 2。在 PowerShell 或 CMD 里輸入wsl -l -v如果你看到版本那列寫的是 1那需要先升級(jí)??梢栽诠芾韱T PowerShell 里執(zhí)行wsl --set-version 發(fā)行版名稱 2如果發(fā)行版是全新的也可以直接安裝指定的 Ubuntu 發(fā)行版并默認(rèn)使用 WSL 2。為了保險(xiǎn)起見(jiàn)建議先執(zhí)行一次wsl --update把 WSL 內(nèi)核更新到最新版本因?yàn)槔蟽?nèi)核可能存在一些 USB 設(shè)備兼容性的 bug。配套表Windows 側(cè)和 WSL 側(cè)需要準(zhǔn)備的組件組件作用安裝方式WSL 內(nèi)核提供 Linux 環(huán)境基礎(chǔ)wsl --update管理員權(quán)限usbipd-winWindows 側(cè) USB 轉(zhuǎn)發(fā)服務(wù)GitHub 發(fā)布頁(yè)下載安裝包Linux 側(cè)usbip客戶端工具WSL 內(nèi)連接 USB 設(shè)備的驅(qū)動(dòng)工具sudo apt install linux-tools-virtual hwdata注意usbipd-win安裝后需要重啟終端或 PowerShell 才能生效。安裝過(guò)程中會(huì)注冊(cè)一個(gè) Windows 服務(wù)重啟后這個(gè)服務(wù)會(huì)自動(dòng)啟動(dòng)。2.2 綁定和連接把這塊板子“遞”給 WSL安裝完usbipd-win后在 Windows PowerShell管理員模式下先看看當(dāng)前有哪些 USB 設(shè)備usbipd list輸出會(huì)類似這樣BUSID VID:PID DEVICE STATE 1-2 0483:3748 STMicroelectronics ST-LINK Not attached其中VID:PID是設(shè)備的供應(yīng)商 ID 和產(chǎn)品 ID。ST-LINK 調(diào)試器的 VID 一般是0483PID 可能是3748等具體看你的硬件版本。找到 BUSID 之后把它綁定給 WSL 使用usbipd bind --busid 1-2這一步意味著 Windows 會(huì)把該 USB 設(shè)備的所有權(quán)“共享”給 WSL。綁定之后設(shè)備狀態(tài)應(yīng)該變成Bound。然后在 WSL 終端里確認(rèn)已經(jīng)安裝了 usbip 客戶端工具。安裝方式sudo apt update sudo apt install linux-tools-generic hwdata接著連接設(shè)備sudo usbip attach -r Windows主機(jī)IP -b 1-2這里的Windows主機(jī)IP一般可以填 localhost如果你是從另一臺(tái) Linux 機(jī)器連過(guò)來(lái)的就要填真實(shí)的主機(jī)地址。執(zhí)行成功后再跑一次lsusb如果列表里出現(xiàn)了類似STMicroelectronics ST-LINK的行恭喜你這塊板子現(xiàn)在在 WSL 的 Linux 內(nèi)核里已經(jīng)被“看到”了。2.3 一個(gè)容易被忽略的細(xì)節(jié)綁定 vs 分離usbipd bind只是授權(quán)把設(shè)備共享出來(lái)但沒(méi)有真正連接到 WSL 內(nèi)核。真正建立連接的是usbip attach。很多人剛接觸時(shí)會(huì)困惑為什么我 bind 了之后lsusb還是看不到因?yàn)?bind 只是設(shè)置了權(quán)限attach 才是激活那根“虛擬數(shù)據(jù)線”。如果不想再讓 WSL 占用設(shè)備斷開(kāi)連接用sudo usbip detach --port 端口號(hào)或者直接在 Windows 端用usbipd unbind --busid 1-2unbind 之后設(shè)備會(huì)回到正常 Windows 獨(dú)占狀態(tài)圖形化燒錄軟件也能正??吹剿=ㄗh每次重新拔插開(kāi)發(fā)板后都重新執(zhí)行一次attach因?yàn)?USB 設(shè)備的熱插拔事件不會(huì)自動(dòng)同步到 WSL 內(nèi)核。3. 燒錄工具選型為什么 OpenOCD 是最優(yōu)選設(shè)備在lsusb里能看到只代表“硬件鏈路通了”離真正燒錄還差一個(gè)軟件層面的工具。這一步我選擇了 OpenOCD目前嵌入式圈子里最常用的開(kāi)源調(diào)試和燒錄工具。3.1 為什么不直接用原廠圖形化工具很多開(kāi)發(fā)板出廠時(shí)原廠會(huì)提供一個(gè)圖形化燒錄軟件功能非常全填好型號(hào)、選擇固件點(diǎn)個(gè)按鈕就能燒。但我在自動(dòng)化工作流中吃過(guò)它的虧命令行的自動(dòng)化支持差、許可證限制多、日志輸出不友好而且在不同機(jī)器上版本兼容性參差不齊。OpenOCD 則完全不同開(kāi)源免費(fèi)安裝方便原生支持命令行接口便于集成到 Makefile 和 CI 流程對(duì) ST-LINK 調(diào)試器、STM32 各系列芯片的支持非常成熟可以通過(guò) GDB 遠(yuǎn)程調(diào)試燒錄只是它的附帶功能之一。3.2 安裝 OpenOCD 與核心配置詳解在 WSL 里安裝 OpenOCDsudo apt install openocd安裝完成后要寫兩段配置一段描述調(diào)試器interface一段描述目標(biāo)芯片target。調(diào)試器配置以 ST-LINK 為例source [find interface/stlink.cfg] transport select hla_swd目標(biāo)芯片配置以 STM32F103 為例source [find target/stm32f1x.cfg]把這兩行寫進(jìn)一個(gè)腳本文件比如stlink_flash.cfg然后運(yùn)行openocd -f stlink_flash.cfg看到類似下面的輸出就說(shuō)明調(diào)試器已經(jīng)識(shí)別到復(fù)位信號(hào)和目標(biāo)芯片了Info : STLINK V2J29S7 (API v2) VID:PID 0483:3748 Info : Target voltage: 3.30 V Info : stm32f1x.cpu: hardware has 4 breakpoints, 2 watchpoints如果 OpenOCD 能正常啟動(dòng)并識(shí)別到內(nèi)核那就意味著燒錄的準(zhǔn)備工作全部完成。注意transport select hla_swd這行是 ST-LINK 專用配置。不同調(diào)試器如 J-Link、CMSIS-DAP需要各自對(duì)應(yīng)的配置文件不要照搬。3.3 燒錄命令從單次燒錄到一鍵腳本OpenOCD 的燒錄命令可以寫在命令行里例如openocd -f stlink_flash.cfg -c program build/firmware.elf verify reset exit其中program是燒錄指令verify表示燒錄完成后自動(dòng)校驗(yàn) Flash 內(nèi)容reset表示燒錄結(jié)束后復(fù)位目標(biāo)板e(cuò)xit表示斷開(kāi)與調(diào)試器的連接。這條命令一次執(zhí)行板子就能自動(dòng)運(yùn)行新固件。每次都要敲這么長(zhǎng)一串很煩所以我會(huì)在 Makefile 里封裝一個(gè)flash目標(biāo)flash: openocd -f stlink_flash.cfg -c program build/firmware.elf verify reset exit以后編譯完只需執(zhí)行make flash從編譯到上板一條命令走完那個(gè)“板子呢”的焦慮徹底消失。4. 摸黑排查實(shí)錄當(dāng)lsusb能看到板子OpenOCD 卻報(bào)找不到設(shè)備正常情況下走到第 3 章就結(jié)束了。但嵌入式開(kāi)發(fā)不可能總是一帆風(fēng)順更何況是在 WSL 這個(gè)“半虛擬化”環(huán)境里折騰。我在一次重新安裝環(huán)境時(shí)遇到了一個(gè)典型的案例lsusb能看到 ST-LINK但 OpenOCD 啟動(dòng)后卻提示找不到目標(biāo)設(shè)備錯(cuò)誤信息大概是Error: open failed或者target not found。這個(gè)問(wèn)題非常有代表性下面是我完整的排查鏈路希望能給你提供一條可復(fù)現(xiàn)的排錯(cuò)思路。4.1 檢查設(shè)備節(jié)點(diǎn)你以為你敢用的Linux 不一定讓你用WSL 內(nèi)lsusb能看到設(shè)備說(shuō)明 USB 轉(zhuǎn)發(fā)鏈路是通的。但訪問(wèn) USB 設(shè)備底層需要經(jīng)過(guò)設(shè)備文件比如/dev/bus/usb/001/003或/dev/ttyUSB0。很多時(shí)候 OpenOCD 報(bào)錯(cuò)是因?yàn)檫@些設(shè)備文件的權(quán)限不夠。Linux 默認(rèn)的權(quán)限策略中普通用戶訪問(wèn) USB 設(shè)備往往受限。WSL 默認(rèn)啟動(dòng)的用戶雖然有sudo權(quán)限但 OpenOCD 不會(huì)自動(dòng)用 sudo 運(yùn)行所以它可能直接連不上設(shè)備節(jié)點(diǎn)。解決方案是添加 udev 規(guī)則讓系統(tǒng)允許普通用戶訪問(wèn)這類設(shè)備。創(chuàng)建一個(gè)規(guī)則文件sudo nano /etc/udev/rules.d/99-stlink.rules寫入SUBSYSTEMusb, ATTR{idVendor}0483, ATTR{idProduct}3748, MODE0666保存后重載規(guī)則sudo udevadm control --reload-rules sudo udevadm trigger這里MODEv0666意思是權(quán)限設(shè)為所有用戶可讀可寫。之后重新啟動(dòng) OpenOCD大概率能解決。4.2 排查 ST-LINK 的供電和 SWD 接線如果權(quán)限解決了還是找不到下一步檢查硬件鏈路。STM32 開(kāi)發(fā)板通常有板載 ST-LINK但也有人用獨(dú)立的 ST-LINK 配合杜邦線連接目標(biāo)板。如果 SWDIO、SWCLK、GND、3.3V 四根線沒(méi)接好OpenOCD 會(huì)一直報(bào)找不到目標(biāo)。有個(gè)小技巧在 OpenOCD 啟動(dòng)時(shí)加上一堆調(diào)試參數(shù)它會(huì)打印出底層訪問(wèn)過(guò)程openocd -d3 -f stlink_flash.cfg日志里如果出現(xiàn)no device found或者unable to open STLink device多半還是 USB 轉(zhuǎn)發(fā)或權(quán)限問(wèn)題如果日志顯示調(diào)試器已連接卻始終無(wú)法識(shí)別目標(biāo)芯片那基本可以判定是 SWD 接線或目標(biāo)板供電問(wèn)題。4.3 WSL 1 vs WSL 2 的內(nèi)核行為差異另一個(gè)踩坑點(diǎn)是如果你用的發(fā)行版還停留在 WSL 1usbipd的 attach 命令雖然不報(bào)錯(cuò)但 USB 設(shè)備根本不會(huì)出現(xiàn)在 Linux 設(shè)備樹(shù)里。這是因?yàn)?WSL 1 沒(méi)有虛擬化硬件層USB/IP 驅(qū)動(dòng)沒(méi)法工作。務(wù)必通過(guò)wsl -l -v確認(rèn)版本。有一次我廢了半天勁最后發(fā)現(xiàn)是之前安裝了 Ubuntu 的舊發(fā)行版默認(rèn)是 WSL 1升級(jí)到 WSL 2 后整個(gè)世界都清凈了。4.4 串口調(diào)試時(shí)的二次心態(tài)崩潰燒錄成功后另一個(gè)常見(jiàn)的“板子呢”場(chǎng)景是程序跑起來(lái)了但串口終端里沒(méi)有任何輸出。這同樣可能是權(quán)限問(wèn)題。STM32 的虛擬串口通常被映射為/dev/ttyACM0或/dev/ttyUSB0。如果你在 WSL 里用screen /dev/ttyACM0 115200打開(kāi)提示權(quán)限不足那就需要給對(duì)應(yīng)設(shè)備節(jié)點(diǎn)也加上 udev 規(guī)則SUBSYSTEMtty, ATTRS{idVendor}0483, ATTRS{idProduct}5740, MODE0666這里的 PID 要看你的具體設(shè)備。設(shè)置完后拔出重插一次讓 udev 重新觸發(fā)。自此以后燒錄和串口調(diào)試都可以在 WSL 里無(wú)縫進(jìn)行。5. 讓“找板子”變成一條命令工程化燒錄的進(jìn)階玩法當(dāng)你能在 WSL 里穩(wěn)定燒錄這塊 STM32 板子之后會(huì)發(fā)現(xiàn)這種命令行工作流越用越順手。為了徹底告別“板子呢”的困擾我還在項(xiàng)目里做了一些工程化封裝讓整個(gè)過(guò)程自動(dòng)化起來(lái)。5.1 編寫 Makefile 觸發(fā)燒錄從零到一一個(gè)常用的 Makefile 片段會(huì)長(zhǎng)這樣TARGET firmware.elf BUILD_DIR build # 默認(rèn)燒錄目標(biāo) flash: $(BUILD_DIR)/$(TARGET) echo Flashing target... openocd -f stlink_flash.cfg -c program $(BUILD_DIR)/$(TARGET) verify reset exit這樣make flash就是直接燒錄。如果還想修改一下燒錄策略比如燒完板子不要自動(dòng)復(fù)位而是停在調(diào)試狀態(tài)flash-noreset: echo Flashing without reset... openocd -f stlink_flash.cfg -c program $(BUILD_DIR)/$(TARGET) verify exit這種靈活性圖形化工具根本給不了。5.2 聯(lián)動(dòng) usbipd讓命令自己“找板子”有時(shí)候插拔多了WSL 可能會(huì)丟失 USB 連接。與其手動(dòng)去 PowerShell 里敲 attach不如直接在 Makefile 里加上一句自動(dòng)處理的鉤子。在腳本開(kāi)始前可以嘗試執(zhí)行usbipd list | grep -q ST-LINK如果查到 Bound 狀態(tài)但未 Attached就執(zhí)行 attach。但這需要 Windows 側(cè)的 PowerShell 權(quán)限所以更穩(wěn)妥的做法是在 Windows 側(cè)定期執(zhí)行綁定操作或者在 WSL 啟動(dòng)腳本中加入 attach 命令。我個(gè)人的習(xí)慣是日常開(kāi)發(fā)時(shí)先手動(dòng) attach 一次然后在整個(gè)工作會(huì)話期間都不斷開(kāi)。如果中途換插其他 USB 設(shè)備就把之前的 detach 掉重新 attach保持每次開(kāi)發(fā)都從干凈狀態(tài)開(kāi)始。5.3 用 .elf、.hex 還是 .bin燒錄文件的取舍OpenOCD 的program命令支持多種格式。我一般用.elf因?yàn)樗{(diào)試信息、入口地址和各段信息OpenOCD 可以自動(dòng)解析。對(duì)于量產(chǎn)場(chǎng)景.hex 或 .bin 更常見(jiàn)因?yàn)樗鼈凅w積小、結(jié)構(gòu)簡(jiǎn)單適合批量寫入。如果固件比較大重新燒錄前可以先執(zhí)行擦除命令openocd -f stlink_flash.cfg -c init -c halt -c flash erase_sector 0 0 last -c exit這條命令會(huì)擦除整個(gè) Flash確保寫入時(shí)不會(huì)被舊數(shù)據(jù)干擾。5.4 我踩過(guò)的“板子呢”最后一坑環(huán)境變量污染在 WSL 里不同項(xiàng)目可能引用不同的編譯器和工具鏈路徑。有一次我明明在 A 項(xiàng)目里能燒錄在 B 項(xiàng)目里執(zhí)行同一套命令卻報(bào)找不到 OpenOCD。檢查發(fā)現(xiàn)是.bashrc里設(shè)置了一個(gè)過(guò)時(shí)的 PATH 變量把 OpenOCD 的路徑指到了另一個(gè)不存在的目錄。排查命令which openocd如果輸出的是某個(gè)可疑的路徑用printenv PATH檢查一下環(huán)境變量。這個(gè)教訓(xùn)讓我意識(shí)到在 WSL 下開(kāi)發(fā)工具鏈的路徑管理要跟 Windows 側(cè)同樣嚴(yán)謹(jǐn)最好統(tǒng)一通過(guò)安裝系統(tǒng)包或 symlink 來(lái)管理。最后說(shuō)兩句體己話折騰完這一整套流程再回頭看“我板子呢”這個(gè)問(wèn)題其實(shí)沒(méi)有那么玄妙。無(wú)非是設(shè)備轉(zhuǎn)發(fā)沒(méi)打通、權(quán)限沒(méi)過(guò)、配置文件不對(duì)這三類原因。但恰恰是這些看似瑣碎的小問(wèn)題構(gòu)成了嵌入式開(kāi)發(fā)中真實(shí)的“時(shí)間黑洞”。我個(gè)人現(xiàn)在的習(xí)慣是所有嵌入式 C 項(xiàng)目的編譯、燒錄、調(diào)試流程都跑在 WSL 里Windows 只負(fù)責(zé)編輯器、文檔和必要時(shí)的硬件管理。這讓我在任何一臺(tái)電腦上都能快速進(jìn)入狀態(tài)也讓我愿意把“燒錄成功”這份底氣分享給團(tuán)隊(duì)里的每一個(gè)人。希望這篇記錄也能幫你少走幾段彎路。