linux開發板物聯網項目實戰之環境檢測學生畢業設計首選

華清遠見研發中心 發佈 2024-04-27T20:47:36.203543+00:00

設置一級照明為 50lux,二級照明為 30lux,三級照明為 1lux,當光照強度小於 50lux 會開啟一個 led 燈,小於 30lux 會開啟 2 個 led 燈,當光照強度小於 1lux 時,3 個 led 燈會全部開啟。

stm32mp157開發板FS-MP1A是華清遠見自主研發的一款高品質、高性價比的linux+單片機二合一的嵌入式教學級開發板。開發板搭載ST的stm32MP157高性能微處理器,集成2個Cortex-A7核和1個Cortex-M4 核,A7核上可以跑Linux作業系統,M4核上可以跑FreeRTOS、RT-Thread等實時作業系統。開發板搭配仿真器、顯示屏、攝像頭、資源擴展板等豐富的擴展模塊,可拓展物聯網、人工智慧等相關技術學習,還可以拓展豐富的項目實戰,非常貼合企業當下開發需求,是一款嵌入式Linux入門進階必備開發板!

可學習技術:嵌入式Linux應用/系統/驅動開發、ARM裸機開發、qt界面編程、STM32單片機、FreeRTOS、人工智慧機器視覺等。其中ARM Cortex-A7裸機開發課程是華清遠見獨有特色課程,可關注:https://www.bilibili.com/video/BV1Xe4y1i7vm/,持續更新中。

14個Linux+Qt綜合項目案例,6個MP1A物聯網拓展項目

關注公眾號「華清遠見在線實驗室」,回復「mp157項目」,即可領取項目配套文檔及源碼。

Linux+QT綜合項目案例:華清遠見stm32mp157開發板優勢特色部分,包括音樂播放器、智慧家庭、智能工業電錶、智能出行助手、智能貓眼、環境監測、智能安防、智能語音識別等10餘個項目案例,涉及家居、醫療、農業多種應用方向,在案例中使用了多種物聯網和嵌入式技術,包括OT開發、linux應用開發、linux驅動開發、物聯網雲端接入、MQTT協議、json字符串等知識點。

基於Linux+Qt的環境檢測項目

項目功能介紹:

(1)設計有環境監測功能,通過溫濕度、光照傳感器實時採集環境溫度、濕度、光照顯示到 LCD 屏上。

(2)設計有設備控制功能,通過控制觸控螢幕來進行控制 3 個 LED 燈、風扇、蜂鳴器。

(3)設計智能檢測功能,用戶可以根據自己的需求修改智能檢測的閾值,開啟智能檢測後,當環境達到閾值後會啟動設備。比如設置溫度閾值為 28 攝氏度,當環境溫度達到 28 攝氏度時,會自動開啟風扇。設置一級照明為 50lux,二級照明為 30lux,三級照明為 1lux,當光照強度小於 50lux 會開啟一個 led 燈,小於 30lux 會開啟 2 個 led 燈,當光照強度小於 1lux 時,3 個 led 燈會全部開啟。

開發平台:

華清遠見stm32mp157開發板豪華套餐(開發板+仿真器+五寸屏+攝像頭+資源擴展板+tf卡+讀卡器)

項目實戰:

Qt 開發環境搭建

主機開發環境說明

1) 本文檔主要介紹 linux 環境下的 Qt 程序開發;

2) 主機 Qt 版本為 5.14.1;

主機 Qt 環境搭建及使用

Qt Creator 安裝

將 qt-creator-opensource-linux-x86_64-4.10.1.run(Qt 實驗源碼\工具軟體) 複製到 ubuntu 主機中,可以採用共享文件夾的方式也可以使用 tfp方式將文 件存入家目錄下的 Downloads 目錄。我們需要在終端中賦予安裝程序可執行的權限

我們可以使用圖形化的文件管理器來查看

雙擊「qt-creator-opensource-linux-x86_64-4.10.1.run」圖標運行安裝程序。

出現如下界面:

等待程序驗證完成後點擊「Next」

這裡我們需要登錄或者註冊一個帳號,如果我們之前已經註冊過直接登錄就可以。如果沒有註冊過則需要新註冊有一個帳號後登錄。這裡筆者已經註冊過帳號,所以直接登錄。 登錄成功後出現如下界面,點擊 Next

這裡選擇安裝路徑

可以直接默認,Next

這路選擇安裝的組件,直接默認即可

這裡我們需要同意用戶協議

這個界面告訴我們安裝完成後需要占用的空間。點擊」Install」按鈕後開始安裝。

安裝完成後出現如下界面

點擊「Finish」按鈕後將彈出 Qt Creator 主界面

點擊「Cancel」按鈕後即可正常使用

Qt5.14.1 安裝

複製到 qt-opensource-linux-x64-5.14.1.run(Qt 實驗源碼\工具軟體)到 ubuntu 主機中,可以採用共享文件夾的方式也可以使用 tfp 方式將文件存入家目錄下的 Downloads 目錄。進入所在文件夾,先給執行權限

輸入命令

chmod +x ./qt-opensource-linux-x64-5.14.1.run

安裝在命令行輸入

./qt-opensource-linux-x64-5.14.1.run

會有可視化引導安裝,一直 next 就行了

在選擇安裝組件的時候要是不知道選擇那些就全選了 大概有 4 個 G 左右

下載 gcc 和 G++

sudo apt-get install gcc g++

下載 cmake

sudo apt-get install cmake

下載連結庫

sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev

Qt Creator 配置

1)配置 GCC

運行 QtCreator 後,依次點擊"Tool"->"Options",出現選項對話框,在左側點擊"Kits",右 邊選擇"Compilers"標籤。 檢查有沒有下圖標註的 C++和 C ,般按上面步驟執行後都會有

點擊右側"Add"按鈕,彈出下拉列表後,選擇"GCC"的"C"

填寫信息如下,"Name"為"Auto-GCC","Compiler path"點擊旁邊的"Browse.."按鈕選擇編譯器的路徑,例子中的路徑是 「/usr/bin/gcc」

2)配置 G++

點擊右側"Add"按鈕,彈出下拉列表後,選擇"GCC"的"C++",下面的文本框填寫"Name" 為"Auto-G++","Compiler path"點擊旁邊的"Browse.."按鈕選擇編譯器的路徑,例子中的路徑是" /usr/bin/g++"。

填寫完成後,點擊"Apply"。

3)配置 qmake

選擇"Qt Versions"標籤,如果有下面紅框中的文本,可以跳過下面步驟

如果沒有,在右側點擊"Add..."

會彈出 qmake 路徑選擇對話框,這裡以"

/home/linux/Qt5.14.1/5.14.1/gcc_64/bin/qmake"為例子。 選擇」qmake」文件後,點擊"Open"按鈕

"Version name"改為" Qt %{Qt:Version} GCC"。然後點擊"Apply"按鈕。

4)配置 Kits

點擊左側"Kits",右側選擇"Kits"標籤。檢查有沒有下圖紅框選中的文本,如果有可以跳過下面步驟

然後沒有,點擊 Add:

在彈出的對話框中"Name"為"Desktop","Device Type"選擇"Desktop"選項, "Sysroot"選擇目標設備的系統目錄,"Compiler"選擇之前配置的名稱"Auto-GCC"和"Auto-G++","Qt version"選擇之前配 置的名稱"Qt 5.14.1GCC",其它默認即可,最後點擊"Apply"和"OK"按鈕。


Qt Creator 新建工程

注意:工程路徑最好不要包含中文、特殊字符、空格等。

我們可以新建一個「qt」文件夾,該文件夾用作我們以後存放原始碼。

打開 Qt Creator,在歡迎頁面點擊 「New」按鈕,來新建一個工程。

在出現的新建項目窗口中,我們選則「Application」->「Qt Widgets

Application」,然後點擊右下方「Choose…」按鈕,來創建一個桌面 Qt 應用。

我們在這裡設置項目介紹和源碼位置,我們這裡創建一個名為「HelloWorld」的示例項目,設置完成之後點擊 next

直接點擊 next

隨後進行細節設置,主要設置要創建的源碼文件的基本類信息,包括類名等。這裡我們可以根據自己的項目特點進行設置。需要說明的一點就是基類的選擇,這裡基類有 QMainWindow、QWidget、QDialog 三種,它們的不同之處如下:

  • QMainWindow 類提供一個帶有菜單條,工具條和一個狀態條的主應用程式窗口。主窗口通常提供一個大的中央窗口部件,以及周圍菜單,工具條,和一個狀態欄。QMainWindow 窗口經常被繼承,使得封裝中央部件,菜單,工具條,狀態欄等都變得很容易,當用戶點擊它的時候,相應的槽就會被調用;
  • QWidget 類是所有用戶界面對象的基類,窗口部件是用戶界面的一個基本單元,它從窗口系統接收滑鼠,鍵盤和其他消息,並在屏幕上繪製自己。一個窗口部件可以被他的父窗口或者是其他窗口擋住一部分;
  • QDialog 類是對話框窗口的基類,對話框窗口主要用於短期任務和用戶進行短期通訊的頂級窗口,QDialog 可以是模態對話框或者是非模態對話框。QDialog 支持擴展並帶有返回值,他們可以帶有默認值;我們在這裡選擇 QDialog 類即可,點擊 next 完成類信息設置。

直接點擊 next 按鈕即可。

然後進行工具選擇,該頁面可以選擇我們創建的工程可以使用的工具,選擇想要使用的編譯器模塊,例如下圖 。點擊 next

最後我們設置匯總信息,如果不需要版本控制等功能,直接點擊完成finish 即可。

隨後我們就進入到了主界面,這時候 Qt 已經幫我們做好了一些準備工作,包括創建了一些文件,寫好了一些前置代碼等等。

我們可以點擊左邊 protect 欄,來查看我們的編譯選項。

我們可以在左下角選擇編譯 Debug 版或者 Release 版,即調試版或發行版。

左下角綠色剪頭是編譯並運行,錘子是僅編譯,我們可以直接點擊綠色小箭頭將我們導入的工程編譯並運行起來。

點擊運行按鈕後,我們可以看到 helloworld 窗口運行起來了。

Qt Creator導入工程

我們可以將已存在的 Qt 程序項目直接打開,這裡以上一章節的HelloWorld 程序為例。首先我們確定源碼存在的位置,如 HelloWorld 程序源碼在 /home/linux/qt/helloworld 路徑下點擊歡迎頁面的「Open」 按鈕可以打開已有的工程

找到我們剛才解壓好的源碼,選擇「helloworld.pro」文件並點擊打開

接下來我們就可以進入到代碼編輯界面了。

左上角是項目欄,點擊項目名稱左邊的小箭頭可以展開項目目錄

我們可以點擊左邊項目欄,來查看我們的編譯選項。需注意的是構建設置中的路徑應與工程路徑處於同級目錄下。

我們可以在左下角選擇編譯 Debug 版或者 Release 版,即調試版或發行版。

左下角綠色剪頭是編譯並運行,錘子是僅編譯,我們可以直接點擊綠色小箭頭將我們導入的工程編譯並運行起來

點擊運行按鈕後,我們可以看到 HelloWorld 窗口運行起來了。

Qt Creator 文件說明

通過上面兩個章節,我們學習到了 Qt 程序的新建與導入的方法,也知道了Qt 會幫我們做一些基礎工作,比如幫我們建立了一些文件,那麼這些文件都是幹什麼用的呢?我們以HelloWorld 程序來說明一下。

以「.pro」為後綴名的文件,為 Qt 的項目管理文件,存儲項目設置的文件;

「Qt += core gui」表示項目中加入 core gui 模塊。core gui 是 Qt 用於GUI 設計的類庫模塊,如果創建的是控制台(console)應用程式,就不需要添加 core gui。

Qt 類庫以模塊的形式組織各種功能的類,根據項目涉及的功能需求,在項目中添加適當的類庫模塊支持。例如,如果項目中使用到了涉及資料庫操作的類就需要用到 sql(資料庫)模塊,在 pro 文件中需要在後面加上 sql:

1 Qt += core gui sql

「greaterThan(QT_MAJOR_VERSION, 4): QT += widgets」,這是個條件執行語句,表示當 Qt 主版本大於 4 時,才加入 widgets 模塊。

「TARGET = HelloWorld」表示生成的目標可執行文件的名稱,即編譯後生成的可執行文件是 HelloWorld.exe。

「TEMPLATE = app」表示項目使用的模板是 app,是一般的應用程式。

後面的 SOURCES、HeaderS、FORMS 記錄了項目中包含的源程序文件、頭文件和窗體文件(.ui 文件)的名稱。這些文件列表是 Qt Creator 自動添加到項目管理文件裡面的,用戶不需要手動修改。當添加一個文件到項目,或從項目里刪除一個文件時,項目管理文件里的條目會自動修改。

文件夾「Header」中,存放的是所設計的窗體類的頭文件;

文件夾「Sources」中,存放著源碼文件。main.cpp 是實現 main()函數的程序文件,HelloWorld.cpp 是 widget.h 里定義類的實現文件。C++中,任何窗體或界面組件都是用類封裝的,一個類一般有一個頭文件(.h 文件)和一個源程序文件(.cpp 文件);

文件夾「Forms」中,存放著界面設計文件,「.ui」文件是一個 XML 格式存儲的窗體上的元件及其布局的文件,雙擊項目文件目錄樹中的文件 ui,會打開一個集成在 Qt Creator 中的 Qt Designer 對窗體進行可視化設計;

UI 設計器有以下一些功能區域:

組件面板:窗口左側是界面設計組件面板,分為多個組,如 Layouts、Buttons、Display Widgets 等,界面設計的常見組件都可以在組件面板里找到。

中間主要區域是待設計的窗體。如果要將某個組件放置到窗體上時,從組件面板上拖放一個組件到窗體上即可。

Signals 和 Slots 編輯器與 Action 編輯器是位於待設計窗體下方的兩個編輯器。Signals 和 Slots 編輯器用於可視化地進行信號與槽的關聯,Action 編輯器用於可視化設計 Action。

布局和界面設計工具欄:窗口上方的一個工具欄,工具欄上的按鈕主要實現布局和界面設計。

對象瀏覽器(Object Inspector):窗口右上方是 Object Inspector,用樹狀視圖顯示窗體上各組件之間的布局包含關係,視圖有兩列,顯示每個組件的對象名稱(ObjectName)和類名稱。

屬性編輯器(Property Editor):窗口右下方是屬性編輯器,是界面設計時最常用到的編輯器。屬性編輯器顯示某個選中的組件或窗體的各種屬性及其取值,可以在屬性編輯器里修改這些屬性的值。屬性編輯器的內容分為兩列,左側為屬性的名稱,右側為屬性的值。屬性又分為多個組,實際上表示了類的繼承關係,位於下方的類屬性組繼承自位於上方的類屬性組;

如果我們需要新建資源文件、源碼文件等,可以在項目文件夾出點擊滑鼠右鍵,選擇 Add New;如果我們有新的文件需要添加,可以在項目文件夾出點擊滑鼠右鍵,選擇 Add Existing Files。

Qt Creator 幫助文檔

Qt 的幫助文檔是伴隨我們學習 Qt 開發的好夥伴。在 Qt 開發過程中,我們會面臨圖形接口使用的問題,它不像 C 語言那樣就那麼幾個函數接口,圖形接口的接口數量可以用海量來形容,常用的我們可能能記住,其它的就沒有必要去記了,用到什麼就去幫助文檔查看用法是比較方便的。我們可以按 F1 按鍵,或通過上方導航欄的「help->contects」來進入幫助文檔。

上方的前進後退按鈕方便我們查看文檔,如返回到上一步,返回到下一步。

我們可以通過幫助文檔來查看以下幾個部分:類使用的相關介紹;

查看相關類的使用介紹,我們可以先進入到幫助文檔,然後在左上角選擇「Search」。筆者這裡以 QWidget 類為例,輸入我們想要查找的類的名字,然後雙擊查找結果來查看說明。

也可以先將滑鼠移動到想要查詢的類的位置,如圖所示,將滑鼠移動至「QWidget」處,然後按「F1」鍵,即可跳轉到相應的幫助文檔。

我們可以通過再按一次「F1」鍵來全窗口查看幫助文檔,按「Esc」鍵可以退出。

部分常用的成員元素包括以下幾項:

公有成員函數:操作部件屬性的相關函數;

公有槽函數:Qt 類中已經定義好的槽函數,直接可與信號相連接;

信號:軟中斷,如按下按鈕觸發 pressed() 信號等;

保護成員函數:通常事件所對應的虛函數放在此處;

事件:常用事件,如操作滑鼠觸發的滑鼠事件;

滾動滑鼠滾輪,向下即可看到「Qwdget Class」類的相關說明了。

部分常用的成員元素包括以下幾項:

公有成員函數:操作部件屬性的相關函數;

公有槽函數:Qt 類中已經定義好的槽函數,直接可與信號相連接;

信號:軟中斷,如按下按鈕觸發 pressed() 信號等;

保護成員函數:通常事件所對應的虛函數放在此處;

事件:常用事件,如操作滑鼠觸發的滑鼠事件;

滾動滑鼠滾輪,向下即可看到「Qwdget Class」類的相關說明了。

1) 查看所用的部件的相應成員函數。

我們可以查找到該類所用部件的相應成員函數的使用方法、功能、參數、返回值等等,我們以「按鈕」控制項,即「QPushButton Class」類為例,我們通過索引搜索的方式,來找到這個類

我們可以通過點擊「Public Functions」 來查看「QPushButton」這個類中的成員函數。

這裡以「QPushButton(const QString &text, QWidget *parent =Q_NULLPTR)」為例,我們點擊函數名字可以進入到函數詳情中。我們可以看到相應的描述為:以「text」為顯示內容,以「parent」為父對象,構造一個push 按鈕。「text」「parent」為函數參數,由於是構造函數,所以此函數沒有返回值。

還有一些函數是繼承自其它類的,例如「Public Functions」中有 21 個繼承自「QAbstractButton」類的函數,我們點擊「QAbstractButton」即可查看。擊「QAbstractButton」即可查看

同樣我們可以點擊相應的函數進入查看詳情。如查看「voidsetText(const QString &text)」。

2) 查看所用的部件的信號。

我們這裡還是以「PushButton」為例,我們點擊「Public Slots」。

可以看到「PushButton」本身有一個「void showMenu()」的信號,並且有很多繼承自其他類的信號。

一般來說我們用的「PushButton」的信號,最多的是用到其繼承自基類「QAbstractButton」中的幾個信號,分別是點擊(按下後抬起)、按壓(單按下)、釋放(單抬起)等

我們可以點擊相應信號查看詳情

3) 查看所用的部件的事件(所對應的虛函數如何編寫)。

部件常用事件主要在 「QWidget」中聲明,選擇「Events」即可查看相關說明。

每個事件都對應著事件函數。

點擊事件函數可查看詳情

項目總體設計介紹

(1)設計有環境監測功能,通過溫濕度、光照傳感器實時採集環境溫度、濕度、光照顯示到 LCD 屏上。

(2)設計有設備控制功能,通過控制觸控螢幕來進行控制 3 個 LED 燈、風扇、蜂鳴器。

(3)設計智能檢測功能,用戶可以根據自己的需求修改智能檢測的閾值,開啟智能檢測後,當環境達到閾值後會啟動設備。比如設置溫度閾值為 28 攝氏度,當環境溫度達到 28 攝氏度時,會自動開啟風扇。設置一級照明為 50lux,二級照明為 30lux,三級照明為 1lux,當光照強度小於 50lux 會開啟一個 led 燈,小於 30lux 會開啟 2 個 led 燈,當光照強度小於 1lux 時,3 個 led 燈會全部開啟

M4 部分實驗原理

M4 部分功能概述

M4 核部分主要實現兩個功能,首先可以接收 A7 核發送的指令,根據指令控制風扇、蜂鳴器以及 LED 燈工作,同時 M4 部分可以採集環境光、溫濕度傳感器數據,將採集數值打包成 JSON 字符串然後發送給 A7。另外,通過按鍵可以控制數碼管顯示採集的溫濕度、環境光數據。

cubeIDE 功能配置

首先打開 cubeIDE,創建一個新工程,進入 cubeMX 配置界面,因為需要實現 A7 與 M4 通信,需要配置 IPCC 與 OPENAMP 部分,如下圖所示

配置 I2C1 用於採集溫濕度、環境光傳感器數據,這裡分別選擇 PF14、PF15 引腳用作 I2C1_SDA 和 I2C1_SCL 功能

因為需要用到數碼管顯示採集的傳感器數值,這裡通過 SPI 進行控制,使用 SPI 默認引腳,分別對 PE11、PE12、PE13 和 PE14 配置為 SPI4_NSS、SPI4_SCK、SPI4_MISO 和 SPI4_MOSI。切換到 SPI4 標籤,勾選給「M4」,Mode」選擇「Full-Duplex-Master」,使用硬體片選,選擇「Hardware NSS

Output Signal」,其配置如下圖所示

擴展板 LED 燈對應的 GPIO 引腳分別為 PE8、PE10、PF10,這裡左鍵點擊設置為 GPIO_Output

可以通過按鍵中斷控制數碼管顯示的數值,這裡配置擴展板的按鍵為PF9,如下所示

另外,這裡還需要對剛才配置的 I/O 引腳設置「 Pin Reservation」給

「 Cortex-M4」,否則 STM32CubeMX 不會生生成 GPIO 初始化相關代碼。具體操作:在剛才選擇的引腳上,滑鼠右鍵選擇「 Pin Reservation」->「 CortexM4」。

打開 GPIO 標籤,對 PF9 引腳進行配置

實驗中還用到了蜂鳴器與風扇,查看原理圖得,對應管腳分別為 PB6 與

PE9,分別配置為 TIM4 與 TIM1 功能,在本次實驗中,系統默認時鐘頻率為

64MHz,TIM 配置如下所示

如果實現開發板與電腦串口通信,這裡可以通過 485 總線實現,即配置UART5,如下所示

另外,在本次實驗中,A7 與 M4 數據通信和數碼管顯示相當於同時運行,可以通過配置 FreeRTOS 實現多任務運行。切換到中間件「Middleware」的

「FREERTOS」,默認自動勾選給 M4 了,然後接口「Interface」選擇

「CMSIS_V2」。為了運行其他任務,這裡需要切換到「Tasks and Queues」標籤,可以看到看到默認有個「defaultTask」任務,我們點擊「Add」再新增一個任務,如圖所示

上述配置完成以後,還要注意一點,因為用到 FreeRTOS,這裡我們不能為每一種外設生成頭文件,如下所示

配置完成以後保存,然後生成初始化代碼,其間會出現如下提示框,選擇「Yes」

代碼實現

生成初始代碼以後,在 usart.c 文件 USER CODE BEGIN 1 與 USER CODE

END 1 之間添加 printf 的重定向函數,實現 UART5 與 printf 綁定。

#ifdef __GNUC__

#define putchar_PROTOTYPE int __io_PUTCHAR(int ch)

PUTCHAR_PROTOTYPE

{

HAL_UART_Transmit(&huart5,(uint8_t*)&ch,1,HAL_MAX_DELAY);

return ch;

}

#endif

這裡創建了一個 RPMSG tty 通道,用於實現 A7 與 M4 之間的數據傳輸

1、初始化 RPMSG tty 虛擬串口

printf("Virtual UART0 OpenAMP-rpmsg channel creation\r\n");

if (VIRT_UART_Init(&huart0) != VIRT_UART_OK) {

printf("VIRT_UART_Init UART0 failed.\r\n");

Error_Handler();

}

2、註冊回調函數以按通道接收消息

if(VIRT_UART_RegisterCallback(&huart0, VIRT_UART_RXCPLT_CB_ID,

VIRT_UART0_RxCpltCallback) != VIRT_UART_OK)

{

Error_Handler();

}

3、虛擬串口回調函數

M4 接收到數據以後,將會調用該回調函數,需要將接收的數據複製到用戶

內存,修改接收標誌位,通知用戶完成數據接收。

void VIRT_UART0_RxCpltCallback(VIRT_UART_HandleTypeDef *huart)

{

printf("Msg received on VIRTUAL UART0 channel: %s \n\r", (char *)

huart->pRxBuffPtr);

/* copy received msg in a variable to sent it back to master processor

in main infinite loop*/

VirtUart0ChannelRxSize = huart->RxXferSize < MAX_BUFFER_SIZE?

huart->RxXferSize : MAX_BUFFER_SIZE-1;

memcpy(VirtUart0ChannelBuffRx, huart->pRxBuffPtr,

VirtUart0ChannelRxSize);

VirtUart0RxMsg = SET;

添加driver_si7006.c文件,編寫溫濕度數據採集函數

uint8_t SI7006_Init(void)

{

HAL_I2C_Init(&hi2c1);

SI7006_WriteByte(SI7006CMD_RESET);

return 0;

}

uint8_t SI7006_WriteByte(uint8_t reg)

{

uint8_t write_data = reg;

if(HAL_I2C_Master_Transmit(&hi2c1, SI7006_ADDR | SI7006_W ,

(uint8_t*)&write_data, 1, 300) != HAL_OK)

{

Error_Handler();

}

while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);

return 0;

}

uint16_t SI7006_ReadWord(uint8_t reg)

{

uint16_t read_data = 0;

if(HAL_I2C_Master_Transmit(&hi2c1, SI7006_ADDR | SI7006_W ,

(uint8_t*)&reg, 1, 300) != HAL_OK) //發送命令

{

Error_Handler();

}

while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);

if(HAL_I2C_Master_Receive(&hi2c1, SI7006_ADDR | SI7006_R ,

(uint8_t*)&read_data, 2, 300) != HAL_OK) //接收 word 數據

{

Error_Handler();

}

return read_data;

}

uint16_t SI7006_Read_Data(uint16_t cmd)

{

uint16_t data = 0,data_low = 0,data_high = 0;

data = SI7006_ReadWord(cmd); //採集溫濕度

data_low = (data & 0xff); //進行高低字節轉換

data_high = (data >> 8) & 0xff;

data = (data_low << 8) + data_high;

return data;

}

uint16_t SI7006_Readhum(void)

{

uint16_t hum = 0;

hum = SI7006_Read_Data(SI7006CMD_RH_HOLD);

hum = (125*hum/65536 - 6);

return hum;

}

uint16_t SI7006_Readtem(void)

{

uint16_t tem = 0;

tem = SI7006_Read_Data(SI7006CMD_TEMP_HOLD);

tem = ((17572*tem)/65536 - 4685);

return tem;

}

添加 driver_ap3216.c 文件,編寫環境光數據採集函數

uint8_t AP3216_Init(void)

{

uint8_t ret_value = 0;

AP3216_WriteOneByte(SYS_CONFIG_ADDR, SYS_SW_RESET);

HAL_Delay(50);

AP3216_WriteOneByte(SYS_CONFIG_ADDR, SYS_ALS_ACT);

HAL_Delay(50);

ret_value = AP3216_ReadOneByte(SYS_CONFIG_ADDR);

if(ret_value != SYS_ALS_ACT)

{

printf("read error \n");

}

printf("\r I2C Configuration register: 0x%x \n", SYS_CONFIG_ADDR);

printf("\r I2C Configuration value: 0x%x \n", SYS_ALS_ACT);

printf("\r I2C Read configuration value: 0x%x \n", ret_value);

return 0;

}

uint8_t AP3216_WriteOneByte(uint8_t reg, uint8_t data)

{

uint16_t write_data = reg | (data<<8)

if(HAL_I2C_Master_Transmit(&hi2c1, AP3216_ADDR | AP3216_W ,

(uint8_t*)&write_data, 2, 300) != HAL_OK)

{

Error_Handler();

}

while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);

return 0;

}

uint8_t AP3216_ReadOneByte(uint8_t reg)

{

uint8_t read_data = 0;

if(HAL_I2C_Master_Transmit(&hi2c1, AP3216_ADDR | AP3216_W ,

(uint8_t*)&reg, 1, 300) != HAL_OK)

{

Error_Handler();

}

while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);

if(HAL_I2C_Master_Receive(&hi2c1, AP3216_ADDR | AP3216_R ,

(uint8_t*)&read_data, 1, 300) != HAL_OK)

{

Error_Handler();

}

return read_data;

}

uint16_t AP3216_Read_ALS_Data()

{

uint8_t als_l = 0, als_h = 0;

uint16_t data;

als_l = AP3216_ReadOneByte(ALS_DATA_LOW);

als_h = AP3216_ReadOneByte(ALS_DATA_HIGH);

data = (als_h<<8) | (als_l);

return data;

}

添加 driver_m74hc.c 文件,編寫數碼管顯示函數

uint8_t num[10] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};

uint8_t rw_595_Register(uint8_t reg,uint8_t data)

{

uint8_t txdata[2] = {reg, data};

if(HAL_SPI_Transmit(&hspi4, txdata ,2,300) != HAL_OK) //發送數據

{

Error_Handler();

}

return 0;

}

void M74HC595_ReadDataTest(uint16_t data,uint8_t sign)

{

uint8_t data1,data2, data3,data4;

data1 = data/1000;

data2 = data%1000/100;

data3 = data%100/10;

data4 = data%10;

num[data1] = (num[data1] & 0x7f);

rw_595_Register(0X01,num[data1]);

HAL_Delay(1);

if(sign == 0) //sign = 1 為整數, = 0 為小數

num[data2] = (num[data2] | 0x80);

else

num[data2] = (num[data2] & 0x7f);

rw_595_Register(0X02,num[data2]);

HAL_Delay(1);

num[data3] = (num[data3] & 0x7f);

rw_595_Register(0X04,num[data3]);

HAL_Delay(1);

num[data4] = (num[data4] & 0x7f);

rw_595_Register(0X08,num[data4]);

HAL_Delay(1);

}

添加 driver_pwm.c 文件,編寫風扇與蜂鳴器控制函數

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

if(htim->Instance==TIM4) //beep

{

__HAL_TIM_SET_COMPARE(&htim4,TIM_CHANNEL_1,9999);

}

if(htim->Instance==TIM1) //fan

{

__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,level *

3000);

}

}

void TIM4_PWM_START(void)

{

if(HAL_TIM_Base_Start_IT(&htim4) != HAL_OK)

{

Error_Handler();

}

HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);

}

void TIM4_PWM_STOP(void)

{

if(HAL_TIM_Base_Start_IT(&htim4) != HAL_OK)

{

Error_Handler();

}

HAL_TIM_PWM_Stop(&htim4,TIM_CHANNEL_1);

}

void TIM1_PWM_START(void)

{

if(HAL_TIM_Base_Start_IT(&htim1) != HAL_OK)

{

Error_Handler();

}

HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);

}

void TIM1_PWM_STOP(void)

{

if(HAL_TIM_Base_Start_IT(&htim1) != HAL_OK)

{

Error_Handler();

}

HAL_TIM_PWM_Stop(&htim1,TIM_CHANNEL_1);

}

在 StartDefaultTask() 任務中實現 A7 與 M4 的數據傳輸,在 StartTask02

() 任務中實現控制數碼管顯示採集的傳感器數值,相關代碼如下

void StartDefaultTask(void *argument)

{

/* USER CODE BEGIN 5 */

printf("Virtual UART0 OpenAMP-rpmsg channel creation\r\n");

if (VIRT_UART_Init(&huart0) != VIRT_UART_OK) {

printf("VIRT_UART_Init UART0 failed.\r\n");

Error_Handler();

}

if(VIRT_UART_RegisterCallback(&huart0, VIRT_UART_RXCPLT_CB_ID,

VIRT_UART0_RxCpltCallback) != VIRT_UART_OK)

{

printf("callback 0 error \n");

Error_Handler();

}

AP3216_Init();

SI7006_Init();

/* Infinite loop */

for(;;)

{

OPENAMP_check_for_message();

if (VirtUart0RxMsg)

{

VirtUart0RxMsg = RESET;

usr = cJSON_CreateObject();

#if 1

json = cJSON_Parse((char *)VirtUart0ChannelBuffRx);

json_led1 = cJSON_GetObjectItem(json,"led1");

json_led2 = cJSON_GetObjectItem(json,"led2");

json_led3 = cJSON_GetObjectItem(json,"led3");

json_beep = cJSON_GetObjectItem(json,"beep");

json_fan = cJSON_GetObjectItem(json,"fan");

if( strcmp((char*)json_led3->string,"led3") == 0)

{

printf("led1 light \n");

if(json_led3->valueint == 1)

HAL_GPIO_WritePin(GPIOE,

GPIO_PIN_8,GPIO_PIN_SET);

else

HAL_GPIO_WritePin(GPIOE,

GPIO_PIN_8,GPIO_PIN_RESET);

}

else if( strcmp((char*)json_led2->string,"led2") == 0)

{

printf("led2 light \n");

if(json_led2->valueint == 1)

HAL_GPIO_WritePin(GPIOF,

GPIO_PIN_10,GPIO_PIN_SET);

else

HAL_GPIO_WritePin(GPIOF,

GPIO_PIN_10,GPIO_PIN_RESET);

}

else if( strcmp((char*)json_led1->string,"led1") == 0)

{

printf("led1 light \n");

if(json_led1->valueint == 1)

HAL_GPIO_WritePin(GPIOE,

GPIO_PIN_10,GPIO_PIN_SET);

else

HAL_GPIO_WritePin(GPIOE,

GPIO_PIN_10,GPIO_PIN_RESET);

}

else if( strcmp((char*)json_beep->string,"beep") == 0)

{

printf("beep work \n");

if(json_beep->valueint == 1)

TIM4_PWM_START();

else

TIM4_PWM_STOP();

}

else if( strcmp((char*)json_fan->string,"fan") == 0)

{

printf("fan work \n");

switch(json_fan->valueint)

{

case 1 :

case 2 :

case 3:

level = json_fan->valueint;

TIM1_PWM_START();

break;

case 0:

TIM1_PWM_STOP();

break;

}

}

#endif

value_als = AP3216_Read_ALS_Data();

value_tem = SI7006_Readtem();

value_hum = SI7006_Readhum();

cJSON_AddNumberToObject(usr,"als",value_als);

cJSON_AddNumberToObject(usr,"tem",value_tem);

cJSON_AddNumberToObject(usr,"hum",value_hum);

out = cJSON_Print(usr);

printf("the out: %s \n",out);

strcpy((char *)BuffTx,out);

if( VIRT_UART_Transmit(&huart0, BuffTx, strlen((const char

*)BuffTx)) != VIRT_UART_OK)

{

printf("transmit error \n");

}

memset(BuffTx, 0 ,strlen((const char *)BuffTx));

cJSON_Delete(usr);

memset(VirtUart0ChannelBuffRx, 0 ,VirtUart0ChannelRxSize);

cJSON_Delete(json);

cJSON_Delete(json_led1);

cJSON_Delete(json_led2);

cJSON_Delete(json_led3);

cJSON_Delete(json_beep);

cJSON_Delete(json_fan);

}

}

/* USER CODE END 5 */

}

void StartTask02(void *argument)

{

/* USER CODE BEGIN StartTask02 */

/* Infinite loop */

for(;;)

{

switch(vol_cur)

{

case 1:

M74HC595_ReadDataTest(value_als,1);

break;

case 2:

M74HC595_ReadDataTest(value_tem,0);

break;

case 3:

M74HC595_ReadDataTest(value_hum,0);

break;

}

}

/* USER CODE END StartTask02 */

}

M4 部分調試、執行

程序編譯沒問題以後,點擊「debug」下載調試,會將生成的 elf 文件下載到開發板/lib/fireware/目錄下

進入內核終端下,查看對應目錄下文件,如下所示

在/usr/local/projects/ 目錄下,可以看到含有所創工程名的目錄

進入目錄下,其包含文件如圖所示

此時執行 ./fw_cortex_m4.sh start便可以啟動 M4 程序。

源碼分析

環境監測模塊和智能檢測模塊

啟動 m4 程序後會生成一個串口設備,m4 程序採集溫濕度、環境光照信息。將採集到的數據寫入到串口,a7 通過實時讀取串口的數據,對數據進行解析、計算獲得溫濕度和光照的數值,再使用信號的方式傳給主線程,實時顯示到 UI 界面上,實現環境的溫濕度和光照的監測。

使用 sqlite3 資料庫進行存儲用戶設置的溫度開啟風扇的閾值、照明等級。用戶每次改變閾值後都會存儲到資料庫里,當關閉程序後,下次啟動會從資料庫讀取上次保存的數值,不用每次都重新設置閾值,更加人性化。

如果開啟了智能檢測,每次採集到數據後會跟用戶設置的閾值進行比較,如果採集的溫度大於設置的溫度閾值,就會自動開啟風扇;如果設置一級照明為 50lux,二級照明為 30lux,三級照明為 1lux,當光照強度小於 50lux 會開啟一個 led 燈,小於 30lux 會開啟 2 個 led 燈,當光照強度小於 1lux 時,3 個led 燈會全部開啟。從而實現智能檢測的功能

設備控制模塊

只需要向相應的設備文件寫入數據就能實現對 LED 燈、風扇、蜂鳴器的控制。在 QT 程序中,只需點擊按鍵後觸發 clicked 信號,進入相應的槽函數,向串口設備文件寫入數據,M4 程序對數據進行採集,根據數據的不同,通過實現對設備的控制,核心代碼如下

void MainWindow::led1_on_btnSlot()

{

QString ctrl ="{\"led1\":1}";

thread_collentdata.ctrlEnv = false;

write(fd, ctrl.toUtf8(), ctrl.length());

qDebug()<<"write fd:"<<fd<<" success"<<" data = "<<ctrl;

ui->pushButton->setEnabled(true);

}

設備樹編譯

使用 M4 程序進行數據採集的時候,因為 A7 和 M4 有衝突,所以需要重新編譯設備樹文件。詳細參考【FS-MP1A 開發教程】中 50.5 linux 源碼編譯章節

1. 進入在鏡像中拿到的 linux 源碼下的 dts 路徑下:

linux@ubuntu:~cd linux/fsmp1a-linux-5.4.31/arch/arm/boot/dts

2. 新建設備樹文件

linux@ubuntu:~/linux/fsmp1a-linux-5.4.31/arch/arm/boot/dts$ cp stm32mp157a-fsmp1aextended-rgb070.dts stm32mp157a-fsmp1a-extended-noi2c1-rgb070.dts

3. 修改複製的文件,將 i2c1 節點關閉

4. 修改 Makefile

linux@ubuntu:~/linux/fsmp1a-linux-5.4.31/arch/arm/boot/dts$ vi Makefile

加入以下內容。

5.進入內核頂層目錄編譯

linux@ubuntu:~/linux/fsmp1a-linux-5.4.31$ make dtbs

6. 拷貝編譯好的設備樹文件到開發板

linux@ubuntu:~/linux/fsmp1a-linux-5.4.31$scparch/arm/boot/dts/stm32mp157a-fsmp1a

extended-noi2c1-rgb070.dtb root@192.168.10.130:/boot

7. 修改默認啟動選項

root@fsmp1a:~# vi /boot/mmc1_extlinux/stm32mp157a-fsmp1a-extended_extlinux.conf

8. 重啟開發板

選擇後,即可載入設備樹文件。

源碼路徑【8_環境檢測\實驗源碼\8_EnvironmentalTest】

注意事項

1.在開發板運行時,需要導入中文字庫,否則會因為識別不了中文。將【8_環境檢測\工具軟體\wqy-zenhei-0.9.47-nightlybuild.tar.gz 或wqy-zenhei-0.8.38-1.tar.gz】複製到 ubuntu 下。並使用 scp 命令將文件拷貝到開發板的 usr/share/fonts 目錄下,使用 tar 命令解壓後即可。

linux@ubuntu:~$ scp wqy-zenhei-0.8.38-1.tar.gz

root@192.168.10.128:/usr/share/fonts/

2.如果使用 mipi 五寸屏運行此項目,需要進行屏幕旋轉以適應屏幕,具體

步驟如下:

在/etc/profile.d/qt-eglfs.sh 添加環境變量如下

下面變量的 event0 設備需要填實際的觸控螢幕設備

這裡即填 event0

export QT_QPA_EGLFS_ROTATION=90

export QT_QPA_EGLFS_NO_LIBINPUT=1

export

QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/event0:rotate=90

關鍵字: