DTMF es la sigla que identifica a Dual-Tone-Multi-Frecuency. En esta oportunidad vamos a realizar un proyecto de control a distancia utilizando el teléfono Celular. Para tal objetivo, nos basaremos en un control por tonos duales, o DTMF.
El tema de decodificación DTMF, es conocido, lo que resulta interesante es la manera de agregar ciertas características profesionales como contraseña de entrada y claves para activar y desactivar, así también como un dispositivo audible desde el mismo celular con que se opera, para la confirmación de las acciones realizadas. La idea también es aprovechar para aprender algo sobre la implementación con Microcontroladores PIC.
El sistema conceptual puede dividirse en 3 módulos como se ve en la figura
Describiremos en profundidad cada bloque con el Hardware y el Software asociado.
DETECTOR DTMF
Los tonos de audio que se generan en un teclado convencional de telefonía están normalizados. En realidad no es un tono simple sino una combinación de tonos formado por dos grupos de tonos, el grupo bajo y el grupo bajo. Cada dígito o carácter del teclado posee una combinación de grupos única e irrepetible que permite determinar el dígito que ha sido pulsado. En este módulo utilizaremos el popular HT9170 que es relativamente simple de utilizar. Este chip puede configurarse de 2 maneras diferentes, en modo diferencial y en modo común. Se utilizó el modo común.
La función de este chip es detectar los grupos validos de un digito y ofrecer un código binario a la salida para que se pueda leer desde un Controlador, o algún otro circuito decodificador.
Les presento el diagrama en bloques:
Si bien el Datasheet está disponible en internet y lo pueden bajar, voy a considerar alguna cuestiones de diseño:
Como se observa, a la entrada se encuentra un amplificador operacional para ajuste de ganancia y filtrado. Para operación en modo común el esquema es el siguiente:
En esta configuración, se ingresa por la pata negativa del operacional y se observan las ecuaciones de ganancia, impedancia y frecuencia de corte, S=JW y es el operador Laplaciano que se usa para modelar las respuestas de frecuencia y trazar diagramas de Bode. Sin entrar en demasiados detalles, se aprecia que la ganancia posee una raíz en el origen S y un polo (lugar donde la función se hace infinita) en S=1/RC. Para analizar esto matemáticamente es necesario trabajar con el módulo por un lado y con la fase por el otro. Para evitar esto se puede trazar el diagrama de Bode que opera en escalas logarítmicas. Las frecuencias se expresan en dedadas y las amplitudes en decibeles. Así por ejemplo vemos que 1 Hz la amplitud es de -100DB, es decir casi cero. El cero no lo representamos porque el Log base 10 de 0 no existe, en el límite de frecuencia tendiente a cero, la ganancia es prácticamente cero, es lógico dado el capacitor C que filtra la continua. Los ceros en Bode se representan con una pendiente de +20Db/década y los polos a -20 Dpb por década. Cuando se llega a la frecuencia del Polo S=1/RC los +20, se cancelan con -20 y queda una constante que es nuestra ganancia Rf/R. La otra frecuencia de corte queda determinada por el operacional.
Como se ve queda una banda de paso. Dentro de esta banda deben ingresar todos los tonos a ser detectados, cuya tabla es la siguiente:
El grupo de frecuencia más pequeño corresponde a 697 Hz, por lo tanto S=1/RC < 697 Hz. Bode también permite saber que justo en la frecuencia de corte la caída o perdida de ganancia es de -3db, es decir 0.707 por la ganancia que tenía hasta ese momento, es decir Rf/R. Todo esto es verificable matemáticamente. El manual recomienda lo siguiente:
Como vemos RF=R es decir la ganancia en la banda de paso es 1, la frecuencia del polo es W=1/RC, sabiendo que W = 2*PI*f, se tiene que fc= 16 Hz, es decir que va a atenuar los tonos por debajo de los 16 Hz y entre 16 y el corte del operacional va a amplificar en 1 los tonos que vengan. Por mi parte lo que hice fue darle una ganancia ajustable que no modifica la respuesta pero si la impedancia de entrada, es una relación de compromiso. RF la formé con una resistencia de 12 K en serie con un Preset Multivueltas de 50 K y R=12 K. Luego la ganancia máxima será de 12+50/12 = 6 veces y el corte me quedó en fc=1/12K*0.1Uf = 132 Hz lo cual limita más la banda de paso y lo hace más inmune al ruido. Todo esto a expensas de bajar la impedancia de entrada lo cual por un lado favorece cuestiones de ruido, pero por el otro carga a la salida de audio del celular y va a bajar un poco el nivel, pero funciona muy bien. Ustedes pueden experimentar con sus propios valores.
El resto, se configura similar al Datasheet, con cristal 3.58Mhz, es importante elegir un cristal adecuado, de bajo corrimiento. Yo use uno de 3.575611 MHz y capacitores de 18pf.
En cuanto al resto que se observa RG/CT y EST configuran las constantes internas de detección de un tono válido y se pueden estudiar en el Datasheet y su diagrama de tiempos, pero respetando estos valores funciona correctamente.
Lo importante es la señal DV que emitirá un pulso de 5v indicando que se ha Latcheado o memorizado en el registro de salida D3 a D0 el código binario de un tono dual válido, ver la tabla de tonos anterior de códigos binarios de decodificación.
El resto de los pines del HT9170
OE= 5V fijos
INH y PWDN = 0v
En la tabla de tonos, se ve que del 1 al 9 sigue la conversión convencional de decimal a binario, el 0 sería el 10 en decimal, el “ * “el 11 y el “ # “ el 12. Las letras no las usamos.
MICROCONTROLADOR
Este bloque tiene el objetivo de procesar las funciones del equipo, Las señales D0 a D3 del DTMF van a conectarse a un Puerto del microcontrolador, La señal DV a otro pin del mismo con ciertas características. El puerto de salida del microcontrolador actuará sobre el Driver del sistema de actuación.
Se utilizó un PIC 16F873 con el entorno de programación MPLABX de microchip y compilador XC8. El micro operará a 4 MHz de frecuencia.
Les dejo los siguientes enlaces:
- Micro: Micro Pics enlace
- Compilador XC8 y MPLAB X: Enlace Herramientas
Esta es la parte más compleja, en el sentido que involucra no solo conocimiento de Hard del micro sino también como herramientas de programación, es decir Soft, y conocimiento del lenguaje C como base que usan los compiladores. A diferencia de ARDUINO, los sketches son más simples, es decir, los sketches ya son un C precompilado lo cual lo hace más simple. La idea es tratar de explicarles lo más simple posible este proyecto DTMF ya que hay lectores que no tienen experiencia y por otro lado los que ya conocen del tema puedan usarlo como referencia. La idea no es un Post sobre PIC’s , sino una implementación usando PIC’s, por lo tanto puede haber lectores que no comprendan algunas cuestiones si no han trabajado antes con ellos.
Este POST, como dije, no trata las características de un PIC sino la implementación del proyecto DTMF, no obstante se tratará de ser lo más didáctico posible.
En principio, vemos que cada pin tiene varias identificaciones esto es debido a que los PIC multiplexan
diferentes funciones en cada pin, como Digitales, Analógicas, de eventos externos, fuentes de reloj diversas, protocolos asincrónicos como USART , y sincrónicos como I2C , MSSI para comunicarse con sensores, memorias, etc.
Para que el micro tenga determinada funcionalidad hay que configurar los Registros internos de acuerdo a lo deseado. Esta es una gran diferencia con ARDUINO, que no es necesario conocer el Hard interno.
El esquema de conexiones para el Microcontrolador como parte del DTMF será el presentado a continuación. He utilizado el Eagle 6.1.0, para realizar el esquemático y el diseño final de la placa .
Concentrándonos en este bloque del Micro, observamos lo necesario:
- Un pulsador de reset general, que es útil para las pruebas.
- El oscilador externo de 4mhz con los capacitores de 18 pf
- RA0 a RA3 serán entradas digitales conectadas a D3-D0 del decodificador DTMF, es decir RA0-D3, RA1-D2, RA2-D1 y RA3-D0.
- El pulso DV de salida de tono válido del DTMF HT9170 se conecta a RB0/INT.
- Las RB7 a RB4 serán salidas digitales que activarán el driver, módulo que sigue. Cada una maneja la activación / desactivación de una carga de 220 VCA, es decir, este equipo tiene la capacidad de manejar 4 cargas.
- RB1 será la salida de activación de un Buzer sonoro para dialogar vía celular.
- RB0/INT será entrada digital del pulso DV del DTMF.
RB0/INT indica que además de ser un pin de entrada/salida digital, puede ser fuente de Interrupciones de eventos externos (INT). De hecho se utilizará de esa manera, es decir cuando se genere un tono válido el pulso de DV disparará una interrupción de software interna en el PIC e irá a atender un bloque de programa especial que atiende dicha interrupción, más adelante se explicará cómo se implementa esto.
Características de funcionamiento
El equipo de control DTMF, funcionará de la siguiente manera:
- Contraseña de 3 dígitos , por ejemplo “7#*” o la que se prefiera
- Clave de activación / desactivación de salidas, ejemplo 11* activa salida 1 y 10* la desactiva. Lo mismo para la 2, 3 y 4. <Salida><1-on, 0-off><*>.
- El sistema opera en 2 estados, estado 0 y estado 1. El estado 0 es cuando se ha conectado y se espera validar la contraseña, sin ella no se podrán operar las salidas. El estado 1 cuando se ha validado la contraseña.
- En estado 0, el equipo valida 5 segundos de espera entre digito y digito, si expira este tiempo habrá que comenzar de nuevo.
- En el estado 1, las salidas se pueden operar y permite un timeOut de 5 minutos desde el último pulsado. Aun cuando se corte la comunicación, se podrá llamar nuevamente y operar las salidas sin necesidad de la clave. Pasado ese tiempo volverá al estado 0.
- Un buzzer sonoro indicará determinadas acciones. Contraseña invalida o clave mal ingresada, serán 10 beeps seguidos. Contraseña válida, un beep largo. Activación / desactivación válida 3 beeps. Estos beeps serán escuchados desde el celular de origen de llamada.
Necesidades de configuración
Pin RBO/INT como entrada y fuente de interrupciones
Timer que cuente 5 segundos y 5 minutos
Funciones de Beep sonoros
Cambios de estado 0 y 1
Contraseñas y claves
Software
Vamos a ir explicando las secciones de código y explicando lo que hacen y su interacción con el Hardware del micro. Para esto es necesario utilizar el MPLABX que es el entorno de desarrollo donde programamos, compilamos, simulamos y guardamos el ejecutable en el propio PIC. Nuevamente, no voy a detenerme en el MPLABX ya que no es el interés de este post. El MPLABX tiene la plataforma del NetBeans de Java.
El proyecto en el MPLABX se verá de esta manera:
Librerías: Se deben incluir en el proyecto y vienen con el compilador XC8
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include<string.h> // Necesaria para trabajar con cadenas #include <stdio.h> // Ansi C de entrada salida #include <stdlib.h> // Ansi C estandard #include "delays.h" // header externo incluido, cuando se importa una librería copiada pero no // //disponible para PIC16, para generar retardos // hay que implementar las funciones en el código de dicha librería, yo agregue las funciones en los archivos fuentes.c #include "pic16f873.h" // librería de definiciones del PIC #include <xc.h> // compilador |
Preprocesadores: todo micro posee registros especiales que preconfiguran globalmente al PIC por ejemplo le decimos que usamos un cristal tipo XT, el WatchDog timer en Off, El Power On Reset que indica que el micro arranca cuando la fuente de alimentación es estable, Programación de bajo voltaje Off, etc. Para más detalle hay que consultar el DataSheet.
1 2 3 4 5 6 7 8 9 |
// CONFIG #pragma config FOSC = XT // Oscillator Selection bits (XT oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config CP = OFF // FLASH Program Memory Code Protection bits (Code protection off) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low Voltage In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EE Memory Code Protection (Code Protection off) #pragma config WRT = ON // FLASH Program Memory Write Enable (Unprotected program memory may be written to by EECON control) |
Declaraciones: Crear nombres de referencia para mapear bits
1 2 3 4 5 6 7 8 9 |
Declaraciones: Crear nombres de referencia para mapear bits #define Sal_1 PORTBbits.RB7 // Salida de control 1 con reles #define Sal_2 PORTBbits.RB6 // Salida de control 2 #define Sal_3 PORTBbits.RB5 // Salida de control 3 #define Sal_4 PORTBbits.RB4 // Salida de control 4 #define Sal_5 PORTBbits.RB3 // Salida de control 5 NO SE PUEDE DIRECTO RA4 ES OPEN DRAIN #define Sal_6 PORTBbits.RB2 // Salida de control 6 #define Beep PORTBbits.RB1 // Buzer de indicacion sonora #define FlagPulsoDTMF INTCONbits.INTF // Flag de evento RBO/INT salisa DV del DTMF |
Vemos que por ejemplo el pin RB7 del PortB lo llamamos SAL_1. Es decir creamos nuestras propias definiciones de nombres más manejables en el programa. Se ve que el programa posee hasta 6 salidas pero yo solo he implementado 4 por tema de espacio en la plaqueta y dimensiones del gabinete final.
Observamos también que la definición FlagPulsoDTMF que se mapeará con el registro INTCON, bit INTF. Para saber que es esto vamos al datasheet:
Este registro controla muchas cosas, lo que nos interesa es que el bit1, INTF es un bit o flag que se enciende cuando ha ocurrido una solicitud de interrupción RB0/INT, para nuestro caso el pulso del DTMF, DV.
Variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// VARIABLES char Digito[14]={'0','1','2','3','4','5','6','7','8','9','0','*','#',''}; // Digito[0], no se usa ya que el Digito[10]='0' caracter , '' fin de cadena String char codigo[4]={'','','',''}; // guarda en formato cadena 3 caracteres de entrada + el null de fin '' const char contrasena [4]="#9*"; // Contraseña para operacion del DTMF unsigned char estado=0; // Variable que indica el estado del controlador DTMF // 0 contraseña invalida o no recibida // 1 contraseña valida y se pueden operar las salidas // DETECCION DE CODIGO DE DIGITO CONTRA DTMF // PUERTO B entradas RA0=D3, RA1=D2, RA2=D1, RA3=D0 DEL DTMF unsigned int contador=0; // Contador de degitos que van ingresando unsigned int ciclosT1=0; // ciclos de cuentas Timer1 unsigned char flagEntrada=0; // Indica que vino un pulso del DTMF 0/1 // ciclosT1= 10 SERAN 5 SEG, 10 desbordes de T1 a 65536 * 0.1us * 8 // ciclosT1= 20 SERAN 10 SEG, 20 desbordes de T1 a 65536 * 0.1us * 8 char S1ON [4]="11*"; // Codigos de activacion y desactivacion salidas char S1OFF [4]="10*"; // agrega '' al final de cada cadena char S2ON [4]="21*"; char S2OFF [4]="20*"; char S3ON [4]="31*"; char S3OFF [4]="30*"; char S4ON [4]="41*"; char S4OFF [4]="40*"; char S5ON [4]="51*"; char S5OFF [4]="50*"; char S6ON [4]="61*"; char S6OFF [4]="60*"; char SALLON[4]="666" ;// ENCIENDE TODAS char SALLOFF[4]="000";// APAGA TODAS |
Aquí hay varias cosas, el arreglo Digito[14], es un vector timo string o cadena que relaciona el índice con el carácter, de manera que Digito[código D3-D0] =Carácter buscado, es decir es como un puntero. Por ejemplo si mi código D3-D0 que es un nible de 4 bits es el 00001010 (solo válido lo últimos 4 bits), entonces Digito [10] = ‘0’ carácter 0. Las variables tipo Unsigned Char, son de 8 bits, es decir representan el código Ascii entre 0 y 255 del carácter guardado.
La variable Char código [4] también es un arreglo de caracteres. El Ansi C, al crear una variable cadena o arreglo siempre hay que finalizarla con el carácter NULL de fin de cadena o ‘’. En esta variable vamos a ir concatenando los dígitos de entrada, la cadena se arma cada 3 dígitos más el NULL, por eso su dimensión es 4.
La variable Const Char Contraseña[], similar, aquí definimos la cadena de contraseña.
Unsigned Char estado =0; es el estado del equipo , 0 o 1, ya explicado.
unsigned int contador, lleva la cuenta de los dígitos o caracteres ingresados en la cadena código[].
unsigned int ciclosT1=0, es una variable entera entre 0 y 65535. Para explicar esto debemos saber como es la temporización en los micros.
El cristal es de 4 MHz, es decir que cada pulso de reloj es de 1/4Mhz= 0.25 us. El TCY o ciclo de instrucción es el tiempo que tarda en ejecutarse una instrucción dentro del micro y es una cantidad conocida. La ejecución se lleva a cabo en 4 etapas dentro de la PIPELINE que posee las etapas de Fetch (búsqueda de instrucción) – Decoding (decodifica la instrucción)- OP (operandos) – Ex (ejecución y almacenamiento), es decir que cada TCY = 4 pulsos de reloj. Hay instrucciones que como las de salto que consumen más de 4 ciclos de reloj. Entonces 1TCY= 4×0.25us = 1us. Los TCY son las fuentes de conteo de los timers internos del PIC, como TMR0 ( timer 0), TMR1 ( timer 1), etc.
El Timer 1 es de 16 bits es decir que puede contar entre 0 y 65535 y luego pasa a cero nuevamente, es decir que cada vuelco del Timer1 o ciclo completo puede generar 65536*1us= 65.536 ms. Es 65536 ya que se considera un pulso más desde 65535 a cero cuando desborda y vuelca, lo cual es indicado por el bit flag TMR1IF. Al Timer 1 se le puede asignar un Prescaller, esto es que el pulso que cuente sea el TCY pero dividido previamente:
Los bits del registro T1CON permiten asignar el prescaller al Timer1. Entonces si lo afectamos por un prescaller de 1:8:
ciclosT1= 10 SERAN 5 SEG, 10 desbordes de T1 a 65536 * 0.1us * 8
Prototipo de Funciones
Se definen funciones o métodos para estructurar mucho más todo el software, es decir dividir el problema en sus problemas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void configIO(void); // Configuracion de perifericos void configIRQ(void); // Configuracion para la interrucion por Flanco ACS RBO/INT void Beeper(int cant, int tpon, int tpof); // Funcion de indicacion sonora // cant=cantidad de beeps, tpon=HIGH de cada beep , tpof= LOW unsigned char LeerPuerto(void); // Lee el puerto del DTMF void limpiarTodo(void); // Inicializa todo nuevamente void configT1(void); // Configuración del Timer 1 para tiempos entre teclas 5 seg y 5 minutos void on_T1(void); // Enciende Timer 1 void chequeaTiempo(void);// Chequea si se ha superado el tiempo entre digito y digito bit chequeaEstadoT1(void); // consulta si el timer esta encendido o void apagaT1(void); // apaga el timer1 void limpiaT1(void); // limpia el Timer1, lo reseta junto con sus ciclos void controlSalidas(void); // controla las salidas void limpiarCodigo(void); // limpia variable de cadena de código |
Configuración I/0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void configIO(void){ ADCON1=0x06; // A/D desactivados Y PORT A RA0-RA5 como Digital pag 114 datashhet ADCON0bits.ADON=0; //Modulo interno de conversion apagado TRISA =0xFF; // PUERTO A todo entrada para señales DTMF //TRISC=0x00; // PUERTO C salida TRISB=0b00000001; // PUERTO B todo salida a excepcion de RB0/INT PORTB=0x00; Sal_1=0; NOP(); Sal_2=0; NOP(); Sal_3=0; NOP(); Sal_4=0; NOP(); Sal_5=0; NOP(); Sal_6=0; NOP(); Beep=0; } |
Los ADCON1 y 0 regulan la operación analógica digital. Cuando arranca el micro siempre están activados impidiendo el uso de pines I/O digital. Por eso se desactivan. El ADCON1 escribirndo un 0x06 en hexadecimal. Fila 7 de la tabla.
Los TRISA, B, C son los registros de dirección del PUERTO A, B, C, etc. El “1” significa entrada y el “0” salida, inversamente a ARDUINO. Son registros de 8 bits, TRISB0 = definirá RBO, TRISB1, RBI y así siguiendo.
Cuando hacemos uso de PORTB, A, C, etc. estamos leyendo el puerto o escribiendo.
PORTB=0, escribirá 0x00 (00000000) todos 0 a la salida de cada RB7 al RB0. RBO es una entrada por lo cual no tendrá efecto la escritura de RB0.
Configuración Interrupción
1 2 3 4 5 6 7 8 9 10 |
void configIRQ(void){ OPTION_REGbits.INTEDG=1; // configura RB0(PulsoDigito) para IRQ Flanco Ascendente INTCONbits.PEIE=0; // interrupción periféricos desactivada RBO/INT no necesita permiso periféricos // Me aseguro que solo RBO/INT sea la fuente de las interrupciones INTCONbits.RBIE=0; // interrupción por RB0 desactivada para cualquier cambio puerto B INTCONbits.INTE=1; // Habilita IRQ RBO/INT INTCONbits.INTF=0; // Limpia Flag de IRQ es lo mismo que FlagPulsoDTMF=0; INTCONbits.GIE=1; // Habilita interrupciones globales } |
Si volvemos a la tabla del INTCON veremos los significados de los diferentes bits. El bit INTEDG del registro OPTION_REG define el flanco de disparo de la interrupción IRQ.
Normalmente las interrupciones se habilitan con permisos de periféricos , con bits especiales y el permiso global de interrupciones bit GIE. Cada periférico sea un conversor AD, PWM, TIMERS, ETC poseen Flags de disparo de eventos, al igual que como hemos explicado el RB0/INT. Veamos el siguiente esquema lógico de permisos:
Los bits IE son los permisos y los IF los flags de eventos. Vemos que GIE=1, habilita globalmente ( compuerta lógica AND). El INTE es el bit que habilita el permiso de Interrupción de RB0/INT via su flag RBIF que ya lo hemos visto. El bit PEIE=0, no permite otra fuente de interrupción de otros periféricos, es un 0 en una AND.
Indicación Sonora
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void Beeper(int cant, int tpon, int tpof){ // TPO MINIMO BASE =0.1 SEG /* 10000* 10 * tpon o tpof HIGH= 0.1* tpon LOW= 0.1 * tpof Contraseña invalida: 10 beeps de 0.1 seg Beeper(10,1,1); Digito valido: 1 beep de 0.5 seg no lo usamos contraseña valida : 1 beep de 2 segundos Beeper(1,20,1); salida activada/desactivada: 3 beep de 0.1 seg Beeper(3,1,1); */ int i; int j; for(i=1;i<=cant;i++){ // la variable unit compilada en la libreria es el // argumento de 10KTCYx y va de 0 a 255 es unsigned char Beep=1; for (j=1;j<=tpon;j++){ Delay10KTCYx(10); } for(j=1;j<=tpof;j++){ Beep=0; Delay10KTCYx(10);// Toff es siempre el mismo } } } |
Esta función se encarga de generar una onda cuadrada , el tiempo en ON, y en OFF asi también como la cantidad de ciclos a generar. La función Delay10Ktcy(A), genera 10*1000*1TCY*A demora de tiempo. Esta función debe ser incluida como archivos fuente y está dentro del compilador XC8.
10KTCY = 10*1K*1TCY. El argumento cant, pasado determina la cantidad de ciclos de beep.
Lectura del Puerto del DTMF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
unsigned char LeerPuerto(void){ // Lee el puerto y devuelve un decimal del digito DTMF unsigned char puerto; // almacena los bits leidos del DTMF unsigned char puertoFinal=0x00; // arma el byte correcto puerto=PORTA; // SOLO INTERESAN RA0-RA3 Lee el puerto puerto&=0x01; // lee D3 MSB puerto<<=3; // QUEDA 0000 D3 000 puertoFinal|=puerto; //0000 D3 000 puerto=PORTA; puerto&=0x02; // Lee D2 puerto<<=1; // queda 000000 D2 00 puertoFinal|=puerto; // QUEDA 0000 D3 D2 0 0 puerto=PORTA; puerto&=0x04; // Lee D1 puerto>>=1; // queda 000000 D1 0 puertoFinal|=puerto; // QUEDA 0000 D3 D2 D1 0 puerto=PORTA; puerto&=0x08; // Lee D0 puerto>>=3; // queda 0000000 D0 puertoFinal|=puerto; // QUEDA 0000 D3 D2 D1 D0 puertoFinal&=0x0f; //puerto>>=4; // desplaza 4 lugares a la derecha asi se puede usar como indice return puertoFinal; } |
Esta función es compleja debido a que en la construcción , para que el HT9170 se conecte al PIC línea a línea y sin cruces de pistas, el PORTA leído y llevado a una variable puerto de 8 bits queda de la siguiente manera:
Variable puerto:
x | x | x | x | D0 | D1 | D2 | D3 |
Como vemos está invertido en los pesos binarios , y hay que llevarlo a que quede de esta manera:
0 | 0 | 0 | 0 | D3 | D2 | D1 | D0 |
Esto va a permitir leer la variable puerto directamente el valor decimal del código.
Esto se logra leyendo de a cada bit individual e ir desplazándolo en la variable final juntamente con operaciones OR. Lo dejo para ustedes para analizar.
Ahora el byte retornado será usado de índice para el arreglo Digito[].
Limpiar Todo
1 2 3 4 5 6 7 8 9 |
void limpiarTodo(void){ estado=0; contador=0; limpiarCodigo(); flagEntrada=0; ciclosT1=0; apagaT1(); } |
Esta función, básicamente vuelve al estado del micro a estado=0, reinicia las variables como si arrancara nuevamente.
El Timer 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void configT1(void){ // Configura Timer 1 para que cuente 5 seg y 10 seg // Configurarlo con prescaller 1:8 T1CKPS1=T1CKPS0=1 // Oscilador timer1 externo desactivado T1OSCEN=0 // EN FORMA SINDRONICA T1SYNC#=0 // Fuente de cuentas interno xtal/4 TMR1CS=0 // TMR1ON =1/0, enciende y apaga T1CON=0x30; //T1CONbits.TMR1ON=0; // apagado apagaT1(); TMR1H=0x00; // Puesta a cero TMR1L=0x00; // ciclosT1= 10 SERAN 5 SEG, 10 desbordes de T1 a 65536 * 0.1us * 8 // tiempo de validacion entre tecla y tecla para la clave Estado=1 // ciclosT1= 600 SERAN 300 SEG=5 minutos, 600 desbordes de T1 a 65536 * 0.1us * 8 // Una vez entrada la clave permite 5 minutos a partir del último ingreso de tecla // pasados los 5 min vuelve al Estado= 0 } |
Básicamente se le asigna la fuente de conteo al Timer1, que sea el reloj o Xtal= 4mhz, la fuente de reloj secundaria y externa apagada, el prescaller en 8, su cuenta en cero, y se lo deja apagado. Todo esto se logra con T1CON=0x30;
Encender Timer1
1 2 3 4 5 |
void on_T1(void){ T1CONbits.TMR1ON=1; // enciendo Timer limpiaT1(); } |
Enciende el Timer, ver tabla del registro T1CON, el bit TMRION
Chequear Tiempo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void chequeaTiempo(void){ if(PIR1bits.TMR1IF==1){ // DESBORDO?? ciclosT1++; PIR1bits.TMR1IF=0; // limpia desborde if (estado==0){ // validar clave if(ciclosT1==10){ // pasaron los 5 segundos limpiarTodo(); } } if(estado==1){ // ya esta validada la clave if(ciclosT1==600){ // 5 MINUTOS limpiarTodo(); } } } } |
Si el estado=0, no valido contraseña, el Timer1 se cuentan 10 desbordes del Timer , 5 seg. Si el estado es 1, se cuentan 600 desbordes del Timer. Cada desborde lo acusa el flag TMR1IF , el cual es borrado manualmente ya que no se borra solo después de seteado.
Chequear el estado del Timer1
1 2 3 4 5 6 |
bit chequeaEstadoT1(void){ if(T1CONbits.TMR1ON==1){ return 1; } return 0; } |
Simplemente chequea si el Timer esta encendido o apagado..
Apaga el Timer1
1 2 3 |
void apagaT1(void){ T1CONbits.TMR1ON=0; // enciendo Timer } |
Limpiar el Timer1
1 2 3 4 5 6 |
void limpiaT1 (void){ TMR1H=0x00; TMR1L=0x00; PIR1bits.TMR1IF=0; // Limpia desborde ciclosT1=0; } |
Lo pone a cero, su cuenta interna actual, pone a cero la bandera de desborde y el contador de ciclos de espera en 0. Este proceso se realiza al validar un digito presionado.
Control de Salidas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
void controlSalidas(void){ unsigned char resultado=0; if(strcmp(codigo,S1ON)==0){ Sal_1=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S1OFF)==0){ Sal_1=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S2ON)==0){ Sal_2=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S2OFF)==0){ Sal_2=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S3ON)==0){ Sal_3=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S3OFF)==0){ Sal_3=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S4ON)==0){ Sal_4=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S4OFF)==0){ Sal_4=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S5ON)==0){ Sal_5=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S5OFF)==0){ Sal_5=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S6ON)==0){ Sal_6=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S6OFF)==0){ Sal_6=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,SALLON)==0){ Sal_1=1; Sal_2=1; Sal_3=1; Sal_4=1; Sal_5=1; Sal_6=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,SALLOFF)==0){ Sal_1=0; Sal_2=0; Sal_3=0; Sal_4=0; Sal_5=0; Sal_6=0; Beeper(3,1,1); resultado=1; } limpiarCodigo(); if(resultado==0){ // error de tecleado Beeper(10,1,1); } } |
Esta función compara el código cadena de 3 digitos con las respectivas claves. Se Observa que dentro de esta función se encuentran strcmp() para comparar cadenas , perteneciente a la librería String.h.
Limpiar código
1 2 3 4 5 6 |
void limpiarCodigo(void){ int i; for(i=0;i<4;i++){ // Limpia la entrada de codigos todos a NULL codigo[i]=''; } } |
Esta función lo que hace es resetear la variable cadena código[], con caracteres NULL.
ARMANDO EL PROYECTO DE SOFTWARE COMPLETO
Vamos a comenzar ahora a unir los métodos aislados en una función o método principal que es el main(). Antes de esto vamos a analizar la rutina de Interrupción que es llamada con cada pulso DV del DTMF:
Notar que en todo el código , todo lo colocado entre /*…….. */ son comentarios no tenidos en cuenta por el compilador.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
void interrupt pulso (void){ // Rutina de Interrupción Flanco ASC RB0/INT /*Beep=1; Delay10KTCYx(50); Beep=0;*/ FlagPulsoDTMF=0; // Limpia flag evento Flanco RBO/INT limpiaT1(); flagEntrada=1; char caracter[2]={'',''}; // string local que guarda el digito del DTMF recordar '' unsigned char indice; // digito leido de 0 al 12 del puerto contador++; indice=LeerPuerto(); // del 0 al 12 caracter[0]=Digito[indice]; // trae el caracter según subindice y lo trnasforma en cadnena strcat(codigo,caracter); // Concatena el caracter leido dentro de la cadena string del codigo[] // para armar la cadena de 3 caracteres if(contador==3){ contador=0; if( estado==0){ // contraseña no validada //controlSalidas(); if(strcmp(contrasena,codigo)==0){ // Cadenas iguales estado=1; Beeper(1,20,1); limpiarCodigo(); } else{ // contraseña invalida Beeper(10,1,1); limpiarTodo(); } } else{ // Estado =1 controlSalidas(); } } } |
Lo primero que se hace es limpiar el Flag de interrupción ya definido, esto evita que al salir de esta rutina se vuelva a ingresar creando un Loop inestable. Luego limpiamos el Timer1 que es el que cuneta entre digito y digito, seteamos el flagEntrada en ON o 1, que va a indicar al programa principal que se recibió un dígito. Se crea una variable local char carácter[2] que almacena temporalmente el carácter recibido. Luego se crea un índice que va a traer el valor en decimal del código binario leído en el puerto. Después con ese índice entramos como puntero al vector Digito[] y se lo asignamos a carácter[]. Armamos la cadena concatenando con dicho carácter , función strcat(). Si el contador de caracteres es 3, se resetea a cero y se verifica el estado del equipo. Si el estado es cero, verificamos contraseña con strcmp(). Si el código = contraseña, la función devuelve 0 y se pasa a estado =1 y se avisa con el dispositivo de audio, caso contrario, si no es la contraseña, se limpia todo. Si el estado es 1, se va a verificar las claves de control de salidas.
Programa principal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void main(void) { configIO(); configIRQ(); configT1(); limpiarTodo(); //Beeper(1,10,1); //on_T1(); while(1){ if(flagEntrada==1){ // Vino un digito //SLEEP(); if(chequeaEstadoT1()==0){ // APAGADO on_T1(); } chequeaTiempo();// Chequea los tiempos limites } //limpiarTodo(); } return; } |
Al arrancar el PIC ejecuta el main() , lo que hace es llama a los métodos de configuración ya vistos y luego entra en un loop continuo del que nunca sale, salvo cuando se dispara una interrupción.
Si el flagEntrada está activo es que se produzco una interrupción y si el Timer1 no está activado, lo activa, luego chequea los tiempos y se queda haciendo esto esperando a que se cumpla en tiempo entre digito y digito o los 5 minutos después de pasar al estado 1.
PROYECTO COMPLETO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
/* * File: main_dtmf.c * Author: Gustavo * * Created on 20 de febrero de 2015, 17:54 */ #include<string.h> #include <stdio.h> #include <stdlib.h> #include "delays.h" // header externo incluido,cuando se importa una libreria copiada pero no disponible para PIC16 // hay que implementar las funciones en el codigo, yo agregue las funciones en los fuentes.c #include "pic16f873.h" #include <xc.h> // CONFIG #pragma config FOSC = XT // Oscillator Selection bits (XT oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config CP = OFF // FLASH Program Memory Code Protection bits (Code protection off) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low Voltage In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EE Memory Code Protection (Code Protection off) #pragma config WRT = ON // FLASH Program Memory Write Enable (Unprotected program memory may be written to by EECON control) // Declaraciones #define Sal_1 PORTBbits.RB7 // Salida de control 1 con reles #define Sal_2 PORTBbits.RB6 // Salida de control 2 #define Sal_3 PORTBbits.RB5 // Salida de control 3 #define Sal_4 PORTBbits.RB4 // Salida de control 4 #define Sal_5 PORTBbits.RB3 // Salida de control 5 NO SE PUEDE DIRECTO RA4 ES OPEN DRAIN #define Sal_6 PORTBbits.RB2 // Salida de control 6 #define Beep PORTBbits.RB1 // Buzer de indicacion sonora #define FlagPulsoDTMF INTCONbits.INTF // Flag de evento RBO/INT salisa DV del DTMF // VARIABLES char Digito[14]={'0','1','2','3','4','5','6','7','8','9','0','*','#',''}; // Digito[0], no se usa ya que el Digito[10]='0' caracter , '' fin de cadena String char codigo[4]={'','','',''}; // guarda en formato cadena 3 caracteres de entrada + el null de fin '' const char contrasena [4]="#9*"; // Contraseña para operacion del DTMF unsigned char estado=0; // Variable que indica el estado del controlador DTMF // 0 contraseña invalida o no recibida // 1 contraseña valida y se pueden operar las salidas // DETECCION DE CODIGO DE DIGITO CONTRA DTMF // PUERTO B entradas RA3=D3, RA2=D2, RA1=D1, RA0=D0 DEL DTMF unsigned int contador=0; // Contador de degitos que van ingresando unsigned char flagEntrada=0; // Indica que vino un puldo del DTMF 0/1 unsigned int ciclosT1=0; // ciclos de cuentas Timer1 // ciclosT1= 10 SERAN 5 SEG, 10 desbordes de T1 a 65536 * 0.1us * 8 // ciclosT1= 20 SERAN 10 SEG, 20 desbordes de T1 a 65536 * 0.1us * 8 char S1ON [4]="11*"; // Codigos de activacion y desactivacion salidas char S1OFF [4]="10*"; // agrega '' al final de cada cadena char S2ON [4]="21*"; char S2OFF [4]="20*"; char S3ON [4]="31*"; char S3OFF [4]="30*"; char S4ON [4]="41*"; char S4OFF [4]="40*"; char S5ON [4]="51*"; char S5OFF [4]="50*"; char S6ON [4]="61*"; char S6OFF [4]="60*"; char SALLON[4]="666" ;// ENCIENDE TODAS char SALLOFF[4]="000";// APAGA TODAS // Funciones void configIO(void); // Configuración de periféricos void configIRQ(void); // Configuración para la interrupción por Flanco ACS RBO/INT void Beeper(int cant, int tpon, int tpof); // Función de indicación sonora // cant=cantidad de beeps, tpon=HIGH de cada beep , tpof= LOW unsigned char LeerPuerto(void); // Lee el puerto del DTMF void limpiarTodo(void); // Inicializa todo nuevamente void configT1(void); // Configuracion del Timer 1 para tiempos muertos void on_T1(void); // Enciende Timer 1 void chequeaTiempo(void);// Chequea si se ha superado el tiempo entre digito y digito bit chequeaEstadoT1(void); // consulta si el timer esta encendido o void apagaT1(void); // apaga el timer1 void limpiaT1(void); // limpia el Timer1, lo reseta junto con sus ciclos void controlSalidas(void); // controla las salidas void limpiarCodigo(void); // limpia variable de cadena de codigo void interrupt pulso (void){ // Rutina de Interrupción Flanco ASC RB0/INT /*Beep=1; Delay10KTCYx(50); Beep=0;*/ FlagPulsoDTMF=0; // Limpia flag evento Flanco RBO/INT limpiaT1(); flagEntrada=1; char caracter[2]={'',''}; // string local que guarda el digito del DTMF recordar '' unsigned char indice; // digito leido de 0 al 12 del puerto contador++; indice=LeerPuerto(); // del 0 al 12 caracter[0]=Digito[indice]; // trae el caracter según subindice y lo trnasforma en cadnena strcat(codigo,caracter); // Concatena el caracter leido dentro de la cadena string del codigo[] // para armar la cadena de 3 caracteres if(contador==3){ contador=0; if( estado==0){ // contraseña no validada //controlSalidas(); if(strcmp(contrasena,codigo)==0){ // Cadenas iguales estado=1; Beeper(1,20,1); limpiarCodigo(); } else{ // contraseña invalida Beeper(10,1,1); limpiarTodo(); } } else{ // Estado =1 controlSalidas(); } } } void main(void) { configIO(); configIRQ(); configT1(); limpiarTodo(); while(1){ if(flagEntrada==1){ // Vino un digito if(chequeaEstadoT1()==0){ // APAGADO on_T1(); } chequeaTiempo();// Chequea los tiempos limites } } return; } void configIO(void){ ADCON1=0x06; // A/D desactivados Y PORT A RA0-RA5 como Digital pag 114 datashhet ADCON0bits.ADON=0; //Modulo interno de conversion apagado TRISA =0xFF; // PUERTO A todo entrada para señales DTMF //TRISC=0x00; // PUERTO C salida TRISB=0b00000001; // PUERTO B todo salida a excepcion de RB0/INT PORTB=0x00; Sal_1=0; NOP(); Sal_2=0; NOP(); Sal_3=0; NOP(); Sal_4=0; NOP(); Sal_5=0; NOP(); Sal_6=0; NOP(); Beep=0; } void configIRQ(void){ OPTION_REGbits.INTEDG=1; // configura RB0(PulsoDigito) para IRQ Flanco Ascendente INTCONbits.PEIE=0; // interrupción perifericos desactivada RBO/INT no necesita permiso perifericos // Me aseguro que solo RBO/INT sea la fuente de las interrupciones INTCONbits.RBIE=0; // interrupción por RB0 desactivada para cualquier cambio puerto B INTCONbits.INTE=1; // Habilita IRQ RBO/INT INTCONbits.INTF=0; // Limpia Flag de IRQ es lo mismo que FlagPulsoDTMF=0; INTCONbits.GIE=1; // Habilita interrupciones globales } void configT1(void){ // Configura Timer 1 para que cuente 5 seg y 10 seg // Configurarlo con prescaller 1:8 T1CKPS1=T1CKPS0=1 // Oscilador timer1 externo desactivado T1OSCEN=0 // EN FORMA SINDRONICA T1SYNC#=0 // Fuente de cuentas interno xtal/4 TMR1CS=0 // TMR1ON =1/0, enciende y apaga T1CON=0x30; //T1CONbits.TMR1ON=0; // apagado apagaT1(); TMR1H=0x00; // Puesta a cero TMR1L=0x00; // ciclosT1= 10 SERAN 5 SEG, 10 desbordes de T1 a 65536 * 0.1us * 8 // tiempo de validacion entre tecla y tecla para la clave Estado=1 // ciclosT1= 600 SERAN 300 SEG=5 minutos, 600 desbordes de T1 a 65536 * 0.1us * 8 // Una vez entrada la clave permite 5 minutos a partir del ultimo ingreso de tecla // pasados los 5 min vuelve al Estado= 0 } void Beeper(int cant, int tpon, int tpof){ // TPO MINIMO BASE =0.1 SEG /* 10000* 10 * tpon/tpof HIGH= 0.1* tpon LOW= 0.1 * tpof Contraseña invalida : 10 beeps de 0.1 seg Beeper(10,1,1); Digito valido: 1 beep de 0.5 seg no lo usamos contraseña valida : 1 beep de 2 segundos Beeper(1,20,1); salida activada/desactivada: 3 beep de 0.1 seg Beeper(3,1,1); */ int i; int j; for(i=1;i<=cant;i++){ // la variable unit compilada en la libreria es el // argumento de 10KTCYx y va de 0 a 255 es unsigned char Beep=1; for (j=1;j<=tpon;j++){ Delay10KTCYx(10); } for(j=1;j<=tpof;j++){ Beep=0; Delay10KTCYx(10);// Toff es siempre el mismo } } } unsigned char LeerPuerto(void){ // Lee el puerto y devuelve un decimal del digito DTMF unsigned char puerto; // almacena los bits leidos del DTMF unsigned char puertoFinal=0x00; // arma el byte correcto puerto=PORTA; // SOLO INTERESAN RA0-RA3 Lee el puerto puerto&=0x01; // lee D3 MSB puerto<<=3; // QUEDA 0000 D3 000 puertoFinal|=puerto; //0000 D3 000 puerto=PORTA; puerto&=0x02; // Lee D2 puerto<<=1; // queda 000000 D2 00 puertoFinal|=puerto; // QUEDA 0000 D3 D2 0 0 puerto=PORTA; puerto&=0x04; // Lee D1 puerto>>=1; // queda 000000 D1 0 puertoFinal|=puerto; // QUEDA 0000 D3 D2 D1 0 puerto=PORTA; puerto&=0x08; // Lee D0 puerto>>=3; // queda 0000000 D0 puertoFinal|=puerto; // QUEDA 0000 D3 D2 D1 D0 puertoFinal&=0x0f; //puerto>>=4; // desplaza 4 lugares a la derecha asi se puede usar como indice return puertoFinal; } void limpiarTodo(void){ estado=0; contador=0; limpiarCodigo(); flagEntrada=0; ciclosT1=0; apagaT1(); } void on_T1(void){ T1CONbits.TMR1ON=1; // enciendo Timer limpiaT1(); } void apagaT1(void){ T1CONbits.TMR1ON=0; // enciendo Timer } void chequeaTiempo(void){ if(PIR1bits.TMR1IF==1){ // DESBORDO?? ciclosT1++; PIR1bits.TMR1IF=0; // limpia desborde if (estado==0){ // validar clave if(ciclosT1==10){ // pasaron los 5 segundos limpiarTodo(); } } if(estado==1){ // ya esta validada la clave if(ciclosT1==600){ // 5 MINUTOS limpiarTodo(); } } } } bit chequeaEstadoT1(void){ if(T1CONbits.TMR1ON==1){ return 1; } return 0; } void limpiaT1 (void){ TMR1H=0x00; TMR1L=0x00; PIR1bits.TMR1IF=0; // Limpia desborde ciclosT1=0; } void limpiarCodigo(void){ int i; for(i=0;i<4;i++){ // Limpia la entrada de codigos todos a NULL codigo[i]=''; } } void controlSalidas(void){ unsigned char resultado=0; if(strcmp(codigo,S1ON)==0){ Sal_1=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S1OFF)==0){ Sal_1=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S2ON)==0){ Sal_2=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S2OFF)==0){ Sal_2=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S3ON)==0){ Sal_3=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S3OFF)==0){ Sal_3=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S4ON)==0){ Sal_4=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S4OFF)==0){ Sal_4=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S5ON)==0){ Sal_5=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S5OFF)==0){ Sal_5=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S6ON)==0){ Sal_6=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,S6OFF)==0){ Sal_6=0; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,SALLON)==0){ Sal_1=1; Sal_2=1; Sal_3=1; Sal_4=1; Sal_5=1; Sal_6=1; Beeper(3,1,1); resultado=1; } if(strcmp(codigo,SALLOFF)==0){ Sal_1=0; Sal_2=0; Sal_3=0; Sal_4=0; Sal_5=0; Sal_6=0; Beeper(3,1,1); resultado=1; } limpiarCodigo(); if(resultado==0){ // error de tecleado Beeper(10,1,1); } } |
MÓDULO DRIVER RELÉ
Para manejar los Reles, en lugar de usar los clásicos transistores en emisor común, se utilizó el ULN2803 que es un driver colector abierto para manejar, en general, cargas inductivas como en este caso relés de 12 V o motores PAP.
Los diodos son de protección del clásico efecto de sobrepico Lenz al desactival la carga inductiva o bobina de rele. Se produce un sobrepico de tensión que es recortado por los diodos. Para esto se debe colocar el COM a 12 V que es la tensión de manejo de los reles.
El esquema de Drivers es el siguiente:
El puerto PORTB del micro se conecta a las entradas del Driver, y sus salidas de esta manera:
Juntamente con los relés, se colocan leds indicadores del estado de cada salida. Cuando la salida de driver va a 0 Volt, se activa el rele asociado y su led.
El esquema completo no se va a visualizar muy bien, pero pueden enviarme un mail y se los paso.
Les dejo un video de muestra del equipo funcionando. Es necesario configurar el celular remoto para respuesta automática de llamada en modo auriculares y con volumen de medio a alto. Espero les haya gustado
FIN