Cours:TPheritageIO

De troyesGEII
Aller à : navigation, rechercher

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 mcp23008, possède 8 gpio. Son @i2c est réglable à l'aide de 3 broches (sur la carte utilisée 3 switchs) ce qui permet au maximum d'en utiliser 8 sur le même bus i2c.

On peut donc ajouter jusqu'à 64 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

"Diagramme" de classe

classe abstraite Lampe

Question.jpg Faire une classe Lampe qui:

  • 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

Question.jpg 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 classe GPIO ) ... oui oui dans le constructeur !
  • pour modifier la sortie, on utilisera la méthode setValue de la classe GPIO


Todo.jpg Peut-être le moment de tester un peu tout ça ??

classe LampeI2c

Question.jpg 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
    • 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 I2C1
  • 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)
  • Le constructeur devra permettre d'indiquer :
    • le port i2c à utiliser ( I2C0 I2C1 I2C2 ...)
    • l'@ i2c du composant (valeur entre 0x20 et 0x27 suivant position des 3 switchs A0/A1/A2)
    • le numéro de gpio

Remarques :

  • les registres du mcp23008
    enum regAd {IODIR = 0 ,
                IPOL = 1,
                GPINTEN = 2,
                DEFVAL = 3,
                INTCON = 4,
                IOCON = 5,
                GPPU = 6,
                INTF = 7,
                INTCAP = 8,
                GPIO = 9,
                OLAT = 10,
               };
  • le numéro de gpio (ainsi que l'@ i2c du composant) ne changeant pas, on peut les déclarer comme constante à condition de procéder de la façon suivante (ex pour le numéro de gpio) :
    • déclaration : const int pinMCP;
    • définition :
      LampeI2c::LampeI2c( int _pinMCP, ...): Lampe{parent},pinMCP{_pinMCP} ....
  • pour déclarer la broche en sortie:
    unsigned char value=i2c.readRegister(IODIR);
    value&=~(1<<pinMCP);
    i2c.writeRegister(IODIR,value);
  • pour modifier la valeur de la sortie
    unsigned char value=i2c.readRegister(GPIO);
    if (isAllumee==true) value|= (1<<pinMCP);
    else                 value&=~(1<<pinMCP);
    i2c.writeRegister(GPIO,value);


Todo.jpg 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

Question.jpg Vous pouvez écrire 2 classes Controleur différentes pour mettre en pratique ces 2 approches

Ressources

carte d'extension mcp23008

CarteMCP23008.jpg

Les numéros sérigraphiés sous les boutons poussoirs indiquent les numéros de GPIO.

Les cavaliers JPxx permettent de positionner le GPIO en entrée ou en sortie.

Classe QI2cDevice

qi2cdevice.h

#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

qi2cdevice.cpp

#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