Programme de l’oscilloscope.

Particularités apportées à l’oscilloscope expérimental.

Ceux modifications notables vont améliorer la version initiale, outre l’aspect graphique sur lequel nous ne reviendrons pas. La première vise à augmenter la rapidité d’échantillonnage. Cette éventualité avait déjà été évoquée. Dans la version du MINI LABORATOIRE, les échantillons étaient sauvegardés dans une table de 128 OCTETs. Chaque mesure du CAN comprise entre [0 et 1023] était transposée entre [1 et 16] par la ligne source :
Tension_lue = map(analogRead(1), 0, 1023, 1, 16);
Cette instruction prend du temps et limite la cadence des saisies de valeurs.
Pour des graduations « en largeur » de la base de temps, chaque « colonne » fait 12 pixels.
Le nombre total d’échantillons sera donc de 12 x 10 = 120 OCTETs. (Au lieu de 128)
Comme la définition de l’écran OLED est bien meilleure, la transposition de valeurs sera effectuée entre [0 et 254] pour bénéficier de l’information la plus importante possible sur un byte. Nous pourrions utiliser l’instruction : Tension_lue = map(analogRead(1), 0, 1023, 0, 254);
Mais à y regarder de plus près, on peut voir qu’en fait ce traitement revient à diviser la valeur issue du CAN par quatre. Hors diviser par quatre peut se faire en décalant Tension_lue deux fois à droite. L’instruction chargée de cette opération est :
MEMOIRE_ECHANTILLONS[PTR] = analogRead(1) >> 2;
Ce codage plus compact est plus rapide à réaliser par le microcontrôleur. On double pratiquement la vitesse d’échantillonnage. Du coup, la valeur la plus faible de la base de temps reste 2mS, mais cette fois ce laps de temps englobe 12 échantillonnages.

Table des délais pour la BASE de TEMPS.

Construire la base de temps revient à confier dans un tableau la valeur des délais qui seront imposés au microcontrôleur entre deux stockages de données numérisées. Le principe du calcul des valeurs de temporisation est relativement simple et résumé dans l’encadré ci-dessous.

x

x

x

x

x

x

x
On arrondi les valeurs à trois chiffres significatifs et l’on obtient le tableau :

Le programme doit alors calculer à partir de la base de temps désirée, le délai T4 en µS qui ajustera avec précision le temps passé entre deux saisies d’échantillons. La formule de calcul est simple :


Les divers essais destinés à mesurer le temps de traitement pour (T1 + T2 + T3 + T5 + T6) ont donné comme résultat 110µS. Comme les constantes dans Valeurs_BT sont consignées en mS, le calcul de TBT devient :


Dans le programme, le pointeur PTR est représenté par l’identificateur Ordre_BT, qui correspond mieux à la nature réelle de cet index. Cette valeur est calculée hors boucle d’échantillonnage pour optimiser la rapidité des captures numériques. Pour la première valeur, la théorie conduit à 2mS :
Valeur_BT = 2/ 12 = 166.666µS
En arrondissant on devrait coder 167 comme première constante du tableau. Hors la consultation du listage montre une valeur plus élevée de 3µS. Ce sont les essais poussés en détails qui ont montré la nécessité de ce petit correctif logiciel.
Le tableau float Valeurs_BT[18] contient les valeurs des délais entre chaque saisie d’un échantillon. Du point de vue pratique, ce qu’il faut afficher à l’écran dans les paramètres d’initialisation, c’est la durée que représente chaque graduation verticale qui est composée de 12 pixels, donc de 12 échantillons. Le calcul de la durée à afficher pour la base de temps est élémentaire :
display.print((Valeurs_BT[Ordre_BT] * 12),0);
Le ,0 impose à l’instruction display.print de n’afficher aucune décimale, car sans cette directive, par défaut les réels sont présentés avec deux chiffres « après la virgule ».

Détermination du temps exigé par (T1 + T2 + T3 + T5 + T6).

Deux possibilités s’offrent à nous : Soit on « descend au temps processeur » et l’on calcule la durée de toutes les instruction situées dans la boucle, soit pour affiner la valeur de la constante d’ajustement de la base de temps on injecte en E  un étalon de fréquence en signal carré de 50Hz et on modifie la constante NNN jusqu’à afficher exactement une période sur la largeur complète de l’écran. Pour affiner passer en double définition et sans grille. (On voit mieux la trace sur le dernier pixel.) L’ajustement agissant sur 120 échantillons, au final on sera à 2 « pixels » d’écart sur l’écran complet, dont un caché par le cadre des graduations. En théorie on aurait 102µS, mais il faut passer à 110 pour compenser le temps consommé dans l’appel à la procédure delayMicroseconds(TBT).
Pour confirmer le bienfondé de la valeur NNN adoptée, on combine alors diverses fréquences étalon et toutes les possibilités de la base de temps. On peut ensuite faire confiance à la grille d’écran.

Le gain automatique.

Caractéristique incontestablement très intéressante, cette particularité permet d’avoir une précision jusqu’à quatre fois plus grande par rapport à un gain fixe qui limiterait l’amplitude verticale à la plage comprise entre [0 et +5V]. La numérisation étant effectuée entre 0 et 254 alors que verticalement on dispose que de 64 pixels, il faut diviser la valeur numérisée par 4. Supposons que le signal présente une amplitude maximale de 2,5V. La trace occupera en hauteur la moitié de l’écran. Si maintenant la plus grande tension reste inférieure à 1,25V la trace sur l’écran sera très « plate » et manquera singulièrement de relief. Pour exploiter au mieux la définition du graphe, il suffit de ne diviser que par deux les valeurs si l’amplitude est inférieure à 2,5V et de l’utiliser directement si les échantillons ne dépassent pas 64. Dans le premier cas la hauteur de l’écran représente 2,5V et chaque niveau est égal à 0,5V. Tout se passe comme si nous avions un gain de x2. Si les échantillons sont utilisés directement pour tracer la courbe, la hauteur totale vaudra 1,25V et chaque niveau sera équivalent à 0,25V avec un gain de x4.
Pour émuler cette caractéristique très séduisante, la numérisation d’un groupe d’échantillons est suivie d’un calcul du gain à appliquer au moment de tracer la courbe. La méthode est résumée sur la Fig.81 qui établit la relation qui existe entre la valeur numérisée et la limitation de l’écran à 64 pixels. On teste les 120 échantillons. Il suffit qu’un seul dépasse 127 pour imposer le gain unitaire. (Diviser les valeurs par quatre pour tracer les points lumineux du graphe.) Si aucun de dépasse 127 on peut pousser l’amplification à x2 ce qui revient à ne diviser les valeurs que par deux. Enfin si l’intégralité des numérisations reste en dessous de 65, on utilisera directement les valeurs pour illuminer les points et l’amplification sera réputée x4.

La Fig.82 est issue d’une capture d’échantillons en appliquant sur E un signal alternatif d’amplitude faible. Les « pointes » négatives sont « rabotées » à la valeur zéro par le convertisseur analogique numérique de l’ATmega328. L’amplitude maximale sur la totalité des valeurs mémorisées fait environ +1,7V. Numériquement le CAN a délivré des valeurs crêtes de 348, qui divisées par quatre ont été enregistrées à 87. Comme aucun des échantillons ne dépasse le seuil de 127, le gain calculé est de x2. Pour présenter les pixels sur le graphe, le logiciel procède donc à une division par deux ce qui donne en hauteur 43 ou 44 pixels en fonction de l’arrondi pratiqué ou non lors de l’affichage.

Valeur efficace de l’onde enregistrée.

Cette notion théorique a déjà été abordée et détaillée dans le chapitre Utiliser le MINI LABORATOIRE dont la Fig.11 donne divers exemples concrets. Un parallèle est effectué sur la Fig.83 à associer directement à la Fig.82 dont elle reprend le signal numérisé. La « surface » du rectangle violet est égale à la surface située « sous la courbe » et coloriée en rose. La valeur efficace revient à faire la somme des grandeurs des 120 échantillons et à en déduire la moyenne.
ATTENTION : La valeur calculée par le programme ne correspondra à une valeur efficace du signal que si la capture contient un nombre entier d’alternances complètes. (Nombre entier de PÉRIODES.) Si par exemple il y a deux « pics » positifs et un seul « creux », la valeur calculée sera surévaluée.
Pour achever ce paragraphe, nous allons examiner quelques exemples typiques, tous issus d’une onde presque sinusoïdale de fréquence 50Hz. Sur la Fig.84 le gain est toujours de x2 et l’amplitude du signal oscille entre +0,2V et +2,25V. La valeur moyenne pour cette capture est d’environ 1,24V. La composante continue est légèrement supérieure à la valeur crête du signal alternatif. Sur la Fig.85 la tension alternative fait environ 2,8V crête à crête. Le seuil de synchronisation est de +4V. La composante continue ajoutée est d’environ +2,6V. Le signal injecté sur E dépassant largement les +2,5V le gain déterminé sera unitaire. La Fig.86 est typique d’une saturation de la plage de numérisation. Le CAN « rabote » les valeurs à 1023. (La LED rouge s’illumine un peu si l’impédance du générateur est suffisamment faible pour pouvoir débiter du courant.) Pour ma part j’introduis des résistances de fortes valeurs en sortie du transformateur secteur pour protéger le matériel, les LED de protection ne s’illuminent donc pas. La Fig.87 comme les deux précédentes conduit à des valeurs élevées qui se traduisent par un gain unitaire. La synchronisation s’effectue sur un front montant, avec un seuil de détection de +2V. Elle est issue du signal PWM auquel est ajouté une petite composante continue. L’onde « carrée » oscille entre environ +0,7V et +3,3V. Ayant pratiquement dix périodes complètes à l’écran on peut affirmer que la valeur efficace est d’environ +1,9V.

Économie de place dans la zone de programme.

Prévenir est toujours préférable à guérir. Notre petite merveille d’oscilloscope démontre à la compilation une consommation de place importante dans la mémoire réservée au programme. En particulier la table réservée au logo : byte Salamandre[120]. Cent vingt octets juste pour un dessin, confine informatiquement à du gaspillage. Hors il s’agit de constantes, c’est à dire de valeurs qui n’ont pas besoin d’être logées dans de la RAM. L’EEPROM de l’ATmega328 est spécifiquement dédiée à ce type de données. Une EEPROM est une mémoire qui peut être écrite électriquement, (Et aussi lue !) mais qui conserve ses informations quand l’alimentation est coupée. Cette technologie est analogue à celle des SDRAM qui équipent nos appareils photographiques avec les technologies numériques actuelles.

Loger des données constantes dans l’EEPROM du microcontrôleur fait partie de son utilisation standard. On se doute que pour nous aider à le faire, l’IDE met à notre disposition des outils bien adaptés. Incluse et reconnue par défaut dans ce dernier, la bibliothèque EEPROM.h est dédiée à la mise en œuvre de l’EEPROM des microcontrôleurs. Il suffit de la déclarer en tête de programme. L’encadré ci-dessous détaille les procédures ainsi mises à notre disposition :

Quand on fait appel à EEPROM.h on dispose de deux nouvelles instructions spécifiques. Une qui permet d’écrire un OCTET en mémoire non volatile, l’autre qui permet de lire un OCTET en précisant son adresse relative dans cette dernière. Avant de chercher à exploiter le LOGO, il faut commencer par l’inscrire OCTET par OCTET dans l’EEPROM. C’est le rôle du petit programme P11_Ecriture_LOGO_en_EEPROM.ino dans lequel il vous suffira éventuellement de remplacer la table byte Salamandre[120] par votre dessin personnel. Téléversez ce croquis « outil » qui se charge de l’écriture des 120 OCTETs à partir de l’adresse relative $0000.
Afficher sur OLED un texte du genre « EEPROM avec la SALAMANDRE. » ne prouve strictement rien. Pour s’assurer que l’ATmega328 soit correctement « imprimé », il faut impérativement faire appel à un programme de vérification. Téléversez dans ce but le démonstrateur P12_Tracer_la_Salamandre.ino et observez son listage. On n’y trouve aucun dessin « binaire ». Seule une boucle va chercher OCTET par OCTET des données dans l’EEPROM et les inscrit aux bonnes coordonnées dans la matrice de l’afficheur OLED. Quand vous activez ce programme, soit au moment du téléversement, soit sur un RESET, si le petit animal est correctement représenté sur l’écran, on peut en déduire que :
• La tables des données est bien inscrite dans l’EEPROM,
• Les OCTETS successifs sont bien écrits depuis $0000 jusqu’à $0078,
• L’index PTR « linéaire » associé à la récupération de ces valeurs est correctement synchronisé avec le balayage ligne/colonne effectuée dans les deux
x  boucles imbriquées.
On Place ces instructions dans le programme complet, et l’on peut sereinement envisager la suite.

Intégration de l’oscilloscope dans le programme complet.

Ajouter un nouvel item dans le MENU de base de PICOLAB confine à une récompense. Ayant travaillé plusieurs heures sur une nouvelle séquence, quand cette dernière donne pleine satisfaction elle vient alors enrichir notre appareil de mesurage et d’analyse.  Le programme P10_Oscilloscope_seul.ino est parfaitement au point. En  http://www.robotix.fr/ouvrages/micheldroui/pico-laboratoire-lcd/strategie-de-developpement/ on a abordé la Stratégie de développement des fonctions du PICOLAB et détaillé l’utilisation du module de développement. Il ne reste qu’à mettre en œuvre ces méthodes pour agrémenter PICOLAB de l’oscilloscope expérimental. Rien de bien compliqué, on voit la vie résolument en rose, ce qui justifie pleinement l’exclamation FACILE ! dans le paragraphe « La totale« .
Pour créer la version complète avec les fonctions déjà au point, on extrait tous les éléments nouveaux dans P10_Oscilloscope_seul.ino que l’on introduit dans le programme complet en cours d’élaboration. On aboutit à P13_Complet_avec_PB_PILE.ino que vous téléversez sur BOARDUINO pour admirer le résultat final.

– Scongregneugneu … mais c’est que l’écran OLED est déficient !

La Fig.88 montre l’affichage dès que l’on sort du LOGO en cliquant sur l’un des deux B.P.
Manifestement, dans la zone en médaillon rouge de nombreux pixels affichent n’importe quoi. Ce problème est présent quel que soit l’item du MENU validé. La compilation annonce que seulement  70% de l’espace de programme est occupée. Quand aux données numériques, à peine 42% sur les 2048 octets sont utilisés. On peut en déduire que le programme n’est pas en cause. C’est alors l’afficheur OLED qui serait en défaut. Sur RESET, la totalité de l’écran est cohérente, ce qui invalide la supposition précédente. Du reste, possédant deux exemplaires OLED, la permutation des deux composants présente un comportement strictement identique. Le problème n’est donc pas matériel mais logiciel.
Ma longue expérience en programmation binaire à l’aide du langage d’assemblage m’a montré que statistiquement, quand un tel incident se produit, il y a une forte probabilité qu’il résulte d’une collision de PILE.
Un petit rappel sur l’architecture de l’espace mémoire sera certainement plus que le bienvenu. Je vous invite fortement à revoir la fiche Organisation de la mémoire vive et en particulier la notion de PILE. J’envisage ici un « croisement » de la zone en rouge délimitée vers le bas par le SP et le haut du TAS (La mémoire dynamique.) pointé par __heap_end.
Quand la PILE vient écraser les données du TAS, on convient de dire qu’il y a collision de PILE.
Encore faut-il s’en assurer avant de chercher à corriger ce problème. Il serait dommage d’en rester là et de ne pas pourvoir PICOLAB de notre bel oscilloscope numérique.
Chargez le démonstrateur P14_Recherche_du_probleme.ino qui n’est qu’une copie de P13_Complet_avec_PB_PILE.ino dans lequel on a ajouté la fonction :

Cette fonction crée une variable n’occupant qu’un seul OCTET BIDON qui est placée au sommet du TAS. Puis, avec des pointeurs particuliers dédiés à ce type de programmation et définis par l’IDE, on calcule la place qui reste de disponible dans la mémoire dynamique.
Comme on a un réel problème de collision, dans la procédure Afficher_QRG_produite(), douze lignes de programme ont été placées en remarques. Sans cet artifice le programme ne démarre pas et au téléversement rien ne se passe. Lorsque vous activez le démonstrateur, le compilateur annonce 1220 octets de disponibles pour les variables locales. Mais quand on appui sur l’un des deux B.P. du clavier, il y a effacement de l’écran, détermination de l’espace réellement disponible. Le résultat est sans appel : 185 OCTETs alors que l’on a fait déjà pas mal de vide avec les lignes passées en remarques. On peut toujours se demander à quoi correspondent les valeurs annoncées par le compilateur. Force est de constater que nous somme face à un réel problème d’encombrement.
Vérifions encore et encore, comme devrait le faire un journaliste sérieux qui croise toutes ses informations. Le démonstrateur met en évidence une divergence d’informations entre celles fournies par le compilateur, et une réalité issue d’utilisation de pointeurs spécifiques gérant les diverses zones de la RAM. Pour assoir notre diagnostic, nous allons nous livrer à une expérience fort simple : On recharge P13_Complet_avec_PB_PILE.ino dans l’éditeur de texte de l’IDE. Puis, pour diminuer la place occupée par les données dans la mémoire dynamique, on ne laisse dans le tableau de la BASE de TEMPs que trois options. L’instruction devient : float Valeurs_BT[3] {0.17, 0.25, 0.333};
Puis activez cette dernière mouture par téléversement.
Le programme a retrouvé un fonctionnement correct. (Sauf pour la sélection des options de la base de temps, car seule la déclaration a été modifiée. Le potentiomètre fait afficher correctement les trois premières valeurs, mais ensuite on alterne des 0mS et ovfmS. ovf pour overflow.)

CONCLUSIONS :
• Dans l’état actuel des études, le programme complet n’est pas viable car il se produit manifestement un collision de PILE,
• Il suffit de diminuer la place occupée par Valeurs_BT[N] pour rétablir un fonctionnement correct.

Ben … pas si évident que ça ! En effet, la bibliothèque EEPROM.h ne sait manipuler que des OCTETs. Hors Valeurs_BT[18] préserves des float, c’est à dire des réels codés chacun sur 32 BITs. (Quatre Octets.) Pour « transporter » ces 18 x 4 = 72 octets, il suffit de pointer le tout premier OCTET de la table puis d’écrire en EEPROM les 72 cellules RAM consécutives. La difficulté, c’est que nous ne savons pas où le compilateur à logé les nombreuses constantes et variables en RAM.

Donnée identifiée par VALEUR ou par ADRESSE.

Simplifier au maximum le travail d’un programmeur constitue la raison d’être des compilateurs. Pour du développement « courant », ils gèrent intégralement l’espace mémoire à leur façon, et nous n’avons pas besoin de chercher à savoir comment ils sont optimisés. Il peut arriver parfois, mais c’est très peu fréquent, que nous soyons obligés de travailler avec les adresses de stockage des données en RAM. Le problème présent est l’un de ces cas particulier. Ce didacticiel n’est pas un cours spécifique sur les pointeurs, donc nous n’allons qu’effleurer ce sujet : Le juste nécessaire pour comprendre comment nous allons nous sauver de ce très mauvais pas. Pour bien cerner ce que nous désirons faire, nous allons raisonner sur un exemple dans lequel on suppose que le programme aurait besoin de deux données LARGEUR et HAUTEUR. On code en tête du programme :
const float HAUTEUR = 14.235;
const float LARGEUR = 42.76;

Quand le compilateur rencontre la première ligne d’instruction, il crée la variable HAUTEUR en réservant deux emplacements qui se suivent en RAM et qui sont placés en haut du TAS actuel. Puis, comme représenté sur la Fig.89 il note l’ADRESSE de l’OCTET qui suit ces deux emplacements ADH et ADL et la place dans HAUTEUR au moment de l’analyse de cette première lignes. La valeur 14.235 est alors codée en binaire sur quatre OCTETs dans le format propre au compilateur utilisé. Enfin, ces quatre OCTETs sont déposés les uns à la suite des autres à partir de la cellule d’adresse ADH/ADL. La fin actuelle de la mémoire dynamique est alors mise à jour pour pointer la première cellule libre en sommet du TAS.


Passant à la ligne suivante dans le programme source, un autre POINTEUR nommé LARGEUR sera instancié sur deux autres OCTETS. Le réel 42.76 sera ensuite codé en binaire et logé dans les quatre cellules mémoire qui suivent. La limite actuelle du TAS est encore réévaluée.
Dans un tel processus nous devons retenir quelques notions de base fondamentales. Toute constante ou toute variable devient au moment de la compilation un identificateur associé à un POINTEUR. Ce pointeur est constitué de deux OCTETs qui contiennent une ADRESSE mémoire : Celle du premier OCTET de la donnée déclarée. Cette donnée occupera un OCTET si c’est un char ou un byte, deux OCTET pour un int, quatre OCTETs pour un float etc. Ce sont ces divers emplacements réservés en RAM qui contiendront les VALEURs des constantes ou des variables déclarées, avant de pouvoir les utiliser dans le programme source.

Manipulation des POINTEURS en langage C++.

Ayant bien compris qu’ils sont des entités chargés de repérer la position des variables ou des constantes dans la RAM, nous allons maintenant détailler leur caractéristiques, et en particulier le fait qu’ils sont typés. Le mieux pour expliquer cette particularité consiste à détailler le fonctionnement du compilateur quand il traite la déclaration :

float Valeurs_BT[18] {0.17, 0.25, 0.333, 0.417, 0.5, 0.583, 0.667, 0.75, 0.833, 1.667, 2.5, 3.333, 4.167, 5, 5.833, 6.667, 7.5, 8.333};

• Le compilateur réserve deux OCTETs pour un POINTEUR associé à l’identificateur Valeurs_BT,
• Il « note » que ce POINTEUR manipulera des entités float de quatre OCTETs chacune,
• Prenant l’adresse ADH/ADL du premier OCTET qui suit Valeurs_BT il la mémorise,
• Ensuite il va activer une boucle 18 fois durant laquelle à chaque pas il va :
x   * Traduire en binaire la constate déclarée entre {} en commençant par la première,
x   * Placer la version binaire dans les quatre OCTETs à partir de la cellule ADH/ADL, (1)
x   * Incrémenter quatre fois ADH/ADL pour pointer le float suivant. (1)
x            (1) C’est ici que le pointeur doit savoir quelle est l’étendue de la donnée traitée, donc son type.
• Réécrit ADH/ADL dans les deux OCTETs du pointeur Valeurs_BT. (Replacer au début du tableau.)

Supposons alors que l’on introduit dans le programme une instruction telle que : BT = Valeurs_BT[6];

Pour réaliser cette instruction, le POINTEUR Valeurs_BT reçoit l’ADRESSE ADH/ADL. Mais il « vise » alors le premier OCTET de la première constante. Hors l’instruction fait référence à la septième. Comme le POINTEUR est de type float, le compilateur sait que la donnée à aller lire est 6 éléments plus avant dans la RAM. Il faut donc incrémenter l’ADRESSE ADH/ADL de 6 x 4 soit 24. Les quatre OCTETs successifs seront alors lus en RAM puis déposés à partir de l’ADRESSE contenue dans l’identificateur BT.

Utilisation basique des POINTEURS. (Codage en C++)

Pour déclarer un pointeur on utilise une instruction du type :
type *Nom_du_pointeur = NULL;                            .
x      Le symbole ‘*‘ précise au compilateur que  Nom_du_pointeur est un pointeur qui ciblera une variable dont la taille en octets est définie par type. Bien que
x      ce ne soit pas impératif, l’affectation à NULL signifie que pour le moment ce POINTEUR n’est pas encore initialisé. Ce n’est pas une obligation, mais une
x      bonne précaution à prendre en général.
Référencer consiste à affecter l’ADRESSE d’une donnée à un pointeur :
x      Nom_du_pointeur = &VARIABLE; // Constante ou variable.
Déréférencer consiste à lire le contenu de la donnée pointée.
x      VARIABLE = *PTR // Retourne dans VARIABLE le contenu de la donnée ciblée.
Bien que toutes ces explications demeurent scandaleusement trop sommaires, nous en savons assez pour pouvoir décrypter les programmes d’utilisation en EEPROM de la table Valeurs_BT[18].

Stockage des valeurs de la base de temps en EEPROM.

Exactement comme c’était le cas pour le LOGO, avant de pouvoir utiliser les constantes de la base de temps il faut commencer par les inscrire dans l’EEPROM à l’aide d’un petit programme utilitaire. Ce dernier va activer plusieurs POINTEURs ou INDEX imbriqués dans des boucles de balayage. Nous sommes à ce stade presque dans de la routine, vu que c’était exactement la même structure logicielle pour figer la Salamandre dans l’EEPROM du microcontrôleur. Avant de s’aventurer dans de décryptage de P15_Ecriture_Base_Temps_en_EEPROM.ino chargé de cette mission, synthétisons sur la Fig.90 les opérations à effectuer. Notez au passage que ce programme rend compte de l’évolution de son exécution sur la voie série USB de l’IDE. Validez le moniteur série quand vous utiliserez cet utilitaire. La logique élémentaire consiste à placer la table float Valeurs_BT[18] à la suite du LOGO, donc à partir de l’OCTET d’ADRESSE relative  120, sachant que Salamandre occupe actuellement les adresses comprises entre $0000 et $0078.

Comprendre le programme qui loge la base de temps en EEPROM.

Assurant la vérification de la bonne inscription des valeurs dans la mémoire non volatile de l’ATmega328, il relit les 72 Octets en retraduisant les valeurs binaires en un réel décimal. La Fig.92 issue d’une copie d’écran montre ce que vous devez obtenir si l’opération s’est correctement effectuée. Passons à l’étude des diverses instruction du programme utilitaire :

float CIBLE Contiendra le réel « Base de temps » à placer en EEPROM.
byte PTR; Sera un INDEX à déplacement « linéaire qui va balayer l’EEPROM.
  // Ecriture les 18 réels en EEPROM à partir de l’emplacement 120.
  PTR = 120; L’INDEX « linéaire » se place au premier emplacement « rouge » dans l’EEPROM.
  for (byte I=0; I<18; I++) On va faire 18 fois ce qui est entre {}.
{CIBLE = Valeurs_BT[I];  Lecture d’une valeur dans la table des « Base de temps ».
Ecrit_un_float_en_EEPROM(PTR, &CIBLE);  Écriture du float CIBLE en EEPROM.
PTR = PTR+4;}  Passage à la cellule mémoire suivante dans l’EEPROM.

&CIBLE passe en paramètre l’ADRESSE en RAM de la variable identifiée par CIBLE.

// Lecture des 19 réels en EEPROM à partir de l’emplacement 120.
PTR = 120; L’INDEX linéaire se place au premier emplacement « rouge » dans l’EEPROM.
for (byte I=0; I<18; I++) On va faire 18 fois ce qui est entre {}.
     {Lire_un_float_en_EEPROM(PTR, &CIBLE);  Lecture du float CIBLE en EEPROM.
Serial.print(« …) ;  Séquence pour afficher la valeur du réel codé et lu en EEPROM.
PTR = PTR+4;}  Passage à la cellule mémoire suivante dans l’EEPROM.
INFINI: goto INFINI; } Bloquer la boucle de base pour ne plus rien faire.

Pour écrire un réel dans l’EEPROM, comme représenté sur la Fig.91, il faut pointer son premier OCTET en RAM. Puis, à partir de l’emplacement repéré par PTR en EEPROM recopier les quatre OCTETs qui se suivent. Nous sommes donc amenés à effectuer quatre fois un traitement par OCTET.

Pour le compilateur, ces quatre OCTET représentent la valeur d’un réel transformé en binaire en respectant un codage particulier. Pour l’ATmega328, ce ne sont que des OCTETS, c’est à dire des cellules de RAM dans lesquels il puise huit « 0 » ou « 1 » et d’EEPROM dans lesquels il les recopie.
Examinons maintenant la séquence qui écrit ces quatre OCTETs en EEPROM :

void Ecrit_un_float_en_EEPROM(byte PTR, float *Quartet) {
unsigned char *PTR_dans_le_Quartet;
byte I;
PTR_dans_le_Quartet = (unsigned char*)&CIBLE;
for(I=0; I<4; I++) {EEPROM.write(PTR+I, *(PTR_dans_le_Quartet+I));} }
Ce codage en C++ peut sembler un peu confus, il ne fait qu’utiliser des conventions d’écritures relatives aux pointeurs.
Voyons ligne à ligne ce que l’on a proposé à l’IDE :

void Ecrit_un_float_en_EEPROM(byte PTR, float *Quartet)
On fournit à la procédure la valeur de l’INDEX linéaire PTR de balayage de l’EEPROM. PTR contient en entrée de procédure l’adresse relative du prochain OCTET à écrire. PTR est une variable locale à la procedure. Sa modification dans cette dernière ne change pas l’INDEX global ayant un identificateur de nom identique. La procédure reçoit également en paramètre l’adresse de CIBLE qu’elle préserve dans un pointeur local que l’on désigne par l’identificateur *Quartet.
PTR_dans_le_Quartet = (unsigned char*)&CIBLE;
Pour pouvoir balayer des quatre OCTETs successifs, on crée un pointeur de char. (char est équivalent à un OCTET.) On donne à ce pointeur le contenu de CIBLE qui est une ADRESSE, donc cette affectation est « légale ».
for(I=0; I<4; I++) {EEPROM.write(PTR+I, *(PTR_dans_le_Quartet+I));}
Boucle qui fait quatre fois ce qui est entre {} :
• Lire le contenu de la cellule RAM
x pointée par PTR_dans_le_Quartet+I,
• Écrire ce contenu en EEPROM dans
x la cellule indexée par PTR+I,
• Incrémenter I et recommencer s’il est
x inférieur à 4.
La procédure
void Lire_un_float_en_EEPROM()
effectue un traitement inverse. À partir de l’index PTR elle lit quatre OCTETs successifs en EEPROM et les loge en RAM à partir de l’ADRESSE contenue dans le pointeur désigné par l’identificateur CIBLE. Pour bien comprendre ces techniques de manipulation d’ADRESSEs et de VALEURs rangées dans des OCTETs de la mémoire RAM, on doit impérativement faire l’effort d’étudier les nombreux tutoriels mis en ligne sur le sujet. La partie un peu compliquée dans les traitements détaillés de cette page vient du fait que deux POINTEURs sont utilisés. L’un typé float sert à trouver l’ADRESSE en RAM de CIBLE. L’autre, typé char est utilisé à partir de cette ADRESSE pour lire quatre OCTET successifs.

Rien ne vous oblige à comprendre totalement l’écriture du programme outil P15_Ecriture_Base_Temps_en_EEPROM.ino, vous pouvez vous contenter de le faire fonctionner une fois pour placer la base de temps en EEPROM. Le programme complet n°30 fonctionnera alors correctement. Notre but sera pleinement atteint … vive les POINTEURs !

>>> Page suivante.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *