Archivo de la categoría: Curso Avanzado 2017

Memoria Arduino

Arduino y todos los microcontroladores tienen varios tipos de memoria integradas en el mismo chip, en el caso de Arduino y los microcontroladores AVR de Atmel usan tres tipos de memorias:

  • SRAM (static random access memory): Variables locales, datos parciales. Usualmente se trata como banco de registros y memoria volátil. Es la zona de memoria donde el sketch crea y manipula las variables cuando se ejecuta. Es un recurso limitado y debemos supervisar su uso para evitar agotarlo.
  • EEPROM:  Memoria no volátil para mantener datos después de un reset. Se puede grabar desde el programa del microcontrolador, usualmente, constantes de programa. Las EEPROMs tienen un número limitado de lecturas/escrituras, tener en cuenta a la hora de usarla. Esta memoria solo puede leerse byte a byte y su uso puede se un poco incómodo. También es algo más lenta que la SRAM. La vida útil de la EEPROM es de unos 100.000 ciclos de escritura
  • Flash: Memoria de programa. Usualmente desde 1 Kb a 4 Mb (controladores de familias grandes). Es donde se guarda el sketch ya compilado. Sería el equivalente al disco duro de un ordenador. En la memoria flash también se almacena del bootloader. Se puede ejecutar un programa desde la memoria flash, pero no es posible modificar los datos, sino que es necesario copiar los datos en la SRAM para modificarlos.
    La memoria flash usa la misma tecnología que las tarjetas SD, los pen drives o algunos tipos de SSD, esta memoria tiene una vida útil de unos 100.000 ciclos de escritura, así que cargando 10 programas al día durante 27 años podríamos dañar la memoria flash.

Memorias en Arduino:

La memoria flash y la EEPROM son no volátiles, es decir, la información persiste tras el apagado del Arduino.

Memoria de Arduino UNO:

  • Flash  32k bytes (of which 0.5k is used for the bootloader)
  • SRAM   2k bytes
  • EEPROM 1k byte

Memoria de Arduino MEGA:

  • Flash  256k bytes (of which 8k is used for the bootloader)
  • SRAM   8k bytes
  • EEPROM 4k byte

Memoria de Arduino MKR1000:

  • Flash  256k bytes
  • SRAM   32k bytes
  • EEPROM no. Dispone de EEPROM emulation en la memoria flash (ver documentación del microcontrolador)

Memoria ESP8266:

  • 64 KiB of instruction RAM, 96 KiB of data RAM
  • External QSPI flash – 512 KiB to 4 MiB (no dispone de memoria Externa)

La memoria SRAM es un recurso escaso que debe gestionarse, especialmente si se usan los strings o cadenas de caracteres de forma intensiva. Si un Arduino se queda sin memoria SRAM, el sketch compilará bien y se cargará en el Arduino sin problema, pero se producirán efectos inesperados.

En caso de usar muchos strings, una técnica para evitar agotar la memoria SRAM es guardar en la memoria flash los strings que no se modifiquen en tiempo de ejecución, usando PROGMEM: https://www.arduino.cc/en/Reference/PROGMEM

Desde la versión 1.0 del IDE de Arduino, se introdujo la macro F(). Esta sintaxis se usa para almacenar strings en la memoria flash en lugar de en la memoria SRAM. No es necesario cargar ninguna librería para usar la macro F().

  • Serial.println(«This string will be stored in flash memory»); //este print ocupará 42 bytes de memoria SRAM con el contenido de la constante string
  • Serial.println(F(«This string will be stored in flash memory»)); //el string dentro de del println no se carga en la SRAM y se lee de la flash

En el caso que el sketch ocupe más memoria flash, el IDE te avisa de que no puede cargarlo en Arduino.

Desde las últimas versiones del IDE de Arduino tras compilar el sketch, aparece un resumen de la memoria flash que ocupa el programa y la memoria ocupada por las variables globales en la SRAM y el espacio que queda para las variables locales. Como recomendación, si se supera el 70%-75% de la  SRAM con las variables globales es muy probable que Arduino se quede sin memoria RAM.

Recordar que al incluir una librería, estoy añadiendo variables y tamaño al programa, lo que aumentará el uso de memoria SRAM y flash. Algunas librerías hacen un uso grande de la memoria SRAM y flash.

Memoria en Arduino:

Un buen tutorial para aprender como funcionan las memorias de Arduino: https://learn.adafruit.com/memories-of-an-arduino/you-know-you-have-a-memory-problem-when-dot-dot-dot

Toda la información de las memoria del ATMega328p está en la página 34 del datasheet http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Más información de las memorias de Arduino

Para profundizar sobre el uso de memoria en los microcontroladores AVR de Atmel, memoria dinámica, reserva y liberar memoria, visitar los siguientes enlaces:

Memoria SRAM

Al ser el recurso más escaso en Arduino hay que entender bien cómo funciona. La memoria SRAM puede ser leída y escrita desde el programa en ejecución.

La memoria SRAM es usada para varios propósitos:

  • Static Data: Este bloque de memoria reservado en la SRAM para todas las variables globales y estáticas. Para variables con valores iniciales, el sistema copia el valor inicial desde la flash al iniciar el programa.
  • Heap: Es usado para las variables o elementos que asignan memoria dinámicamente. Crece desde el final de la zona de Static Data a medida que la memoria es asignada. Usada por elementos como los objetos y los Strings.
  • Stack: Es usado por las variables locales y para mantener un registro de las interrupciones y las llamadas a funciones. La pila crece desde la zona más alta de memoria hacia el Heap. Cada interrupción, llamada de una función o llamada de una variable local produce el crecimiento de la memoria.
    La mayor parte de los problemas ocurren cuando la pila y el Heap colisionan. Cuando esto ocurre una o ambas zonas de memoria se corrompen con resultados impredecibles. En uno casos se produce un “cuelgue” del programa y en otros casos la corrupción de memoria puede notarse un tiempo después.

A partir de la versión 1.6 del IDE al compilar un sketch nos da el tamaño que va a ocupar en la flash el proyecto y el espacio que va a ocupar en la SRAM las variables globales, es decir, la zona de static data.

Como ya se ha visto anteriormente, en la memoria SRAM también se encuentran los registros que ocupan las primeras 256 direcciones de memoria. Por lo tanto la SRAM empieza a partir de la dirección 0x0100.

Los registros al estar en la SRAM son volátiles y no conservan su valor después de un reset. Mirando la documentación del microcontrolador se puede ver cuales son los valores por defecto de los registros.

Si vemos a fondo la memoria SRAM de Arduino a partir de la dirección 0x0100:

  • .data variables is the first RAM section and it is used to store program static data, such as strings, initialized structures and global variables.
  • .bss variables is the memory allocated for uninitialized global and static variables.
  • heap is the dynamic memory area, and this is the playground area for malloc (and alike). The heap can grow (when new allocation is made) or «possibly» decrease in size (when memory is released, as for example when using free) based on the requirements.
  • stack is the memory area located at the end of the RAM and it grows towards the heap area. The stack area is used for function calls, storing values for local variables. Memory occupied by local variables is reclaimed when the function call finished.
  • external RAM is only available to some of the MCUs and it means that it is possible to add RAM in a kind of similar way that we do for a PC. Usually this is expensive (a few KB of external RAM costs in general more than the MCU) and requires also advanced hardware and software skills.
  • free available memory is the area between heap and stack and this is what we need to measure in order to detect problems caused by not enough RAM resources.When this area is either too small for the required tasks, or is missing at all (heap meets stack), our MCU starts to missbehave or to restart itself.

Más información sobre heap y stack:

Calcular Memoria SRAM Libre

El siguiente código permite calcular la memoria libre en bytes para un Arduino y funciona tanto con el IDE de Arduino como con Atmel Studio:

 
extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;

uint16_t getFreeSram() {
  uint8_t newVariable;
  // heap is empty, use bss as start memory address
  if ((uint16_t)__brkval == 0)
    return (((uint16_t)&newVariable) - ((uint16_t)&__bss_end));
  // use heap end as the start of the memory address
  else
    return (((uint16_t)&newVariable) - ((uint16_t)__brkval));
};

Explicación del código:

  • extern en un cualificador de variables que indica al compilador de la existencia de variables globales definidas en otros fichero de cabecera que hemos incluido (#include) y no es necesario definirlas en nuestro fichero.
    extern int x; le dice al compilador que un objeto llamado x de tipo int existe en algún sitio.
  • La función getFreeRam define una nueva variable (llamada NewVariable), que se se almacenará una variable local de una función en la pila (stack). Debido a que el área de memoria de pila crece hacia el heap, la dirección de memoria de esta nueva variable es la última dirección de memoria utilizada por la pila en el momento de llamar a este método.
  • El *__brkval es un puntero a la última dirección de memoria (hacia la pila) utilizado por el heap. No tenemos que preocuparnos acerca de la gestión de __brkval ya que esto se hace internamente.
  • También tenemos que estar seguros de que heap no está vacío, porque entonces __brkval no se puede utilizar (que es un puntero NULL). Si el heap está vacío, entonces usamos __bss_end que es una variable definida internamente, y se almacena en la última parte de la zona de memoria RAM variables de .bss
  • La cantidad de memoria libre de RAM es la diferencia entre las direcciones de memoria usada por la nueva variable newVariable y dirección referenciada por __brkval o la dirección de _bss_end si la zona de heap está vacía.
  • El resultado es el número de bytes en MCUs de 8 bits como en Arduino uno. En el caso de MCUs de 32 bits como el Arduino DUE, que son bloques de 32 bits.

Más información: http://web-engineering.info/node/30

Este código está disponible en la librería: https://github.com/maniacbug/MemoryFree

Y la versión revisada: https://github.com/McNeight/MemoryFree

En el playground de Arduino hay más información sobre el cálculo de memoria disponible: https://playground.arduino.cc/Code/AvailableMemory

Un código más simple de calcular la memoria libre:

 
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Códigos para cálculo de memoria libre SRAM

Como medir a memoria libre flash, SRAM y EEPROM: https://learn.adafruit.com/memories-of-an-arduino/measuring-free-memory  

I/O Memory

El espacio de memoria I/O de los registros son un conjunto de registros adiciona a los registros de propósito general.

Los 32 (32x8bits) registros de propósito general están directamente conectados con la ALU.

Todas las I/Os (Inputs/Oputputs) y periféricos están localizados en el espacio de memoria I/O. Todas las localizaciones de los registros I/O pueden ser accedidos mediante las instrucciones LD/LDS/LDD y ST/STS/STD, transfiriendo datos entre los registros de propósito general y el espacio I/O.

Instrucción LDS (Load Direct from Data Space): http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_lds.html

Instrucción SBI (Set Bit in I/O Register): http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_sbi.html

Instrucción CBI (CBI – Clear Bit in I/O Register): http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_CBI.html

Lista de instrucciones en ensamblador:  http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_instruction_list.html

Los registros I/O en el rango 0x00-0x1F son accesible directamente mediante las funciones SBI y CBI.  En estos registros el valor de los bits puede ser leído con las instrucciones SBIS y SBIC.

Memoria Flash

La memoria flash —derivada de las siglas EEPROM— permite la lectura y escritura de múltiples posiciones de memoria en la misma operación. Gracias a ello, la tecnología flash, siempre mediante impulsos eléctricos, permite velocidades de funcionamiento muy superiores frente a la tecnología EEPROM primigenia, que sólo permitía actuar sobre una única celda de memoria en cada operación de programación. Se trata de la tecnología empleada en los dispositivos denominados memoria USB.

Más sobre la memoria flash.

En Arduino la memoria flash o espacio de programa es donde el sketch de arduino en binario es almacenado.

La memoria flash en Arduino está dividida o particionada en dos zonas  una para el bootloader y otra para almacenar el sketch.

El bootloader se trata de un programa especial y puede leer datos de una fuente externa como UART, I2C, CAN, etc… para reescribir el programa guardado en la memoria flash del microcontrolador. El bootloader es un programa que se ejecuta inmediatamente antes de ejecutar el programa que hay en la memoria flash al que cede el control cuando finaliza su ejecución.

Más información sobre el bootloader en: https://aprendiendoarduino.wordpress.com/2016/11/09/bootloader/

Incluso se podría particionar la memoria flash para dejar una zona con un sistema de ficheros donde se podrían almacenar archivos, como ya se hace en algunos casos con el ESP8266.

PROGMEM

PROGMEM se usa para guardar en memoria flash en lugar de en la SRAM y ahorrar espacio en la SRAM, especialmente cuando se usa gran cantidad de cadenas de caracteres. La palabra PROGMEM en un modificador de variable que debe usarse solo con los tipos de datos definidos en pgmspace.h. Al usarlo le dice al compilador que ponga la información de la variable en la memoria flash en lugar de la SRAM, donde iría normalmente.

PROGMEM es parte de la librería pgmspace.h http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html que solo está disponible para la arquitectura AVR, así que para usarlo hay que inclirla al principio del sketch con #include <avr/pgmspace.h>

Más información:

  • sizeof() – devuelve el número de bytes en una variable o el número de bytes ocupados por un array.
  • PROGMEM

En muchos casos, una gran cantidad de RAM es ocupada por la memoria estática, como resultado del uso de variables globales (tales como cadenas o números). Siempre que estos datos no se vayan a cambiar, puede ser fácilmente almacenado en la llamada PROGMEM (memoria de programa o flash). Esto ocuparía un trozo de la memoria flash, y es bueno saber que, en general, la memoria flash es mucho más grande que la memoria RAM (por ejemplo, Atmega2560 tiene 8 KB de RAM y flash de 256 KB). Una desventaja de usar PROGMEM podría ser la velocidad de lectura, que es más lento en comparación con la lectura de los mismos datos de la RAM, aunque la diferencia de velocidad no es mucha.

La verdadera utilidad de PROGMEM es en bloques de datos grandes que necesitan ser almacenados en la flash. El uso de PROGMEM se hace en dos pasos, después de hacer que la variable se guarde en la flash, necesitamos de varios métodos definidos en la librería pgmspace.h, para leer los datos de la flash y cargarlos en la SRAM.

IMPORTANTE: para el uso de PROGMEM, las variables deben ser o bien definidas de forma global o definidas como static.

La sintaxis de uso de PROGMEM es:

 
const dataType variableName[] PROGMEM = {};   // use this form
const PROGMEM  dataType  variableName[] = {}; // or this form
const dataType PROGMEM variableName[] = {};   // not this one

Más información sobre PROGMEM en Arduino:

La macro F() se puede usar para facilitar el manejo de PROGMEM en las instrucciones print, de forma que todo el texto a imprimir (ya sea en Serial, Ethernet u otra librería) se lea de la Flash y no ocupando tamaño en la SRAM. Esta macro está incluida en el core de Arduino.

Cuando un sketch tiene problemas de memoria SRAM, el primer y más sencillo paso a aplicar es poner todos los print con la macro F.

Sintaxis:

 
Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));

Más información sobre la macro F y consideraciones a tener en cuenta al usarla: https://www.baldengineer.com/arduino-f-macro.html

Ver librería Flash que facilita el uso de PROGMEM: http://arduiniana.org/libraries/flash/

Fuses & Lock Bits

Ya sabemos lo que es la flash, EEPROM y RAM como parte de la MCU, pero no se ha mencionado anteriormente que hay 3 bytes de memoria permanente llamados fuses. Los fuses determinan cómo se va a comportar el microcontrolador, si tiene bootloader, a que velocidad y voltaje va a funcionar la MCU, etc… Notar que a pesar de llamarse fuses (fusibles) puede configurarse una y otra vez y no tienen nada que ver con la protección de sobrecorrientes.

Los tres bytes que conforman los fuses en el ATMega328p:

También hay un cuarto byte que se usa para programar los lock bits. Los lock bits pueden ser usados para restringir la lectura/escritura en la memoria de programa (flash), la zona de boot y la de aplicación tienen sus propios lock bits y es posible controlar el acceso a ellos de forma separada.

Para cambiar la configuración de los fuses, puede usarse el avrdude.Tutorial para cambiar la configuración de los fuses: http://www.instructables.com/id/How-to-change-fuse-bits-of-AVR-Atmega328p-8bit-mic/

Arduino default fuse settings: http://www.codingwithcody.com/2011/06/25/arduino-default-fuse-settings/

Calculadora de Fuses:

Más información:

Práctica: Uso de Memoria en Arduino

Para entender el uso de la memoria, hagamos una práctica añadiendo y quitando elementos del sketch y viendo la ocupación de memoria.

Función para calcular memoria libre en Arduino:

 
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Paso 1: Calcula memoria RAM y Flash en Arduino UNO de un programa que solo ejecute la función freeRam en el loop cada 30 segundos y muestre el dato por el monitor serie.

Solución Ejercicio14_1: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio14-Memoria_1

Paso 2: Calcula memoria RAM y Flash en Arduino UNO de un programa que imprima por el monitor serie en cada loop cada 30 segundos el siguiente texto:

“Arduino es una plataforma de hardware libre, basada en una placa con un microcontrolador y un entorno de desarrollo, diseñada para facilitar el uso de la electrónica en proyectos multidisciplinares. El hardware consiste en una placa con un microcontrolador Atmel AVR y puertos de entrada/salida. Los microcontroladores más usados son el Atmega168, Atmega328, Atmega1280, ATmega8 por su sencillez y bajo coste que permiten el desarrollo de múltiples diseños. Por otro lado el software consiste en un entorno de desarrollo que implementa el lenguaje de programación Processing/Wiring y el cargador de arranque que es ejecutado en la placa.Desde octubre de 2012, Arduino se usa también con microcontroladoras CortexM3 de ARM de 32 bits,5 que coexistirán con las más limitadas, pero también económicas AVR de 8 bits. ARM y AVR no son plataformas compatibles a nivel binario , pero se pueden programar con el mismo IDE de Arduino y hacerse programas que compilen sin cambios en las dos plataformas. Eso sí, las microcontroladoras CortexM3 usan 3,3V, a diferencia de la mayoría de las placas con AVR que generalmente usan 5V. Sin embargo ya anteriormente se lanzaron placas Arduino con Atmel AVR a 3,3V como la Arduino Fio y existen compatibles de Arduino Nano y Pro como Meduino en que se puede conmutar el voltaje.”

Solución Ejercicio14_2: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio14-Memoria_2

Paso 3: Calcula memoria RAM y Flash en Arduino UNO del programa del paso 2 pero usando la macro F en el Serial.print.

Solución Ejercicio14_3: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio14-Memoria_3

Práctica: Velocidad Memoria en Arduino

Hacer un programa para hacer una comparativa de la velocidad de lectura de la memoria SRAM, Flass y EEPROM.

Solución Ejercicio15: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio15-Velocidad_Memoria

Para ampliar ver ejercicio de llenar memoria: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Otros/llenarMemoria

Puertos Analógicos Arduino Avanzado

Ya hemos visto cómo manejar con registros las entradas y salidas de Arduino, pero nos centrado en las correspondientes a las I/O digitales. Para el caso de las entradas y salidas analógicas hay muchos detalles que no se han visto.

Los 6 pines correspondientes a las entradas analógicas A0..A5 del Arduino UNO corresponde al puerto C. Estos 6 pines se pueden usar como entradas y salidas digitales como cualquier otro puerto digital como hemos visto en el apartado anterior. Además de denominarlas A0..A5, también es posible llamarlas como pines 14..19.

La forma de manejar con registros las entradas analógicas correspondientes al puerto C con PORT, DDR y PIN es para usar esos pines como I/O digitales, puesto que los pines de los microcontroladores son multipropósito como se ha dicho anteriormente.

Tabla de equivalencia:

  • Pin 14 = Analog in 0
  • Pin 15 = Analog in 1
  • Pin 16 = Analog in 2
  • Pin 17 = Analog in 3
  • Pin 18 = Analog in 4
  • Pin 19 = Analog in 5

Por ejemplo estas tres instrucciones son equivalentes:

  • analogRead(0);
  • analogRead(A0);
  • analogRead(14);
  • digitalWrite(A1);
  • digitalWrite(15);

Pin mapping ampliado:

Entradas Analógicas

En las entradas analógicas entran en juego los conversores Analógico Digital (ADC)

Toda la información de ADC para entradas analógicas se encuentra en la página 305 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Conversor analógico digital (ADC)

Un microcontrolador solo entiende señales digitales (1’s y 0’s), por lo tanto para poder leer señales analógicas necesitamos los convertidores Analógico a Digital (ADC).

Cómo funciona un conversor analógico a digital:

ADC en Arduino

El microcontrolador de Arduino UNO contiene internamente un conversor analógico a digital de 6 canales. El conversor tiene una resolución de 10 bits, devolviendo enteros entre 0 y 1023. Los pines analógicos de Arduino también tienen todas las funcionalidades de los pines digitales. Por lo tanto, si necesitamos más pines digitales podemos usar los pines analógicos.

En arduino los pines analógicos se definen y tienen las propiedades siguientes: http://arduino.cc/en/Tutorial/AnalogInputPins

El datasheet de ATmega advierte de hacer lecturas rápidas entre pines analógicos (analogRead). Esto puede causar ruido eléctrico e introducir jitter en el sistema analógico. Se aconseja que después de manipular pines analógicos (en modo digital), añadir un pequeño retraso antes de usar analogRead () para leer otros pines analógicos.

Un microcontrolador solo entiende señales digitales (1’s y 0’s), por lo tanto para poder leer señales analógicas necesitamos los convertidores Analógico a Digital (ADC). Esta conversión consiste en la transcripción de señales analógicas en señal digital, con el propósito de facilitar su procesamiento (codificación, compresión, etcétera) y hacer la señal resultante (digital) más inmune al ruido y otras interferencias a las que son más sensibles las señales analógicas.

Para el ATMega328p toda la información del conversor analógico a digital se encuentra en la página 305 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

El ATMega328p, al igual que toda la gama ATMega de Atmel y otros microcontroladores, tienen un ADC integrado y no necesita ningún hardware adicional, esto nos permite simplemente conectar un sensor analógico. El ADC interno del microcontrolador tiene una resolución de 10 bits, esto significa que la tensión analógica de entrada se convierte en un valor numérico entre 0 y 1023.

NOTA: en el caso de una Raspberry Pi necesitamos de un ADC externo como el MCP3008 https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/overview

Aunque el ATmega328P tiene 6 pines que son capaces de ser utilizados como pines de entrada analógicos (Port C), sólo hay un ADC en el microcontrolador, pero entre el ADC y los pines hay un multiplexor analógico, esto permite que podamos elegir qué pin está conectado al ADC, es decir, aunque podemos utilizar todos los pines, sólo se puede leer el valor de uno de ellos a la vez, para casi todas las aplicaciones esto es más que suficiente, pero en algunos casos limitados que necesitan lecturas ADC de alta velocidad se podría necesitar el uso de ADC externos. En el caso de la ATmega328P los pines que se pueden utilizar una entrada analógica son todos los del puerto C.

También se puede cambiar la tensión máxima (siempre por debajo de Vcc) que utiliza el ADC como referecia, es la  llamada tensión de referencia y es la tensión contra la que todas las entradas analógicas hacen las conversiones. Esta tensión de referencia se toma del pin AREF. Reducir el voltaje máximo del ADC tiene sentido para mejorar la resolución del ADC. Con 5V la resolución es de 5/1023 = 4,88 mV para cada valor, pero para un sensor que no pasa de 3.3V la resolución es de 3.3/1023 = 3.22mV.

El ADC interno también se puede utilizar en un modo de 8 bits, donde sólo se utilizan los 8 bits más significativos de la resolución de 10 bits completa, esto podría ser útil cuando se trabaja en ambientes ruidosos y sólo necesita 8 bits de resolución, el uso de este modo es un plus debido a que no es necesario dedicar más tiempo de CPU calculando los 10 bits completos. El ADC también puede configurarse para que lleve a cabo una conversión y detenerse o puede ser configurado para funcionar en un modo de funcionamiento libre, la primera opción es la mejor opción cuando queremos leer diferentes pines, y el segundo es mejor cuando sólo tenemos que leer un pin y esto puede ahorrar algo de tiempo entre las conversiones.

También tenemos que tener cuidado de la frecuencia máxima de trabajo del ADC, este valor se especifica en la ficha técnica y es de 200 kHz, este es el valor del reloj interno de la circuitería del ADC y se genera dividiendo el reloj principal ATmega, que en el caso del UNO es 16 MHz, este divisor del reloj se realiza mediante pre-escaladores y sólo hay un rango limitado de valores, por lo que la frecuencia máxima que podemos utilizar y estar dentro de la frecuencia máxima de trabajo es 125 kHz. El siguiente pre-escalador supone usar el ADC a 250 kHz, en este caso no se puede garantizar la resolución de 10 bits, pero si una resolución de 8 bits. De todas formas en caso de necesitar un ADC más rápido se podría usar uno externo.

El ADC puede trabajar en dos modos: single conversion mode y free running mode. En modo single conversion el ADC hace una sola conversión y para, pero en modo free running el ADC está continuamente convirtiendo, es decir, hace una conversión y luego comienza con la siguiente.

El ADC en microcontroladores AVR utiliza una técnica conocida como aproximación sucesiva mediante la comparación de la tensión de entrada con la mitad de la tensión de referencia generada internamente. La comparación continúa dividiendo de nuevo la tensión y actualizando cada bit del registro ADC a 1 si el voltaje es HIGH en la comparación o 0 en el otro caso. Este proceso se realiza 10 veces (por cada bit de resolución del ADC) y genera como resultado la salida binaria.

Los registros utilizados en el manejo de las entradas analógicas son:

  • ADCSRA: ADC Control and Status Register A. Control del ADC y su estado. Página 319.

  • ADCSRB: ADC Control and Status Register B.
  • ADCL: ADC Data Register Low. Cuando la conversión ADC ha finalizado, el resultado se deja en estos dos registros.
  • ADCH: Data Register High

  • DIDR0: Digital Input Disable Register 0. Para deshabilitar la entrada digital de los pines analógicos. Página 326.

Diagrama de bloques:

Más información en:

Entendamos el proceso usado anteriormente para calcular la temperatura interna:

 
// Set the internal reference and mux.
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  // enable the ADC

  delay(20);            // wait for voltages to become stable.

  ADCSRA |= _BV(ADSC);  // Start the ADC

  // Detect end-of-conversion
  while (bit_is_set(ADCSRA, ADSC));

  // Reading register "ADCW" takes care of how to read ADCL and ADCH.
  word wADC = ADCW;

Más información:

En caso que el ADC propio de Arduino sea insuficiente para nuestra aplicación, podemos usar Conversores Analógico Digital de alta resolución externos:

AREF

Una de las características claves del convertidor, es su número de bits, que define la resolución con la que podemos cuantificar esa conversión a digital.  En el caso de Arduino, son 10 los bits que maneja en la puertas analógicas, lo que significa que su resolución es 1.024 posibles valores. Cuanto mayor sea esta resolución mejor es la capacidad de aproximación al valor real cuya conversión buscamos.

Un ADC compara sucesivamente la señal que queremos cuantificar en la entrada, con una tensión de referencia contra la que hace las comparaciones.

Un ADC no proporciona valores absolutos, sino que proporciona una comparación cuantificada con relación a un valor de referencia. Por eso, al usar el sensor de temperatura TMP36 en https://aprendiendoarduino.wordpress.com/2017/06/24/ejemplo-sensor-de-temperatura/ hemos calculado la tensión de entrada en uno de los pines Analógicos como la lectura multiplicada por una relación entre el valor de la máxima de la entrada 5V con la máxima medida del conversor 1024.

Como las señales que normalmente manejamos en Arduino están alrededor de los 5V, comparar contra 5V es lo razonable, porque además la industria tiene una gama completa de sensores cuyo valor máximo devuelve 5V. Pero cada vez más, la industria produce electrónica de 3,3V o podemos encontrar sensores con una sensibilidad de menos de 3V, y si usamos el ADC para digitalizar señales de pico 3,3V o menos, estamos perdiendo precisión y resolución, porque estamos desperdiciando una parte de las posibles comparaciones.

En el caso de un sensor a 3.3V, al ser 3,3V el máximo de la tensión de entrada compararlo contra 5V supone que nunca tendremos lecturas mayores de 1.024 * 3,3 /5 = 675 y seguiremos teniendo escalones de entrada de 5mV. Como el ADC es un comparador de tensiones, si pudiéramos cambiar el valor de tensión contra el que comparamos por una de 3,3V, los escalones serian de 3,3V/1024 = 0,00322265625 o sea 3,2 mV.

La tensión de comparación contra la que realizamos la conversión de analógico a digital, debería ser el valor máximo posible de la señal de entrada. Porque es cuándo tendremos la mejor resolución posible.

El pin rotulado como AREF (Analog Reference o Referencia Analógica), que no habíamos usado hasta ahora, permite conectar una tensión externa de referencia, contra la que se comparará la señal que leamos en los pines A0 a A5.

Más información: http://www.prometec.net/aref/

Para cambiar la referencia analógica se debe usar el comando AnalogReference(): https://www.arduino.cc/en/Reference/AnalogReference que nos permite usar los siguientes valores de referencia:

  • DEFAULT: the default analog reference of 5 volts (on 5V Arduino boards) or 3.3 volts (on 3.3V Arduino boards)
  • INTERNAL: an built-in reference, equal to 1.1 volts on the ATmega168 or ATmega328 and 2.56 volts on the ATmega8(not available on the Arduino Mega)
  • INTERNAL1V1: a built-in 1.1V reference (Arduino Mega only)
  • INTERNAL2V56: a built-in 2.56V reference (Arduino Mega only)
  • EXTERNAL: the voltage applied to the AREF pin (0 to 5V only) is used as the reference.

El pin AREF dispones de una resistencia interna de 32 Kohms.

Práctica: Entrada Analógica Modo Free Running

Hagamos un sencillo programa de test para ver el funcionamiento del ADC. Para ello usaremos un potenciómetro conectado al puerto 0 del PORT C, es decir, el puerto A0 de la placa Arduino y en en función del valor leido encender o apagar el led conectado al pin 5 del PORT B, el decir el led en el pin 13.

Primero debe darse un reloj al ADC para establecer la velocidad de muestreo, pero esta tiene un máximo de 200kHz, este valor se establece mediante los prescaler. Estos son configurados por el registro ADCSRA y los bits relacionados que son ADPS2, ADPS1 and ADPS0 con valores de prescaler de 2 a 128. Como el microcontrolador funciona a 16 MHz se usará el prescaler 128 para que el reloj del ADC funcione a 125 KHz.

El siguiente paso es configurar el voltaje de referencia usado por el ADC, este este caso es 5V. El voltaje de referencia es configurado en el registro ADMUX con los bits REFS1 y REFS0. En este caso es el valor por defecto y no hace falta modificar.

En el registro ADMUX también podemos seleccionar en qué canal se va a hacer la conversión mediante los bits del MUX3 al MUX0. En este caso es el valor por defecto y no hace falta modificar.

El ADC está casi configurado, solo hace falta iniciarlo (por defecto está apagado para consumir menos) y para ello se debe poner a 1 el bit ADEN del registro ADCSRA y luego poner a 1 el bit ADSC para comenzar la conversión en el mismo registro. En el registro ADCSRB los bits ADTS2, ADTS1 and ADTS0 determinan como una nueva conversión comienza, por defecto está en mode free running. También debe ponerse a 1 el bit ADATE para que en modo free running comience la conversión.

Ahora solo queda leer el valor devuelto por el ADC en los registros ADCH y ADCL.

Luego encender el led 13 (PORTB 5) si el valor leído es mayor que 512 y sino apagarlo.

Solución Ejercicio10: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio10-Entrada_Analogica_Free_Running

Versión para usar en Atmel Studio (comparar con la versión para IDE Arduino):

 
#include <avr/io.h>
int adc_value;		//Variable used to store the value read from the ADC converter
 
int main(void){
  DDRB |= (1<<PB5);	///PB5/digital 13 is an output
  ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0));	//Prescaler at 128 so we have an 125Khz clock source
  ADMUX |= ~(1<<REFS0);
  ADMUX &= ~(1<<REFS1);				//Avcc(+5v) as voltage reference
  ADCSRB &= ~((1<<ADTS2)|(1<<ADTS1)|(1<<ADTS0));	//ADC in free-running mode
  ADCSRA |= (1<<ADATE);				//Signal source, in this case is the free-running
  ADCSRA |= (1<<ADEN);				//Power up the ADC
  ADCSRA |= (1<<ADSC); //Start converting for(;;){ //The infinite loop adc_value = ADCW; //Read the ADC value, really that's just it if(adc_value > 512){
      PORTB |= (1<<PB5);	//If ADC value is above 512 turn led on
    }
    else {
      PORTB &= ~(1<<PB5);	//Else turn led off
    }
  }
  return 0;
}

}

Práctica: Entrada Analógica Modo Single Conversión

En el ejemplo anterior usamos ADATE para activar el auto trigger y el modo free running porque solo estamos leyendo de un pin analógico. En caso que se vaya a leer diferentes pines, no tiene sentido usar el modo free running porque existe una limitación que al cambiar de pin debemos esperar dos conversiones para hacerlo, porque es el tiempo que le cuesta al multiplexor cambiar de un pin a otro. Haciendo una conversión simple poniendo el byte ADATE a 0, no es necesario esperar ese tiempo.

Al usar el modo de conversión simple (single conversion) debemos comprobar el bit ADSC hasta que vuelve a 0 (cleared) y la conversión está hecha. A cambio no debemos configurar los bits ADTS y ADATE.

Luego encender el pin 6 si el valor leído es mayor que 512 o sino encender el pin 7. Poner los LEDs y resistencias correspondientes.

En este caso poner el potenciómetro en el pin A5.

Solución: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio11-Entrada_Analogica_Single_Conversion

Versión para usar en Atmel Studio (comparar con la versión para IDE Arduino):

 
#include <avr/io.h>
#define PORT_ON(port,pin) port |= (1<<pin)
#define PORT_OFF(port,pin) port &= ~(1<<pin)
int adc_value;		//Variable used to store the value read from the ADC converter

int main(void)
{
  unsigned int adc_value; // Variable to hold ADC result
  DDRD=0xff; // Set Port D as Output
  PORTD = 0x00;
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS0);
  // ADEN: Set to turn on ADC , by default it is turned off
  //ADPS2: ADPS2 and ADPS0 set to make division factor 32
  ADMUX=0x05; // ADC input channel set to PC5
  while (1)
  {
     ADCSRA |= (1<<ADSC); // Start conversion
        while (ADCSRA & (1<<ADSC)); // wait for conversion to complete
     adc_value = ADCW; //Store ADC value
           if (adc_value < 512)
           {
                 PORT_OFF(PORTD,7); // Toggle LEDs
                 PORT_ON (PORTD,6);
           }
           else
           {
                 PORT_ON(PORTD,7); // Toggle LEDs
                 PORT_OFF (PORTD,6);
           }
  }
}

Salidas Analógicas

Las salidas analógicas puras necesitan de un conversor digital analógico (DAC). Solo unos pocos modelos de Arduino disponen de salida analógica pura como el Arduino DUE, el Zero o el MKR1000.

El arduino due, posee dos salidas analógicas puras mediante dos conversores digital a analógico.

Para manejar las salidas analógicas puras usamos la función analogWrite() pero disponemos de funciones adicionales como AnalogWriteResolution() https://www.arduino.cc/en/Reference/AnalogWriteResolution y la librería audio https://www.arduino.cc/en/Reference/Audio

Conversor digital  analógico (DAC)

Definición: http://en.wikipedia.org/wiki/Digital-to-analog_converter

Al contrario que las señales analógicas, las señales digitales se pueden almacenar y transmitir sin degradación. Los DAC se usan para los altavoces, amplificadores para producir sonido. Ejemplo de la transmisión de la voz por la líneas telefónicas.

PWM

Los Arduino que no tienen DAC y por lo tanto no poseen salidas analógicas puras, usan la técnica de PWM para las salidas analógicas.

Toda la información de PWM para salidas analógicas está en la página 125, 149 y 189 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Las Salidas PWM (Pulse Width Modulation) permiten generar salidas analógicas desde pines digitales. Arduino Uno no posee salidas analógicas puras.

Definición de PWM en la web de Arduino: http://arduino.cc/en/Tutorial/PWM

La modulación por ancho de pulsos (también conocida como PWM, siglas en inglés de pulse-width modulation) de una señal o fuente de energía es una técnica en la que se modifica el ciclo de trabajo de una señal periódica (una senoidal o una cuadrada, por ejemplo), ya sea para transmitir información a través de un canal de comunicaciones o para controlar la cantidad de energía que se envía a una carga.

El ciclo de trabajo de una señal periódica es el ancho relativo de su parte positiva en relación con el período. duty cycle = (tiempo que la salida está a uno o HIGH)/ (periodo de la función)

En Arduino la frecuencia de PWM es de 500Hz. Pero es un valor que puede modificarse en caso que lo necesitemos.

PWM tiene varios usos en los microcontroladores:

  • Dimming un LED
  • Obtener una salida analógica
  • Ofrecer un voltaje analógico entre el 0% y el 100% de Vcc
  • Generar señales de audio
  • Controlar velocidad de motores
  • Generar una señal modulada, por ejemplo para manejar un LED infrarrojo de un mando a distancia.

En las salidas analógicas con PWM se usan los timers de microcontrolador para hacer la forma de onda PWM.

Para generar la señal PWM se utiliza los timers configurándose varias formas de trabajo. La forma de onda PWM, en el modo de trabajo más sencillo (Fast PWM), se genera de la  forma mostrada en la siguiente gráfica:

  1. El registro del contador se pone en marcha desde cero y cuenta de modo ascendente. En el momento de empezar la cuenta se activa el pin de salida del PWM.
  2. Cuando el valor de este registro se iguala al de otro registro de comparación se conmuta el pin de salida. El registro del contador sigue contando en forma normal.
  3. Cuando el valor del registro del contador llega al final (TOP) vuelve a comenzar (BOTTOM). El pin de salida vuelve a cambiar.
  4. El tiempo que tarda el contador en llegar al final fija el periodo de la señal.

Los microcontroladores usan varios modos de PWM, uno de ellos el el Fast PWM que puede ser generado 8 (256), 9 (512) y 10 (1024) bits, una resolución mayor de 8 bits solo es posible usando un timer de 16 bits. Otro modo de PWM es Phase Correct PWM que es el que debería usarse para el control de motores. Otro modo es Frequency and Phase Correct PWM.

Esta imagen explica cómo funciona el phase correct PWM, en este caso el timer cuenta hacia arriba y luego hacia abajo:

En los microcontroladores AVR, el PWM está disponible con todos los timers. Timer 0 y timer 2 dan una resolución de 8 bit mientras que el timer 1 ofrece una resolución de 16 bits. Con 8 bits hay 256 pasos individuales y en 16 bit hay una resolución de 65536 pasos.

La forma de generar la onda PWM es diferente en cada uno de los modos y la señal obtenida es diferente.

Puesto que las ondas generadas son diferentes, el centro de la parte en HIGH no es constante en el fast PWM y sí en el phase correct PWM, esa es la principal diferencia entre ambos modos y la razón de porque para control de motores es mejor usar el phase correct PWM.

Fast PWM:

Phase Correct PWM:

Más información en: https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogWrite.html

El modo PWM en los microcontroladores AVR se controla por hardware. Esto significa que todo se lleva a cabo por la CPU AVR. Todo lo que se necesita hacer es inicializar e iniciar el temporizador y establecer el ciclo de trabajo. El ATmega328p tiene 3 timers  y cada timer maneja dos salidas A y B, en total tenemos 6 salidas PWM. Estos temporizadores generan interrupciones cuando alcanzan el overflow o cuando alcanzan el registro de comparación. Los registros de control del timer/counter n (n va de 0  a 2) son TCCRnA y TCCRnB y tienen los principales controles de los temporizadores.

Estos registros tienen varios grupos de bits:

  • Waveform Generation Mode bits (WGM): these control the overall mode of the timer. (These bits are split between TCCRnA and TCCRnB.)
  • Clock Select bits (CS): these control the clock prescaler
  • Compare Match Output A Mode bits (COMnA): these enable/disable/invert output A
  • Compare Match Output B Mode bits (COMnB): these enable/disable/invert output B

Los registros de comparación de salida OCRnA y OCRnB establece los niveles en los que las salidas A y B se verán afectados. Cuando el valor del temporizador coincide con el valor del registro, la salida correspondiente será modificado como se especifica en el modo.

Timers relacionados con los pines PWM de Arduino y salidas de los comparadores:

Arduino Uno, Mini y Nano disponen de tres temporizadores.

  • Timer0, con una frecuencia de 62500Hz, y preescalados de 1, 8, 64, 256 y 1024.
  • Timer1, con una frecuencia de 31250Hz, y preescalados de 1, 8, 64, 256, y 1024.
  • Timer2, con una frecuencia de 31250Hz, y preescalados de 1, 8, 32, 64, 128, 256, y 1024.

Los registros para el control de PWM  con el timer 0 son TCCR0A y TCCR0B y dentro de ellos los bits WGM02, WGM01 y WGM00:

Para utilizar fast PWM hay dos modos para elegir, los modos 3 y 7, la principal diferencia entre estos dos modos es que en el modo 3 TOP se fija en 0xFF y en el modo de 7 TOP es definido por el registro OCRA, esto significa que si existe la necesidad, podemos cambiar el número máximo que el temporizador hará hasta que haga overflow, así que esto significa que podemos controlar la frecuencia y el ciclo de trabajo.

Un buen tutorial de PWM: https://www.luisllamas.es/salidas-analogicas-pwm-en-arduino/

Debemos tener en cuenta los efectos que supone la rápida conexión y desconexión de la señal pulsada puede suponer en el dispositivo alimentado. Por ejemplo, en el caso de cargas inductivas (motores, relés, o electroimanes) la desconexión supondrá la generación de voltaje inducido que puede dañar la salida digital o el propio dispositivo, por lo que será necesario disponer de las protecciones oportunas.

En cuanto a transistores, en general, los de tipo BJT resultan apropiados para funcionar como amplificación de señales PWM. Esto no suele ser así en los transistores MOS, donde los efectos capacitivos del mismo, unidos a la limitación de corriente de las salidas digitales, frecuentemente harán que necesitemos un driver de amplificación previo para evitar que el transistor trabaje en zona activa.

Más información sobre PWM:

Muy buena explicación en profundidad: https://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogWrite.html

Incompatibilidades

El uso de los Timer no es exclusivo de las salidas PWM, si no que es compartido con otras funciones. Emplear funciones que requieren el uso de estos Timer supondrá que no podremos emplear de forma simultánea alguno de los pines PWM.

Incompatibilidades más frecuentes:

  • Servo: La librería servo hace un uso intensivo de temporizadores por lo que, mientras la estemos usando, no podremos usar algunas de las salidas PWM. En el caso de Arduino Uno, Mini y Nano, la librería servo usa el Timer 1, por lo que no podremos usar los pines 9 y 10 mientras usemos un servo.
  • Comunicación SPI: En Arduino Uno, Mini y Nano, el pin 11 se emplea también para la función MOSI de la comunicación SPI. Por lo tanto, no podremos usar ambas funciones de forma simultánea en este pin.
  • Función Tone: La función Tone emplea el Timer 2, por lo que no podremos usar los pines 3 y 11.

Cambiar Frecuencia PWM

Para cambiar la frecuencia de PWM que por defecto en Arduino UNO está a 500 HZ, se puede usar la función definida en http://playground.arduino.cc/Code/PwmFrequency, donde indicando el pin y el divisor de frecuencia. Esta función cambia los registros TCCRnB en el timer correspondiente en función del pin, con el divisor que queramos.

O simplemente en el setup() del sketch inicializar los bits CS00, CS01 y CS02 como se indica en este enlace: https://arduino-info.wikispaces.com/Arduino-PWM-Frequency, pero teniendo en cuenta que estos cambios en los timers 0, 1 y 2 puede tener efectos en otras funciones que usen los timers como delay(), millis() o la librería servo:

  • Changes on pins 3, 5, 6, or 11 may cause the delay() and millis() functions to stop working. Other timing-related functions may also be affected.
  • Changes on pins 9 or 10 will cause the Servo library to function incorrectly.

La frecuencia resultante de PWM es la resultante de la división de la frecuencia base entre el divisor.

  • The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
  • The base frequency for pins 5 and 6 is 62500 Hz.
  • The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64, 256, and 1024.
  • The divisors available on pins 3 and 11 are: 1, 8, 32, 64, 128, 256, and 1024.

También se puede hacer PWM en todos los pins, programando nosotros en lugar de dejarlo a la CPU del microcontrolador y los timers: http://playground.arduino.cc/Main/PWMallPins

Muy buena explicación de como cambiar la frecuencia a PWM y en general del funcionamiento de PWM

Práctica: PWM Arduino

El código necesario para encender una salida PWM es muy sencillo gracias a las bibliotecas de Arduino, que configuran por defecto las salidas de PWM en la función Setup, ocultando la dificultad de manipulación de los Timer.

Así, en en el ejemplo más básico, simplemente definimos el pin PWM que queremos emplear, y usamos la función analogWrite para escribir el valor del Duty Cycle, medido de 0 a 255.

El siguiente código incrementa progresivamente el valor de una señal analógica desde 0 a 255. Al alcanzar el valor máximo el contador pasará a 0, por lo que el ciclo volverá a iniciarse. ¿Ves porque si inicializa el contador?

 
const int analogOutPin = 11; // Analog output pin
byte outputValue = 0;        // valor del PWM

void setup() {
	Serial.begin(9600);
}

void loop() {
	analogWrite(analogOutPin, outputValue);
	Serial.println(outputValue);
	delay(10);
	outputValue++;
}

Hacer una versión equivalente a bajo nivel donde ilumine el led conectado en el pin 11 con su resistencia.

Pin Mapping: http://brittonkerin.com/cduino/pin_map.html

Ver registro TCCR2A en página 203 y TCCR2B en página 206  y OCR2A en página 209 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf

Solución: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio12-PWM_Avanzado

Práctica: Fade Arduino

Hacer un encendido y apagado progresivo en un led colocado en el pin 6 con su resistencia usando los registros.

Función delay AVR libc http://www.atmel.com/webdoc/avrlibcreferencemanual/group__util__delay_1gad22e7a36b80e2f917324dc43a425e9d3.html

TCCR0A

TCCR0B

OCR0A

Solución: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio13-PWM_Fade_Avanzado

Más información: http://www.ermicro.com/blog/?p=1971

Puertos Digitales Arduino Avanzado

La mayoría de los pines de los microcontroladores son multipropósito, es decir, en función de su configuración se comportan de una forma u otra, tal y como se muestra en la figura.

El ATmega328p como cualquier otro microcontrolador tiene registros, algunos de estos registros están conectados con los puertos de entrada/salida, cada puerto tiene un nombre específico y sus registros asociados, de hecho, el 328p tiene los puertos B, C y D, y cada puerto 8 pines de la MCU conectados. En función del encapsulado puede haber una restricción en el número de pines. Por ejemplo en el paquete de 28 pines PDIP.

Cada pin puede tener múltiples funciones, como la generación de PWM, o las capacidades de ADC, los pines 6 y 7 del PORTB son los pines de entrada para el oscilador de cristal, y el pin 6 del PORTC corresponde al botón de reinicio.  En esta imagen se puede ver todas las funciones alternativas que cada pin puede tener.

NOTA: Esta nomenclatura del los GPIO luego el IDE de Arduino se ha encargado de nombrarlos de una forma más sencilla y consecutiva, más fácil de entender.

Por ejemplo en el Wemos D1 basado en el ESP8266, internamente los GPIO tiene una nomenclatura que puede llevar a error, pero luego el soporte para estas placas en el IDE de Arduino hace que coincida con la nomenclatura de Arduino y con lo etiquetado en la placa.

Entradas y Salidas Digitales Arduino a Fondo

Una señal digital es un tipo de señal generada por algún tipo de fenómeno electromagnético en que cada signo que codifica el contenido de la misma puede ser analizado en término de algunas magnitudes que representan valores discretos, en lugar de valores dentro de un cierto rango. Por ejemplo, el interruptor de la luz sólo puede tomar dos valores o estados: abierto o cerrado, o la misma lámpara: encendida o apagada.

Más información:

Los sistemas digitales, como por ejemplo un microcontrolador, usan la lógica de dos estados representados por dos niveles de tensión eléctrica, uno alto, H y otro bajo, L (de High y Low, respectivamente, en inglés). Por abstracción, dichos estados se sustituyen por ceros y unos, lo que facilita la aplicación de la lógica y la aritmética binaria. Si el nivel alto se representa por 1 y el bajo por 0, se habla de lógica positiva y en caso contrario de lógica negativa.

Cabe mencionar que, además de los niveles, en una señal digital están las transiciones de alto a bajo y de bajo a alto, denominadas flanco de bajada y de subida, respectivamente. En una señal digital, se denomina flanco a la transición del nivel bajo al alto (flanco de subida) o del nivel alto al bajo (flanco de bajada).

Para interactuar con los pines digitales de Arduino ya conocemos las funciones que nos ofrece Arduino en https://www.arduino.cc/en/Tutorial/DigitalPins como digitalRead() y digitalWrite().

Pero a bajo nivel estas funciones están manejando registros de los puestos B, C y D. Los estados y comportamiento de cada puerto y sus pines se maneja mediante tres registros. Hay un registro dedicado para cada puerto que define si cada pin es una entrada o una salida, que es el registro de DDRX, donde x es la letra del puerto que queremos configurar, en el caso de la Arduino hay DDRB, DDRC y DDRD. Como toda variable lógica, cada bit en los registros DDRX puede ser 1 ó 0, poner un bit específico de DDRX a 1 configura el pin como salida y ponerla a 0 configura el pin como una entrada.

Los pines usados en la placa Arduino poseen tres puertos en el caso de ATmega328p (Arduino Uno):

  • B (pines digitales del 8 al 13)
  • C (entradas analógicas)
  • D (pines digitales del 0 al 7)

El Arduino Mega presenta varios puertos B,C,D,E,F, etc.

Cada puerto es controlado por tres registros, los cuales también están definidos como variables en el lenguaje del Arduino.

  • El registro DDR, determina si el pin es una entrada o una salida (1 salida, 0 entrada).
  • El registro PORT controla si el pin está en nivel alto (1) o en nivel bajo (0).
  • El registro PIN permite leer el estado de un pin. (solo lectura)

Dentro del Microcontrolador ATmega328P esta es la relación de los puestos con los pines:

Cada bit de estos registros corresponden con un solo pin; por ejemplo el bit menos significativo de los registros DDRB, PORTB, y PINB hace referencia al pin PB0 (pin digital 8)

Dentro de la avr-libc tenemos un fichero de definición de los registros para cada microcontrolador. Estos fichero se pueden encontrar en la ruta: C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr

Para ATmega328p el fichero iom328p.h es donde se encuentran las definiciones para el microcontrolador. Ver que las direcciones de los registros van desplazados 0x20 posiciones de memoria por los fuses que son 4 bytes de memoria permanente llamados fuses que determinan cómo se va a comportar el microcontrolador, si tiene bootloader, a que velocidad y voltaje va a funcionar, etc… .

El fichero <avr/sfr_defs.h> está incluido en todos los ficheros <avr/ioxxxx.h> que usan unas macros definidas en sfr_defs.h que hacen que los special function registers parezcan variables de C a las que podemos llamar para obtener su valor:

 
#define PORTA   _SFR_IO8(0x02)
#define EEAR    _SFR_IO16(0x21)
#define UDR0    _SFR_MEM8(0xC6)
#define TCNT3   _SFR_MEM16(0x94)
#define CANIDT  _SFR_MEM32(0xF0)

Por ejemplo, las limitaciones eléctricas en el microcontrolador son por puerto y por pin:

Práctica: Registros PORT Arduino

Un ejemplo que configura pines 0,1,2,3 como entradas digitales y los pines 4,5,6,7 como salidas digitales, se hace mediante el comando: DDRD = 0b11110000;

Si queremos poner todos los pines como salidas: DDRD = 0b11111111;

Al utilizar Registros DDR tenemos la ventaja de que con solo una instrucción podemos declarar el pin como entrada o salida, sin embargo con pinMode() necesitaríamos 8 instrucciones.

La relación entre los registros B, C y D y los pines es:

Leer los registros al poner varios pines como INPUT, INPUT_PULLUP y OUTPUT.

Probar con este código:

 
void setup() {
  //PORTD maps to Arduino digital pins 0 to 7
  pinMode(2, INPUT_PULLUP);
  pinMode(3, OUTPUT);
  digitalWrite(3, HIGH);
  pinMode(4, INPUT);
  pinMode(5, OUTPUT);
  digitalWrite(5, LOW);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH);
  Serial.begin(9600);
}

void loop() {
  Serial.println("DDRD - The Port D Data Direction Register. DDRX, 1=OUTPUT, 0=INPUT");
  Serial.println(DDRD, DEC);
  Serial.println(DDRD, HEX);
  Serial.println(DDRD, BIN);  
  Serial.println("PIND - The Port D Input Pins Register. Lectura INPUT");
  Serial.println(PIND, DEC);
  Serial.println(PIND, HEX);
  Serial.println(PIND, BIN);
  Serial.println("PORTD - The Port D Data Register. Escritura OUTPUT");
  Serial.println(PORTD, DEC);
  Serial.println(PORTD, HEX);
  Serial.println(PORTD, BIN);
  delay(10000);
}

Poner los pines de input (2, 4 y 6) a 5V y GND y ver como cambia el contenido del registro PIN.

Solución Ejercicio07: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio07-Registos_PORT_simple

Manejo Registros de Pines Arduino

Par poder manejar registros disponemos de unas funciones especiales para registros.

Operadores Bit a Bit:

  • & (bitwise and)
  • | (bitwise or)
  • ^ (bitwise xor)
  • ~ (bitwise not)
  • << (bitshift left) (IMPORTANTE)
  • >> (bitshift right)

Tutorial de matemáticas con bits: http://playground.arduino.cc/Code/BitMath

Referencia de <avr/sfr_defs.h> Special Function Registers:

Manipulación de puertos: https://www.arduino.cc/en/Reference/PortManipulation

Tutorial de Bit Math: http://playground.arduino.cc/Code/BitMath

Escribir en un pin

Ya podemos decir al Atmega cómo serán utilizados sus pines, pero queremos saber cómo leer y escribir datos en dichos pines, de modo que para escribir datos en un determinado puerto, se utiliza el registro PORTx, éste es fácil de recordar, donde x es el nombre del puerto, y después de la configuración de un pin como salida es sólo una cuestión de poner 0 o 1 en el registro PORTx para controlar que el pin de este en estado alta o baja.

Un ejemplo sería:

 
DDRD = 0b11111111; // Todos los pines de PORTD son salidas.
PORTD = 0b11111111; // Todos los pines de PORTD están en estado alto.
DDRD = 0b11111111; // Todos los pines de PORTD son salidas.
PORTD = 0b00000000; // Todos los pines de PORTD están estado bajo.

Se debe tener cuidado cuando se utiliza PORTD y el puerto serie porque los pines 0 y 1 del PORTD son los utilizados por la USART y si se pone estos dos como entradas o salidas, la USART será incapaz de leer o escribir datos en los pines. Este es un ejemplo del cuidado que se debe tener al usar esta programación en lugar de la capa de programación que nos ofrece Arduino.

Leer un Pin

Para leer en el pin y poder leer los datos de los sensores o cuando se pulsa un botón de un pin digital configurado como entrada, vamos a utilizar un tercer registro llamado PINX, donde de nuevo x es el nombre del puerto donde se encuentra el pin, así que primero con DDRX decimos al microcontrolador que queremos algunos pines como entradas digitales, y luego usando PINX leemos sus valores

Ejemplo:

 
DDRD = 0b00000000; // Todos los pines del PORTD son entradas
char my_var = 0; // variable para guardar la información leída en PORTD
my_var = PIND; // Lee PORTD y pone la información en la variable

Es casi tan fácil como puede ser usar el digitalWrite o digitalRead de Arduino, pero con acceso directo al puerto se puede ahorrar espacio en el la memoria flash y también puede ganar mucha velocidad, porque las funciones Arduino puede tomar más de 40 ciclos de reloj para leer o escribir un solo bit en un puerto, además para leer un solo bit el código es bastante complejo con un montón de líneas que ocupan por lo menos unos 40 bytes, que podría ser un pequeño ahorro en flash y es un gran paso para acelerar cualquier programa.

No es normal que se necesite leer o escribir en un puerto completo en cada momento, por ejemplo, si queremos encender un LED, o leer un botón sólo tendrá que utilizar un pin, y escribir todos los bits uno a uno cada vez que queremos cambiar un valor en un puerto, es una tarea aburrida, pero la librería C de AVR tiene algunas pocas palabras definidas como Px(0..7), donde x es de nuevo el puerto que desea utilizar y 0..7 es el valor del pin individual de dicho puerto, por lo que para iluminar un LED debemos hacer algo como esto:

 
DDRD = (1<<PD2); // Configura el pin 2 de PORTD como salida.
PORTD = (1<<PD2); // El pin 2 de PORTD tiene ahora un valor lógico 1.

La operación << es un operador binario que desplaza hacia la izquierda el número de posiciones especificadas.

Para leer el estado de un botón:

 
DDRD = 0b11111101; //Configura pin 1 de PORTD como entrada y el resto salida
char my_var = 0; //Variable para guardar la información leída en PORTD
my_var = (PIND & (1<<PD1)); /* Lee el pin 1 de PORTD y lo coloca en la variable.*/

Explicación de la operación binaria bit a bit: https://www.arduino.cc/en/Reference/Bitshift

También se puede utilizar la macro Px0..7 varias veces en una misma instrucción, por ejemplo, en este código, se ejecutará algo de código sólo si al menos uno de los botones está pulsado:

 
DDRD = 0b11111100; // Los pines 0 y 1 de PORTD son entradas, y el resto salidas.
if (PIND & ((1<<PD0) | (1<<PD1))) {
/* Algún código dentro del if() que se ejecutará solo si al menos uno de los dos botones se encuentra activado. */
}

Mediante los registros también podemos controlar las resistencias internas de pullup. Cuando hay un botón que puede tener dos estados, uno es desconectado, y cuando se presiona hará una conexión entre los pines del microcontrolador y permite por ejemplo, conectarse a masa, pero cuando se desconecta, no hay nada que fuerce un valor estable en el pin de entrada, y el pin puede leer 1 ó 0 ya que el pin es muy sensible al ruido electromagnético, como una pequeña antena. Se puede resolver este problema de dos maneras similares, una es para conectar una resistencia de 10 Kohms o más entre el Vcc (+5 v) y el pin de entrada, o usar los pull-ups del microcontrolador que tienen integrados, también hace más simples los circuitos.

Para habilitar las resistencias pullup tenemos que hacer algo que puede resultar un poco extraño, no existe un registro dedicado para activar o desactivar el pull-ups, estos son activados o desactivados escribiendo 1 o 0 respectivamente en el registro PORTx cuando el registro DDRX se configuran como entradas. Ejemplo:

 
DDRD = 0b00000000; // Todos los pines de PORTD son entradas.
PORTD = 0b00001111; // Habilito las Pull-ups de los pines 0,1,2 y 3
char my_var = 0; // Creo una variable para guardar la información leída en PORTD.
my_var = PIND; // Leo PORTD y colocó la información en la variable.

Si se ejecuta este código, sin tener nada conectado a PORTD, los cuatro bits más altos de la variable my_var puede ser 0 ó 1, cualquier combinación posible de ellos porque son flotantes (actúan como pequeñas antenas), pero los cuatro bits más bajos leerá todos un 1 debido a que el pull-ups imponen una señal de 5V débil que se lee como un valor lógico 1.

En un sentido básico esto es todo lo que se necesita saber para dominar la manipulación directa de los puertos. La manipulación de bits enseña cosas más ingeniosas como las máscaras de bits, las operaciones AND, OR, NOT y XOR y cómo configurar y limpiar los bits en un registro y algunos buenos trucos con las operaciones de desplazamiento derecho e izquierdo, esta técnica puede acelerar un sketch y son muy útiles cuando se utilizan los puertos digitales.

Ventajas y Desventajas que nos ofrece al utilizar los registros:

  • Desventajas:
    • El código es mucho más difícil de depurar y mantener, y es mucho más difícil de entender. Solo lleva algunos microsegundos al procesador ejecutar código, pero podría llevar horas descubrir por qué no funciona y arreglarlo.
    • Es mucho más fácil causar mal funcionamiento no intencionado usando el acceso directo a un puerto. Con DDRD = B11111110, el pin 0 se debe dejar como una entrada. El pin 0 la línea de recepción (RX) en el puerto serial. Podría ser muy fácil causar el puerto serial deje de funcionar por cambiar el pin 0 a una salida.
  • Ventajas:
    • Se puede cambiar los pines de estado muy rápido, en fracciones de microsegundos. Las funciones digitalRead() y digitalWrite() se componen cada una de ellas de cerca de una docena de líneas de código, lo cual se convierte en unas cuantas instrucciones máquina.
    • Cada instrucción máquina necesita un ciclo de reloj a 16MHz, lo cual puede sumar mucho tiempo en aplicaciones muy dependientes del tiempo. El Registro PORT (Puerto) puede hacer el mismo trabajo en muchos menos ciclos de trabajo.
    • Algunas veces necesitamos configurar muchos pines exactamente al mismo tiempo. Por lo que usar las funciones digitalWrite (10,HIGH), seguida de la función digitalWrite (11,HIGH), causará que el pin 10 se ponga en nivel alto varios microsegundos después que el pin 11, lo cual puede confundir circuitos digitales conectados al Arduino, cuyo funcionamiento dependa del tiempo preciso del cambio de esos bits.
    • Si te estás quedando sin memoria, se pueden usar estos trucos para hacer que tu código sea más pequeño. Usando este método se necesitan muchos menos bytes de código compilado que si se hace un bucle para que se vaya cambiando cada pin uno por uno.

En las librerías podemos ver que usan la manipulación de registros en lugar de las instrucciones que nos ofrece el core de Arduino lo que las hace más rápidas.

En el siguiente enlace de github puede verse el fichero wiring_digital.c que es parte del core de Arduino donde se puede ver el código de las instrucciones pinMode(), digitalWrite() y digitalRead(): https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_digital.c

Y en el fichero pins_arduino.h está la definición de pines: https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/variants/standard/pins_arduino.h

Más información en:

Práctica: Operaciones con Entradas y Salidas Digitales

Leer Pin

  • Configurar mediante el registro DDRB el pin 9 como entrada y el resto de pines del PORT B como salida.
  • Configurar el pin 9 como INPUT_PULLUP mediante el registro PORTB
  • Usando el registro PORTB poner a LOW todos los pines de OUTPUT salvo el pin 10
  • Sacar por el monitor serie el estado de los pines del PORTB mediante el registro PINB

Solución Ejercicio08: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio08-Leer_pin

Velocidad Read

Para demostrar que la operación digitalRead() es lenta hacer un ejercicio que lea 1000, 10000 y 100000 veces el puerto 9 configurado como entrada, mediante digitalRead() y mediante PIND y saque el tiempo que ha costado cada operación.

Solución Ejercicio 09: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio09-Velocidad_Read

Microcontroladores Arduino a Fondo

Un microcontrolador es un integrado capaz de ser programado desde un ordenador y seguir la secuencia programada.

Como vimos anteriormente, Arduino es una plataforma para programar de forma sencilla algunos microcontroladores de la familia AVR de Atmel http://es.wikipedia.org/wiki/AVR y también microcontroladores Atmel ARM Cortex-M0+ o Intel http://www.intel.com/content/dam/support/us/en/documents/boardsandkits/curie/intel-curie-module-datasheet.pdf, y un hardware con ese MCU donde acceder físicamente a sus puertos.

Pero también Arduino y su entorno de programación se está convirtiendo en un estándar de facto para la programación de cualquier tipo de placas de desarrollo y prototipado, es decir, de otro tipo de microcontroladores no incluidos en los productos de Arduino. Gracias a la comunidad es posible programar otros microcontroladores de ST microelectronics o los ESP8266 y ESP32 de espressif http://espressif.com/ que podemos comprar con placas como la nodeMCU http://nodemcu.com/index_en.html o la bluepill http://wiki.stm32duino.com/index.php?title=Blue_Pill.

Puesto que Arduino es una plataforma open source disponemos de toda la documentación de los microcontroladores usados.

Por ejemplo, el microcontrolador de Arduino UNO es el ATmega328p y toda la documentación la tenemos en http://www.microchip.com/wwwproducts/en/ATmega328p. El data sheet completo es un documento de 442 páginas que podemos ver en http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf. Cuando necesitemos más información o cómo funciona este microcontrolador debemos ir a este documento.

Como muestra de la documentación que tenemos disponible:

  • Página 34 tenemos el detalle de cómo se distribuye la memoria en el ATmega328p
  • Página 97 tiene en detalle los puertos digitales I/O y página 100 donde da el código para definir un pines a high y low y también como input pullup.
  • Página 436 tenemos los 3 encapsulados posibles para este microcontrolador
  • Página 316 nos muestra que este microcontrolador tiene un sensor de temperatura integrado y que es posible habilitarlo para leer su temperatura.
  • Página 378 se pueden ver los consumos de Arduino y la dependencia entre la frecuencia máxima de reloj y el Vcc.
  • Página 428 hay un resumen de todos los registros del microcontrolador y su dirección de memoria.

Cuando el microcontrolador ejecuta una instrucción que definimos en el sketch, internamente hace muchas operaciones y cada una de esas operaciones se ejecuta en un ciclo de reloj. Para el ATmega 328p que tiene una frecuencia de 16 MHz, es decir, cada ciclo tarda 0,0000000625 segundos = 0,0625 microsegundos = 62,5 nanosegundos

Así se ejecutaría una instrucción, en cada ciclo de reloj se ejecuta cada subinstrucción.

La importancia de conocer el ciclo de ejecución de instrucciones en un microcontrolador estriba en que en ocasiones es necesario calcular de forma precisa el tiempo de ejecución de los bucles para actuar en tiempo real.

Cálculos de la velocidad de las operaciones en Arduino: http://forum.arduino.cc/index.php?topic=200585.0

El método para calcular estas tablas está en http://forum.arduino.cc/index.php?topic=200585.0

Diferencia entre los microcontroladores de 8 bits, 16, y 32 bits, es tamaño de palabra que manejan e influye en los registros y direccionamiento de memoria: http://es.wikipedia.org/wiki/Palabra_(inform%C3%A1tica)

Este mismo análisis hecho con el ATmega328P, podemos hacerlo con otros microcontroladores:

Dentro de los microcontroladores, la tendencia es a usar MCUs de 32 bits con arquitectura ARM. La arquitectura ARM es el conjunto de instrucciones de 32 y 64 bits más ampliamente utilizado en unidades producidas. Concebida originalmente por Acorn Computers para su uso en ordenadores personales, los primeros productos basados ​​en ARM eran los Acorn Archimedes, lanzados en 1987.

La relativa simplicidad de los procesadores ARM los hace ideales para aplicaciones de baja potencia. Como resultado, se han convertido en dominante en el mercado de la electrónica móvil e integrada, encarnados en microprocesadores y microcontroladores pequeños, de bajo consumo y relativamente bajo costo. En 2005, alrededor del 98% de los más de mil millones de teléfonos móviles vendidos utilizaban al menos un procesador ARM. Desde 2009, los procesadores ARM son aproximadamente el 90% de todos los procesadores RISC de 32 bits integrados.

La arquitectura ARM es licenciable. Esto significa que el negocio principal de ARM Holdings es la venta de núcleos IP, estas licencias se utilizan para crear microcontroladores y CPUs basados ​​en este núcleo. ARM Cortex M es un grupo de procesadores RISC de 32 bits licenciados por ARM Holdings. La web oficial es http://www.arm.com/products/processors/cortex-m. Además existen otras familias de ARM: https://en.wikipedia.org/wiki/List_of_ARM_microarchitectures

Más información:

Para saber más de microcontroladores, ver estos recursos:

AVR vs PIC:

Esquema lógico de Arduino

El funcionamiento interno de un microcontrolador se puede explicar con un diagrama de bloques o esquema lógico, donde se ven en cada bloque cada unidad interna del microcontrolador y cómo se comunica con el restos de unidades.

Arquitectura de microcontroladores: http://sistdig.wikidot.com/wiki:arquitectura

Diagrama de bloques simplificado de un microcontrolador. Se compone de tres bloques fundamentales: la CPU ( central Processing Unit), memoria (RAM y ROM) y las entrada y salidas. Los bloques se conectan entre sí mediante grupos de líneas eléctricas denominadas buses o pistas. Los buses pueden ser de direcciones (si transportan direcciones de memoria o entrada y salida), de datos (si transportan datos o instrucciones) o de control (si transportan señales de control diversas). La CPU es el cerebro central del microprocesador y actúa bajo control del programa almacenado en la memoria. La CPU se ocupa básicamente de traer las instrucciones del programa desde la memoria, interpretarlas y hacer que se ejecuten. La CPU también incluye los circuitos para realizar operaciones aritméticas y lógicas elementales con los datos binarios, en la denominada Unidad Aritmética y Lógica (ALU: Aritmetic and Logic Unit).

Diagramas de bloques de un microcontrolador PIC:

Diagrama de bloques de un microcontrolador AVR de Atmel, incluido el ATmega328p:

El sistema de reloj determina la velocidad de trabajo del microcontrolador. Con 16 MHZ se ejecuta una instrucción en 62,5 nanosegundos (1/16 Mhz), correspondiente a 1 ciclo de máquina. El microcontrolador tiene diferentes opciones de circuito de reloj tal como lo muestra la siguiente imagen:

En un registro interno del microcontrolador se encuentran 5 opciones diferentes de reloj que son seleccionadas por medio de un Multiplexor. De este multiplexor sale la señal de reloj, la cual pasa a través de un prescaler, este prescaler se puede utilizar para reducir la frecuencia, reducir el consumo de energía y mejorar la estabilidad de la señal de reloj.El factor del prescaler va de 1 a 256, en potencias de 2. En Arduino, por defecto está desactivado, por consiguiente trabaja a la frecuencia del oscilador externo.

La señal de reloj es distribuida por la unidad de control a los diferentes bloques existentes: la CPU, las memorias, los módulos de entrada/salida, los contadores/timers, el SPI y la USART, al igual que el conversor Análogo Digital ADC.

El microcontrolador ATmega328  tiene tres timers (timer 0, timer 1, timer 2) que también se pueden usar como contadores. Los timers 0 y 2 son de 8 bits y el timer 1 de 16. Estos timers tienen un módulo de preescalado para su propia señal de reloj que puede provenir de su sistema de reloj interno o por pines externos (modo contador).

Son módulos que funcionan en paralelo a la CPU y de forma independiente a ella. El funcionamiento básico consiste en aumentar el valor del registro del contador al ritmo que marca su señal de reloj.

Usando el reloj interno o un cristal externo puede ser utilizado para medir tiempos puesto que utiliza una señal periódica, precisa y de frecuencia conocida; mientras que si la señal viene de un pin externo puede contar eventos que se produzcan en el exterior y que se reflejen en cambios de nivel de tensión de los pines.

Estos contadores también forman parte del generador de señales PWM y permiten configurar tanto la frecuencia como el ciclo de trabajo.

Registros de memoria

Todos los microcontroladores tienen un conjunto de instrucciones que suele ser un conjunto pequeño al tratarse de arquitectura RISC. La CPU cuenta con ese número de instrucciones que sabe ejecutar.

El conjunto de instrucciones para los microcontroladores Atmel de 8 bits es: http://www.atmel.com/Images/Atmel-0856-AVR-Instruction-Set-Manual.pdf

En el caso del ATmega328p, tiene una arquitectura RISC con 131 instrucciones, la mayoría de ellas ejecutadas en un solo ciclo de reloj.

Más información sobre la ALU del Atmega328p en la página 25 de http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf  

Los registros son unas zonas concretas de la memoria RAM accesibles directamente desde la CPU o desde otros elementos del microcontrolador que permite hacer operaciones directamente y de forma más rápida.

Trabajar con registros de memoria puede ser difícil si sólo se escribe un programa en lenguaje ensamblador. Al utilizar el lenguaje de programación de alto nivel como es C basta con escribir el nombre del registro y su dirección de memoria, a partir de esa información, el compilador selecciona el registro necesario. Las instrucciones apropiadas para la selección del registro serán incorporadas en el código durante el proceso de la compilación.

Más información: https://en.wikipedia.org/wiki/Processor_register

La memoria RAM en el ATmega328p se divide en varias partes, todos los grupos de registros se ponen a cero al apagar la fuente de alimentación. La SRAM del 328p se distribuye de la siguiente forma:

Las primeras 32 localizaciones de la memoria son el fichero de registros (Register File). Las siguientes 64 localizaciones de memoria es la standard I/O memory y después las 160 siguientes localizaciones son la Extended I/O memory. Por último las siguientes 2K localizaciones son la memoria interna SRAM.

Las 5 diferentes modos de direccionamiento para los datos de memoria son:

  • Direct – The direct addressing reaches the entire data space.
  • Indirect with Displacement – The Indirect with Displacement mode reaches 63 address locations from the base address given by the Y- or Z-register.
  • Indirect – In the Register File, registers R26 to R31 feature the indirect addressing pointer registers.
  • Indirect with Pre-decrement – The address registers X, Y, and Z are decremented.
  • Indirect with Post-increment – The address registers X, Y, and Z are incremented.

Los 32 registros de propósito general, los 64 I/O Registers, los 160 Extended I/O Registers y los 2K bytes de SRAM interna en el dispositivo son todos accesibles mediante estos modos de direccionamiento.

Los registros de propósito general se utilizan para almacenar los datos temporales y los resultados creados durante el funcionamiento de la ALU. Los 32 General Purpose Working Registers están directamente conectados a la ALU, permitiendo ser accedidos dos registros de forma independiente en una sola instrucción ejecutada en un ciclo de reloj.

Seis de los 32 registros de propósito general pueden ser usados como 3 punteros de registros de 16-bit de acceso indirecto para direccionamiento del espacio de datos, habilitando cálculos eficientes de direcciones. Uno de estos punteros de dirección puede ser usado como un puntero de dirección para búsqueda en las memoria Flash del microcontrolador. Estos registros con funciones añadidas con los registros de 16-bit X-, Y- y Z-. Más información en la página 28 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Para más información ver página 35 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Los I/O registers localizados en las direcciones 0x20 a 0xFF, a diferencia de los registros de propósito general, su propósito es predeterminado durante el proceso de fabricación y no se pueden cambiar. Como los bits están conectados a los circuitos particulares en el chip (convertidor A/D, módulo de comunicación serial, etc), cualquier cambio de su contenido afecta directamente al funcionamiento del microcontrolador o de alguno de los circuitos. Esta es la forma en que a bajo nivel se interactúa por ejemplo con los pines del microcontrolador.

Un resumen de todos los registros I/O del ATmega328p se puede ver en la página 428 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Registros para:

Más información sobre registros y su uso en:

Práctica: Registros Arduino

Veamos algunos valores de los registros de Arduino con el sketch ShowInfo de http://playground.arduino.cc/Main/ShowInfo

Este sketch dispone de un menú que nos permite hacer varias operaciones, pulsar opción i (Show Information) y t (Timer Register Dump) para ver datos de los registros.

Este sketch es bastante complejo pero puede servir como plantilla cuando queramos acceder desde un sketch a un registro concreto.

Algunos detalles del sketch:

Solución Ejercicio 05: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio05-showInfo

Práctica: Temperatura Interna Microcontrolador

La mayoría de los nuevos chips AVR (utilizados en el Arduino) tiene un sensor de temperatura interno. No suele utilizarse, ya que no es exacta. Sin embargo, hay varias situaciones en las que se puede utilizar este sensor.

La temperatura interna es la temperatura dentro del chip, al igual que la temperatura de la CPU de un ordenador. Si el Arduino no está durmiendo, esta temperatura aumentará. Si los pines de salida se utilizan para dar corriente (por ejemplo encender leds) la temperatura interna aumenta más. Esta temperatura no puede usarse para medir la temperatura ambiente.

En situaciones con altas temperaturas una lectura de temperatura calibrada podría evitar daños. La mayoría de los chips AVR más recientes tienen un rango de temperatura de hasta 85 grados Celsius. Esta funcionalidad podría utilizarse para apagarse a 80 grados Celsius.

Más información: http://playground.arduino.cc/Main/InternalTemperatureSensor

Ejecutar el sketch leer y entender lo que hace y probarlo. Comparar con otros Arduinos y calibrar.

Ver en la página 306 y 316 de la documentación del microcontrolador: http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

La función GetTemp() es la que calcula la temperatura. Para ello configura la referencia interna del conversor analógico digital y lo habilita. Luego lee el dato del ADC mediante el registro ADCW y hace el cálculo para obtener la temperatura.

Como el offset puede variar de una MCU a otra, para calibrar, lo mejor es leer la temperatura de una placa fría y comparar con la temperatura exterior.

Solución Ejercicio 06: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio06-InternalTemperature

Librerías Arduino

Las librerías son trozos de código hechos por terceros que usamos en nuestro sketch. Esto nos facilita mucho la programación y hace que nuestro programa sea más sencillo de hacer y de entender. En este curso no veremos como hacer o modificar una librería pero en este curso debemos ser capaces de buscar una librería, instalarla, aprender a usar cualquier librería y usarla en un sketch.

Las librerías normalmente incluyen los siguientes archivos comprimidos en un archivo ZIP o dentro de un directorio. Estas siempre contienen:

  • Un archivo .cpp (código de C++)
  • Un archivo .h o encabezado de C, que contiene las propiedades y métodos o funciones de la librería.
  • Un archivo Keywords.txt, que contiene las palabras clave que se resaltan en el IDE (opcional).
  • Muy posiblemente la librería incluye un archivo readme con información adicional de lo que hace y con instrucciones de como usarla.
  • Directorio denominado examples con varios sketchs de ejemplo que nos ayudará a entender cómo usar la librería (opcional).

Como instalar librerías: http://arduino.cc/en/Guide/Libraries

Hay varios métodos de instalar librerías:

  • Mediante el IDE de Arduino de forma automática. Admite la instalación desde un fichero zip o desde una carpeta ya descomprimida.
  • Instalación Manual. Descomprimiendo en un directorio la librería y copiandolo en el directorio de librerías. Generalmente Mi Documentos – Arduino – libraries. Aquí se guardan las librerías contribuidas por el usuario como lo denomina el IDE.
  • Desde el gestor de librerías. A partir de la versión 1.6.2 del IDE de Arduino se incorpora el gestor de librerías que facilita el trabajo. Esta herramienta es accesible desde Programa → Incluir Librería → Gestionar Librerías. Desde aquí podemos ver las librerías instaladas, buscar librerías disponibles, instalar librerías y actualizarlas.
    Esta herramienta también nos permite gestionar las librerías instaladas manualmente.
    Desde C:\Users\nombre_usuario\AppData\Local\Arduino15, podemos ver en formato json el listado de librerías y placas disponibles desde el gestor de librerías y tarjetas.

La librerías instaladas se guardan en el directorio indicado desde las preferencias del IDE.

Todas las librerías disponibles en el gestor de librerías pueden encontrarse en http://www.arduinolibraries.info/

IMPORTANTE: Para añadir una librería a nuestro proyecto simplemente se añade a nuestro código la palabra clave #include seguido del nombre de la librería.

Más información: https://aprendiendoarduino.wordpress.com/2017/06/20/librerias-arduino-3/

Práctica: Instalación de Librerías

Instalar las librerías:

Más información: https://aprendiendoarduino.wordpress.com/2017/06/20/uso-de-librerias-arduino/

Práctica: Comparación de Timers

Compara el funcionamiento y limitaciones de las librerías MsTimer2.h y Timer.h

Ejecutar el ejercicio04 y comparar el funcionamiento de los dos timers.

Solución Ejercicio04: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio04-Compara_Timers

MsTimer2 – Solo permite un temporizador que se ejecuta gracias a la interrupción asociada al timer 2 que dispone Arduino. Tiene prioridad de ejecución por encima de cualquier otra operación que se esté ejecutando.

Timer – Permite muchos temporizadores. Ejecuta la instrucción temporizada cuando puede en función de cuando se llama a “t.update();”, si hay retrasos en el loop la función se retrasa.

¿Alguna otra librería que queráis aprender a manejar? Enviar correo a aprendiendoarduino@gmail.com