Vie d'un objet

Bonjour ou bonsoir,

Depuis que je joue avec un STM32 j'ai accès à vrai programme C/C++ avec une fonction main et une boucle while(1). Cela m'a donné envie et permis de manipuler un peu plus ces êtres que l'on appelle des objets.

Pour fixer les idées voici le "blink" à la sauce MBed.

#include "stm32f103c8t6.h"
#include "mbed.h"

Serial      pc(PA_9, PA_10);    //Tx, Rx 

int main() 
{
    confSysClock();     //Configuration du système d'horloges internes  (HSE = 72MHz, USB = 48MHz)
    pc.baud(115200);
    DigitalOut  loupiote(LED1);
    
    while(1) 
    {        
        loupiote = 0; 
        wait_ms(400); 
        loupiote = 1; 
        wait_ms(400); 
        pc.printf("ca marche\r\n");
    }
}

J'ai écris un programme pour tester l'expanseur MCP23S17, c'est un équivalent 16 bits du PCF8574 sauf que j'ai choisi un modèle SPI. La bibliothèque du MCP23S17 a été prise sur Mbed.

J'ai successivement fait des tests qui fonctionnent tous mais ce n'est pas parce que cela à l'air de fonctionner qu'il n'y a pas des chausses-trappes auxquelles j'aurais échappé par hasard.

Je n'aime pas ce qui tombe en marche parce que si ça tombe en panne je ne saurai pas dépanner. Donc merci aux spécialistes de la programmation de regarder ce qui suit.

Test 1 Très classique : objet instancié en global, utilisation dans main() Note j'ai nommé l'objet expenseur U100 du nom de la référence sur mon schéma électrique. J'utilise une sortie sur chaque port 8 bit de l'expenseur, une del avec un courant de 4mA est connectée sur chaque sortie.

#include "stm32f103c8t6.h"
#include "mbed.h"
#include "MCP23S17.h"

SPI spi(PB_15, PB_14, PB_13) ;  //Mosi, Miso, Sclk
Serial      pc(PA_9, PA_10);

char Opcode = 0x40 ;
MCP23S17 U100 = MCP23S17(spi, PA_8, Opcode) ; // --> MCP23S17, U100 est la référence sur mon schéma électrique
DigitalOut  loupiote(LED1);
  
int main() 
{
    confSysClock();     //Configure system clock (72MHz HSE clock, 48MHz USB clock)    
    pc.baud(115200);
        
    spi.frequency(1000000);
    U100.direction(PORT_A, 0x00) ;
    U100.direction(PORT_B, 0x00) ;
      
    while(1) 
    {
        loupiote = 0;       // Del allumée
        U100.write(PORT_A, 0b10000000) ;   //St A7
        U100.write(PORT_B, 0b00000000) ;   //St B0
        wait_ms(200);       
        
        loupiote = 1;       // Del éteinte
        U100.write(PORT_A, 0b00000000) ;
        U100.write(PORT_B, 0b00000001) ;
        wait_ms(200);      
        
        pc.printf("ca marche\r\n");
    }
}

Test 2 Maintenant je n'instancie plus l'objet U100 en global mais dans main() . Cela fonctionne

Test 3 Maintenant je créé une fonction MCP ( mon"loop") avec toujours l'objet instancié dans main() Cela fonctionne a condition de passer l'objet en paramètre dans MCP

#include "stm32f103c8t6.h"
#include "mbed.h"
#include "MCP23S17.h"

SPI spi(PB_15, PB_14, PB_13) ;  //Mosi, Miso, Sclk


char Opcode = 0x40 ;

DigitalOut  loupiote(LED1);

void MCP(MCP23S17 U100)
{
         loupiote = 0;       // Del allumée
        U100.write(PORT_A, 0b10000000) ;   //St A7
        U100.write(PORT_B, 0b00000000) ;   //St B0
        wait_ms(200);       
        
        loupiote = 1;       // Del éteinte
        U100.write(PORT_A, 0b00000000) ;
        U100.write(PORT_B, 0b00000001) ;
        wait_ms(200);      
}
  
int main() 
{
    MCP23S17 U100 =MCP23S17(spi, PA_8, Opcode) ;
    confSysClock();       
        
    spi.frequency(1000000);
    U100.direction(PORT_A, 0x00) ;
    U100.direction(PORT_B, 0x00) ;
  
    while(1) 
    {        
        MCP(U100);
    }
}

Tous ces tests me paraissent normaux j'étais juste curieux de vérifier le passage d'un objet en paramètre. Par contre je me pose des questions pour le dernier T4.

Test 4 Instanciation et utilisation de l'objet U100 dans la fonction MCP qui tourne dans la boucle infinie.

#include "stm32f103c8t6.h"
#include "mbed.h"
#include "MCP23S17.h"

SPI spi(PB_15, PB_14, PB_13) ; 


char Opcode = 0x40 ;
//
DigitalOut  loupiote(LED1);


void MCP(); 
  
int main() 
{
    confSysClock();            
    spi.frequency(1000000);
    
    while(1) 
    {
        MCP();
    }
}

void MCP()
{
    MCP23S17 U100 =MCP23S17(spi, PA_8, Opcode) ;
    
    U100.direction(PORT_A, 0x00) ;
    U100.direction(PORT_B, 0x00) ;
    
    loupiote = 0;       // Del allumée
    U100.write(PORT_A, 0b10000000) ;  
    U100.write(PORT_B, 0b00000000) ;  
    wait_ms(200);       

    loupiote = 1;       // Del éteinte
    U100.write(PORT_A, 0b00000000) ;
    U100.write(PORT_B, 0b00000001) ;
    wait_ms(500);        
}

Cela fait 3 heures de fonctionnement continu sans signe apparent de comportement anormal. Je sais qu'à la sortie d'une fonction une variable déclarée en local est détruite et l'espace mémoire libéré. ~~Je sais ~~ J'ai lu que pour détruire un objet on utilise le destructeur.

Là je n'ai rien fait. L'objet est-il détruit automatiquement à la sortie de la fonction ? Si ce n'est pas le cas faut-il s'attendre à une saturation de l'espace mémoire (ram 20k sur STM32 F103C8T6)

Merci de m'avoir lu et si vous avez des réponses je les lirai avec plaisir.

Bonjour,

Oui, l'objet est détruit en sortie de la fonction. Pour s'en convaincre tu peux mettre une trace (ou faire clignoter une led ) dans le destructeur.

Merci.

Je confirme. A partir du moment ou tu n'utilises par l'allocateur new(), pas de problème, pas besoin d''appeler delete(). Ton objet est alloué sur la pile. D'ailleurs je me demande ce que pourrait donner l'allocation dynamique sur un ARDUINO avec si peu de mémoire RAM et une fréquence d'allocation / libération importante. Fragmentation ? probable. Jamais essayé, même avec malloc / free. Je suis bien trop prudent.

@+

MCP23S17 U100 = MCP23S17(spi, PA_8, Opcode) ;

Cette écriture est redondante - quoique sans effet néfaste. J'aurais écrit :

MCP23S17 U100 (spi, PA_8, Opcode);
void MCP(MCP23S17 U100)
{....}

Là tu passes l'objet par valeur, c'est à dire que tu fais implicitement une copie de l'objet. Sans conséquence dans ton exemple, mais ce peut être désastreux dans d'autres cas... La copie de l'objet sera détruite quand la fonction se termine. Il vaut mieux, en général, passer à la fonction une référence sur l'objet :

void MCP ( MCP23S17& U100 )
{....}

Le code de la fonction ne change pas. Dans le cas où lobjet passé ne doit pas être modifié (c-à-d si la fonction invoque uniquement des méthodes const de l'objet), il est important de le déclarer :

void UneFonction ( const UneClasse& objet_a_ne_pas_modifier ) {....}

L'objet est-il détruit automatiquement à la sortie de la fonction ?

C'est plus général que cela : un objet créé (sur la pile, pas par new) à l'intérieur d'un bloc { } est détruit automatiquement (son destructeur est invoqué) dès la sortie du bloc.

Ok, merci.

Mais quand on trouve une bibliothèque qui n'a pas de destructeur explicite que ce passe-t-il?

Le C++ prévoit-il un destructeur implicite ?

Quand l'objet est détruit, la mémoire est libérée. S'il y a des actions supplémentaires à faire (par exemple libération de mémoire allouée dynamiquement par la classe) on peut les mettre dans le destructeur (qui sera exécuté avant libération mémoire). Le destructeur n'est pas obligatoire, dans ce cas aucune action supplémentaire le sera exécutée à la destruction de la classe.

Merci.

@biggil Quand je fait :

MCP23S17 U100 (spi, PA_8, Opcode);

au lieu de :

MCP23S17 U100 = MCP23S17(spi, PA_8, Opcode) ;

(recopie de la ligne de code que j'ai trouvé dans l'exemple joint à la bibliothèque), j'obtiens l'erreur : Error: No default constructor exists for class "MCP23S17" in "mcp23s17.cpp", Line: 15, Col: 11 Error: Expected a ";" in "mcp23s17.cpp", Line: 15, Col: 16

C'est peut-être du au code de la bibliothèque que je n'ai pas encore joint :

Dans ces fichiers il y a des choses qui me dépassent. Il est vrai que je n'ai pas choisi l'exemple le plus simple : un circuit qui existe sous les formes I2C(MCP23017) et SPI (MCP23S17) et dont le modèle SPI se gère avec une adresse I2C je n'imaginais pas que cela pouvait exister.

Dans l'objet MCP23S17 on transmet un objet spi, une pin pour le Chip Select et "une adresse I2C". Je n'ai pas compris comment la pin pour le CS était gérée : aucune déclaration DigitalOut(?). Tout ce que je sais c'est que quand je retire la connexion PA_8 cela ne fonctionne plus, c'est donc qu'elle est commandée.

Si vous avez une réponse (simple) tant mieux mais c'est plus une remarque qu'une demande, je ne voudrais pas vous monopoliser sur ce point

Pour le fichier d'en-tête :

/* MCP23S17 - drive the Microchip MCP23S17 16-bit Port Extender using SPI
* Copyright (c) 2010 Romilly Cocking
* Released under the MIT License: http://mbed.org/license/mit
*
* version 0.4
*/
#include "mbed.h"

#ifndef  MCP23S17_H
#define  MCP23S17_H

#define INTERRUPT_POLARITY_BIT 0x02
#define INTERRUPT_MIRROR_BIT   0x40

// all register addresses assume IOCON.BANK = 0 (POR default)

#define IODIRA   0x00
#define GPINTENA 0x04
#define DEFVALA  0x06
#define INTCONA  0x08
#define IOCON    0x0A
#define GPPUA    0x0C
#define GPIOA    0x12
#define OLATA    0x14

// Control settings

#define IOCON_BANK  0x80 // Banked registers
#define IOCON_BYTE_MODE 0x20 // Disables sequential operation. If bank = 0, operations toggle between A and B registers
#define IOCON_HAEN  0x08 // Hardware address enable

enum Polarity { ACTIVE_LOW , ACTIVE_HIGH };
enum Port { PORT_A, PORT_B };

class MCP23S17 {
public:
    MCP23S17(SPI& spi, PinName ncs, char writeOpcode);
    void direction(Port port, char direction);
    void configurePullUps(Port port, char offOrOn);
    void interruptEnable(Port port, char interruptsEnabledMask);
    void interruptPolarity(Polarity polarity);
    void mirrorInterrupts(bool mirror);
    void defaultValue(Port port, char valuesToCompare);
    void interruptControl(Port port, char interruptContolBits);
    char read(Port port);
    void write(Port port, char byte);
protected:
    SPI& _spi;
    DigitalOut _ncs;
    void _init();
    void _write(Port port, char address, char data);
    void _write(char address, char data);
    char _read(Port port, char address);
    char _read(char address);
    char _readOpcode;
    char _writeOpcode;
};

#endif

et pour le cpp

/* MCP23S17 - drive the Microchip MCP23S17 16-bit Port Extender using SPI
* Copyright (c) 2010 Romilly Cocking
* Released under the MIT License: http://mbed.org/license/mit
*
* version 0.4
*/

#include "mbed.h"
#include "MCP23S17.h"

MCP23S17::MCP23S17(SPI& spi, PinName ncs, char writeOpcode) : _spi(spi), _ncs(ncs) {
    _writeOpcode = writeOpcode;
    _readOpcode = _writeOpcode | 1; // low order bit = 1 for read
    _init();
}

char MCP23S17::_read(char address) {
    _ncs = 0;
    _spi.write(_readOpcode);
    _spi.write(address);
    char result = _spi.write(0);
    _ncs = 1;
    return result;
}

char MCP23S17::_read(Port port, char address) {
    return _read(address + (char) port);
}

void MCP23S17::_write(char address, char data) {
    _ncs = 0;
    _spi.write(_writeOpcode);
    _spi.write(address);
    _spi.write(data);
    _ncs = 1;
}

void  MCP23S17::_write(Port port, char address, char data) {
    _write(address + (char) port, data);
}

void MCP23S17::_init() {
    _write(IOCON, (IOCON_BYTE_MODE | IOCON_HAEN )); // Hardware addressing on, operations toggle between A and B registers
}

void MCP23S17::direction(Port port, char direction) {
    _write(port, IODIRA, direction);
}

void MCP23S17::configurePullUps(Port port, char offOrOn) {
    _write(port, GPPUA, offOrOn);
}

void MCP23S17::interruptEnable(Port port, char interruptsEnabledMask) {
    _write(port, GPINTENA, interruptsEnabledMask);
}

void MCP23S17::mirrorInterrupts(bool mirror) {
 char iocon = _read(IOCON);
    if (mirror) {
        iocon = iocon | INTERRUPT_MIRROR_BIT;
    } else {
        iocon = iocon & ~INTERRUPT_MIRROR_BIT;
    }
    _write(IOCON, iocon);

}

void  MCP23S17::interruptPolarity(Polarity polarity) {
    char iocon = _read(IOCON);
    if (polarity == ACTIVE_LOW) {
        iocon = iocon & ~INTERRUPT_POLARITY_BIT;
    } else {
        iocon = iocon | INTERRUPT_POLARITY_BIT;
    }
    _write(IOCON, iocon);
}

void MCP23S17::defaultValue(Port port, char valuesToCompare) {
    _write(port, DEFVALA, valuesToCompare);
}

void MCP23S17::interruptControl(Port port, char interruptContolBits) {
    _write(port, INTCONA, interruptContolBits);
}

void MCP23S17::write(Port port, char byte) {
    _write(port, OLATA, byte);
}

char MCP23S17::read(Port port) {
    return _read(port, GPIOA);
}

68tjs: Quand je fait :

MCP23S17 U100 (spi, PA_8, Opcode);

au lieu de :

MCP23S17 U100 = MCP23S17(spi, PA_8, Opcode) ;

j'obtiens l'erreur : Error: No default constructor exists for class "MCP23S17" in "mcp23s17.cpp", Line: 15, Col: 11 Error: Expected a ";" in "mcp23s17.cpp", Line: 15, Col: 16

Bizarre, bizarre. Le fichier mcp23s17.cpp, c'est le tien ou celui de la bibliothèque ? Si c'est celui de la biblio, je ne comprends pas comment une modif dans ton source amène une erreur de compil de la biblio !

La 1ère erreur semble indiquer que tu as écrit qque chose comme :

MCP23S17 U100;

avec le point-virgule juste après le nom de l'objet. Fais voir la ligne 15 du fichier mcp23s17.cpp, si tu veux bien.

Arf !!! J'ai écrit : MCP23S17 U100 MCP23S17(spi, PA_8, Opcode) ; en retirant simplement le signe égal !

Je pense que la bonne syntaxe est MCP23S17 U100(spi, PA_8, Opcode) ;

Je vérifie, Ca compile Quel boulet !, l'électronique c'est plus simple :grin:

Merci d'avoir perdu un peu de temps avec moi.

Super. J'attirais ton attention sur l'écriture :

UneClasse A = UneClasse ( paramètres... );

Car a-priori voilà ce qui se passe: - Un objet, nommé A, de la classe UneClasse est créé sur la pile, en utilisant le constructeur par défaut. Si ce dernier n'existe pas -> erreur. - un objet temporaire, sans nom, est créé en utilisant le constructeur avec paramètres. - l'opérateur d'affectation ( UneClasse& operator= ( const UneClasse & );) est utilisé pour copier l'objet temporaire dans A. Si cet opérateur d'affectation n'est pas fourni, on utilise la copie membre à membre. - Il nous reste "à la main" un objet devenu inutile. Le compilateur le détruira (en invoquant son destructeur s'il existe) quand ça lui chante !

Ca fait beaucoup de boulot et plein plein de pièges pour des objets complexes. Heureusement que le compilateur est intelligent, et qu'il comprend que tout cela n'est pas nécessaire: il créera donc un seul objet A avec le bon constructeur (avec paramètres).

Par contre il fera tout le binz que j'énumère si on écrit :

UneClasse A;
A = UneClasse ( paramètres... );

Subtile différence d'écriture, mais grosse différence de comportement !

La formulation que tu employais fait donc se poser des question inutiles au programmeur ... sortir le bouquin de C++ pour vérifier que tout ça est prévu ... autant employer la formulation courte :)