O que reside nos diferentes tipos de memória de um microcontrolador?

25

Existem diferentes segmentos de memória nos quais vários tipos de dados são inseridos no código C após a compilação. Ou seja:.text , .data, .bss, pilha e heap. Eu só quero saber onde cada um desses segmentos residiria na memória do microcontrolador. Ou seja, quais dados entram em que tipo de memória, dado que os tipos de memória são RAM, NVRAM, ROM, EEPROM, FLASH etc.

Encontrei respostas para perguntas semelhantes aqui, mas elas não conseguiram explicar qual seria o conteúdo de cada um dos diferentes tipos de memória.

Qualquer tipo de ajuda é muito apreciada. Desde já, obrigado!

Soju T Varghese
fonte
11
NVRAM, ROM, EEPROM e Flash são praticamente nomes diferentes para a mesma coisa: memória não volátil.
Lundin
Um pouco tangente à pergunta, mas o código pode (excepcionalmente) existir em muitos deles, principalmente se você considerar o uso de patches ou calibrações. Às vezes, ele será movido antes da execução, às vezes executado no local.
Sean Houlihane
@SeanHoulihane O OP está perguntando sobre microcontroladores, que quase sempre são executados fora do Flash (você qualificou seu comentário como excepcional). Micro processadores com MB de RAM externo executando o Linux, por exemplo, iria copiar seus programas na memória RAM para executá-los, talvez fora um cartão SD agindo como um volume montável.
precisa saber é o seguinte
@tcrosley Agora existem microcontroladores com TCM, e algumas vezes os microcontroladores fazem parte de um SoC maior. Eu também suspeito que há casos como dispositivos eMMC em que o mcu se auto-inicia para executar a partir da RAM a partir de seu próprio armazenamento (com base na memória de alguns hardwares de alguns anos atrás). Concordo, não é uma resposta direta - mas acho muito relevante que os mapeamentos típicos não sejam de forma alguma regras rígidas.
Sean Houlihane
11
não há regras para conectar uma coisa à outra, com certeza o material somente leitura, como texto e rodata, seria ideal para flash, mas os dados. e o deslocamento e tamanho dos. código de inicialização). Esses termos (.text, etc) não têm nada a ver com microcontroladores; é uma coisa de compilador / cadeia de ferramentas que se aplica a todos os destinos de compiladores / cadeias de ferramentas. no final do dia, o programador decide para onde as coisas vão e informa a cadeia de ferramentas através de um script vinculador normalmente.
old_timer

Respostas:

38

.texto

O segmento .text contém o código real e é programado na memória Flash para microcontroladores. Pode haver mais de um segmento de texto quando houver vários blocos não contíguos de memória Flash; por exemplo, um vetor inicial e vetores de interrupção localizados na parte superior da memória e código começando em 0; ou seções separadas para um programa de inicialização e principal.

.bss e .data

Existem três tipos de dados que podem ser alocados externos a uma função ou procedimento; o primeiro são dados não inicializados (historicamente chamados .bss, que também incluem os 0 dados inicializados) e o segundo é inicializado (não-bss) ou .data. O nome "bss" vem historicamente de "Block Started by Symbol", usado em uma montadora há 60 anos. Ambas as áreas estão localizadas na RAM.

À medida que um programa é compilado, as variáveis ​​serão alocadas para uma dessas duas áreas gerais. Durante o estágio de vinculação, todos os itens de dados serão coletados juntos. Todas as variáveis ​​que precisam ser inicializadas terão uma parte da memória do programa reservada para armazenar os valores iniciais e, pouco antes de main () ser chamado, as variáveis ​​serão inicializadas, normalmente por um módulo chamado crt0. A seção bss é inicializada com todos os zeros pelo mesmo código de inicialização.

Com alguns microcontroladores, há instruções mais curtas que permitem o acesso à primeira página (primeiras 256 localizações, às vezes chamada página 0) da RAM. O compilador para esses processadores pode reservar uma palavra-chave nearpara designar variáveis ​​a serem colocadas lá. Da mesma forma, também existem microcontroladores que podem apenas referenciar determinadas áreas por meio de um registrador de ponteiro (exigindo instruções extras), e essas variáveis ​​são designadas far. Finalmente, alguns processadores podem endereçar uma seção da memória pouco a pouco e o compilador poderá especificar isso (como a palavra-chave bit).

Portanto, pode haver segmentos adicionais, como .nearbss e .neardata, etc., onde essas variáveis ​​são coletadas.

.rodata

O terceiro tipo de dado externo a uma função ou procedimento é como as variáveis ​​inicializadas, exceto que são somente leitura e não podem ser modificadas pelo programa. Na linguagem C, essas variáveis ​​são indicadas usando a constpalavra - chave Eles geralmente são armazenados como parte da memória flash do programa. Às vezes, eles são identificados como parte de um segmento .rodata (dados somente leitura). Em microcontroladores usando a arquitetura Harvard , o compilador deve usar instruções especiais para acessar essas variáveis.

pilha e pilha

A pilha e o heap são colocados na RAM. Dependendo da arquitetura do processador, a pilha pode crescer ou diminuir. Se crescer, será colocado na parte inferior da RAM. Se crescer, será colocado no final da RAM. O heap usará a RAM restante não alocada para variáveis ​​e aumentará a direção oposta da pilha. O tamanho máximo da pilha e da pilha geralmente pode ser especificado como parâmetros do vinculador.

Variáveis ​​colocadas na pilha são quaisquer variáveis ​​definidas em uma função ou procedimento sem a palavra-chave static. Eles já foram chamados de variáveis ​​automáticas ( autopalavra-chave), mas essa palavra-chave não é necessária. Historicamente, autoexiste porque fazia parte da linguagem B que precedeu C, e era necessário. Os parâmetros de função também são colocados na pilha.

Aqui está um layout típico para RAM (supondo que não haja uma seção especial na página 0):

insira a descrição da imagem aqui

EEPROM, ROM e NVRAM

Antes da chegada da memória Flash, a EEPROM (memória somente leitura programável apagável eletricamente) era usada para armazenar o programa e os dados const (segmentos .text e .rodata). Agora, há apenas uma pequena quantidade (por exemplo, 2 KB a 8 KB de bytes) de EEPROM disponível, se houver alguma, e é normalmente usada para armazenar dados de configuração ou outras pequenas quantidades de dados que precisam ser retidas durante a inicialização da inicialização. ciclo. Elas não são declaradas como variáveis ​​no programa, mas são gravadas para o uso de registros especiais no microcontrolador. A EEPROM também pode ser implementada em um chip separado e acessada por um barramento SPI ou I²C.

A ROM é essencialmente a mesma do Flash, exceto que é programada na fábrica (não programável pelo usuário). É usado apenas para dispositivos de volume muito alto.

A NVRAM (RAM não volátil) é uma alternativa à EEPROM e geralmente é implementada como um IC externo. A RAM comum pode ser considerada não volátil se for feita com bateria; nesse caso, nenhum método de acesso especial é necessário.

Embora os dados possam ser salvos no Flash, a memória Flash possui um número limitado de ciclos de apagamento / programa (1000 a 10.000), portanto, não é realmente projetado para isso. Também exige que os blocos de memória sejam apagados de uma só vez, por isso é inconveniente atualizar apenas alguns bytes. Destina-se a variáveis ​​de código e somente leitura.

A EEPROM tem limites muito mais altos nos ciclos de apagamento / programa (100.000 a 1.000.000), portanto é muito melhor para esse propósito. Se houver EEPROM disponível no microcontrolador e for grande o suficiente, é onde você deseja salvar dados não voláteis. No entanto, você também precisará apagar os blocos primeiro (normalmente 4KB) antes de escrever.

Se não houver EEPROM ou for muito pequeno, será necessário um chip externo. Uma EEPROM de 32 KB é de apenas 66 ¢ e pode ser apagada / gravada em 1.000.000 de vezes. Uma NVRAM com o mesmo número de operações de apagar / programa é muito mais cara (x10). As NVRAMs geralmente são mais rápidas para leitura do que as EEPROMs, mas mais lentas para gravação. Eles podem ser gravados em um byte de cada vez ou em blocos.

Uma alternativa melhor para ambos é a FRAM (RAM ferroelétrica), que possui essencialmente ciclos infinitos de gravação (100 trilhões) e sem atrasos de gravação. É aproximadamente o mesmo preço da NVRAM, cerca de US $ 5 por 32KB.

tcrosley
fonte
Essa foi uma informação realmente útil. Você poderia fornecer uma referência à sua explicação? Como livros ou revistas, caso queira ler mais sobre isso ..?
Soju T Varghese
mais uma pergunta, você poderia dar uma idéia das vantagens ou desvantagens de uma memória sobre a outra (EEPROM, NVRAM e FLASH)?
Soju T Varghese
Boa resposta. Postei uma complementar, focando mais detalhadamente o que acontece especificamente na linguagem C.
Lundin
11
@SojuTVarghese Atualizei minha resposta e incluí algumas informações sobre o FRAM também.
precisa
@Lundin, usamos os mesmos nomes de segmento (por exemplo, Rodata), para que as respostas se complementem bem.
precisa saber é o seguinte
21

Sistema incorporado normal:

Segment     Memory   Contents

.data       RAM      Explicitly initialized variables with static storage duration
.bss        RAM      Zero-initialized variables with static storage duration
.stack      RAM      Local variables and function call parameters
.heap       RAM      Dynamically allocated variables (usually not used in embedded systems)
.rodata     ROM      const variables with static storage duration. String literals.
.text       ROM      The program. Integer constants. Initializer lists.

Além disso, geralmente existem segmentos flash separados para o código de inicialização e os vetores de interrupção.


Explicação:

Uma variável tem duração de armazenamento estático se for declarada como staticou se residir no escopo do arquivo (algumas vezes chamado de "global"). C possui uma regra informando que todas as variáveis ​​de duração de armazenamento estático que o programador não inicializou explicitamente devem ser inicializadas como zero.

Cada variável duração de armazenamento estático que é inicializado para zero, implícita ou explicitamente, acaba em .bss. Enquanto aqueles que são explicitamente inicializados com um valor diferente de zero terminam em .data.

Exemplos:

static int a;                // .bss
static int b = 0;            // .bss      
int c;                       // .bss
static int d = 1;            // .data
int e = 1;                   // .data

void func (void)
{
  static int x;              // .bss
  static int y = 0;          // .bss
  static int z = 1;          // .data
  static int* ptr = NULL;    // .bss
}

Lembre-se de que uma configuração não padrão muito comum para sistemas embarcados é ter uma "inicialização mínima", o que significa que o programa ignorará todas inicialização de objetos com duração de armazenamento estático. Portanto, pode ser aconselhável nunca escrever programas que dependam dos valores de inicialização dessas variáveis, mas defini-los no "tempo de execução" antes de serem usados ​​pela primeira vez.

Exemplos dos outros segmentos:

const int a = 0;           // .rodata
const int b;               // .rodata (nonsense code but C allows it, unlike C++)
static const int c = 0;    // .rodata
static const int d = 1;    // .rodata

void func (int param)      // .stack
{
  int e;                   // .stack
  int f=0;                 // .stack
  int g=1;                 // .stack
  const int h=param;       // .stack
  static const int i=1;    // .rodata, static storage duration

  char* ptr;               // ptr goes to .stack
  ptr = malloc(1);         // pointed-at memory goes to .heap
}

Variáveis ​​que podem ir para a pilha geralmente acabam nos registros da CPU durante a otimização. Como regra geral, qualquer variável que não tenha seu endereço utilizado pode ser colocada em um registro da CPU.

Observe que os ponteiros são um pouco mais complexos do que outras variáveis, pois permitem dois tipos diferentes de const, dependendo se os dados apontados devem ser somente leitura ou se o próprio ponteiro deve ser. É muito importante saber a diferença para que seus ponteiros não acabem na RAM por acidente, quando você queria que eles estivessem em flash.

int* j=0;                  // .bss
const int* k=0;            // .bss, non-const pointer to const data
int* const l=0;            // .rodata, const pointer to non-const data
const int* const m=0;      // .rodata, const pointer to const data

void (*fptr1)(void);       // .bss
void (*const fptr2)(void); // .rodata
void (const* fptr3)(void); // invalid, doesn't make sense since functions can't be modified

No caso de constantes inteiras, listas de inicializadores, literais de strings, etc., eles podem terminar em .text ou .rodata, dependendo do compilador. Provavelmente, eles acabam como:

#define n 0                // .text
int o = 5;                 // 5 goes to .text (part of the instruction)
int p[] = {1,2,3};         // {1,2,3} goes to .text
char q[] = "hello";        // "hello" goes to .rodata
Lundin
fonte
Não entendo, no seu primeiro código de exemplo, por que 'static int b = 0;' entra em .bss e por que 'static int d = 1;' entra em .data ..? No meu entendimento, ambas são variáveis ​​estáticas que foram inicializadas pelo programador. Então, o que faz a diferença? @Lundin
Soju T Varghese
2
@SojuTVarghese Porque os dados .bss são inicializados em 0 como um bloco; valores específicos como d = 1 devem ser armazenados no flash.
tcrosley
@SojuTVarghese Adicionados alguns esclarecimentos.
Lundin
@Lundin Além disso, no seu último exemplo de código, isso significa que todos os valores inicializados entram em .text ou .rodata e suas respectivas variáveis ​​somente entram em .bss ou .data? Nesse caso, como as variáveis ​​e seus valores correspondentes são mapeados entre si (ou seja, entre os segmentos .bss / .data e .text / .rodata)?
Soju T Varghese
@SojuTVarghese Não, .datanormalmente tem o chamado endereço de carregamento em flash, onde os valores iniciais são armazenados, e o chamado endereço virtual (não é realmente virtual em um microcontrolador) na RAM, onde a variável é armazenada durante a execução. Antes de maininiciar, os valores iniciais são copiados do endereço de carregamento para o endereço virtual. Você não precisa armazenar zeros, portanto .bss, não precisa armazenar seus valores iniciais. sourceware.org/binutils/docs/ld/…
starblue
1

Embora qualquer dado possa entrar na memória escolhida pelo programador, geralmente o sistema funciona melhor (e deve ser usado) onde o perfil de uso dos dados corresponde aos perfis de leitura / gravação da memória.

Por exemplo, o código do programa é WFRM (escreva poucos leem muitos), e há muito disso. Isso se encaixa perfeitamente no FLASH. ROM OTOH é W uma vez RM.

A pilha e a pilha são pequenas, com muitas leituras e gravações. Isso caberia melhor na RAM.

A EEPROM não se adequaria bem a nenhum desses usos, mas se adequa ao perfil de pequenas quantidades de dados perisistentes nos power-ups, portanto, dados de inicialização específicos do usuário e talvez resultados de registro.

Neil_UK
fonte