Strings são “do mal”

Objetos da classe String, não os “char arrays”, mas aquelas variáveis declaradas como |String str = “some text”|, são realmente um veneno em um programa para Arduino.

Provavelmente você pensará algo como “Mas elas são tão legais, não preciso alocá-las explicitamente, posso concatená-las como se estivesse programando em Java ou Javascript, …”

 

Não interessam quais razões você tenha para utilizá-las, esqueça-as e livre-se delas!!!

Mas por quê?

Explico:

Cada variável declarada como “String”, quando o arduino inicializa o programa, instancia um objeto que, para que possa realizar a “mágica” dele, pré-aloca uma quantidade enorme da memória SRAM, mesmo para as menores strings.

Se você usa um Arduino Galileo, com 512Kbytes de SRAM, isso não chega a ser um problema. Mas no meu caso, e provavelmente no seu, se estiver usando os arduinos mais populares como o UNO, você tem apenas 2Kb (2048 bytes) de SRAM para se virar com as suas variáveis.

Usando Strings, um programa de teste que codifiquei, usou todos os 2Kb, e restartava toda vez que eu iniciava uma conexão via módulo bluetooth. Depois que removi as Strings, e as substituí por “progr_char[] PROGMEM arrays” (ver abaixo), o mesmo programa passou a utilizar apenas 570 bytes de SRAM e rodar sem problemas.

Possíveis Soluções

char* / chr[]

Usar char arrays para as constantes e char pointers para strings dinâmicas. Elas ainda acabam sendo armazenadas na SRAM quando o programa é carregado, mas usam menos memória que as Strings (apenas a quantidade de caracteres de cada string).

prog_char[] PROGMEM

Para strings constantes, essa é a forma preferida, poeque deixa as strings armazenadas na memória de programa, ao invés da SRAM (o Arduino UNO possui 32Kbytes de memória de programa).

Claro, tem uma pegadinha: Não é possível acessar as strings diretamente na memória de programa. Primeir, é preciso alocar um char[] ou um char*, como buffer, na SRAM. Depois, você carrega o conteúdo da string para dentro do buffer e o utilizada para o fim que você necessitar..

Por exemplo, você pode declarar algumas strings e uma função para carregá-las:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
char pmString[20];
 
prog_char pms_0[] PROGMEM = "String 1";
prog_char pms_1[] PROGMEM = "Qualquer String 2";
prog_char pms_2[] PROGMEM = "Outra String 3"
 
PROGMEM const char *pms_table[] = {pms_0, pms_1, pms_2};
 
char* getPMString(int pos) {
    strcpy_P(pmString, (char*)pgm_read_word(&(pms_table[pos])));
    return pmString;
}
 
const unsigned byte st1 = 0;
const unsigned byte anySt = 1;
const unsigned byte otherSt = 2;
 
///// em algum lugar do programa
Serial.println(getPMString(anySt));

Note que:

  • É necessário declarar cada char[] para cada string e declarar um array contendo todas elas.
  • É necessário ter um buffer de char, cujo tamanho precisa ser o da maior string armazenada na memória de programa.
  • A função carrega qualquer das strings no mesmo buffer estático (neste caso), então não é possível carregar duas strings separadas antes de usá-las.
  • Se você precisar carregar mais de uma string antes de usá-las, você precisa alterara a função getPMString() para retornar um char* alocado, ou copiar o conteúdo do do buffer estático para outro char*. Em qualquer um dos casos, você precisa ser muito cuidadoso para liberar a memória depois de usar estas strings, caso contrário, a memória vai lotar bem rápido.
  • É recomendável usar algumas constantes int para facilitar a referência às strings que se quer carregar. É claro que se pode referenciá-las diretamente pelo seu índice numérico, mas não é uma boa prática.