Cours:TPheritageIO
Nous allons utiliser un gpio expander en i2c qui permet d'ajouter facilement un grand nombre d'entrées/sorties sur un système.
Le composant utilisé, un mcp23017, possède 16 gpio. Son @i2c est réglable à l'aide de 3 broches ce qui permet au maximum d'en utiliser 8 sur le même bus i2c.
On peut donc ajouter jusqu'à 128 entrées/sorties en utilisant ce composant.
Nous souhaitons écrire un programme qui permet de piloter des lampes/leds, certaines connectées directement sur les gpio de la carte programmable, d'autres branchées sur le gpio expander.
Dans tous les cas, il s'agit de Lampe
, qu'elle soit une LampeI2c
ou une LampeGPIO
.
Chaque Lampe
présente :
- la propriété ou attribut suivant :
- isAllumee
- les fonctionnalités ou méthodes suivantes :
- allumer
- eteindre
- changer
Sommaire
"Diagramme" de classe
classe abstraite Lampe
- hérite de QObject
- a un attribut protected :
- isAllumee : bool
- a des slots public :
- allumer
- eteindre
- changer
- a une méthode protected
- virtual void setValue(bool etat)
Remarque :
- setValue est la seule méthode qui sera redéfinie dans les classes filles
- du coup il est nécessaire de la déclarer comme virtual
- les méthodes(slots plutôt) allumer/eteindre/changer doivent utiliser la méthode setValue
classe LampeGPIO
Ecrivez maintenant la classe LampeGPIO
:
- elle hérite de
Lampe
- le constructeur doit permettre d'indiquer le numéro de broche
- elle a un attribut protected
- pin de type GPIO
- elle doit redéfinir
setValue
- on ajoute le mot clé override dans la déclaration :
virtual void setValue(bool etat) override
- virtual n'est pas indispensable, il est utile si on souhaite spécialiser la classe
LampeGPIO
- dans la définition de la méthode, on pourra appeler la méthode de la classe mère :
Lampe::setValue(etat)
Remarque :
- il convient de définir la broche en sortie ( méthode
setDirection
de la classeGPIO
) ... oui oui dans le constructeur ! - pour modifier la sortie, on utilisera la méthode
setValue
de la classeGPIO
Peut-être le moment de tester un peu tout ça ??
classe LampeI2c
Et c'est parti pour la dernière classe LampeI2c
:
- elle spécialise également la classe
Lampe
- on ajoute des attributs
- pinMCP : int le numéro du GPIO, par ex 2 si on utilise PA2
- port de type portMCP
- enum portMCP {portA,portB};
- du coup ce sera soit portA soit portB comme valeur
- i2c de type
QI2cDevice
- ça va servir ... à utiliser le bus i2c c'est ça ;-)
- chercher à la fin de l'énoncé, il doit y avoir les fichiers
- sur les cartes raspberry-pi on utilise le bus i2c 1
- il faut évidemment redéfinir
setValue
comme pour la classe LampeGPIO- dans la définition de la méthode, on pourra appeler la méthode de la classe mère :
Lampe::setValue(etat)
- dans la définition de la méthode, on pourra appeler la méthode de la classe mère :
Remarques :
- les registres du mcp23017
enum regAd {IODIRA = 0 ,IODIRB = 1,
IPOLA = 2 ,IPOLB = 3,
GPINTENA = 4 ,GPINTENB = 5,
DEFVALA = 6 ,DEFVALB = 7,
INTCONA = 8 ,INTCONB = 9,
IOCON = 10 ,IOCON_ = 11,
GPPUA = 12 ,GPPUB = 13,
INTFA = 14 ,INTFB = 15,
INTCAPA = 16 ,INTCAPB = 17,
GPIOA = 18 ,GPIOB = 19,
OLATA = 20 ,OLATB = 21
};
- le numéro de port/pin ne changeant pas, on peut les déclarer comme constante à condition de :
- déclaration :
const int pinMCP;
- définition :
LampeI2c::LampeI2c( int _pinMCP, ...): Lampe{parent},pinMCP{_pinMCP} ....
- déclaration :
- pour déclarer la broche en sortie:
regAd regDir;
if (port==portA) regDir=IODIRA;
else regDir=IODIRB;
unsigned char value=i2c.readRegister(regDir);
value&=~(1<<pinMCP);
i2c.writeRegister(regDir,value);
- pour modifier la valeur de la sortie
regAd regGPIO;
if (port==portA) regGPIO=GPIOA;
else regGPIO=GPIOB;
unsigned char value=i2c.readRegister(regGPIO);
if (isAllumee==true) value|= (1<<pinMCP);
else value&=~(1<<pinMCP);
i2c.writeRegister(regGPIO,value);
Vérifier le fonctionnement de cette classe LampeI2c
Utilisation : polymorphisme
Nous allons créer une classe Controleur
qui pourra agir sur n'importe quel type de Lampe
.
Le comportement du Controleur
est libre, par exemple arrêter/mettre en route le clignotement d'un voyant.
Comme le type de Lampe
dépend du Controleur
, nous allons devoir créer un objet de type Lampe
(ou d'une classe fille !) à l'extérieur du Controleur
Le Controleur
doit posséder un attribut pour ce voyant, il peut s'agir :
- d'une référence
- d'un pointeur
Par référence
L'utilisation de références permet d'éviter les erreurs de manipulation de pointeurs, mais peut être impossible dans certaines situations.
Le principe est le suivant :
class Controleur
{
Controleur(Lampe &l);
Lampe & led;
}
|
class Utilisation
{
// attention l'ordre est important,
// on doit créer l'objet de type Lampe avant le Controleur
LampeGPIO l1;
Controleur c1;
}
|
//on doit donner la référence
//avant le début du code du constructeur
Controleur::Controleur(Lampe &l)
:led{l}
{
}
|
//exécution dans l'ordre des
//constructeurs des attributs l1 et c1
//avec passage de la référence sur l1
Utilisation::Utilisation()
:l1{10}, c1{l1}
{
}
|
Par pointeur
L'intérêt des pointeurs est de pouvoir créer des objets lorsque bon nous semble ... et aussi de faire n'importe quoi !
Voici une possibilité :
class Controleur
{
Controleur(Lampe * l);
Lampe * led = nullptr;
}
|
class Utilisation
{
// on ne peut pas créer le Controleur
// avant d'avoir créer une Lampe
// du coup on utilise également un pointeur
Controleur * c1 = nullptr;
}
|
Controleur::Controleur(Lampe * l)
{
// on pointe sur l'objet de type led dont on a passé l'adresse
led = l;
}
|
Utilisation::Utilisation()
{
// on crée un objet de type Lampe
Lampe * l = new LampeGPIO(5);
// on peut ensuite créer un controleur
c1 = new Controleur(l);
}
|
A vous de jouer
Vous pouvez écrire 2 classes Controleur différentes pour mettre en pratique ces 2 approches
Ressources
carte d'extension mcp23008
Les numéros sérigraphiés sous les boutons poussoirs indiquent les numéros de GPIO.
Classe QI2cDevice
#ifndef QI2CDEVICE_H
#define QI2CDEVICE_H
#include <QObject>
enum I2C_BUS{ I2C0, I2C1, I2C2 };
class QI2cDevice : public QObject
{
Q_OBJECT
public:
explicit QI2cDevice(I2C_BUS bus, unsigned char device,QObject *parent = nullptr);
virtual int writeRegister(unsigned int registerAddress, unsigned char value);
virtual int write(unsigned char value);
virtual unsigned char readRegister(unsigned int registerAddress);
virtual bool readRegisters(unsigned char number, unsigned char data[] , unsigned char fromAddress = 0);
private:
virtual void open();
unsigned char bus;
unsigned char device;
unsigned char nbConnectionsErrors;
int file = {-1};
signals:
};
#endif // QI2CDEVICE_H
|
#include "qi2cdevice.h"
#include <string>
#include<fcntl.h>
#include<unistd.h>
#include<sys/ioctl.h>
#include<linux/i2c-dev.h>
using namespace std;
QI2cDevice::QI2cDevice(I2C_BUS bus, unsigned char device, QObject *parent)
: QObject(parent),bus(bus),device(device)
{
open();
}
void QI2cDevice::open()
{
string devname;
if (bus==I2C_BUS::I2C0) devname = "/dev/i2c-0";
else if (bus==I2C_BUS::I2C1) devname = "/dev/i2c-1";
else if (bus==I2C_BUS::I2C2) devname = "/dev/i2c-2";
else throw runtime_error("undefined I2C port !");
// ouverture du périphérique i2c
if((this->file=::open(devname.c_str(), O_RDWR)) < 0)
{
throw runtime_error("unable to open I2C dev file");
}
// connection au composant
if(ioctl(this->file, I2C_SLAVE, this->device) < 0)
{
::close(this->file);
this->file = -1;
throw runtime_error("unable to open I2C port");
}
}
int QI2cDevice::writeRegister(unsigned int registerAddress, unsigned char value)
{
unsigned char buffer[2];
buffer[0] = registerAddress;
buffer[1] = value;
if(::write(this->file, buffer, 2)!=2){
perror("I2C: Failed write to the device\n");
return 1;
}
return 0;
}
int QI2cDevice::write(unsigned char value)
{
unsigned char buffer[1];
buffer[0]=value;
if (::write(this->file, buffer, 1)!=1){
perror("I2C: Failed to write to the device\n");
return 1;
}
return 0;
}
unsigned char QI2cDevice::readRegister(unsigned int registerAddress)
{
this->write(registerAddress);
unsigned char buffer[1];
if(::read(this->file, buffer, 1)!=1){
perror("I2C: Failed to read in the value.\n");
return 1;
}
return buffer[0];
}
bool QI2cDevice::readRegisters(unsigned char number, unsigned char data[] , unsigned char fromAddress)
{
this->write(fromAddress);
if(::read(this->file, data, number)!=(char)number){
perror("IC2: Failed to read in the full buffer.\n");
return false;
}
return true;
}
|
Activer l'i2c sur les rpi
- installer les logiciels ic2detect/i2cset/i2cget
- apt install i2c-tools
- i2cdetect -y 1 (pour scanner les périphériques connectés sur le bus i2c1)
- attention, débrancher la connexion i2c avec l'écran (fils vert et jaune : il ne doit y avoir que les 2 fils d'alimentation)
- activer l'i2c au démarrage
- raspi-config
- interfacing options
- i2c