La memoria es el lugar donde almacenamos informaciones o datos. Las memorias electrónicas, son los circuitos usados para almacenar datos. Los microcontroladores poseen varios tipos de memoria y casi todo proyecto o equipo electrónico moderno usa ellas para almacenar datos. Esto es muy análogo al cerebro humano, el cual posee una memoria donde almacenamos los datos. en este articulo aprenderemos como es una memoria electrónica y como podemos usarla para almacenar datos.

 

Todas las instrucciones de un microcontrolador se pueden clasificar en 3 clases principales:

 

1 – Instrucciones de Salto Condicional.

2 – Instrucciones de memorias (movimientos de variables).

3 – Instrucciones Aritméticas y Lógicas.

 

En el presente artículo vamos a estudiar y poner en práctica las Instrucciones de Movimiento de Datos en Memoria. Comenzar a programar microcontroladores puede parecer una tarea que requiere de mucho saber o conocimiento, pero, con Arduino, vamos a iniciarnos en el desarrollo de proyectos y circuitos, haciendo uso de microcontroladores, de una manera fácil y amena. Lo primero que necesitamos saber, es como es por dentro un microcontrolador, de que partes está formado, como funciona y cuál es la relación con el compilador o lenguaje de programación.

 

PARTES DE UN MICROCONTROLADOR.

 

Son tres las partes principales de un microcontrolador:

1 – La Memoria.

2 – CPU o Unidad Central de Proceso.

3 – Los Periféricos.

 

La Figura 1 muestra las partes principales de un microcontrolador.

 

Figura 1_Partes basicas de un Microcontrolador
Figura 1_Partes basicas de un Microcontrolador

 

 

La Memoria es la parte del microcontrolador que se encarga de almacenar las instrucciones y datos.

La CPU es la parte de microcontrolador que se encarga de ejecutar las órdenes o instrucciones del programa escrito en lenguaje C.

Los Periféricos, es la parte del microcontrolador que se encarga de sacar o entrar información o datos de la parte exterior. Normalmente se le conoce como entradas o salidas (I/O).

 

LA MEMORIA.

Para entender la memoria, podemos hacer una analogía con la mente humana. La mente almacena información o datos; igual es la memoria digital, es usada para almacenar información o datos. Podemos representar una memoria como una serie de cajones o gavetas donde se coloca la información.

En electrónica digital existe un circuito llamado flip-flop tipo D o data, este circuito almacena información tras recibir un pulso digital. La Figura 2 muestra un esquema de este circuito. Lo que hace este circuito es leer el nivel lógico presente en la entrada D y cuando se recibe un pulso por la entrada Clock, almacenarlo en la salida Q .

 

Figura 2_Diagrama básico del Flip-Flop tipo D
Figura 2_Diagrama básico del Flip-Flop tipo D

 

La Figura 3 muestra un ejemplo del circuito, en un determinado estado, antes de recibir un pulso en la entrada Clock . En este estado, la salida Q está a nivel lógico 1 (alto) y la entrada a nivel lógico 0 (bajo).

 

Figura 3_Flip-Flop antes de recibir el pulso Clock
Figura 3_Flip-Flop antes de recibir el pulso Clock

 

Ahora, si damos un pulso positivo (alto) en la entrada de Clock, el nivel lógico presente en la entrada D, pasa a ser almacenado en el flip-flop y su valor es presentado en la salida Q . Esto lo podemos ver en la Figura 4.

 

Figura 4_Flip-Flop despues de recibir el pulso Clock
Figura 4_Flip-Flop despues de recibir el pulso Clock

 

 

Ahora, podemos formar un circuito que tenga 8 flip-flops tipo D y el circuito lo representa la Figura 5.

 

Figura 5_Circuito con 8 Flip-Flops tipo D
Figura 5_Circuito con 8 Flip-Flops tipo D

 

 

Tenemos 8 entradas D y 8 salidas Q independientes. El Clock está en paralelo para los 8 flip-flops. El funcionamiento es exactamente igual para los 8 flip-flops, como explicado anteriormente para un solo flip-flop. A este tipo de circuito se le llama Registro . La Figura 6 muestra un ejemplo en un posible estado de este circuito, antes de recibir un pulso por la entrada Clock .

 

Figura 6 - 8 Flip-Flops D antes de recibir el pulso Clock
Figura 6 - 8 Flip-Flops D antes de recibir el pulso Clock

 

 

La Figura 7, muestra el circuito después de recibir un pulso positivo en la entrada Clock . Los valores son almacenados en las salidas Q.

 

Figura 7 - 8 Flip-Flops D despues de recibir el pulso Clock
Figura 7 - 8 Flip-Flops D despues de recibir el pulso Clock

 

 

Así, podemos almacenar datos o información digital. Existen varios circuitos digitales que incorporan varios flip-flops y siguen el funcionamiento anteriormente explicado.

Para formar una memoria, podemos unir varios flip-flops, organizados como muestra la Figura 8.

 

 

 

Figura 8 - Construccion de memoria con flip-flops
Figura 8 - Construccion de memoria con flip-flops

 

 

Los datos son colocados en las entradas D0, D1, D2, D3, D4, D5, D6, D7. Los datos son almacenados en el respectivo grupo de flip-flops y mostrados o sacados por las salidas Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7. Gracias a que todas salidas Q se encuentran en alta impedancia, es decir una alta resistencia, no hay problemas con corto circuito y solamente el registro seleccionado estará presente en las salidas Q.

Lo que necesitamos ahora, es una manera de seleccionar cada registro. Para esto, podemos usar un circuito digital llamado decodificador. Este circuito es formado por algunas compuertas digitales NOT y AND. La Figura 9 muestra el funcionamiento lógico para una compuerta NOT. Este circuito lo que hace es invertir el nivel lógico presente en la entrada.

 

Figura 9 - Compuerta NOT
Figura 9 - Compuerta NOT

 

 

La Figura 10 muestra el funcionamiento lógico para una compuerta AND. Es decir que solamente cuando todas sus entradas están a nivel lógico 1 (alto), su salida se coloca a nivel lógico 1; caso contrario su salida presenta nivel lógico 0 (bajo). Con estos componentes podemos formar un decodificador.

 

Figura 10 - Compuerta AND
Figura 10 - Compuerta AND

 

 

La Figura 11 muestra un decodificador de 2 entradas y 4 salidas. Notemos, que cuando las entradas están a nivel lógico 0, los negadores seleccionan la primera salida. Ahora uniremos el decodificador a los registro para poder seleccionar el que se desee.

 

Figura 11 - Decodificador de 2 entradas 4 salidas
Figura 11 - Decodificador de 2 entradas 4 salidas

 

 

La Figura 12 muestra el circuito.

 

 

Figura 12 - Decodificador de 2 entradas 4 para seleccionar flip-flop (registros)
Figura 12 - Decodificador de 2 entradas 4 para seleccionar flip-flop (registros)

 

 

La Figura 13 muestra el diagrama en bloques para el circuito de la figura.

 

 

Figura 13 - Diagrama en bloques del decodificador 2X4 para seleccion de flip-flops
Figura 13 - Diagrama en bloques del decodificador 2X4 para seleccion de flip-flops

 

 

Ahora necesitamos un circuito que nos permita utilizar el bus de datos de entrada y salida en un solo bus. Para esto utilizaremos los buffer tri-estado (Transeiver). La Figura 14 muestra como con un par de buffer tri-estado podemos utilizar una misma línea digital para que en un momento sea entrada y en otro momento sea salida.

 

Figura 14 - Buffer triestado
Figura 14 - Buffer triestado

 

 

Cuando el nivel de entrada en la línea de Control está a nivel lógico 0, la salida de la compuerta NOT invierte para nivel lógico 1 y selecciona el buffer tri-estado B, lo cual hace que el valor de Q, aparezca en la línea I/O . Cuando la línea de Control está a nivel lógico 1, la compuerta NOT invierte y presentara nivel lógico 0, deshabilitando el buffer tri-estado B . Pero ahora, está habilitado el buffer tri-estado A, por lo que el nivel lógico presente en la línea I/O, está presente en la línea A .

 

Basado en este principio podemos hacer un circuito de control para el circuito de la Figura 12. El circuito de la Figura 15 presenta este circuito y la Figura 16 su respectivo diagrama de bloques.

 

Figura 15 - Control del bus de datos con buffer triestado
Figura 15 - Control del bus de datos con buffer triestado

 

 

 

Figura 16 - Diagrama en bloques del control de bus de datos con triestado
Figura 16 - Diagrama en bloques del control de bus de datos con triestado

 

  

MEMORIA DE PROGRAMA Y DE DATOS.

Los microcontroladores tienen una memoria para el almacenamiento de las instrucciones llamada memoria de programa. En ella son almacenadas las instrucciones en forma secuencial. La Unidad Central de Proceso (CPU), busca las instrucciones en esta memoria y las ejecuta. Vea la Figura 17.

 

 

Figura 17_Memoria de Programa y de Datos
Figura 17_Memoria de Programa y de Datos

 

 

Los datos son almacenados en la memoria de datos. Los datos son valores que pueden variar durante la ejecución del programa, por tal motivo son llamadas variables.

La memoria de programa es de tipo no volátil, es decir cuando el microcontrolador es desconectado de la fuente de voltaje, la información permanece. Como por ejemplo las memorias ROM, PROM, EPROM, EEPROM, FLASH, etc. Ya la memoria de datos, es de tipo volátil, es decir que al desconectar la fuente de voltaje, la información se pierde. Como por ejemplo las memorias RAM dinámicas (DRAM) o estáticas (SRAM).

Las memorias son construidas con diferentes tipos de arreglos (arrays) o arquitectura, pero independiente de esto, todas almacenan información y su principio de funcionamiento es el mismo.

 

 

EL COMPILADOR Y AMBIENTE DE DESARROLLO (IDE).

 

El compilador es el editor donde se hacen los programas. Es principalmente un editor de texto y las instrucciones se van escribiendo en forma secuencial, según uno quiera que el microcontrolador las ejecute. En ambiente de desarrollo de Arduino (IDE), la función main() es llamada de forma automática. La función main, llama la función setup() y loop(). Pero la mayoría de libros y textos de microcontroladores usan la función main(), por eso, para entender mejor cómo funcionan la memorias dentro del microcontrolador, en la siguiente explicación la usaremos.

El código se comienza a ejecutar en una función llamada main(). El siguiente es el código para esta función:

 

int main()

   {

   }

 

Para asignar nombres a las direcciones de la memoria de datos, se hace antes de esta función. Por ejemplo, la siguiente línea indica al compilador que separe un byte en la memoria de datos y le asigne el nombre count.

 

usigned char count;

 

La siguiente línea de código le indica al compilador que escriba el valor 9 en la dirección de memoria count.

 

int main()

  {

      count = 9;

  }

 

En la Figura 18 podemos observar lo que pasa antes de ejecutar la instrucción.

 

Figura 18_Antes de ejecutar la instruccion
Figura 18_Antes de ejecutar la instruccion

 

 

La Figura 19, muestra cuando la CPU lee la instrucción y la Figura 20 después de ejecutar la instrucción.

 

 

Figura 19_La CPU lee la instruccion de la memoria de programa
Figura 19_La CPU lee la instruccion de la memoria de programa

 

 

 

Figura 20_Despues de ejecutar la instruccion
Figura 20_Despues de ejecutar la instruccion

 

 

En el siguiente ejemplo, vamos a separar 3 posiciones en memoria de datos:

usigned char count;

usigned char temperature;

usigned char humidity;

 

El siguiente código, asigna valores a estas posiciones de memoria. A la posición de memoria count le asigna el valor 7, a la posición de memoria temperature, le asigna el valor 22 y a la posición de memoria humidity le asigna el valor 16:

 

usigned char count;
usigned char temperature;
usigned char humidity;


int main()
   {
      count = 7;
      temperature = 22;
      humidity = 16;
   
      while(1)
      {
      }
}

 

Vea la Figura 21 para entender lo que sucedió en la memoria de datos. La instrucción while(1), es un bucle (loop) infinito que por el momento no hace nada.

 

Figura 21 - Programa ejemplo
Figura 21 - Programa ejemplo

 

 

En el siguiente ejemplo vamos a ejecutar una instrucción de incremento en la variable temperature . La instrucción:

 

temperature++;

 

Incrementa en 1 el valor almacenado en la variable temperature. Es decir que después de ejecutado el programa, la variable temperature quedara con el valor 23. La Figura 22 muestra este ejemplo.

 

 

Figura 22 - Programa ejemplo con instruccion de incremento
Figura 22 - Programa ejemplo con instruccion de incremento

 

 

usigned char count;
usigned char temperature;
usigned char humidity;


int main()
   {
      count = 7;
      temperature = 22;
      humidity = 16;
      temperature++;
       
      while(1)
      {
      }
   }

 

 

EL OPERADOR DE ASIGNACION (=).

En la Figura 23 podemos observar la tarjeta Arduino Uno y en la Figura 24 la IDE o ambiente de desarrollo, la cual puede ser bajada de la página web de Arduino. Para asignar un valor a un variable usamos el operador de asignación (=). En el siguiente ejemplo la variable sensor es almacenada en la variable display.

 

 

Figura 23  - Targeta de desarrollo Arduino Uno
Figura 23 - Targeta de desarrollo Arduino Uno

 

 

 

Figura 24 - Ambiente de desarrollo de Arduino Uno
Figura 24 - Ambiente de desarrollo de Arduino Uno

 

 

 

display = sensor;

 

En la anterior instrucción, el valor contenido en la variable sensor, es movido o transferido para la localización de memoria display . Por ejemplo, supongamos que el valor de la localización de memoria display es 27 y el valor de la variable sensor es 9. Después de ejecutar la instrucción display = sensor; el valor de la variable display será igual a 7. Así, es como podemos mover datos de una localización de memoria para otra, o de un puerto para memoria o de memoria para un puerto.

 

El leguaje C usa el operador de asignación (=), para transferir datos de una localización de memoria para otra. También, existen funciones listas para ser usadas, como en el caso de la memoria EEPROM. Las siguientes son funciones para escribir y leer en esta memoria. Los códigos ejemplos pueden ser encontrados en el menú: Archivos->Ejemplos->EEPROM. Vea la Figura 25.

 

 

Figura 25 - Menu de codigo ejemplo para memoria EEPROM
Figura 25 - Menu de codigo ejemplo para memoria EEPROM

 

 

 

Caso quiera incluir la librería en algún proyecto es necesario ir al menú: Programa->Incluir Librería->EEPROM. Para esto, vea la Figura 26.

 

 

 

Figura 26 - Menu para incluir la libreria EEPROM
Figura 26 - Menu para incluir la libreria EEPROM

 

 

 

INSTRUCCION PARA ESCRIBIR EN MEMORIA EEPROM (EEPROM.write).

 

La instrucción EEPROM.write permite escribir un byte en la memoria EEPROM. Para esto usamos la función write de la siguiente manera:

 

EEPROM.write(address, value)

Parametros:

address: la localización a escribir, comenzando de 0 (int)

value: el valor a escribir de 0 hasta 255 (byte)

Retorno:

ninguno

 

En el siguiente ejemplo, almacenamos los valores leídos desde la entrada analógica 0, en la memoria EEPROM. Estos valores permanecerán en la memoria EEPROM, cuando el circuito sea apagado (off) y pueden ser leídos por otro programa. La Figura 27 muestra el circuito usado para este programa.

 

 

Figura 27 - Circuito para testar la memoria EEPROM en la placa Arduino Uno
Figura 27 - Circuito para testar la memoria EEPROM en la placa Arduino Uno

 

 

El programa puede ser encontrado en el menú: Archivos->Ejemplos->EEPROM->eeprom_read.

 

#include

* the current address in the EEPROM (i.e. which byte we're going to write to next) **/
int addr = 0;

void setup() {

   /** Empty setup. **/

}


void loop() {
   /***
   Need to divide by 4 because analog inputs range from
   0 to 1023 and each byte of the EEPROM can only hold a
   value from 0 to 255.
***/

int val = analogRead(0) / 4;

/***
Write the value to the appropriate byte of the EEPROM.
these values will remain there when the board is
turned off.
***/


EEPROM.write(addr, val);


/***
Advance to the next address, when at the end restart at the beginning.
Larger AVR processors have larger EEPROM sizes, E.g:
- Arduno Duemilanove: 512b EEPROM storage.
- Arduino Uno: 1kb EEPROM storage.
- Arduino Mega: 4kb EEPROM storage.

Rather than hard-coding the length, you should use the pre-provided length function.

This will make your code portable to all AVR processors.

***/

   addr = addr + 1;
   if (addr == EEPROM.length()) {
       addr = 0;
   }

  /***
   As the EEPROM sizes are powers of two, 
    wrapping (preventing overflow) of an
    EEPROM address is also doable by a bitwise and of the length - 1.

    ++addr &= EEPROM.length() - 1;

    ***/


   delay(100);
  }

 

 

INSTRUCCIONES PARA LEER UN BYTE DE LA MEMORIA EEPROM (EEPROM.read)

 

La instrucción EEPROM.read permite leer un byte desde la memoria EEPROM. Si por acaso la localización de memoria leída, nunca ha sido escrita el valor devuelto será 255 o 0Xff en hexadecimal. El formato de la instrucción read es el siguiente:

 

EEPROM.read(address)

Parametros:

address: la localizacion a leer, comenzando desde 0 (int)

Retorno:

El valor almacenado en la localizacion (byte)

 

El siguiente programa lee los valores de cada byte de la memoria EEPROM e imprime el al computador. Para esto es necesario abrir el monitor serial. Este programa lo puede encontrar en el menú: Archivos->Ejemplos->EEPROM->eeprom_read.

 

#include

// start reading from the first byte (address 0) of the EEPROM

int address = 0;
byte value;


void setup() {
   // initialize serial and wait for port to open:
   Serial.begin(9600);
   while (!Serial) {
      ; // wait for serial port to connect. 
         Needed for native USB port only
    }
}


void loop() {
   // read a byte from the current address of the EEPROM
   value = EEPROM.read(address);

   Serial.print(address);
   Serial.print("\t");
   Serial.print(value, DEC);
   Serial.println();

   /***
    Advance to the next address, when at the end restart at the beginning.
    Larger AVR processors have larger EEPROM sizes, E.g:
    - Arduno Duemilanove: 512b EEPROM storage.
    - Arduino Uno: 1kb EEPROM storage.
    - Arduino Mega: 4kb EEPROM storage.
    Rather than hard-coding the length, you should use the pre-provided length function.
    This will make your code portable to all AVR processors.
     ***/
     address = address + 1;

     if (address == EEPROM.length()) {
        address = 0;
     }

     /***
     As the EEPROM sizes are powers of two, wrapping (preventing overflow) of an
    EEPROM address is also doable by a bitwise and of the length - 1.

     ++address &= EEPROM.length() - 1;
     ***/

    delay(500);

}

 

Como conclusión, podemos ver que para transferir datos de un lugar de memoria para otro, usamos el operador de asignación (=). Para transferir datos para la memoria EEPROM, podemos usar funciones como EEPROM.write o EEPROM.read. Asi, es muy fácil la transferencia o movimiento de datos entre localizaciones de memoria.

 

Buscador de Datasheets



N° de Componente