La communication série
Présentation
Qt fournit un interfaçage complet d'une liaison série à travers 2 classes :
QSerialPort
qui hérite deQIODevice
pour les opérations d'E/SQSerialPortInfo
Afin de pouvoir utiliser ces classes, il est nécessaire de joindre à notre projet de développement un module Qt additionnel : serialport
.
Méthode : Ajouter le module Qt serialport
#-------------------------------------------------
#
# Project created by QtCreator 2016-02-27T15:26:58
#
#-------------------------------------------------
QT += core gui serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = ditRasp
TEMPLATE = app
target.path = /home/pi
INSTALLS += target
SOURCES += main.cpp\
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 :
QList<QSerialPortInfo> listPsi; // liste des ports série existant
listPsi = QSerialPortInfo::availablePorts(); // récupère les ports série disponibles
for(int i=0 ; i<listPsi.size() ; i++)
qDebug() << "CCommuniquer : " << listPsi.at(i).portName() << " " << listPsi.at(i).description();
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ément : Mé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 :
// ouverture de la liaison data
mPs = new QSerialPort(this);
mPs->setPortName("/dev/ttyUSB0");
mPs->setBaudRate(QSerialPort::Baud9600);
mPs->setDataBits(QSerialPort::Data8);
mPs->setParity(QSerialPort::NoParity);
mPs->setStopBits(QSerialPort::OneStop);
mPs->setFlowControl(QSerialPort::NoFlowControl);
if (mPs->open(QIODevice::ReadWrite)) {
mPs->setRequestToSend(false);
qDebug() << "Connected to ttyUSB0";
connect(mPs, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
} else
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
.
void CCommuniquer::onReadyRead()
{
QByteArray car;
int nb = mPs->bytesAvailable();
for(int i=0 ; i<nb ; i++) {
car = mPs->read(1); // exemple de lecture d'un caractère
qDebug() << "CCommuniquer::onReadyRead: Lecture d'un caractère : " << car;
}
}
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.
Attention : Signal 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ément : Cas 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.
Pour désactiver le Bluetooth, il faut modifier le fichier /boot/config.txt
en indiquant :
dtoverlay=pi3-disable-bt
Il faut aussi modifier le fichier /boot/cmdline
en supprimant :
console=serial0,115200
Exemple : Classe de gestion d'une liaison série
Cette classe facilite l'utilisation de la classe QSerialPort
.
Voici CRs232c.h
:
// 3s timeout réception
class CRs232c : public QObject
{
Q_OBJECT
public:
explicit CRs232c(QObject *parent = 0, const QString &nomPort = "/dev/ttyUSB0");
~CRs232c();
int initialiser(QSerialPort::BaudRate vitesse, QSerialPort::DataBits data,
QSerialPort::Parity parity, QSerialPort::StopBits nbStop,
QSerialPort::FlowControl flow);
int ouvrirPort();
char ecrire(const char *trame, int nbOctets);
private:
QSerialPort *m_Sp;
QObject *m_parent;
signals:
void sigErreur(QSerialPort::SerialPortError err);
void sigData(QByteArray ba);
public slots:
void onReadyRead();
void onErreur(QSerialPort::SerialPortError err);
};
// 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 :
// crs232c.cpp
CRs232c::CRs232c(QObject *parent, const QString &nomPort)
{
m_parent = parent;
m_Sp = new QSerialPort(parent);
m_Sp->setPortName(nomPort);
connect(m_Sp, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
connect(m_Sp, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onErreur(QSerialPort::SerialPortError)));
qDebug() << "L'objet CRs232c est créé par " << m_parent->thread();
}
CRs232c::~CRs232c()
{
m_Sp->close();
delete m_Sp;
qDebug() << "L'objet CRs232c est détruit par " << m_parent->thread();
}
int CRs232c::initialiser(QSerialPort::BaudRate vitesse, QSerialPort::DataBits data,
QSerialPort::Parity parity, QSerialPort::StopBits nbStop,
QSerialPort::FlowControl flow)
{
m_Sp->setBaudRate(vitesse);
m_Sp->setDataBits(data);
m_Sp->setParity(parity);
m_Sp->setStopBits(nbStop);
m_Sp->setFlowControl(flow);
return 0;
}
int CRs232c::ouvrirPort()
{
bool res=false;
res=m_Sp->open(QIODevice::ReadWrite);
if (!res) {
m_Sp->close();
} // if res
return m_Sp->isOpen();
}
char CRs232c::ecrire(const char *trame, int nbOctets)
{
int lg = m_Sp->write(trame, nbOctets);
if ( lg < nbOctets) {
return ERREUR;
} // if erreur
m_Sp->waitForBytesWritten(2000); // stop le thread actuel pour attendre max 2s écriture
return lg;
}
void CRs232c::onReadyRead()
{
QByteArray ba;
ba = m_Sp->readAll();
emit sigData(ba);
}
void CRs232c::onErreur(QSerialPort::SerialPortError err)
{
emit sigErreur(err);
}
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.
Attention : Lecture/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.