[prog] Déclaration de variables et mémoire, const volatile static...

Salut,

Super_Cinci:
1 - les constantes : exemple d'une tempo générale...

#define tempo 150
const tempo = 150;
const byte tempo = 150;

dans mon code, j'utilise un peu partout un delay(tempo);.
Je sais que avec le #define, le mot "tempo" est remplacé par "150" avant la compilation, et le code est alors envoyé au compilateur avec des delay(150);, il n'existe pas de variable tempo.
Mais est-ce qu'en utilisant un const ou un const byte, une variable "tempo" en lecture seule est déclarée (et me bouffe de la RAM inutilement, contrairement à un #define)? ou est-ce que le pré-compilateur va traduire mon const byte tempo = 150; en #define tempo 150 et donc ne pas générer de variable?

Le mot clef "const" indique au compilateur que la variable en question doit être en "lecture seule".

Suivant le niveau d'optimisation (-O0, -O1, ... -Os) :

  • si les optimisations sont activées :
    const = define (mais avec les info sur le type de la variable en plus) -> 0 octet de ram utilisé
  • si les optimisations sont désactivées :
    const = variable classique en lecture seule -> n octets de ram utilisé

Attention: cela ne s'applique que sur les types de base, char, int, long, ... pas sur les structures, tableaux, ...

Super_Cinci:
1bis - les tableaux de constantes

const byte array[5] = {...};

Ca va me donner quoi réellement? 5 octets en RAM que le compilateur va m'interdire de modifier?

C'est un cas spécial du const.
Les tableaux de const (et par extension les structures const) sont des ensembles de valeurs d'un type défini (ou de plusieurs types défini dans le cas d'une structure ou d'une union).

Avec avr-gcc ces ensembles de const sont stockés en RAM et marqués comment étant en "lecture seul".
Bien que les AVR possèdent une instruction spécial (LPM) pour stocker en flash (PROGMEM) le compilateur ne l'utilise pas (par défaut) pour stocker les tableaux de const.
Contrairement à d'autre compilateurs comme celui pour PIC32 ou ARM qui stocke par défaut les const en flash et non en RAM.

Dans ton exemple (et sur une plateforme AVR avec avr-gcc) tu aurais donc effectivement 5 octets "non modifiable" en RAM.

Super_Cinci:
2 - le mot clé static

il me semble qu'une déclaration avec static crée une variable en ram qui ne sera accessible qu'au code dans lequel elle a été déclarée et dont l'emplacement (l'adresse) est défini et invariable.

Le mot clef "static" fonctionne différemment suivant le contexte :

  • sur une variable globale :
    la variable n'est visible que dans le fichier source en cours de compilation.
    Il est alors possible d'avoir plusieurs variables globale de même nom dans plusieurs fichiers source différents.
    Cela permet aussi au compilateur de faire une meilleur optimisation (pas besoin de s'occuper des accès à la variable depuis l'extérieur).
  • sur une variable locale :
    la variable est initialisé avec la valeur fourni lors du premier appel à la fonction mère, puis elle conserve sa valeur aux appels suivant.
    En gros c'est une variable globale avec une visibilité locale.
  • sur une fonction :
    Même principe que pour une variable globale, la fonction n'est visible que dans le fichier en cours.

Super_Cinci:
3 - le mot clé volatile

J'ai lu quelque part qu'une variable "volatile" était déclarée en RAM de manière définitive et une fois pour toutes (un peu comme en static), mais ce mot en français me fait penser aux vapeurs comme l’éther, c-à-d que ça se promène sans dire où ça va... un faux-ami? Pour moi, une "variable volatile" en français est une variable qui peut disparaître à tout moment, et réapparaître n'importe-où, comme un oiseau (qui lui est un volatile)... mais c'est pas ça du tout.

Toute variables déclaraient comme "volatile" sont (obligatoirement) laissez telle quelle par le compilateur.
Même si tu lui demande d'optimiser à mort ton code il ne touchera pas aux variables déclaré "volatile".

On utilise le mot clef "volatile" sur des variables globale partageaient entre une interruption et une fonction classique.
Si elle n'était pas "volatile" le compilateur l'optimiserait et l'accès à cette variable depuis l'interruption serait alors corrompu.

Super_Cinci:
5 - PROGMEM

Là, il y a un compromis à trouver entre le gain de ram et le temps de lecture de la variable...

D'un point de vue purement assembleur :

  • Lecture dans r0 d'une variable en RAM à l'adresse 0x0042 :
LDS r0, 0x0042
; la valeur de ram[0x0042] est désormais dans r0
  • Lecture dans r0 d'une variable en flash à l'adresse 0x0042 (voir datasheet §26.2) :
LDI r30, $00 
LDI r31, $42 ; r30 & r31 = registre Z
LPM ; r0 <- flash[Z]
; la valeur de flash[0x0042] est désormais dans r0

1 instruction pour la ram VS 3 instructions pour progmem, donc à part si tu travailles avec des timings hyper précis ou avec des boucles trés grosse, c'est quasiment la même chose.

Super_Cinci:
6 - Conclusion : comme vous venez de le lire, j'y comprends rien dans les déclarations de variables. Je voudrais pouvoir coder et savoir réellement avant de compiler quelle taille de RAM va être occupée, et ce qu'il reste comme place pour le stack (donc les paramètres des fonctions, variables locales des fonctions...). Je veut pouvoir imaginer si ma pile (stack) va avoir une taille qui va beaucoup varier ou si justement, cette pile va être optimisée pour ne pas venir recouvrir mes variables...

Oublie l'ide arduino, crée toi un makefile (ou prend en un tout fait : ici dans la partie code) et utilise :

avr-size -C --mcu=atmega328p

Tu auras alors le détail de l'utilisation de ta RAM (statique).
Exemple :

avr-size -C --mcu=atmega328p dcpu.elf
AVR Memory Usage
----------------
Device: atmega328p

Program:    4532 bytes (13.8% Full)
(.text + .data + .bootloader)

Data:       1831 bytes (89.4% Full)
(.data + .bss + .noinit)

.text = code machine
.data = données statique (chaine de char, ...)
.bootloader = zone mémoire pour le bootloader
.bss = données statique/globales non initialisé
.noinit = sous partie de .bss
(cf Memory Sections)