飛控C源碼全流程解析)
簡介CANUAV是一套采用C語言實(shí)現(xiàn)的UAVCAN開源協(xié)議棧專為STM32F407等內(nèi)置CAN控制器的ARM Cortex-M4微控制器設(shè)計面向無人機(jī)飛控、電機(jī)控制、GPS接收等自動化設(shè)備開發(fā)者解決多節(jié)點(diǎn)之間可靠、實(shí)時、分布式通信的問題。資源包為zip壓縮格式共36個文件、約103KB以C/C源碼和頭文件為主體輔以Markdown設(shè)計文檔、單元測試用例、Python輔助腳本及CMake工程文件結(jié)構(gòu)清晰便于閱讀與二次開發(fā)。項(xiàng)目基于輕量級Canard庫實(shí)現(xiàn)完整覆蓋UAVCAN協(xié)議核心功能包括消息到CAN幀的映射、基本數(shù)據(jù)類型編解碼、多播傳輸、心跳廣播與節(jié)點(diǎn)管理并針對STM32F407的CAN控制器做了適配可在真實(shí)總線上直接運(yùn)行。源碼中還提供stm32、socketcan、avr等驅(qū)動目錄tests目錄包含CRC、標(biāo)量編碼、內(nèi)存分配等測試配合Python輔助腳本可核對數(shù)據(jù)類型有助于深入理解協(xié)議細(xì)節(jié)并快速集成。目前已有945人學(xué)習(xí)下載適合具備一定嵌入式基礎(chǔ)、希望掌握UAVCAN協(xié)議棧移植與二次開發(fā)的開發(fā)者。 做無人機(jī)開發(fā)這些年我最大的感受是飛控代碼本身的門檻其實(shí)沒那么高真正決定一架飛機(jī)“好不好飛”的是通信鏈路穩(wěn)不穩(wěn)、任務(wù)調(diào)度順不順、傳感器數(shù)據(jù)準(zhǔn)不準(zhǔn)。最近整理手頭項(xiàng)目時我專門把一套基于CAN總線的無人機(jī)C源碼CANUAV從頭到尾過了一遍用git拉源碼、配置工具鏈、編譯燒錄再逐模塊對照分析整個過程踩了不少坑也攢了不少經(jīng)驗(yàn)。這篇就把CANUAV這套C源碼的全流程實(shí)操記錄下來包括git獲取源碼的細(xì)節(jié)、交叉編譯環(huán)境的搭建、核心通信與控制模塊的代碼思路以及我在實(shí)際調(diào)試中遇到的問題和排查方法。如果你在做四軸、固定翼飛控或工業(yè)級無人機(jī)想了解CAN總線怎么在嵌入式設(shè)備上落地這篇文章應(yīng)該能幫你省下不少折騰時間。1. CANUAV到底是什么從項(xiàng)目定位到源碼結(jié)構(gòu)1.1 為什么無人機(jī)要引入CAN總線先說個最現(xiàn)實(shí)的問題傳統(tǒng)無人機(jī)用PWM控制電調(diào)和舵機(jī)一路信號線就對應(yīng)一路輸出通道一多線束立刻變得夸張。八軸十六路輸出機(jī)臂上密密麻麻全是線重量和故障點(diǎn)同步上升。串口雖然能解決點(diǎn)對點(diǎn)通信但擴(kuò)展性差一個串口只能接一個設(shè)備總線利用率很低。CAN總線在這里的優(yōu)勢非常明顯兩根差分信號線CAN_H和CAN_L就能掛幾十個節(jié)點(diǎn)帶優(yōu)先級仲裁機(jī)制高優(yōu)先級報文可以搶占總線實(shí)時性有硬件級保障而且雙線差分傳輸天然抗共模干擾在電機(jī)轉(zhuǎn)動帶來的強(qiáng)電磁環(huán)境下比單端信號穩(wěn)得多。CANUAV這個項(xiàng)目就是圍繞這條總線做文章。飛控作為總線主節(jié)點(diǎn)電調(diào)、舵機(jī)、GPS、空速計、電量計都作為CAN節(jié)點(diǎn)掛到同一條總線上數(shù)據(jù)幀直接廣播節(jié)點(diǎn)只處理自己關(guān)心的報文。這樣整機(jī)線束能減少一大半對大載重、長航時平臺來說省下的每一克重量都很有意義。另外要理解CAN的報文結(jié)構(gòu)。標(biāo)準(zhǔn)幀的ID是11位擴(kuò)展幀是29位數(shù)據(jù)域最多8字節(jié)。8字節(jié)看著小對飛控夠用了——電調(diào)指令無非是轉(zhuǎn)速、方向、狀態(tài)傳感器數(shù)據(jù)分幀上傳即可。CANUAV源碼里大量涉及幀ID的劃分、數(shù)據(jù)字節(jié)的打包解包搞懂報文結(jié)構(gòu)是讀代碼的前提。1.2 CANUAV源碼包的目錄和模塊劃分git克隆下來之后第一件事就是看目錄結(jié)構(gòu)。以我拿到的這份源碼為例整體組織方式在嵌入式項(xiàng)目里很有代表性canuav/ ├── app/ // 應(yīng)用層主邏輯main.c、任務(wù)初始化 ├── driver/ // MCU外設(shè)驅(qū)動CAN、UART、I2C、SPI、定時器 ├── module/ // 功能模塊IMU、GPS、電調(diào)協(xié)議、遙控器SBUS解析 ├── algorithm/ // 算法層姿態(tài)解算、PID控制、濾波 ├── protocol/ // CAN應(yīng)用層協(xié)議幀ID分配、數(shù)據(jù)格式定義 ├── lib/ // 第三方庫、CRC校驗(yàn)、數(shù)學(xué)庫 ├── docs/ // 文檔協(xié)議說明、硬件接線圖 ├── Makefile // 構(gòu)建腳本 └── README.md // 項(xiàng)目說明拿到源碼后先別急著編譯把README和docs里關(guān)于幀ID分配、波特率設(shè)置的說明讀一遍后面排查問題會輕松很多。我見過不少朋友上來就make結(jié)果編譯過了上板子卻沒有任何報文輸出最后發(fā)現(xiàn)是總線波特率跟外設(shè)節(jié)點(diǎn)對不上——這種低級錯誤完全可以在動手前避免。2. 通過git獲取CANUAV源碼環(huán)境準(zhǔn)備與實(shí)踐2.1 先裝好git并完成基本配置這套源碼本身在git倉庫里管理所以git環(huán)境是第一步。Windows下推薦裝官方的Git for Windows安裝時默認(rèn)選項(xiàng)基本夠用唯一要注意的是在“調(diào)整PATH”那一步務(wù)必選上把git添加到系統(tǒng)環(huán)境變量的選項(xiàng)不然裝完在cmd或PowerShell里敲git --version會提示“無法將‘git’項(xiàng)識別為cmdlet、函數(shù)、腳本文件或可運(yùn)行程序的名稱”原因就是環(huán)境變量沒配上。Linux下用發(fā)行版自帶的包管理器裝就行# Debian/Ubuntu sudo apt install git # CentOS/RHEL系 sudo yum install git裝完先做兩個全局配置不然后續(xù)提交或切換分支容易出問題git config --global user.name 你的名字 git config --global user.email 你的郵箱Windows用戶如果不想頻繁敲命令可以再裝一個TortoiseGit小烏龜右鍵菜單直接可視化操作在源碼目錄里查看改動、提交、拉取都比較直觀。但底層命令還是建議掌握畢竟很多自動化腳本只認(rèn)命令行。2.2 克隆完整源碼并處理子模塊拉取CANUAV源碼用標(biāo)準(zhǔn)clone操作git clone 倉庫地址 canuav cd canuav這里有個細(xì)節(jié)很多嵌入式項(xiàng)目會把第三方庫以submodule的形式嵌進(jìn)來而不是直接放在主倉庫里。你git clone完后發(fā)現(xiàn)某些目錄是空的別慌先把子模塊同步下來git submodule update --init --recursive如果只需要編譯固件而不做二次開發(fā)可以用淺克隆只拉最新一次提交倉庫體積小很多clone速度也快git clone --depth1 倉庫地址 canuav不過淺克隆后期想切別的分支會受限需要fetch補(bǔ)充歷史所以做開發(fā)的話還是建議完整克隆。2.3 版本切換與提交記錄查看進(jìn)入倉庫后先看當(dāng)前在哪個分支git branch -a git log --oneline -10CANUAV這種項(xiàng)目一般會有個穩(wěn)定的發(fā)布版本比如v1.x的tag。我習(xí)慣先切到一個明確版本的tag上再開始編譯而不是直接跟著main分支跑——main分支的開發(fā)狀態(tài)經(jīng)常是半成品我今天編譯的行為可能跟作者昨天的代碼邏輯都對不上。git checkout v1.2.0 git status確認(rèn)工作區(qū)干凈、當(dāng)前版本清晰之后再動手改代碼。很多人忽略這一步結(jié)果搞不清楚自己改的東西是基于哪個版本后面升級維護(hù)時一堆沖突。3. 編譯CANUAV固件從工具鏈到燒錄3.1 交叉編譯環(huán)境搭建CANUAV這類飛控源碼的目標(biāo)平臺一般是STM32等Cortex-M系列MCU開發(fā)機(jī)是x86架構(gòu)不能直接運(yùn)行目標(biāo)代碼必須用交叉編譯器把C源碼編譯成ARM指令集的固件。常用的工具鏈?zhǔn)莂rm-none-eabi-gcc。Linux下直接裝包sudo apt install gcc-arm-none-eabi裝完驗(yàn)證一下arm-none-eabi-gcc --versionWindows用戶建議直接用STM32CubeIDE里面集成了編譯器和調(diào)試器圖形界面配置省心一些。也可以用官方獨(dú)立的gcc-arm-none-eabi工具鏈解壓后把bin目錄加到PATH里即可。3.2 編譯整個工程的步驟CANUAV源碼的構(gòu)建系統(tǒng)分兩種老一點(diǎn)的多用Makefile新一點(diǎn)的會遷到CMake。Makefile方式最直接make clean make all編譯過程中如果看到一堆warning: unused variable這種提示先不用太糾結(jié)只要沒有error就能出固件。但要注意工具鏈版本不同版本的arm-none-eabi-gcc對C語言標(biāo)準(zhǔn)支持有差異我用高版本編譯器去編老工程時經(jīng)常遇到inline關(guān)鍵字語義變化導(dǎo)致鏈接報錯這時可以檢查Makefile里的CROSS_COMPILE和編譯選項(xiàng)必要時換成工程作者推薦的版本。編譯產(chǎn)物一般有三個關(guān)鍵文件.elf用于調(diào)試、.bin用于燒錄、.map用于內(nèi)存布局分析。如果鏈接腳本.ld文件里指定的Flash或RAM大小跟實(shí)際芯片不符編譯大概率會報region FLASH overflowed這時候要打開Makefile或ld文件確認(rèn)芯片型號配置。如果工程是CMake管理的流程稍有差別mkdir build cd build cmake .. make -j43.3 固件燒錄與上電檢查燒錄方式常見的有兩種ST-Link配合STM32CubeProgrammer或者直接用OpenOCD。我的習(xí)慣是用OpenOCD命令行可控性高寫個腳本就能反復(fù)燒錄openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \ -c program build/canuav.bin 0x08000000 verify reset exit燒錄成功后別急著上槳先把飛控用USB接地面站或者用CAN分析儀接到總線確認(rèn)CAN報文實(shí)時往外發(fā)。正常情況應(yīng)該能在總線日志里看到周期性報文時間戳間隔穩(wěn)定。如果靜悄悄的大概率是接線或配置問題這部分我會在第5節(jié)列個排查清單。4. CANUAV核心C源碼模塊精讀4.1 CAN驅(qū)動與報文協(xié)議解析CANUAV的底層驅(qū)動核心是CAN外設(shè)初始化和中斷接收。初始化時主要做三件事配置引腳復(fù)用功能、設(shè)置波特率、配置過濾器。波特率這塊我建議先確認(rèn)整條總線上所有節(jié)點(diǎn)的波特率一致通常默認(rèn)1Mbps但如果總線上有老設(shè)備只支持500kbps飛控端的配置必須降下來否則通信直接失敗。過濾器是CAN硬件自帶的報文篩選機(jī)制外設(shè)可以只放行關(guān)心的幀ID進(jìn)中斷其他報文直接丟棄。CANUAV源碼里一般會配置成接收所有或不屏蔽特定ID的廣播幀調(diào)試階段建議屏蔽少一點(diǎn)讓所有報文都能進(jìn)中斷方便用邏輯分析儀對照。應(yīng)用層協(xié)議是閱讀的重點(diǎn)。CANUAV里幀ID不是隨便用的通常按bit位拆成幾個字段比如高幾位表示源節(jié)點(diǎn)、中幾位表示消息類型、低幾位表示目標(biāo)節(jié)點(diǎn)。數(shù)據(jù)域的字節(jié)順序更是容易踩坑的地方CAN協(xié)議本身是大端傳輸?shù)芏鄠鞲衅鞯臄?shù)據(jù)是小端打包解包時如果不統(tǒng)一解析出來的數(shù)值會完全錯亂。我調(diào)試時發(fā)現(xiàn)姿態(tài)角跳變排查到最后就是IMU數(shù)據(jù)字節(jié)序反了。判斷方法很簡單用CAN分析儀抓一幀對照協(xié)議文檔里手工算一遍對不上就翻轉(zhuǎn)字節(jié)序重試。4.2 姿態(tài)解算與飛行控制邏輯源碼里最核心的控制邏輯集中在這幾塊傳感器讀取、姿態(tài)解算、PID控制、輸出映射。姿態(tài)解算常用Mahony互補(bǔ)濾波或更復(fù)雜的EKFCANUAV這類中等規(guī)模項(xiàng)目一般用Mahony計算量小在STM32F4上跑幾百赫茲很輕松。姿態(tài)控制通常是串級PID外環(huán)控制角度內(nèi)環(huán)控制角速度。C源碼的組織方式一般是先定義好PID結(jié)構(gòu)體再寫姿態(tài)誤差計算和輸出更新函數(shù)。這里有個經(jīng)驗(yàn)剛拿到別人源碼時先把PID參數(shù)默認(rèn)值拍照留底再開始調(diào)參不然改亂了還能回退。調(diào)試時可以通過CAN總線的上行數(shù)據(jù)把實(shí)時姿態(tài)發(fā)送到地面站畫曲線看響應(yīng)??刂戚敵龅綀?zhí)行機(jī)構(gòu)是通過CAN報文下發(fā)到電調(diào)節(jié)點(diǎn)。電調(diào)返回當(dāng)前轉(zhuǎn)速、電流、溫度等狀態(tài)飛控把這些信息再轉(zhuǎn)發(fā)給地面站。整條鏈路就是傳感器采集 → 姿態(tài)解算 → 控制律計算 → CAN報文打包 → 總線廣播 → 電調(diào)執(zhí)行。4.3 任務(wù)調(diào)度的實(shí)現(xiàn)思路CANUAV這類飛控代碼要么裸機(jī)大循環(huán)要么跑輕量級RTOS比如FreeRTOS。裸機(jī)方案最簡單main函數(shù)里初始化完硬件然后while(1)循環(huán)里依次執(zhí)行傳感器讀取、姿態(tài)結(jié)算、控制輸出定時功能用定時器中斷計數(shù)實(shí)現(xiàn)。帶RTOS的版本則會把不同頻率的任務(wù)拆開IMU采集可能跑1kHz姿態(tài)控制跑500HzCAN報文發(fā)送跑200Hz串口日志跑50Hz。不同任務(wù)通過隊(duì)列或信號量傳遞數(shù)據(jù)避免共享變量的競爭問題。讀源碼時重點(diǎn)關(guān)注任務(wù)優(yōu)先級和CAN接收中斷的優(yōu)先級配置——如果中斷里處理了太多邏輯會把控制任務(wù)餓死表現(xiàn)就是飛控時不時卡頓一下。我個人建議如果你第一次接觸CANUAV先跑通裸機(jī)版本的邏輯鏈路把CAN收發(fā)、傳感器、控制輸出串起來之后再去看RTOS版本怎么拆任務(wù)理解會順很多。5. 實(shí)操踩坑記錄git與編譯常見問題速查5.1 git使用中的坑這一節(jié)把我在git使用過程中反復(fù)遇到的幾個問題整理成表格都是實(shí)際踩過的報錯或現(xiàn)象原因解決辦法git 不是內(nèi)部或外部命令Windows下git未加入PATH重裝Git for Windows安裝時勾選添加到PATHfatal: not a git repository (or any of the parent directories): .git當(dāng)前目錄不在git倉庫內(nèi)或者倉庫被拷貝時漏掉了隱藏的.git目錄cd到倉庫根目錄或用git init重新初始化僅適合新建倉庫Permission denied (publickey)沒有配置SSH公鑰生成密鑰ssh-keygen把.pub內(nèi)容添加到托管平臺的SSH Keys配置里RPC failed; curl 56 HTTP/2 stream error倉庫太大或網(wǎng)絡(luò)抖動導(dǎo)致clone中斷增加緩沖區(qū)git config --global http.postBuffer 524288000或者用淺克隆clone完成后子模塊目錄為空未同步submodule執(zhí)行g(shù)it submodule update --init --recursive提交時把build目錄、.o文件也提交了缺少.gitignore在倉庫根目錄創(chuàng)建.gitignore把build/、.o、.elf等加入忽略列表克隆倉庫時如果網(wǎng)絡(luò)不穩(wěn)定除了淺克隆還可以先下載一個zip包解壓然后在目錄里手動git init并關(guān)聯(lián)遠(yuǎn)程倉庫這樣也能繼續(xù)拉取歷史。不過這種方式比較繞優(yōu)先還是把clone本身跑通。5.2 構(gòu)建和運(yùn)行階段的坑編譯階段最典型的報錯是arm-none-eabi-gcc: command not found幾乎所有新手都會遇到本質(zhì)是工具鏈沒裝或沒在PATH里。有次我發(fā)現(xiàn)工具鏈裝了但編譯還報錯最后定位到是Makefile里寫死了CROSS_COMPILE路徑指向了不存在的目錄把Makefile改成直接調(diào)用arm-none-eabi-gcc就正常了。鏈接階段的undefined reference to xxx也很常見先看是不是子模塊沒同步、第三方的庫沒編進(jìn)來再排查源碼版本和工具鏈版本是否匹配。CANUAV這類項(xiàng)目如果長期未更新用新版編譯器容易出現(xiàn)兼容問題最穩(wěn)妥的辦法是按README里標(biāo)注的歷史版本環(huán)境搭建。上電運(yùn)行階段遇到?jīng)]有CAN報文輸出按這個順序查先用萬用表測CAN_H和CAN_L之間的電壓正常靜態(tài)時約2.5V通信時會有跳變再確認(rèn)總線兩端各有一個120Ω終端電阻這個很關(guān)鍵沒有終端電阻波形會反射導(dǎo)致通信不穩(wěn)定接著核對波特率用CAN分析儀設(shè)置成與飛控一致的速率最后檢查CAN_H和CAN_L是否接反了接反時兩端對地電壓都異常。有個老工程師教我先用示波器看差分波形有波就有物理層通信沒波就回頭查硬件這個檢查思路幫我省了很多時間。調(diào)試時還有個小經(jīng)驗(yàn)給飛控上電前先斷開總線上所有其他節(jié)點(diǎn)只留飛控和一個CAN分析儀排除從機(jī)節(jié)點(diǎn)干擾確認(rèn)飛控能正常發(fā)幀之后再逐個接入電調(diào)、傳感器節(jié)點(diǎn)誰接上總線出問題問題就在誰身上。這套流程走下來從git拉取CANUAV源碼到編譯出固件、燒錄進(jìn)飛控再到通過CAN分析儀確認(rèn)總線報文整個鏈路就算是打通了。我個人在實(shí)際操作中的體會是讀這類飛控源碼最忌諱只盯著一行行代碼鉆研先把通信架構(gòu)和控制鏈路的大框架裝進(jìn)腦子里再回過來看具體函數(shù)效率完全不一樣。最后再分享一個小技巧每次改完源碼編譯燒錄前先git diff看一眼改動內(nèi)容養(yǎng)成這個習(xí)慣之后調(diào)試姿態(tài)亂飄、報文錯亂這類問題時能省掉大量回溯排查的時間。本文還有配套的精品資源點(diǎn)擊獲取