Décodage de trames avec des tableaux et opérateurs bit-à-bit

On vous propose dans ce TD de mettre en œuvre les tableaux en langage C ainsi que les opérateurs bit-à-bit (et, ou, décalage…​) dans le but de décoder des trames.

🖮 Travail n° 1

On considère dans un 1er temps une trame — constituée d’octets — qui nous est renvoyée par un capteur de température fictif.

Cette trame est constituée de 3 octets :

octet n°1 octet n°2 octet n°3

0x54

[0x00…​0xFF]

[0x00…​0xFF]

Le 1er octet contient 0x54 qui est le code ASCII de la lettre 'T' (comme Température)

Le reste de la trame contient la valeur de la température codée sur 2 octets (→ 0x0000…​0xFFFF) en 1/100ème de °C :

HEX DEC °C

0x0000

0

0.00°C

0x001

1

0.01°C

…​

…​

…​

0xFFFF

65535

655.35°C

  1. Compléter le code de la fonction getTemperature() dans le code source ci-dessous pour qu’elle renvoie (sous forme de float) la valeur de la température contenue dans la trame qui lui est fournie en argument.

#include <stdio.h>
#include <stdint.h>

float getTemperature(uint8_t * frame) {
    // A COMPLETER
}

int main(void) {
    uint8_t sensorFrame[] = {'T', 0x4b, 0x32};

    float tC = getTemperature(sensorFrame);

    printf("t°C = %05.2f\n", tC);

    return 0;
}
Résultat attendu

t°C = 19.25

🖮 Travail n° 2

On considère à présent que la trame est complétée par un octet qui va renseigner :

  1. sur le signe de la température (→ positive ou négative)

  2. l’unité dans laquelle est codée la température

La structure du 4èmeoctet de la trame est la suivante :

b7 b6 b5 b4 b3 b2 b1 b0

Signe :

0 → +

1 → -

RFU*

RFU*

RFU*

RFU*

RFU*

Unité :

01 → °C

10 → °F

11 → °K

* RFU : Reserved for Future Use

  1. Compléter le code de la fonction getTemperature() dans le code source ci-dessous pour qu’elle renvoie sous forme de float la valeur de la température — positive ou négative — contenue dans la trame qui lui est fournie en argument.

    L’argument unit, passé par pointeur, contiendra en sortie de la fonction la lettre associé à l’unité de température codée dans le 4ème octet de la trame ( → ‘C’ si b1b0=01 etc…​).

#include <stdio.h>
#include <stdint.h>

float getTemperature(uint8_t * frame, char * unit) {
    // A COMPLETER
}

int main(void) {
    // Définit un tableau contenant 3 trames
    uint8_t sensorFrames[ ][4] = {
        {'T', 0x0F, 0x07, 0x82}
        , {'T', 0x0f, 0x4b, 0x81}
        , {'T', 0x5b, 0xba, 0x03}
    };

    char deg;
    float t;

    // Parcoure le tableau pour :
    // . extraire la t° contenue dans chaque trame
    // . l'afficher avec son signe et son unité
    for(int i = 0; i < 3; i++) {
        t = getTemperature(sensorFrames[ i ], &deg);

        printf("t = %05.2f°%c\n", t, deg);
    }

    return 0;
}
Résultat attendu

t = -38.47°F

t = -39.15°C

t = 234.82°K

Solution
float getTemperature(uint8_t * frame, char * unit) {
    char units[] = {'?', 'C', 'F', 'K'};

    // On extrait la valeur de la t° de la tramme
    float temp = ((frame[ 1 ] << 8) | frame[ 2 ]) / 100.0;

    // SI le bit 7 est à 1 ALORS
    if( (frame[ 3 ] & (1<<7)) != 0) {
        // Inverser le signe de la t° pour obtenir une t° négative
        temp = -temp;
    }
    // FINSI

    // Décoder l'unité de t° (correspondance directe entre le codage de l'unité
    // et le rang de sa valeur dans le tableau `units`).
    int idxUnits = frame[ 3 ] & 0x03;
    *unit = units[  idxUnits ];

    return temp;
}

🖮 Travail n° 3

On considère enfin que la trame des exercices précédents est complétée par une somme de contrôle (ou checksum en anglais) codée sur un octet.

Le rôle de ce checksum est de vérifier l’intégrité des données c’est-à-dire de vérifier que ces dernières n’ont pas été altérées lors de leur transmission ou de leur stockage.

Plusieurs algorithmes existent pour calculer le checksum mais un des plus simples consiste à faire la SOMME MODULO 256 de tous les octets de la trame puis à ajouter le COMPLÉMENT À 2 du résultat obtenu en fin de trame avant de l’envoyer. Pour valider le message, le récepteur effectue également la SOMME MODULO 256 de tous les octets de la trame, y compris le checksum. Le résultat obtenu doit alors être 0 (⇒ un résultat différent de 0 indique une erreur durant la transmission).

On vous demande de modifier la fonction getTemperature() pour prendre en charge la vérification de la somme de contrôle. Pour cela, on modifie la prototype de la fonction (voir plus loin).

  1. Compléter le code de la fonction getTemperature() dans le code source ci-dessous pour satisfaire à ce qui est indiqué dans sa description (→ commentaire qui la précède).

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h> // pour la définition de la constante NAN

#define FRAME_LENGTH    5

/** (1)
@brief Extrait la température, avec son unité, de la trame issue du capteur fictif.

@param frame tableau contenant la trame
@param temp pointeur sur la variable qui contiendra la valeur de t° en sortie de fonction
@param unit pointeur sur la variable qui contiendra l'unité de t° en sortie de fontion
@return true si la somme de contrôle est correcte.  temp et unit contiennent alors la valeur de la t° et son unité ('C', 'F' ou 'K')
@return false si la vérification de la somme de contrôle échoue. temp et unit contiennent alors respectivement la constante NaN (Not a Number) et '?'.
*/
bool getTemperature(uint8_t * frame, float * temp, char * unit) {

    // Calculer le checksum de la trame (somme modulo 256 de
    // tous les octets de la trame y compris le checksum)

    // SI le checksum vaut 0 ALORS
       // Extraire la t° et l'unité de la trame et les affecter
       // aux variables pointées par 'temp' et 'unit'

       // retourner true
    // SINON
        // Mettre la constante NAN dans la variable pointée par `temp`
        // et '?' dans la variable pointée par `unit`

        // retourner false
    // FINSI
}


int main(void) {
    // Définit un tableau contenant 4 trames dont la dernière
    // contient un mauvais checksum
    uint8_t sensorFrames[ ][ FRAME_LENGTH ] = {
        {'T', 0x0F, 0x07, 0x82, 0x14}
        , {'T', 0x0f, 0x4b, 0x81, 0xd1}
        , {'T', 0x5b, 0xba, 0x03, 0x94}
        , {'T', 0x5b, 0xba, 0x03, 0x95}
    };

    char deg;
    float t;

    // Parcoure le tableau pour :
    // . extraire la t° contenue dans chaque trame
    // . l'afficher avec son signe et son unité
    for(int i = 0; i < sizeof(sensorFrames)/sizeof(uint8_t[FRAME_LENGTH]); i++) {
        bool isOk;
        isOk = getTemperature(sensorFrames[ i ], &t, &deg);
        if(isOk) {
           printf("t = %05.2f°%c\n", t, deg);
        } else {
           printf("Erreur de CRC dans trame n° %d (-> t='%f', deg='%c')\n", i+1, t, deg);
        }
    }

    return 0;
}
1 Dans le code ci-dessus on a commenté la fonction en utilisant des mots clés (→ @brief, @param, @return) qui sont reconnus par un outil nommé Doxygen link. Lorsqu’on traite le fichier source avec Doxygen, ce dernier peut alors générer automatiquement de belles documentations avec différents formats dont HTML.
Résultat attendu

t = -38.47°F

t = -39.15°C

t = 234.82°K

Erreur de CRC dans trame n° 4 (→ t='nan', deg='?')

🖮 Travail n° 4 Application

On vous charge de faire un programme en C capable de décoder les trames renvoyées par le capteur de température/humidité SHT20 de chez Sensirion.

Pour cela, vous devez étudier le document constructeur (ou datasheet).

À la page 8, le document explique que la partie utile de la trame (ou payload) renvoyée par le capteur, c’est-à-dire la partie contenant les informations, est constituée de 3 octets :

  • 2 octets contenant sur les 14 bits de poids fort la valeur de la température ou de l’humidité relevée par le capteur et sur les 2 bits de poids faible des informations d’état :

    • Le bit 1 indique le type de mesure : ‘0’ → température, ‘1’ → humidité

    • Le bit 0 n’est pas utilisé actuellement

  • 1 octet contenant le checksum de la trame

For both modes, since the maximum resolution of a measurement is 14 bit, the two last least significant bits (LSBs, bits 43 and 44) are used for transmitting status information. Bit 1 of the two LSBs indicates the measurement type (‘0’: temperature, ‘1’ humidity). Bit 0 is currently not assigned.

— datasheet SHT20 p.8

La datasheet indique également dans les paragraphes §6.1 et §6.2 les formules à appliquer sur les valeurs brutes renvoyées par le capteur pour obtenir les valeur de températures en °C et de l’humidité en % :

RH = -6 + 125 . Srh / 216 (1)

T = -46.85 + 175.72 . ST /  216 (2)
1 Calcul de l’humidité en % à partir de la valeur brute (Srh)
2 Calcul de la température en °C à partir de la valeur brute (ST)

La datasheet explique aussi que les valeurs brutes à utiliser dans les formules correspondent aux 2 premiers octets de la trame dans lesquels on doit forcer les 2 premiers bits à 0.

  1. Coder la fonction dont le prototype vous est donné ci-dessous et qui doit pour l’instant retourner la valeur de température ou d’humidité codée dans la trame transmise en paramètre (→ frame).

    Rappel : La formule devant être appliquée (calcul température ou humidité) sera déterminée à partir du bit 1 du 2ème octet de la trame.

    float getSHT20Value(uint8_t * frame);
  2. Tester la fonction en l’appelant depuis la fonction main() en lui fournissant plusieurs trames de différents types (température et humidité) pour s’assurer qu’elle renvoie le bon résultat.

    Exemples :
    // trame codant une humidité de 42.5%
    uint8_t frameH[] = {0x63, 0x52};
    
    // trame codant une température de 19.6°C
    uint8_t frameT[] = {0x60, 0xCC};
  3. On vous demande à présent de faire évoluer le code pour intégrer la vérification du checksum de la trame.

    Pour le calcul du checksum, la datasheet du SHT20 vous renvoie vers un autre document.

    Ce document explique comment est calculé le checksum qui est de type CRC-8 mais vous donne également le code C++ correspondant.

    Intégrer ce code dans la fonction getSHT20Value() pour contrôler la valeur du checksum contenu dans la trame avec celui que vous aurez calculé.

    Si le checksum de la trame n’est pas le checksum attendu, la fonction doit alors renvoyée la constante NAN (Not A Number, définie dans math.h)

  4. Tester à nouveau votre fonction en lui fournissant des trames avec des bons et mauvais checksums :

    Exemples :
    // trame codant une humidité de 42.5% avec un checksum correct
    uint8_t frameHChksOK[] = {0x63, 0x52, 0x64};
    
    // trame codant une humidité de 42.5% avec un checksum incorrect
    uint8_t frameHChksNOK[] = {0x63, 0x52, 0xaa};
    
    // trame codant une température de 19.6°C avec un chesksum correct
    uint8_t frameTChksOK[] = {0x60, 0xCC, 0x6F};
    
    // trame codant une température de 19.6°C avec un chesksum incorrect
    uint8_t frameTChksNOK[] = {0x60, 0xCC, 0x6E};

Le CRC-8 peut être calculé grâce au calculateur en ligne CRC Calculator (Javascript) link avec la configuration suivante :

crc8 sht20
Solution
#include <stdio.h>
#include <math.h> // pow(), NAN
#include <stdint.h> // uint8_t...
#include <stdbool.h> // bool

//CRC
#define POLYNOMIAL 0x131 //P(x)=x^8+x^5+x^4+1 = 100110001

float getSHT20Value(uint8_t * frame) {
    float result;

    // Calculer checksum
    uint8_t crc = 0;
    uint8_t byteCtr;
    for (byteCtr = 0; byteCtr < 2; ++byteCtr) {
        crc ^= (frame[ byteCtr ]);
        for (uint8_t bit = 8; bit > 0; --bit) {
            if (crc & 0x80) {
                crc = (crc << 1) ^ POLYNOMIAL;
            } else {
                crc = (crc << 1);
            }
        }
    }
    // SI erreur de checksum ALORS
    if (crc != frame[ 2 ]) {
        // Résultat fonction = NaN
        result = NAN;
    // SINON
    } else {
       // Décoder trame :
       // 1/ Détecter si trame de T°C ou de RH%
       bool isTemperature;
       if ((frame[ 1 ] & 0x02) != 0) { // bit1 est à 1
          isTemperature = false;
       } else {
          isTemperature = true;
       }

       // 2/ Masquer les 2 bits de poids faible pour obtenir valeur "brute"
       uint16_t rawValue = (frame[ 0 ] << 8) | (frame[ 1 ] & 0xFC);
       //   1111 1100
       // & xxxx xxxx
       // -----------
       //   xxxx xx00


       // 3/ Appliquer la formule appropriée
       // SI trame de T°C ALORS
       if (isTemperature == true) {
          // appliquer T = -46.85 + 175.72 . Valeur brute /  2^16
          result = -46.85 + 175.72 * (rawValue / pow(2, 16));
       } else {
       // SINON
          // appliquer RH = -6 + 125 . Valeur brute / 2^16
          result = -6 + 125 * (rawValue / 65536.0);
       }
    }

   // Renvoyer résultat
   return result;
}

int main()
{
    // trame codant une humidité de 42.5% avec un checksum correct
    uint8_t frameHChksOK[] = {0x63, 0x52, 0x64};

    // trame codant une humidité de 42.5% avec un checksum incorrect
    uint8_t frameHChksNOK[] = {0x63, 0x52, 0xaa};

    // trame codant une température de 19.6°C avec un chesksum correct
    uint8_t frameTChksOK[] = {0x60, 0xCC, 0x6F};

    // trame codant une température de 19.6°C avec un chesksum incorrect
    uint8_t frameTChksNOK[] = {0x60, 0xCC, 0x6E};

    // Regrouper les trames dans un tableau pour pouvoir le parcourir
    // avec une boucle
    uint8_t * frames[] = {
        frameHChksOK
        , frameHChksNOK
        , frameTChksOK
        , frameTChksNOK
    };

    float valeur;

    // Parcourir le tableau de trames et afficher pour chacune
    // le résultat de son décodage
    for(int i = 0; i < sizeof(frames)/sizeof(uint8_t *); i++) {
        valeur = getSHT20Value(frames [ i ]);

        printf("Décodage trame n°%d : %+04.1f\n", i+1, valeur);
    }


    return 0;
}

🞄  🞄  🞄