Para realizar la matriz de pulsadores he distribuido, de manera similar a los leds, 9 pulsadores en una configuración de 3x3.
Como se puede ver en la figura, las tres columnas están conectadas directamente a tres salidas digitales del procesador (en este caso son las etiquetadas como A0, A1 y A2 del Arduino) mientras que las filas tienen en este caso una configuración un poco más compleja. Cada fila está equipada con una resistencia de pull-up de 1K y con un condensador de 1u en pull-down. Como cada fila está configurada como entrada, es necesario poner resistencias en pull-up para garantizar que se lea un 1 (5 voltios) cuando no haya nada pulsado. El condensador de 1u hace de filtro pasivo paso-bajo ante los posibles rebotes mecánicos de los pulsadores.
De forma similar a como hacíamos con la matriz de leds, vamos haciendo un barrido de cada una de las columnas (vamos poniéndolas a 0) y vamos comprobando el valor de todas las filas para cada activación de columna. En este caso, aunque hemos incluido un condensador antirrebote, no hay que fiarse y es mejor habilitar un mecanismo por software para minimizar el impacto del posible rebote de los pulsadores:
inicialización:
estado := SELECCIONAR_COLUMNA
columna := 0
fila := 0
ejecución:
si (estado = SELECCIONAR_COLUMNA) entonces
activarColumna(columna)
cargarContadorDescendenteTimer(10ms)
estado := ESPERAR_COLUMNA
en otro caso, si (estado = ESPERAR_COLUMNA) entonces
si (contadorTimer = 0) entonces
estado := LEER_FILAS
fin si
en otro caso, si (estado = LEER_FILAS) entonces
fila := 0;
encontrada := false
mientras ((fila < 3) AND (no encontrada)) hacer
si (activadaFila(fila)) entonces
encontrada := true
en otro caso
fila := fila + 1
fin si
fin mientras
si (encontrada) entonces
// NOTIFICAR: SE HA PULSADO LA TECLA (fila, columna)
estado := ESPERAR_REBOTE
cargarContadorDescendenteTimer(20ms)
en otro caso
columna := ((columna + 1) % 3)
estado = SELECCIONAR_COLUMNA
fin si
en otro caso, si (estado = ESPERAR_REBOTE) entonces
si (contadorTimer = 0) entonces
estado := ESPERAR_LIBERACIÓN
fin si
en otro caso, si (estado = ESPERAR_LIBERACIÓN) entonces
si (no activadaFila(fila)) entonces
// NOTIFICAR: SE HA LIBERADO LA TECLA (fila, columna)
estado := ESPERAR_REBOTE2
cargarContadorDescendenteTimer(20ms)
fin si
en otro caso, si (estado = ESPERAR_REBOTE2) entonces
si (contadorTimer = 0) entonces
columna := 0
estado := SELECCIONAR_COLUMNA
fin si
fin si
inicialización se ejecutará al principio del programa mientras que ejecución deberá ejecutarse continuamente en el bucle principal de la aplicación.
activarColumna(c) pone a 0 (0 voltios) la columna “c” y pone a 1 (5 voltios) el resto de columnas. activadaFila(f) devuelve “true” si la fila “f” está a 0 (hay un pulsador presionado que está poniendo a 0 voltios esa fila).
En cuanto se detecta una tecla pulsada se notifica y se espera un tiempo sin leer nada (ESTADO_REBOTE), cumplido ese tiempo, se espera a que la tecla pulsada se libere y en cuanto se libera se vuelve a esperar un tiempo sin leer nada (ESTADO_REBOTE2). Estos dos tiempos “muertos” son cortos pero necesarios para proteger a la aplicación de pulsaciones espúreas en caso de producirse rebote mecánico en los pulsadores.
He colgado un pequeño vídeo en el que se puede ver tanto la matriz de leds como la matriz de pulsadores en acción. Por ahora no hace nada del otro mundo: Cada tecla está vinculada a un led, lo enciende o lo apaga cuando se pulsa (no es nada del otro mundo pero bueno, es una prueba de concepto :-) )
[ añadir comentario ] ( 1585 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1891 )
La matriz de leds es un mecanismo de salida muy utilizado en el ámbito de los microcontroladores ya que permite controlar una gran cantidad de leds con relativamente pocos pines. En este post describiré cómo he aplicado la multiplexación para incluir una matriz de leds en el Arduino. En un segundo post describiré cómo implementar un teclado mediante una matriz de pulsadores.
Para realizar la matriz de leds he distribuido 9 leds en una configuración de 3x3 como se indica en la figura:
Como se puede apreciar los cátodos de los 9 diodos están conectados a las líneas verticales, que llamaremos columnas, mientras que los ánodos de los 9 diodos están conectados a las líneas horizontales, que llamaremos filas. Cada columna está conectada directamente a una salida digital mientras que cada fila está conectada mediante una resistencia a una salida digital. En total necesitamos 6 salidas digitales (3 + 3) para controlar los 9 leds.
Si asumimos que la información de iluminación la tenemos almacenada en una matriz booleana:
#define APAGADO false #define ENCENDIDO true bool leds[3][3]; // [0..2][0..2]
El procedimiento de multiplexación es muy sencillo y puede definirse, en pseudocódigo, como sigue:
inicialización:
columna := 0
cargarContadorDescendenteTimer(10ms)
ejecución:
si (contadorTimer = 0) entonces
activarColumna(columna)
para fila := 0 hasta 2 hacer
v := leds[fila][columna]
activarFila(fila, v)
fin para
columna := ((columna + 1) % 3)
cargarContadorDescendenteTimer(10ms)
fin si
inicialización se ejecutará al principio del programa mientras que ejecución deberá ejecutarse continuamente en el bucle principal de la aplicación.
activarColumna(c) debe encargarse de poner a 0 (0 voltios) la columna “c” y de poner a 1 (5 voltios) el resto de columnas. Poner a 0 voltios la columna “c” la “activa” en el sentido que los tres leds colocados a lo largo de ella son susceptibles de ser polarizados y, por lo tanto de encenderse.
activarFila(f, v) debe encargarse de poner a 1 (5 voltios) la fila “f” si “v” es “true” y de ponerla a 0 (0 voltios) si “v” es “false”. Poner a 5 voltios la fila “f” enciende el led localizado en esa columa “f” y en la columna previamente activada.
Con una adecuada temporización, la sensación es de que no hay parpadeo. Ya tenemos a nuestra disposición un pequeño display de 9 luces utilizando tan solo 6 salidas digitales. Este tipo de multiplexación puede ser ampliado, obviamente, a cualquier configuración (por ejemplo 3 x 4 = 12 luces con 3 + 4 = 7 salidas digitales).
En mi caso particular, he mejorado las características de la matriz incluyendo un tercer estado de “parpadeo”. Para este caso, partiendo del pseudocódigo anterior, tan solo hay que hacer algunos pequeños cambios:
Definimos la matriz de leds como multivaluada:
#define APAGADO 0 #define PARPADEANTE 1 #define ENCENDIDO 2 uint8_t leds[3][3]; // [0..2][0..2]
Y utilizamos un timer adicional más lento para generar el parpadeo:
inicialización:
columna := 0
cargarContadorDescendenteTimer1(10ms)
cargarContadorDescendenteTimer2(500ms)
parpadeoEncendido := true
ejecución:
si (contadorTimer1 = 0) entonces
activarColuma(columna)
para fila := 0 hasta 2 hacer
aux := leds[fila][columna]
v := (aux == 2) OR ((aux == 1) AND parpadeoEncendido)
activarFila(fila, v)
fin para
columna := ((columna + 1) % 3)
cargarContadorDescendenteTimer1(10ms)
fin si
si (contadorTimer2 = 0) entonces
parpadeoEncendido := no parpadeoEncendido
cargarContadorDescendenteTimer2(500ms)
fin si
Ahora tenemos que cada led puede estar en tres estados: apagado, parpadeando o encendido.
Existen otras técnicas de multiplexado, como el Charlieplexing (http://en.wikipedia.org/wiki/Charlieplexing) con las que se consiguen controlar una mayor cantidad de leds con menos pines pero a costa de unos mayores requerimientos de hardware (el charlieplexing requiere más circuitería y que las salidas sean triestadas).
[ añadir comentario ] ( 1502 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1829 )
Programar el microcontrolador AVR el Arduino con el lenguaje Processing está muy bien y es una forma muy rápida de desarrollar aplicaciones sencillas. Sin embargo cualquiera que quiera hacer algo medianamente estructurado o complejo echará rápidamente de menos el C o el C++.
Lo cierto es que el toolkit que se instala con Arduino viene ya con una completa toolchain GNU de C y C++: El propio lenguaje Processing es una especie de "subconjunto" de C y lo que hace el IDE del Arduino no es otra cosa que traducir a C los sketchs que hacemos en lenguaje Processing.
Para hacer nuestro primer programita en C++ para el Arduino vamos a probar hacer una versión OOP del famoso parpadeo (
blink.cc
):#include <avr/io.h> #include <util/delay.h> using namespace std; class Led { public: Led(); void on(); void off(); }; Led::Led() { // bit 7 el puerto C en modo salida DDRC |= 0x80; } void Led::on() { // bit 7 del puerto C a 1 PORTC |= 0x80; } void Led::off() { // bit 7 del puerto C a 0 PORTC &= 0x7F; } int main() { Led led; while (1) { led.on(); _delay_ms(250); led.off(); _delay_ms(250); } }
Hacer parpadear un led en C++ con OOP no deja de ser como matar moscas a cañonazos pero bueno, se trata de una prueba de concepto :-)
Para compilar el programita utilizamos la toolchain de GNU que ya viene con el software del Arduino:
PREFIJO_RUTA_BIN/avr-g++ -DF_CPU=16000000UL -mmcu=atmega32u4 -Os -c -o blink.o blink.cc PREFIJO_RUTA_BIN/avr-g++ -DF_CPU=16000000UL -mmcu=atmega32u4 -Os -o blink.elf blink.o PREFIJO_RUTA_BIN/avr-objcopy -O ihex blink.elf blink.hex
Utilizo la opción
-mmcu=atmega32u4
porque en mi caso concreto estoy utilizando un Arduino Leonardo, que posee un procesador ATmega32u4.Ahora sólo falta subir el fichero
hex
al Arduino mediante la utilidad avrdude
incluida en la toolchain de GNU. El Arduino cuando arranca permanece unos pocos segundos en modo "boot" a la espera de ver si desde un ordenador conectado por USB se le envía algún fichero hex para instalar y ejecutar. En caso de que no se le envíe nada en este modo, el procesador del Arduino pasa a ejecutar el código que contenga ahora mismo la flash interna. Si estando en modo "boot" le llega algún nuevo programa, lo escribe en la flash interna y a continuación lo ejecuta.Para poder cargar un fichero hex en el Arduino bastará con lanzar el siguiente comando inmediatamente después de hacer un reset:
PREFIJO_RUTA_BIN/avrdude -patmega32u4 -cavr109 -P/dev/ttyUSBXXX -b57600 -D -Uflash:w:blink.hex:i -C PREFIJO_RUTA_ETC/avrdude.conf
Algunos de los parámetros que le paso al
avrdude
son específicos para Arduino Leonardo, en caso de usar otro modelo de Arduino hay que mirar qué parámetros son los adecuados. Nótese que el ejecutable avrdude
no se encuentra en la misma carpeta que el fichero avrdude.conf
. Hay que asegurarse de que se usan las rutas correctas y acordes a las carpetas de instalación del software del Arduino.Si en el Arduino se encuentra previamente cargado un sketch realizado con lenguaje Processing es posible cargar un nuevo fichero hex sin necesidad de reiniciar el Arduino: Basta con abrir el puerto serie USB y configurarlo a 1200 bps (sin necesidad de enviar ni recibir ningún byte) para que el Arduino pase a modo "boot", como si lo hubiésemos reiniciado (gracias a Nicholas Kell por esta info).
He aquí un programa de ejemplo, compilable en cualquier *nix, que realiza este "reseteo soft":
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <termios.h> #include <string.h> int main(int argc, char *argv[]) { struct termios tio; int fd; if (argc < 2) return 1; fd = open(argv[1], O_RDONLY | O_NONBLOCK); tcgetattr(fd, &tio); tio.c_cflag = CS8; cfsetspeed(&tio, B1200); tcsetattr(fd, TCSANOW, &tio); close(fd); return 0; }
De esta manera y, de forma genérica, el script
upload.sh
podríamos dejarlo como sigue:#!/bin/sh ./reset $2 sleep 2 PREFIJO_RUTA_BIN/avrdude -patmega32u4 -cavr109 -P$2 -b57600 -D -Uflash:w:$1:i -C PREFIJO_RUTA_ETC/avrdude.conf
Invocándolo de la siguiente manera:
./upload.sh blink.hex /dev/ttyUSBXXX
Y voilà, ya tenemos nuestro primer led parpadeante hecho en C++ :-)
[ añadir comentario ] ( 2538 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1870 )
En 1961 el Lee C.Y. de los Bell Telephone Labs desarrolló un algoritmo muy sencillo y eficaz para el trazado de rutas sobre mallas de puntos. Este algoritmo es muy usado en la actualidad para el enrutado automático de las pistas de cobre en las placas de circuitos impresos.
El algoritmo, que parte de dos puntos que se desean conectar, se divide en dos pasos:
Paso 1. Se genera un frente de onda partiendo del punto origen de la ruta hasta tocar al punto destino. Este frente de onda se va extendiendo por la malla mediante un recorrido en amplitud marcando los puntos por los que va pasando y asignándoles valores cada vez mayores a medida que el frente de onda se aleja del punto origen.
A continuación puede verse cómo se propagaría el frente de onda desde el punto $o=(5, 5)$ y llega hasta el punto $d=(9, 9)$ sobre una malla de 11x11.
0 10 9 8 7 6 7 8 9 10 0
10 9 8 7 6 5 6 7 8 9 10
9 8 7 6 5 4 5 6 7 8 9
8 7 6 5 4 3 4 5 6 7 8
7 6 5 4 3 2 3 4 5 6 7
6 5 4 3 2 o1 2 3 4 5 6
7 6 5 4 3 2 3 4 5 6 7
8 7 6 5 4 3 4 5 6 7 8
9 8 7 6 5 4 5 6 7 8 9
0 9 8 7 6 5 6 7 8 d9 10
0 0 9 8 7 6 7 8 9 10 0
Paso 2. Se genera la ruta partiendo del punto destino y volviendo hacia atrás sobre las marcas generadas por el frente de onda de tal forma que cada punto sólo se conecta con otro que tenga una marca con un valor menor. Así hasta llegar al punto origen.
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 1 0 0 0 0
0 0 0 0 0 0 1 1 0 0 0
0 0 0 0 0 0 0 1 1 0 0
0 0 0 0 0 0 0 0 1 1 0
0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 0
En pseudocódigo sería algo así:
Entrada:
M : Malla de puntos en la que cada punto tiene una marca (número) y un trazo
origen : Punto origen de la malla
destino: Punto destino de la malla
Salida:
La misma malla M con los puntos trazados uniendo origen con destino
Algoritmo:
cola := {}
meter(cola, origen)
poner todas las marcas de la malla a 0
poner_marca(M, origen, 1)
hacer
p := sacar(cola)
para cada punto q de M vecino de p, no marcado, ni trazado hacer
meter(cola, q)
poner_marca(M, q, obtener_marca(M, p) + 1)
fin para
fin hacer mientras ((cola no vacía) y (p != destino))
si (p == destino)
p := destino
poner_trazo(M, p)
mientras (p != origen) hacer
aux := punto de la vecindad de p con marca inferior a p y más cercano a origen
poner_trazo(M, aux)
p := aux
fin mientras
en otro caso
error: no hay ruta desde origen a destino
fin si
En la sección soft de la web puede descargarse una implementación en C++ que he hecho de este algoritmo. Como se puede apreciar la mayor carga de complejidad computacional recae en la primera parte, encargada de la propagación del frente de onda ya que se trata de un recorrido en amplitud.
El código de ejemplo en C++ genera una malla de 11 por 11 puntos e intenta trazar sobre ella 6 rutas. Las 5 primeras rutas se trazan mientras que para el último par de puntos el algoritmo no encuentra una ruta válida.
tracing from (5, 5) to (9, 9)... ok
tracing from (5, 10) to (9, 3)... ok
tracing from (0, 10) to (10, 6)... ok
tracing from (1, 1) to (1, 1)... ok
tracing from (0, 1) to (2, 1)... ok
tracing from (6, 8) to (3, 2)... no route found
output grid:
5 5 5 0 0 0 0 0 0 0 0
5 4 5 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 3 3 3
0 0 0 0 0 0 0 3 3 2 3
0 0 0 0 3 3 3 3 0 2 3
0 0 0 0 3 1 1 0 0 2 3
0 0 0 0 3 0 1 1 0 2 3
0 0 0 3 3 0 0 1 1 2 2
0 0 3 3 0 0 0 0 1 1 2
0 3 3 0 0 0 0 0 0 1 2
3 3 0 0 0 2 2 2 2 2 2
Como se puede ver es un algoritmo muy útil para el trazado automático de circuitos impresos.
[ 2 comentarios ] ( 1790 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1904 )
El modelado de masas interconectadas con muelles es muy utilizado para la simulación de sistemas físicos y de otras estructuras dinámicas. Plantearemos las ecuaciones diferenciales básicas y a continuación realizaremos una implementación numérica mediante el método de Euler.
La teoría.
La fuerza que actúa sobre un punto $\vec{p}_i$ se puede definir, según la primera ley de Newton, como:
$$\vec{F}_i = m_i \vec{a}_i$$
Si despejamos la aceleración nos queda:
$$\vec{a}_i = {1 \over m_i} \vec{F}_i$$
Como la aceleración es la derivada de la velocidad con respecto al tiempo:
$${d\vec{v}_i \over dt} = {1 \over m_i} \vec{F}_i$$
Teniendo en cuenta que la fuerza total aplicada sobre el punto $\vec{p}_i$ es la suma de cada una de las fuerzas aplicadas en ese punto:
$${d\vec{v}_i \over dt} = {1 \over m_i} \sum\limits_j \vec{F}_{ij}$$
Siendo $\vec{F}_{ij}$ la fuerza aplicada en el punto $\vec{p}_i$ por parte del muelle que conecta dicho punto al punto $\vec{p}_j$. Desarrollando la ecuación anterior utilizando la ley de Hooke para el cálculo de $\vec{F}_{ij}$, obtenemos el siguiente sistema de ecuaciones diferenciales:
$${d\vec{v}_i \over dt} = {1 \over m_i} \sum\limits_j (k_{mij} \vec{e}_{ij} - k_{aij}(\vec{v}_i - \vec{v}_j))$$
$${d\vec{p}_i \over dt} = \vec{v}_i$$
Siendo:
$\vec{p}_i$ = El vector posición del punto $i$.
$\vec{v}_i$ = El vector velocidad del punto $i$ (derivada de $\vec{p}_i$ con respecto al tiempo).
$\vec{v}_j$ = El vector velocidad del punto $j$ (derivada de $\vec{p}_j$ con respecto al tiempo).
$\vec{e}_{ij}$ = El vector elongación del punto $i$ con respecto al punto $j$.
$m_i$ = La masa del punto $i$.
$k_{mij}$ = La constante del muelle que une los puntos $i$ y $j$.
$k_{aij}$ = La constante del amortiguador que une los puntos $i$ y $j$.
En esta ecuación, $k_{mij} \vec{e}_{ij}$ es la fuerza del muelle puro y $k_{aij}(\vec{v}_i - \vec{v}_j)$ es la fuerza del amortiguador.
El vector de elongación $\vec{e}_{ij}$ se calcula de la siguiente manera:
Siendo $l_{rij}$ la longitud, en reposo, del muelle que une los puntos $\vec{p}_i$ y $\vec{p}_j$.
Si $Distancia(\vec{p}_i, \vec{p}_j) < l_{rij}$, entonces $\vec{e}_{ij}$ es el vector unitario que va de $\vec{p}_j$ a $\vec{p}_i$.
Si $Distancia(\vec{p}_i, \vec{p}_j) > l_{rij}$, entonces $\vec{e}_{ij}$ es el vector unitario que va de $\vec{p}_i$ a $\vec{p}_j$.
Si $Distancia(\vec{p}_i, \vec{p}_j) = l_{rij}$, entonces $\vec{e}_{ij} = (0, 0)$.
El sumatorio debe recorrer todos los $j$ que representen puntos unidos al punto $i$ mediante un muelle.
Para implementar las ecuaciones diferenciales se puede aplicar el método de Euler (tiene un buen comportamiento para intervalos de $t$ pequeños y constantes y es múy fácil de programar aunque el error es un poco alto). Lo que se suele recomendar en foros de programación es el método de Runge-Kutta de cuarto orden: no es complicado de implementar y tiene un error razonablemente bajo.
Implementación mediante el método de Euler.
Para una ecuación diferencial de la siguiente forma:
$${dy \over dt} = f(t, y)$$
Podemos aproximar de forma numérica la integral resultante:
$$y=\int f(t, y)dt$$
mediante la siguiente ecuación de recurrencia:
$$y_{n+1} = y_n + hf(t_n, y_n)$$
Siendo $h$ el ancho del intervalo de integración en el tiempo (cuando más chico mejor). Si integramos el sistema de ecuaciones diferenciales del sistema de puntos unidos por muelles:
$$\vec{v}_i = \int {1 \over m_i} \sum\limits_j (k_{mij} \vec{e}_{ij} - k_{aij}(\vec{v}_i - \vec{v}_j))dt$$
$$\vec{p}_i = \int \vec{v}_i dt$$
podemos aplicar el método de Euler de forma directa:
$$\vec{v}_i[n+1] = \vec{v}_i[n] + h{1 \over m_{i}} \sum\limits_j (k_{mij}\vec{e}_{ij}[n] - k_{aij}(\vec{v}_i[n] - \vec{v}_j[n]))$$
$$\vec{p}_i[n+1] = \vec{p}_i[n] + h \vec{v}_i[n]$$
$h$ es el ancho en segundos de cada intervalo de simulación. Esta $h$ se puede calcular de la siguiente manera:
$$h = {1 \over fps}$$
Siendo $fps$ la tasa de refresco en frames por segundo.
En esta web he implementado en javascript y HTML5 un pequeño sistema de cuatro masas unidas por muelles. Haciendo click con el ratón se pueden mover las masas e interactuar con el sistema.
[ añadir comentario ] ( 1570 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1851 )