戰(zhàn):從交叉編譯到樹(shù)莓派智能小車(chē)項(xiàng)目構(gòu)建)
1. 從“小白”到“實(shí)戰(zhàn)”我的嵌入式C學(xué)習(xí)路徑復(fù)盤(pán)最近在整理學(xué)習(xí)筆記翻到第27篇時(shí)感觸頗深。這篇筆記的標(biāo)題很簡(jiǎn)單——“嵌入式系統(tǒng)開(kāi)發(fā)小白學(xué)習(xí)筆記27”但內(nèi)容卻是一片空白。這恰恰是我當(dāng)時(shí)學(xué)習(xí)狀態(tài)的真實(shí)寫(xiě)照面對(duì)嵌入式系統(tǒng)這個(gè)龐大而復(fù)雜的領(lǐng)域尤其是當(dāng)C這個(gè)“巨獸”加入戰(zhàn)局后常常感到無(wú)從下筆學(xué)了后面忘了前面項(xiàng)目實(shí)踐更是磕磕絆絆。我相信很多從單片機(jī)裸機(jī)C語(yǔ)言轉(zhuǎn)向Linux嵌入式、并試圖引入C進(jìn)行系統(tǒng)設(shè)計(jì)的初學(xué)者都有過(guò)類(lèi)似的迷茫。今天我就以這篇“空白筆記”為引子結(jié)合我后續(xù)在樹(shù)莓派智能小車(chē)等項(xiàng)目上的實(shí)戰(zhàn)經(jīng)驗(yàn)系統(tǒng)性地復(fù)盤(pán)一下嵌入式C的學(xué)習(xí)核心、避坑指南以及從理論到項(xiàng)目的構(gòu)建心法。這不是一篇速成教程而是一個(gè)踩過(guò)無(wú)數(shù)坑的同行為你梳理的一條相對(duì)清晰、可執(zhí)行的進(jìn)階路線(xiàn)。嵌入式開(kāi)發(fā)尤其是帶操作系統(tǒng)的嵌入式Linux開(kāi)發(fā)和單純的單片機(jī)編程是兩碼事。它要求你不僅要懂硬件、懂驅(qū)動(dòng)還要懂操作系統(tǒng)原理、懂系統(tǒng)編程而當(dāng)項(xiàng)目復(fù)雜度上升純C語(yǔ)言在模塊化、抽象和代碼復(fù)用上的劣勢(shì)就顯現(xiàn)出來(lái)了。這時(shí)C帶著它的類(lèi)、模板、RAII等特性登場(chǎng)但同時(shí)也帶來(lái)了內(nèi)存管理、實(shí)時(shí)性、二進(jìn)制兼容性等一系列新挑戰(zhàn)。網(wǎng)絡(luò)上搜索“嵌入式 C”關(guān)聯(lián)的熱詞五花八門(mén)從“C八股文”、“C面試題”到“vscode配置c/c環(huán)境”、“c運(yùn)行庫(kù)”再到具體的“樹(shù)莓派智能小車(chē)嵌入式系統(tǒng)開(kāi)發(fā)實(shí)戰(zhàn)”這恰恰反映了學(xué)習(xí)者的普遍痛點(diǎn)基礎(chǔ)不牢、環(huán)境混亂、理論與實(shí)戰(zhàn)脫節(jié)。本文將圍繞這些核心痛點(diǎn)拆解嵌入式C學(xué)習(xí)的幾個(gè)關(guān)鍵階段。2. 基石構(gòu)建超越“Hello World”的嵌入式C環(huán)境與核心語(yǔ)法很多教程的第一步就是教你在Ubuntu上g -o hello hello.cpp輸出一個(gè)“Hello World”就宣告環(huán)境搭建成功。但對(duì)于嵌入式開(kāi)發(fā)這遠(yuǎn)遠(yuǎn)不夠。你的“世界”很可能是一個(gè)資源受限的ARM板子交叉編譯工具鏈、系統(tǒng)頭文件、標(biāo)準(zhǔn)庫(kù)版本都是需要提前厘清的問(wèn)題。2.1 開(kāi)發(fā)環(huán)境搭建交叉編譯是第一步嵌入式開(kāi)發(fā)99%的情況是“交叉編譯”即在性能強(qiáng)大的宿主機(jī)如你的x86電腦上編譯出能在目標(biāo)板如ARM架構(gòu)的樹(shù)莓派上運(yùn)行的程序。這里的第一步是獲取正確的交叉編譯工具鏈。對(duì)于樹(shù)莓派官方的gcc-arm-linux-gnueabihf工具鏈?zhǔn)莻€(gè)好起點(diǎn)。在Ubuntu上可以這樣安裝sudo apt-get update sudo apt-get install gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf安裝后編譯命令不再是g而是arm-linux-gnueabihf-g。例如arm-linux-gnueabihf-g -o my_embedded_app main.cpp -static # 靜態(tài)鏈接避免目標(biāo)板缺少庫(kù)為什么強(qiáng)調(diào)靜態(tài)鏈接在目標(biāo)板初始階段可能沒(méi)有完整的C運(yùn)行庫(kù)如libstdc.so。使用-static參數(shù)可以將所有依賴(lài)庫(kù)靜態(tài)鏈接到最終的可執(zhí)行文件中生成的文件會(huì)變大但確保了在目標(biāo)板上的可運(yùn)行性非常適合初期調(diào)試。這就是一個(gè)典型的嵌入式思維優(yōu)先考慮目標(biāo)環(huán)境的約束。2.2 C核心語(yǔ)法精要嵌入式視角下的取舍面對(duì)《C Primer》這樣的巨著嵌入式開(kāi)發(fā)者需要有選擇地深挖。以下是我認(rèn)為必須牢固掌握且與嵌入式特性強(qiáng)相關(guān)的部分面向?qū)ο笈c封裝這是用C替代C進(jìn)行模塊化設(shè)計(jì)的主要?jiǎng)恿?。將某個(gè)傳感器如MPU6050陀螺儀的驅(qū)動(dòng)封裝成一個(gè)類(lèi)私有數(shù)據(jù)成員保存I2C句柄、校準(zhǔn)參數(shù)公有成員函數(shù)提供init(),read_data()等接口。這比C語(yǔ)言里一堆全局變量和散亂的函數(shù)要清晰、安全得多。資源管理即對(duì)象生命周期RAII這是C相比C在嵌入式系統(tǒng)安全性的巨大提升。核心思想是在構(gòu)造函數(shù)中獲取資源如分配內(nèi)存、打開(kāi)文件描述符、鎖住互斥鎖在析構(gòu)函數(shù)中釋放資源。這確保了即使函數(shù)異常返回或提前退出資源也能被正確釋放有效防止內(nèi)存泄漏和資源鎖死。例如用一個(gè)FileDescriptor類(lèi)封裝對(duì)Linux系統(tǒng)調(diào)用open和close的操作。模板與泛型編程適度使用模板在嵌入式領(lǐng)域常用于編寫(xiě)硬件無(wú)關(guān)的驅(qū)動(dòng)框架或通用算法容器。例如你可以寫(xiě)一個(gè)泛型的CircularBuffer環(huán)形緩沖區(qū)類(lèi)模板用于UART、SPI等不同總線(xiàn)的數(shù)據(jù)緩沖而不需要為每種數(shù)據(jù)類(lèi)型重寫(xiě)一遍。但要注意過(guò)度使用模板會(huì)導(dǎo)致代碼膨脹二進(jìn)制體積增大和編譯時(shí)間增長(zhǎng)在資源緊張的MCU上需謹(jǐn)慎。Lambda表達(dá)式與STL算法C11的Lambda在嵌入式Linux應(yīng)用層編程中非常實(shí)用特別是在配合std::thread、std::async進(jìn)行異步任務(wù)處理或使用std::sort、std::find_if等算法處理數(shù)據(jù)集合時(shí)能讓代碼更簡(jiǎn)潔。但同樣需評(píng)估目標(biāo)板的STL庫(kù)支持情況和性能開(kāi)銷(xiāo)。需要警惕的特性異常處理Exception、RTTI運(yùn)行時(shí)類(lèi)型識(shí)別、以及過(guò)于復(fù)雜的多重繼承。這些特性通常會(huì)導(dǎo)致額外的運(yùn)行時(shí)開(kāi)銷(xiāo)和二進(jìn)制體積膨脹在許多實(shí)時(shí)性要求高或資源極度受限的嵌入式場(chǎng)景中項(xiàng)目規(guī)范會(huì)明確禁止使用。你的學(xué)習(xí)重點(diǎn)應(yīng)該放在如何不用這些特性也能寫(xiě)出健壯的代碼。3. 系統(tǒng)融合C與Linux嵌入式環(huán)境的交互實(shí)踐掌握了C語(yǔ)法下一步就是讓它與嵌入式Linux系統(tǒng)“對(duì)話(huà)”。這涉及到系統(tǒng)調(diào)用、多線(xiàn)程、進(jìn)程間通信等是嵌入式應(yīng)用開(kāi)發(fā)的核心。3.1 使用C封裝Linux系統(tǒng)調(diào)用直接混用C風(fēng)格的open、read、write和C代碼是可行的但不夠“C”。更好的做法是進(jìn)行輕量級(jí)封裝。例如封裝一個(gè)GPIO控制類(lèi)// GpioPin.h class GpioPin { public: enum class Direction { IN, OUT }; enum class Value { LOW, HIGH }; GpioPin(int pin_number); ~GpioPin(); // 析構(gòu)函數(shù)中確保取消導(dǎo)出GPIO bool setDirection(Direction dir); bool setValue(Value val); Value getValue(); private: int pin_number_; int value_fd_{-1}; // 文件描述符RAII管理 // 禁止拷貝 GpioPin(const GpioPin) delete; GpioPin operator(const GpioPin) delete; };在實(shí)現(xiàn)文件中使用std::fstream或unistd.h中的open/write等系統(tǒng)調(diào)用在setDirection等方法中向/sys/class/gpio下的虛擬文件寫(xiě)入數(shù)據(jù)。這樣主程序中使用GpioPin led(17); led.setDirection(GpioPin::Direction::OUT); led.setValue(GpioPin::Value::HIGH);不僅安全而且意圖清晰。3.2 多線(xiàn)程與實(shí)時(shí)性考量嵌入式系統(tǒng)經(jīng)常需要處理多任務(wù)例如一個(gè)線(xiàn)程負(fù)責(zé)讀取傳感器數(shù)據(jù)另一個(gè)線(xiàn)程負(fù)責(zé)網(wǎng)絡(luò)通信第三個(gè)線(xiàn)程負(fù)責(zé)UI更新。C11提供了優(yōu)秀的thread,mutex,condition_variable等標(biāo)準(zhǔn)庫(kù)支持。#include thread #include atomic #include queue #include mutex class SensorDataProcessor { public: void start() { producer_thread_ std::thread(SensorDataProcessor::producerLoop, this); consumer_thread_ std::thread(SensorDataProcessor::consumerLoop, this); } void stop() { running_.store(false); if(producer_thread_.joinable()) producer_thread_.join(); if(consumer_thread_.joinable()) consumer_thread_.join(); } private: void producerLoop() { while(running_.load()) { Data data readFromHardware(); // 模擬硬件讀取 { std::lock_guardstd::mutex lock(queue_mutex_); data_queue_.push(data); } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } void consumerLoop() { while(running_.load() || !data_queue_.empty()) { Data data; { std::lock_guardstd::mutex lock(queue_mutex_); if(!data_queue_.empty()) { data data_queue_.front(); data_queue_.pop(); } } if(/*data有效*/) processData(data); std::this_thread::yield(); // 避免空轉(zhuǎn)消耗CPU } } std::atomicbool running_{true}; std::thread producer_thread_, consumer_thread_; std::queueData data_queue_; std::mutex queue_mutex_; };關(guān)鍵點(diǎn)與坑使用std::atomic對(duì)于簡(jiǎn)單的標(biāo)志位如running_使用原子變量比互斥鎖性能更高且能避免數(shù)據(jù)競(jìng)爭(zhēng)。鎖的粒度鎖的范圍要盡可能小。上面代碼中鎖只保護(hù)了隊(duì)列的push和pop操作。實(shí)時(shí)性不是默認(rèn)的標(biāo)準(zhǔn)C線(xiàn)程的調(diào)度策略取決于底層操作系統(tǒng)通常是Linux的CFS完全公平調(diào)度器。如果你的某個(gè)線(xiàn)程有嚴(yán)格的實(shí)時(shí)性要求如電機(jī)控制你需要使用POSIX線(xiàn)程pthread的API來(lái)設(shè)置線(xiàn)程的調(diào)度策略和優(yōu)先級(jí)例如pthread_setschedparam。這是C標(biāo)準(zhǔn)庫(kù)未覆蓋的領(lǐng)域需要與系統(tǒng)API混用。優(yōu)先級(jí)反轉(zhuǎn)當(dāng)高優(yōu)先級(jí)線(xiàn)程等待一個(gè)被低優(yōu)先級(jí)線(xiàn)程占有的鎖時(shí)如果低優(yōu)先級(jí)線(xiàn)程被中優(yōu)先級(jí)線(xiàn)程搶占就會(huì)導(dǎo)致高優(yōu)先級(jí)線(xiàn)程無(wú)限期等待。在嵌入式實(shí)時(shí)系統(tǒng)中需要考慮使用優(yōu)先級(jí)繼承互斥鎖如pthread_mutexattr_setprotocol設(shè)置PTHREAD_PRIO_INHERIT。4. 項(xiàng)目實(shí)戰(zhàn)以樹(shù)莓派智能小車(chē)為例的完整構(gòu)建流程理論最終要落地到項(xiàng)目?!皹?shù)莓派智能小車(chē)”是一個(gè)絕佳的嵌入式C綜合實(shí)戰(zhàn)項(xiàng)目它涵蓋了傳感器驅(qū)動(dòng)超聲波、攝像頭、電機(jī)控制PWM、多線(xiàn)程、甚至簡(jiǎn)單的決策邏輯。4.1 項(xiàng)目架構(gòu)設(shè)計(jì)模塊化與分層不要一上來(lái)就寫(xiě)main.cpp。先進(jìn)行模塊劃分這體現(xiàn)了C面向?qū)ο蟮膬?yōu)勢(shì)。一個(gè)典型的分層架構(gòu)可以是硬件抽象層HAL提供統(tǒng)一的硬件操作接口。例如MotorController類(lèi)控制L298N驅(qū)動(dòng)板、UltrasonicSensor類(lèi)HC-SR04、Camera類(lèi)使用OpenCV的VideoCapture。這些類(lèi)的實(shí)現(xiàn)依賴(lài)于具體的硬件和底層庫(kù)如wiringPi用于GPIOOpenCV用于圖像但對(duì)外接口保持一致。核心邏輯層包含主要的業(yè)務(wù)邏輯。例如Navigation類(lèi)根據(jù)傳感器數(shù)據(jù)計(jì)算行進(jìn)指令、ObstacleAvoidance類(lèi)避障算法。應(yīng)用層main函數(shù)所在層負(fù)責(zé)初始化所有模塊啟動(dòng)主循環(huán)或線(xiàn)程協(xié)調(diào)各層工作。在構(gòu)建系統(tǒng)上放棄手動(dòng)寫(xiě)編譯命令使用CMake。它是管理跨平臺(tái)、交叉編譯項(xiàng)目的工業(yè)標(biāo)準(zhǔn)。一個(gè)簡(jiǎn)單的CMakeLists.txt骨架如下cmake_minimum_required(VERSION 3.10) project(RaspberryPiCar CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 交叉編譯配置 (在宿主機(jī)上構(gòu)建時(shí)使用) if(CMAKE_CROSSCOMPILING) set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g) # 指定目標(biāo)系統(tǒng)的根文件系統(tǒng)路徑sysroot用于查找頭文件和庫(kù) set(CMAKE_SYSROOT /path/to/raspberrypi/sysroot) endif() # 查找依賴(lài)庫(kù)例如OpenCV find_package(OpenCV REQUIRED) # 添加可執(zhí)行文件 add_executable(pi_car_main src/main.cpp src/hal/MotorController.cpp src/hal/UltrasonicSensor.cpp src/logic/Navigation.cpp ) # 鏈接庫(kù) target_link_libraries(pi_car_main ${OpenCV_LIBS} wiringPi # 如果是動(dòng)態(tài)鏈接需要確保目標(biāo)板上有此庫(kù) pthread # 鏈接線(xiàn)程庫(kù) ) # 設(shè)置目標(biāo)屬性例如靜態(tài)鏈接 set_target_properties(pi_car_main PROPERTIES LINK_FLAGS -static )使用CMake你可以在宿主機(jī)上創(chuàng)建一個(gè)構(gòu)建目錄然后通過(guò)指定工具鏈文件Toolchain File來(lái)一鍵進(jìn)行交叉編譯極大提升了開(kāi)發(fā)效率。4.2 核心模塊實(shí)現(xiàn)示例電機(jī)控制與避障邏輯以MotorController類(lèi)為例展示如何用C封裝硬件操作// MotorController.h #pragma once #include cstdint class MotorController { public: enum class Direction { FORWARD, BACKWARD, STOP }; MotorController(int in1_pin, int in2_pin, int pwm_pin); ~MotorController(); bool initialize(); // 初始化GPIO和PWM void setSpeed(uint8_t speed); // 速度 0-255 void setDirection(Direction dir); void emergencyStop(); private: int in1_pin_, in2_pin_, pwm_pin_; int pwm_duty_cycle_{0}; Direction current_direction_{Direction::STOP}; // 可能還需要保存wiringPi或sysfs PWM的文件描述符 };在UltrasonicSensor類(lèi)中實(shí)現(xiàn)測(cè)距并在ObstacleAvoidance類(lèi)中整合// ObstacleAvoidance.cpp 片段 void ObstacleAvoidance::avoidanceLoop() { while (is_running_) { auto distance front_sensor_-getDistance(); if (distance SAFE_DISTANCE_THRESHOLD) { // 太近執(zhí)行避障策略 motor_controller_-emergencyStop(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 簡(jiǎn)單策略右轉(zhuǎn) motor_controller_-setDirection(MotorController::Direction::BACKWARD); std::this_thread::sleep_for(std::chrono::milliseconds(300)); motor_controller_-setDirection(MotorController::Direction::STOP); motor_controller_-setDirection(MotorController::Direction::FORWARD); motor_controller_-setSpeed(LOW_SPEED); // 持續(xù)右轉(zhuǎn)一小段時(shí)間 std::this_thread::sleep_for(std::chrono::milliseconds(500)); motor_controller_-setDirection(MotorController::Direction::FORWARD); motor_controller_-setSpeed(NORMAL_SPEED); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 循環(huán)頻率 } }這個(gè)簡(jiǎn)單的邏輯運(yùn)行在一個(gè)獨(dú)立的線(xiàn)程中通過(guò)共享的motor_controller_和sensor_指針需做好線(xiàn)程安全保護(hù)與系統(tǒng)其他部分交互。4.3 調(diào)試與部署從宿主機(jī)到目標(biāo)板調(diào)試是嵌入式開(kāi)發(fā)的一大難點(diǎn)。我的經(jīng)驗(yàn)是分層調(diào)試單元測(cè)試在宿主機(jī)上使用Google Test等框架對(duì)純邏輯類(lèi)如Navigation進(jìn)行測(cè)試。可以模擬傳感器輸入驗(yàn)證算法輸出是否正確。這能保證核心邏輯的正確性與硬件無(wú)關(guān)。硬件在環(huán)測(cè)試將編譯好的程序通過(guò)SCP拷貝到樹(shù)莓派上運(yùn)行。首先用strace跟蹤系統(tǒng)調(diào)用看文件操作GPIO/Sysfs是否正常。使用top或htop查看CPU和內(nèi)存占用。對(duì)于多線(xiàn)程問(wèn)題gdb配合gdbserver進(jìn)行遠(yuǎn)程調(diào)試是終極武器雖然配置稍復(fù)雜。日志系統(tǒng)在代碼中嵌入日志輸出至關(guān)重要。不要僅用printf可以封裝一個(gè)簡(jiǎn)單的日志類(lèi)支持日志級(jí)別DEBUG, INFO, ERROR并輸出到文件或網(wǎng)絡(luò)。在樹(shù)莓派上可以用tail -f logfile實(shí)時(shí)查看運(yùn)行狀態(tài)。部署時(shí)除了可執(zhí)行文件還要考慮依賴(lài)庫(kù)。如果使用動(dòng)態(tài)鏈接需要將目標(biāo)板缺失的庫(kù)如特定版本的libopencv_core.so一并拷貝過(guò)去并設(shè)置LD_LIBRARY_PATH環(huán)境變量。這也是為什么前期建議使用靜態(tài)鏈接進(jìn)行初步測(cè)試的原因。5. 進(jìn)階與避坑性能、內(nèi)存與常見(jiàn)“暗礁”當(dāng)項(xiàng)目跑起來(lái)后優(yōu)化和穩(wěn)定性就成了下一個(gè)課題。嵌入式C開(kāi)發(fā)中以下幾個(gè)坑我?guī)缀趺看味加龅健?.1 內(nèi)存管理杜絕泄漏與碎片即使在有操作系統(tǒng)的嵌入式Linux中內(nèi)存泄漏也是致命的長(zhǎng)時(shí)間運(yùn)行會(huì)導(dǎo)致系統(tǒng)耗盡內(nèi)存。C雖然提供了RAII但不當(dāng)使用依然會(huì)泄漏。智能指針是首選對(duì)于動(dòng)態(tài)分配的資源優(yōu)先使用std::unique_ptr獨(dú)占所有權(quán)和std::shared_ptr共享所有權(quán)。它們能自動(dòng)管理生命周期。注意在極端的實(shí)時(shí)循環(huán)中智能指針的引用計(jì)數(shù)操作可能帶來(lái)不可預(yù)測(cè)的開(kāi)銷(xiāo)需評(píng)估。避免在關(guān)鍵循環(huán)中動(dòng)態(tài)分配頻繁的new/delete或malloc/free不僅可能產(chǎn)生性能瓶頸還會(huì)導(dǎo)致內(nèi)存碎片。對(duì)于固定大小的緩沖區(qū)、對(duì)象池可以考慮使用靜態(tài)分配或標(biāo)準(zhǔn)庫(kù)容器如std::array在棧或全局?jǐn)?shù)據(jù)區(qū)預(yù)先分配。使用工具檢測(cè)在宿主機(jī)上可以使用valgrind的memcheck工具對(duì)單元測(cè)試或模擬運(yùn)行進(jìn)行內(nèi)存泄漏檢測(cè)。雖然不能直接用于ARM目標(biāo)板但能在開(kāi)發(fā)階段發(fā)現(xiàn)大部分問(wèn)題。5.2 性能優(yōu)化時(shí)間就是一切編譯器優(yōu)化熟悉GCC的編譯優(yōu)化選項(xiàng)如-O2平衡優(yōu)化、-Os優(yōu)化代碼大小對(duì)嵌入式很重要、-O3激進(jìn)優(yōu)化可能增加代碼體積。在CMake中可以通過(guò)set(CMAKE_CXX_FLAGS_RELEASE -O2 -DNDEBUG)來(lái)設(shè)置。關(guān)注拷貝開(kāi)銷(xiāo)C中對(duì)象的拷貝構(gòu)造函數(shù)可能很重尤其是包含動(dòng)態(tài)內(nèi)存的類(lèi)。在函數(shù)傳參和返回時(shí)多考慮使用const T常量引用傳遞大對(duì)象或使用移動(dòng)語(yǔ)義std::move轉(zhuǎn)移資源所有權(quán)。測(cè)量不要猜測(cè)使用std::chrono高精度時(shí)鐘來(lái)測(cè)量關(guān)鍵代碼段的執(zhí)行時(shí)間。樹(shù)莓派上也可以使用clock_gettime(CLOCK_MONOTONIC, ...)。優(yōu)化前先找到真正的熱點(diǎn)。5.3 交叉編譯與依賴(lài)庫(kù)的“坑”這是新手最容易崩潰的地方。錯(cuò)誤常常是“在宿主機(jī)上編譯運(yùn)行得好好的放到板子上就報(bào)GLIBCXX_3.4.29‘ not found或者Segmentation fault”。庫(kù)版本不一致宿主機(jī)和目標(biāo)板的C標(biāo)準(zhǔn)庫(kù)libstdc、系統(tǒng)庫(kù)如libc版本必須兼容。最穩(wěn)妥的方法是使用與目標(biāo)板系統(tǒng)版本盡可能一致的交叉編譯工具鏈和sysroot。構(gòu)建第三方庫(kù)如OpenCV for ARM時(shí)務(wù)必使用相同的工具鏈。Segmentation Fault調(diào)試在目標(biāo)板上可以通過(guò)dmesg查看內(nèi)核日志獲取崩潰的地址信息。編譯時(shí)一定要加上-g選項(xiàng)保留調(diào)試符號(hào)即使發(fā)布版本也可以考慮保留方便定位問(wèn)題。使用addr2line工具同樣是ARM版本可以將地址轉(zhuǎn)換成代碼行號(hào)。靜態(tài)鏈接的副作用雖然-static解決了庫(kù)依賴(lài)問(wèn)題但它會(huì)把所有用到的庫(kù)代碼都打包進(jìn)來(lái)導(dǎo)致最終可執(zhí)行文件巨大。而且如果靜態(tài)鏈接了GPL協(xié)議的庫(kù)可能會(huì)影響你的軟件發(fā)行許可。需要權(quán)衡利弊?;仡欉@篇從“空白”開(kāi)始的筆記嵌入式C的學(xué)習(xí)是一場(chǎng)漫長(zhǎng)的修行它沒(méi)有捷徑。我的建議是先建立一個(gè)能交叉編譯、能運(yùn)行“Hello World”到板子上的最小環(huán)境然后選擇一個(gè)核心C特性如類(lèi)封裝和一個(gè)系統(tǒng)知識(shí)點(diǎn)如多線(xiàn)程做一個(gè)微型實(shí)踐比如讓兩個(gè)LED燈以不同頻率閃爍最后將這些碎片整合到一個(gè)像智能小車(chē)這樣的綜合項(xiàng)目中。過(guò)程中你會(huì)反復(fù)遇到環(huán)境、編譯、運(yùn)行時(shí)的問(wèn)題每一次解決問(wèn)題的過(guò)程都是對(duì)底層知識(shí)的一次鞏固。別怕那第27篇空白筆記它代表著一個(gè)新的開(kāi)始當(dāng)你用C讓硬件真正“智能”起來(lái)時(shí)所有的困惑都會(huì)化為透徹的理解。