Sempre que utilizamos strings e arrays constantes em nossos programas, por padrão elas vão parar na memória RAM. Como tempo pouca memória RAM, seria ótimo conseguir colocar isso na memória de programa, liberando a preciosa RAM para as variáveis.
Neste post vemos como fazer isso nos microcontroladores AVR, como o ATmega328P do Arduino Uno.
Strings na memória de programa
Consideramos a seguinte função, que compara a string de entrada com uma string constante. Ela retorna verdadeiro se as strings são iguais e falso se as strings são diferentes. Este tipo de comparação é muito utilizada quando lendo comandos da serial, por exemplo.
#include <string.h> bool is_hello(const char *str) { return !strcmp(str, "hello"); }
Agora que temos um exemplo para trabalhar, vejamos como colocar string de comparação na memória de programa.
Primeiro precisamos incluir o cabeçalho avr/pgmspace.h que contém as definições.
Para colocar a string constante na memória de programa basta envolver ela com a macro PSTR().
Se tentarmos executar o programa agora, ele não funcionará, pois a função strcmp() espera strings na memória RAM, e não na memória de programa.
Precisamos substituir a função strcmp() por outra, que faz a leitura da segunda string na memória de programa. A função que faz isso é strcmp_P().
Parabéns, você acabou de economizar preciosos 5 bytes de memória RAM!!!
O código final fica da forma a seguir.
#include <string.h> #include <avr/pgmspace.h> bool is_hello(const char *str) { return !strcmp_P(str, PSTR("hello")); }
Há diversas funções de string alternativas, que fazem uso de strings na memória de programa. Inclusive versões para *printf()!
Você pode consultar os cabeçalhos avr/pgmspace.h e stdio.h para ver mais funções de string.
Arrays na memória de programa
Consideramos a seguinte função, que retorna um valor em uma tabela (array). Este é um exemplo simples que pode ser facilmente programado como uma função em vez de uma tabela. Colocar arrays na memória de programa é muito útil para CRC (códigos de verificação de erro), onde temos tabelas muito grande.
const unsigned char lut[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; unsigned char get_lut(unsigned char pos) { return lut[pos]; }
Novamente, agora que temos um exemplo para trabalhar, vejamos como colocar a tabela na memória de programa.
Primeiro precisamos incluir o cabeçalho avr/pgmspace.h que contém as definições.
Para colocar a tabela (array) na memória de programa basta colocar PROGMEM na declaração. Note que é necessário declarar a tabela como constante.
Se tentarmos executar o programa agora, ele não funcionará, pois o programa tenta acessar a tabela na memória RAM, e não na memória de programa.
Em vez de usar lut[pos] para obter o valor, precisamos chamar uma função específica que faz a leitura do valor da memória de programa. A função que faz isso é pgm_read_byte() e para chamá-la passamos o endereço (&) do valor que queremos ler, ou seja, pgm_read_byte(&lut[pos]).
Parabéns, você acabou de economizar preciosos 16 bytes de memória RAM!!!
O código final fica da forma a seguir.
#include <avr/pgmspace.h> PROGMEM const unsigned char lut[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; unsigned char get_lut(unsigned char pos) { return pgm_read_byte(&lut[pos]); }
Há várias funções para fazer leitura de valores na memória de programa:
- pgm_read_byte – Ler byte (inteiro de 8 bits)
- pgm_read_word – Ler word (inteiro de 16 bits)
- pgm_read_dword – Ler double word (inteiro de 32 bits)
- pgm_read_float – Ler float
Você pode consultar o cabeçalho avr/pgmspace.h ver mais funções.
Leia mais:
Veja mais posts da Categoria Programação!