3. Memoria y periféricos del microcontrolador.

memss

En este tutorial vamos a ver cómo funciona un microcontrolador internamente, cómo debemos manipular los registros de su memoria en el programa en C para que el microcontrolador realice la tarea requerida dentro del circuito electrónico.

En la hoja de características del microcontrolador podemos ver que cada uno de sus pines tiene uno o varios nombres:

Pines ATtiny841

Pines ATtiny841

Ese nombre indica las distintas tareas que el microcontrolador puede realizar con ese pin dentro del circuito electrónico. Algunos pines se pueden usar para leer un valor analógico, otros para generar una señal PWM, para establecer una comunicación mediante un protocolo serie, etc.. Es decir el nombre del pin indica a que parte del hardware del microcontrolador se puede conectar dicho pin.

En la hoja de características del microcontrolador podemos ver los distintos módulos hardware (periféricos) que lo componen:

Módulos hardware internos de un ATtiny841.

Módulos hardware internos de un ATtiny841.

Cada cuadrado de la imagen anterior lo podemos ver como un circuito electrónico que está dentro del microcontrolador y capaz de realizar una función determinada. Los módulos se comunican con la CPU del microcontrolador, donde se ejecutan las instrucciones del programa, y con el mundo exterior mediante los pines de entrada y salida.

En el programa que hacemos para el microcontrolador se configura el funcionamiento de los distintos módulos, así como qué módulo va conectado a cada pin del microcontrolador. Para saber cómo se configuran y se usan los distintos módulos (periféricos) del microcontrolador tenemos que mirar como se estructura su memoria.

El microcontrolador tiene 3 tipos de memorias: la memoria de programa FLASH, la memoria de datos SRAM, y la memoria de datos EEPROM.

Memorias ATtiny841

Memorias ATtiny841

La FLASH es la zona de memoria donde se almacena el programa que grabamos en el microcontrolador, contiene el conjunto de instrucciones que la CPU del microcontrolador ejecuta de manera secuencial para realizar una tarea. Esta memoria no se pierde cuando se desconecta la alimentación del microcontrolador y no puede ser modificada mediante la ejecución del programa.

La memoria FLASH está organizada en 4096 direcciones de memoria de 16 bits cada una.

Memoria Flash

Memoria Flash

La memoria EEPROM es una memoria que podemos leer y escribir mientras se ejecuta el programa para almacenar datos. Cuando el microcontrolador pierde la alimentación su contenido no se borra, la próxima vez que encendamos el circuito con el microcontrolador estarán los datos almacenados por el programa.

La memoria eeprom se organiza en 512 direcciones de memoria, cada dirección de memoria contiene un Byte de datos.

EEPROM

EEPROM

Por último nos queda la memoria SRAM que es una memoria volátil, si el microcontrolador se desconecta de la alimentación la información almacenada en ella se pierde. El programa del microcontrolador usa esta memoria para almacenar variables que necesita durante su ejecución, así como también para configurar y comunicarse con los módulos hardware que tiene en su interior.

Memoria SRAM ATtiny841

Memoria SRAM ATtiny841

La memoria de SRAM se organiza en grupos de 8 bits llamados registros a los que se les asigna una dirección.

Los primeros 32 registros de la memoria SRAM se corresponden con unos registros de acceso rápido que utiliza la CPU durante la ejecución del programa.

En los siguientes 224 bytes (I/O register file) están los registros que van a configurar y controlar el funcionamiento de los pines del microcontrolador, y el funcionamiento de los distintos módulos hardware (periféricos: UART, ADC, timers, etc..) que podemos encontrar en el interior de éste. En función del valor de los bits de cada registro asociado a un periférico definiremos su configuración y funcionamiento.

En la dirección 0x0100 encotramos 512 direcciones de memoria de datos que el programa utilizará para almacenar variables necesarias para su correcta ejecución, así como para almacenar el stack.

La principal idea a tener es que los datos se almacenan en grupos de 8 bits (una variable de mayor tamaño en el programa requiere más de un acceso a la memoria). Y que al hardware del microcontrolador (pines, periféricos) accedemos a él como si de variables de nuestro programa se tratasen, es decir, el hardware está mapeado en memoria.

Por ejemplo si queremos utilizar los pines del puerto A (A0, A1,…. , A7) como salidas digitales, debemos operar sobre las direcciones de memoria (registros) que definen el funcionamiento de esos pines como salidas digitales.

Registros salidas digitales PORTA.

Registros salidas digitales PORTA.

Poniendo a 1 el valor de los bits de la dirección de memoria 0x1A en la SRAM, denominada DDRA, establecemos un pin del puerto A como salida. Y poniendo a 1 ó a 0 los bits de la dirección 0x1B, denominada PORTA, damos un nivel alto o bajo al pin de salida correspondiente.

El microcontrolador lo vamos a programar en lenguaje C usando como compilador AVR-gcc, lo primero que debemos aprender es cómo cambiar el valor de los bits de los registros asociados al hardware del microcontrolador. Se puede hacer de varias formas, una muy sencilla es aplicar una máscara con los operadores AND (&) y OR (|) (no confundir con && y ||) sobre el registro que queramos modificar.

Resultado operación OR.
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1

Resultado operación AND.
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1

Los operadores AND y OR actuan a nivel de bit. Si tenemos dos registros de 8 bits:

Registro_1 = (bit7, bit6, bit5, bit4, bit3, bit2, bit1, bit0)

Registro_2 = (BIT7, BIT6, BIT5, BIT4. BIT3, BIT2, BIT1, BIT0)

y hacemos la operación Registro_1 & Registro_2, el resultado serán 8 bits cuyo valor es el resultado de:

Resultado = (bit7 & BIT7, bit6 & BIT6, bit5 & BIT5, bit4 & BIT4, bit3 & BIT3, bit2 & BIT2, bit1 & BIT1, bit0 & BIT0)

El operador OR actua de igual forma haciendo la operación OR bit a bit.

Por ejemplo si queremos poner el bit 5 del registro DDRA (dirección en la memoria SRAM 0x1A) a 1, dejando el resto de bits de DDRA como estaban, escribiremos una de las siguientes líneas de código en el programa en C usando el operador OR y una máscara:

DDRA = DDRA | 0b00100000;
DDRA |= 0x20;  // DDRA |= equivalente a DDRA = DDRA | ..

En este caso a la constante 0b00100000 (en binario) ó 0x20 (en hexadecimal) se le llama máscara. La línea de código anterior dejará el resto de bits de DDRA como estaban: X | 0 = X y pondrá a 1 el bit 5: X | 1 = 1. Siendo X cualquier valor del bit (0 ó 1).

Si en lugar de a 1 queremos poner el bit anterior de DDRA a 0, usaremos el operador AND con la máscara correspondiente:

DDRA = DDRA & 0b11011111;
DDRA &= 0xDF; // DDRA &= equivalente a DDRA = DDRA & ..

La línea de código anterior dejará el resto de bits de DDRA como estaban: X & 1 = X y pondrá a 0 el bit 5: X & 0 = 0. Siendo X cualquier valor del bit (0 ó 1).

Aplicando una máscara con el operador & o | podemos modificar uno o varios bits de un registro sin afectar al resto de bits de ese registro, es decir cambiar solo los bits que nos interesen.

Siempre que hacemos un programa para el microcontrolador vamos a poner un #include con <avr/io.h>. Al hacer esto incluimos en nuestro código las librerías necesarias donde están definidos todos los nombres de las posiciones de memoria (registros) del hardware del microcontrolador, así como la posición del nombre de los bits de los registros.

Mirando las librerías que se han incluido con <avr/io.h> en nuestro programa en C:

Con #include  definimos las posiciones de memoria del hardware del microcontrolador.

Con #include <avr/io.h> definimos las posiciones de memoria del hardware del microcontrolador.

Podemos ver como están definidos los registros del hardware del microcontrolador con la posición de memoria que ocupan en la SRAM, así como el nombre de cada uno de sus bits que compone el registro, definiendo el valor de su posición dentro del registro.

Por lo que vamos a ver una forma más cómoda de crear la máscara sin tener que estar escribiendola directamente, para ello vamos a utilizar los operadores &, |, “<<” desplazamiento a la izquierda y “~” NOT.

Si como en el ejemplo anterior queremos poner el bit 5 de DDRA a 1, una forma más sencilla de hacerlo que estar escribiendo 0b00100000 para crear la máscara es la siguiente:

DDRA |= (1<<DDRA5); // (1<<5) = 00100000, DDRA5 se ha definido como 5

la línea anterior es equivalente a:

DDRA = DDRA | 0b00100000;

Si en lugar de modificar un solo bit de DDRA queremos cambiar varios podemos escribir la siguiente línea para crear la máscara:

DDRA |= ((1<<DDRA5) | (1<<DDRA2) |(1<<DDRA0));

La línea anterior es equialente a:

DDRA = DDRA | 0b00100101;

De igual forma podemos crear la máscara para poner a 0 los bits de un registro usando los operadores &, | y ~.

Si por ejemplo queremos poner el valor del bit 5 de DDRA a 0 escribiremos lo siguiente:

DDRA &= ~(1 << DDRA5); //~(1 << DDRA5) = ~(00100000) = (11011111)

La línea anterior es equivalente a:

DDRA = DDRA & 0b11011111;

Si queremos poner a 0 varios bits de un registro en una línea escribiremos lo siguiente:

 DDRA &= ~((1 << DDRA5) | (1 << DDRA2) | (1 << DDRA0));

La línea anterior es equivalente a:

DDRA = DDRA & 0b11011010;

Puede parecer un poco complicado estar manipulando los bits de los registros de esta forma, pero cuando se hace unas pocas veces se vuelve algo muy sencillo. Ya que solo tenemos que saber el nombre del registro que controla el módulo hardware que queremos utilizar, y el nombre del bit de ese registro que queremos poner a 1 ó 0. La información sobre los registros y su configuración la encontramos en el datasheet del microcontrolador.

Si alguien no conoce el uso de estos operadores de bit (&, |, ~, <<, ^) con leerse las páginas correspondiente en un libro de C será suficiente.

En los siguientes tutoriales se muestra el uso del hardware del microcontrolador con ejemplos, por lo que si no queda muy claro ahora la forma de programar el microcontrolador quedará más claro cuando se vea el código para un ejemplo en particular. En este tutorial solo ha de quedar claro que para controlar el hardware y los pines del microcontrolador debemos escribir los bits de ciertos registros (posiciones de memoria), para ello usaremos los operadores & y | sobre el registro con una máscara.

Enlaces relacionados: