快捷搜索:  汽车  科技

visualstudio怎么连接qt(Studio创建一个嵌入式应用)

visualstudio怎么连接qt(Studio创建一个嵌入式应用)Project Properties > WSL Post-Build Event > Command Line要在为了在每次构建结束时自动复制应用程序文件,可以在“ WSL Post-Build Event”属性页中设置如下命令(注意: 这将以明文形式保存设备密码)。1>------ Build started: Project: QuickMirror Configuration: Debug_RPi x64 ------ 1>rcc qml.qrc 1>Invoking 'mkdir -p $(dirname qml.qrc); mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/main.qml); mkdir -p $(dirname /mnt/c/Users/user

在上文中(如何用Visual Studio创建一个嵌入式应用?Qt框架有妙招!(一)),我们展示了如何在Visual Studio中针对Windows和嵌入式Linux创建多平台Qt Quick应用程序项目。现在,我们将演示如何在嵌入式设备上运行该应用程序。然后,我们将继续开发该项目,实现预定的完整嵌入式应用程序。最后,我们将使用VS调试器对应用的C 和QML代码进行远程调试。

在嵌入式设备上运行

我们已经演示了如何交叉编译在Visual Studio中创建的“hello world” Qt Quick应用程序。现在,我们将看到如何在树莓派上运行该应用程序。由于我们将以全屏模式运行,因此必须首先在应用程序窗口中添加一些内容。

main.qml Window { visible: true title: qsTr("Hello World") Text { id: clock font.pointSize: 72 Timer { interval: 1000; running: true; repeat: true onTriggered: clock.text = (new Date).toLocaleTimeString(Qt.locale("de_DE") "hh:mm:ss"); } } }

Qt Quick "Hello World"

和以前一样,选择Linux项目配置,然后按F7键开始交叉编译。

Visual Studio构建输出

1>------ Build started: Project: QuickMirror Configuration: Debug_RPi x64 ------ 1>rcc qml.qrc 1>Invoking 'mkdir -p $(dirname qml.qrc); mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/main.qml); mkdir -p $(dirname /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Debug_RPi/rcc/qrc_qml.cpp); (/home/user/raspi/qt5/bin/rcc /mnt/c/Users/user/Source/Repos/QuickMirror/qml.qrc --name qml -o /mnt/c/Users/user/Source/Repos/QuickMirror/obj/x64/Debug_RPi/rcc/qrc_qml.cpp)' working directory: '/mnt/c/Users/user/Source/Repos/QuickMirror' 1>Starting remote build 1>Compiling sources: 1>qrc_qml.cpp 1>Linking objects 1>QuickMirror.vcxproj -> C:\Users\user\Source\Repos\QuickMirror\bin\x64\Debug_RPi\QuickMirror.out ========== Build: 1 succeeded 0 failed 0 up-to-date 0 skipped ==========

VS中交叉编译Qt项目

现在我们把应用程序的二进制文件上传到树莓派。构建输出窗口显示了生成的二进制文件的位置(倒数第二行)。

Windows命令提示

C:\Users\user> scp C:\Users\user\Source\Repos\QuickMirror\bin\x64\Debug_RPi\QuickMirror.out pi@192.168.1.98:/home/pi/ pi@192.168.1.98's password: QuickMirror.out 100% 465KB 1.6MB/s 00:00 C:\Users\user>

将应用程序二进制文件上传到目标设备

要在为了在每次构建结束时自动复制应用程序文件,可以在“ WSL Post-Build Event”属性页中设置如下命令(注意: 这将以明文形式保存设备密码)

Project Properties > WSL Post-Build Event > Command Line

curl --insecure --user pi:<password> -T /mnt/$(TargetPath.Replace('\' '/').Replace(':' '').ToLower()) scp://<device-addr>/home/pi/$(TargetFileName)

在每次构建结束时将二进制文件复制到设备端

在启动Qt Quick应用程序之前,我们需要设置一些必需的环境变量:

  • LD_LIBRARY_PATH: Qt二进制文件安装的路径。
  • QT_QPA_PLATFORM:QPA平台插件。
  • QT_QPA_PLATFORM_PLUGIN_PATH:QPA平台插件安装的路径。
  • QT_QPA_EGLFS_PHYSICAL_WIDTH、QT_QPA_EGLFS_PHYSICAL_HEIGHT:物理屏幕的宽度和高度,以毫米为单位。
  • qml2_IMPORT_PATH:安装的QML模块的路径。

树莓派命令外壳

pi@raspberry-pi:~$ export LD_LIBRARY_PATH="/usr/local/qt5pi/lib" pi@raspberry-pi:~$ export QT_QPA_PLATFORM="eglfs" pi@raspberry-pi:~$ export QT_QPA_PLATFORM_PLUGIN_PATH="/usr/local/qt5pi/plugins/platforms" pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_WIDTH="326" pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_HEIGHT="520" pi@raspberry-pi:~$ export QML2_IMPORT_PATH="/usr/local/qt5pi/qml" pi@raspberry-pi:~$ ./QuickMirror.out

树莓派显示器

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(1)

在树莓派上运行“ Hello World”应用程序

应用程序开发过程

应用程序的要求包括显示以下信息:

  • 当前时间
  • 当前日期
  • 重要公共纪念日
  • 天气预报
  • 下一班公共交通工具
  • 新闻

我们将把每个功能项封装为独立的QML类型。为此,我们必须首先将QML模块定义(qmldir)文件添加到项目中:

  • 选择"Project > Add New Item.. > Qt > QML Module Definition"。
  • 在位置字段中,设置包含QML文件的文件夹路径。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(2)

向项目添加新的QML模块定义

按下“Add”后,qmldir 文件将在项目树中变为可用。我们将使用此文件来映射每种QML类型到其对应的源文件。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(3)

将QML类型映射到源文件

将新的QML源文件添加到项目中:

  • 选择“Project > Add New Item... > Qt > QML File"”。
  • 将位置设置为qmldir同级目录。
  • 设置QML文件名。
  • 按“Add”。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(4)

向项目添加新的QML文件

我们将首先添加用于显示当前时间、当前日期和重要公共纪念日的QML类型。该Clock类型将显示当前时间,每秒刷新一次。

Text { font.family: FontFamily_Clock font.styleName: FontStyle_Clock font.pointSize: 144 color: "white" renderType: Text.NativeRendering antialiasing: false function refresh() { text = (new Date).toLocaleTimeString(Qt.locale("de_DE") "hh:mm"); } Component.onCompleted : refresh(); Timer { interval: 1000; running: true; repeat: true onTriggered: parent.refresh(); } }

Clock QML类型的定义

该Calendar类型将显示当前日期,并在不同城市名之间循环。

Text { renderType: Text.NativeRendering id: calendar color: "white" font.family: FontFamily_Bold font.styleName: FontStyle_Bold font.pointSize: 72 property var locales: ["en_US" "de_DE" "pt_PT"] property var localeIdx: 0 function capitalize(s) { return s.replace(/(^|-)./g function(c) { return c.toUpperCase(); }); } function setNextLocale() { localeIdx = (localeIdx 1) % locales.length; } function getCurrentText() { var date = new Date; var locale = Qt.locale(locales[localeIdx]); var calendarText = capitalize(date.toLocaleDateString(locale "dddd dd")); var monthShort = date.toLocaleDateString(locale "MMM"); var monthLong = date.toLocaleDateString(locale "MMMM"); if (monthLong.length <= 5) { calendarText = capitalize(monthLong); } else { calendarText = capitalize(monthShort); if (!monthShort.endsWith(".")) calendarText = "."; } calendarText = date.toLocaleDateString(locale " yyyy"); return calendarText; } Component.onCompleted: { text = getCurrentText(); } Timer { interval: 15000; running: true; repeat: true onTriggered: { setNextLocale(); text = getCurrentText(); } } Behavior on text { SequentialAnimation { NumberAnimation { target: calendar; property: "opacity"; to: 0.0; duration: 1000 } PropertyAction { target: calendar; property: "text" } NumberAnimation { target: calendar; property: "opacity"; to: 1.0; duration: 500 } } } }

Calendar QML类型的定义

除了日期/时间,我们的应用程序还将依靠Web API来检索信息。我们将在一个单独的进程中运行curl以连接到Web API。进程创建由名为Process的C 类处理。然后,QML类型ApiCall将通过一个Process对象传送必要的参数运行curl并收集其输出。

Item { property var url: "" property var path: [] property var query: [] signal response(var response) signal error(var error) Process { id: curl property var path: Q_OS_WIN ? "C:\\Windows\\System32\\curl.exe" : "/usr/bin/curl" property var request: "" command: path " -s \"" request "\"" } function sendRequest() { curl.request = url; if (path.length > 0) curl.request = "/" path.join("/"); if (query.length > 0) curl.request = "?" query.join("&"); curl.start(); } connections { target: curl onExit /*(int exitCode QByteArray processOutput)*/ : { if (exitCode != 0) { console.log("ApiCall: exit " exitCode); console.log("==== ApiCall: request: " curl.request); return error("exit " exitCode); } try { return response(JSON.parse(processOutput)); } catch (err) { console.log("ApiCall: error: " err.toString()); console.log("==== ApiCall: request: " curl.request); console.log("==== ApiCall: response: " processOutput); return error(err); } } } }

ApiCall QML类型的定义

创建Process的C 类

  • 选择"Project > Add Qt Class > Qt Class"
  • 将类名设置为Process
  • 按“Add”

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(5)

向项目添加新的Qt C 类

Process.h

public: Q_INVOKABLE void start(); void setCommand(const QString& cmd); QString command() const; signals: void commandChanged(); void exit(int exitCode QByteArray processOutput); protected: void onFinished(int exitCode QProcess::ExitStatus status); void onErrorOccurred(QProcess::ProcessError error); private: QString m_command; }; Process.cpp Process(QObject* parent) : QProcess(parent) { connect( this QOverload<int QProcess::ExitStatus>::of(&QProcess::finished) this &Process::onFinished); connect( this &QProcess::errorOccurred this &Process::onErrorOccurred); } Process::~Process() { } void Process::setCommand(const QString& cmd) { if (cmd != m_command) { m_command = cmd; emit commandChanged(); } } QString Process::command() const { return m_command; } void Process::start() { if (State() == ProcessState::NotRunning) QProcess::start(m_command); else qInfo() << "==== QProcess: ERROR already running:" << m_command; } void Process::onFinished(int exitCode QProcess::ExitStatus status) { emit exit((status == ExitStatus::NormalExit) ? exitCode : -1 readAll()); } void Process::onErrorOccurred(QProcess::ProcessError error) { qInfo() << "==== QProcess: ERROR " << error; } main.cpp int main(int argc char* argv[]) { qmlRegisterType<Process>("Process" 1 0 "Process"); ...

Process类的定义

OnThisDay QML类型将使用ApiCall的实例来获取重要公共纪念日列表,并每隔几秒钟循环一次。

QuickMirror.OnThisDay.qml

Item { id: onThisDay clip: true property int viewportHeight property var events: [] property var births: [] property var deaths: [] property int idxEventType: -1 ApiCall { id: onThisDayApi property int month: 0 property int day: 0 property string eventType: "" url: "https://byabbe.se"; path: ["on-this-day" month day eventType ".json" ] onResponse: { if ("events" in response) { events = shuffle(response.events); eventType = "births"; sendRequest(); } else if ("births" in response) { births = shuffle(response.births); for (var i in births) births[i].year = "*" births[i].year; eventType = "deaths"; sendRequest(); } else if ("deaths" in response) { deaths = shuffle(response.deaths); for (var i in deaths) deaths[i].year = "<sup>†</sup>" deaths[i].year; next(); } } } function init() { events = []; births = []; deaths = []; idxEventType = -1; var today = new Date; onThisDayApi.month = today.getMonth() 1; onThisDayApi.day = today.getDate(); onThisDayApi.eventType = "events"; onThisDayApi.sendRequest(); } function next() { if (events.length births.length deaths.length == 0) return; var today = new Date; if (onThisDayApi.month != today.getMonth() 1 || onThisDayApi.day != today.getDate()) return init(); onThisDayText.color = "white"; idxEventType = (idxEventType 1) % 3; var event; switch (idxEventType) { case 0: if (events.length == 0) return next(); event = events.shift(); events = shuffle(events); events.push(event); break; case 1: if (births.length == 0) return next(); event = births.shift(); births = shuffle(births); births.push(event); break; case 2: if (deaths.length == 0) return next(); event = deaths.shift(); deaths = shuffle(deaths); deaths.push(event); break; } onThisDayText.text = event.year " – " event.description; showText.start(); } Component.onCompleted: { init(); } Timer { id: timerRetry interval: 10000; running: true; repeat: true onTriggered: { if (events.length births.length deaths.length == 0) init(); } } SequentialAnimation { id: showText PropertyAction { target: onThisDayText; property: "y"; value: 25 } NumberAnimation { target: onThisDayText; property: "opacity"; to: 1.0; duration: 500 } PauseAnimation { duration: 3000 } NumberAnimation { target: onThisDayText property: "y" to: Math.min(-(25 onThisDayText.contentHeight) viewportHeight 25) duration: Math.max(0 (Math.abs(to - from) * 1000) / 25) } PauseAnimation { duration: 3000 } NumberAnimation { target: onThisDayText; property: "opacity"; to: 0.0; duration: 1000 } onFinished: { onThisDay.next(); } } Text { renderType: Text.NativeRendering id: onThisDayText wrapMode: Text.WordWrap font.family: FontFamily_Normal font.styleName: FontStyle_Normal font.pointSize: 40 textFormat: Text.RichText color: "white" y: 25 anchors.left: parent.left width: parent.width height: contentHeight opacity: 0 } Rectangle { id: top anchors.top: parent.top anchors.left: parent.left width: parent.width height: 10 gradient: Gradient { orientation: Gradient.Vertical GradientStop { position: 0.0; color: "black" } GradientStop { position: 0.5; color: "transparent" } } } Rectangle { id: bottomFade anchors.top: parent.top anchors.topMargin: viewportHeight anchors.left: parent.left width: parent.width height: 0.1 * viewportHeight gradient: Gradient { orientation: Gradient.Vertical GradientStop { position: 0.0; color: "transparent" } GradientStop { position: 0.5; color: "black" } } } Rectangle { anchors.top: bottomFade.bottom anchors.bottom: parent.bottom anchors.left: parent.left width: parent.width color: "black" } }

现在,我们已经定义了一些应用程序的QML类型,我们将在主QML文件上组织它们。

main.qml

import "QuickMirrorTypes" Window { visible: true title: qsTr("Quick Mirror") Flickable { anchors.fill: parent contentWidth: mirror.width contentHeight: mirror.height Rectangle { id: mirror width: 1080 height: 1920 color: "black" Clock { id: clock anchors.top: mirror.top anchors.left: mirror.left } Calendar { id: calendar anchors.top: clock.bottom anchors.topMargin: -20 anchors.left: mirror.left } Rectangle { anchors.top: calendar.bottom anchors.topMargin: -5 anchors.left: mirror.left width: 800 height: 2 color: "white" } OnThisDay { id: onThisDay anchors.top: calendar.bottom anchors.left: mirror.left anchors.leftMargin: 10 anchors.bottom: mirror.bottom width: 780 viewportHeight: 260 } } } }

最后,所有QML文件和qmldir文件必须添加到应用程序的资源文件中:

  • 双击项目树中的QRC文件
  • 在“Qt Resource Editor”窗口中,按“Add > Add Files”
  • 选择所有QML文件和qmldir文件
  • 在Qt Resource Editor中按“Save”

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(6)

QML文件和qmldir已添加到资源文件

构建和部署后,我们将能启动应用程序并查看显示的信息。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(7)

在树莓派上运行的应用程序

在Visual Studio中进行调试

VS支持通过gdb调试运行在WSL上的应用程序。要在树莓派上运行过程中调试,我们将使用gdbserver启动应用程序,然后配置gdb连接到设备并启动远程调试会话。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(8)

使用gdb 和gdbserver从Visual Studio进行远程调试

为此, WSL中安装的gdb组件必须支持目标设备体系架构。一个简单的方法是安装gdb-multiarch。为了确保VS使用正确的调试器,我们将创建一个符号链接,把gdb映射到gdb-multiarch。我们将创建从gdbgdb-multiarch的符号链接。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(9)

在Visual Studio中设置远程调试会话,必须向gdb传递两个附加命令。在“GDB Debugger”属性页面中进行配置。

Project Properties > Debugging > Additional Debugger Commands

target extended-remote 192.168.1.98:2345
set remote exec-file /home/pi/QuickMirror.out

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(10)

在开始远程调试会话之前,我们必须设置所需的环境变量并在设备上启动gdbserver

Raspberry Pi Command Shell

pi@raspberry-pi:~$ export LD_LIBRARY_PATH="/usr/local/qt5pi/lib" pi@raspberry-pi:~$ export QT_QPA_PLATFORM="eglfs" pi@raspberry-pi:~$ export QT_QPA_PLATFORM_PLUGIN_PATH="/usr/local/qt5pi/plugins/platforms" pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_WIDTH="326" pi@raspberry-pi:~$ export QT_QPA_EGLFS_PHYSICAL_HEIGHT="520" pi@raspberry-pi:~$ export QML2_IMPORT_PATH="/usr/local/qt5pi/qml" pi@raspberry-pi:~$ gdbserver --once --multi :2345 Listening on port 2345

按F5将启动远程调试会话。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(11)

远程QML调试

在嵌入式设备上运行应用程序时,也可以调试QML代码。

  • 在Qt设置中启用QML调试:Project Properties > Qt Project Settings

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(12)

  • 设置应用程序启动参数,启动QML调试会话
  • 设置启动QML调试会话的程序参数

Project Properties > Debugging > Program Arguments

-qmljsdebugger=port:8989 host:192.168.1.98 block

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(13)

总结

我们展示了如何使用Qt VS Tools扩展在Visual Studio中创建基于Qt Quick技术的多平台嵌入式应用程序。包括:

  • 从头开始创建Qt Quick项目
  • 用QML编写应用程序代码
  • 交叉编译应用程序
  • 在嵌入式设备上部署和运行
  • 在Visual Studio中对C 和QML代码进行远程调试

该项目,包括所有源代码,可在https://github.com/micosta/quickmirror获得。

visualstudio怎么连接qt(Studio创建一个嵌入式应用)(14)

运行在嵌入式设备上的应用程序

本文转载自Qt中国

猜您喜欢: