--- title: 【韌體】C/C++ 混編專案架構 tags: TTennis Pickup Robot disqus: hackmd --- <h1 style="text-align: center; color: orange;"> 🛠️ 【韌體】STM32CubeIDE 🛠️ </h1> <h2 style="text-align: center; color: skyblue;">C/C++ 混編專案架構</h2> <center> STM32 HAL 固件庫是基於 C 語言開發的, 為了使用 C++ 中許多更高級或方便的功能(class、overload...), 以及像是使用 rosserial 通訊庫這種基於 cpp 開發的庫, 就誕生 C/C++ 混編的需求了。 我們知道 C++ 是向下兼容 C 語言的, 其中涉及蠻多 C/C++ 的編譯知識,留給讀者自行查詢。 STM32CubeIDE 有很多混編架構的解法, 我會放上我目前認為最優雅的架構, 完整版可參考我的 [Github](https://github.com/pomelo925/TTennis-Pickup-Robot/tree/main/2.Firmware/Core-STM32H7)。 </center> </br> --- <h3><font color="magenza"> 1. 初步理解</font></h3> :::spoiler 初期建立專案時記得選 C++。 <center> ![image](https://hackmd.io/_uploads/SJey59wqC.png =70%x) </center> ::: </br> 架構圖(不用看箭頭們): ![STM 架構](https://hackmd.io/_uploads/r1Cediv9C.png) * `Core/`、`Manual/`、`Motor/` …: 這些是同層級的資料夾們,其中只有 `Core/` 是建立專案時即生成的。 * `Core/` : 底下的 `main.c` 是 STM 的程式進入點。 之後會新增 `mainpp.cpp`,接入 C++。 * `Interrupt/`: HAL 庫中原本用 C 語言寫的中斷函式,也可被拉到 `.cpp` 中實作。 * `Manual/`、`Chassis/`、`Motor/` ...: 這些是自定義,並以功能區分的 C++ lib。 他們可以引用 HAL 固件庫,他們之間可以有依賴關係。 他們也可以被 `Core/mainpp.cpp` 引入。 <center> :::info 這個架構最大好處,除了可以混編 C/C++, 是每個功能都被解藕成獨立的資料夾,要新增功能或移植代碼都非常方便。 不過在這個專案中,C++ lib 資料夾間是有依賴關係的。 所以也算不上真正地解藕,可以依照個人喜好彈性調整。 ::: </center> </br> --- <h3><font color="magenza"> 2. 專案架構 - 程式進入點 mainpp.h/.cpp</font></h3> > 接下來的部分,因為實際步驟繁瑣,且涉及的原理知識其實不少。 > 我會建議先照著做,之後再去好好理解這幾個東西。 > 關鍵字:Header File、Header Guard、extern "C"、class、constructor。 <center> :::info 為了避免炸版,請搭配 [Github](https://github.com/pomelo925/TTennis-Pickup-Robot/tree/main/2.Firmware/Core-STM32H7) 看哦 ::: :::success **`mainpp.cpp` 以及 `mainpp.h` 是混編的核心, 是 C 和 C++ 共同編譯的橋梁。** ::: </center> 1. `main.c` 只改兩個地方: * `#include "mainpp.h"`:引入 mainpp 的標頭檔。 * `main_function();`:這是個 `while(1)` 的死迴圈,目的是阻斷原本 `main.c` 的 `while(1)`,我們希望死迴圈是運作在 C++ 的環境裡。 <center> :::warning 之後,我們就不會在 main.c 中做任何事情,全部都拉出來實作。 ::: </center> 2. `mainpp.h`:全部照抄即可,`extern "C"` 原理自己查囉。 4. `mainpp.cpp`:可照抄可自改,只要架構對就沒問題。 :::spoiler mainpp.cpp 架構 ```cpp= //// 這裡 include 自己的 library //// #include <dc_motor.h> #include <mainpp.h> #include <manual.h> #include <ros1.h> void main_function(void){ //// 一次性初始化 //// DC_MOTOR::init(); ROS1::init(); //// while(1) 放要一直執行的邏輯 //// while(1){ //// 這裡如果 MANUAL::MODE 是 true,就會在 MANUAL::loop()中無限迴圈 //// //// 否則就會每次都執行一次 ROS1::spinCycle() //// if(MANUAL::MODE) MANUAL::loop(); else ROS1::spinCycle(); } //// 不會跑到 return //// return; } ``` ::: </br> 至此,我會建議 mainpp.cpp 先註解掉 `#include` 和 `while(1)` 內部邏輯。 並嘗試編譯,成功後再慢慢加入自定義 library。 </br> --- <h3><font color="magenza"> 3. 專案架構 - 自定義 library</font></h3> > 因為接下來的 library,像是 Chassis、Motor、Manual 等, > 都有依賴關係,就是 A #include B,B #include C 之類的。 > 我會建議先按照下方步驟複製貼上全部的,懂了再自己改就好。 <center> ::: warning 注意和 ROS1 相關的先全部省略, 包含 mainpp.cpp 的 ROS1 函式庫以及 ROS1 資料夾, 因為那必須多安裝 rosserial 庫才能正確編譯, 只有其他部分還是能正常編譯的。 ::: </center> 這裡我先示範如何新增 Manual 的 Library,其他資料夾完全同理。 * <font color="yellow"> step 1. 在專案根目錄中,新增「Source Folder」。</font> 輸入 Manual 後 Enter。 ![image](https://hackmd.io/_uploads/rJf6MiP50.png =90%x) * <font color="yellow"> step 2. 對 Manual 資料夾右鍵,新增「Source File」。</font> 取名 manual.cpp,記得無論名字為何都要有檔名 .cpp。 跳出來的檔案中應該會有類似這樣的註解。 ```cpp= /* * manual.cpp * * Created on: Aug 6, 2024 * Author: User */ ``` * <font color="yellow"> step 3. 對 Manual 資料夾右鍵,新增「Header File」。</font> 一樣取名 manual.h,檔案會有自動生成的 Header Guard。 ```cpp= /* * manual.h * * Created on: Aug 5, 2024 * Author: User */ #ifndef INC_MANUAL_HPP_ #define INC_MANUAL_HPP_ //// 新增代碼都要在這裡面 //// #endif /* INC_MANUAL_HPP_ */ ``` * <font color="yellow"> step 4. 直接複製貼上 Github 上對應代碼。</font> * <font color="yellow"> step 5. 設定專案的路徑設定。</font> 注意上面 Configuration 中的 Debug 和 Release 要分別新增。 ![image](https://hackmd.io/_uploads/HJiLIoPqR.png) * <font color="yellow"> step 6. 這樣就可以了,其實很簡單。</font> </br> </br>