Répondre à la discussion
Affichage des résultats 1 à 7 sur 7

[Projet] Girouette pour bateau (Merci de ne pas interrompre)




  1. #1
    Murayama

    [Projet] Girouette pour bateau (Merci de ne pas interrompre)

    Bonjour!

    Ce qui suit est un petit projet pour créer une girouette pour bateau. Je l'ai déjà écrit
    en C pour une carte TI, recyclage d'un autre programme pour mesurer des distances. Vous
    le trouverez facilement en cherchant "girouette". Comme la carte utilisée n'est plus en
    vente, je porte l'ensemble sur une carte récente. Et comme la dernière fois il n'y avait
    pas d'affichage, je vais ajouter un affichage tout-à-fait standard.

    0. Avant propos

    0.1. Le choix du sujet
    Je suis conscient que parler de C++ à des électroniciens est un sujet à guerre de
    religion. Alors précisons dès maintenant: mon but n'est pas de discréditer ceux qui font
    du C ou de l'assembleur, mais simplement de présenter une autre approche à ceux qui
    n'y ont jamais touché. Ou même à ceux qui font du C mais n'ont jamais eu la curiosité
    de voir ce que fait C++. Un bit étant un bit, qu'il soit écrit en C, C++ ou en assembleur,
    qu'il soit sur un système embarqué, qu'il vienne d'un mac ou d'un pc, c'est toujours un
    bit, alors il n'y a pas lieu de s'entr'engueuler sur le sujet, il y a juste plusieurs approches.
    J'ai choisi un sujet simple, avec peu d'éléments, que n'importe qui peut construire.
    J'utilise une carte bon marché, et en ce qui concerne le soft, j'utilise IAR.
    La version gratuite est limitée à 4K d'exécutable. J'ai l'impression que cela
    suffira largement. Si ce n'est pas le cas, il y a un compilateur chez Texas qui va
    jusqu'à 8k ou 32, je ne sais plus, en version gratuite. Et si ce compilateur est configuré
    en option gcc, je crois que c'est totalement gratuit. On verra si le projet grossit
    un peu trop...

    0.2. Fiabilité
    Inutile de me dire que le code n'est pas "professionnel", il s'agit d'un exemple.
    Ce code n'a pas de "ceintures de sécurité". Exemple: quand on initialise un capteur,
    on doit d'habitude vérifier qu'il est bien initialisé. Mais ici, ce n'est pas le but.
    Donc je n'appelle que la fonction init(), et je ne teste pas, je ne fais que supposer
    que c'est fait. Mais ça rend le code plus court, donc plus clair. La plupart du temps,
    le fonctionnement sera irréprochable.

    0.3. Courtoisie
    Dernier point, un peu de courtoisie serait bienvenue. Ceci est redondant parce que c'est
    déjà dans la charte, mais je rappelle que je ne suis pas payé pour écrire ceci, au
    contraire, j'ai même payé les composants. Alors plutôt que de dire: "je suis l'expert,
    le fait que tu écrives de cette façon montre que tu n'a rien compris", comme récemment,
    je préfèrerais "cette solution fonctionne, mais il serait avantageux de faire xxx xxxx
    pour la raison suivante: <explication détaillée de cette raison>". Et bien sûr, quand on
    fait une suggestion, on ne reste pas les mains dans les poches. Il est donc préférable
    de la tester sur le même hard (qui ne vous coûtera pas grand chose) et de poster le code
    avec les modifications. Dans ce cas, je serai évidemment ravi d'accueillir toute critique
    courtoise, étayée, fondée et construite. S'il n'y a rien de tout cela, il est préférable
    de ne pas commenter, surtout que comme je suis assez primaire, j'ai tendance à répondre
    sèchement quand on vient me les briser (d'autant que je n'en ai pas d'autres). Ce serait
    dommage. Bon, au boulot!

    1. Plan de travail

    Je pars du principe que le lecteur a des notions de programmation en C, sait ce qu'est
    un pointeur, une fonction, un préprocesseur, et a déjà écrit quelques programmes.
    Je vais repartir du programme posté précédemment, avec modification pour qu'il fonctionne
    sur une carte très bon marché (1/2 ~ 1/3 d'Arduino uno pour quelque chose de bien plus
    puissnat. Je parlerai ensuite de découpage de code en sous-ensembles (modules), puis je
    passerai petit à petit à un programme en C++, ce qui me permettra de rétablir les
    contre-vérités suivantes:

    - Mélanger C et C++ est dénaturé
    Je vais vous montrer que cela fonctionne très bien ensemble
    - C++ n'est pas portable et est en plus dépendant du processeur.
    J'ai déjà dit récemment pourquoi c'est portable, voir ANSI C++.
    - C++ ne peut pas travailler intimement avec le hardware
    Je vais montrer que, au contraire, c'est prévu pour cela.
    - C++ n'apporte strictement rien, C++ est une démarche inappropriée, etc,etc...
    Je montrerai ce que cela apporte. Qu'on aime ou pas est une autre chose.

    2. Le hardware
    J'ai choisi une carte LaunchPad de Texas, avec un F5529. Le 5529 a 128k de flash, 8k
    de RAM et peut fonctionner à 25 MHz. Avec en plus une petite section de flash utilisateur
    (512 bytes, je crois) qui permet de stocker des données persistentes (survivant à une
    coupure d'alimentation). Mais ce programme utilisera peut-être 1~2% des capacités de la carte.
    - J'ai pris un LCD tout-venant, un 4x20 caractères. Je préfère les graphiques, mais
    il faut reconnaître que la mise en oeuvre est plus simple.

    Voici une photo du hard. Carte LaunchPad, LCD, potentiomètre pour le contraste, et
    bien sûr, capteur magnétique.

    02Elements.jpg

    Un petit inventaire des pattes disponibles sur la carte Launchpad. Je m'aperçois que
    je l'ai écrit à la façon japonaise: un petit rond signifie oui, une croix signifie non.
    C'est simple pour s'en souvenir: o = oui ou OK, x comme quand le prof de math faisait
    une grande croix rouge sur un devoir. Ça ne vous rappelle pas la prépa?
    Bref, il y avait un seul port complet, le port 3 pour les données du LCD (1). Le port 4
    peut être utilisé pour le SPI, et quelques signaux du port 1 pour les commandes du LCD.

    01PinList.png

    (1) Il est possible d'utiliser le LCD par demi-bytes. J'ai câblé le port complet, c'est
    plus simple à gérer.

    Les noms des pattes sont sérigraphiés sur la carte. Donc par exemple si vous voulez
    câbler MISO, vous verrez dans la table qu'il correspond a port 4, bit 2, et sur la carte,
    vous trouverez P4.2.

    Une photo du câblage. Tout au plus une 20 aine de fils. Mais il manque encore le
    capteur qui n'est pas câblé. Je le ferai plus tard.

    03Connections.jpg

    Voilà une photo du hard, incomplet. Il reste à câbler le capteur. J'ai écrit un pilote
    de LCD vite fait, mais tellement vite fait que j'y mettrai un peu d'ordre avant de le
    lâcher, histoire que l'on ne me jetât pas trop de pierres, elles viennent déjà assez
    facilement même sans ça.

    04Result.jpg

    3. Aperçu des étapes suivantes

    3.1 Dans le deuxième étape, je vais (essayer de) vous présenter un programme complet
    qui lit les données et les affiche. En C. De cette manière, on part de quelque chose
    que la plupart des programmeurs en embarqué connaissent.

    3.2 Dans la 3ème étape, je vais introduire l'encapsulation, le groupement de données
    relatives à une même unité, par exemple le LCD ou le capteur. La classe LCD sera
    créée, et aussi des méthodes surchargées (overloaded), qui permettent d'utiliser
    les mêmes noms de fonctions avec des paramètres différents.

    3.3 Dans la 4ème étape, je vais essayer de mettre un autre LCD pour montrer qu'il
    est possible de créer une classe unique LCD qui gère les deux types. Introduction
    de l'héritage (inheritance). Des méthodes virtuelles.

    Voilà. J'arrête pour ce soir...

    À suivre...

    Pascal

    -----


  2. Publicité
  3. #2
    Murayama

    Re : Girouette pour bateau

    Bonsoir!

    Voici le 2ème épisode. La dernière fois, j'avais ajouté un écran et j'ai écrit le soft que voici.
    Pour l'instant, c'est du C. Bon, je ne vais mettre que ça dans ce message parce que c'est très long.
    J'ai écrit le programme Lundi, il a tourné en continu pendant 1 jour et demi. C'est le seul test que
    j'ai fait...
    Je vais tout de même mettre une photo que je viens de prendre. Elle ne correspond pas tout-à-fait
    au code, j'ai ajouté un symbole "degré" et une remise automatique à 0 au commencement.

    Girouette01.jpg

    L'étape suivante consitera à découper le code en morceaux. Comme nous avons un LCD, un
    capteur, ça fait déjà 2 morceaux évidents. Ensuite, le SPI et le timer sont aussi deux morceaux
    bien distincts. Donc on se retrouve dans le prochain message.

    Pascal

    Code:
    #include "MSP430F5529.h"
    
    
    //    Define commonly used types
    typedef    unsigned char uint8;
    typedef unsigned short uint16;
    typedef unsigned long uint32;
    typedef    signed char int8;
    typedef signed short int16;
    typedef signed long int32;
    
    
    //    A few constants
    #define    CLOCK            1000000
    #define    CLOCK_MHZ        1
    //    LCD related definitions
    #define    LCD_DATA_DIR    P3DIR
    #define    LCD_DATA_OUT    P3OUT
    #define    LCD_CTRL_DIR    P1DIR
    #define    LCD_CTRL_OUT    P1OUT
    //    LCD control signals
    #define    LCD_E            0x10    //    Bit set to 0 for enable
    #define    LCD_CD            0x20    //    Bit set to 0 for command, to 1 for data
    #define    LCD_WR            0x40    //    Bit set to 0 for write, to 1 for read
    #define    EN_DELAY        (2 * CLOCK_MHZ)        //    Delay should be 1200 ns. Set to 2us
    #define CMD_DELAY        (35 * CLOCK_MHZ)    //    Total delay - 37 us
    //    LCD commands
    #define    LCD_CLEAR        0x01    //    Clears the LCD RAM
    #define    LCD_HOME        0x02    //    Move to the home position
    #define    LCD_MODE        0x04    //    Set the mode with the 2 following parameters
    #define    LCD_DIR            0x02    //    Parameter of LCD_MODE
    #define    LCD_SH            0x01    //    Parameter of LCD_MODE
    #define    LCD_ONOFF        0x08    //    Set the display on or off, see following parameters
    #define    LCD_DISP        0x04    //    Parameter of LCD_ONOFF
    #define LCD_CURSOR        0x02    //    Parameter of LCD_ONOFF
    #define LCD_CUR_BLINK    0x01    //    Parameter of LCD_ONOFF
    //    Other defines
    #define    MILLISECOND        1000    //    As default clock is about 1 MHz, 1000 clock shal mean 1 millisecond
    //    definiton of the 4 working modes, combinations of W/R and C/D
    #define    SET_WC()        LCD_CTRL_OUT &=    ~LCD_WR; LCD_CTRL_OUT &= ~LCD_CD    //    Write Command
    #define    SET_WD()         LCD_CTRL_OUT &=    ~LCD_WR; LCD_CTRL_OUT |= LCD_CD        //    Write Data
    #define    SET_RC()        LCD_CTRL_OUT |=    LCD_WR; LCD_CTRL_OUT &= ~LCD_CD        //    Read busy flags & address
    #define    SET_RD()        LCD_CTRL_OUT |=    LCD_WR; LCD_CTRL_OUT &= LCD_CD        //    Read data
    //    SPI related definitions
    #define    SPI_SEL            P4SEL    //    P4SEL allows to change mode from GPIO to SPI
    #define    SPI_CTRL_DIR    P4DIR
    #define    SPI_CTRL_OUT    P4OUT
    #define    SPI_BITS        0x0E    //    0 = CS, 1,2,3 = MOSI, MISO, CLK
    #define    SPI_CS            0x01
    void spi_init(void);
    uint8 spi_write_byte(uint8 b);
    //    Sensor related definitions
    #define    SENSOR_CTRL_DIR    P1DIR
    #define    SENSOR_CTRL_OUT    P1OUT
    #define    SENSOR_RST        0x04
    #define    SENSOR_RQ        0x08
    uint16 read_sensor(void);
    void sensor_init(void);
    //    Function prototypes
    //    LCD related
    void lcd_init(void);
    void lcd_clear(void);
    void lcd_write(char c);
    void lcd_write_str(char* c, uint8 len);
    void lcd_write_cmd(uint8 c);
    void lcd_enable(void);
    void lcd_move_to(uint8 column, uint8 line);
    void make_layout(void);
    //    Timer related
    void timer_init(uint8 ms);
    //    Miscellaneous
    void delay_ms(uint16 ms);
    
    
    void main(void) {
        WDTCTL = WDTPW + WDTHOLD;
        lcd_init();
        make_layout();
        spi_init();
        sensor_init();
        timer_init(1);
        __bis_SR_register(LPM0_bits + GIE);
    }
    
    
    //    The initialization is taken as is, out of the specification sheet. I don't
    //    know why sending 3 times the same command, but indeed, it works.
    //    NB: although it comes from the datasheet, it's bad programming practice.
    //    Never use hardcoded commands.
    void lcd_init(void) {
        lcd_enable();
        //    Start initialization
        delay_ms(40);
        lcd_write_cmd(0x30);
        delay_ms(5);
        lcd_write_cmd(0x30);
        delay_ms(1);            //    Specs say 160us.
        lcd_write_cmd(0x30);
        delay_ms(1);            //    Specs say 160us.
        lcd_write_cmd(0x38);
        lcd_write_cmd(0x10);
        lcd_write_cmd(0x0C);
        lcd_write_cmd(0x06);
        lcd_write_cmd(LCD_CLEAR);
        delay_ms(2);            //    1.52 ms according to specs
    }
    
    
    void lcd_write_at(uint8 what, uint8 col, uint8 line) {
        lcd_clear();
        lcd_move_to(col, line);
        lcd_write(what);
    }
    
    
    void lcd_move_to(uint8 column, uint8 line) {
        uint8 dram_add = 0;
        switch(line) {
        case 0:
            dram_add = column;
            break;
        case 1:
            dram_add = 0x40 + column;
            break;
        case 2:
            dram_add = 0x14 + column;
            break;
        case 3:
            dram_add = 0x54 + column;
            break;
        }
        lcd_write_cmd(0x80 + dram_add);
    }
    
    
    //    This function just sets the ports and their direction for LCD use.
    void lcd_enable(void) {
        //    Enable the 3 LCD control lines
        LCD_CTRL_DIR    |=    (LCD_E | LCD_CD | LCD_WR);
        //    At start, CS should be 1 (disable), then the 2 are 0 in most cases.
        LCD_CTRL_OUT &= ~(LCD_E | LCD_WR | LCD_CD);
        //    Enable data out
        LCD_DATA_DIR = 0xFF;
        LCD_DATA_OUT = 0;
    }
    
    
    void lcd_write(char c) {
        SET_WD();
        LCD_CTRL_OUT |= LCD_E;
        LCD_DATA_OUT = c;
        __delay_cycles(EN_DELAY);
        LCD_CTRL_OUT &= ~LCD_E;
        __delay_cycles(CMD_DELAY);
    }
    
    
    void lcd_write_str(char* c, uint8 len) {
        uint8 i;
        for(i = 0 ; i < len ; ++i) {
            if(c[i] == 0) return;
            lcd_write(c[i]);
        }
    }
    
    
    void lcd_write_cmd(uint8 c) {
        SET_WC();
        LCD_CTRL_OUT |= LCD_E;
        LCD_DATA_OUT = c;
        __delay_cycles(EN_DELAY);
        LCD_CTRL_OUT &= ~LCD_E;
        __delay_cycles(CMD_DELAY);
    }
    
    
    void lcd_write_int16(int16 val) {
        uint8 c, d, u;
        uint8 sign;
        if(val < 0) {
            sign = '-';
            val = -val;
        }
        else {
            sign = ' ';
        }
        c = 0;
        d = 0;
        u = 0;
        while(val >= 100) {
            c++;
            val -= 100;
        }
        while(val >= 10) {
            d++;
            val -= 10;
        }
        u = val;
        lcd_move_to(6, 2);
        lcd_write(sign);    
        lcd_write('0' + c);
        lcd_write('0' + d);
        lcd_write('0' + u);
    }
    
    
    void make_layout() {
        lcd_move_to(3, 0);
        lcd_write_str("Wind direction", 20);
    }
    
    
    void delay_ms(uint16 ms) {
        while(ms > 0) {
            __delay_cycles(MILLISECOND);
            ms--;
        }
    }
    //    Timer initialization (the only time we need to care about the timer is
    //    at initialization. Then it will call interrupts regulrarly.
    void timer_init(uint8 ms) {
        if(ms > 60) ms = 60;
        TBCCTL0 = CCIE;                        //    CCR0 interrupt enabled
        TBCCR0 = (ms * 1000) -1;            //    Timer period (1ms)
        TBCTL = TBSSEL_2 + MC_1 + TBCLR;    //    SMCLK, contmode, clear TBR
    }
    //    SPI initialization
    void spi_init() {
        SPI_SEL |= SPI_BITS;                //    Change the output assignment from GPIO output to SPI
        SPI_CTRL_DIR |= SPI_CS;                //    Set bit to output
        UCB1CTL1 |= UCSWRST;                //    Put SPI engine to reset mode in order to configure it.
        UCB1BRW = 2;                        //    Max possible bit rate for current clock frequency
        UCB1CTL0 = UCSYNC    |                //    SPI is synchronous
            UCMST            |                //    Master mode
            UCMSB;                            //    Transmit most significant bit first.
        UCB1CTL1 |= UCSSEL_2;                //    Select Sub Master clock (SMCLK)
            UCB1CTL1 &= ~UCSWRST;            //    Enable SPI engine
    }
    
    
    //    Simple send with flag polling
    uint8 spi_write_byte(uint8 b) {
        uint8 retval;
        UCB1IFG &= ~UCTXIFG;
        UCB1TXBUF = b;                        //    Send the parameter
        while(!(UCB1IFG & UCRXIFG));        //    Wait until byte is sent (and therefore something is received)
        retval = UCB1RXBUF;                    //    Get received value
        return retval;                        //    return received value
    }
    
    
    //    Get the 2 position bytes from the sensor. CAREFUL !! This function is endianness
    //    depedent. You may want to change to val = val_high << 8 + val_low; But anyway
    //    most systems are little endian.
    uint16 read_sensor(void) {
        int32 acc;
        int16 retval;
        uint8 * retbuf = (uint8 *)(&retval);
        SPI_CTRL_OUT &= ~SPI_CS;
        retbuf[1] = spi_write_byte(0);
        retbuf[0] = spi_write_byte(0);
        SPI_CTRL_OUT |= SPI_CS;
        retval &= 0x03FF;                    //    Return only 10 bits
        acc = 360;
        acc *= retval;
        acc /= 1024;
        retval = acc;
        retval -= 180;
        return retval;
    }
    
    
    void sensor_init(void) {
        SENSOR_CTRL_DIR |= (SENSOR_RST | SENSOR_RQ);
        SENSOR_CTRL_OUT |= SENSOR_RST;
        SENSOR_CTRL_OUT &= ~SENSOR_RQ;
        //    Reset the sensor
        SENSOR_CTRL_OUT &= ~SENSOR_RST;
        delay_ms(10);
        SENSOR_CTRL_OUT |= SENSOR_RST;
    }
    
    
    //    Timer interruption. There is a single interrupt in the whole program,
    //    for timer B0 only. Disabling the interrupt prevents another interrupt
    //    from coming if the processing is not ended.
    #pragma vector=TIMER0_B0_VECTOR
    __interrupt void Timer0B0_interrupt(void) {
        int16 sensorval;
        _DINT();                            //    Avoid nested interrupts
        sensorval = read_sensor();
        lcd_write_int16(sensorval);
        _EINT();                            //    Reenable interrupts
    }

  4. #3
    Murayama

    Re : Girouette pour bateau

    Re!

    Voici le 3ème épisode.
    Donc la girouette, ou disons plutôt le capteur d'angle pour l'instant fonctionne. Comme le code
    gagne à être découpé, je l'ai séparé en paires de fichiers .c / .h.
    Le résultat est ici:
    Girouette01.zip

    1. Mode d'emploi
    - Utilisez l'environnement IAR (il y en a d'autres, mais je l'ai fait avec IAR) EW430.
    EW signifie Embedded Workbench. C'est gratuit jusqu'à 4k d'exécutable, et nous en sommes loin.
    - Créez un projet
    - Copiez les fichiers dans le "workspace" (espace de travail??)
    - Compilez, ça doit fonctionner.

    2. Introduction d'un objet C++
    Avant de commencer, il faut sélectionner "C++" comme langage. Sélectionnez le projet.
    Click droit -> options
    Dans le panneau, choisissez dans Language 1 C++
    Profitez-en pour aller dans optimisations et configurez sur none. Pas d'optimisation, c'est pratique
    pour debugger, parce que les variables de test que vous ajoutez ne sont pas éliminées.

    J'ai choisi de commencer par le capteur (TW11).
    On va le montrer directement, avec syntaxe colorée, ça sera mieux.

    Girouette02_sens.PNG

    Voilà, un objet C++, ça ressemble à ça.

    3. Vocabulaire
    Il y a un jargon spécial qui n'est nullement obligatoire, mais qu'il vaut mieux connaître si vous
    lisez un article sur du C++.
    J'ai nommé le type d'objet de mon capteur TW11Sensor. TW11 parce que le circuit est un TW11, et
    Senor pour capteur. Vous pouvez remarquer qu'il y a des fonctions TW11Sensor() et ~TW11Sensor() à
    l'intérieur de l'objet. Ces fonctions qui portent exactement le même nom que la classe (très important)
    s'appellent le constructeur et le destructeur. Comme leurs noms le suggèrent, quand on instancie
    un objet (c'est à dire quand on déclare qu'on va en utiliser un), le constructeur peut servir à l'initialiser.
    De la même façon, quand on le détruit (pour libérer de la mémoire), le destructeur se charge de
    libérer ce qui a été alloué.
    Par contre, en programmation embarquée, faire de l'allocation dynamique coûte cher en ressources,
    alors sur les petits processeurs comme ce MSP430F5529 qui n'a que 8k de mémoire, on ne fait
    pas d'allocation dynamique.

    Pour résumer, pour utiliser un objet, vous faites comme en C pour utiliser une variable.
    en C:
    int my_variable;
    en C++:
    some_class MyObjectl;

    Dans ce programme, comme il n'y a qu'un seul capteur, je l'ai déclaré extern dans le .h au lieu d'encombrer
    le main. Et l'instanciation elle-même est dans le .cpp.

    Bon, nous en étions au vocabulaire:
    Il y a 3 zones dans la déclaration de la classe:
    public:
    Tout ce qui est dans cette zone peut être accessible extérieurement. Si vous ajoutez
    par exemple dans cette zone une variable MyVariable, alors vous pouvez la copier extérieurement
    en écrivant par exemple: a = MyObject.MyVariable;
    protected:
    Je me le mets sur l'oreille pour plus tard.
    private:
    Tout ce qui est dans cette zone n'est vu directement que par l'objet lui-même.
    Il y a une catégorie de classe spéciale, la friend class, classe amie, qui a le droit de voir
    ce qu'il y a dans privé. C'est un peu comme ce que l'on a dans la culotte, il n'y a que les
    très bonnes amies qui ont le droit de voir. Enfin, je mets amies au féminin, on risque de
    m'accuser d'être sexiste...
    Comme les variables relatives à l'objet lui appartiennent, on parle d'encapsulation.

    Les fonctions s'appellent des méthodes, et les variables internes à l'objet s'appellent des
    membres.

    Voilà. Pendant que j'y étais, j'ai aussi fait un objet pour le LCD.
    Le code est ici.
    Girouette02.zip

    Voilà, vous pouvez le compiler exactement comme avant. Comme il y a plus de fichires, il va falloir
    les ajouter. Click droit dans la zone des projets (en général à gauche si vous n'avez pas changé la mise
    en page). Donc click droit -> add files. Vous prenez tout le contenu de Girouette02.zip, vous le mettez
    dans le "workspace" et vous pouvez compiler. Ça devrait compiler tel quel. S'il y a un problème,
    je repasse de temps en temps, alors écrivez le ici.

    4. Récapitulation des résultats obtenus


    4.1. Ce que l'on gagne


    4.1.1 Parfaite compatibilité de C et C++

    Le fait que j'ai introduit des objets CPP dans un programme C n'a absolument rien changé.
    La compilation s'est passée exactement comme en C et les modules qui sont encore en C ont
    été parfaitement compilés par le compilateur C++. Les langages C et C++ semblent donc
    bien être parfaitement compatibles et intimement liés, contrairement à ce que prétendait
    un expert récemment. Et si vous n'oubliez pas de configurer le compilateur sur C++, tout
    se passera bien. C++ est un ensemble qui inclut C, ce ne sont absolument pas des frères ennemis.

    4.1.2 Parfaite aptitude à parler directement au hardware
    Nous pouvons remarquer que la fonction TW11Sensor::Init() accède directement aux registres
    des périphériques du processeur. Donc contrairement à une 2ème idée reçue, le C++ est
    parfaitement capable de parler au hardware. Les registres ont uniquement été renommés
    par des instructions de préprocesseur #define pour améliorer la portabilité sur un autre
    processeur.
    À propos de portabilité, il y a évidemment en embarqué beaucoup de zones non portables.
    Par exemple, en MSP430, les registrs s'appellent P1, P2, ec... alors qu'en STM32, ils s'appellent
    PA, PB. Donc tous les accès aux registres sont peu portables. Mais ce n'est pas le fait de C++,
    c'est la même chose en C ou en assembleur.
    NB: Comme le MSP430 est un 16 bits, il y a aussi des lettres. Exemple: P1 et P2 forment PA,
    P3 et P4 forment PB, etc...


    4.1.3 Overload (surcharge???)
    La surcharge des fonctions permet d'utiliser le même nom de fonction avec des variables
    différentes. Vous pouvez voir que la fonction Write du LCD est capable d'écrire soit un caractère,
    soit un chaîne. Comment c'est possible? Le compilateur génère 2 fonctions différentes grâce à ce
    qui est appelé le name mangling (aucune idée en français).C'est à dire que pour les différencier, le compilateur va générer une fonction
    __void__LCD20x4__char__, et une fonction __void__LCD20x4__char_ptr__int __
    NB: je ne sais pas si c'est la bonne syntaxe pour les noms, mais c'est l'idée. Ce qui fait que pour
    C++, c'est bien le même nom de fonction vu par l'utilisateur, mais ce sont deux fonctions différentes.

    4.1.4 Valeurs par défaut
    Il est possible de mettre une valeur par défaut pour les méthodes. Par exemple, si vous
    déclarez dans le .h SomeFunction(int a = 30); alors vous pouvez appeler MyObject.SomeFonction()
    sans paramètre, dans quel cas la valeur par défaut est utilisée.

    4.1.5. Sécurité
    Du fait de la structure, les variables sont groupées, et il est plus facile de ne pas les oublier.
    De plus, les variables étant inaccessibles extérieurement, il y a peu de chances de les écraser
    sans le vouloir.

    4.2 Ce que l'on perd.
    Il serait malhonnête d'occulter les inconvénients. J'en vois 2:
    4.2.1. Appel de méthodes légèrement plus long que l'appel d'une fonction.
    Vous avez remarqué dans le code une fonction pulse(). Et dans l'objet LCD, une autre fonction Pulse();
    Dans l'interruption, je les appelle l'une après l'autre, ce qui permet de comparer les temps
    d'exécution. Vu par l'oscillo, on obtient ceci:

    tek0003.png

    On voit deux chose. L'une modérément ennuyeuse, l'appel d'une méthode d'un objet prend plus
    de temps. On perd 2 coups d'horloge. Et au retour de la fonction, il n'y a aucune perte.
    Je ne pense pas m'être trompé en comptant. Si c'est le cas, dites le moi.
    On peut voir par contre que toutes les opérations sur les registres à l'intérieur d'une fonction
    prennent exactement le même temps, que ce soit en C++ ou en C. J'ai vérifié dans les specs du chip,
    et assigner une valeur sur un port, c'est 4 clocks, exactement ce que nous avons sur l'oscilloscope.
    Donc ce serait aussi exactement le même temps en assembleur.
    C'est donc à l'utilisateur de savoir si la perte de ces 2 coups d'horloge est importante ou pas, et si cet
    inconvénient est compensé par ce que l'on gagne.
    Mon opinion est que l'appel avec les processeurs récents (quelques dizaines de MHz) est
    négligeable. Mais évidemment, il ne faut pas faire de surdécoupage de programme. Si vous
    utilisez une fonction "papillon" par exemple dans une FFT(qui est assez gourmande en
    calcul), alors vous allez augmenter un peu le temps de calcul. Mais évidemment personne ne fait ça.

    4.2.2. Volume de l'exécutable.
    Le programme est plus volumineux.
    Mais on doit noter aussi que les objets ne font quasiment rien. Donc la structure est importante
    comparée au contenu. Plus vous ajoutez du code, moins la proportion sera importante.
    Désolé, j'ai oublié de vérifier, mais la version avec mes 2 objets sensor et LCD fait 1.4k d'exécutable.
    Je n'ai jamais utilisé aucun processeur où il n'y aurait pas la place. Dans les processeurs récents, j'ai
    déjà utilisé un MSP430G2231, (2k de flash, 128 bytes de RAM. Il est donc compilable avec la version
    gratuite de IAR (jusqu'à 4K).
    Et d'autre part, il faut savoir aussi que l'héritage (inheritance) permet en quelque sorte
    une factorisation du code et permet de gagner de la place. Là aussi, il faut que l'utilisateur
    juge en fonction de la place disponible en flash.


    5. Conclusion
    On a vu que bien des a priori ne tiennent pas. On a vu que C++ a de nombreux avantages même
    quand il s'agit de le faire fonctionner sur de petits processeurs, et que la perte de vitesse est pour
    ainsi dire négligeable.
    Je vais chercher une astuce pour introduire une caractéristique très importante du C++, l'"inheritance"
    (héritage). Bon, j'y réfléchis.

    À bientôt!

    Pascal


  5. #4
    Murayama

    Re : Girouette pour bateau

    Bonjour!

    Je trouve la présentation du projet intéressante et bien rédigée
    Je suis content que quelqu'un apprécie!
    Je vois en me relisant que j'ai fait une erreur dans le 4.1.2:
    "Par exemple, en MSP430, les registrs s'appellent P1, P2, ec... ". Je voulais dire:
    "Par exemple, en MSP430, les ports s'appellent P1, P2, ec..."

    Concernant le C++ j'aurais une question/remarque : Ne perd-on pas la valeur ajoutée du
    C++ à cause de l'absence d'allocation dynamique de mémoire?
    On perd un peu de l'intérêt du C++ par l'absence d'allocation dynamique, c'est vrai.
    Mais on perd aussi un peu de l'intéret du C tout court par l'interdiction des malloc,
    alors je ne pense pas que ce soit une bonne raison pour ne pas l'utiliser.

    Tout dépend de ce qui est à faire. Naturellement, s'il s'agit d'un thermomètre médical,
    un capteur, un processeur 8 bits ou même 4 (ça existe toujours??), 128 bytes de RAM et
    512 de flash, alors l'intérêt est assez mince.

    Prenons un exemple concret. Le capteur utilisé par la girouette est aussi utilisé
    pour faire des potentiomètres sans contact. Le principe: capteur d'angle -> µP ->
    potentiomètre digital.

    Pour chaque potentiomètre, il faut connaître l'amplitude (ohms), le type (Lin / Log),
    et garder ces variables pour chaque potentiomètre. Il vaut mieux évidemment ne pas
    mélanger les paramètres du potentiomètre 1 avec ceux du potentiomètre 2. En C,
    on est obligé de bien vérifier et tester "à la main" pour être certain que les
    données correspondent bien. En C++, on n'a même pas besoin de vérifier puisque
    chaque objet s'occupe de ses oignons uniquement. C'est une sécurité quasiment gratuite
    moyennant les inconvénients mineurs que j'ai mentionnés.

    J'ai aussi l'impression pour ne pas dire la conviction que C++ force à réfléchir
    quand on définit le .h. On réfléchit bien plus à ce que doit être l'interface
    (public) et ce qui doit être caché, que l'extérieur n'a pas besoin de svoir
    (private).

    Et je n'ai pas encore parlé de l'héritage (inheritance) et des méthodes virtuelles pures
    qui apportent un degré supplémentaire de sécurité. J'y réfléchis.

    Je fais surtout de l'embarqué "bare metal" en C (STM32, PICs et MSP430) mais n'ai jamais
    sauté le pas du C++ car je n'y voyais pas l'intérêt pour mes projets à cause de
    l'impossibilité d'utiliser les allocations de mémoire justement.
    Je suis dans le même cas.
    Sauter le pas est toujours difficile. Programmer en C++ ne se fait pas du jour
    au lendemain. En plus, quand le programmeur a un chef qui tourne en rond derrière sa
    chaise en attendant que le code soit fini, ce n'est pas un contexte propice à se lancer.
    J'ai la chance de ne plus être dans ce cas depuis longtemps.

    La plupart des programmeurs ne font d'ailleurs que du C compilé avec un
    compilateur C++, et je n'y ai pas échappé. Il faut que ça mûrisse. Et c'est pour ainsi
    dire explosif, du moins ça l'a été pour moi. J'ai compris du jour au lendemain ce que
    je pouvais en tirer quand j'ai eu à utiliser des écrans graphiques pour faire des
    menus. Quand on change de contexte, en général, il faut effacer tout l'écran (je parle
    d'écrans dans le genre 128 x 64 ou 160 x 128). C'est le rôle de la classe de base, ce
    qui fait que l'on est presque certain de ne jamais oublier.

    Tiens, ce serait une idée. Montrer un menu manager, avec des boutons, des menus, des
    config stockées en flash, etc....

    Pascal

  6. #5
    Murayama

    Re : Girouette pour bateau

    Bonjour!

    Ce projet que je résume en une mesure d'angle serait-il transposable (évidemment tout ou presque est à réécrire) à des commandes de gouvernes (aéro)?
    Aéromodélisme? Oui, sans aucun problème. Et avec le même processeur, on peut sortir du PWM. Je peux aussi mettre
    un exemple, j'en ai, aussi bien en C qu'en C++

    N'y aurait-il pas un inconvénient, un allongement du temps de réponse consécutif à un code plus volumineux?
    Non, pour plusieurs raisons.
    Le temps de réponse s'allonge de 2 clocks quand on appelle un objet au lieu d'une fonction standard.
    Ici, j'ai utilisé le processeur à 1 MHz, donc 2 clocks, ça fait 2 µs.
    Si l'avion (en admettant que c'en est un) vole à, disons, 50 m/s (ce qui est déjà pas mal, je pense), alors le retard
    induit en matière de distance sera de... 100 µm, soit 0.1mm.

    D'autre part, code plus volumineux, je n'ai hélas pas fait le test de quel est l'accroissement de taille.
    Mais même si la taille augmente, cela ne change pas la vitesse d'exécution du code. Il faudrait que je
    vérifie, je me souviens de processeurs il y a longtemps qui prenaient plus de temps quand on fait un branch
    au dessus d'une certaine taille. Mais je pense que dans les processeurs récents, ce n'est plus le cas.
    À mon avis, ça ne change strictement rien, et quand bien même on perdrait quelques clocks, voir permière
    partie de la réponse, ça se traduit en des déplacements extrêmement petits.

    Et troisième point, si on utilise le processeur à ses capacités nominales (25MHz), alors tout ira 25 fois plus vite.

    On est toujours surpris de la vitesse de l'électronique par rapport à la mécanique. L'autre jour, un client me disait
    que le délai d'un chip était énorme (25µs). Je lui ai fait observer que même si son moteur tourne à fond (6000 rpm),
    alors le délai de 25 µs se traduira par 100 tours / seconde * 0.000025, soit 0.0025 tour. Et 0.0025 tours, c'est
    0.9 degrés.

    Jusqu'à maintenant j'utilise l'assembleur, avec l'inconvénient de n'être pas transposable, ce qui est fort gênant
    lors d'un changement de matériel.


    Comme je le disais, il y a toujours une partie non transposable, mais c'est vrai que quand on peut garder toute
    la structure et séparer intelligemment ce qui est dépendant du matériel, on peut réutiliser beaucoup de choses.

    Pascal


  7. A voir en vidéo sur Futura
  8. #6
    Murayama

    Re : Girouette pour bateau

    Re!

    c'est vrai que repartir de presque zéro en C++ (ce qui n'est pas tout à fait vrai, on a eu droit à un bel exemple de concurrence des deux langages


    Oui, c'est très loin d'un redémarrage de 0. Le plus long, peut-être, c'est de bien réfléchir à la séparation
    de ce qui est public et de ce qui ne l'est pas. Ensuite, une fois que le .h et écrit, copy&paste des fonctions,
    ajout de Object:: devant les noms de fonctions, et ensuite copy&paste de ce qui existe déjà.

    Mais en fait, après quelques programmes, on est quasiment en pilotage automatique, et la façon de découper
    le code devient évidente.

    Pascal


  9. #7
    gienas

    Re : Girouette pour bateau

    Bonsoir à tous

    Il avait été annoncé que ce projet allait être ouvert:

    http://forums.futura-sciences.com/el...ammable-c.html

    Merci de ne pas intervenir ici, avant le déménagement dans la section des projets.

    Cette demande s'adresse aux intervenants autres que l'auteur du projet.


    Tout ce qui n'a pas directement rapport avec le projet va être déménagé dans une discussion parallèle.

    Il est demandé instamment de cesser les attaques de quelque nature que soit.

    Cela a déjà été annoncé et si récidive il doit y avoir, elles seront supprimées et les discussions fermées.

    Merci pour la compréhension et le civisme de tous. Nous ne sommes pas sur un ring.


  10. Publicité

Discussions similaires

  1. [Numérique] Adaptation d'un ensemble anémomètre-girouette bateau à une station météo
    Par djoilba dans le forum Électronique
    Réponses: 33
    Dernier message: 15/04/2016, 22h53
  2. quelle carte electronique pour mon projet ? merci d avance
    Par bullkiki dans le forum Électronique
    Réponses: 11
    Dernier message: 18/03/2013, 11h02
  3. Projet élec : BATEAU SOLAIRE
    Par Fanouilleuh dans le forum Électronique
    Réponses: 14
    Dernier message: 30/01/2011, 20h16
  4. aide pour un petit projet merci
    Par loulou680 dans le forum Électronique
    Réponses: 10
    Dernier message: 09/07/2010, 21h13