Texto em memória: o que funciona e o que dá pau
Quem começa com microcontroladores acha que declarar uma string é só escrever `char msg[] = "hello"`. Funciona até você perceber que o RAM acabou e o programa reinicia sem motivo aparente. Aí chega a hora de entender onde esses textos realmente vivem no hardware. O conceito básico é simples: textos podem ficar em memória volátil (RAM) ou não volátil (flash/ROM). A escolha afeta consumo de energia, velocidade de acesso e espaço disponível. Você não tem escolha quando o chip tem 2KB de RAM e 256KB de flash.
texto de memória exemplos práticos
Exemplo mais direto em AVR/Arduino. Você usa a macro `PROGMEM` para colocar a string na flash: const char mensagem[] PROGMEM = "Dados salvos na flash"
Só declarar não basta. Se você usar `Serial.println(mensagem)` normalmente, vai imprimir lixo ou nada. Precisa usar a função de leitura adequada: Serial.println(pgm_read_word_near(&mensagem));
Para strings completas, existe o atalho `strcpy_P()` que copia da flash para um buffer temporário na RAM antes de processar: char buf[64]; strcpy_P(buf, mensagem); Serial.println(buf);
Em ARM/MCU modernos sem PROGMEM, o equivalente é usar `const` corretamente e deixar o compilador decidir. Mas em ESP32 com Dual-Boot ou partições, você pode precisar mapear manualmente usando atributos de seção: const char dado[] __attribute__((section(".my_flash_section"))) = "conteúdo";
Aí no linker script você define onde essa seção deve ser alocada. É chato na primeira vez, mas depois vira rotina.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Armazenamento em EEPROM e Flash pelo usuário
Tem ainda a situação em que você precisa gravar textos dinamicamente e recuperá-los depois. A EEPROM do ATmega328 tem 1KB. Dura aproximadamente 100 mil ciclos de gravação por célula. Se você escreve a mesma string a cada ciclo de operação, a memória morre em poucas semanas. Uma solução comum é espelhar os dados em dois endereços alternados e só escrever quando houver mudança real. Isso corta a desgaste em algo como 95% em aplicações que atualizam configuração uma vez por hora.
Em ESP32, a situação é diferente. Não há EEPROM física. Usa-se a biblioteca `Preferences` que fatia a flash em partições. Funciona bem até você precisar de mais de 4MB para dados persistentes, aí o gerenciamento de wear leveling entra em conflito com setores específicos do NVS.
Pegadinha que eu levei tempo pra entender
Num projeto com STM32G0, eu declarei uma tabela de strings como `const char* tabela[]` na flash. Parecia correto. O código compilava, rodava... até pedir uma string específica e retornar ponteiros errados. O problema era que o inicializador estava em RAM, não em flash. O compilador não colocou os ponteiros onde eu esperava porque faltava especificar o endereço da seção explicitamente. A correção foi usar `__attribute__((section(".flash_text")))` tanto no array quanto nos literais individuais, e ajustar o linker para mapear essa seção no endereço correto do flash. Sem isso, o MCU lia endereços de RAM que continham lixo de outras variáveis.
Se você está em dúvida se uma string está em RAM ou flash, compila com `-Warray-bounds` e `-Wstringop-overflow` habilitados e inspeciona o `.map` gerado. A seção do símbolo aparece lá. Se estiver em `.data` ou `.bss`, está em RAM. Se estiver em `.text` ou `.rodata`, está na flash.
Quanto tempo isso economiza
Em um projeto com 47 strings de média 30 caracteres cada, mover todas para flash reduziu o uso de RAM de 1.8KB para 312 bytes no ATmega2560. O tempo de desenvolvimento inicial aumentou cerca de 20 minutos por causa das funções de leitura adicionais, mas a estabilidade do sistema melhorou significativamente porque variáveis locais ganharam espaço. Em plataformas mais modernas como ESP32-S3 com 8MB de RAM, esse problema quase não existe mais. A otimização vale a pena mesmo assim, especialmente em projetos que rodam com sensors e WiFi ativos simultaneamente.
Quando não usar texto em memória fixa
Se o texto precisa mudar em runtime — como mensagens dinâmicas de I2C, dados de sensores formatados, ou strings construídas com `sprintf` — não faz sentido colocar em flash. Use heap ou stack normalmente. A vantagem de PROGMEM é para dados que são conhecidos em tempo de compilação e nunca mudam. Também não adianta muito em chips com menos de 16KB de flash. Aoverhead das rotinas de leitura da flash consome mais ciclos do que o espaço que você economiza. Nesses casos, o compilador já otimiza bem declarando tudo como `const` normal.
Para lidar com internacionalização, existe uma abordagem híbrida: strings em flash com índices em RAM. Você mantêm um array de chaves na RAM e resolve o texto real só quando necessário. Isso economiza memória enquanto permite trocar o idioma sem regravar a flash inteira.