Luces del belén controladas por Arduino con ciclo día-noche 
El año pasado por estas mismas fechas planteé y desarrollé unas luces para el belén controladas por una placa Arduino y que generaban una cadencia de destellos en función de la luz ambiente: a menor luz ambiente, más destellos y a mayor luz ambiente, menos destellos. Este año he planteado y desarrollado una segunda revisión un poco más avanzada del concepto de luces del belén.

Aspectos funcionales

La idea es que las luces del belén varíen sus destellos, no en función de la luz ambiente, sino en función de la hora que sea en cada momento. Es preciso definir lo que se entiende por “día” y por “noche” (en intervalo horario) y se debe especificar un patrón de comportamiento de las luces para cada uno de esos intervalos horarios.

Lo que he hecho en este caso es definir, para cada tramo horario, la probabilidad de que una luz cualquiera de las 5 brille. Dicha probabilidad será de 0.1 cuando estemos en el intervalo horario que hemos definido como “día” y de 0.8 cuando estemos en el intervalo horario que hemos definido como “noche”.

Aspectos técnicos

El problema es principalmente saber cuándo estamos en el intervalo horario “día” y cuándo estamos en el intervalo horario “noche”. Para ello, he recurrido al circuito desarrollado en el anterior post, en el que conecté un chip RTC (el clásico DS1307) a la placa Arduino mediante el bus I2C.



El circuito consta de varias partes:

- Por un lado tenemos la parte del reloj RTC con los dos hilos del bus I2C interconectando el Arduino y el DS1307 (SDA y SCL). El chip RTC tiene conectada una pila para mantener la hora cuando el resto del circuito esté apagado y un cristal de cuarzo de 32768Hz necesario para que el reloj sea preciso.

- Por otro lado tenemos las luces en sí: 5 leds blancos de alta luminosidad.

- Y finalmente tenemos un pulsador y un led adicional etiquetados como “Reset RTC” y “Led indicador de reset del RTC” respectivamente. El pulsador sirve para reiniciar la hora del RTC a las 00:00 (para ponerlo en hora, vamos) y el led indica que se ha puesto en hora de forma satisfactoria. Este último led se deja en la misma placa, no forma parte de las luces parpadeantes del belén. Para evitar que pulsaciones accidentales cambien la hora del chip RTC, es necesario mantener pulsado el botón al menos 2 segundos para que tenga efecto.

Las clases I2C y RTC desarrolladas en el post anterior han tenido que ser readaptadas para hacerlas no bloqueantes (básicamente usando autómatas). Las principales clases son las siguientes:

ADCManager: Es una utility class que permite leer las entradas analógicas. Se utiliza para inicializar el generador de números pseudoaleatorios.
I2C: Otra utility class para gestionar la comunicación a través del bus I2C del microcontrolador (SDA y SCL). Incluye un autómata para controlar los estados de espera del bus.
RTC: Es otra utility class que llama internamente a los métodos de I2C utilizando comandos específicos del RTC DS1307.
RTCListener: Es una clase abstracta que debe ser implementada por aquel objeto que desee ser avisado cada vez que cambia la hora.
RTCObserver: Una utility class encargada de consultar el RTC cada 250 milisegundos y de avisar al objeto que implemente RTCListener cada vez que cambie la hora. Lo he hecho así para no “estresar” el RTC.
StarsManager: Es la clase encargada de generar los destellos. Hereda de RTCListener y, cada vez que cambia la hora, comprueba en qué intervalo se encuentra (“día” o “noche”) y modifica, si procede, la probabilidad de destello de los leds del belén.

Todo el código fuente, en C++, puede ser descargado de la sección soft. Para compilar el proyecto sólo es necesario tener instalada la toolchain de GNU para AVR, dicha toolchain se instala junto con el resto de software que viene con el Arduino. Sólo hay que revisar el fichero Makefile y cambiar las rutas para adecuarlas a la ruta de instalación que tengamos en nuestro ordenador.



[ añadir comentario ] ( 1524 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1662 )
Conectar un reloj de tiempo real al microcontrolador AVR 
Los microcontroladores AVR poseen una interface de bus I2C que permite conectarlos a EEPROMs, RTCs, DACs y muchos otros periféricos. El bus I2C es un estándar ampliamente utilizado para la interconexión de dispositivos a bajo nivel y en este post analizaré cómo conectar un microcontrolador AVR (presentes en la familia Arduino) con un chip RTC (Real Time Clock) utilizando este bus I2C.



La inicialización del bus I2C la podemos encapsular dentro de una clase estática:

#include "I2C.h"
#include <stdint.h>
#include <avr/io.h>

using namespace avelino;
using namespace std;

void I2C::init() {
    TWSR = 0x00;
    // TWBR = 12;   // 400KHz
    TWBR = 72;    // 100KHz
    TWCR = (1 << TWEN);
}

void I2C::start() {
    TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
    while ((TWCR & (1 << TWINT)) == 0)
        ;
}   
    
void I2C::stop() {
    TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
}

void I2C::write(uint8_t v) {
    TWDR = v;
    TWCR = (1 << TWINT) | (1 << TWEN);
    while ((TWCR & (1<<TWINT)) == 0)
        ;
}

uint8_t I2C::readACK() {
    TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
    while ((TWCR & (1 << TWINT)) == 0)
        ;
    return TWDR;
}

uint8_t I2C::readNACK() {
    TWCR = (1 << TWINT) | (1 << TWEN);
    while ((TWCR & (1 << TWINT)) == 0)
        ;
    return TWDR;
}

uint8_t I2C::getStatus() {
    return (TWSR & 0xF8);
}

En este caso configuramos la velocidad I2C a 100KHz ya que es la velocidad a la que trabaja el chip RTC DS1307.

A continuación podemos definir otra clase estática para acceder al RTC:

#include "RTC.h"
#include "I2C.h"

using namespace avelino;
using namespace std;

void RTC::init() { 
    I2C::init();
    // read halt bit
    I2C::start();
    I2C::write(0xD0);
    I2C::write(0x00);
    I2C::start();
    I2C::write(0xD1);
    uint8_t v = I2C::readNACK();
    I2C::stop();
    if ((v & 0x80) != 0) {
        // clock is disabled, enabling
        I2C::start();
        I2C::write(0xD0);
        I2C::write(0x00);
        I2C::write(v & 0x7F);
        I2C::stop();
    }
}       

void RTC::read(uint8_t &hour, uint8_t &minute, uint8_t &second) {
    I2C::start();
    I2C::write(0xD0);
    I2C::write(0x00);
    I2C::start();
    I2C::write(0xD1);
    second = I2C::readACK();
    minute = I2C::readACK();
    hour = I2C::readNACK();
    I2C::stop();
}

El método init, tras inicializar el bus I2C, consulta la dirección de memoria 0 del RTC que, además del secundero del reloj, también almacena el halt bit (bit 7). Este bit se encuentra a 1 de fábrica y debe ser puesto a 0 para que el RTC arranque. En el if se comprueba si este bit está a 1, si es así, se pone a 0.

Por ahora no nos estamos preocupando de la hora real. Cuando el DS1307 se activa comienza a contar como si fuesen las 0:00 horas de 1 de enero de 2000.

A continuación, para ver que el RTC funciona bien, podemos hacer un sencillo programa que cambie el estado del led de la placa Arduino por cada segundo que pasa:

#include <stdint.h>
#include "Led.h"
#include "RTC.h"

using namespace avelino;
using namespace std;

uint8_t hour, minute, second, prevSecond;
    
int main() {
    RTC::init();
    Led::init();
    while (1) {
        RTC::read(hour, minute, second);
        if (second != prevSecond) {
            Led::change();
            prevSecond = second;
        }
    }   
}       

Voilà, ya tenemos nuestro microcontrolador conectado al reloj de tiempo real.



[ añadir comentario ] ( 1323 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 2.9 / 1661 )
Tres en raya con el Arduino 
Partiendo del diseño hardware de los leds y los interruptores multiplexados realizado en anteriores posts de este blog he realizado una implementación “tonta” del juego de tres en raya.

Aspectos funcionales

La idea es realizar un juego de tres en raya utilizando la matriz de 3x3 leds en combinación con la matriz de 3x3 pulsadores descritas ambas en post anteriores. Esta implementación inicial tiene las siguientes características y limitaciones:
- El microcontrolador utiliza un algoritmo tonto para realizar movimientos: elige aleatoriamente sobre qué celda jugar en cada turno.
- Siempre comienza jugando el microcontrolador.
- En lugar de círculos y aspas utilizamos la marca “luz fija” y “luz parpadeante”. El microcontrolador juega siempre con “luz parpadeante”.
- En cuanto el juego termina (ya sea porque gana el microcontrolador, porque gana el jugador o porque quedan en tablas), el juego se para.

El funcionamiento es el siguiente:
- Tras el reset, el microcontrolador mueve y marca una casilla del tablero (el microcontrolador siempre juega con la marca “luz parpadeante”).
- El microcontrolador espera a que el jugador humano mueva activando el pulsador correspondiente a la casilla que quiere marcar.
- Al activar el pulsador, se marca el led correspondiente como “luz fija” e inmediatamente después, el microcontrolador vuelve a mover.
Así sucesivamente hasta que gane uno de los dos jugadores o el juego quede en tablas. Como se puede adivinar, al ser el jugador de la máquina un jugador “tonto” que elige sus movimientos de forma aleatoria, es muy fácil ganar :-). En sucesivas versiones intentaré ir mejorando la “inteligencia” del microcontrolador.

Diagrama de clases


Las principales clases serían las siguientes:
Game: Se encarga de la mecánica abstracta de cualquier juego de dos jugadores, los turnos, quién gana, etc.
GameBoard: Tablero de cualquier juego (abstracto).
TTTGameBoard: Tablero del juego del tres en raya.
TTTLedGameBoard: Tablero del juego del tres en raya especializado en visualizar su estado en la matriz de leds.
GamePlayer: Jugador de cualquier juego (abstracto).
TTTGamePlayer: Jugador del juego del tres en raya.
TTTRandomGamePlayer: Jugador “tonto” del tres en raya. Elige los movimientos aleatoriamente.
TTTHumanGamePlayer: Jugador humano del tres en raya. Elige los movimientos leyéndolos de la matriz de pulsadores.

A continuación, se puede ver la rutina principal. Se utilizan dos estados de juego ("jugando" y "terminado"):

#include "util.H"
#include "Timer.H"
#include "MyLedMatrixManager.H"
#include "MyKeyMatrixManager.H"
#include "TTTRandomGamePlayer.H"
#include "Game.H"
#include "TTTHumanGamePlayer.H"
#include "TTTLedGameBoard.H"

using namespace std;
using namespace avelino;

#define GAME_STATUS_PLAYING  1
#define GAME_STATUS_FINISHED 2

MyLedMatrixManager ledMatrixManager;
MyKeyMatrixManager keyMatrixManager;
TTTRandomGamePlayer p1;
TTTHumanGamePlayer p2;
Game g;
TTTLedGameBoard board;
uint8_t gameStatus;

int main() {
    Timer::init();
    ledMatrixManager.init();
    board.init(ledMatrixManager);
    p1.init(1, board);
    p2.init(2, board);
    keyMatrixManager.init(p2);
    g.init(p1, p2, board);
    gameStatus = GAME_STATUS_PLAYING;
    while (true) {
        ledMatrixManager.run();
        if (gameStatus == GAME_STATUS_PLAYING) {
            if (!g.isFinished()) {
                keyMatrixManager.run();
                g.run();
                board.show();
            }
            else 
                gameStatus = GAME_STATUS_FINISHED;
        }
        else if (gameStatus == GAME_STATUS_FINISHED) {
            //
        }
    }
    return 0;
} 


Consideraciones adicionales a tener en cuenta

La generación de números aleatorios

Para la implementación de la clase TTTRandomGamePlayer (el jugador “tonto”) se necesitan las funciones “srand” y “rand” de la librería estándar de C (u otras similares). En lugar de la clásica solución
srand(time(NULL));

que no puede ser utilizada en este caso ya que el sistema carece de reloj de tiempo real, opté por utilizar como semilla aleatoria la lectura de una de las entradas analógicas del microcontrolador que se encuentra sin cablear (al aire):
ADCManager::init();
srand(ADCManager::get(0));

Siendo ADCManager una clase con dos métodos estáticos: “init” inicializa el subsistema de conversión analógico-digital del microcontrolador y “get” lee la entrada analógica que se le pasa por parámetros.

Evaluación del tablero

La evaluación del tablero del tres en raya para determinar quién ha ganado tras cada movimiento la he implementado basándome en el algoritmo descrito en el documento “A general algorithm for tic-tac-toe board evaluation” del profesor Aaron Gordon (departamento de ciencias de la computación y sistemas de información de la universidad de Fort Lewis, Estados Unidos). Dicho algoritmo organiza la cuadrícula de 3x3 en forma de cuadrado mágico: un cuadrado en el que la suma de las columnas, las filas y las diagonales siempre da el mismo resultado. El algoritmo es muy ingenioso y eficiente y para entenderlo bien vale la pena leerse el artículo (aunque está en inglés, es muy sencillo de leer y se entiende perfectamente). De todas formas, en un futuro post abordaré el estudio de este interesante algoritmo.

En la sección soft puede descargarse el código fuente del proyecto.



[ 2 comentarios ] ( 1915 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1682 )
Matriz de pulsadores con Arduino 
En el post anterior presenté el desarrollo para Arduino de una matriz de leds utilizando la técnica de la multiplexación. En este post desarrollaré el mismo concepto pero esta vez para leer un teclado en forma de matriz de pulsadores.

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 ] ( 1364 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1620 )
Matriz de leds con Arduino 
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 ] ( 1323 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1572 )

<< <Anterior | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Siguiente> >>