Eu tenho algumas dificuldades para entender o gerenciamento de memória.
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. 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. Quero fazer uso disso para tornar possível o meu menu de interface do usuário em um LCD.
Mas fico perplexo com essas instruções para apenas ler e imprimir dados da memória do programa:
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
Serial.println( buffer );
Por que diabos eu tenho que copiar o conteúdo maldito para a RAM antes de acessá-lo? E se isso for verdade, o que acontece com todo o código? Também é carregado na RAM antes da execução? Como o código (32kiB) é tratado com apenas 2kiB de RAM? Onde estão aqueles duendes carregando disquetes?
E ainda mais interessante: o que acontece com constantes literais como nesta expressão:
a = 5*(10+7)
5, 10 e 7 são realmente copiados para a RAM antes de carregá-los nos registros? Eu simplesmente não posso acreditar nisso.
fonte
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.Respostas:
O AVR é uma família de arquitetura Harvard modificada , portanto, o código é armazenado apenas em flash, enquanto os dados existem principalmente na RAM ao serem manipulados.
Com isso em mente, vamos abordar suas perguntas.
Você não precisa fazer isso por si só, mas, por padrão, o código pressupõe que os dados estejam na RAM, a menos que o código seja modificado para procurá-lo especificamente em flash (como com
strcpy_P()
).Não. Arquitetura de Harvard. Veja a página da Wikipedia para mais detalhes.
O preâmbulo gerado pelo compilador copia os dados que devem ser modificáveis / modificados na SRAM antes de executar o programa real.
Não sei. Mas se você os vir, não há nada que eu possa fazer para ajudar.
Nah. O compilador avalia a expressão em tempo de compilação. O que quer que aconteça depende das linhas de código ao seu redor.
fonte
const uint8_t test1[5]= { 0x54, 0x65, 0x73, 0x74, 0x31 }; const uint8_t bla[9]= { 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x62 }; const uint8_t Menu[4]= { 0x3d, 0x65, 0x6e, 0x75};
como faço para trazer esses dados para piscar e mais tarde para a função SPI.transfer (), que leva um uint8_t por chamada.É assim que as
Print::print
impressões são impressas na memória do programa na biblioteca do Arduino:__FlashStringHelper*
é uma classe vazia que permite que funções sobrecarregadas, como a impressão, diferenciem um ponteiro para programar a memória de uma para a memória normal, pois ambas são vistasconst char*
pelo compilador (consulte /programming/16597437/arduino-f- o que-faz-realmente-faz )Assim, você pode sobrecarregar a
print
função do seu monitor LCD para que ele pegue um__FlashStringHelper*
argumento, vamos chamá-loLCD::print
e, em seguida, uselcd.print(F("this is a string in progmem"));' to call it.
F () `é uma macro que garante que a string esteja na memória do programa.Para predefinir a string (para ser compatível com a impressão interna do Arduino), usei:
Eu acho que uma alternativa seria algo como
o que evitaria o
__FlashStringHelper
elenco.fonte
Todas as constantes estão inicialmente na memória do programa. Onde mais eles estariam quando a energia estiver desligada?
Na verdade, é a arquitetura de Harvard .
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:
Uma desmontagem dessas linhas mostra (em parte):
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.
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.
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 é:
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.
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:
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:
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.
fonte