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.
No hay comentarios:
Publicar un comentario