La communication série

Présentation

Qt fournit un interfaçage complet d'une liaison série à travers 2 classes :

  • QSerialPort qui hérite de QIODevice pour les opérations d'E/S

  • QSerialPortInfo

Afin de pouvoir utiliser ces classes, il est nécessaire de joindre à notre projet de développement un module Qt additionnel : serialport.

MéthodeAjouter le module Qt serialport

La documentation (extrait de l'image ci-contre) nous permet de connaître le nom du module à ajouter à notre projet : serialport.

Pour cela, il suffit de l'ajouter dans le fichier de projet de notre application (.pro) selon l'exemple ci-dessous :

Extrait de la documentation
Extrait de la documentation de QSerialPortInfo
1
#-------------------------------------------------
2
#
3
# Project created by QtCreator 2016-02-27T15:26:58
4
#
5
#-------------------------------------------------
6
7
QT       += core gui serialport
8
9
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
10
11
TARGET = ditRasp
12
TEMPLATE = app
13
14
target.path = /home/pi
15
INSTALLS += target
16
17
SOURCES += main.cpp\
18
        mainwindow.cpp \

QSerialPortInfo

La classe QSerialPortInfo

Cette classe fournit des informations concernant les ports séries existants.

Une méthode statique availablePorts() permet de rapatrier les informations concernant les ports séries existants.

L'exemple de code ci-dessous obtient la liste des ports série disponibles sur l'ordinateur :

1
    QList<QSerialPortInfo> listPsi;  // liste des ports série existant
2
    listPsi = QSerialPortInfo::availablePorts();  // récupère les ports série disponibles
3
    for(int i=0 ; i<listPsi.size() ; i++)
4
        qDebug() << "CCommuniquer : " << listPsi.at(i).portName() << " " << listPsi.at(i).description();
5

QList est une classe permettant de stocker toutes sortes d'objets. Dans notre cas, ce sera des objets de type QSerialPortInfo.

Cet exemple ci-dessus permet d'afficher le nom de toutes les voies séries disponibles sur la console de débogage.

ComplémentMéthode statique en C++

C'est une méthode qui est indépendante de tout objet. Elle s'appelle directement en faisant précéder du nom de la classe qui la contient suivi de l'opérateur d'accès (: :).

C'est utile lorsqu'un fonctionnement commun à tous les objets est requis. D'autres classes Qt disposent de méthodes statiques (ex : QSqlDatabase).

QSerialPort

La classe QSerialPort

Cette classe fournit toutes les fonctionnalités de contrôle d'une liaison série.

Considérons l'exemple de code ci-dessous :

1
    // ouverture de la liaison data
2
    mPs = new QSerialPort(this);
3
    mPs->setPortName("/dev/ttyUSB0");
4
    mPs->setBaudRate(QSerialPort::Baud9600);
5
    mPs->setDataBits(QSerialPort::Data8);
6
    mPs->setParity(QSerialPort::NoParity);
7
    mPs->setStopBits(QSerialPort::OneStop);
8
    mPs->setFlowControl(QSerialPort::NoFlowControl);
9
    if (mPs->open(QIODevice::ReadWrite)) {
10
        mPs->setRequestToSend(false);
11
        qDebug() << "Connected to ttyUSB0";
12
        connect(mPs, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
13
    } else
14
        qDebug() << "Impossible to connect to ttyUSB0";

Ces lignes de code permettent d'ouvrir le port série ttyUSB0 en lecture/écriture, de fixer les paramètres de communication, le protocole de gestion de flux et même de commander individuellement la ligne RTS (d'autres sont possibles).

Ci-dessous un exemple de méthode onReadyRead() d'une classe CCommuniquer qui est le slot de réception du signal ReadyRead() de l'objet QSerialPort.

1
void CCommuniquer::onReadyRead()
2
{
3
    QByteArray car;
4
    int nb = mPs->bytesAvailable();
5
    for(int i=0 ; i<nb ; i++) {
6
        car = mPs->read(1); // exemple de lecture d'un caractère
7
        qDebug() << "CCommuniquer::onReadyRead: Lecture d'un caractère : " << car;
8
    }
9
}

La méthode bytesAvailable() renvoie le nombre d'octets disponibles dans la zone mémoire tampon de réception de la voie série.

Dans cet exemple, les caractères sont lus un par un et affichés sur la console de débogage.

AttentionSignal readyRead

Ce signal est généré par l'objet port série dès qu'un ou plusieurs caractères sont arrivés par le port série.

Tant que les caractères arrivés ne sont pas lus (extraits du buffer de réception), le signal ne sera pas réémis, même si entre temps, de nouveaux caractères sont arrivés.

Il faut donc être particulièrement vigilant et organisé pour gérer la liaison série avec cet événement.

ComplémentCas particulier de la Raspberry 3

Cette carte a subi une évolution dans sa conception.

Le port série est utilisé pour piloter le Bluetooth. De ce fait, il n'est pas possible de l'utiliser sans désactiver le Bluetooth.

Il existe bien un autre port série appelé "mini port série" mais il n'est pas très fiable du fait qu'il dépend de l'horloge variable du processeur. La vitesse de transmission n'est donc pas du tout fiable. Il est déconseillé de l'utiliser.

Voir l'article sur Internet.

Et aussi celui-là.

Pour désactiver le Bluetooth, il faut modifier le fichier /boot/config.txt en indiquant :

1
dtoverlay=pi3-disable-bt

Il faut aussi modifier le fichier /boot/cmdline en supprimant :

1
console=serial0,115200

ExempleClasse de gestion d'une liaison série

Cette classe facilite l'utilisation de la classe QSerialPort.

Voici CRs232c.h :

1
#ifndef CRS232C_H
2
#define CRS232C_H
3
4
#include <QSerialPort>
5
#include <QTimer>
6
#include <QDebug>
7
#include <QThread>
8
9
#ifndef ERREUR
10
   #define ERREUR (char)-1
11
#endif
12
#define OK (char)0
13
#define TO 3000  // 3s timeout réception
14
15
class CRs232c : public QObject
16
{
17
    Q_OBJECT
18
19
public:
20
    explicit CRs232c(QObject *parent = 0, const QString &nomPort = "/dev/ttyUSB0");
21
    ~CRs232c();
22
    int initialiser(QSerialPort::BaudRate vitesse, QSerialPort::DataBits data,
23
                    QSerialPort::Parity parity, QSerialPort::StopBits nbStop,
24
                    QSerialPort::FlowControl flow);
25
    int ouvrirPort();
26
    char ecrire(const char *trame, int nbOctets);
27
28
private:
29
    QSerialPort *m_Sp;
30
    QObject *m_parent;
31
32
signals:
33
    void sigErreur(QSerialPort::SerialPortError err);
34
    void sigData(QByteArray ba);
35
36
public slots:
37
    void onReadyRead();
38
    void onErreur(QSerialPort::SerialPortError err);
39
};
40
41
#endif // CRS232C_H

Cette classe gère 2 signaux pour faciliter les échanges avec les autres objets :

  • sigErreur() pour informer d'une erreur liée au port série.

  • sigData() pour communiquer les données reçues.

Notez que la méthode lire() n'existe pas car elle est remplacée par l'émission du signal sigData().

Ci-dessous, le fichier CRs232c.cpp implémente les méthodes de la classe :

1
// crs232c.cpp
2
#include "crs232c.h"
3
4
CRs232c::CRs232c(QObject *parent, const QString &nomPort)
5
{
6
    m_parent = parent;
7
    m_Sp = new QSerialPort(parent);
8
    m_Sp->setPortName(nomPort);
9
    connect(m_Sp, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
10
    connect(m_Sp, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onErreur(QSerialPort::SerialPortError)));
11
    qDebug() << "L'objet CRs232c est créé par " << m_parent->thread();
12
}
13
14
CRs232c::~CRs232c()
15
{
16
    m_Sp->close();
17
    delete m_Sp;
18
    qDebug() << "L'objet CRs232c est détruit par " << m_parent->thread();
19
}
20
21
int CRs232c::initialiser(QSerialPort::BaudRate vitesse, QSerialPort::DataBits data,
22
                         QSerialPort::Parity parity, QSerialPort::StopBits nbStop,
23
                         QSerialPort::FlowControl flow)
24
{
25
    m_Sp->setBaudRate(vitesse);
26
    m_Sp->setDataBits(data);
27
    m_Sp->setParity(parity);
28
    m_Sp->setStopBits(nbStop);
29
    m_Sp->setFlowControl(flow);
30
    return 0;
31
}
32
33
int CRs232c::ouvrirPort()
34
{
35
    bool res=false;
36
    res=m_Sp->open(QIODevice::ReadWrite);
37
    if (!res) {
38
        m_Sp->close();
39
    } // if res
40
    return m_Sp->isOpen();
41
}
42
43
char CRs232c::ecrire(const char *trame, int nbOctets)
44
{
45
    int lg = m_Sp->write(trame, nbOctets);
46
    if ( lg < nbOctets) {
47
        return ERREUR;
48
    } // if erreur
49
    m_Sp->waitForBytesWritten(2000); // stop le thread actuel pour attendre max 2s écriture
50
    return lg;
51
}
52
53
void CRs232c::onReadyRead()
54
{
55
    QByteArray ba;
56
    ba = m_Sp->readAll();
57
    emit sigData(ba);
58
}
59
60
void CRs232c::onErreur(QSerialPort::SerialPortError err)
61
{
62
    emit sigErreur(err);
63
}

La méthode slot onReadyRead() lit tous les caractères arrivés et les transmet par le signal sigData().

Le rôle de cette classe n'est pas de gérer un protocole. Pour cela, une classe de plus haut niveau connectera le signal sigData() afin de gérer le protocole du périphérique série.

AttentionLecture/Ecriture avec QSerialPort

Les opérations de lecture/écriture (read(), write())de la classe QSerialPort hérite de QIODevice.

La documentation indique que ces méthodes sont asynchrones.

Cela signifie qu'une écriture sur le port série n'est effective qu'en sortie de la méthode appelante.

Pour forcer l'écriture sur le port avant la sortie de la fonction, il faut utiliser la méthode waitForBytesWritten(2000), comme dans la méthode CRs232c : :ecrire().

Au bout de 2000 ms, si l'écriture n'a pas eu lieu, la méthode renvoie la valeur false sinon true et le signal bytesWritten() est émis.