A documentação do Arduino diz que é possível manter constantes como seqüências de caracteres ou o que eu não quiser alterar durante o tempo de execução na memória do programa.
Todas as constantes estão inicialmente na memória do programa. Onde mais eles estariam quando a energia estiver desligada?
Penso nisso como incorporado em algum lugar do segmento de código, o que deve ser razoavelmente possível dentro de uma arquitetura von-Neumann.
Na verdade, é a arquitetura de Harvard .
Por que diabos eu tenho que copiar o conteúdo maldito para a RAM antes de acessá-lo?
Você não De fato, há uma instrução de hardware (LPM - Load Program Memory) que move os dados diretamente da memória do programa para um registro.
Eu tenho um exemplo dessa técnica na saída do Arduino Uno para o monitor VGA . Nesse código, há uma fonte de bitmap armazenada na memória do programa. É lido a partir do momento e copiado para a saída da seguinte maneira:
// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
Uma desmontagem dessas linhas mostra (em parte):
f1a: e4 91 lpm r30, Z+
f1c: e0 93 c6 00 sts 0x00C6, r30
Você pode ver que um byte da memória do programa foi copiado no R30 e, em seguida, armazenado imediatamente no registro USART UDR0. Nenhuma RAM envolvida.
No entanto, há uma complexidade. Para seqüências normais, o compilador espera encontrar dados na RAM e não PROGMEM. São espaços de endereço diferentes e, portanto, 0x200 na RAM é algo diferente de 0x200 no PROGMEM. Portanto, o compilador se preocupa em copiar constantes (como seqüências de caracteres) na RAM na inicialização do programa, para que não precise se preocupar em saber a diferença posteriormente.
Como o código (32kiB) é tratado com apenas 2kiB de RAM?
Boa pergunta. Você não terá mais de 2 KB de seqüências constantes, porque não haverá espaço para copiá-las todas.
É por isso que as pessoas que escrevem coisas como menus e outras coisas prolixo, tomam medidas extras para atribuir às seqüências o atributo PROGMEM, que as impede de serem copiadas na RAM.
Mas fico perplexo com essas instruções para apenas ler e imprimir dados da memória do programa:
Se você adicionar o atributo PROGMEM, precisará executar etapas para que o compilador saiba que essas seqüências de caracteres estão em um espaço de endereço diferente. Fazer uma cópia completa (temporária) é uma maneira. Ou apenas imprima diretamente do PROGMEM, um byte de cada vez. Um exemplo disso é:
// 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
Se você passar essa função como ponteiro para uma seqüência de caracteres no PROGMEM, ela fará a "leitura especial" (pgm_read_byte) para extrair os dados do PROGMEM, e não da RAM, e os imprimirá. Observe que isso leva um ciclo de clock adicional, por byte.
E ainda mais interessante: o que acontece com constantes literais como nesta expressão a = 5*(10+7)
são 5, 10 e 7 realmente copiadas para a RAM antes de carregá-las nos registradores? Eu simplesmente não posso acreditar nisso.
Não, porque eles não precisam ser. Isso seria compilado em uma instrução "carregar literal no registrador". Essa instrução já está em PROGMEM, então o literal agora é tratado. Não é necessário copiá-lo para a RAM e depois lê-lo novamente.
Tenho uma descrição extensa dessas coisas na página Colocando dados constantes na memória do programa (PROGMEM) . Que possui um código de exemplo para configurar strings e matrizes de strings, razoavelmente facilmente.
Ele também menciona a macro F (), que é uma maneira fácil de simplesmente imprimir a partir de PROGMEM:
Serial.println (F("Hello, world"));
Um pouco de complexidade do pré-processador permite que ele seja compilado em uma função auxiliar, que extrai os bytes da string do PROGMEM, um byte por vez. Nenhum uso intermediário de RAM é necessário.
É bastante fácil usar essa técnica para outras coisas que não Serial (por exemplo, seu LCD) derivando a impressão de LCD da classe Print.
Como exemplo, em uma das bibliotecas de LCD que escrevi, fiz exatamente isso:
class I2C_graphical_LCD_display : public Print
{
...
size_t write(uint8_t c);
};
O ponto principal aqui é derivar do Print e substituir a função "write". Agora sua função substituída faz o que for necessário para gerar um caractere. Como é derivado do Print, agora você pode usar a macro F (). por exemplo.
lcd.println (F("Hello, world"));
string_table
matriz. Essa matriz pode ter 20 KB e nunca caberia na memória (mesmo que temporariamente). No entanto, você pode carregar apenas um índice usando o método acima.