Archivo de la categoría: Arduino

Funciones Definidas por Usuario Avanzado

Una función es un bloque de código que tiene un nombre y un conjunto de instrucciones que son ejecutadas cuando se llama a la función. Son funciones setup() y loop() de las que ya se ha hablado.

Una función tiene un nombre y un conjunto de instrucciones que son ejecutadas cuando se llama a la función. Son funciones setup() y loop() de las que ya se ha hablado anteriormente.

Las funciones de usuario pueden ser escritas para realizar tareas repetitivas y para reducir el tamaño de un programa. Segmentar el código en funciones permite crear piezas de código que hacen una determinada tarea y volver al área del código desde la que han sido llamadas.

Una función puede llamarse múltiples veces e incluso llamarse a sí misma (función recurrente). Las funciones pueden recibir datos desde afuera al ser llamadas a través de los parámetros y puede entregar un resultado.

Las funciones se declaran asociadas a un tipo de valor. Este valor será el que devolverá la función, por ejemplo ‘int’ se utilizará cuando la función devuelva un dato numérico de tipo entero. Si la función no devuelve ningún valor entonces se colocará delante la palabra “void”, que significa “función vacía”

Sintaxis:

 
tipo nombreFunción (parámetros) {
   instrucciones;
}

Para llamar a una función, simplemente:

 
nombreFunción(parámetros);

En una función que devuelve un valor siempre debe tener la instrucción Return, este termina una función y devuelve un valor a quien ha llamado a la función: http://arduino.cc/en/Reference/Return

Si se define una función y no ponemos return el valor devuelto es cero. No da error de compilación.

Ventajas del uso de funciones:

  • Ayuda a tener organizado el código.
  • Una función codifica una tarea en un lugar de nuestro sketch, así que la función solo debe ser pensada una sola vez.
  • Reduce la probabilidad de errores al modificar el código.
  • Hacen que el tamaño del sketch sea menor porque el código de la función es reutilizado.
  • Facilita la lectura del código.
  • Hace más sencillo reutilizar código en otros sketches.

Más información: http://arduino.cc/en/Reference/FunctionDeclaration

Nombres de funciones

Generalmente los nombres de las funciones deben ser en minúscula, con las palabras separadas por un guión bajo, aplicándose éstos tanto como sea necesario para mejorar la legibilidad.

“mixedCase” (primera palabra en minúscula) es aceptado únicamente en contextos en donde éste es el estilo predominante con el objetivo de mantener la compatibilidad con versiones anteriores.

En el caso de las clases, los nombres deben utilizar la convención “CapWords” (palabras que comienzan con mayúsculas).

Las funciones en Arduino pueden estar dentro del mismo fichero .ino o en otro fichero con extensión .ino dentro del directorio del sketch.

Paso por Valor y Paso por Referencia

Hasta ahora siempre hemos declarado los parámetros de nuestras funciones del mismo modo. Sin embargo, éste no es el único modo que existe para pasar parámetros.

La forma en que hemos declarado y pasado los parámetros de las funciones hasta ahora es la que normalmente se conoce como “por valor“. Esto quiere decir que cuando el control pasa a la función, los valores de los parámetros en la llamada se copian a “objetos” locales de la función, estos “objetos” son de hecho los propios parámetros.

 
int funcion(int n, int m) {
  n = n + 2;
  m = m - 5;
  return n+m;
}

int a = 10;
int b = 20;

Serial.println(funcion(a,b));
Serial.println(funcion(10,20));

Empezamos haciendo a = 10 y b = 20, después llamamos a la función “funcion” con las objetos a y b como parámetros. Dentro de “funcion” esos parámetros se llaman n y m, y sus valores son modificados. Sin embargo al retornar al programa que lo llama, a y b conservan sus valores originales. Lo que pasamos no son los objetos a y b, sino que copiamos sus valores a los objetos n y m. Es lo mismo que hacer funcion(10,20), cuando llamamos a la función con parámetros constantes. Si los parámetros por valor no funcionasen así, no sería posible llamar a una función con valores constantes o literales.

Las referencias sirven para definir “alias” o nombres alternativos para un mismo objeto. Para ello se usa el operador de referencia (&).

Por ejemplo: 

 
int a;
int& r = a;  //Referencia a entero y debe siempre inicializarse

a = 10;
Serial.println(r);

En este ejemplo los identificadores a y r se refieren al mismo objeto, cualquier cambio en una de ellos se produce en el otro, ya que son, de hecho, el mismo objeto. El compilador mantiene una tabla en la que se hace corresponder una dirección de memoria para cada identificador de objeto. A cada nuevo objeto declarado se le reserva un espacio de memoria y se almacena su dirección. En el caso de las referencias, se omite ese paso, y se asigna la dirección de otro objeto que ya existía previamente. De ese modo, podemos tener varios identificadores que hacen referencia al mismo objeto, pero sin usar punteros.

El operador de dirección (&) devuelve la dirección de memoria de cualquier objeto

Los punteros se declaran igual que el resto de los objetos, pero precediendo el identificador con un asterisco (*)

 
int *pEntero;
int x = 10;
int y;

pEntero = &y; //pEntero apunta a la variable entera y
*pEntero = x; // y = 10

Más información sobre punteros: https://es.stackoverflow.com/questions/46909/cual-es-la-diferencia-entre-int-e-int

Si queremos que los cambios realizados en los parámetros dentro de la función se conserven al retornar de la llamada, deberemos pasarlos por referencia. Esto se hace declarando los parámetros de la función como referencias a objetos. Por ejemplo:

 
int funcion(int &n, int &m) {
  n = n + 2;
  m = m - 5;
  return n+m;
}

int a = 10;
int b = 20;

Serial.println(funcion(a,b));
Serial.println("a = " + String(a) + " b = " + String(b));
Serial.println(funcion(10,20)); //es ilegal pasar constantes como parámetros cuando estos son referencias

En este caso, los objetos “a” y “b” tendrán valores distintos después de llamar a la función. Cualquier cambio de valor que realicemos en los parámetros dentro de la función, se hará también en los objetos referenciadas. Esto quiere decir que no podremos llamar a la función con parámetros constantes, ya que aunque es posible definir referencias a constantes, en este ejemplo, la función tiene como parámetros referencias a objetos variables. Y si bien es posible hacer un casting implícito de un objeto variable a uno constante, no es posible hacerlo en el sentido inverso. Un objeto constante no puede tratarse como objeto variable.

Una const reference es una referencia a que no permite cambiar la variable a través de esa referencia. Por ejemplo const int& r = a; en r tengo el valor de a pero no puedo cambiar el valor de a usando r.

No confundir este concepto con el modificador de variable static, que es utilizado para crear variables que solo son visibles dentro de una función, sin embargo, al contrario que las variables locales que se crean y destruyen cada vez que se llama a la función, las variables estáticas mantienen sus valores entre las llamadas a las funciones.

Más información:

Sobrecarga de Funciones

Hay operadores que tienen varios usos, como por ejemplo *, &, << o >>. Esto es lo que se conoce en C++ como sobrecarga de operadores. Con las funciones existe un mecanismo análogo, de hecho, en C++, los operadores no son sino un tipo especial de funciones, aunque eso sí, algo peculiares.

En C++ podemos definir varias funciones con el mismo nombre, con la única condición de que el número y/o el tipo de los argumentos sean distintos. El compilador decide cuál de las versiones de la función usará después de analizar el número y el tipo de los parámetros. Si ninguna de las funciones se adapta a los parámetros indicados, se aplicarán las reglas implícitas de conversión de tipos.

Las ventajas son más evidentes cuando debemos hacer las mismas operaciones con objetos de diferentes tipos o con distinto número de objetos. También pueden usarse macros para esto, pero no siempre es posible usarlas, y además las macros tienen la desventaja de que se expanden siempre, y son difíciles de diseñar para funciones complejas. Sin embargo las funciones serán ejecutadas mediante llamadas, y por lo tanto sólo habrá una copia de cada una.

Ejemplo:

 
int mayor(int a, int b);
char mayor(char a, char b);
float mayor(float a, float b);
int mayor(int a, int b, int c, int d);

int mayor(int a, int b) {
  if(a > b) return a; else return b;
}

char mayor(char a, char b) {
  if(a > b) return a; else return b;
}

float mayor(float a, float b) {
  if(a > b) return a; else return b;
}

int mayor(int a, int b, int c, int d) {
  return mayor(mayor(a, b), mayor(c, d));
}

Las llamadas a funciones sobrecargadas se resuelven en la fase de compilación. Es el compilador el que decide qué versión de la función debe ser invocada, después de analizar, y en ciertos casos, tratar los argumentos pasados en la llamadas. A este proceso se le llama resolución de sobrecarga.

Tener en cuenta que el tipo de retorno de la función no se considera en la sobrecarga de funciones. Consideremos el caso en el que desea escribir una función que devuelve un número aleatorio, pero se necesita una versión que devolverá un entero, y otra versión que devolverá un doble.

int getRandomValue();
double getRandomValue();

Pero el compilador toma esto como un error. Estas dos funciones tienen los mismos parámetros (ninguno) y en consecuencia, la segunda getRandomValue () serán tratada como una redeclaración errónea de la primera. En consecuencia, tendrán que ser dado diferentes nombres a estas funciones.

Más información:

Sobrecarga de operadores:

Ámbito de las variables

Una variable puede ser declarada al inicio del programa antes de la parte de configuración setup(), a nivel local dentro de las funciones, y, a veces, dentro de un bloque, como para los bucles del tipo if.. for.., etc. En función del lugar de declaración de la variable así se determinará el ámbito de aplicación, o la capacidad de ciertas partes de un programa para hacer uso de ella.

Una variable global es aquella que puede ser vista y utilizada por cualquier función y estamento de un programa. Esta variable se declara al comienzo del programa, antes de setup().

Recordad que al declarar una variable global, está un espacio en memoria permanente en la zona de static data y el abuso de variables globales supone un uso ineficiente de la memoria.

Una variable local es aquella que se define dentro de una función o como parte de un bucle. Sólo es visible y sólo puede utilizarse dentro de la función en la que se declaró. Por lo tanto, es posible tener dos o más variables del mismo nombre en diferentes partes del mismo programa que pueden contener valores diferentes, pero no es una práctica aconsejable porque complica la lectura de código.

En el reference de Arduino hay una muy buena explicación del ámbito de las variables:http://arduino.cc/en/Reference/Scope

La variables estáticas solo se crean e inicializan la primera vez que la función es llamada. Ver ejemplo en: http://arduino.cc/en/Reference/Static

Más información:

NOTA: Hay una práctica del ámbito de las variables en el capítulo del curso de programación: “Prácticas: Variables y Tipos de Datos en Arduino”, Ejercicio07-Ambito_Variables

Inline

Cuando usamos el nombre de una función, indicando valores para sus argumentos, dentro de un programa, decimos que llamamos o invocamos a esa función. Esto quiere decir que el procesador guarda la dirección actual, “salta” a la dirección donde comienza el código de la función, la ejecuta, recupera la dirección guardada previamente, y retorna al punto desde el que fue llamada.

Esto es cierto para las funciones que hemos usado hasta ahora, pero hay un tipo especial de funciones que trabajan de otro modo. En lugar de existir una única copia de la función dentro del código, si se declara una función como inline, lo que se hace es insertar el código de la función, en el lugar (y cada vez) que sea llamada. Esta indica al compilador que cada llamada a la función inline deberá ser reemplazado por el cuerpo de esta función. En la práctica la función inline es utilizado sólo cuando las funciones son pequeñas para evitar generar un ejecutable de tamaño considerable.

La palabra reservada inline tiene la ventaja de acelerar un programa si éste invoca regularmente a la función inline. Permite reducir considerablemente el código, en particular para los accesadores de una clase. Un accesador de clase es típicamente una función de una línea. 

Ejemplo:

 
inline int mayor(int a, int b) {
  if(a > b) return a;
  return b;
}

Más información de inline:

Prototipos de Funciones

Primero recordar que en el lenguaje de Arduino al contrario que en estandar C, no es necesario declarar los prototipos de las funciones, puesto que de eso se encarga el de incluirlo el arduino builder, al igual que de añadir la función Main.

En C++ es obligatorio usar prototipos. Un prototipo es una declaración de una función. Consiste en una presentación de la función, exactamente con la misma estructura que la definición, pero sin cuerpo y terminada con un “;”. 

En general, el prototipo de una función se compone de las siguientes secciones:

  • Opcionalmente, una palabra que especifique el tipo de almacenamiento, puede ser extern o static. Si no se especifica ninguna, por defecto será extern.
  • El tipo del valor de retorno, que puede ser void, si no necesitamos valor de retorno.
  • Modificadores opcionales. 
  • El identificador de la función. Es costumbre, muy útil y muy recomendable, poner nombres que indiquen, lo más claramente posible, qué es lo que hace la función, y que permitan interpretar qué hace el programa con sólo leerlos.
  • Una lista de declaraciones de parámetros entre paréntesis. Los parámetros de una función son los valores de entrada (y en ocasiones también de salida). 

Un prototipo sirve para indicar al compilador los tipos de retorno y los de los parámetros de una función, de modo que compruebe si son del tipo correcto cada vez que se use esta función dentro del programa, o para hacer las conversiones de tipo cuando sea necesario.

Normalmente, los prototipos de las funciones se declaran dentro del fichero del programa, o bien se incluyen desde un fichero externo, llamado fichero de cabecera, (para esto se usa la directiva #include).

Ya lo hemos dicho más arriba, pero las funciones son extern por defecto. Esto quiere decir que son accesibles desde cualquier punto del programa, aunque se encuentren en otros ficheros fuente del mismo programa. En contraposición las funciones declaradas static sólo son accesibles dentro del fichero fuente donde se definen.

Si programamos en Arduino clases o librerías, es posible que debamos utilizar los prototipos de funciones.

Más información: 

Bibliotecas/Librerías en C++

Junto con los compiladores de C y C++, se incluyen ciertos archivos llamados bibliotecas más comúnmente librerías. Las bibliotecas contienen el código objeto de muchos programas que permiten hacer cosas comunes, como leer el teclado, escribir en la pantalla, manejar números, realizar funciones matemáticas, etc.

Las bibliotecas están clasificadas por el tipo de trabajos que hacen, hay bibliotecas de entrada y salida, matemáticas, de manejo de memoria, de manejo de textos y como imaginarás existen muchísimas librerías disponibles y todas con una función específica.

La declaración de librerías, tanto en C como en C++, se debe hacer al principio de todo nuestro código, antes de la declaración de cualquier función o línea de código, debemos indicarle al compilador que librerías usar, para el saber qué términos están correctos en la escritura de nuestro código y cuáles no. La sintaxis es la siguiente: #include <nombre de la librería> o alternativamente #include “nombre de la librería”. En tu código puedes declarar todas las librerías que quieras aunque en realidad no tienen sentido declarar una librería que no vas a usar en tu programa, sin embargo no existe límite para esto.

La directiva de preprocesador #include se usa en los lenguajes C y C++ para “incluir” las declaraciones de otro fichero en la compilación. Esta directiva no tiene más misterio para proyectos pequeños. En cambio, puede ayudar aprovechar bien esta directiva en proyectos con un gran número de subdirectorios.

Ejemplo:

 
#include "iostream"
#include "string"
#include <math.h>
using namespace std;

Lo único adicional, es la línea que dice using namespace std; esta línea nos ayuda a declarar un espacio de nombre que evita tener que usarlo cada que accedemos a alguna función específica de una librería. Teniendo este namespace declarado podemos llamar por ejemplo el comando cout >>, que pertenece a la librería iostream, sin embargo sin este namespace sería std::cout >>, imagina tener que hacer esto cada vez que uses algún comando o función de las librerías, sería bastante tedioso.

Algunas de las librerías de uso más común de C++ y que forman parte de las librerías estándar de este lenguaje.

  • fstream: Flujos hacia/desde ficheros. Permite la manipulación de archivos desde el programar, tanto leer como escribir en ellos.
  • iosfwd: Contiene declaraciones adelantadas de todas las plantillas de flujos y sus typedefs estándar. Por ejemplo ostream.
  • iostream: Parte del a STL que contiene los algoritmos estándar, es quizá la más usada e importante (aunque no indispensable).
  • math: Contiene los prototipos de las funciones y otras definiciones para el uso y manipulación de funciones matemáticas.
  • memory: Utilidades relativas a la gestión de memoria, incluyendo asignadores y punteros inteligentes (auto_ptr). “auto_ptr” es una clase que conforma la librería memory y permite un fácil manejo de punteros y su destrucción automáticamente.
  • ostream: Algoritmos estándar para los flujos de salida.
  • Librería stdio: Contiene los prototipos de las funciones, macros, y tipos para manipular datos de entrada y salida.
  • Librería stdlib: Contiene los prototipos de las funciones, macros, y tipos para utilidades de uso general.
  • string: Parte de la STL relativa a contenedores tipo string; una generalización de las cadenas alfanuméricas para albergar cadenas de objetos.
  • vector: Parte de la STL relativa a los contenedores tipo vector; una generalización de las matrices unidimensionales C/C++
  • list: Permite implementar listas doblemente enlazadas (listas enlazadas dobles) fácilmente.
  • iterator: Proporciona un conjunto de clases para iterar elementos.
  • regex: Proporciona fácil acceso al uso de expresiones regulares para la comparación de patrones.
  • thread: Útil para trabajar programación multihilos y crear múltiples hilos en nuestra aplicación.

Arduino ya incluye diversas librerías por defecto sin llamarlas explícitamente, p.e. math.h

Más información:

Software para visualizar dependencias de ficheros:

Funciones Anónimas y Lambda

En la programación, una función anónima (función literal, abstracción lambda o expresión lambda) es una definición de función que no está vinculada a un identificador. Las funciones anónimas son a menudo argumentos que se pasan a funciones de orden superior, o que se utilizan para construir el resultado de una función de orden superior que necesita devolver una función. Si la función sólo se utiliza una vez, o un número limitado de veces, una función anónima puede ser sintácticamente más ligera que la que se utiliza con una función nombrada. Las funciones anónimas se utilizan en casi todos los lenguajes de programación.

En algunos lenguajes de programación, las funciones anónimas se implementan comúnmente para propósitos muy específicos, como la vinculación de eventos funciones de callback, o la instanciación de la función para valores particulares, que pueden ser más eficientes, más legibles y menos propensos a errores que la llamada a una función con nombre más genérico.

C++ soporta funciones anónimas, llamadas expresiones lambda, que tienen la forma::

[capture](parameters) -> return_type { function_body }

Un ejemplo de función lambda se define de la siguiente manera:

 
[ ](int x, int y) -> int { return x + y; }

C++11 también soporta cierres. Los cierres (closures) se definen entre corchetes ([,]) en la declaración de expresión lambda. El mecanismo permite capturar estas variables por valor o por referencia. La siguiente tabla lo demuestra:

  • [ ]        //no variables defined. Attempting to use any external variables in the lambda is an error.
  • [ x, &y ]   //x is captured by value, y is captured by reference
  • [ & ]       //any external variable is implicitly captured by reference if used
  • [ = ]       //any external variable is implicitly captured by value if used
  • [ &, x ]    //x is explicitly captured by value. Other variables will be captured by reference
  • [ =, &z ]   //z is explicitly captured by reference. Other variables will be captured by value

Más información:

Librerías vs Funciones en Arduino

Si en nuestro código tenemos múltiples funciones, podemos incluirlas en un fichero de C++ por ejemplo funciones.h, e incluyéndose en el principal con la instrucción #include “funciones.h”. En C++ el código se organiza en diferentes ficheros con extensiones .h y .cpp a los que se van llamando con #include para añadirlos al fichero que lo llama para poder usar su contenido.

Ejemplo: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_2017/tree/master/Ejercicio45-Librerias

Como se ha visto anteriormente, las librerías son trozos de código hechas 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 luego de entender. Más adelante veremos cómo hacer una librería.

Las librerías en Arduino 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).

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

Una librería a diferencia de las funciones debe estar al menos en un fichero diferente con extensión .h y opcionalmente en otro .cpp y además debe ser llamada con #include desde el sketch de arduino y estar en una ruta accesible desde el IDE de Arduino, ya sea el mismo directorio del sketch o en algunas de las rutas configuradas para librerías. 

La ventaja de usar librerías frente a las funciones es que no es necesario incluir el código cada vez que se va a reutilizar sino que con tener la librería instalada en el IDE y llamarla mediante #include ya la puedo usar en mi código.

Al llamar a una librería desde un sketch, la librería completa es cargada a la placa de Arduino incrementando el tamaño del espacio usado en el microcontrolador, tanto en la memoria flash como en la RAM.

Las librerías que usamos para los sketches tienen una versión, que se suelen actualizar con frecuencia. También tenemos un control de versiones en el nuevo IDE a partir de 1.6.4 que nos facilita la gestión de la versión de las librerías usadas. Este aspecto es importante porque un sketch que funciona con una versión de una librería es posible que al compilarlo con otra versión en otro IDE no funcione. Por ello es importante documentar con que versión de librería está hecho o distribuir el sketch con la librería con la que se ha creado. Generalmente las librerías tienen compatibilidad hacia atrás, pero puede que no ocurra o que el comportamiento de la librería sea diferente.

Al cambiar el IDE también nos podemos encontrar que nuestro sketch no es compatible con la versión de la librería que estemos usando, que es diferente con la que se diseñó originalmente el sketch.

Listado de librerías Arduino:

Prácticas: Estructuras Propias Arduino

Montaje Arduino UNO:

Montaje Wemos:

Señales digitales:

Resultado de imagen de flip-flop-periodo.gif

Ejercicio13 – Calcular Tiempo Pulso

Vamos a calcular el tiempo entre dos pulsaciones de un pulsador, esto tienes muchas aplicaciones para calcular tiempos entre dos señales digitales, p.e. calcular si pasa una persona, bicicleta, coche o camión en un paso con una fotocelula: 

Ejemplo: https://www.mytienda.es/p225/fotocelulas-sensor-infrarrojo-garaje

Para ello vamos a usar la función PulseIn: https://www.arduino.cc/en/Reference/PulseIn 

Ejemplo de PulseIn para calcular distancia con un sensor ultrasónico: https://www.luisllamas.es/medir-distancia-con-arduino-y-sensor-de-ultrasonidos-hc-sr04/

  • Paso 1 – Usando la función pulsein calcular el tiempo que mantengo pulsado el botón B y mostar por la consola.
  • Paso 2 – Usar ese tiempo para distinguir entre pulsación corta < 2 segundos y pulsación larga >= 2 segundos.
  • Paso 3 – Para una pulsación larga encender el primer led (encendido del sistema) y una vez encendido cada pulsación corta pasa de un led a otro en la secuencia 1-2-3-4-1-2-3-4-… Si se hace una pulsación corta apagar los leds.

Es un sistema que con una pulsación larga apaga o enciende el sistema y con una una corta cambia el led si anteriormente he encendido el sistema.

Sacar también los datos de tiempos por la pantalla LCD.

Solución: https://codeshare.io/2jbRYP

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio13-Pulse

Ejercicio propuesto: hacer un juego para dos jugadores con los dos botones en el que gana el que más se acerque en la pulsación a un valor de segundos generado aleatoriamente.

Ejercicio14 – Control Efectos LED

Basándonos en el ejercicio Ejercicio12-RGB_Wemos hacer un sketch que controle los efectos, con una pulsación corta cambia el color del led girando y con una larga apaga o enciende el sistema.

Usar la función millis para calcular el tiempo de la pulsación. De esta forma no se bloquea el programa en la función PulseIn. ESTO ES UN EJEMPLO DE MULTITAREA

Solución: https://codeshare.io/5vQRn7

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio14-Control_RGB_Wemos

Estructuras Propias de Arduino

Lo visto hasta ahora son estructuras y sintaxis de programación que es casi genérica a C++, pero el core de Arduino incluye algunas funciones propias para el uso de Arduino como la lectura de entradas analógicas y digitales y la escritura de salidas analógicas y digitales.

Para saber más de C y de la API de Arduino:

Veamos estas estructuras más específicas que están documentadas en el reference de Arduino https://www.arduino.cc/en/Reference/HomePage

Cómo Leer el Reference Arduino

En el reference de Arduino tenemos toda la información necesaria para entender cómo funciona el lenguaje o core de programación de Arduino. Está todo perfectamente documentado de cómo funciona cada estructura y cómo se comportan las funciones,

Reference: https://www.arduino.cc/en/Reference/HomePage 

Cuando entramos en el reference y vemos un nuevo método o función deberemos leer:

  • Description: Hace una descripción de lo que hace ese método y función
  • Syntax: Sintaxis de la función con todas las formas de llamarlo y los parámetros a pasar. Puede haber varias formas de llamar a una función, por ejemplo https://www.arduino.cc/en/Reference/EthernetBegin
  • Parameters: Descripción de los parámetros a pasar a esa función. Puede que no haya que pasar ninguno: https://www.arduino.cc/en/Reference/Millis
  • Returns: que valor me devuelve y qué tipo de dato es. Puede que no devuelva nada como https://www.arduino.cc/en/Reference/PinMode
  • Note: Nota sobre el uso de la función
  • Example: Un ejemplo de cómo usar la función
  • See Also: Otras funciones relacionadas

Entradas y Salidas Digitales

En arduino para tratar las entradas y salidas digitales usamos las siguientes funciones:

En la imagen siguiente se muestra el estado por defecto de una I/O digital en un microcontrolador de Arduino. Se ha simplificado con interruptores la compleja electrónica que hay dentro. Por defecto los digital I/O pins están configurados como inputs en un estado de alta impedancia (equivalente a una resistencia de 100 Mohms en frente del pin), es decir, SW3 a ON y no hace falta llamar a la función pinMode() aunque es recomendable para aclarar el código.

  • PinMode(x, INPUT) –> SW3 = ON (resto a OFF). Los valores leídos serán aleatorios si el pin de Arduino está al aire. El pin está en un estado de alta impedancia (resistencia de 100 Mohms).
  • PinMode(x,INPUT_PULLUP) –> SW3 = ON & SW4 = ON (resto a OFF). Los valores leídos sin nada conectado al pin es HIGH. La Resistencia R1 tiene un valor dependiendo del microcontrolador, pero tiene un valor entre 20kOhm y 150kOhm.
  • PinMode(x, OUTPUT) & digitalWrite(x,HIGH) –> SW2 = ON & SW1 = +5V (resto a OFF). Estado de baja impedancia, no hay resistencia interna y es necesario poner una resistencia adecuada a la salida el pin para no superar los 40mA (source) máximos admitidos
  • PinMode(x, OUTPUT) & digitalWrite(x,LOW) –> SW2 = ON & SW1 = GND (resto a OFF). Estado de baja impedancia, no hay resistencia interna y es necesario poner una adecuada para no superar los 40mA (sink) máximos admitidos

Entradas y Salidas Analógicas

En Arduino para tratar las entradas y salidas analógicas usamos las siguientes funciones:

Otras funciones interesantes con entradas/salidas analóicas:

La mayoría de Arduino no tienen salidas analógicas puras sino PWM. Algunos pines digitales pueden usarse como salidas analógicas PWM:

Las Salidas PWM (Pulse Width Modulation) permiten generar salidas analógicas desde pines digitales. Arduino Uno no posee salidas analógicas puras, sin embargo el Arduino Due sí tiene salidas analógicas puras mediante dos DAC. El arduino due, posee dos salidas analógicas puras mediante dos conversores digital a analógico. Estos pines pueden usarse para crear salidas de audio usando la librería correspondiente.

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)

Diferentes valores de una señal PWM:

Entradas y Salida Avanzadas

Advanced I/O:

  • tone() – Genera una onda cuadrada (ciclo y 50% de servicio) de la frecuencia especificada en un pin. Una duración se puede especificar, de lo contrario la onda continúa hasta una llamada a noTone (). El pin puede ser conectado a un zumbador piezo u otro altavoz para reproducir tonos.
  • noTone() – Detiene la generación de una onda cuadrada provocada por tone(). No tiene ningún efecto si no se está generando el tono.
  • shiftOut() – Desplaza un byte de datos de un bit cada vez. Comienza a partir del más o menos significativo. Cada bit es escrito en un pin cada vez que se produce un pulso de reloj.
  • shiftIn() – Desplaza un byte de datos de un bit cada vez. Comienza a partir dell más o menos significativo. Para cada bit, el reloj es puesto a HIGH, el siguiente bit es leído de la línea de datos y entonces el reloj es puesto a LOW.
  • pulseIn() – Lee un pulso de un pin. Si el valor es HIGH, la función espera a que el pin se ponga a HIGH, comienza a temporizar y espera hasta que el pin vuelve a LOW, devolviendo la longitud del pulso en microsegundos.

Ejemplo de uso: https://www.arduino.cc/en/Tutorial/ShiftOut 

Bits y Bytes

Bits and Bytes

  • lowByte(): extrae el byte más a la derecha (low-order o menos significativo de una variable.
  • highByte(): extrae el byte más a la izquierda (high-order o más significativo de una variable.
  • bitRead(): lee el bit de una variable numérica. 
  • bitWrite(): escribe el bit de una variable numérica
  • bitSet(): pone a 1 un bit de una variable numérica 
  • bitClear(): pone a 0 un bit de una variable numérica 
  • bit(): Calcula el valor del bit especificado (el bit 0 es 1, el bit 1 es 2, el bit 2 es 4, etc.)

Constantes

Constants;

  • HIGH | LOW – Al leer o escribir en un pin solo hay estas dos posibilidades. Su significado es diferente si el pin está configurado como INPUT o OUTPUT.
  • INPUT | OUTPUT | INPUT_PULLUP – Modo en el que pueden configurarse los pines digitales.
  • LED_BUILTIN – La mayoría de las placas Arduino tienen un pin conectado a un led en la placa y esta constante devuelve el número de pin en función de la placa.
  • true | false – Representación de las constantes booleanes en arduino
  • integer constants – son los números usados directamente en un sketch como ‘123’. Por defecto estos números son tratados como enteros pero puede cambiarse con los modificadores U y L. Las constante enteras se tratan como base 10 (decimal) pero puede usarse otra notación.
  • floating point constants – al igual que las constantes enteras, las constantes de coma flotante permite definir los número decimales. Son posibles varias notaciones para expresar constantes de coma flotante, por ejemplo: n = .005. También pueden expresarse en notación científica como 2.34E5.

Utilidades

Utilities:

  • sizeof() – devuelve el número de bytes en una variable o el número de bytes ocupados por un array.
  • PROGMEM – se usa para guardar en la memoria flash en lugar de en la SRAM. 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>

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.

Operadores Bit a Bit

Bitwise Operators:

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

Operadores Compuestos

Compound Operators

  • ++ (increment)
  • (decrement)
  • += (compound addition)
  • -= (compound subtraction)
  • *= (compound multiplication)
  • /= (compound division)
  • %= (compound modulo)
  • &= (compound bitwise and)
  • |= (compound bitwise or)

Ambito de las Variables y Calificadores

Variable Scope & Qualifiers

  • variable scope – ámbito de la variable
  • static – hace que el valor de una variable local persista más alla de la función haciendo que su valor se mantenga entre llamadas a funciones
  • volatile – Declarar una variable volátil es una directiva para el compilador. Específicamente, dice al compilador que cargue la variable desde la RAM y no desde un registro de almacenamiento, que es una ubicación de memoria temporal donde se almacenan y manipulan las variables del programa. Bajo ciertas condiciones, el valor de una variable almacenada en registros puede ser inexacto.
  • const – es un cualificador de variable que modifica el comportamiento de la variable haciendo que sea de solo lectura.

Se deben declarar como “volatile” cualquier variable que sea modificada dentro de la función llamada por una interrupción.

Funciones de Tiempo

Funciones para trabajar con el tiempo

  • delay() – Para el programa en unidades de ms
  • delayMicroseconds() – Para el programa en unidades de us
  • micros() – Microsegundos transcurridos desde el inicio o reset de la placa
  • millis() – Milisegundos transcurridos desde el inicio o reset de la placa

Funciones USB

Keyboard – Las funciones del teclado permiten que las tarjetas micro basadas en 32u4 o SAMD envíen las pulsaciones de teclas a un ordenador conectado a través del puerto USB nativo de su micro.

Mouse – Las funciones del ratón permiten a las tarjetas micro basadas en 32u4 o SAMD controlar el movimiento del cursor en un ordenador conectado a través del puerto USB nativo de su micro. Cuando se actualiza la posición del cursor, siempre es relativa a la posición anterior del cursor.

Funciones Matemáticas

La librería math.h ya está incluida por defecto en Arduino. 

Librería math C++: http://www.cplusplus.com/reference/cmath/

Librería math.h de AVR libc:

Algunas de la funciones matemáticas del Reference de Arduino:

Números Pseudoaleatorios

random() – generador de números pseudoaleatorios

randomSeed() – Inicializa el generador de números

Comunicación Serie

Librería Stream de la que heredan las librerías de comunicación serie: https://www.arduino.cc/reference/en/language/functions/communication/stream/

Comunicación serie: https://www.arduino.cc/reference/en/language/functions/communication/serial/

Funciones:

Prácticas: Operadores y Estructuras de Control con Arduino

Montaje Arduino UNO:

Montaje Wemos:

Ejercicio10-Alarma

Ejercicio10: hacer un sistema de alarma que haga sonar el buzzer cuando la temperatura suba de 24 grados. Hacer una constante con #define llamada UMBRAL donde declaramos el umbral.

Encender los leds cuando se supere la temperatura, de forma que con 24 grados se enciendo el primero, con 25 grados el segundo y así hasta encender los 4 leds.

Adicionalmente mandar por el puerto serie y por la pantalla LCD la temperatura en la primera línea y un mensaje de “ALARMA” o “NORMAL” en la segunda línea, cada vez que entre o salga del estado alarma al superar el umbral. Necesitaremos una variable global llamada alarma_temperatura que deberá actualizarse.

Diagrama de flujo (en este caso es el pin 11 donde leo la temperatura con el sensor DHT11, en lugar de A0):

Solución: https://codeshare.io/29wqDB

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio10-Alarma

Ejercicio11-while

Ejercicio11: Usando un bucle while y las instrucciones continue y break, imprimir por el puerto serie y pantalla LCD los número impares del 0 al 100 usando un delay de 100 ms. Usa una variable contador dentro del bucle while. Al llegar a 100 encender hacer parpadear 5 veces consecutivas los 4 leds.

Solución: https://codeshare.io/ay9VYz

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio11-While

Ejercicio12-RGB_Wemos

Basándonos en el ejemplo de la librería de los leds RGB del Shield Wemos RGB https://github.com/wemos/D1_mini_Examples/blob/master/examples/04.Shields/RGB_LED_Shield/simple/simple.ino, hacer el efecto un led girando en sentido horario que cada 5 vueltas aumente la velocidad, empezando desde 200 ms mostrando un led hasta 20ms continuamente. La velocidad aumenta restando 20 ms en cada ciclo: 200 – 180 – 160 – 140, etc…

Funciones:

  • leds.setPixelColor(i, leds.Color(R, G, B)); — pone el led i al color con la combinación R, G, B
  • leds.show(); — Muestra la configuración hecha con setPixelColor

Solución: https://codeshare.io/ay9VAo

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio12-RGB_Wemos

Prácticas: Variables y Tipos de Datos en Arduino

Montaje Arduino UNO:

Montaje Wemos:

Ejercicio07 – Ámbito Variables

Ámbito de las variables en C++

Ejercicio07: Mover el servo mediante el botón A. Para ello cada vez que se pulse mover 10 grados y cuando llegue a 170 grados volver a la posición inicial de 10 grados.

Usar una una variable llamada “grados” que aumente su valor cada vez que se detecte un flanco ascendente. Hacerlo como variable local y global.

Hacer una función “detectaFlanco” que use una variable local para detectar flanco.

Sacar por el puerto serie el ángulo que está, por la pantalla LCD y por el Serial Plotter

Solución: https://codeshare.io/GAoV7v

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio07-Ambito_Variables

Ejercicio08 – Puntuaciones

Hacer un programa,de forma que muestre por la pantalla LCD el valor del LDR y un contador de puntos. Al pulsar un botón A, acumule el valor del LDR multiplicado por 10 hasta un máximo de 8 pulsaciones y luego empiece en cero. Ver quién consigue más puntos. ¿Quién llegará a 80000 puntos?

Solución: https://codeshare.io/5OY4ZP

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio08-Puntuaciones

Ejercicio09 – Puntuaciones Wemos

Adaptar el código del Ejercicio09-Puntuaciones_Wemos para el Wemos D1 Mini con la pantalla oled.

Comprobar cómo adaptar un código de una placa a otra y con componentes diferentes

NOTA: conectar el LDR al pin A0 del Wemos D1 Mini

Solución: https://codeshare.io/5eey47

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio09-Puntuaciones_Wemos