Direct Memory AccessはCPUを介さずにメモリー間のデータを転送する方法です。メモリーにデータを蓄積している間でもルーチンプログラムを動かし続けることができるので、オーディオストリーミングやディスプレイ出力など、組み込み機器で滞りなくデータを送受信し続けたい場合には必須のテクニックです。PIC32シリーズのマイコンではチャンネル単位で処理を確保し、PIC32MXにおいては最大4つの転送(4チャネル)を同時に行えます。

DMAを使用するに当たって必要なメモリーに関連する情報は「転送元の物理アドレス」「転送元のサイズ」「転送先の物理アドレス」「転送先のサイズ」「転送するサイズの上限」「1回につき転送するサイズ(4バイト単位、最大256バイト)」で、チャネル1に割り当てる場合はそれぞれ「DCH1SSA」「DCH1SSIZ」「DCH1DSA」「DCH1DSIZ」「DCH1CSIZ」となります。周辺モジュールからだけではなく、SRAM同士での転送にも使用できます。ただし、指定するアドレスは、マクロなどを使って仮想アドレスから変換する必要があります。
#include <sys/kmem.h>

DCH1SSA = KVA_TO_PA((void*)&SPI1BUF); // DMA1転送元物理アドレス
DCH1SSIZ = 4;                         // DMA1転送元サイズ
DCH1DSA = KVA_TO_PA(active_frame);    // DMA1転送先物理アドレス
DCH1DSIZ = PCM_BUFFER_SIZE;           // DMA1転送先サイズ
DCH1CSIZ = 4;                         // 一度に転送するサイズ
転送を実行するタイミングは「CHSIRQ」に割り込みベクターの値を与えることで有効になります。例えば「DCH1ECONbits.CHSIRQ = _TIMER_1_IRQ」ならタイマーによる一定周期ごとに、「DCH1ECONbits.CHSIRQ = _SPI1_RX_IRQ;」なら、SPI1のデータ受信時に転送が実行されます。

DCHxININレジスタのビットを有効にすることで、それらの値に対応したDMA割り込みが発生します。「DCH1INTbits.CHBCIE = 1」であれば指定したサイズ分のデータ転送が完了したときに割り込まれるので、このタイミングで転送先のアドレスを参照すれば、転送済みであるすべてのデータを取得することができます。

ここで紹介するサンプルはSPIモジュールを使った、デジタルオーディオ信号形式のひとつであるPCM(I2Sと違い、データ信号が途切れないのが特徴)の転送方法です。終了割り込みが発生するたびに、転送先のアドレスを差し替えており、これによりダブルバッファ処理を実装しています。
#include <p32xxxx.h>
#include <sys/kmem.h>
#include <sys/attribs.h>
#include <stdio.h>

#pragma config FNOSC = FRCPLL
#pragma config FPLLIDIV = DIV_2 // PLL入力分数 = x1/2
#pragma config FPLLMUL = MUL_20 // PLL逓倍比 = x20
#pragma config FPLLODIV = DIV_2 // PLL出力分数 = x1/2

#pragma config ICESEL = ICS_PGx3
#pragma config DEBUG  = ON
#pragma config FWDTEN = OFF

#define PCM_BUFFER_SIZE 32

char frame_buffer0[PCM_BUFFER_SIZE];
char frame_buffer1[PCM_BUFFER_SIZE];
char *active_frame = frame_buffer0;

void __ISR(_DMA1_VECTOR, IPL5AUTO) OnDma1(void)
{     
    if(DCH1INTbits.CHBCIE == 1){
        active_frame = (active_frame == frame_buffer0) ? frame_buffer1 : frame_buffer0;
        DCH1DSA = KVA_TO_PA(active_frame);

        DCH1CONbits.CHEN = 1;   // DMA1を有効にする
        
        DCH1INTbits.CHBCIF = 0; // ブロック終了割り込みフラグのクリア
    }
    IFS1bits.DMA1IF = 0;        // DMA1割り込みフラグのクリア
}

void InitSpi()
{
    SPI2CONbits.ON = 0;
  
    SDI2Rbits.SDI2R = 0b0011;  // B13をSDI2に割り当て
    SS2Rbits.SS2R = 0b0001;    // B14をSS2に割り当て
    
    SPI2CONbits.MSTEN = 0;     // SPI2スレーブモード
    SPI2CONbits.SPIFE = 1;     // フレーム同期モード
    SPI2CONbits.MODE32 = (PCM_BUFFER_SIZE == 32) ? 1 : 0; // 32bitモード
    SPI2CON2bits.IGNROV = 1;   // オーバーフローエラーを無視する

    SPI2CON2bits.AUDEN = 1;     // オーディオプロトコルを使用
    SPI2CON2bits.AUDMOD = 0b11; // PCM/DSPモード

    SPI2CONbits.ON = 1;         // SPI2を有効にする
}

void InitDma()
{
    IEC1bits.DMA1IE = 0;   // 設定のためDMA1を一時停止させる
   
    DCH1CON = 0;
    DCH1ECON = 0;
    DCH1INT = 0;

    DCH1CONbits.CHPRI = 3;              // DMAチャネルの優先度(3)
    DCH1ECONbits.CHSIRQ = _SPI2_RX_IRQ; // DMAを開始する割り込み番号
    DCH1ECONbits.SIRQEN = 1;            // 割り込みによる転送を有効にする
    DCH1INTbits.CHBCIE = 1;             // DMA割り込みを有効にする
    
    DCH1CONbits.CHEN = 1;               // DMA1を使用する
    
    DCH1SSA = KVA_TO_PA((void*)&SPI2BUF);
    DCH1SSIZ = 4;
    DCH1DSA = KVA_TO_PA(active_frame);
    DCH1DSIZ = PCM_BUFFER_SIZE;
    DCH1CSIZ = 4;
    
    IFS1bits.DMA1IF = 0;   // 割り込みフラグのクリア
    IPC10bits.DMA1IP = 5;  // 割り込み優先度
    IPC10bits.DMA1IS = 3;  // 副割り込み優先度
    IEC1bits.DMA1IE = 1;   // DMA1割り込みを有効にする
   
    DMACONbits.ON = 1;     // DMAモジュールを有効にする
}

int main(void)
{
    ANSELB = 0x00; 
    TRISB = 0xFFFFFFFF;
    
    InitSpi();
    InitDma();
      
    INTCONbits.MVEC = 1;
       
    __builtin_enable_interrupts();
     
    while(1) ;
}
割り込みフラグ解除はDMA割り込みだけでなく、DMAブロック終了割り込みなどの、関連する割り込み情報のフラグもリセットしないと次の割り込みが来ないので気をつけましょう。
 | 2018年12月9日

紫外線で硬化するドライフィルムを使った電子基板の生産が工業的に行われています。このドライフィルムは個人輸入という形式にはなりますが、Amazonで500円足らずで購入することができます。受託製造が個人でもできる昨今では、家庭での基盤自作は趣味の色合いが濃くなってしまいましたが、Makerとしての興味から、どのような感じで作れるのかを試してみました。なお、ベストショットを集めたので、写真の時系列や基板が一致していませんのであしからず。

届いた商品を開封してみると、青いサランラップのようなものが黒い保護ビニールにくるまれて入っていました。
まずは基板設計図をインクジェットプリンターで印刷します。サンハヤトの露光基板専用紙(PF-3R-A4)は少し高価なので、市販のインクジェットプリンター用OHPシートを購入しました。
光をある程度通す必要があるという製品の性質上、やや薄めに出力されるので、パターンは2つ印刷して、それを重ねて遮光性を高める必要があります。また、トナー転写とはネガ・ポジが逆となっています。
生基盤に適当な大きさに切ったドライフィルムを重ねます。適当な大きさでフィルムを切り取ったら、フィルムの裏側(内側にくるまっている方)の接着剤保護フィルムを剥がします。手で剥がしにくい場合はセロテープをひっつけてみましょう。
生基板にフィルムを均等に貼り付けたら、それを当て紙(コピー用普通紙で可)でくるみ、熱を加えて吸着させます。トナー転写とは違い、アイロンを真上から軽く当てるだけでひっつきます。変に力を加えてしまうと、フィルムがずれて歪むので気をつけましょう。
フィルムを接着した基板に回路図を印刷したフィルムを重ねて、露光します。ここで重要なのが回路図と基板をとにかく密着させることです。少しでも隙間が浮いていると、そこから紫外線が入り込んで失敗するので、透明なプラスチック(しなりにくいアクリルやガラス板がベスト)で挟み、強力なクリップやクランプでしっかりと固定させましょう。

私が持っているドライフィルムと紫外線照射器(UVライト)の組み合わせでは、4分ほどでパターンの露光ができました。
遮光されたことで硬化しなかった部分をアルカリ溶液で溶かし去ります。メーカーのサイトでは水に溶かした重曹を使うとあったのですが、これでは全くといっていいほど溶けなかったので、私は100円均一で売っている過炭酸ナトリウムを用いました。ただし、これはフローリングのコーティングが禿げるほどアルカリ性が強く、また漂白剤特有の刺激臭もあるので、手袋を着用するなど、取り扱いには十分注意してください。

表側についていた硬化剤保護フィルムを剥がし、溶液に浸しながらナイロンブラシでブラッシングしていきます。漬け置く必要はありません。
銅の部分がしっかりと露出しているかが鍵です。もし、硬化剤にかすれが出てしまったようであれば、その部分を油性ペンで補完しましょう。
ここから先はトナー転写方式と同様、エッチング溶液に浸して不要な銅を除去します。残った硬化剤は、アルカリ溶液に10分くらい漬けて軟化させてから金ブラシなどで削り取りましょう。
個人的な結果では、トナー転写よりも荒さが目立たず、より細い線も再現することができました。
 | 2018年11月17日

Androidアプリは基本的にJava言語やそれをもとにしたKotlin言語で開発するのが前提で、Andoid NDKよりもAndroid SDKのほうが簡単に実装できることが多かったりします。Android版QtはNDKによるC++言語が用いられていますが、もちろんJavaコードとのやりとりが可能です。

C++とJavaを相互に連携するにはJNI(Java Native Interface)を使用します。JNIには特有の決まり事や癖が多いので、細かいことはAndroid NDKプログラミングの専門書に任せるとして、ここではQtプログラミングという観点からテクニックをご紹介します。
Qtには「androidextras」というAndroid拡張ライブラリが本体とは別に提供されており、"pro"ファイルに宣言を追加することで、JNIのラッパークラスを使用できるようになります。

android{ QT += androidextras }

では、JniTest.javaというJavaクラスを例に、Qtと連携する実例を記載していきます。
package com.tnksoft.helloqt;

import java.io.*;
import java.net.*;

import android.util.Log;

public class JniTest
{
    private native void error(long handle, String errmsg);
    private native void complete(long handle, byte[] result);

    private long _handle;

    public JniTest(long handle)
    {
        _handle = handle;
    }

    public static boolean Initialize(int v)
    {
        Log.d("QTJ", "initialized.");
        return true;
    }

    public void begin()
    {
        Log.d("QTJ", "begin was called.");
    }

    public void download(String path)
    {
        byte[] result = new byte[256];
        complete(_handle, null);
    }
}
JNIインターフェイス「JNIEnv」を取得するには「QAndroidJniEnvironment」を使用します。jclassを扱うには「QAndroidJniObject」を作成し、QAndroidJniEnvironmentオブジェクトを指定した上で、クラスやメソッドIDを確保します。
// JNI関連のファイルをインクルード
#include <QtAndroidExtras/QAndroidJniObject>
#include <QtAndroidExtras/QAndroidJniEnvironment>

void MainView::MainView()
{
    QAndroidJniEnvironment env;

    // Javaクラス名の定義
    const char* JniTestClassName = "com/tnksoft/helloqt/JniTest";

    // JniTestクラスのオブジェクトを作成する。第二引数以降はコンストラクタに渡すためのもので、
    // 引数にはこのクラスのポインタを64bit整数値に変換したものを指定している。
    JniTestClass = new QAndroidJniObject(JniTestClassName, "(J)V", reinterpret_cast<jlong>(this));

    // クラスに登録されているメソッドのIDを取得して、メンバ変数として記録しておく。
    testClass = env->GetObjectClass(JniTestClass->object());
    _begin = env->GetMethodID(testClass, "begin", "()V");
    _download = env->GetMethodID(testClass, "download", "(Ljava/lang/String;)V");

    // Javaから呼び出されたときに実行するC言語関数を登録。
    // "メソッド名", "シグニチャ", "C言語関数ポインタ"の配列
    JNINativeMethod pmethods[] {
        {"error", "(JLjava/lang/String;)V", reinterpret_cast<void *>(MainView::errorCallback)},
        {"complete", "(J[B)V", reinterpret_cast<void *>(MainView::completeCallback)},
    };

    // Qtドキュメントには詳細について言及されていないが、
    // これはJNI関数「RegisterNatives」のラッパーである。
    env->RegisterNatives(testClass, pmethods, 2);

    // 静的メソッドを呼び出す。引数は見本のためのダミー数値
    jboolean res = QAndroidJniObject::callStaticMethod<jboolean>(JniTestClassName, "initialize", "(I)Z", 4);
}

void MainView::onCallJni()
{
    QAndroidJniEnvironment env;

    // 戻り値がない(void)のJavaメソッドを呼び出す
    // 例えば、戻り値がintの関数を呼び出したいのなら、callIntMethodを使用する。
    env->CallVoidMethod(JniTestClass->object(), _begin);
}
jcharはJavaのchar型であり、UTF16の2バイト文字が格納されるため、QStringに変換するには「QString::fromUtf16」を使用します。
void MainView::errorCallback(JNIEnv *env, jobject thiz, jlong handle, jstring errmsg)
{
    Q_UNUSED(thiz);
    MainView *nw = reinterpret_cast<MainView*>(handle);
    const jchar* c = env->GetStringChars(errmsg, nullptr);
    QString msg = QString::fromUtf16(c);

    // QMLにシグナルを送信
    emit nw->error(msg);
}
逆にQStringからJNIのjstring型に変換するには「QAndroidJniObject::fromString」を使用します。
QAndroidJniObject jmsg = QAndroidJniObject::fromString("Hello from Qt C++.");
env->CallVoidMethod(JniTestClass->object(), _download, jmsg.object<jstring>());
jbytearrayなどのJNI配列型はVMでの参照先であり、C言語のポインタではないので、適時ポインタとして取得する必要があります。これは、JNIのバイト配列をQt Quickアプリでよく使用されるバイト配列管理クラス「QByteArray」に変換する例です。
void MainView::completeCallback(JNIEnv *env, jobject thiz, jlong handle, jbyteArray res)
{
    Q_UNUSED(thiz);
    MainView *mw = reinterpret_cast<MainView*>(handle);
    // nullでなければバイト配列に変換
    if(res){
        jboolean cpy;
        jsize sz = env->GetArrayLength(res);
        jbyte *buf = env->GetByteArrayElements(res, &cpy);
        QByteArray *ba = new QByteArray(reinterpret_cast<char*>(buf), sz);
        env->ReleaseByteArrayElements(res, buf, 0);

        // サンプルではこれ以上必要ないので削除している
        delete ba;
    }

    emit mw->complete();
}
こちらからダウンロードできるQtプロジェクトでは、QtActivityを拡張したクラスで表示されたQMLボタンを押すと、C++とJava間でのデータのやりとりが実行されます。
Qtプロジェクトファイルのダウンロード
 | 2018年11月14日

Android向けQt QuickではQtActivityというAndroid標準のActivityを介してUIが描画されています。つまり、このQtActivityを自前のActivityに差し替えることで、より柔軟な画面設計ができるようになります。

Qt Creatorの「プロジェクト→Build & Run」より「Androidテンプレートの作成」ボタンを押すと、gradleファイルをはじめとしたAndroid SDKによるビルド用ファイルがプロジェクトフォルダ以下に作成されます。この記事ではパッケージ名は「com.tnksoft.helloqt」となっていますので、実際のプログラミングではパッケージ名やプロジェクト名は適時置き換えてください。
作成されたファイルはプロジェクトの「Other files」にあるので、ここから「AndroidManifest.xml」ファイルを開きます。
Qt Creatorで編集する場合は、エディタ上部の「一般」から「XMLソース」に切り替え、XMLのテキストコードを表示させ、「activity」タグにある「android:name」の値を「com.tnksoft.helloqt.HelloActivity」のように、独自Activityのクラス名に書き換えます。
Activityクラスのコードを作成します。Android Studioでの編集と同様、「android/src/com/tnksoft/helloqt」フォルダーを作成し、[独自Activityクラス名].javaファイルを作成し、それをプロジェクトに加えます。
もし、今後多くのJavaファイルを作成するようであれば、Qtの「pro」ファイルにJavaソースファイルのあるフォルダーをあらかじめ指定しておけば、ファイルを新しく作るたびにいちいち登録していく必要がなくなります。

android{ ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android }

ActivityにはQtActivityを継承したクラスを使用します。あとはこのファイルに独自拡張するためのプログラムコードを記入していけば、カスタマイズされたウィンドウでアプリを実行できるようになります。
package com.tnksoft.helloqt;

import android.util.Log;

public class HelloActivity extends org.qtproject.qt5.android.bindings.QtActivity
{
    public HelloActivity()
    {
        Log.d("QTJ", "HelloActivity has created.");
    }
}
 | 2018年11月13日

Windows 10で動作するUnivwersal Windows Platform Appではマイクロソフトの独自拡張プログラム言語「C++/CX」による開発がサポートされています。COMによる純粋なC++でUWPアプリを開発することも不可能ではありませんが、非同期処理やランタイムライブラリーの取り扱といったUWP固有の取り決めをスムーズに取り入れることができるので、Qt WinRTでUWPのネイティブAPIを組み込みたい場合は、C++/CXでコーディングしたほうが効率が良いと思います。

MSVCコンパイラでは「ZW」オプションを加えることでC++/CX拡張のコードも扱えるようになります。よって、QtでC++/CXを使うにはプロジェクトファイルでC++オプションを追加します。
winrt{ QMAKE_CXXFLAGS += -ZW }
C++/CXによるクラス宣言や非同期処理のプログラム方法はUWP・C#プログラマーのためのC++/CX基礎知識にまとめていますので、こちらもご一読ください。

文字列をQtとC++/CXでやり取りしたいときは、utf16形式に変換して文字列クラスを作成・複製します。
String ^cxstr = "Test";
// WinRT文字列オブジェクトでは、Data()でUTF16形式の文字配列を取得できる
QString qstr = QString::fromWCharArray(str->Data());
// 同様にQString::utf16からwchar_tの文字配列を取得してStringオブジェクトを新規作成する
String ^nstr = nullptr;
cxstr = ref new String((const wchar_t*)qstr.utf16());
WinRT C++オブジェクトに組み込んだ普遍的なC++クラスをパブリックな変数や関数として公開することは禁止されているので、C++/CXクラスでQObject派生クラスを取り扱いたいのであればfriendを活用するのがよいでしょう。
// C++(QObject)派生クラスの定義
class Controller : public QObject
{
    Q_OBJECT
private:
    InputDevice ^gidev = nullptr;
public:
    void begin()
    {
        gidev = ref new InputDevice();
        // 自身のC++クラスをfriendより登録
        gidev->gc = this;
        gidev->Start();
    }

    void postHat(quint8 n)
    {
        // ここでQQuickItemなどに入力データを送る
    }
}

// WinRTオブジェクトの定義
ref class InputDevice sealed
{
    friend class Controller;
private:
    HidDevice ^device = nullptr;
    Controller *gc = nullptr;
public:
    void Start()
    {
        // HIDデバイスを検索するプログラム例
        auto selector = HidDevice::GetDeviceSelector(0x1, 0x4);
        task<DeviceInformationCollection^> c =
            create_task(DeviceInformation::FindAllAsync(selector));
            c.then([this](DeviceInformationCollection^ info){
              // 列挙したデバイスから使用したデバイスを取得してイベントを登録するが、ここでは割愛。
        });
    }

    void OnInputReportReceived(HidDevice ^sender, HidInputReportReceivedEventArgs ^args)
    {
        // HIDデバイスから入力を受け取ったら発生するイベント
        gc->postHat(0);
    }
}

 | 2018年11月12日

QtはC++がベースの開発環境であり、また、基本的にプラットフォーム標準のコンパイラ(WindowsならMSVC、Androidならgcc)でアプリを生成するため、ラッパーコードなどを必要とせず、C言語ライブラリーをそのままプログラムに取り入れることができます。

これらはQtプロジェクトファイル(.pro)で定義することになります。「$$」はqmakeマクロを意味しています。この場合であれば、プロジェクトファイルのあるディレクトリの「opencv」フォルダーがインクルードフォルダーに追加されます。
INCLUDEPATH += $$_PRO_FILE_PWD_/opencv/
Cプリプロセッサを定義するには「DEFINES」に文字列を追加します。
DEFINES += PARAM_SINGLE_ONLY
ライブラリーディレクトリを定義するには「LIBS」を記入します。ただし、記述方法はコンパイラに依存するため、プラットフォームごとにパラメータを変更しなくてはいけません。
LIB_DIR = $$_PRO_FILE_PWD_/libav/$${QMAKE_HOST.os}/$${QMAKE_HOST.arch} android{ LIBS += -L"$$LIB_DIR" -lavformat-57 } winrt{ LIBS += "$$LIB_DIR\avformat.lib" }

以上を踏まえて、GPIOコントローラーライブラリ「wiringPi」使ってRaspberry Piで動作するQtアプリでLチカを試してみましょう。新規プロジェクトを作成したら、「LIBS += -lwiringPi」の一文を追加します。
main.qmlファイルにボタンを追加し、ボタンを押したり離したりした際にシグナルを発生させます。
import QtQuick 2.9
import QtQuick.Controls 2.0

Item {
    signal ledOn()
    signal ledOff()
    Button{
        text:"LED"
        onPressed: ledOn()
        onReleased: ledOff()
    }
}
wiringPiのQtでの使い方は、コンソールアプリにおけるC言語プログラミングと全く同じです。
#ifndef MAINVIEW_H
#define MAINVIEW_H

#include <QQuickView>

class MainView : public QQuickView
{
    Q_OBJECT
public:
    MainView();
public slots:
    void onLedOn();
    void onLedOff();
};

#endif
#include "mainview.h"
#include "wiringPi.h"

#include <QQuickItem>

#define GPIO_LED 17

MainView::MainView()
{
    setResizeMode(QQuickView::SizeRootObjectToView);
    setSource(QUrl("qrc:/main.qml"));

    QObject *root = this->rootObject();
    connect(root, SIGNAL(ledOn()), this, SLOT(onLedOn()));
    connect(root, SIGNAL(ledOff()), this, SLOT(onLedOff()));

    wiringPiSetupGpio();
    pinMode(GPIO_LED, OUTPUT);
}

void MainView::onLedOn()
{
    digitalWrite(GPIO_LED, 1);
}

void MainView::onLedOff()
{
    digitalWrite(GPIO_LED, 0);
}
このプログラム例では、Qtアプリに表示されたボタンを押すと、GPIO17がオンに、離すとオフになります。
 | 2018年11月10日

前回ではRaspberry Pi用のQtライブラリのビルドをUbuntuで行いました。今回は、それをもとにアプリのコンパイルとリモートデバッグを行います。

始めにLinux x64版Qt CreatorをUbuntuへインストールします。
wget http://download.qt.io/official_releases/qt/5.11/5.11.2/qt-opensource-linux-x64-5.11.2.run chmod +x qt-opensource-linux-x64-5.11.2.run sudo ./qt-opensource-linux-x64-5.11.2.run
Ubuntuのアプリ一覧から「Qt Creator」を選んで起動します。メニューの「Tools」より「Devices→Devices」を選び、デバイスの追加より、Raspberry Pi(IPアドレスとユーザー名、パスワード)を入力します。
「Kits→Compilers」の「Add」より「C→"/opt/qt5pi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc"」「C++→"/opt/qt5pi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-g++"」をそれぞれ追加します。ABIの項目が「arm-linux-generic-elf-32bit」になっているかも確認してください。
「Kits→Debbugers」の「Add」より"/usr/bin/gdb-multiarch"ファイルを登録します。
「Kits→Qt Versions」の「Add」より"/opt/qt5pi/sysroot/usr/local/qt5pi/bin/qmake"ファイルを登録します。 「Kits→Kits」の「Add」より新規追加し、これまでに登録したデバッガ、コンパイラ、Qtバージョンを指定していきます。
Raspbianにリモートデバッグツールを導入します。
sudo apt-get install gdbserver
起動するアプリはフルスクリーンモードで動作し、通常はHDMIモニターでしか描画されません。液晶モニターではなくVNCでモニタリングしているのであれば、VNCサーバーのオプションで「Enable experimental direct capture mode」にチェックを入れます。
「sudo nano ~/.bashrc」などでbashファイルを開き、Qtにおけるディスプレイの物理サイズをミリ単位で登録しておきます。例えば、Amazonで3000円ほどで売っているOSOYOOのHDMI 3.5インチLCDディスプレイを使用するのであれば、「export QT_QPA_EGLFS_PHYSICAL_WIDTH=86」「export QT_QPA_EGLFS_PHYSICAL_HEIGHT=56」になります。
Qtプロジェクトを作成したら、proファイルを開いて、ビルドしたLinuxアプリのインストール先を「/home/pi/$${TARGET}/bin」のように、ホストからアクセス可能なディレクトリに置き換えます。
アプリが正常に動作しない原因はたいてい外部ライブラリーが見つからなかったことによるものなので、この場合は、Qt関連のライブラリーだけではなく「libxcb-xinerama0-dev」といった、サードパーティーライブラリーが正しく登録されているか、不要だと思ったアプリをアンインストールしてしまったことで関連したライブラリーが失われていないか、などを検証してみましょう。
 | 2018年11月9日

Raspberry PiはストレージがSDカードという特性上、多くのファイルが作成されるC言語によるアプリ開発をしようとすると、コンパイルにわりかし時間がかかってしまいます。アプリを動かすのとは異なるパソコンでプログラムをコンパイルする手法はクロスコンパイルと呼ばれており、SSD搭載などの最新の環境でプログラムをビルドすることができれば、格段に開発効率が上がります。今回はLinuxディストリビューションのひとつであるUbuntuを使って、ラズベリーパイのアプリを開発・リモートデバッグする方法をご紹介します。

基本的な流れは、稼働中のRaspberry Piからarm用プログラムライブラリーをデスクトップパソコンで動かしているUbuntuにコピーして、それらをもとにQtのオリジナルソースをコンパイルすることとなります。Windowsでも同様のことはできなくもないのですが、Linuxに比べて環境構築の難易度が相当高いので、Windowsユーザーは仮想マシンのLinuxで行いましょう。

まずはRaspbian側でarmライブラリーをダウンロードします。余計なライブラリー情報がない、新規インストールしたRaspbianで行うのがいいでしょう。

「/etc/apt/sources.list」ファイル内にある3行目の行頭にある「#」を削除したものを上書き保存し、ダウンロードソースの追加を指示します。
テキストの更新後は「apt-get update」を実行する必要があります。
sudo apt-get update sudo apt-get upgrade sudo rpi-update reboot
ちなみに、最新のWindows 10にはSSHクライアントが標準で含まれており、このサービスを有効にすることで、Windows PowerShellからRaspbianのターミナルを操作することができます。パワーシェルよりアクセスするには「ssh [ユーザー名]@[接続先アドレス]」を入力します。
sudo apt-get build-dep qt5-default sudo apt-get install qtdeclarative5-dev libts-dev
ビルド後のライブラリーを保存するディレクトリを作成します。
sudo mkdir /usr/local/qt5pi sudo chown -R pi:pi /usr/local/qt5pi
Ubuntu側の設定に移ります。Ubuntuのターミナルを起動したら開発ツール一式をインストールします。
sudo apt-get install gcc git bison python gperf pkg-config gdb-multiarch
作業用ディレクトリーを作成します。
sudo mkdir /opt/qt5pi sudo chown 1000:1000 /opt/qt5pi
ラズベリーパイ用のツールチェインをGithubよりダウンロードします。
git clone https://github.com/raspberrypi/tools
このツールチェインのディレクトリーをシステムに登録します。「~/.bashrc」ファイルを開いて、次の一文を追加しましょう。
export PATH=$PATH:/opt/qt5pi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
Ubuntuを再起動してパスを有効にしたら、Qtのソースコードをダウンロードし、アーカイブファイルを展開します。現時点の最新版はVer 5.11ですが、5.9がLTS(より長い期間メンテナンスされるバージョン)として提供されています(2018年11月末に公開予定のVer 5.12はLTS)。
cd /opt/qt5pi wget http://download.qt.io/official_releases/qt/5.11/5.11.2/single/qt-everywhere-src-5.11.2.tar.xz tar xf qt-everywhere-src-5.11.2.tar.xz
Qtのメイクプリセットには「浮動小数点演算をサポートしたARM Linux(linux-arm-gnueabihf)」のプロファイルが用意されていないので、「linux-arm-gnueabi」をベースに、自分でこのプロファイルを作りましょう。このコマンドラインでは、「mkspecs」にある「linux-arm-gnueabi」フォルダーをコピーして、中にあるテキストを「arm-linux-gnueabihf」に置換する作業を行っています。
cp -R qt-everywhere-src-5.11.2/qtbase/mkspecs/linux-arm-gnueabi-g++ qt-everywhere-src-5.11.2/qtbase/mkspecs/linux-arm-gnueabihf-g++ sed -i -e 's/arm-linux-gnueabi-/arm-linux-gnueabihf-/g' qt-everywhere-src-5.11.2/qtbase/mkspecs/linux-arm-gnueabihf-g++/qmake.conf
Rasbianを動かしている本体から、ライブラリー一式をUbuntuへコピーします。接続がうまくいかないようなら、「raspberrypi.local」の箇所をRaspberry Piの保有するローカルIPアドレスに置き換えてください。
mkdir sysroot sysroot/usr sysroot/opt rsync -avz pi@raspberrypi.local:/lib sysroot rsync -avz pi@raspberrypi.local:/usr/include sysroot/usr rsync -avz pi@raspberrypi.local:/usr/lib sysroot/usr rsync -avz pi@raspberrypi.local:/opt/vc sysroot/opt
Raspberry Piのファイルをそのまま持ってきただけでは、ライブラリーのシンボリックリンクに問題が残るので、以下のコマンドでそれらのリンクを修正します。
mv sysroot/usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 sysroot/usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0_backup mv sysroot/usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 sysroot/usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0_backup ln -s sysroot/opt/vc/lib/libEGL.so sysroot/usr/lib/arm-linux-gnueabihf/libEGL.so.1.0.0 ln -s sysroot/opt/vc/lib/libGLESv2.so sysroot/usr/lib/arm-linux-gnueabihf/libGLESv2.so.2.0.0 ln -s sysroot/opt/vc/lib/libEGL.so sysroot/opt/vc/lib/libEGL.so.1 ln -s sysroot/opt/vc/lib/libGLESv2.so sysroot/opt/vc/lib/libGLESv2.so.2 wget https://raw.githubusercontent.com/riscv/riscv-poky/master/scripts/sysroot-relativelinks.py chmod +x sysroot-relativelinks.py ./sysroot-relativelinks.py sysroot
ソースコードのビルドに移ります。現在のディレクトリが「/opt/qt5pi/」であることを確認し、Configureツールを実行します。赤文字の箇所を「linux-rasp-pi2-g++」「linux-rasp-pi3-g++」に差し替えると、それぞれの機器に最適化されたコンパイルオプションが付与されます。この例では、ウェブブラウザエンジンなどのコンパイルに時間がかかったり、使用頻度の少ないライブラリーをskipオプションで省略しています。ターミナルへのコピー&ペーストだとたまに認識されないことがあるので注意。
../qt-everywhere-src-5.11.2/configure -opengl es2 -device linux-rasp-pi-g++ -device-option CROSS_COMPILE=arm-linux-gnueabihf- -sysroot /opt/qt5pi/sysroot -prefix /usr/local/qt5pi -opensource -confirm-license -skip qtwebengine -skip qtscript -skip qtlocation -skip qtserialbus -nomake examples -make libs
makeによるビルドを始める際、「j」オプションを付けると、ビルドに同時使用するCPUのコア数を変更することができます。たとえば、「-j4」とすると、4つのコアがつかわれるため、適切なCPU(と適切な仮想マシン設定)であれば、オプションを付けない場合に比べて理論上は4倍速になります。
make -j4 make install
ビルドとインストールが完了したら、それらのファイルをラズベリーパイへコピーします。
cd /opt/qt5pi rsync -avz sysroot/usr/local/qt5pi pi@raspberrypi.local:/usr/local
UbuntuのQt Creatorでアプリを開発する方法は次回にて。
 | 2018年11月9日

操作性をより重視したスマートフォン向けOSでは、メインスレッドがビジー状態になると有無を言わさずアプリをシャットダウンしたり、別スレッドで呼び出さないと動作しないAPIなどがあるため、マルチスレッド処理はアプリ開発において不可欠のテクニックです。

C++でQMLコンポーネントを作成するで取り上げたQTimerクラスの扱い方と同じように、Qt Quickにおけるマルチスレッド処理では、スレッドの開始と終了のタイミングをシグナルとして受け取り、その内部で処理する必要があります。

こちらはスレッドの作成例です。newによりQThreadオブジェクトを作成したら、QThread::start()シグナルとスレッドループの関数をつなぎます。また、QThread::finished()シグナルとQThread::deleteLater()を紐付けることで、スレッドが完了したら自動でオブジェクトを破棄されるようにしています。
void ThreadTest::execThread()
{
    QThread *thread = new QThread(this);
    connect(thread, SIGNAL(started()), this, SLOT(onStartThread()), Qt::DirectConnection);
    connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
    thread->start();
}
引数にある「Qt::DirectConnection」はスレッドを直に実行するオプションです。これを「Qt::BlockingQueuedConnection」にすると、キューがタスクに追加され、既存のスレッドが完了するまで待機状態になります。

スレッドを中断させたいときはQThread::requestInterruption()を使い、スレッド側で「QThread::isInterruptionRequested()==true」であるかどうかを調べます。スレッドの完了を待機したいのであれば、QThread::quit()を呼び出してからQThread::wait()を実行させます。

void ThreadTest::exitThread()
{
    // 中断要求を出して、スレッドが完全に終了するのを待機する
    thread->requestInterruption();
    thread->quit();
    thread->wait();
}

void ThreadTest::onExecThread()
{
    if(thread){
        // 既存のスレッドが実行状態なら何もしない
        if(thread->isRunning() == true) return;
        exitThread();
    }

    thread = new QThread(this);
    connect(thread, SIGNAL(started()), this, SLOT(onStartThread()), Qt::DirectConnection);
    connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
    thread->start();
}

void ThreadTest::onStartThread()
{
    while(true){
        // 現在のスレッドで中断要求があればループから抜ける
        if(QThread::currentThread()->isInterruptionRequested() == true) return;
        // スレッド内での処理
    }
}
別スレッドからでもQMLのプロパティを取得・変更することはできますが、QMLのメソッドを呼び出すことはできません。異なるスレッドからUIスレッドを操作したい場合は「QMetaObject::invokeMethod」を使います。QMLの内部はJavaScriptである関係上、引数はQVariant型に変換してvarとして渡す必要があります。
import QtQuick 2.9
import QtQuick.Controls 1.0

Item{
    id: w
    signal execThread()
    property alias progressVal: pgb.value

    Column{
        width: parent.width
        anchors.verticalCenter: parent.verticalCenter

        Button{
            id: btn
            objectName: "btn"
            anchors.horizontalCenter: parent.horizontalCenter
            text:"Execute"
            onClicked: execThread()
            function updateText(v){
                btn.text = v;
            }
        }

        ProgressBar{
            id: pgb
            anchors.horizontalCenter: parent.horizontalCenter

            width: parent.width * 0.8
            maximumValue: 1000
            minimumValue: 0
        }
    }
}
class MainView : public QQuickView
{
    Q_OBJECT
private:
    QThread *pgb_thread = nullptr;
public:
    MainView();
    void exitThread();

public slots:
    void onExecThread();
    void onStartThread();
};

MainView::MainView()
{   
    connect(this->rootObject(), SIGNAL(execThread()), this, SLOT(onExecThread()));
}

void MainView::exitThread()
{
    if(!pgb_thread) return;
    pgb_thread->requestInterruption();
    pgb_thread->quit();
    pgb_thread->wait();
}

void MainView::onExecThread()
{
    if(pgb_thread){
        if(pgb_thread->isRunning() == true) return;
        exitThread();
    }

    pgb_thread = new QThread(this);
    connect(pgb_thread, SIGNAL(started()), this, SLOT(onStartThread()), Qt::DirectConnection);
    connect(pgb_thread, SIGNAL(finished()), SLOT(deleteLater()));
    pgb_thread->start();
}

void MainView::onStartThread()
{   
    // QMLから特定のアイテムを検索する。
    // ただし、QMLとC++とのロジック分離の観点より、ルートではないアイテムを取得するのは本来好ましくない。
    // また、キーワードはobjectNameの値でありidでない点に注意!
    QQuickItem* btn = rootObject()->findChild<QQuickItem*>("btn");
    if(btn){
        QMetaObject::invokeMethod(btn, "updateText",
            Q_ARG(QVariant, QVariant::fromValue(QString("Running"))));
        return;
    }

    for(int i = 0; i < 1000; i++){
        if(QThread::currentThread()->isInterruptionRequested() == true) return;
        QThread::msleep(200);
        // プロパティを取得したい場合の例
        // int v = rootObject()->property("progressVal").toInt();
        rootObject()->setProperty("progressVal", i);
    }
}
 | 2018年11月7日

デフォルトプロジェクトで生成されるQMLはWindowsコンポーネントを使ったデスクトップスタイルのプログラムとなっています。しかしながら、スマートフォンやタブレットのアプリでは基本的にウィンドウの概念がなく、単一のビューで成り立っています。

Qt QuickにはQQuickWindowから派生したQQuickViewが提供されており、このクラスを使うことで、それらの環境に適したアプリを開発することができます。

QQuickViewを使ったひな形の例です。QQuickViewの派生クラス・MainViewを作成し、main()でMainViewの作成と表示を行います。MainViewのコンストラクタでは、リサイズモードに「QQuickView::SizeRootObjectToView(ビューのサイズに応じて最上位QMLコンポーネントサイズを自動で変更)」を指定してから、QMLファイルを呼び出しています。

スマートフォンアプリでは原則としてウィンドウのサイズはOSが決めるので、アプリ側で初期サイズを指定する必要はありませんが、デスクトップOSとのクロスプラットフォーム開発をするのであれば、ビューを表示する前にwidthとheightの値を指定しておきます。
#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "mainview.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    // メインビューの作成と表示
    MainView mv;
    mv.show();

    return app.exec();
}
import QtQuick 2.9

// Windowの配置や、Itemのサイズ指定は不要
Item {
}
#include <QQuickView>

class MainView : public QQuickView
{
    Q_OBJECT
public:
    MainView();
};

MainView::MainView()
{   
    // デスクトップOSならウィンドウサイズを指定
#if defined(Q_OS_LINUX) || (defined(Q_OS_WIN) && !defined(Q_OS_WINRT))
    setWidth(640);
    setHeight(480);
#endif

    // 最上位コンポーネントのサイズをビューに合わせる
    this->setResizeMode(QQuickView::SizeRootObjectToView);
    // 最初に適用するQMLをリソースから読み込む
    this->setSource(QUrl("qrc:/main.qml"));
}
せっかくなので、ビュースタイルアプリ特有のイベントを処理してみましょう。ウィンドウのないOSでは、アプリを切り替えるなどでバックグラウンドに移動したときにサスペンドイベントが発生します。Qt 5.2以降では「QGuiApplication::applicationState」にアプリが休止状態であるかどうかが記録されており、applicationStateChangedシグナルを受け取ることで、アプリの休止や復旧時の処理を行うことができます。
class MainView : public QQuickView
{
    Q_OBJECT
public slots:
    void onAppStateChanged(Qt::ApplicationState s);
};

MainView::MainView()
{   
    // グローバルなQGuiApplicationオブジェクトはqAppとして呼び出せる
    connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
        this, SLOT(onAppStateChanged(Qt::ApplicationState)));

    // アプリが終了されるときに呼び出される
    connect(qApp, SIGNAL(aboutToQuit()), SLOT(onQuit()));
}

void MainView::onAppStateChanged(Qt::ApplicationState s)
{
    if (s == Qt::ApplicationSuspended){
        // サスペンド状態に入るので、設定データの保存などを行う
    } else if (s == Qt::ApplicationActive){
        // サスペンドから復旧したので、接続の再開などを行う
    }
}

void MainView::onQuit()
{
    // OSによってはアプリ終了時にサスペンドに移行しないため、直接呼び出している
    onAppStateChanged(Qt::ApplicationSuspended);
}
 | 2018年11月6日