學(xué)習(xí)路線:從通信協(xié)議到內(nèi)核源碼的實(shí)戰(zhàn)指南)
聊到嵌入式開發(fā)很多人的第一反應(yīng)是雜、難、門檻高。我做這行十幾年帶過團(tuán)隊(duì)也手把手帶過不少新人最大的感受是嵌入式從來不缺資料缺的是一張能串起來的地圖。今天這篇就當(dāng)是給同行們的一份福音從通信協(xié)議到學(xué)習(xí)路線從Linux環(huán)境到內(nèi)核源碼從面試八股到比賽實(shí)戰(zhàn)把嵌入式開發(fā)者最該啃的硬骨頭一次捋清楚。如果你正在入門或者工作一兩年卡在瓶頸期我建議你按這篇的思路走一遍先把主干立起來。為什么敢這么說因?yàn)榻^大多數(shù)人不是不努力而是不知道先學(xué)什么、學(xué)到什么程度算夠。嵌入式硬件、嵌入式軟件、嵌入式Linux這三條線看著各自獨(dú)立但在真實(shí)項(xiàng)目里早就纏在一起。一個成熟工程師要能看懂原理圖、寫驅(qū)動、調(diào)協(xié)議棧還要會跟測試、產(chǎn)線和供應(yīng)鏈溝通。所以我不打算只給你羅列工具和資料而是把底層邏輯和踩坑點(diǎn)都講透爭取讓剛畢業(yè)的學(xué)生也能照著上手。1. 先看清方向嵌入式開發(fā)到底學(xué)什么1.1 嵌入式開發(fā)的核心知識樹先搞懂學(xué)什么我一般把嵌入式知識樹分成四層。底層是硬件基礎(chǔ)包括電路、芯片、電源、時鐘這層決定你能不能把板子跑起來。第二層是嵌入式C語言和數(shù)據(jù)結(jié)構(gòu)這部分和通用C語言還不完全一樣你得更在意內(nèi)存布局、指針、寄存器操作、中斷上下文。第三層是外設(shè)與協(xié)議GPIO、定時器、UART、SPI、I2C、CAN這些是芯片跟外部世界打交道的通道。第四層才是系統(tǒng)與架構(gòu)裸機(jī)、RTOS、Linux或者雙核異構(gòu)它決定了你如何組織代碼、如何調(diào)度任務(wù)。很多新人一上來就想學(xué)操作系統(tǒng)結(jié)果連點(diǎn)燈都吃力。我自己帶人時會要求先花兩周時間把C語言指針、數(shù)組、結(jié)構(gòu)體、內(nèi)存地址這些概念練熟再去碰寄存器。為什么因?yàn)榍度胧介_發(fā)不像桌面開發(fā)那樣有那么多緩存幫你兜著你寫的每一行代碼幾乎都在直接碰內(nèi)存和硬件。寄存器本質(zhì)就是一個特殊地址你要通過指針讀它、寫它如果C語言底子不牢后面寫驅(qū)動會非常痛苦。這里也順便說一句嵌入式C語言面試時經(jīng)常考const、volatile、static這幾個關(guān)鍵字看起來簡單真要結(jié)合硬件場景說一說很多人就露餡了。四層知識不是讓你一層學(xué)完再學(xué)下一層而是像螺旋一樣同步推進(jìn)。比如你在調(diào)一個按鍵點(diǎn)亮LED的小項(xiàng)目就會同時用到硬件原理圖、GPIO寄存器、C語言分支以及定時器消抖。最理想的學(xué)習(xí)方式是一個小項(xiàng)目串起多層知識而不是單純背知識點(diǎn)。檢驗(yàn)標(biāo)準(zhǔn)很簡單給你一塊沒接觸過的開發(fā)板你愿不愿意從原理圖開始把它跑起來并且能說清楚每一步為什么這么操作。1.2 應(yīng)用層開發(fā)到底算不算嵌入式我的判斷社區(qū)里經(jīng)常有人問“應(yīng)用層開發(fā)是不是嵌入式”。我的看法是應(yīng)用層開發(fā)可以是嵌入式開發(fā)的一部分但如果你只寫應(yīng)用層核心競爭力會弱不少。比如嵌入式Linux項(xiàng)目里有些工程師主要寫網(wǎng)絡(luò)服務(wù)、業(yè)務(wù)邏輯、QT界面這確實(shí)要受嵌入式約束內(nèi)存就那么大、CPU也不高、可能有實(shí)時要求。但如果你完全不理解底層驅(qū)動不知道ioctl怎么走、設(shè)備樹是什么、中斷為什么有時會丟遇到性能問題基本原地抓瞎。嵌入式軟件工程師和普通軟件工程師最大的區(qū)別就是你要對硬件、系統(tǒng)、應(yīng)用三層都心里有數(shù)。那從應(yīng)用層往底層轉(zhuǎn)該從哪里下手我建議先把Linux的文件操作和驅(qū)動框架摸熟。你可以在開發(fā)板上寫一個最簡單的字符設(shè)備驅(qū)動完成read、write、ioctl的回調(diào)然后在用戶空間用open、read、write去操作這個設(shè)備。這件事一旦跑通你會突然看懂很多“上層調(diào)下層”的原理。接著再學(xué)編譯、鏈接、加載的過程搞清楚arm-linux-gcc交叉編譯出來的可執(zhí)行文件是怎么在目標(biāo)板上被加載運(yùn)行的。面試時如果能把應(yīng)用層一路講到驅(qū)動模型會直接拉開和普通候選人的差距。順帶提一句現(xiàn)在很多人喜歡用AI輔助讀datasheet、生成初始化代碼這確實(shí)是嵌入式好用的AI場景。但AI生成的配置不一定正確尤其像時鐘樹、GPIO復(fù)用這類關(guān)鍵參數(shù)錯了板子就跑不起來。你一定要會看數(shù)據(jù)手冊里的時序圖和寄存器位定義把AI工具當(dāng)參考而不是當(dāng)權(quán)威不然被帶進(jìn)溝里還不知道怎么爬出來。2. 吃透5種通信協(xié)議嵌入式項(xiàng)目就穩(wěn)了大半2.1 UART與SPI最常用的兩個基礎(chǔ)協(xié)議嵌入式開發(fā)里最離不開的兩個協(xié)議就是UART和SPI。UART是異步串行只需要RX、TX兩根線通信雙方必須先約定波特率。常見幀格式是1位起始位、8位數(shù)據(jù)位、1位停止位也可以加校驗(yàn)位。絕大多數(shù)調(diào)試手段都建立在UART上printf打印串口日志幾乎是嵌入式開發(fā)者的半條命。像藍(lán)牙傳歌詞的小產(chǎn)品本質(zhì)也是UART把歌詞數(shù)據(jù)吐給MCUMCU解析之后再顯示到屏幕。UART常見坑有兩個一是RX和TX接反二是串口共地不良導(dǎo)致亂碼。有一次我調(diào)一個藍(lán)牙模塊串口打印全是亂碼換了三個波特率都沒用最后量了一下模塊和板子的參考地發(fā)現(xiàn)壓差將近2V加了一根地線立刻正常。SPI是同步串行總線一般用SCK、MOSI、MISO、CS四根線主從模式下由主機(jī)產(chǎn)生時鐘。SPI速率可以做到幾十Mbps適合Flash存儲、顯示屏、ADC采樣這些吞吐量要求高的場景。最坑的是時鐘極性和相位也就是CPOL和CPHA主設(shè)備配置和從設(shè)備不一致時數(shù)據(jù)讀出來會莫名其妙地錯位。我調(diào)SPI時習(xí)慣先上邏輯分析儀抓波形看片選什么時候拉低、數(shù)據(jù)在哪個邊沿采樣基本一眼就能定位。再往上走還有MIPI和LVDS這類高速差分顯示接口它們更多出現(xiàn)在嵌入式Linux、工業(yè)視覺和DSP平臺上工控領(lǐng)域經(jīng)常能遇到但純MCU階段可以暫時不碰。2.2 I2C、CAN與以太網(wǎng)選對協(xié)議事半功倍I2C的總線結(jié)構(gòu)很討巧兩根線就夠SDA和SCL都是開漏輸出所以必須接上拉電阻。多設(shè)備掛在同一根總線上靠7位或10位地址區(qū)分。I2C速度不高幾百kbps到幾Mbps很適合接EEPROM、溫濕度傳感器、陀螺儀。調(diào)試中十個有八個問題出在上拉電阻上拉電阻漏焊、焊錯位置總線拉不到高電平設(shè)備就會時好時壞。另外一個容易忽略的細(xì)節(jié)是讀操作要發(fā)repeated start否則從機(jī)狀態(tài)機(jī)容易紊亂讀回來的數(shù)據(jù)錯位。CAN總線是工控和車載的??蛢筛罘志€CANH和CANL需要外部收發(fā)器才能接到控制器。CAN的優(yōu)點(diǎn)是多主仲裁、抗干擾強(qiáng)、傳輸距離遠(yuǎn)在嵌入式工業(yè)設(shè)備里基本是剛需。設(shè)計(jì)CAN網(wǎng)絡(luò)時要特別注意報文的IDID不光是一個地址還決定了仲裁優(yōu)先級。ID設(shè)得不好高優(yōu)先級消息可能搶不到總線低優(yōu)先級消息卻一直占著。軟件層面還要合理配置硬件濾波器只接收跟當(dāng)前節(jié)點(diǎn)相關(guān)的報文否則CPU會被無關(guān)消息敲得暈頭轉(zhuǎn)向。以太網(wǎng)在嵌入式Linux網(wǎng)關(guān)上繞不開。硬件上選PHY芯片軟件上用LWIP這類協(xié)議棧。遇到ping不通我通常按順序查先看PHY link是否建立再查MDIO能不能讀寫PHY寄存器最后查MAC地址和收發(fā)描述符是否初始化正確。下面這張表是5種協(xié)議的快速對比大家選型時可以參考不用死記。協(xié)議線數(shù)典型速率抗干擾典型場景UART2數(shù)Mbps一般調(diào)試、GPS、藍(lán)牙SPI4CS數(shù)十Mbps一般Flash、屏、ADCI2C2數(shù)百kbps~數(shù)Mbps一般EEPROM、傳感器CAN2差分1~8Mbps強(qiáng)車載、工控Ethernet4/8百兆/千兆較強(qiáng)網(wǎng)關(guān)、攝像頭、傳輸選型永遠(yuǎn)看場景。比如做一個嵌入式環(huán)境監(jiān)控節(jié)點(diǎn)一組傳感器用I2C采集節(jié)點(diǎn)間用CAN匯總會比RS485更省心做迷你電容觸控板模塊內(nèi)部用I2C上報坐標(biāo)就很穩(wěn)。協(xié)議本身沒有絕對好壞只有適合不適合。能把5種常規(guī)協(xié)議的時序、電氣特性和典型坑講清楚嵌入式項(xiàng)目里八成以上的通信問題都能自己解決。3. 嵌入式Linux開發(fā)環(huán)境Ubuntu、VSCode與遠(yuǎn)程調(diào)試3.1 要不要在Ubuntu下開發(fā)嵌入式Linux結(jié)論很明確這是新手問得特別多的問題。直接說結(jié)論做嵌入式Linux必須用Linux環(huán)境行業(yè)標(biāo)配基本是Ubuntu或者Debian。原因很實(shí)際交叉編譯工具鏈、內(nèi)核源碼樹、Buildroot、Yocto這些構(gòu)建體系在Linux下兼容性最好很多廠商SDK就只提供Linux版本。驅(qū)動開發(fā)調(diào)試依賴sysfs、/proc、內(nèi)核日志W(wǎng)indows上看不到也沒法方便地操作設(shè)備節(jié)點(diǎn)。如果只是做STM32單片機(jī)裸機(jī)Windows加VSCode完全沒問題但一旦你的項(xiàng)目開始跑Linux老老實(shí)實(shí)裝Ubuntu是最省事的路線。那新手到底怎么搭我給的建議是日常辦公留在Windows虛擬機(jī)里跑Ubuntu先把Linux基礎(chǔ)命令、Makefile、交叉編譯這套流程跑順。等熟悉之后再考慮一臺物理機(jī)專門裝Ubuntu。有人問我WSL2行不行如果你想做串口和JTAG調(diào)試WSL2對USB直連的支持并不完美配置過程也比較繞容易把新手勸退。我踩過這個坑后直接在Windows上用VSCode的Remote-SSH連到Ubuntu虛擬機(jī)既保留了Windows辦公體驗(yàn)又有完整Linux編譯環(huán)境工作量少很多。如果你還要帶QT5界面建議先把底層串口、驅(qū)動跑通再考慮GUI層否則界面卡了都不知道是渲染慢還是驅(qū)動問題。3.2 VSCode遠(yuǎn)程開發(fā)嵌入式Linux的高效姿勢開發(fā)嵌入式Linux項(xiàng)目最高效的姿勢不是把代碼拷貝來拷貝去而是用VSCode的Remote-SSH插件直接連到目標(biāo)環(huán)境。做法很簡單先在Linux開發(fā)機(jī)或開發(fā)板上開啟SSH服務(wù)Windows上的VSCode安裝Remote-SSH插件配置好主機(jī)地址和免密登錄然后就能像編輯本地文件一樣編輯遠(yuǎn)程代碼。接著安裝C/C擴(kuò)展把交叉編譯器路徑配置到c_cpp_properties.json里這樣代碼補(bǔ)全和跳轉(zhuǎn)才能識別arm-linux-gnueabihf的頭文件。編譯和調(diào)試也要在VSCode里集成起來。用一個tasks.json定義構(gòu)建任務(wù)調(diào)用遠(yuǎn)程環(huán)境里的make或cmake再用launch.json配置gdb遠(yuǎn)程調(diào)試。比如下面的片段目標(biāo)板上先運(yùn)行g(shù)dbserverVSCode這邊的gdb客戶端連過去就能像本地調(diào)試一樣打斷點(diǎn)、看變量。{ version: 0.2.0, configurations: [ { name: Remote GDB, type: cppdbg, request: launch, program: /home/user/arm-project/build/app, miDebuggerPath: /usr/bin/arm-linux-gnueabihf-gdb, miDebuggerServerAddress: 192.168.1.100:2345 } ] }目標(biāo)板上需要提前跑gdbserver :2345 ./app然后開發(fā)機(jī)上發(fā)起調(diào)試。Linux用戶程序調(diào)試基本可以這樣搞定。編譯產(chǎn)物的同步我習(xí)慣用rsync命令直接推到開發(fā)板比用U盤拷貝快得多。整個閉環(huán)下來就是“編輯、編譯、推送、調(diào)試”四步循環(huán)。團(tuán)隊(duì)協(xié)作時也可以把Ubuntu虛擬機(jī)當(dāng)作統(tǒng)一的編譯服務(wù)器Windows本機(jī)只做前端這樣每個人環(huán)境一致不會出現(xiàn)“我這邊編譯沒問題你那邊就是過不了”的尷尬。4. 進(jìn)階硬骨頭內(nèi)核源碼、DSP內(nèi)存映射與緩存優(yōu)化4.1 嵌入式內(nèi)核源碼怎么看抓住驅(qū)動這條主線Linux內(nèi)核源碼體量很大硬著頭皮從目錄第一個文件看到最后一個沒人能堅(jiān)持下來。正確做法是帶著問題看。內(nèi)核的頂層目錄結(jié)構(gòu)其實(shí)很有規(guī)律arch放體系結(jié)構(gòu)相關(guān)代碼drivers按子系統(tǒng)羅列驅(qū)動kernel和mm分別對應(yīng)調(diào)度、鎖和內(nèi)存管理。你要做的不是看全部而是抓住一條主線驅(qū)動是如何和設(shè)備樹、總線模型、用戶空間發(fā)生聯(lián)系的。我最推薦的切入點(diǎn)是寫一個LED驅(qū)動。整體鏈路大概是這樣的設(shè)備樹里加一個節(jié)點(diǎn)描述LED的GPIO驅(qū)動里用platform_driver框架注冊一個驅(qū)動當(dāng)設(shè)備樹節(jié)點(diǎn)匹配到compatible后系統(tǒng)自動回調(diào)probe函數(shù)。probe里做資源獲取、注冊字符設(shè)備或miscdevice然后在file_operations里實(shí)現(xiàn)open、read、write、ioctl。用戶空間open這個設(shè)備節(jié)點(diǎn)后通過write一個值驅(qū)動里拿到值再去操作GPIO寄存器。這條鏈路跑通之后你再去看“ARM-Linux嵌入式系統(tǒng)開發(fā)綜合應(yīng)用題”里??嫉膯恿鞒獭Ⅱ?qū)動框架、地址映射都會輕松很多因?yàn)槟阋呀?jīng)知道現(xiàn)實(shí)中它們是怎么串起來的??丛创a時不要逐行背重點(diǎn)是抓核心結(jié)構(gòu)體和回調(diào)函數(shù)。比如platform_driver里的probe和removefile_operations里那一堆函數(shù)指針?biāo)鼈冊谑裁磿r機(jī)被誰調(diào)用搞清楚這些驅(qū)動框架就算入門了。內(nèi)核里大量使用鏈表和宏比如container_of一定要親手解析一遍不然看紅黑樹、工作隊(duì)列時會很吃虧。嵌入式內(nèi)核源碼不是用來背的是用來查的。技術(shù)方案設(shè)計(jì)時知道去哪里查、怎么查比記住一百個函數(shù)名更實(shí)際。4.2 OMAP-L137內(nèi)存映射與C674x緩存架構(gòu)性能優(yōu)化實(shí)戰(zhàn)如果做工業(yè)音頻、電機(jī)控制或者信號處理遲早會碰到TI的DSP平臺。OMAP-L137是一款比較經(jīng)典的雙核SoC一個ARM926EJ-S負(fù)責(zé)控制和跑Linux一個C674x DSP負(fù)責(zé)數(shù)字信號處理兩個內(nèi)核通過共享內(nèi)存通信。這類異構(gòu)架構(gòu)里DSP側(cè)的緩存優(yōu)化往往決定了整個系統(tǒng)的實(shí)時性能上限也是嵌入式系統(tǒng)性能優(yōu)化實(shí)戰(zhàn)里最容易拉開差距的地方。C674x的緩存架構(gòu)大致是這樣的L1P和L1D各32KBL2有256KB可以部分當(dāng)cache部分當(dāng)可尋址SRAM。訪問外部DDR時如果命中率不好DSP會花大量時間在等待數(shù)據(jù)上實(shí)時算法根本跑不起來。我實(shí)際做過一個音頻后處理項(xiàng)目系數(shù)表放在DDR里每次算法循環(huán)都要等DDR的數(shù)據(jù)總時間多出不少。后來把系數(shù)表放到了L2 SRAM性能提升非常明顯。關(guān)鍵思路是把高頻訪問的小塊數(shù)據(jù)pin在L2 SRAM里而不是指望cache把所有數(shù)據(jù)都緩存住。另一個繞不開的問題是cache一致性。DSP用EDMA搬數(shù)據(jù)時CPU可能在cache里存了一份舊數(shù)據(jù)DMA又寫入了新數(shù)據(jù)兩邊不知道就會出錯。正確做法是DMA寫入前invalid cacheDMA讀取后writeback cache必要時加memory barrier。雙核之間通過共享內(nèi)存通信時也要注意這兩層內(nèi)核間數(shù)據(jù)同步加緩存一致性操作邏輯同步用核間中斷。很多人覺得這是偏門知識點(diǎn)但在OMAP-L137這類平臺上緩存配置錯了輕則性能差重則數(shù)據(jù)隨機(jī)錯亂調(diào)試排錯極其痛苦。如果能找一個實(shí)際項(xiàng)目把C674x的L2配置、cache操作、EDMA搬移完整走一遍你會對嵌入式底層有更扎實(shí)的理解。5. 面試、比賽與開源用作品證明自己5.1 嵌入式面試八股文別死背要會講原理嵌入式面試被吐槽最多的就是八股文但八股本身其實(shí)是“基礎(chǔ)概念的壓縮包”當(dāng)年能傳下來說明確實(shí)高頻。與其抱怨不如搞懂每道題背后的原理。比如volatile標(biāo)準(zhǔn)回答是“防止編譯器優(yōu)化每次訪問都從內(nèi)存讀取”但面試官真正想看的是你能不能結(jié)合寄存器場景說清楚。硬件寄存器可能被外設(shè)隨時改變?nèi)绻幾g器把讀操作優(yōu)化成讀寄存器緩存那你讀到的就是舊值。為了驗(yàn)證可以反匯編看優(yōu)化前后的指令差別這樣一答面試官馬上知道你是有實(shí)操經(jīng)驗(yàn)的。static也是高頻考法。修飾局部變量會延長生命周期修飾全局變量會限制作用域修飾函數(shù)會改變鏈接屬性??雌饋砗唵蔚旁谇度胧嚼锝?jīng)常用來做模塊化隔離。內(nèi)存對齊和cache一致性是嵌入式面經(jīng)里??嫉难由祛}比如結(jié)構(gòu)體對齊影響訪問效率DMA緩沖區(qū)需要對齊到cacheline。再比如進(jìn)程和線程區(qū)別、spinlock和mutex怎么選、中斷上下文能不能睡眠、內(nèi)核驅(qū)動模型里probe怎么觸發(fā)這些都要有一套自己的理解。嵌入式面試題不是靠背而是靠“項(xiàng)目八股”結(jié)合。你說做過環(huán)境監(jiān)控節(jié)點(diǎn)就要講清楚傳感器數(shù)據(jù)怎么采集、丟包怎么處理、協(xié)議怎么解析遠(yuǎn)比干巴巴背題有說服力。5.2 藍(lán)橋杯嵌入式省賽題目怎么拆解藍(lán)橋杯嵌入式競賽是很多學(xué)生接觸嵌入式實(shí)戰(zhàn)的第一步近幾屆省賽題目雖然每年有小變化但核心套路很穩(wěn)按鍵輸入、LCD顯示、ADC采集、PWM輸出、EEPROM存儲?!暗?6屆”這類新題也基本是這些模塊的組合和升級。我覺得準(zhǔn)備比賽最重要的是先搭一套穩(wěn)定的基礎(chǔ)框架而不是押題。具體來說第一步用CubeMX配置好時鐘樹、GPIO、串口、ADC、定時器和I2C。第二步把按鍵掃描、LCD顯示、LED輸出封裝成獨(dú)立模塊。第三步用定時器定時輪詢按鍵不要在main循環(huán)里用delay消抖否則整個系統(tǒng)會被按鍵拖死。第四步把主循環(huán)當(dāng)成一個狀態(tài)機(jī)按鍵事件產(chǎn)生狀態(tài)切換狀態(tài)決定屏幕刷新內(nèi)容和PWM輸出。第五步寫EEPROM時要注意頁寫邊界寫入后最好讀回校驗(yàn)否則掉電數(shù)據(jù)莫名丟了你都不知道是從哪里開始的。競賽真正考驗(yàn)的是代碼規(guī)范、模塊化能力和異常處理。比如PWM占空比不能超過定時器周期ADC多通道采集要注意采樣順序切換LCD刷新率不要因?yàn)轭l繁清屏導(dǎo)致閃爍。這些經(jīng)驗(yàn)官方教程不會寫但實(shí)操過才能體會到。項(xiàng)目做完之后如果想申請軟件著作權(quán)一定要把系統(tǒng)框圖、流程圖、核心代碼結(jié)構(gòu)整理成設(shè)計(jì)說明書別只貼一堆代碼。評審看重的是邏輯設(shè)計(jì)和模塊劃分寫得太亂容易被駁回。5.3 值得關(guān)注的開源項(xiàng)目與經(jīng)典設(shè)計(jì)模式嵌入式開源項(xiàng)目是很好的學(xué)習(xí)資源。RT-Thread國產(chǎn)文檔全很適合MCU裸機(jī)轉(zhuǎn)RTOS的人Zephyr在無線聯(lián)網(wǎng)場景里很有優(yōu)勢很多低功耗藍(lán)牙產(chǎn)品都在用LVGL是輕量級GUI庫能直接跑在MCU上做顯示交互MCUboot做OTA升級很成熟產(chǎn)品和項(xiàng)目里可以直接參考。嵌入式Linux方向BusyBox和Buildroot是制作精簡文件系統(tǒng)的利器想理解Linux系統(tǒng)怎么從無到有走一遍Buildroot會有收獲。設(shè)計(jì)模式方面我特別推薦“時間觸發(fā)嵌入式系統(tǒng)設(shè)計(jì)模式”。它的思路很簡單用一個任務(wù)表配合超級循環(huán)按固定時隙輪番執(zhí)行不同任務(wù)屬于協(xié)作式調(diào)度。沒有RTOS的并發(fā)復(fù)雜度也沒有優(yōu)先級反轉(zhuǎn)、棧溢出這些隱性風(fēng)險非常適合小資源裸機(jī)項(xiàng)目。比如一個觸控板模塊既要掃描電容觸摸、又要刷新LED、還要和主機(jī)通信用時間觸發(fā)的任務(wù)表就能安排得井井有條。很多老工程師喜歡在裸機(jī)里用這套模式就是因?yàn)樗拇_定性極高。不要一說多任務(wù)就上RTOS有些場景裸機(jī)加協(xié)作式調(diào)度反而更穩(wěn)。開源項(xiàng)目不要只看著去讀代碼、跑example、移植到自己板子上才算是真正內(nèi)化。6. 排障實(shí)錄串口亂碼、燒錄失敗與DDR翻車6.1 編譯不過、燒錄失敗、串口亂碼的坑先整理一個排查速查表這些都是日常開發(fā)里最高頻的問題遇到時可以照著查?,F(xiàn)象可能原因排查步驟串口輸出亂碼波特率不對、電平不共地、晶振頻率配置錯誤先核對代碼配置再量RX/TX電平用示波器看波形程序燒不進(jìn)去目標(biāo)板未上電、接線錯誤、芯片被鎖、boot模式不對量電源、查接線、按住復(fù)位嘗試燒錄、看工具狀態(tài)燈編譯報一堆未定義頭文件路徑?jīng)]配、宏開關(guān)沒打開、鏈接庫順序不對先編譯單個文件再檢查Makefile的include路徑USB設(shè)備枚舉失敗用了充電線、驅(qū)動沒裝、供電不足換數(shù)據(jù)線、裝官方驅(qū)動、外接電源供電串口亂碼是最典型的。有一次我調(diào)GPS模塊模塊默認(rèn)波特率是9600我固件里配置成115200輸出的自然全是亂碼。這算是粗心。更隱蔽的一次是外部晶振沒起振代碼里卻按外部晶振配置時鐘串口實(shí)際波特率跑偏了。后來我學(xué)乖了每次先把晶振頻率和系統(tǒng)時鐘樹核對一遍再查波特率。燒錄失敗也很有迷惑性。一次換了一個新板子J-Link死活連接不上芯片量了半天發(fā)現(xiàn)目標(biāo)板是電池供電調(diào)試器的參考地板和目標(biāo)板不共地導(dǎo)致復(fù)位時序不對。把調(diào)試器地線和目標(biāo)板地線連起來之后一次就成功了。記住調(diào)試器、目標(biāo)板、串口工具、示波器所有設(shè)備的參考地必須連在一起。很多靈異問題最后都是共地問題。6.2 從忘記上拉到DDR訓(xùn)練失敗的教訓(xùn)最后分享一個更進(jìn)階的排障故事。有一塊ARM板剛貼片回來DDR測試時好時壞Linux系統(tǒng)經(jīng)常隨機(jī)崩潰。一開始我懷疑是DDR初始化參數(shù)不對反復(fù)調(diào)節(jié)奏、改頻率問題依舊。后來搬出示波器量DDR時鐘和復(fù)位腳發(fā)現(xiàn)復(fù)位腳的電平上升沿不穩(wěn)定翻原理圖才發(fā)現(xiàn)DDR復(fù)位信號的上拉電阻漏貼了。這個電阻決定復(fù)位釋放的時序少了它芯片每次上電復(fù)位都處在不確定狀態(tài)。補(bǔ)焊之后批量驗(yàn)證全部通過問題徹底消失。這件事給我的教訓(xùn)很深。很多嵌入式工程師把注意力全放在軟件上覺得硬件問題是硬件工程師的活。但一個新人最容易翻車的恰恰就是硬件細(xì)節(jié)某個電阻貼沒貼、某個電源有沒有起來、某個引腳有沒有短接。哪怕你是純軟件背景也要養(yǎng)成看原理圖、看PCB的習(xí)慣至少知道哪些信號線關(guān)系到全局穩(wěn)定性。拿到一塊新板子第一件事不是寫代碼而是拿萬用表把電源、地、關(guān)鍵上拉電阻和復(fù)位信號都量一遍。這個習(xí)慣幫我省掉了大量Debug時間也送給正在看這篇的你當(dāng)個壓箱底的經(jīng)驗(yàn)。