Vúmetro LCD 
La posibilidad de redefinir una parte del juego de caracteres en los displays LCD alfanuméricos, en combinación con el uso de una de las entradas analógicas del AVR y un pequeño circuito analógico, nos va a permitir la implementación de un sencillo vúmetro en el Arduino.

Un vúmetro no es más que un medidor gráfico de intensidad de señal de audio. En este caso vamos a abordar una implementación muy sencilla y lineal (no logarítmica).



Entrada analógica

La entrada analógica del microcontrolador AVR en el Arduino permite medir entre 0 y 5 voltios con una precisión de 10 bits, de 0 a 1023. 0 se corresponde con 0 voltios y 1023 se corresponde con 5 voltios. Para obtener la intensidad de señal en una de las entradas analógicas, realizamos los siguientes pasos:

1. Amplificación
2. Detección de envolvente

La amplificación se ha implementado construyendo una sencilla etapa estándar de amplificación basada en un transistor NPN con configuración de emisor común:



La señal de entrada es una señal de audio en alterna (en este caso la he conectado a la salida de auriculares de mi móvil). Dicha señal, al pasar por el circuito de amplificación se invierte en fase (no nos importa) y, lo más importante, se amplifica. La señal de salida es también en alterna (el condensador de salida elimina la componente de continua) y pasa la siguiente parte del circuito: el detector de envolvente.



Un detector de envolvente es un circuito que, a partir de una señal de entrada, determina su envolvente:



(imagen extraida de Wikimedia Commons)

El circuito detector de envolvente es muy sencillo: La señal alterna, al hacerla pasar por un diodo, se rectifica y sólo deja pasar los semiciclos positivos. En cada uno de los semiciclos positivos de la señal se carga el condensador y, durante las pausas entre semiciclos, en ausencia de corriente que pase por el diodo, el condensador se descarga lentamente a través de la resistencia, así hasta el siguiente ciclo, que se repite el proceso. El resultado en la salida es una señal que “sigue” de forma aproximada a los picos de la señal de audio que hay en la entrada.

Hay que elegir correctamente los valores del condensador y la resistencia: Un valor de condensador muy bajo hará que se descargue rápidamente mientras que un valor de condensador muy alto hará que tarde excesivamente en cargarse. En el caso de la resistencia de descarga del condensador un valor muy alto hará que el condensador apenas se descargue en las pausas entre ciclos (lo que puede provocar que los ciclos se “sumen” a medida que llegan) y un valor muy bajo hará que el condensador se descargue muy rápido, perdiendo el efecto de seguimiento de envolvente.

Display LCD

En post anteriores de este blog publiqué varias clases C++ para la gestión de displays LCD. En este caso he reutilizado la clase “Lcd20x4” de proyectos anteriores, redefiniendo de forma estática 5 de los caracteres definibles por el usuario.

Para implementar una barra horizontal hay que tener en cuenta que tenemos 20 columnas y que cada carácter tiene 5 columnas de puntos: En total tenemos 100 pixels en horizontal para un display LCD de 20x4. Si definimos los caracteres de la siguiente forma:
* . . . .
* . . . .
* . . . .
* . . . . --> 1
* . . . .
* . . . .
* . . . .

* * . . .
* * . . .
* * . . .
* * . . . --> 2
* * . . .
* * . . .
* * . . .

...

* * * * *
* * * * *
* * * * *
* * * * * --> 5
* * * * *
* * * * *
* * * * *

Utilizando este método de visualización, la barra LCD podrá adoptar valores entre 0 y 100. El pseudocódigo para visualizar un valor “v” será:

procedimiento mostrar_valor(v)     // 0 <= v <= 100
numCaracteresTotalmenteLlenos := parte entera de (v / 5)
valorCaracterParcial := (v mod 5)
ir_coordenada(0, 0)
para x := 1 hasta numCaracteresTotalmenteLlenos hacer
escribir_carácter(5)
fin para
escribir_carácter(valorCaracterParcial)
para x := (numCaracteresTotalmenteLlenos + 2) hasta 20 hacer
escribir_carácter(0)
fin para
fin procedimiento

Como se puede ver, para cada valor “v” que se quiera visualizar en la barra, se escribe toda una fila horizontal del display LCD. La visualización puede optimizarse si partimos de la base de que la diferencia entre un valor “v” enviado en un instante “t” y el valor “v” enviado al display en un instante “t + d” no va a variar mucho si “d” es lo suficientemente pequeño. En nuestro caso vamos a hacer un muestreo de la entrada analógica cada 50ms por lo que la “v” no va a variar excesivamente entre un instante de muestreo y el siguiente.

Si asumimos que la “v” no va a variar mucho podemos enviar al LCD sólo los cambios:

barraLogica[20]
barraFisica[20]

procedimiento mostrar_valor(v)
numCaracteresTotalmenteLlenos := parte entera de (v / 5)
valorCaracterParcial := (v mod 5)
j := 0
para x := 1 hasta numCaracteresTotalmenteLlenos hacer
barraLogica[j] := 5
j := j + 1
fin para
barraLogica[j] := valorCaracterParcial
j := j + 1
para x := (numCaracteresTotalmenteLlenos + 2) hasta 20 hacer
barraLogica[j] := 0
j := j + 1
fin para
fin procedimiento

procedimiento actualiza
dentroCambio := NO
inicioCambio := -1
para j := 0 hasta 19 hacer
si (dentroCambio) entonces
si (barraFisica[j] = barraLogica[j]) entonces
escribir_barra_fisica(inicioCambio, j)
dentroCambio := NO
fin si
en otro caso
si (barraFisica[j] <> barraLogica[j]) entonces
inicioCambio := j
dentroCambio := SI
fin si
fin si
barraFisica[j] := barraLogica[j]
fin para
si (dentroCambio) entonces
escribir_barra_fisica(inicioCambio, 19)
fin si
fin procedimiento

El procedimiento “actualiza” se ejecutará de forma periódica y, como se puede ver, sólo envía al display LCD los caracteres que cambien. Esta forma de refresco del display LCD es más eficiente y nos permite tasas de muestreo mayores.

En el siguiente vídeo puede verse el circuito en acción:



El código en C++ puede descargarse de la sección soft.

[ añadir comentario ] ( 1830 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 14897 )
Control de display LCD con soporte para caracteres en español 
El control de displays LCD alfanuméricos desde microcontroladores es un tópico ampliamente abordado en muchas webs y tutoriales. El juego de caracteres utilizado por este tipo de displays es de tipo ASCII con algunos símbolos adicionales, sobre todo asiáticos, y se echan en falta varios de los símbolos propios del español (tildes, diéresis, etc.). A lo largo de este post se plantea un sencillo algoritmo de gestión del display LCD que permite la utilización de texto en español de forma transparente al usuario.

Características del display

Los displays LCD alfanuméricos estándar almacenan los mapas de bits (los dibujos) de su juego de caracteres en una ROM interna, con la excepción de los 8 primeros caracteres (del 0 al 7). Los mapas de bits de estos 8 primeros caracteres se almacenan en una parte de la RAM del LCD denominada CGRAM (Character Generator RAM). Es esta parte de la RAM la que hay que utilizar para representar los caracteres extendidos no ASCII del español en nuestro display.

El mapa de bits de cada carácter está formado por una matriz de 5x7 puntos que se almacena en la CGRAM como 8 bytes consecutivos: 1 byte por cada línea horizontal (de la cual sólo son significativos los 5 bits más bajos) y un 1 byte adicional para la línea de cursor que siempre se pone a 0 para displays de 5x7. Como son configurables sólo los 8 primeros caracteres del juego de caracteres, esto nos da 8 * 8 = 64 bytes para la CGRAM.
        . . . x .       0 0 0 1 0
. . x . . 0 0 1 0 0
. x x x . 0 1 1 1 0
á --> . . . . x --> 0 0 0 0 1
. x x x x 0 1 1 1 1
x . . . x 1 0 0 0 1
. x x x x 0 1 1 1 1
. . . . . 0 0 0 0 0

Gestión de los caracteres

En español tenemos 16 caracteres que se salen de la simbología ASCII:
á é í ó ú ü ñ Á É Í Ó Ú Ü Ñ ¿ ¡

Como no es posible cargar en la CGRAM los mapas de bits de estos 16 caracteres de forma simultánea, es necesario implementar algún tipo de gestión a nivel software que cargue en CGRAM sólo los caracteres que necesitamos en cada momento.

Para la gestión de los caracteres se han utilizado las siguientes estructuras de datos (en pseudocódigo):
EntradaTabla {
caracterLatin1
mapaDeBits
caracterLcd
caracterLcdDefecto
vecesUsado
marcaCarga
}

EntradaTabla tabla[16]
Cola caracteresLcdDisponibles

Cada entrada de la tabla de caracteres especiales incluye el carácter “ISO-8859-1” o “latin1” correspondiente y el mapa de bits que lo dibuja en el display. caracterLcd es el carácter del LCD (1 al 7, no vamos a usar la entrada 0 por si acaso) que está mapeando a este carácter especial. caracterLcdDefecto es el carácter de la ROM que mapeará a este carácter latin1 en caso de que no esté disponible ningún hueco en la CGRAM (por ejemplo ‘á’ tiene como carácter por defecto ‘a’).

vecesUsado indica cuántas veces está siendo usado esa entrada de la tabla de caracteres extendidos: cada vez que se utiliza un carácter especial, se incrementa este contador de la entrada correspondiente y cada vez que se deja de utilizar en alguna parte de la pantalla (se borra o se sustituye por otro), se decrementa este contador de la entrada correspondiente. Cuando un contador llega a 0, el hueco que ocupaba esa entrada en la CGRAM es marcado como vacío (metido en la cola de caracteres LCD disponibles.

marcaCarga indica cuando una entrada de la tabla debe ser cargada en la CGRAM. La carga de los bitmaps de las entradas marcadas se realiza a posteiori para no influir en la escritura de los caracteres. Primero se escribe en la DDRAM (la memoria de pantalla) y al final, si es necesario enviar bitmaps de caracteres no ASCII, se escribe en la CGRAM.
inicializar(EntradaTabla e)
e.caracterLcd = 0
e.vecesUsado = 0
e.marcaCarga = NO
fin

inicializarCola
meter(caracteresLcdDisponibles, 1)
meter(caracteresLcdDisponibles, 2)
meter(caracteresLcdDisponibles, 3)
meter(caracteresLcdDisponibles, 4)
meter(caracteresLcdDisponibles, 5)
meter(caracteresLcdDisponibles, 6)
meter(caracteresLcdDisponibles, 7)
fin

buscarEntrada(c) {
Devuelve la entrada en “tabla” que cumpla “caracterLatin1 == c”, o NULL en caso de no haber ninguna entrada.
fin

reemplazarCaracter(nuevo, viejo)
EntradaTabla e = buscarEntrada(viejo)
si (e != NULL) {
e.vecesUsado = e.vecesUsado - 1
si (e.vecesUsado == 0) {
meter(caracteresLcdDisponibles, e.caracterLcd)
e.caracterLcd = 0
fin si
fin si
e = buscarEntrada(nuevo);
si (e == NULL)
devolver nuevo
en otro caso
si (e.caracterLcd > 0)
e.vecesUsado = e.vecesUsado + 1
en otro caso
si (caracteresLcdDisponibles está vacía)
devolver e.caracterLcdDefecto
e.caracterLcd = sacar(caracteresLcdDisponibles)
e.vecesUsado = 1
e.marcaCarga = SI
fin si
devolver e.caracterLcd
fin si
fin

procesar
para todos los segmentos de texto que haya que cambiar en el display
para todos los caracteres del segmento de texto
nuevo = nuevo carácter
viejo = actual carácter
v = reemplazarCaracter(nuevo, viejo)
emitir(v)
fin para
fin para
para todas las entradas e de la tabla con marcaCarga = SI hacer
cargar e.mapaDeBits en la CGRAM correspondiente al carácter e.caracterLcd
e.marcaCarga = NO
fin para
fin

Ejemplo de traza

Imaginemos que tenemos la pantalla en blanco (recién inicializada): Todas las entradas de la tabla las tenemos inicializadas (caracterLcd=0, vecesUsado=0, marcaCarga=NO) y la cola “caracteresLcdDisponibles” inicializada con los 7 huecos.

Para escribir la palabra “Máquina”, el proceso irá llamando a “reemplazarCaracter” por cada nueva letra:
    reemplazarCaracter(‘M’, ‘’)
No hay entrada en la tabla para el carácter ‘’
No hay entrada en la tabla para el carácter ‘M’
devuelve ‘M’
reemplazarCaracter(‘á’, ‘’)
No hay entrada en la tabla para el carácter ‘’
Hay una entrada e para el carácter ‘á’
como e.caracterLcd = 0, entonces
la cola de caracteres disponibles no está vacía
e.caracterLcd = 1
e.vecesUsado = 1
e.marcarCarga = SI
devuelve e.caracterLcd (1)
reemplazarCaracter(‘q’, ‘’)
No hay entrada en la tabla para el carácter ‘’
No hay entrada en la tabla para el carácter ‘q’
devuelve ‘q’
reemplazarCaracter(‘u’, ‘’)
No hay entrada en la tabla para el carácter ‘’
No hay entrada en la tabla para el carácter ‘u’
devuelve ‘u’
...

Al final del proceso de escritura de la cadena ‘Máquina’ tenemos que la cola de caracteres disponibles está así: [2, 3, 4, 5, 6, 7] y que la entrada de la tabla de caracteres correspondiente a la letra ‘á’ tiene caracterLcd=1, vecesUsado=1, el resto de entradas siguen como al principio.



De esta forma se va alojando espacio en la CGRAM del display LCD en función de los caracteres especiales que necesitamos en todo momento. Nótese que, en caso de que ya no nos queden huecos libres (la cola de huecos esté vacía), devolvemos el carácter por defecto.

Políticas alternativas de sustitución de caracteres

Una política alternativa podría ser establecer una prioridad por cada carácter de la tabla: en caso de vaciado de la cola de huecos, se sacrifica el carácter con menor prioridad de los que estén siendo usados en ese momento. Un criterio de prioridad podría ser en función del valor de “vecesUsado”. De esta forma se sacrificarían los caracteres menos usados.

Nótese que esta política de “sacrificado” de caracteres menos prioritarios obligaría a refrescar los caracteres a sacrificar y cambiarlos por los caracteres por defecto correspondientes. En todas las posiciones donde se encuentren.

En esta implementación no se lleva a cabo ninguna política de sacrificado de caracteres. Cuando la cola de caracteres disponibles se acaba, se imprime el carácter por defecto.

Implementación

Este algoritmo de gestión de caracteres extendidos para displays LCD se ha implementado sobre un Arduino Leonardo en C++.



Se ha optado por una anchura de bus de 4 bits para minimizar el número de cables. El código fuente puede descargarse de la sección soft.

[ añadir comentario ] ( 1873 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1915 )
Tres en raya con el Arduino utilizando el algoritmo de decisión minimax 
Ampliando un post anterior en el que desarrollé un juego de tres en raya con el Arduino, he desarrollado una implementación “inteligente” del mismo. En la anterior versión, si bien el jugador jugaba contra la máquina, las posiciones que jugaba la máquina eran totalmente aleatorias y no seguían ningún criterio. En este caso la máquina utiliza un algoritmo de decisión (el minimax) para calcular el siguiente movimiento y así intentar ganar a su oponente humano.

Antecedentes

En la implementación anterior del tres en raya teníamos una clase abstracta denominada TTTGamePlayer de la cual heredaban las clases TTTHumanGamePlayer y TTTRandomGamePlayer. TTTHumanGamePlayer además de heredar de TTTGamePlayer, también heredaba de KeyMatrixListener, de esta forma los eventos de los pulsadores se transforman en movimientos del jugador humano.

La clase TTTRandomGamePlayer era una clase que inicializaba una semilla aleatoria. Dentro de esta clase el método getSpot(), que es el método virtual puro de TTTGamePlayer que debe devolver el movimiento que desea realizar el jugador, devolvía en este caso una posición aleatoria de entre todas las posiciones libres que quedaban en el tablero.

Algoritmo Minimax

El algoritmo minimax es un algoritmo de decisión muy simple desarrollado a partir del Teorema Minimax de John von Neumann en 1926 (para juegos de suma cero e información perfecta). El algoritmo es muy sencillo:

- Cuando nos toque mover a nosotros, calculamos el árbol de decisión del juego.

- Los nodos hoja del árbol representarán todas las posibles formas de terminar el juego. Asignamos a cada uno de los nodos hoja del árbol un valor numérico directamente proporcional a nuestro beneficio (o inversamente proporcional al beneficio de nuestro oponente).

- Vamos ascendiendo en el árbol escogiendo, alternativamente, el mínimo o el máximo de los hijos (minimax) en función de si cada decisión debe ser tomada por mí o por mi oponente (yo busco maximizar mi beneficio y mi oponente busca minimizarlo o, lo que es lo mismo, maximizar el suyo propio), así hasta la raíz. De esta forma podemos elegir el movimiento que minimize nuestra pérdida esperada.

Con un ejemplo del 3 en raya se ve mejor. Imaginemos que somos X y que nos toca mover a nosotros:
o x x
x . . sig. mov. = x, beneficio = max(-1, -1, 0) = 0
o o .

o x x
x x . sig. mov. = o, beneficio = min(0, -1) = -1
o o .

o x x
x x o sig. mov. = x, beneficio = max(0) = 0
o o .

o x x
x x o tablas, beneficio = 0
o o x

o x x
x x . gana o, beneficio = -1
o o o

o x x
x . x sig. mov. = o, beneficio = min(1, -1) = -1
o o .

o x x
x o x sig. mov. = x, beneficio = max(1) = 1
o o .

o x x
x o x gana x, beneficio = 1
o o x

o x x
x . x gana o, beneficio = -1
o o o

o x x
x . . sig. mov = o, beneficio = min(1, 0) = 0
o o x

o x x
x o . sig. mov. = x, beneficio = max(1) = 1
o o x

o x x
x o x gana x, beneficio = 1
o o x

o x x
x . o sig. mov. = x, beneficio = max(0) = 0
o o x

o x x
x x o tablas, beneficio = 0
o o x

A partir del nodo raíz, el beneficio máximo se corresponde con el movimiento:
o x x        o x x
x . . ---> x . .
o o . o o x

En este caso “beneficio” se refiere a “beneficio para X”. Como se puede ver, partiendo del estado del tablero indicado por la raíz del árbol, el mejor movimiento será el de marcar la fila 3 y la columna 3.

Hardware

El montaje lo he realizado utilizando un Arduino Leonardo, conectando la matriz de pulsadores a los puertos A0, A1, A2, D11, D12 y D13

Y la matriz de leds a los puertos D0, D1, D2, D3, D4 y D5

Si vamos a realizar el montaje para otro modelo de Arduino hay que comprobar el mapeo de los puertos en el microcontrolador y modificar la implementación de las clases MyLedMatrixManager y MyKeyMatrixManager (como se trata de una implementación en C++, no tenemos a nuestra disposición la abstracción de puertos que nos proporciona el lenguaje Arduino).

Implementación

La clase TTTMinimaxGamePlayer es la que implementa el algoritmo minimax: Al igual que las clases TTTRandomGamePlayer y TTTHumanGamePlayer, hereda de TTTGamePlayer con la diferencia de que en su método getSpot() calcula el siguiente movimiento a realizar utilizando el algoritmo de decisión minimax.

uint8_t TTTMinimaxGamePlayer::getSpot() {
    uint8_t ret = 0;
    this->minimax(*this->board, true, ret);
    return ret;
}

int8_t TTTMinimaxGamePlayer::minimax(TTTGameBoard &board, bool me, uint8_t &bestMovement) {
    uint8_t winner = board.getWinner();
    if (board.isFinished() || (winner != 0)) {
        int8_t ret = 0;
        if (winner == this->number)
            ret = 1;
        else if (winner == this->opponentNumber)
            ret = -1;
        return ret;
    }
    else {
        int8_t limit = 0;
        if (me)
            limit = -10;
        else
            limit = 10;
        TTTGameBoard auxBoard;
        uint8_t numMovements = 0;
        for (uint8_t m = board.getFirstAvailableMovement(); m != 0; m = board.getNextAvailableMovement(), numMovements++) {
            auxBoard.copyFrom(board);
            if (me)
                auxBoard.set(m, this->number);
            else
                auxBoard.set(m, this->opponentNumber);
            uint8_t childBestMovement;
            int8_t v = minimax(auxBoard, !me, childBestMovement);
            if (me) {
                if (v > limit) {
                    limit = v;
                    bestMovement = m;
                }
            }
            else {
                if (v < limit) {
                    limit = v;
                    bestMovement = m;
                }
            }
        }
        return limit;
    }
}

Se trata de una implementación recursiva y el parametro “me” indica cuando se está simulando un movimiento propio (true) o un movimiento del oponente (false). A pesar de usarse una implementación recursiva, nótese que no se realizarán más de 8 llamadas recursivas: Cada nivel es un movimiento adicional y un tablero de 3 en raya sólo tiene 9 posiciones.

A modo de visión global, el diagrama de clases queda ahora así:


En la sección soft puede descargarse el código fuente del proyecto en C++. Aquí una pequeña guía sobre cómo desarrollar en C++ para el Arduino.



[ añadir comentario ] ( 2591 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1868 )
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 ] ( 1530 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1664 )
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 ] ( 1328 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1665 )

<< <Anterior | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Siguiente> >>