Logs
Consultez les logs.
OK
Liste des données
Consultez la liste des données.
OK
Loading...
Formulaire
Saisissez vos données.
Enregistrer
Annuler

Oscilloscope numérique

Vues
419

Introduction


ReadyScope est un oscilloscope numérique pour le streaming de données texte ou binaires via TCP, ports série et Bluetooth RFCOMM. Il offre un rendu flexible et un format de streaming binaire ou de flux de texte hautement configurable. Il se compose d'une fenêtre principale avec un nombre variable d'oscilloscopes affichant les traces de signaux entrants.

Les sources de données de signal incluent les sources TCP, les ports série (USB ou série matériel) et Bluetooth RFCOMM. Le format des données entrantes comprend le format texte brut et le format de streaming binaire. Le format de streaming binaire est entièrement configurable, y compris l'en-tête, le nombre de canaux et la largeur des données de canal.

Les données acquises peuvent être exportées vers un fichier.

Le nombre d'oscilloscopes, ainsi que le nombre de traces par oscilloscope sont entièrement configurables

Caractéristiques principales:

Connexion série (USB et série matérielle), Bluetooth RFCOMM et TCP.
Format de données texte et binaire.
Indication en temps réel de la vitesse des données entrantes.
Portées entièrement configurables, avec plusieurs traces par portée, échelle X/Y configurable, couleurs, etc.
Vitesse d'affichage configurable.
Zoom (temps et amplitude) et mise à l'échelle automatique.
Exporter les données du signal vers un fichier.

image.png


Création d'un projet C++ avec CMake sous VSCode


Nous utilisons l'éditeur (VSCode) pour configurer un projet C++ avec l'outil (CMake) sous l'environnement (WSL-Ubuntu) du système (Windows).


Cloner un dépôt volumineux sous Git


// Terminal
git clone http://github.com/large-repository --depth 1
cd large-repository
git fetch --unshallow

Installer l'extension WSL sous VSCode


// VSCode (Extensions)
VSCode > Extensions > WSL > Install

Ouvrir un dossier avec WSL sous VSCode


// VSCode (WSL Folder)
Barre de recherche > Show and Run Commands
> WSL: Open Folder in WSL
Dossier > v03 > Sélectionner un dossier

Installer l'extension C++ sous VSCode


// VSCode (Extensions C++)
VSCode > Extensions > C/C++ > Install

Installer l'extension CMake sous VSCode


// VSCode (Extensions CMake)
VSCode > Extensions > CMake > Install
VSCode > Extensions > CMake Tools > Install

Configurer l'outil CMake


// VSCode (CMakeLists.txt)
Barre de recherche > Show and Run Commands 
> CMake: Quick Start
Name : ReadyScope
C++ : Create a C++ project
Executable : Create an executable
OK > OK

Configurer le projet C++


// main.cpp
#include <iostream>
...
int main(int argc, char *argv[])
{
    std::cout << "Démarrage de l'application..." << std::endl;
    return 0;
}

// CMakeLists.txt
cmake_minimum_required(VERSION 3.5.0)
project(ReadyScope VERSION 0.1.0 LANGUAGES C CXX)
...
add_executable(ReadyScope main.cpp)

Compiler le projet C++


// VSCode (Build)
CMake Tools > Build > Run

Exécuter le projet C++


// VSCode (Launch)
CMake Tools > Launch > Run

image.png


Débogage d'un projet C++ avec CMake sous VSCode


Nous utilisons l'éditeur (VSCode) et l'extension (CMake) pour déboguer un projet C++.


Placer les points d'arrêt


// VSCode (Points d'arrêt)
VSCode > Placer des points d'arrêt aux lignes de débogage souhaitées
...
Cliquer le bord gauche d'une ligne pour placer un point d'arrêt

image.png

Démarrer le débogueur


// VSCode (Debug)
CMake Tools > Debug > Run
...
Le débogueur s'arrête au premier point d'arrêt trouvé

image.png

Visualiser les variables locales


// VSCode (Debug)
VSCode > Debug > Variables > Locals

image.png

Visualiser la pile des apples

 
// VSCode (Debug)
VSCode > Debug > Call Stacks

image.png


Configuration d'un projet C++ en ligne de commande avec CMake


Nous éditons les fichiers (makes.sh), (commands.mak), (envs.sh) pour configurer le projet C++ en ligne de commande sous (CMake) avec la commande (make).


Configurer la commande make


// build-cmd/make.sh
MAKE_CMD="$1"
MAKE_ARGS=""
shift
...
while test $# -gt 0
do
    MAKE_ARGS="$MAKE_ARGS $1"
    shift
done
...
make -f commands.mak $MAKE_CMD ARGS="$MAKE_ARGS"

Configurer les règles de construction


// build-cmd/commands.mak
SRC_DIR = ..
BUILD_DIR = ../build
TARGET_NAME = ReadyScope
SETUP_NAME = $(BUILD_DIR)/$(TARGET_NAME)
...
all: clean_exe cmake compile run
all_g: clean_exe cmake_g compile run_g
...
cmake:
	@echo
	@cmake -B $(BUILD_DIR) -S $(SRC_DIR)
cmake_g:
	@echo
	@cmake -DCMAKE_BUILD_TYPE=Debug -B $(BUILD_DIR) -S $(SRC_DIR)
compile:
	@echo
	@make -C $(BUILD_DIR)
clean_exe:
	@echo
	@rm -f $(SETUP_NAME)
clean: clean_exe
	@echo
	@make -C $(BUILD_DIR) clean
clean_all:
	@echo
	@rm -rf $(BUILD_DIR)/*
run:
	@echo
	@./envs.sh && $(SETUP_NAME) $(ARGS)
	@echo
run_g:
	@echo
	@./envs.sh && gdb --args $(SETUP_NAME) $(ARGS)
	@echo

Configurer le fichier de variables d'environnement


// Fichiers
Ajouter le fichier > build-cmd/envs.sh

Ouvrir le terminal dans VSCode


// VSCode (Terminal)
View > Terminal

Rendre exécutable les fichiers de script


// Terminal
chmod a+x makes.sh
chmod a+x envs.sh

Exécuter le projet


// Terminal
./makes.sh all

image.png


Configuration d'un projet C++ Qt sous CMake


Nous éditons les fichiers (c_cpp_properties.json), (CMakeLists.txt), (main.cpp) pour configurer le projet C++ avec la bibliothèque (Qt) sous l'outil de construction (CMake).


Configurer la version WSL-v2


// Terminal
wsl -l -v
...
  NAME              STATE           VERSION
* Ubuntu            Stopped         1
  docker-desktop    Stopped         2
...
wsl --set-version Ubuntu 2
...
wsl -l -v
...
  NAME              STATE           VERSION
* Ubuntu            Running         2
  docker-desktop    Stopped         2

Récupérer l'emplacement du répertoire include


// Windows (Explorateur)
Ouvrir l'explorateur Windows
Explorer > Linux > Ubuntu
...
Ouvrir le dossier > include
Récupérer le chemin du dossier dans la barre d'adresse
\\wsl.localhost\Ubuntu\home\admins\Qt5.14.2\5.14.2\gcc_64\include
...
Adapter le chemin du dossier
/home/admins/Qt5.14.2/5.14.2/gcc_64/include 

Créer le fichier de configurations C/C++


// VSCode (c_cpp_properties.json)
Barre de recherche > Show and Run Commands
> C/C++: Edit Configurations (JSON)

Configurer les répertoires include


// .vscode/c_cpp_properties.json
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/admins/Qt5.14.2/5.14.2/gcc_64/include",
                "/home/admins/Qt5.14.2/5.14.2/gcc_64/include/QtCore",
                "/home/admins/Qt5.14.2/5.14.2/gcc_64/include/QtWidgets"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/g++",
            "cStandard": "gnu17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

Configurer le projet Qt sous CMake


// CMakeLists.txt
cmake_minimum_required(VERSION 3.5.0)
project(ReadyScope VERSION 0.1.0 LANGUAGES C CXX)
...
find_package(Qt5 REQUIRED COMPONENTS
Widgets
)
...
add_executable(ReadyScope
main.cpp
)
...
target_link_libraries (ReadyScope
Qt5::Widgets
)

// main.cpp
#include <QApplication>
#include <QDebug>
...
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug() << "Démarrage de l'application...";
    return a.exec();
}

Exécuter le projet


// Terminal
./makes.sh all

image.png


Configuration d'une interface utilisateur (ui) avec Qt Designer


Nous éditons les fichiers (resources.qrc), (CMakeLists.txt), (main.cpp), (MainWindow.h), (MainWindow.cpp), (MainWindow.ui) pour configurer l'interface utilisateur (ui) avec Qt Designer.


Configurer le fichier ressource Qt


// resources.qrc
<RCC>
    <qresource prefix="/">
        <file>data/img/app-logo.ico</file>
    </qresource>
</RCC>

Configurer le logo de l'application


// Fichiers
Ajouter le fichier > data/img/app-logo.ico

Ouvrir l'outil Qt Designer


// Terminal
/home/admins/Qt5.14.2/5.14.2/gcc_64/bin/designer

Créer l'interface utilisateur (ui)


// Qt Designer (MainWindow.ui)
File > New > Main Window > Create
...
File > Save As
Lock In > v03
File name > MainWindow.ui
Save

Ajouter une ressource Qt à l'interface utilisateur (ui)


// Qt Designer (MainWindow.ui)
Resource Browser > Edit Resources > Open Resource File 
Look In > v03
File name > resources.qrc > Open
OK

image.png

Configurer les propriétés de l'interface utilisateur (ui)


// Qt Designer (MainWindow.ui)
QMainWindow > Property > objectName > MainWindow
QMainWindow > Property > windowTitle > ReadyScope - Oscilloscope numérique
QMainWindow > Property > windowIcon > Choose Resource > app-logo.ico > OK

image.png

Configurer l'interface utilisateur (ui)


// main.cpp
#include <QApplication>
#include <QStyleFactory>
#include <QDebug>
...
#include "MainWindow.h"
...
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    qDebug() << "Démarrage de l'application...";
    ...
    MainWindow w;
    w.show();
    ...
    return a.exec();
}

// MainWindow.h
#pragma once
...
#include <QMainWindow>
...
namespace Ui
{
    class MainWindow;
}
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    ...
private:
    Ui::MainWindow *ui;
};

// MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
...
#include <QDebug>
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}
...
MainWindow::~MainWindow()
{
    delete ui;
}

// CMakeLists.txt
cmake_minimum_required(VERSION 3.5.0)
project(ReadyScope VERSION 0.1.0 LANGUAGES C CXX)
...
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
...
find_package(Qt5 REQUIRED COMPONENTS
Widgets
)
...
set(SRC_FILES
main.cpp
)
...
set(UI_FILES
MainWindow.cpp
)
...
set(RCC_FILES
resources.qrc
)
...
add_definitions(-DDEVELMODE)
...
qt_wrap_ui(${UI_FILES})
...
add_executable(ReadyScope
${SRC_FILES}
${UI_FILES}
${RCC_FILES}
)
...
target_link_libraries (ReadyScope
Qt5::Widgets
)

Exécuter le projet


// Terminal
./makes.sh all
...
L'application est lancée
...
L'icône de l'application apparaît dans la barre des tâches

image.png


Configuration d'un indicateur de définition en C++ avec CMake


Nous éditons les fichiers (CMakeLists.txt), (MainWindow.cpp) pour configurer l'indicateur de définition (DEVELMODE) en C++ avec l'outil de construction (CMake).


Configurer l'indicateur de définition


// CMakeLists.txt
cmake_minimum_required(VERSION 3.5.0)
project(ReadyScope VERSION 0.1.0 LANGUAGES C CXX)
...
add_definitions(-DDEVELMODE)
...

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ...
#ifdef DEVELMODE
    qDebug() << "Démarrage de l'application [WITH_DEVEL_MODE]...";
#else
    qDebug() << "Démarrage de l'application [NO_DEVEL_MODE]...";
#endif
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
L'indicateur de définition (DEVELMODE) est bien pris en compte
...
Le bon message apparaît à l'écran

image.png


Initialisation du fond d'écran de la fenêtre d'affichage de l'oscilloscope


Nous éditons les fichiers (MainWindow.ui), (MainWindow.h), (MainWindow.cpp), (DScopesQTWidget.h), (DScopesQTWidget.cpp), (DScopesQT.h), (DScopesQT.cpp), (Scopes.h), (Scopes.cpp), (CMakeLists.txt) pour initialiser le fond d'écran de la fenêtre d'affichage de l'oscilloscope.


Créer l'interface utilisateur (ui)


// Qt Designer (MainWindow.ui)
QStackedWidget > Property > objectName > stackedWidget
QTabWidget > Property > objectName > tabWidget
QVBoxLayout > Property > layoutName > layScope

image.png

Initialiser le fond d'écran de l'oscilloscope


// MainWindow.h
...
namespace Ui
{
    class MainWindow;
}
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ...
private:
    Ui::MainWindow *ui;
    DScopesQTWidget *dscopes;
};
...

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      ...
{
    ui->setupUi(this);
    ...
    ui->stackedWidget->setCurrentIndex(0);
    ui->tabWidget->setCurrentIndex(0);
    dscopes = new DScopesQTWidget(0, 0, 640, 480, false);
    ui->layScope->addWidget(dscopes);
    ...
    resize(800, 640);
}
...

// scope/DScopesQTWidget.h
...
class DScopesQTWidget : public QWidget, public DScopesQT
{
    Q_OBJECT
    ...
public:
    DScopesQTWidget(unsigned _x, unsigned _y, unsigned _w, unsigned _h, bool 
    _alpha = false, unsigned scale = 1, QWidget *parent = 0);
    ~DScopesQTWidget();
    ...
protected:
    void paintEvent(QPaintEvent *event);
    void resizeEvent(QResizeEvent *event);
    ...
private:
    QImage pixmap;
    unsigned basex, basey;
    unsigned scale;
};
...

// scope/DScopesQTWidget.cpp
...
DScopesQTWidget::DScopesQTWidget(unsigned _x, unsigned _y, unsigned _w, unsigned _h, bool _alpha, unsigned _scale, QWidget *parent)
    : QWidget(parent),
      DScopesQT(&pixmap, _w / _scale, _h / _scale, _alpha),
      pixmap(_w / _scale, _h / _scale, format),
      basex(_x),
      basey(_y),
      scale(_scale)
{
    setMinimumSize(QSize(320, 200));
    setCursor(QCursor(Qt::CrossCursor));
    QPainter painter;
    painter.begin(&pixmap);
    painter.fillRect(_x, _y, w, h, Qt::black);
    painter.end();
    ...
}
...
void DScopesQTWidget::paintEvent(QPaintEvent *event)
{
    QPainter p(this);
    QRect validRect = rect() & event->rect();
    p.setClipRect(validRect);
    p.drawImage(basex, basey, surface->scaled(surface->width() * scale, surface->height() * scale));
}
...
void DScopesQTWidget::resizeEvent(QResizeEvent *event)
{
    pixmap = pixmap.scaled(QSize(event->size().width() / scale, event->size().height() / scale));
}
...

// scope/DScopesQT.h
...
class DScopesQT : public Scopes
{
public:
    DScopesQT(QImage *s, unsigned _w, unsigned _h, bool _alpha = false);
    ...
protected:
    QImage *surface;
};
...

// scope/DScopesQT.cpp
...
DScopesQT::DScopesQT(QImage *s, unsigned _w, unsigned _h, bool _alpha)
    : Scopes(_w, _h)
{
    surface = s;
} 
...

// scope/Scopes.h
...
class Scopes
{
public:
    Scopes(unsigned _w, unsigned _h);
    ...
protected:
    unsigned w, h;
};
...

// scope/Scopes.cpp
...
Scopes::Scopes(unsigned _w, unsigned _h)
    : w(_w),
      h(_h)
{
}
...

// CMakeLists.txt
...
set(SRC_FILES
...
scope/Scopes.cpp
scope/DScopesQT.cpp
scope/DScopesQTWidget.cpp
)
...
include_directories(
scope
)
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre princiaple de l'application apparaît
...
L'oscilloscope apparaît avec un fond d'écran noir

image.png


Connexion automatique d'un signal à un slot en C++ avec Qt


Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp) pour connecter automatiquement le signal (valueChanged) de l'objet (edtRefreshRate) de type (QSpinBox) au slot (on_edtRefreshRate_valueChanged).


Créer l'interface utilisateur (ui)


// Qt Designer (MainWindow.ui)
QSpinBox > Property > objectName > edtRefreshRate
QSpinBox > Property > value > 10
QSpinBox > Property > minimum > 1
QSpinBox > Property > maximum > 60

image.png

Configurer la connexion automatique


// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
private slots:
    void on_edtRefreshRate_valueChanged(int i);
    ...
};

// MainWindow.cpp
...
void MainWindow::on_edtRefreshRate_valueChanged(int i)
{
    qDebug() << "MainWindow::on_edtRefreshRate_valueChanged...";
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre princiaple de l'application apparaît
...
Cliquer sur l'onglet (Config)
...
Modifier le taux de rafraîchissement de l'oscilloscope
Modifier le taux de rafraîchissement de l'oscilloscope
Modifier le taux de rafraîchissement de l'oscilloscope
...
Le slot connecté au changement du taux de rafraîchissement est appelé
Le slot connecté au changement du taux de rafraîchissement est appelé
Le slot connecté au changement du taux de rafraîchissement est appelé

image.png


Configuration du chargement des paramètres de l'application


Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp) pour charger les paramètres de l'application.

// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ...
private:
    bool loadSettings(const QString &fileName = "");
    ...
};

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ...
    loadSettings();
}
...
bool MainWindow::loadSettings(const QString &fileName)
{
    QSettings *settings;
    ...
    if (fileName == "")
        settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, 
        "ReadyDev", "ReadyScope");
    else
        settings = new QSettings(fileName, QSettings::IniFormat);
    ...
    if (settings->status() != QSettings::NoError)
        return false;
    ...
    int refreshRate = settings->value("RefreshRate", 10).toInt();
    ...
    if (refreshRate < ui->edtRefreshRate->minimum())
        refreshRate = ui->edtRefreshRate->minimum();
    if (refreshRate > ui->edtRefreshRate->maximum())
        refreshRate = ui->edtRefreshRate->maximum();
    ...
    ui->edtRefreshRate->setValue(refreshRate);
    on_edtRefreshRate_valueChanged(refreshRate);
    ...
    delete settings;
    return true;
}
...


Configuration de la sauvegarde des paramètres de l'application


Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp) pour configurer le timer associé à l'objet de type (MainWindow) hérité du type (QMainWindow).


Configurer la sauvegarde


// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    ~MainWindow();
    ...
private:
    bool saveSettings(const QString &fileName = "");
    ...
};

// MainWindow.cpp
...
MainWindow::~MainWindow()
{
    saveSettings();
    delete ui;
}
...
bool MainWindow::saveSettings(const QString &fileName)
{
    QSettings *settings;
    ...
    if (fileName == "")
        settings = new QSettings(QSettings::IniFormat, QSettings::UserScope, 
        "ReadyDev", "ReadyScope");
    else
        settings = new QSettings(fileName, QSettings::IniFormat);
    ...
    if (settings->status() != QSettings::NoError)
        return false;
    ...
    settings->setValue("RefreshRate", ui->edtRefreshRate->value());
    ...
    delete settings;
    return true;
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre princiaple de l'application apparaît
...
Fermer l'application
...
Le fichier (settings.ini) est créé
Les paramètres de l'application sont bien enregistrés

// /home/admins/.config/ReadyDev/ReadyScope.ini
[General]
RefreshRate=15


Configuration du timer associé à un objet Qt en C++


Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp) pour configurer le timer associé à l'objet de type (MainWindow) hérité du type (QMainWindow).


Configurer le timer


// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ...
protected:
    void timerEvent(QTimerEvent *event);
    ...
private:
    bool loadSettings(const QString &fileName = "");
    ...
private slots:
    void on_edtRefreshRate_valueChanged(int i);
    ...
private:
    Ui::MainWindow *ui;
    int timer;
    ...
};

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      timer(0)
{
    ui->setupUi(this);
    ...
    loadSettings();
}
...
void MainWindow::timerEvent(QTimerEvent *event)
{
    qDebug() << "MainWindow::timerEvent...";
}
...
bool MainWindow::loadSettings(const QString &fileName)
{
    ...
    on_edtRefreshRate_valueChanged(refreshRate);
    ...
    return true;
}
...
void MainWindow::on_edtRefreshRate_valueChanged(int i)
{
    if (timer)
    {
        killTimer(timer);
        timer = 0;
    }
    timer = startTimer(1000 / i);
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre princiaple de l'application apparaît
...
Le timer associé à la fenêtre principale est bien en marche

image.png


Conception d'un gestionnaire de sauvegarde de paramètres (ini)



Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp), (DSettings.h), (DSettings.cpp), (CMakeLists.txt) pour créer un gestionnaire de sauvegarde de paramètres (ini).


Créer le gestionnaire de sauvegarde


// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    ~MainWindow();
    ...
private:
    bool saveSettings(const QString &fileName = "");
    ...
};

// MainWindow.cpp
...
MainWindow::~MainWindow()
{
    saveSettings("settings.ini");
    delete ui;
}
...
bool MainWindow::saveSettings(const QString &fileName)
{
    if (fileName == "")
        return false;
    ...
    DSettings *settings;
    settings = new DSettings(fileName);
    ...
    if (settings->status() != QSettings::NoError)
        return false;
    ...
    settings->setValue("RefreshRate", ui->edtRefreshRate->value());
    ...
    delete settings;
    return true;
}
...

// DSettings.h
...
class DSettings : public QObject
{
    Q_OBJECT
    ...
public:
    explicit DSettings(const QString &fileName, QObject *parent = nullptr);
    ~DSettings();
    QSettings::Status status() const;
    QString escape(const QString &str);
    void setValue(const QString &key, const QString &value);
    void setValue(const QString &key, int value);
    ...
private:
    QFile file;
    QTextStream outstream;
};
...

// DSettings.cpp
...
DSettings::DSettings(const QString &fileName, QObject *parent)
    : QObject(parent)
{
    if (fileName.isNull())
        return;
    ...
    file.setFileName(fileName);
    ...
    if (file.open(QIODevice::WriteOnly | QIODevice::Text | 
    QIODevice::Truncate))
    {
        outstream.setDevice(&file);
        outstream << "[General]" << Qt::endl;
    }
}
...
DSettings::~DSettings()
{
    if (file.isOpen())
    {
        file.close();
    }
}
...
QSettings::Status DSettings::status() const
{
    if (file.isOpen())
        return QSettings::NoError;
    else
        return QSettings::AccessError;
}
...
QString DSettings::escape(const QString &str)
{
    if (str.indexOf("\n") == -1 && str.indexOf("\a") == -1 && 
    str.indexOf(";") == -1)
        return str;

    QString escaped(str);
    escaped = escaped.replace("\n", "\\n");
    escaped = escaped.replace("\a", "\\a");
    return "\"" + escaped + "\"";
}
...
void DSettings::setValue(const QString &key, const QString &value)
{
    outstream << key << "=" << escape(value) << Qt::endl;
}
...
void DSettings::setValue(const QString &key, int value)
{
    setValue(key, QString("%1").arg(value));
}
...

// CMakeLists.txt
...
set(SRC_FILES
...
DSettings.cpp
)
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre princiaple de l'application apparaît
...
Fermer l'application
...
Le fichier (settings.ini) est créé
Les paramètres de l'application sont bien enregistrés

// settings.ini
[General]
RefreshRate=10


Lecture des arguments passés en ligne de commande en C++ avec Qt


Nous éditons le fichier (MainWindow.cpp) pour lire les arguments passés en ligne de commande en C++ avec la bibliothèque (Qt).


Lire les arguments


// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      timer(0)
{
    ui->setupUi(this);
    ...
    QStringList arg = qApp->arguments();
    qDebug() << arg;
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre princiaple de l'application apparaît
...
Les arguments passés en ligne de commande sont affichés

image.png


Navigation entre les pages de la fenêtre principale de l'application


Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp) pour naviguer entre les pages de la fenêtre principale de l'application.


Créer la page (0)


// Qt Designer (MainWindow.ui - Page 0)
QStackedWidget > Property > objectName > stackedWidget
...
QWidget > Property > objectName > page_0

image.png

Créer la page (1)


// Qt Designer (MainWindow.ui - Page 1)
QStackedWidget > Property > objectName > stackedWidget
...
QWidget > Property > objectName > page_1

image.png

Naviguer entre les pages


// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ...
private slots:
    void on_btnPreviousPage_clicked();
    void on_btnNextPage_clicked();
    ...
};

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      timer(0)
{
    ui->setupUi(this);
    ...
}
...
void MainWindow::on_btnPreviousPage_clicked()
{
    int current = ui->stackedWidget->currentIndex();
    if (current <= 0)
        return;
    ui->stackedWidget->setCurrentIndex(current - 1);
}
...
void MainWindow::on_btnNextPage_clicked()
{
    int current = ui->stackedWidget->currentIndex();
    if (current >= ui->stackedWidget->count() - 1)
        return;
    ui->stackedWidget->setCurrentIndex(current + 1);
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre princiaple de l'application apparaît
...
Cliquer sur le bouton (Previous page)
...
La page (0) apparaît dans la fenêtre principale

image.png

// Application
Cliquer sur le bouton (Next page)
...
La page (1) apparaît dans la fenêtre principale

image.png


Démarrage de la connexion au port série en C++ avec Qt


Nous éditons les fichiers (MainWindow.ui), (MainWindow.h), (MainWindow.cpp), (ParseConnection.h), (ParseConnection.cpp), (IoDevice.h), (IoDevice.cpp) pour démarrer la connexion au port série (/dev/pts/11) en C++ avec Qt.


Installer la librairie Qt Serial Port


// Terminal
apt-cache search serial | grep qt
...
libqt5serialport5-dev - Qt 5 serial port development files
...
sudo apt install libqt5serialport5-dev

Installer le gestionnaire de port série virtuel


// Terminal
sudo apt install socat

Créer un port série virtuel


// Terminal
socat -d -d pty,raw,echo=0 pty,raw,echo=0
...
2024/12/07 12:01:32 socat[335058] N PTY is /dev/pts/11
2024/12/07 12:01:32 socat[335058] N PTY is /dev/pts/12
2024/12/07 12:01:32 socat[335058] N starting data transfer loop with FDs [5,5] and [7,7]
...

Se connecter au port série virtuel


// MainWindow.ui
QPushButton > Property > objectName > btnConnect
QPushButton > Property > text > &Connect
...
QLineEdit > Property > objectName > edtHostPort
...

// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ...
protected:
    void timerEvent(QTimerEvent *event);
    ...
private slots:
    void on_btnConnect_clicked();
    void iodevread(QByteArray ba);
    void ioconnected();
    void ioerror(QString err);
    void ioconnectionerror();
    ...
private:
    Ui::MainWindow *ui;
    DScopesQTWidget *dscopes;
    IoDevice iodev;
    ...
};

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
{
    ui->setupUi(this);
    ...
    connect(&iodev, SIGNAL(readyRead(QByteArray)), this, SLOT(iodevread(QByteArray)));
    connect(&iodev, SIGNAL(connected()), this, SLOT(ioconnected()));
    connect(&iodev, SIGNAL(error(QString)), this, SLOT(ioerror(QString)));
    connect(&iodev, SIGNAL(connectionError()), this, SLOT(ioconnectionerror()));
    ...
}
...
void MainWindow::timerEvent(QTimerEvent *event)
{
    dscopes->Render();
    dscopes->repaint();
}
...
void MainWindow::iodevread(QByteArray ba)
{
    qDebug() << "MainWindow::iodevread...";
}
...
void MainWindow::ioconnected()
{
    qDebug() << "MainWindow::ioconnected...";
}
...
void MainWindow::ioerror(QString err)
{
    QMessageBox::critical(this, "ReadyScope - Error", QString("I/O error: %1").arg(err));
}
...
void MainWindow::ioconnectionerror()
{
    QMessageBox::critical(this, "ReadyScope - Error", "Connection error");
}
...
void MainWindow::on_btnConnect_clicked()
{
    ConnectionData conn;
    ...
    bool ok = ParseConnection(ui->edtHostPort->text(), conn);
    ...
    if (!ok)
    {
        QMessageBox::critical(this, "ReadyScope - Device specification error", conn.message);
        return;
    }
    ...
    iodev.open(conn);
}
...

// parser/ParseConnection.h
...
bool ParseConnection(QString str, ConnectionData &conn);

// parser/ParseConnection.cpp
...
bool ParseConnection(QString str, ConnectionData &conn)
{
    str = str.toUpper();
    QStringList parts = str.split(':');
    ...
    for (int i = 0; i < parts.size(); i++)
        parts[i] = parts[i].trimmed();
    ...
    if (parts[0] != "SER" && parts[0] != "TCP" && parts[0] != "BT")
    {
        conn.message = "Invalid connection string: the format is <BT|SER|TCP>:<param>[:<param>]";
        return false;
    }
    ...
    if (parts[0] == "BT")
    {
    }
    else if (parts[0] == "SER")
    {
        conn.port = "/dev/pts/11";
        conn.baud = QSerialPort::Baud4800;
        conn.type = DevSerialConnection;
        return true;
    }
    else if (parts[0] == "TCP")
    {
    }
    ...
    return false;
}

// IoDevice.h
...
typedef enum _connect_t
{
    DevNotConnected,
    DevTCPConnection,
    DevSerialConnection,
    DevBTConnection
} connect_t;
...
typedef struct _ConnectionData
{
    connect_t type;
    QString message;
    QString port;
    QSerialPort::BaudRate baud;
} ConnectionData;
...
class IoDevice : public QObject
{
    Q_OBJECT
    ...
public:
    explicit IoDevice(QObject *parent = nullptr);
    ~IoDevice();
    ...
    bool open(ConnectionData cd);
    bool close(void);
    ...
private slots:
    void SERGotData();
    void SERError(QSerialPort::SerialPortError err);
    ...
signals:
    void readyRead(QByteArray b);
    void connected();
    void disconnected();
    void error(QString errmsg);
    void connectionError();
    ...
private:
    ConnectionData connectiondata;
    QSerialPort deviceSER;
    ...
};

// IoDevice.cpp
...
IoDevice::IoDevice(QObject *parent)
    : QObject(parent),
      deviceSER(parent)
{
    connect(&deviceSER, SIGNAL(readyRead()), this, SLOT(SERGotData()));
    connect(&deviceSER, SIGNAL(errorOccurred(QSerialPort::SerialPortError)), this, SLOT(SERError(QSerialPort::SerialPortError)));
    ...
}
...
IoDevice::~IoDevice()
{
}
...
bool IoDevice::open(ConnectionData cd)
{
    ...
    connectiondata = cd;
    ...
    switch (cd.type)
    {
    case DevTCPConnection:
        ...
        break;
    case DevSerialConnection:
        deviceSER.setPortName(cd.port);
        deviceSER.setDataBits(QSerialPort::Data8);
        deviceSER.setFlowControl(QSerialPort::NoFlowControl);
        deviceSER.setParity(QSerialPort::NoParity);
        deviceSER.setStopBits(QSerialPort::OneStop);
        ...
        deviceSER.open(QIODevice::ReadWrite);
        ...
        if (!deviceSER.isOpen())
        {
            qDebug("IoDevice: Serial: cannot open. Check port:speed settings.\n");
            emit connectionError();
            return false;
        }
        else
        {
            qDebug("IoDevice: Serial: Open successful\n");
            emit connected();
            return true;
        }
        ...
        break;
    case DevBTConnection:
        ...
        break;
    case DevNotConnected:
        break;
    }
    return true;
}
...
void IoDevice::SERGotData()
{
    qDebug() << "IoDevice::SERGotData...";
}
...
void IoDevice::SERError(QSerialPort::SerialPortError err)
{
    if (err == QSerialPort::NoError)
    {
        return;
    }
    ...
    QString str = QString("IoDevice: serial error %1").arg(err);
    emit error(str);
    close();
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
Cliquer sur le bouton (Next page) pour passer à la page de connexion
...
Saisir la valeur (SER) dans le champ (edtHostPort)
...
Cliquer sur le bouton (Connect)
...
L'application se connecte au port série (/dev/pts/11)
...
Le message ci-dessous apparaît
...
MainWindow::ioconnected...

image.png

// Application (En cas d'erreur)
Fermer le port série virtuel > Ctrl + C > dans le terminal
...
Saisir la valeur (SER) dans le champ (edtHostPort)
...
Cliquer sur le bouton (Connect)
...
La connexion au port série (/dev/pts/11) échoue
...
oDevice: Serial: cannot open. Check port:speed settings.
...
Cliquer sur le bouton > OK > OK

image.png

image.png


Lecture des données sur un port série en C++ avec Qt


Nous éditons les fichiers (ParseConnection.cpp), (IoDevice.cpp), (MainWindow.cpp) pour lire des données sur le port série (/dev/pts/8) en C++ avec Qt.


Démarrer le port série virtuel


// Terminal
socat -d -d pty,raw,echo=0 pty,raw,echo=0
...
2024/12/11 17:00:15 socat[397279] N PTY is /dev/pts/8
2024/12/11 17:00:15 socat[397279] N PTY is /dev/pts/9
2024/12/11 17:00:15 socat[397279] N starting data transfer loop with FDs [5,5] and [7,7]
...

Lire des données du port série virtuel


// Terminal
cat /dev/pts/8
...
Démarrage du port série...
Démarrage du port série...
Démarrage du port série...
...

Ecrire des données sur le port série virtuel


// Terminal
echo -ne "Démarrage du port série...\n\r" > /dev/pts/9
echo -ne "Démarrage du port série...\n\r" > /dev/pts/9
echo -ne "Démarrage du port série...\n\r" > /dev/pts/9

Configurer la lecture des données du port série virtuel


// parser/ParseConnection.cpp
...
bool ParseConnection(QString str, ConnectionData &conn)
{
    ...
    else if (parts[0] == "SER")
    {
        conn.port = "/dev/pts/8";
        conn.baud = QSerialPort::Baud4800;
        conn.type = DevSerialConnection;
        return true;
    }
    ...
}
...

// IoDevice.cpp
...
void IoDevice::SERGotData()
{
    QByteArray ba = deviceSER.readAll();
    emit readyRead(ba);
}
...

// MainWindow.cpp
...
void MainWindow::iodevread(QByteArray ba)
{
    qDebug() << "MainWindow::iodevread..."
             << "|data=" << ba;
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
Cliquer sur le bouton (Next page) pour passer à la page de connexion
...
Saisir la valeur (SER) dans le champ (edtHostPort)
...
Cliquer sur le bouton (Connect)
...
L'application se connecte au port série (/dev/pts/8)
...
Le message ci-dessous apparaît
...
MainWindow::ioconnected...
...
L'application attend des messages sur le port série (/dev/pts/8)

image.png

// Terminal
echo -ne "Demarrage du port serie..." > /dev/pts/9
echo -ne "Demarrage du port serie..." > /dev/pts/9
echo -ne "Demarrage du port serie..." > /dev/pts/9
...
L'application reçoit le message sur le port série (/dev/pts/8)
...
Le message ci-dessous apparaît
...
MainWindow::iodevread... |data= "Demarrage du port serie..."
MainWindow::iodevread... |data= "Demarrage du port serie..."
MainWindow::iodevread... |data= "Demarrage du port serie..."

image.png


Configuration du débogage d'un projet C++ Qt sous VSCode


Nous éditons le fichier (.gdbinit) pour configurer le débogage d'un projet C++ Qt sous VSCode.


Configurer le fichier (CMakeLists.txt)


// VSCode (Debug)
Barre de recherche > Show and Run Commands
> CMake: Show Configure Command
Fichier > v03/CMakeLists.txt

Configurer le plugin Qt pour VSCode


// Terminal
cd $HOME
mkdir .qt-vscode
cd .qt-vscode/
...
wget https://raw.githubusercontent.com/KDE/kdevelop/master/plugins/gdb/printers/helper.py
...
wget https://raw.githubusercontent.com/KDE/kdevelop/master/plugins/gdb/printers/qt.py
...

Ouvrir le fichier (.gdbinit)


// Terminal
cd $HOME
touch .gdbinit
nano .gdbinit

Configurer le plugin Qt pour VSCode


// /home/admins/.gdbinit
python
...
import sys, os.path
sys.path.insert(0, os.path.expanduser("~/.qt-vscode"))
...
from qt import register_qt_printers
register_qt_printers (None)
...
end

Exécuter le débogueur


// VSCode (Debug)
Placer des points d'arrêt aux lignes de débogage souhaitées
...
Démarrer le débogueur avec CMake sous VSCode
...
Placer le curseur sur une variable Qt
...
Le contenu de la variable Qt est afficher dans le débogueur

image.png


Calcul du temps écoulé en secondes depuis l'Epoch


Nous éditons les fichiers (PreciseTimer.h), (PreciseTimer.cpp), (MainWindow.h), (MainWindow.cpp) pour calculer le temps écoulé en secondes depuis l'Epoch (01/01/1970) qui marque la date à laquelle le système d'exploitation UNIX a vu le jour.


Calculer le temps écoulé en secondes


// PreciseTimer.h
...
class PreciseTimer
{
public:
    static double QueryTimer();
}; 

// PreciseTimer.cpp
...
double PreciseTimer::QueryTimer()
{
    timeval t1;
    double dt;
    ...
    gettimeofday(&t1, 0);
    ...
    dt = (((double)t1.tv_sec) + ((double)t1.tv_usec) / 1000000.0);
    ...
    qDebug("PreciseTimer::QueryTimer..."
           "|time=%f (sec)",
           dt);
    ...
    return dt;
}

// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
protected:
    void timerEvent(QTimerEvent *event);
    ...
};

// MainWindow.cpp
...
void MainWindow::timerEvent(QTimerEvent *event)
{
    double f = 0.9;
    double t = PreciseTimer::QueryTimer();
    ...
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre principale de l'application apparaît
...
Le temps écoulé depuis l'Epoch s'affiche en secondes

image.png


Calcul de la fréquence de rafraichissement de l'oscilloscope


Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp) pour calculer la fréquence de rafraichissement de l'oscilloscope.


Calculer la fréquence de rafraichissement


// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
protected:
    void timerEvent(QTimerEvent *event);
    ...
Private:
    double time_displayed_last, time_displayed_delta;
    ...
};

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      time_displayed_delta(0),
      ...
{
    ui->setupUi(this);
    ...
    time_displayed_last = PreciseTimer::QueryTimer();
    ...
}
...
void MainWindow::timerEvent(QTimerEvent *event)
{
    double f = 0.9;
    double t = PreciseTimer::QueryTimer();
    time_displayed_delta = f * time_displayed_delta + (1.0 - f) * (t - 
    time_displayed_last);
    time_displayed_last = t;
    ...
    qDebug("MainWindow::timerEvent..."
           "|Display rate: %3.0lf Hz",
           1.0 / time_displayed_delta);
    ...
}
...

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre principale de l'application apparaît
...
La fréquence de rafraichissement de l'oscilloscope apparaît

image.png


Affichage de la fréquence de rafraichissement dans la barre d'état


Nous éditons les fichiers (MainWindow.h), (MainWindow.cpp) pour afficher la fréquence de rafraichissement de l'oscilloscope dans la barre d'état de la fenêtre principale de l'application.


Afficher la fréquence de rafraichissement


// MainWindow.h
...
class MainWindow : public QMainWindow
{
    Q_OBJECT
    ...
protected:
    void timerEvent(QTimerEvent *event);
    ...
Private:
    double time_displayed_last, time_displayed_delta;
    QLabel *displayrateLabel;
    ...
};

// MainWindow.cpp
...
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
      ui(new Ui::MainWindow),
      time_displayed_delta(0),
      ...
{
    ui->setupUi(this);
    ...
    time_displayed_last = PreciseTimer::QueryTimer();
    ...
    displayrateLabel=new QLabel(statusBar());
    statusBar()->addWidget(displayrateLabel);
    ...
}
...
void MainWindow::timerEvent(QTimerEvent *event)
{
    double f = 0.9;
    double t = PreciseTimer::QueryTimer();
    time_displayed_delta = f * time_displayed_delta + (1.0 - f) * (t - 
    time_displayed_last);
    time_displayed_last = t;
    ...
    QString s;
    s = QString::asprintf("Display rate: %3.0lf Hz", 1.0 / 
    time_displayed_delta);
    displayrateLabel->setText(s);
    ...
}
...

Identifier la barre d'état de la fenêtre principale


// MainWindow.ui (Qt Designer)
QStatusBar > Property > objectName  > statusBar
...

image.png

Exécuter le projet


// Terminal
./makes.sh all
...
La fenêtre principale de l'application apparaît
...
La fréquence de rafraichissement de l'oscilloscope apparaît
dans la barre d'état de l'application

image.png


Suite


À suivre...