La documentation Arduino dit, il est possible de garder des constantes comme des chaînes ou tout ce que je ne veux pas changer pendant l'exécution dans la mémoire du programme.
Toutes les constantes sont initialement dans la mémoire du programme. Où seraient-ils ailleurs lorsque le courant est coupé?
Je pense qu'il est intégré quelque part dans le segment de code, ce qui doit être assez possible dans une architecture von-Neumann.
C'est en fait l'architecture de Harvard .
Pourquoi diable dois-je copier le fichu contenu sur la RAM avant d'y accéder?
Non. En fait, il existe une instruction matérielle (LPM - Load Program Memory) qui déplace les données directement de la mémoire du programme dans un registre.
J'ai un exemple de cette technique en sortie Arduino Uno sur un moniteur VGA . Dans ce code, une police bitmap est stockée dans la mémoire du programme. Il est lu à partir de cela à la volée et copié dans la sortie comme ceci:
// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
Un démontage de ces lignes montre (en partie):
f1a: e4 91 lpm r30, Z+
f1c: e0 93 c6 00 sts 0x00C6, r30
Vous pouvez voir qu'un octet de mémoire de programme a été copié dans R30, puis immédiatement stocké dans le registre USART UDR0. Aucune RAM impliquée.
Il existe cependant une complexité. Pour les chaînes normales, le compilateur s'attend à trouver des données dans la RAM et non PROGMEM. Ce sont des espaces d'adressage différents, et donc 0x200 dans la RAM est quelque chose de différent de 0x200 dans PROGMEM. Ainsi, le compilateur se donne la peine de copier des constantes (comme des chaînes) dans la RAM au démarrage du programme, il n'a donc pas à se soucier de connaître la différence plus tard.
Comment est alors géré le code (32 ko) avec seulement 2 ko de RAM?
Bonne question. Vous ne vous en sortirez pas avec plus de 2 Ko de chaînes constantes, car il n'y aura pas de place pour les copier toutes.
C'est pourquoi les gens qui écrivent des choses comme des menus et d'autres trucs verbeux, prennent des mesures supplémentaires pour donner aux chaînes l'attribut PROGMEM, ce qui les désactive d'être copiées dans la RAM.
Mais je suis déconcerté par ces instructions pour simplement lire et imprimer les données de la mémoire du programme:
Si vous ajoutez l'attribut PROGMEM, vous devez prendre des mesures pour informer le compilateur que ces chaînes se trouvent dans un espace d'adressage différent. Faire une copie complète (temporaire) est un moyen. Ou imprimez simplement directement à partir de PROGMEM, un octet à la fois. Un exemple de cela est:
// Print a string from Program Memory directly to save RAM
void printProgStr (const char * str)
{
char c;
if (!str)
return;
while ((c = pgm_read_byte(str++)))
Serial.print (c);
} // end of printProgStr
Si vous transmettez à cette fonction un pointeur sur une chaîne dans PROGMEM, elle effectue la "lecture spéciale" (pgm_read_byte) pour extraire les données de PROGMEM plutôt que de RAM et les imprime. Notez que cela prend un cycle d'horloge supplémentaire, par octet.
Et encore plus intéressant: qu'arrive-t-il aux constantes littérales comme dans cette expression a = 5*(10+7)
les 5, 10 et 7 sont-ils vraiment copiés dans la RAM avant de les charger dans les registres? Je ne peux pas croire ça.
Non, car ce n'est pas obligatoire. Cela se compilerait en une instruction "charger le littéral dans le registre". Cette instruction est déjà dans PROGMEM, donc le littéral est maintenant traité. Pas besoin de le copier dans la RAM puis de le relire.
J'ai une longue description de ces choses sur la page Mettre des données constantes dans la mémoire du programme (PROGMEM) . Cela a un exemple de code pour configurer des chaînes et des tableaux de chaînes, assez facilement.
Il mentionne également la macro F () qui est un moyen simple d'imprimer simplement à partir de PROGMEM:
Serial.println (F("Hello, world"));
Un peu de complexité du préprocesseur permet de compiler dans une fonction d'assistance qui extrait les octets de la chaîne de PROGMEM un octet à la fois. Aucune utilisation intermédiaire de RAM n'est requise.
Il est assez facile d'utiliser cette technique pour des choses autres que série (par exemple votre écran LCD) en dérivant l'impression LCD de la classe d'impression.
Par exemple, dans l'une des bibliothèques LCD que j'ai écrites, j'ai fait exactement cela:
class I2C_graphical_LCD_display : public Print
{
...
size_t write(uint8_t c);
};
Le point clé ici est de dériver de Print et de remplacer la fonction "écriture". Maintenant, votre fonction remplacée fait tout ce dont elle a besoin pour sortir un caractère. Puisqu'il est dérivé de Print, vous pouvez maintenant utiliser la macro F (). par exemple.
lcd.println (F("Hello, world"));
string_table
tableau. Ce tableau peut être de 20 Ko et ne tient jamais en mémoire (même temporairement). Vous ne pouvez cependant charger qu'un seul index en utilisant la méthode ci-dessus.