40 votos

¿Qué reside en los diferentes tipos de memoria de un microcontrolador?

Existen diferentes segmentos de memoria en los que se introducen diversos tipos de datos desde el código C tras la compilación. Por ejemplo .text , .data , .bss , pila y montón. Sólo quiero saber dónde residiría cada uno de estos segmentos en la memoria de un microcontrolador. Es decir, qué datos van en qué tipo de memoria, dado que los tipos de memoria son RAM, NVRAM, ROM, EEPROM, FLASH, etc.

He encontrado respuestas a preguntas similares aquí, pero no explicaban cuál sería el contenido de cada uno de los diferentes tipos de memoria.

Cualquier tipo de ayuda es muy apreciada. Gracias de antemano.

52voto

Matt McMinn Puntos 6067

.texto

El segmento .text contiene el código real, y se programa en la memoria Flash de los microcontroladores. Puede haber más de un segmento de texto cuando hay varios bloques no contiguos de memoria Flash; por ejemplo, un vector de inicio y vectores de interrupción situados en la parte superior de la memoria, y el código que comienza en 0; o secciones separadas para un programa de arranque y otro principal.

.bss y .data

Hay tres tipos de datos que se pueden asignar de forma externa a una función o procedimiento; el primero son los datos no inicializados (históricamente llamados .bss, que también incluyen los datos inicializados 0), y el segundo es el inicializado (no bss), o .data. El nombre "bss" proviene históricamente de "Block Started by Symbol", utilizado en un ensamblador hace unos 60 años. Ambas áreas se encuentran en la RAM.

Cuando se compila un programa, las variables se asignan a una de estas dos áreas generales. Durante la etapa de vinculación, todos los elementos de datos se recogerán juntos. Todas las variables que necesitan ser inicializadas tendrán una porción de la memoria del programa reservada para mantener los valores iniciales, y justo antes de llamar a main(), las variables serán inicializadas, típicamente por un módulo llamado crt0. La sección bss es inicializada a todos los ceros por el mismo código de inicio.

En algunos microcontroladores existen instrucciones más cortas que permiten acceder a la primera página (las primeras 256 posiciones, a veces llamada página 0) de la RAM. El compilador para estos procesadores puede reservar una palabra clave como near para designar las variables que se colocarán allí. Del mismo modo, también hay microcontroladores que sólo pueden hacer referencia a ciertas áreas a través de un registro de puntero (lo que requiere instrucciones adicionales), y tales variables se designan far . Por último, algunos procesadores pueden direccionar una sección de memoria bit a bit y el compilador tendrá una forma de especificarlo (como la palabra clave bit ).

Por lo tanto, puede haber segmentos adicionales como .nearbss y .neardata, etc., donde se recogen estas variables.

.rodata

El tercer tipo de datos externos a una función o procedimiento es como las variables inicializadas, excepto que es de sólo lectura y no puede ser modificado por el programa. En el lenguaje C, estas variables se denotan con el símbolo const palabra clave. Normalmente se almacenan como parte de la memoria flash del programa. A veces se identifican como parte de un segmento .rodata (datos de sólo lectura). En los microcontroladores que utilizan el Arquitectura de Harvard el compilador debe utilizar instrucciones especiales para acceder a estas variables.

pila y montón

La pila y el montón se colocan en la RAM. Dependiendo de la arquitectura del procesador, la pila puede crecer hacia arriba o hacia abajo. Si crece hacia arriba, se colocará en la parte inferior de la RAM. Si crece hacia abajo, se colocará al final de la RAM. El montón utilizará el resto de la RAM no asignada a las variables, y crecerá en la dirección opuesta a la pila. El tamaño máximo de la pila y del montón se puede especificar normalmente como parámetros del enlazador.

Las variables colocadas en la pila son cualquier variable definida dentro de una función o procedimiento sin la palabra clave static . Antes se llamaban variables automáticas ( auto ), pero esa palabra clave no es necesaria. Históricamente, auto existe porque formaba parte del lenguaje B que precedió al C, y allí era necesario. Los parámetros de las funciones también se colocan en la pila.

Esta es una disposición típica para la RAM (asumiendo que no hay una sección especial de página 0):

enter image description here

EEPROM, ROM y NVRAM

Antes de que apareciera la memoria Flash, se utilizaba la EEPROM (memoria de sólo lectura programable y borrable eléctricamente) para almacenar el programa y los datos const (segmentos .text y .rodata). Ahora sólo hay una pequeña cantidad (por ejemplo, de 2KB a 8KB bytes) de EEPROM disponible, si es que hay alguna, y normalmente se utiliza para almacenar datos de configuración u otras pequeñas cantidades de datos que necesitan ser retenidos durante un ciclo de encendido y apagado. Estos no se declaran como variables en el programa, sino que se escriben utilizando registros especiales en el microcontrolador. La EEPROM también puede ser implementada en un chip separado y accedida a través de un bus SPI o I²C.

La ROM es esencialmente lo mismo que la Flash, excepto que se programa en la fábrica (no es programable por el usuario). Se utiliza sólo para dispositivos de muy alto volumen.

La NVRAM (RAM no volátil) es una alternativa a la EEPROM, y suele implementarse como un CI externo. La RAM normal puede considerarse no volátil si está respaldada por una batería; en ese caso no se necesitan métodos de acceso especiales.

Aunque los datos pueden guardarse en Flash, la memoria Flash tiene un número limitado de ciclos de borrado/programación (de 1000 a 10.000), por lo que no está realmente diseñada para ello. También requiere que se borren bloques de memoria a la vez, por lo que es inconveniente actualizar sólo unos pocos bytes. Está pensado para código y variables de sólo lectura.

La EEPROM tiene límites mucho más altos en los ciclos de borrado/programación (de 100.000 a 1.000.000), por lo que es mucho mejor para este propósito. Si hay EEPROM disponible en el microcontrolador y es lo suficientemente grande, es donde quieres guardar los datos no volátiles. Sin embargo también tendrás que borrar en bloques primero (típicamente 4KB) antes de escribir.

Si no hay EEPROM o es demasiado pequeña, se necesita un chip externo. Una EEPROM de 32KB es sólo 66¢ y se puede borrar/escribir hasta 1.000.000 de veces. Una NVRAM con el mismo número de operaciones de borrado/programación es mucho más cara (x10) Las NVRAM suelen ser más rápidas para leer que las EEPROM, pero más lentas para escribir. Pueden escribirse en un byte cada vez, o en bloques.

Una alternativa mejor a ambas es la FRAM (RAM ferroeléctrica), que tiene ciclos de escritura esencialmente infinitos (100 billones) y no tiene retrasos de escritura. Tiene el mismo precio que la NVRAM, unos 5 dólares por 32KB.

30voto

Neil Foley Puntos 1313

Sistema integrado 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.

Además, suele haber segmentos de flash separados para el código de arranque y los vectores de interrupción.


Explicación:

Una variable tiene una duración de almacenamiento estática si se declara como static o si reside en el ámbito de un archivo (a veces llamado descuidadamente "global"). C tiene una regla que establece que todas las variables de duración de almacenamiento estático que el programador no inicializó explícitamente deben ser inicializadas a cero.

Toda variable de duración de almacenamiento estático que se inicializa a cero, implícita o explícitamente, termina en .bss . Mientras que los que se inicializan explícitamente con un valor distinto de cero terminan en .data .

Ejemplos:

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
}

Tenga en cuenta que una configuración no estándar muy común para los sistemas embebidos es tener una "puesta en marcha mínima", lo que significa que el programa se saltará todo inicialización de objetos con duración de almacenamiento estático. Por lo tanto, podría ser prudente no escribir nunca programas que se basen en los valores de inicialización de tales variables, sino que los establezcan en "tiempo de ejecución" antes de que se utilicen por primera vez.

Ejemplos de los otros 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
}

Las variables que pueden ir a la pila suelen acabar en los registros de la CPU durante la optimización. Como regla general, cualquier variable que no tenga su dirección tomada puede colocarse en un registro de la CPU.

Tenga en cuenta que los punteros son un poco más complicados que otras variables, ya que permiten dos tipos diferentes de const dependiendo de si los datos apuntados deben ser de sólo lectura, o si el propio puntero debe serlo. Es muy importante conocer la diferencia para que tus punteros no acaben en la RAM por accidente, cuando querías que estuvieran en la 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

En el caso de las constantes de enteros, listas de inicializadores, literales de cadena, etc., pueden terminar en .text o en .rodata dependiendo del compilador. Lo más probable es que terminen 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

3voto

user44635 Puntos 4308

Aunque cualquier dato puede ir a cualquier memoria que el programador elija, generalmente el sistema funciona mejor (y está pensado para ser utilizado) cuando el perfil de uso de los datos se ajusta a los perfiles de lectura/escritura de la memoria.

Por ejemplo, el código del programa es WFRM (write few read many), y hay mucho. Esto encaja perfectamente con FLASH. La ROM, en cambio, es W una vez RM.

La pila y el montón son pequeños, con muchas lecturas y escrituras. Eso es lo que mejor encaja con la RAM.

La EEPROM no se adaptaría a ninguno de esos usos, pero sí al perfil de pequeñas cantidades de datos persistentes a través de los encendidos, por lo que los datos de inicialización específicos del usuario, y tal vez el registro de resultados.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X