Mostrando entradas con la etiqueta Microcontrolador. Mostrar todas las entradas
Mostrando entradas con la etiqueta Microcontrolador. Mostrar todas las entradas

PIC-Cal, un PIC hecho calculadora.

... O una calculadora hecha con PIC.
En esta ocación les presento una calculadora hecha con un PIC16F88 y un módulo LCD administrado con el microcontrolador Hitachi 44780 o compatible.
Esta no es una gran calculadora, pero resuelve fórmulas algebraicas reconociendo términos y utilizando parentesis. Esta versión todavía no maneja números negativos ni decimales, ya que solo el hecho de utilizar números float32 aumenta el consumo de memoria flash del PIC desde el 60% hasta cerca del 80%. Si se quisiera hacer una calculadora mas exacta y compleja se tendría que recurrir a un PIC con mas memoria.
El proyecto es una mezcla de la práctica de varios artículos que se pueden encontrar en este blog. Ya que utiliza la librería LCDGAR.c para el display, controlado con un shift register que a su vez se usa para barrer el teclado.
El código es demasiado extenso como para publicarlo en el blog, por ese motivo es que solo dejo el link para descargar el proyecto completo en CCS C y la simulación en ISIS. De todos modos pasaré a comentarlo brevemente:
El bucle principal espera a que se pulse una tecla, cuando esto sucede actua en consecuencia de la tecla pulsada. Esta es la parte sencilla.
Cuando se pulsa cualquier tecla, que no sea el signo igual, la almacena en una cadena y si se pulsa el signo igual, resuelve. Esto en teoría también es sencillo.
Ahora la pregunta es ¿cómo se resuelve?, pues bien, las calculadoras normales van haciendo las operaciones según se las vayan ingresando. Pero sabemos que en matemáticas esto no es así, una calculadora científica, reconoce términos, y eso es lo que se pretendía resolver en este proyecto, luego se agregó el uso de parentesis, pero la forma de resolverlos es igual para ambos casos.
Supongamos que la fórmula ingresada es:

15 X 3 + 8 X 4

Para resolverlo, con lápiz y papel, no podemos hacer:

15 X 3 = 45
45 + 8 = 53
53 X 4 = 212

La forma correcta sería separandolo en términos, que es de esta forma:

15 X 3 = 45
8 X 4 = 32
45 + 32 = 77

15 X 3 + 8 X 4 = 77

Eso es lo que se pretende que haga la calculadora, para eso, una vez pulsada la tecla resolver, la secuencia es como se muestra a continucación:
Paso 1Se recorre toda la fórmula. Si encuentra un + o un - corta la fórmula, de modo que queden tres términos y si hay mas de tres, en el último habrá varios términos.
Paso 2Resuelve el segundo término, dejando en este el resultado.
Paso 3Junta el primer término con el segundo en un único y primer término.
Paso 4Resuelve el primer término, dejando el resultado.
Paso 5Junta el resultado del primer término con el tercero en la fórmula.
Paso 6Si todavía hay cuentas por realizar vuelve al paso 1.
FINPresenta el resultado en pantalla.

En la práctica para la fórmula 3 X 3 + 8 X 4 - 2 X 3, pasaría lo siguiente:
Vuelta 1 Paso 1 Recorre la fórmula buscando + y -, recortándola y dejando:
Primer término: 3 X 3 +
Segundo término: 8 X 4
Tercer término: - 2 X 3
Paso 2 Resuelve el segundo término:
Segundo término: 32
Paso 3Junta el primer y el segundo término:
Primer término: 3 X 3 + 32
Paso 4Resuelve el primer término:
3 X 3 = 9
9 + 32 = 41
Primer término: 41
Paso 5Junta el primer y tercer término, en fórmula:
Fórmula: 41 - 2 X 3
Paso 6Hay cuenta, vuelve al paso 1.
Vuelta 2 Paso 1 Recorre la fórmula buscando + y -, recortándola y dejando:
Primer término: 41 -
Segundo término: 2 X 3
Tercer término: (vacío)
Paso 2 Resuelve el segundo término:
Segundo término: 6
Paso 3 Junta el primer y el segundo término:
Primer término: 41 - 6
Paso 4Resuelve el primer término:
41 - 6 = 35
Primer término: 35
Paso 5Junta el primer y tercer término, en fórmula:
Fórmula: 35 (No hay nada en el tercero)
Paso 6No hay mas cuentas, sale del bucle.
FIN Presenta el resultado en pantalla.
Los parentesis se resuelven utilizando el mismo método, con la diferencia que utiliza el contenido de los mismos como fórmulas independientes y son las primeras en resolverse. El algoritmo detecta parentesis dentro de parentesis.
Para terminar, aclarar que el código no está pulido ni probado al 100%, de modo que puede, y debe, tener muchos bugs. Pero lo comparto por si a alguien le es útil como ejemplo. Si se encuentra algún error o alguna modificación significativa, bienvenidas serán sus sugerencias.
El primer bug que sé que va a tener, es que no se puede ingresar fórmulas de mas de 64 caracteres, y el código no tiene ningún tipo de control para evitar la catástrofe cuando se ingrese el número 65.

Algoritmo antirrebote de pulsadores

Indagando en foros uno se da cuenta que lo que parece tan fácil como capturar la simple pulsación de una tecla conectada a un pin de un microcontrolador, en el fondo lleva consigo algunas técnica a tener en cuenta; Muchos son los que preguntan, sobretodo cuando se está iniciando en materia de programación de microcontroladores, como debería ser un buen algoritmo de antirrebote. Pues bien, en esta ocasión les traigo la implementación de la rutina escrita en CCS C para PIC16F88, que, si bien puede que no sea la mejor, es sin duda, muy funcional y práctica, al menos cumple su función a las mil maravillas. Es el modo en que hago el antirrebote en mis códigos y hasta ahora no ha fallado nunca.

La teoría es bastante sencilla, el programa espera a que se pulse una tecla, cuando detecta la pulsación guarda el número de tecla pulsada en una variable, luego comprueba que sea la primera vez que se pulsó, si es así actúa en consecuencia y si es que está pulsada desde antes, comprueba que haya pasado un tiempo determinado para volver a tenerla en cuenta. De este modo se puede hacer, por ejemplo un reloj que cuando se incremente alguno de sus parámetros, por ejemplo los minutos, y la tecla correspondiente se mantenga pulsada, luego del primer incremento comience a avanzar mas rápido.


He aquí el código en CCS C:
#include <16f88.h>
#use delay(clock=8MHz)
#use fast_io(all)
#fuses MCLR,NOPUT,INTRC_IO,NOWDT,NOBROWNOUT


#define Tec1   PIN_B0
#define Tec2   PIN_B1
#define Tec3   PIN_B2
#define Tec4   PIN_B3   //Pines del PIC correspondientes a cada tecla.

#define Nada   0  
#define Mas    1     // valores validos para Flag
#define Menos  2  

int Segs;            // Segundos.
int Minu;            // Minutos.
int Hora;            // Hora.
int Tmp;             // Temporal.
int Tecla;           // Tecla pulsada.
int anTecla;         // Tecla pulsada en la comprobacion anterior
int Tiempo;          // Tiempo transcurrido desde la ultima comprobacion
                     // de teclado.
int CuantoTiempo;    // Cuanto tiempo debe esperar para la
                     // proxima comprobacion.
int Flag;            // Flag para incrementar o decrementar
                     // el valor deseado.
int Set;             // Flag para seleccionar el valor a cambiar.

void main(){
   
   setup_oscillator(OSC_8MHz);
   
   set_tris_a(0);                // Puerto A como salida
   set_tris_b(0b00001111);       // Puerto B<7:4> salida B<3:0> entradas
   
   // Inicio Variables
   anTecla=255;
   Set=0;
   Hora=0;
   Minu=0;
   Segs=0;  
   
   
   do{   
   
      Tecla=0;
      if (input(Tec1))Tecla=1;        // Comprueba que pulsador se activó
      if (input(Tec2))Tecla=2;        // y lo guarda en Tecla
      if (input(Tec3))Tecla=3;
      if (input(Tec4))Tecla=4;        
      
      if (++Tiempo>CuantoTiempo||Tecla!=anTecla) {  // Incrementa el
                              // tiempo transcurrido, Si se pasa de
                              // CuantoTiempo o cambia el estado de
                              // de las teclas con respecto a la
                              // iteracion anterior.
Flag=Nada;                    // Limpia el flag
            
         if(Tecla!=0){                 // Si hay una tecla pulsada
            if(Tecla!=anTecla){        // si la tecla es la primera
                                       // vez que se pulsa.
               CuantoTiempo=100;       // establece el tiempo de
                                       // espera en 100.               
               if(Tecla==1){           // Si se pulsó la tecla 1
                  if(++Set>3) Set=1;   // Avanza el flag para setear
                                       // segs/minu/hora
               }
               if(Tecla==4) Set=0;     // Se pulsó la tecla 4, desactiva
                                       // el modo configuracion
            
            }else{                     // Si no es la primera vez que se
                                       // pulsa la tecla
               CuantoTiempo=30;        // la proxima comprobacion la
                                       // hará en menos tiempo.
            }
            
            if(Tecla==2) Flag=Menos;   // Si la tecla pulsada es la 2
                                       // flag de decremento
            if(Tecla==3) Flag=Mas;     // y si es la 3 flag de incremento
         }
         
         
         if (Set!=0){                  // Si está en modo configuracion
            
            if(Set==1) Tmp=Segs;       // Si se está configurando los
                                       // segundos Segs a temporal
            if(Set==2) Tmp=Minu;       // lo mismo si está configurando
                                       // los minutos
            if(Set==3) Tmp=Hora;       // o la hora
            
            if (Flag==Menos){          // si el flag es de decremento
               if(--Tmp>200)Tmp=59;    // le resta 1 al temporal si es
                                       // menor a 0 pasa a 59
               if(Set==3&&Tmp==59)Tmp=23;// Si está configurando las
                                       // horas el limite es 23
            }
            
            if (Flag==Mas){            // Si el flag es de incremento
               if(++Tmp>59)Tmp=0;      // Incrementa el temporal y si
                                       // se pasa de 59 vuelve a 0
               if(Set==3&&Tmp>23)Tmp=0;// Si configura las horas y
                                       // se pasa de 23 vuelve a 0
            }
            
            if(Set==1) Segs=Tmp;
            if(Set==2) Minu=Tmp;       // Guarda el valor temporal
            if(Set==3) Hora=Tmp;       // en donde corresponda...
         }      
         anTecla=Tecla;                // Almacena la tecla pulsada
                                       // para la próxima 
         Tiempo=0;                     // iteración, Tiempo a 0 para
                                       // volver a contar el tiempo
            
      }
      delay_ms(5);   // Entre iteración e iteración espera 5 ms que
                     // multiplicado por CuantoTiempo, es el tiempo
                     // que tarda en reaccionar a las pulsaciones y
                     // cuanto tarda en avanzar cuando se mantiene
                     // pulsado un botón.
     
   }while(true);
}

Y aquí un video del algoritmo implantado en un pseudo-reloj, en el ejemplo solo incluí la parte de los pulsadores y el display que están controlados con shift register.



Matriz de 8x8 LED controlada con 2 shift register y PIC

Muchas personas experimentan a diario con arrays de 8x8 LED y un porcentaje de esas personas reflejan sus dudas en distintos sitios desperdigados por la red destinados a este mundillo de la electrónica y los microcontroladores.

Precisamente de una duda que se me planteó hace poco por correo electrónico surgió un pequeño proyecto a modo de ejemplo de un array de 8x8 LED; El cual funciona de maravillas tanto simulado como físicamente y es por eso que lo publico en esta ocación.

Dicho proyecto está basado en una serie de artículos anteriores dedicados a los shift register, en otras palabras es una implementación de los registros para usarlos con una matriz de LED.

Aquí presento el diagrama de conexión:


El PIC utilizado en esta ocación es el PIC16F88 por ser muy fácil de implementar y contar con oscilador interno.

La línea Load se conecta al pin B0 del PIC, Clk al B1, dClm a B2 y dLin a B3.

La lista de componentes es mas cuantiosa que variada pues esta compuesta por:
R1-R88 x Resistencias de 220Ω
R9-R168 x Resistencias de 3,9KΩ
Q1-Q88 x Transistores BC547 o similar

2 x 74HC595

64 x LED rojo de 5mm brillo standard

El código, escrito en CCS C, para probar el hardware es el que sigue a continuación, solo he dejado las letras pertinentes a PICROBOT, ya que sino se hace muy largo y repetitivo para mostrarlo como ejemplo, pero desde este link te puedes descargar el código completo con las letras en mayúsculas A-Z, el .HEX, el .COF para simularlo en el ISIS de Proteus y el .DSN con el diseño.

Hay dos versiones del código en este paquete, matriz8x8Q y matriz8x8. La primera es para cuando se usen los transistores a los cátodos de los LED y la segunda si los cátodos van directamente a las salidas del registro de desplazamiento encargado de controlar las columnas.

La única diferencia entre las dos versiones es que la primer versión (Q) no invierte y la segunda si lo hace, las salidas del registro encargado de controlar las filas.

Se podría haber solucionado el problema declarando o no una macro instrucción dirán algunos, después de todo lo único que varía es un caracter de una versión a otra, pero para no confundir, y como este es un ejemplo sencillo, decidí hacerlo así. En un futuro ejemplo de la implementación tal vez incluya una macro instrucción.

/*************************************************************************
**                                                                      **
**    Ejemplo básico para controlar una matriz de 8x8 LEDs con PIC.     **
**                                                                      **
**                      (c) 2010 Gerardo Ariel Ramírez                  **
**                            picblog@hotmail.com                       **
**                       http://picrobot.blogspot.com/                  **
**                                                                      **
**************************************************************************
**                                                                      **
**  Microcontrolador: PIC16F88           Oscilador: Interno - 8 MHz     **
**          Lenguaje: CCS C                                             **
**                                                                      **
*************************************************************************/

#include <16f88.h>      // Tipo de microcontrolador
#fuses INTRC_IO,MCLR    // Oscilador interno, MCLR activo
#fuses NOPUT,NOBROWNOUT // Sin Brownout reset ni Power up timer 
#use fast_io(all)       // La configuración de los puertos solo se hace al principio.
#use delay(clock=8M)    // Velocidad del oscilador interno 8 MHz

#define Load   PIN_B0   // Load (STCP ambos integrados) B0
#define Clk    PIN_B1   // Clock (SHCP ambos integrados) B1
#define dClm   PIN_B2   // Data para las columnas (DS integrado 1) BC2
#define dLin   PIN_B3   // Data para las lineas (DS integrado 2) B3

char  Memoria[96];      // 96 Bytes para la memoria (0 - 95)
char  Visor[8];         // 8 para el visor (8 columnas)

int1  flag;             // Flags de control
int1  flag2;
int   indx;             // Indice donde almacenará las nuevas columnas.
int   line;             // Linea que a mostrar.
int   time;             // Variables para el control de
int   ptime;            // la velocidad de desplazamiento.
int   t;                // Variable auxiliar.

void CargaMem(char Ascii);
void GuardaClm(char c);

#int_rtcc
void isr(){
   int Mul=128;         // Cada vez que ocurre la interrupcion
   if(++line>7)Line=0;  // selecciona la siguiente linea, si se pasa de 7 vuelve a 0.
   
   if(++ptime>5){      // Suma 1 a ptime. Si se pasa de 20
      ptime=0;          // lo pone en 0 y suma 1 a time.
      if(++time>200){   // Si se pasa de 200
         time=0;        // lo pone en 0
         Flag=true;     // y activa el flag.
      }
   }
   
   
   for(t=0;t<8;t++){    // Bucle 0 - 7 (Lineas)
      
      output_bit(dLin,!!(Visor[Line]&Mul));  // dLin es seteado con el valor
                                             // del bit de la fila actual.     
      if (Line==t)output_high(dClm);         // Si Line es igual a t
                                             // activa el bit correspondiente
      else  output_low(dClm);                // a la columna, sino lo desactiva.
      
      output_low(Clk);  // 
      output_high(Clk); // Rota el contenido interno del 74HC595.
      
      Mul>>=1;          // Divide la mascara que compara con Visor[] (128,64,32...)
   }
      output_low(Load);
      output_high(Load);// El contenido interno del integrado pasa a las salidas.
   
}
void main(){
   int k;   
   set_tris_a(0x00);
   set_tris_b(0x00);
   for (k=0;k<8;k++){
      Visor[k]=0;
   }
   for (k=0;k<96;k++){
      Memoria[k]=0;
   }                    // Limpia la memoria y el visor
   
   flag=true;           // Activo el flag para que cargue la memoria
   
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);  // Configuración del Timer0
   enable_interrupts(int_rtcc);              // Interrupcion por Timer0
   enable_interrupts(global);                // Interrupciones globales
   
   do{
      if (Flag){                 // Si el flag está activado
         flag2=true;             // Activa el flag2
         
         for (k=0;k<8;k++){      // Pasa el contenido de las primeras 8
            visor[k]=Memoria[k]; // columnas en memoria al visor
         }
         
         for (k=0;k<95;k++){        // Rota el contenido de toda la memoria
            Memoria[k]=Memoria[k+1];// a la izquierda 1=1+1, 2=2+1, n=n+1...
            
            if (Memoria[k]!=0){Flag2=false;} // Si hay alguna columna que no
                                             // esté vacía desactiva el flag2
         }         
         Memoria[95]=0;             // Limpia la ultima columna de la memoria
        
        if (Flag2){                 // Si flag2 está activo            
            indx=7;                 // a partir de la columna 7 
            CargaMem("PICROBOT");   // escribe PICROBOT            
         }
         Flag=false;                // Desactiva el flag
         
      }
   }while (true);    // Bucle infinito


}

void GuardaClm(char c){
   if (indx<94){
      Memoria[indx]=c;     // Guarda la columna en la ubicación actual de memoria
      indx++;              // y aumenta el indice
   }
}


void CargaMem(char ascii){    // Carga la memoria con el caracter deseado
   switch (ascii){    
      
      case('B'):
      GuardaClm(0b01111111);
      GuardaClm(0b01111111);
      GuardaClm(0b01001001);
      GuardaClm(0b01001001);
      GuardaClm(0b01111111);
      GuardaClm(0b00110110);      
      break;
      
      case('C'):
      GuardaClm(0b00111110);
      GuardaClm(0b01111111);
      GuardaClm(0b01000001);
      GuardaClm(0b01000001);
      GuardaClm(0b01100011);
      GuardaClm(0b00100010);     
      break;

      case('I'):
      GuardaClm(0b01000001);
      GuardaClm(0b01000001);
      GuardaClm(0b01111111);
      GuardaClm(0b01111111);
      GuardaClm(0b01000001);
      GuardaClm(0b01000001);      
      break;  

      case('O'):
      GuardaClm(0b00111110);
      GuardaClm(0b01111111);
      GuardaClm(0b01000001);
      GuardaClm(0b01000001);
      GuardaClm(0b01111111);
      GuardaClm(0b00111110);      
      break;      
      
      case('P'):      
      GuardaClm(0b01111111);
      GuardaClm(0b01111111);
      GuardaClm(0b00001001);
      GuardaClm(0b00001001);
      GuardaClm(0b00001111);
      GuardaClm(0b00000110);      
      break;
      
      case('R'):
      GuardaClm(0b01111111);
      GuardaClm(0b01111111);
      GuardaClm(0b00001001);
      GuardaClm(0b00011001);
      GuardaClm(0b01111111);
      GuardaClm(0b01100110);
      break;
      
      case('T'):
      GuardaClm(0b00000011);
      GuardaClm(0b00000001);
      GuardaClm(0b01111111);
      GuardaClm(0b01111111);
      GuardaClm(0b00000001);
      GuardaClm(0b00000011);
      break;      
   }
      GuardaClm(0b00000000);
}

Controlar 8 displais de 7 segmentos con shift register

Continuando con el tema de los shift register, ofreceré algunas utilidades a dichos integrados utilizando un microcontrolador a modo de ejemplo y totalmente funcionales.

El ejemplo que aquí propongo es la utilización de los registros para controlar 8 displais de 7 segmentos, como ya vimos anteriormente en este artículo, pero con el agregado de la implementación de un teclado; Pero aquí les proporciono el ejemplo completo con el código fuente en CCS C y su respectivo .HEX para que puedan grabarlo en el PIC y probarlo.

He aquí el diagrama de conexión:

El diagrama es bastante sencillo, solo hace falta conectar cada segmento de cada display a las salidas (Q0-Q7) del shift register encargado de "dibujar" los números y el ánodo común de cada display a cada una de las salidas del segundo integrado. También este diseño es válido si los displais son de cátodo común la única diferencia está en que habría que hacer una pequeña modificación en el código.

Para probar el código que les propongo, al diagrama anterior hay que agregarle algunas cosas como por ejemplo 10 pulsadores, 10 diodos 1N4001-7 y 2 resistencias de unos 10KΩ, como se ve en el siguiente diagrama.

Cada ánodo de los diodos se conecta a una salida diferente del shift register encargado de seleccionar el display que se iluminará, de esta forma multiplexámos el teclado de modo que a cada iteración del bucle encargado de encender un display comprobamos una tecla, como esto nos da la posibilidad de conectar solo 8 pulsadores, se agrega otra entrada para comprobar una segunda línea.

A continuación les dejo el código:
/*************************************************************************
**                                                                      **
**             Ejemplo para controlar 8 display de 7 segmentos          **
**              y un teclado con dos shift register y un PIC.           **
**                                                                      **
**                      (c) 2010 Gerardo Ariel Ramírez                  **
**                            picblog@hotmail.com                       **
**                       http://picrobot.blogspot.com/                  **
**                                                                      **
**************************************************************************
**                                                                      **
**  Microcontrolador: PIC16F88           Oscilador: Interno - 8 MHz     **
**          Lenguaje: CCS C                                             **
**                                                                      **
*************************************************************************/

#include <16f88.h>         // Tipo de microcontrolador
#fuses INTRC_IO,MCLR,NOPUT,NOBROWNOUT // Fuses 
#use fast_io(all)      
#use delay(clock=8M)

#define Load      PIN_B0   // Load (STCP ambos integrados) B0
#define Clk       PIN_B1   // Clock (SHCP ambos integrados) B1
#define dDsp      PIN_B2   // Data para seleccionar display (DS integrado 1) B2
#define dSeg      PIN_B3   // Data para activar segmentos (DS integrado 2) B3
#define Teclado1  PIN_B4   // Entrada de teclas 0-7
#define Teclado2  PIN_B5   // Entrada de teclas 8 y 9

#define Seg_A  0x01
#define Seg_B  0x02
#define Seg_C  0x04
#define Seg_D  0x08
#define Seg_E  0x10
#define Seg_F  0x20        // Los bits correspondientes a cada segmento de cada display.
#define Seg_G  0x40        // Estos bits se comparan mediante la máscara Mask.

int Digit[8];
int Numero[10]={
Seg_A + Seg_B + Seg_C + Seg_D + Seg_E + Seg_F,
Seg_B + Seg_C,
Seg_A + Seg_B + Seg_D + Seg_E + Seg_G,
Seg_A + Seg_B + Seg_C + Seg_D + Seg_G,
Seg_B + Seg_C + Seg_F + Seg_G,
Seg_A + Seg_C + Seg_D + Seg_F + Seg_G,
Seg_A + Seg_C + Seg_D + Seg_E + Seg_F + Seg_G,
Seg_A + Seg_B + Seg_C 
Seg_A + Seg_B + Seg_C + Seg_D + Seg_E + Seg_F + Seg_G,
Seg_A + Seg_B + Seg_C + Seg_D + Seg_F + Seg_G
}; // Establece Numero[0-9] con los datos correspondientes a cada segmento que deberá encender.

void CargaMem(char Ascii);
void GuardaClm(char c);

void main(){
   int d;
   int b;
   int Mask;   
   int Tecla;
   int anTecla;
   
   setup_ccp1(CCP_OFF);
   set_tris_a(0);                      // Puerto A como salida
   set_tris_b(0b00110000);             // Puerto B como salida excepto B4:B5 como entradas
   
   setup_oscillator(OSC_8MHZ);   
   
   d=0;
   anTecla=10;
   
  do{      
      Mask=0x80;                       // Carga la mascara, solo el MSB en high                  
      for (b=0;b<8;b++){               // Un bucle de 8 pasos (0-7)
         
         output_bit(dSeg,!(Mask&Numero[Digit[d]]));         // Le envía los bits al shift
                                                            // register encargado de encender
                                                            // los segmentos.
         if (b==d) output_high(dDsp); else output_low(dDsp);// Pone en alto el bit corres-
                                                            // pondiente al display que se va a
                                                            // encender en esta iteración.
         
         Mask>>=1;                     // Divide la mascara por 2 : 128, 64, 32...
         output_low(Clk);              // Low-to-High en el Clk
         output_high(Clk);             // hace que la memoria del shift register rote
      }
      
      output_low(Load);                // Low-to-High en Load hace que la memoria
      output_high(Load);               // del shift register se represente en sus salidas 
      
      if (input(Teclado2)) Tecla=d+9;  // Comprueba que se haya detectado la pulsación en las
      if (input(Teclado1)) Tecla=d+1;  // entradas de teclado y lo guarda en la variable Tecla.
      
      if (++d>7){                      // Incrementa el display a mostrar
                                       // si se pasa de 7 vuelve a 0-         
         if (Tecla!=0 && AnTecla==0){  // Si Tecla es diferente a 0 (se ha pulsado una tecla)
            for (b=7;b>0;b--){
               Digit[b]=Digit[b-1];    // Hace un scroll en los display
            }
            Digit[0]=Tecla-1;          // y guarda la tecla pulsada en el ultimo display.
         }
         AnTecla=Tecla;                // Guarda la tecla pulsada y 
         Tecla=0;                      // pone Tecla a 0 para comprobar la proxima vez.
         d=0;                          // d a 0 para encender el primer display y comenzar
      }                                // otra vez.
   }while (true);                      // Bucle infinito
}

Espero que les haya gustado y lo próximo que les entregaré será un artículo sobre el control de un array de 8x8 LED con el mismo sistema.

Desde este link se puede bajar el código y el .HEX

Reguladores de voltaje, ¿cuál mejor?

Reguladores de voltaje hay muchos, sin lugar a dudas toda persona del mundillo, conoce por lo menos al archifamoso LM7805 , y seguramente el LM317 , pero lo que veremos en esta ocasión serán alternativas válidas y mejoras a dichos integrados.

Para empezar el LM7805 es un regulador de tensión positiva, perteneciente a la familia 78xx, que entrega 5V (ideal para alimentar microcontroladores) a una corriente de 1A. Y el LM317 es un regulador de tensión variable de hasta 1.5A. Para refrescarse un poco la memoria pueden ver estos dos artículos Reguladores de voltaje 7805 y Fuente de alimentación y cargador de baterías

A la hora de elegir un regulador de voltaje, normalmente nos fijamos en dos cosas: Tensión de salida (Volts) y Corriente de salida (Amperes), pero en realidad hay mas cosas que nos deberían preocupar, por ejemplo el encapsulado, tensión máxima de entrada y sobretodo si nuestro diseño final es algún dispositivo a pilas, deberíamos prestar especial atención a la caída de tensión (Dropout voltage) ya que a menor caída, mayor eficiencia al consumir menos.

Empezaremos comparando los reguladores fijos de 5V de salida:

Modelo Fabricante Tensión Máx.Ent. Corriente salida Dropout voltage Otras tensiones (1) Pinout TO-220 (2)
LM1084-5.0 National 30V 5A 1.3V
1.5V
3.3V, 12V, ADJ GOI
LM1085-5.0 National 30V 3A 1.3V
1.5V
3.3V, 12V, ADJ GOI
LD1085xx50 ST 30V 3A 1.3V
1.5V
ADJ GOI
LM1086-5.0 National 30V 1.5A 1.3V
1.5V
3.3V, 12V, ADJ GOI
L4940V5 ST 17V 1.5A 0.5V
0.9V
8.5V IGO
µA7805 Texas Instruments 25V 1.5A 2V 8V, 10V, 12V, 15V, 24V IGO
LM7805
LM340-5.0
National 35V 1A 2V 12V, 15V IGO
LM7805 Fairchild 35V 1A 2V 6V, 8V, 9V, 12V, 15V, 18V, 24V IGO
L7805 ST 35V 1A 2V 6V, 8V, 8.5V, 9V, 12V, 15V, 18V, 24V IGO
LM2940-5.0 National 26V 1A 0.5V
0.8V
8V, 9V, 10V, 12V, 15V IGO
LF50 ST 16V 0.5A 0.4V
0.7V
1.5V, 1,8V, 2.5V, 3.3V, 6V, 8V, 9V, 12V IGO

(1) Los parámetros especificados en esta tabla corresponden a los modelos de 5V, "Otras tensiones" se refiere a las variedades disponibles en encapsulado TO-220 y cada variedad tiene sus especificaciones propias. Consulte los respectivos Datasheets.

(2) IGO: 1.Input (entrada), 2.GND (ground), 3.Output (salida)
GOI: 1.GND, 2.Output, 3.Input

Observando la tabla se puede ver que para reemplazar el integrado de batalla 7805 se puede usar el LM2940-5.0 ya que es un reemplazo directo en cuanto a disposición de pines y entrega de corriente, pero hay que tener en cuenta que su tolerancia máxima a la tensión de entrada es de 26V contra los 35V del otro, aunque para pruebas con microcontroladores en raras ocasiones se superan los 12V en la entrada del regulador. Este reemplazo trae como ventaja la menor caída de tensión reduciéndola a un cuarto (500mV) en condiciones normales y 800mV en las peores condiciones contra los 2V del 7805, esto es muy importante si la tensión de entrada proviene de una batería ya que a menor caída de tensión, mayor rendimiento, porque no olvidemos que la caída de tensión no es ni mas ni menos que un desperdicio de energía.

También se debe tener en cuenta al L4940V5 cuya caída de tensión es la misma que el anterior en condiciones normales (500mV) y de 900mV en el peor de los casos reduciendo la entrada máxima a 17V pero con el agregado de ser capaz de entregar hasta 1.5A de corriente contra 1A que entrega la familia 78xx, especial para cuando haya muchos integrados que alimentar en el circuito.

Y si 1.5A no nos resulta suficiente tenemos dos modelos más, el LM1085-5.0 y LM1084-5.0 capaces de entregar hasta 3 y 5A respectivamente, pero con una caída de tensión entre 1.3 y 1.5V.

En conclusión el mejor regulador para reemplazar a nuestro viejo y querido LM7805 es el L4940V5, con la ventaja de entregar un 50% mas de corriente y tener una caída de tensión 75% menor.

Generar música con PIC

Antes que nada debo aclarar que no estamos hablando de música con la calidad un CD, ni stereo, ni nada por el estilo, es mas, dudo que tenga alguna calidad, lo digo para no crear falsas expectativas.

En esta ocasión vamos a interpretar alguna melodía utilizando la técnica que vimos en "Generar sonido con PIC" y sólo lo haremos a modo didáctico, ya que es muy útil para comprender como el microcontrolador administra los tiempos y como se utilizan los puertos. Este ejercicio bien podría reemplazar al ya mítico parpadeo de un LED con PIC16F84A ya que es, en teoría, el mismo principio pero con el agregado del control de la frecuencia.

Ahora bien, aquí viene lo mas interesante, investigando un poco me enteré de como es esto de las notas al encontrar este artículo: Frecuencias de las notas musicales , en el que se explica la fórmula para obtener la frecuencia de cada nota musical. Una de las fórmulas, la mas sencilla para llevar a cabo en un programa, es esta:

Con esta fórmula pude sacar las frecuencias para las octavas 0 a la 6, que son las que mejor se ejecutan en el PIC, mas arriba o mas abajo ya es molesto o inaudible. Dichas frecuencias estan en la siguiente tabla:

0123456
DO32,7065,40130,81261,62523,251046,502093,00
DO#34,6469,29138,59277,18554,361108,732217,46
RE36,7073,41146,83293,66587,321174,652349,31
RE#38,8977,78155,56311,12622,251244,502489,01
MI41,2082,40164,81329,62659,251318,512637,02
FA43,6587,30174,61349,22698,451396,912793,82
FA#46,2492,49184,99369,99739,981479,972959,95
SOL48,9997,99195,99391,99783,991567,983135,96
SOL#51,91103,82207,65415,30830,601661,213322,43
LA55,00110,00220,00440,00880,001760,003520,00
LA#58,27116,54233,08466,16932,321864,653729,31
SI61,73123,47246,94493,88987,761975,533951,06
Tabla 1. Frecuencia en Hz de cada nota musical.

En esta otra tabla están representadas los microsegundos necesarios entre estado alto y bajo para generar dichas frecuencias:

0123456
DO15289,027644,513822,251911,12955,56477,78238,89
DO#14430,917215,453607,721803,86901,93450,96225,48
RE13620,976810,483405,241702,62851,31425,65212,82
RE#12856,486428,243214,121607,06803,53401,76200,88
MI12134,906067,453033,721516,86758,43379,21189,60
FA11453,825726,912863,451431,72715,86357,93178,96
FA#10810,975405,482702,741351,37675,68337,84168,92
SOL10204,205102,102551,051275,52637,76318,88159,44
SOL#9631,484815,742407,871203,93601,96300,98150,49
LA9090,904545,452272,721136,36568,18284,09142,04
LA#8580,674290,332145,161072,58536,29268,14134,07
SI8099,074049,532024,761012,38506,19253,09126,54
Tabla 2. Microsegundos de pausa correspondiente a medio periodo de cada nota musical.

Observando la última tabla con atención se ve que la nota de la octava siguiente es igual a la octava actual dividido por 2, o con la equivalencia en CCS C una rotación a la derecha.
De esto se deduce que teniendo un array con los valores de las diferentes notas se puede ir rotando a la derecha tantas veces como octavas queramos subir para obtener la nota y octava precisas.

En C, la función encargada de hacerlo se ve así:

#define Speaker PIN_B0 #define nDO 0 // DO #define nDO_ 1 // DO# #define nRE 2 // RE #define nRE_ 3 // RE# #define nMI 4 // MI #define nFA 5 // FA #define nFA_ 6 // FA# #define nSOL 7 // SOL #define nSOL_ 8 // SOL# #define nLA 9 // LA #define nLA_ 10 // LA# #define nSI 11 // SI int16 FreqNota[12]={ // retardos entre estado alto // y bajo para generar las notas 15289, // DO 14430, // DO# 13620, // RE 12856, // RE# 12134, // MI 11453, // FA 10810, // FA# 10204, // SOL 9631, // SOL# 9090, // LA 8580, // LA# 8099 // SI }; void Play(int nota, int octava, int16 duracion){ int16 fn; int16 mS_Transcurridos=0; // Contadores necesarios // para controlar la duración int16 CiclosL=0; // Contandor de uS fn=FreqNota[nota]; // Define los retardos para generar // la frecuencia de cada nota fn>>=(octava); // Adapta la frecuencia a la octava actual // haciendo una rotación // a la derecha por octava do{ output_high(Speaker); // Genera la frecuancia delay_us(fn); // con los retardos mientras CiclosL+=(fn); // aumenta el contador de // ciclos transcurridos output_low(Speaker); // en dos partes para repartir el delay_us(fn); // trabajo entre estado alto y bajo. CiclosL+=(fn); // CiclosL+=25; // Compensador. while(CiclosL>999){ // Se queda en el bucle mientras CiclosL // sea menor a 1000 (1 mS) CiclosL-=1000; // Le resta 1000 a CiclosL mS_Transcurridos++; // y le suma 1 a mS_Transcurridos. CiclosL+=25; // Compensador. } }while (duracion>mS_Transcurridos); // Repite el bucle hasta que haya // pasado el tiempo indicado. }

Bueno, sabiendo como ejecutar las notas musicales ahora es tiempo de interpretar una melodía.

Como aclaré antes de música no tengo conocimientos, pero buscando alguna melodía a interpretar en el PIC recordé que en BASIC (el antíguo) había una función llamada PLAY y que interpretaba las notas musicales con el PC Speaker. Buscando en Google encontré un artículo titulado DJ QBASIC donde hay diez canciones conocidas. Ahora lo que resta es adaptar el código BASIC a CCS con la función PLAY para C que vimos en la entrada "Generar sonido con PIC", y es cuando vemos otro pequeño inconveniente, las notas musicales en BASIC están en el sistema de notación musical inglés y nosotros usamos el latino, en la WikiPedia encontré este artículo donde hablan de eso y se muestra la equivalencia: Escala musical .

En base a eso hice este ejemplo que interpreta una melodía según se pulse una tecla, si se pulsa la tecla conectada a RB1 suena "Pop Corn", si se pulsa RB2 suena "Ecuador" y si se pulsa RB3 suena "The lion sleep tonight".

/////////////////////////////////////////////////////////////////////// // // // PICMusic v.1.00 // // (c) 2010 Gerardo Ariel Ramírez. // // // /////////////////////////////////////////////////////////////////////// // // // uControlador: PIC16F876A Lenguaje: CCS C // // Xtal: 4MHz // // // /////////////////////////////////////////////////////////////////////// #include <16f876a.h> #use delay(clock=4000000) #use fast_io(all) #fuses HS #FUSES NOPUT #FUSES NOBROWNOUT #define Speaker PIN_B0 #define nDO 0 // DO #define nDO_ 1 // DO# #define nRE 2 // RE #define nRE_ 3 // RE# #define nMI 4 // MI #define nFA 5 // FA #define nFA_ 6 // FA# #define nSOL 7 // SOL #define nSOL_ 8 // SOL# #define nLA 9 // LA #define nLA_ 10 // LA# #define nSI 11 // SI int16 FreqNota[12]={ // retardos entre estado alto // y bajo para generar las notas 15289, // DO 14430, // DO# 13620, // RE 12856, // RE# 12134, // MI 11453, // FA 10810, // FA# 10204, // SOL 9631, // SOL# 9090, // LA 8580, // LA# 8099 // SI }; void Play(int nota,int octava,int16 duracion); void PlayCancion(int cancion); void main(){ set_tris_b(14); // B<3:1>: Pulsadores B0: Speaker while (true){ if(input(PIN_B1))PlayCancion(1); //Si pulso switch 1 toca // Pop Corn if(input(PIN_B2))PlayCancion(2); //Si pulso switch 2 toca // Ecuador if(input(PIN_B3))PlayCancion(3); //Si pulso switch 3 toca // The lion sleep tonight } } void Play(int nota, int octava, int16 duracion){ int16 fn; int16 mS_Transcurridos=0; int16 CiclosL=0; fn=FreqNota[nota]; // Define los retardos para generar // la frecuencia de cada nota fn>>=(octava); // Adapta la frecuencia // a la octava actual do{ output_high(Speaker); // Genera la frecuancia delay_us(fn); // con los retardos mientras CiclosL+=(fn); // aumenta el contador de // ciclos transcurridos output_low(Speaker); // en dos partes para repartir el delay_us(fn); // trabajo entre estado alto y bajo. CiclosL+=(fn); // CiclosL+=25; // Compensador. while(CiclosL>999){ // Se queda en el bucle mientras // CiclosL sea menor a 1000 (1 mS) CiclosL-=1000; // Le resta 1000 a CiclosL mS_Transcurridos++; // y le suma 1 a mS_Transcurridos. CiclosL+=25; // Compensador. } }while (duracion>mS_Transcurridos); // Repite el bucle hasta // que haya pasado el // tiempo indicado. } void PlayCancion(int cancion){ switch (cancion){ case 1: //POP CORN play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nSOL ,4,166); play (nRE_ ,4,166); play (nSOL ,4,166); play (nDO ,4,166); delay_ms (166); play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nSOL ,4,166); play (nRE_ ,4,166); play (nSOL ,4,166); play (nDO ,4,166); delay_ms (166); play (nDO ,5,166); play (nRE ,5,166); play (nRE_ ,5,166); play (nRE ,5,166); play (nRE_ ,5,166); play (nDO ,5,166); play (nRE ,5,166); play (nDO ,5,166); play (nRE ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nLA_ ,4,166); play (nDO ,5,166); play (nSOL_ ,4,166); play (nDO ,5,166); break; case 2: //ECUADOR play (nLA ,3,100); delay_ms (200); play (nMI ,3,100); delay_ms (200); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nLA ,3,100); delay_ms (200); play (nMI ,3,100); delay_ms (200); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nDO ,4,100); delay_ms (200); play (nSOL ,3,100); delay_ms (200); play (nMI ,4,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nMI ,4,100); delay_ms (100); play (nRE ,4,100); delay_ms (100); play (nSOL ,3,100); delay_ms (100); play (nDO ,4,100); delay_ms (200); play (nLA ,3,100); delay_ms (200); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nDO ,4,100); delay_ms (100); play (nSI ,3,100); delay_ms (100); play (nSOL ,3,100); break; case 3: //The lion sleep tonight play (nDO ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (125); play (nMI ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (250); play (nMI ,3,125); play (nFA ,3,125); delay_ms (250); play (nMI ,3,125); delay_ms (125); play (nRE ,3,125); delay_ms (250); play (nDO ,3,125); delay_ms (250); play (nRE ,3,125); play (nMI ,3,125); delay_ms (250); play (nRE ,3,125); delay_ms (125); play (nDO ,3,125); delay_ms (250); delay_ms (125); play (nMI ,3,125); delay_ms (125); play (nRE ,3,500); break; } }

Si no lo quieres copiar, lo quieres modificar o lo quieres ya compilado, te puedes bajar el proyecto completo haciendo click en este link .

Librería para el manejo de un módulo LCD en CCS C

Ya vimos Conexión y funciones de un módulo LCD y Control de un módulo LCD con PIC y CCS C, partiendo de esa base podemos seguir adelante y ¿por qué no directamente con ejemplos? Bueno, en esta ocasión les traigo una librería lista para operar con módulos LCD con buses de 4 u 8 bits, y conexión al PIC de modo serial o paralelo. Se trata de una librería que bauticé con el nombre, no muy creativo, de lcdgar.c.
Lo bueno que tiene esta librería es que para configurarla sólo hacen falta definir o no algunas macros, de modo que es bastante flexible y puede usarse de muchas maneras.
Aquí les presento el primer ejemplo, y es la forma mas rápida de configurar lcdgar, ya que es predeterminada, sin definir nada la librería se auto-configura.
#include <16f88.h>         // Tipo de PIC y declaraciones
#use delay(internal=8MHz)  // Usar delay con un reloj interno a 8 MHz
#Fuses INTRC_IO            // Oscilador interno
#Fuses PUT                 // Espera unos mS a que se estabilice la
                           // tensión antes de iniciar.
#Fuses BROWNOUT            // Si la tensión cae por debajo de un límite
                           // reinicia el PIC

// Defino la configuración para que LCDGAR establezca comunicación con
// el módulo LCD. Debe definirse toda la configuración antes
// de llamar a lcdgar.c

// Como se usará la configuración predeterminada no se definirá nada,
// y al llamar a  se autoconfigurará. Si deseamos cambiar
// dicha configuración aquí deberán aparecer todos los cambios.

#include <lcdgar.c>

void main(){

   init_LCD();       // Inicio el Módulo LCD

   locate(7,0);      // ubico el cursor en la columna 7 de la primer 
   print("HOLA");    // línea, escribe HOLA

   locate(6,1);      // ubico el cursor en la columna 6 de la segunda 
   print("MUNDO");   // fila y escribo MUNDO.
   
   do{               // Entra en un bucle infinito
   }while(true);
}
Con este código cargado en el PIC se puede controlar un módulo LCD con bus de 8 bits usando todo el puerto B para la comunicación PIC-LCD, el Pin A0 será el encargado de la señal ENABLE y el Pin A1 el encargado de la señal RS.
Si quisieramos, por ejemplo, en lugar del Puerto B, utilizar el Puerto C como bus de datos, deberíamos definir, antes de llamar a la librería mediante la directiva #INCLUDE, el macro Bus_Puerto_C:
#define Bus_Puerto_C
Ahora bien, se puede usar un shift register para controlar el LCD y de esa forma ahorrarnos pines del microcontrolador, en la entrada SHIFT REGISTER ¿que son y como se usan? puedes ver más sobre dichos integrados. En el caso del PIC16F877A y con sólo el display conectado no hace falta ahorrar, pero cuando se trata de una aplicación donde no nos quede muchos pines libres, es algo muy valioso, ya que pasaremos de requerir 10 pines en modo paralelo a sólo 4 en el modo serial y sólo el Pin que corresponde a la señal ENABLE debe ser exclusivo para el manejo del módulo, de modo que los demás pines podrán ser compartidos, inclusive los pines del shift register se podrían usar para, por ejemplo, controlar un teclado.
Para utilizar la líbreria en modo serial solo hace falta definir el macro "LCD_Serial" del siguiente modo:
#define LCD_Serial
Si queremos usar el bus de datos de 4 bits debemos definir, tambien antes de llamar a la librería, el macro "LCD_4Bits", mediante:
#define LCD_4Bits
Cabe destacar que cuando se utiliza el Bus de 4 bits, conexión paralela y está definido un puerto como bus, el dato se envía completo al puerto usado, pero el display LCD sólo reconoce los 4 MSB o sea <4:7>. Si no se desea escribir en todo el puerto cada vez que se utiliza el display, puede definir los 4 pines que serán los bits del bus al LCD del siguiente modo:
#define Bit0 Pin_X 
#define Bit1 Pin_X 
#define Bit2 Pin_X 
#define Bit3 Pin_X
Aquí puedes bajarte la última versión de la librería actualizada al día 6/11/2010 versión 1.03. Ahora disponible desde Dropbox

NE555

Sin lugar a dudas el NE555 es uno de los integrados mas conocidos por cualquier hobbista de la electrónica y/o robótica que se aprecie, pero para muchos puede resultar un poco complicado comprender que es lo que realmente hace. En este artículo intentaré explicar, de una manera sencilla, no tanto funcionamiento sino la utilidad, que es lo que realmente a la mayoría de los lectores les importa.

El paso obligado a la hora en empezar a comprender el funcionamiento de cualquier integrado creo que es revisar su datasheet y por eso aquí se los dejo para que lo puedan revisar, aunque como este artículo está basado en la información que ahí figura puede salteárselo el que no entienda mucho de inglés o no sepa todavía leerlos bien. Bueno, para el que no se lo leyó (y para el que se lo leyó también) en la Figura 1 se observa el patillaje o pinout del integrado en cuestión.

Figura 1. Pinout del IC NE555.

Este integrado normalmente se define como un timer, que vendría a ser algo así como un contador, pero también se lo podría definir como un oscilador. Se sabe que un oscilador es un componente capaz de marcar el tiempo, y la forma que tiene nuestro integrado de marcarlo es por medio del paso de alto a bajo del pin salida (Output) a una determinada frecuencia.

La velocidad a la que trabaje nuestro integrado dependerá de la conexión que se le haga a los componentes externos, mas concretamente a las resistencias y capacitores que se elijan. En los ejemplos que daré a continuación se explica las dos configuraciones posibles, monoestable, que es cuando el IC trabaja como un delay, y astable, que es cuando trabaja en modo oscilador o multivibrador. Las dos modalidades se basan en un principio muy fácil de entender, la frecuencia con que oscila el integrado dependerá exclusivamente de la velocidad con que se cargue y descargue el capacitor que se le conecte. Cuando el capacitor se cargue, la salida estará en nivel alto y cuando se descargue en bajo. Pero la principal diferencia entre uno y otro es la utilización del trigger.
El primer circuito que les presento (Figura 2) es para configurar el integrado como monoestable, que significa que solo en un estado, el tiempo es estable, donde se ve que el pin 2 (Trigger) queda libre para conectarlo a una señal de control, cuando dicha señal se activa, la salida (pin 3) se mantiene un tiempo determinado en estado alto para pasar luego a estado bajo hasta un nuevo disparo del pin 2.

Figura 2. Configuración monoestable.
 
El pin 4 (Activación o Reset) se puede conectar directamente a un microcontrolador para activar/desactivar la salida de la señal, esto no es tan necesario en este modo ya que el circuito queda a la espera de una pulso bajo en el pin 2 para activar la salida, pero en el modo astable el uso de activación es fundamental para activarlo cuando se desee ya que de lo contrario en la salida estaría permanentemente la señal presente, pero eso lo veremos mas adelante. Ahora bien ya sabemos como es la conexión en teoría, del integrado, pero falta algo fundamental a la hora de armar el circuito, los valores de cada componente, estos varían dependiendo de la frecuencia que queremos obtener como se habló anteriormente. Para saber que valores usaremos, primero debemos comprender el funcionamiento del circuito monoestable.

En la Figura 3 se ve una gráfica de los valores de la salida dependiendo el estado del trigger a cada momento:

Salida respecto a Trigger Figura 3. Comportamiento del pin 3 (Output) respecto al pin 2 (Trigger) en configuración monostable.
 
Como se observa a simple vista la salida pasa a estado alto durante un cierto tiempo cada vez que se pasa de alto a bajo el pin 2. Mientras el integrado no perciba ese cambio de estado, la salida permanece a estado bajo. El tiempo que la salida permanecerá en estado alto viene determinado por la fórmula tH = 1.1 R1 C1. 

Este circuito es aplicable, por ejemplo, si queremos hacer un circuito capaz de funcionar un determinado tiempo luego de activar un pulsador. Pero como ya se dijo hay otro modo de usar el integrado y es el astable. La configuración astable se utiliza mucho en robótica ya que es totalmente válida para modular la frecuencia con que parpadea un LED infrarrojo o un altavoz, por ejemplo. Se sabe que fuentes de luz como el sol o un foco incandescente producen luz infrarroja y esta puede causar errores de lectura con fototransistores, para evitar esto, la luz del emisor es modulada y por medio de un decodificador en la recepción, la frecuencia es interpretada para obtener una detección segura.

Pero esta modulación significa alternar estados altos y bajos a un ritmo muy alto, y como se ve en el modo monoestable, solo podremos asegurar el tiempo del estado en alto, para poder crear una onda que alterne entre un estado y otro, necesitaremos configurar el NE555 en modo astable. Y para ello deberemos conectarlo como se ve en la Figura 4; Para saber a que frecuencia oscila, deberemos aplicar las fórmulas de la Tabla 1.

Figura 4. Conexión del IC NE555 en modo astable.

C1 Salida (Pin 3) Fórmula
Carga Alto t1 = 0,693 (R1 + R2) C1
Descarga Bajo t2 = 0,693 R2 C1
Tabla 1. Fórmulas para calcular los tiempos del IC NE555 en configuración astable.

El diagrama y las fórmulas nos sirven también para comprender un poco el funcionamiento del circuito. De todo esto se deduce que la corriente pasa por R1, luego por R2 cargando C1, si hojeamos el datasheet verificamos que cuando C1 se carga con 2/3 de Vcc se dispara el pin 7 (descarga - discharge) poniéndose a GND, lo que hace descargar el capacitor (de ahí su nombre) hasta 1/3 de Vcc que es cuando el pin 7 vuelve a ponerse en alta impedancia, abriendo el circuito, volviendo a cargar C1 e iniciando la secuencia nuevamente.

Ahora bien, con estos datos se ve que siempre la carga es mas lenta que la descarga, ya que la carga se hace por medio de R1 + R2 y la descarga solo con R2, lo que nos impide hacer que el circuito oscile con un duty cycle del 50%, es decir que tarde lo mismo estando a nivel bajo que en alto. Para lograrlo necesitaremos impedir que la corriente circule por R2 cuando C1 se esté cargando, ¿Cómo se hace eso? Fácil, con un diodo ubicado en paralelo a R2, nuestro archifamoso compañero, el 1N4007 (o similar) nos servirá de maravilla.

En la Figura 5 se observa como quedaría el circuito para logran un duty cycle del 50% (siempre y cuando R1 sea igual a R2).

Figura 5. Circuito para lograr que el IC NE555 oscile con un duty cycle del 50%.

Y en la tabla de fórmulas lo único que varía es que, para el cálculo de la carga, no interviene R2, por lo tanto quedaría de esta forma:

C1 Salida (Pin 3) Fórmula
Carga Alto t1 = 0,693 R1 C1
Descarga Bajo t2 = 0,693 R2 C1
Tabla 2. Fórmulas para calcular los tiempos del IC NE555 con un duty cycle del 50%.

Como se observa en el diagrama de la Figura 5, para realizar la carga, la corriente pasa por R1 y luego al tener menos resistencia por D1 "corta camino" (el camino de menor resistencia) y pasa directamente a C1. En la descarga D1 impide el paso de corriente en esa dirección lo que provoca que pase por medio de R2 por lo que si las dos resistencias son del mismo valor tardará lo mismo la descarga que la carga (en teoría, ya que la realidad nunca es igual a la teoría, pero para la práctica el error es mínimo).

Bueno, eso es todo por el momento, espero que les haya servido de ayuda y recuerden que esto es a modo de apunte, cualquier corrección o duda que tengan coméntenla y será contestada con la mayor brevedad posible. También invito al público a participar haciendo sus consultas, sugerencias y/o pedidos, por los medios pertinentes, toda colaboración será bienvenida, y no olviden que aquí todo el que quiera puede aprender y/o enseñar algo...

Puente H

Navegando por uno y otro lado, siempre me encuentro con el dilema de la gente a la hora de controlar un motor con un microcontrolador. Hay muchas soluciones en forma de integrados, pero estos son algo limitados y, hasta en algunos casos, demasiado costosos o difíciles de conseguir.

La mejor solución, sin lugar a dudas, es utilizar un puente H, pero con este tema surge otro problema en la gente, ¿como funcionan?, ¿como se arman?, ¿cuál es mejor?... para responder a todas esas preguntas, trataré de explicar aquí de una forma sencilla el funcionamiento de estos.

Se sabe que en un motor de corriente continua, el sentido de giro viene dado por la conexión que se le haga a sus bornes. Entonces para hacer que un robot vaya hacia adelante o hacia atrás, bastará con invertir la polaridad del motor que mueva sus ruedas, el problema es como hacer esto, el PIC solo puede suministrar 5v y 20mA por pin totalmente insuficiente para mover un motor, lo que se intenta lograr es poder controlar una tensión y una corriente superior que la que el integrado pueda darnos por medio de una señal que será enviada por el PIC.

En definitiva, el encargado de controlar esa tensión será el Puente H y el PIC controlará a este último.

Figura 1. Motor de corriente continua conectado a dos interruptores inversores.

Cuando conectamos un motor como en el gráfico de la figura 1, girará en un sentido u otro, o se parará según la combinación de los interruptores, el puente H hace lo mismo con la diferencia de que el control se realizará digitalmente y los interruptores mecánicos se reemplazarán por transistores PNP y NPN. De modo que se puede controlar el motor de una forma muy precisa, y con cambios giro/parada a una velocidad muy elevada, pudiendo controlar la velocidad de giro por una señal PWM (Pulse Width Modulation o Modulación por Ancho de Pulsos).

Figura 2. Puente en H. 

Si se compara la imagen de la figura 1 con el de la figura 2 se observa que es muy similar, puesto que los interruptores de la primera están reemplazados por un par de transistores, cada uno, en la segunda.

Buscando entre los datasheets encontramos el BD677 (NPN) y BD678 (PNP), que son unos transistores complementarios capaces de soportar 60V y 4A. Suficiente para mover cualquier robot de medianas dimensiones.

Entonces sabiendo esto podremos crear nuestro puente H del diagrama utilizando estos componentes:

Q1,Q2Transistores 2N2222
Q3,Q4Transistores BD678
Q5,Q6Transistores BD677
D1,D2,D3,D4Diodos 1N4007
R1,R2Resistores de 470Ω
R3,R4Resistores de 22Ω

Dos puentes H como el aquí mostrado son los encargados de mover al robot SiLMuP, que a su vez son controlados por señales PWM enviadas por un PIC16F876.

Aquí les dejo un vídeo con la demostración del Puente H funcionando, debido a la reductora del motor (es un potenciómetro con motor de equipo de audio) el movimiento es lento, pero el Puente H es apto para mover motores relativamente potentes, hasta 60V y 4A.

En el disipador hay un regulador de voltaje LM7805 y solo se incluyó en el circuito para suministrar la señal de activación avance/retroceso, se podría haber activado con la misma tensión que alimenta al Puente H pero lo incluí para demostrar que el Puente H puede ser perfectamente controlado con un PIC. También sería recomendable, en las enatradas de avance/retroceso, ponerle resistencias pull-down para que no se activen por alguna interferencia.

Controlar display de 7 segmentos con shift register

Ya vimos lo que es y como se controla un shift register, ahora veremos una de las tantas utilidades que tiene. Aquí se explicará como utilizar un shift register para controlar un display de 7 segmentos.

Para el que todavía no lo conozca, un display de 7 segmentos es un panel conformado por 7 LED que comparten entre si sus ánodos o cátodos según si sean de ánodo o cátodo común respectivamente.

A un display de ánodo común deberemos aplicar tensión positiva al pin compartido y GND a cada uno de los pines correspondientes a los segmentos que se quieran iluminar, para un display de cátodo común deberemos invertir la polaridad. La mayoría de displais poseen también un octavo segmento que normalmente es un punto o dos.

Hasta aquí una pequeña introducción para conocer un poco su funcionamiento, ahora bien, vimos que un shift register serial-paralelo “memoriza” el estado (0 o 1) de un pin (entrada) y va rotando esos valores por cada pulso de reloj para representar la secuencia completa, cuando se activa el pin Load, en unos pines asignados que tiene el integrado a tal fin.

Si a cada pin de salida (Q0-Q7) del integrado le conectamos un segmento del display, como en la figura 1, logramos encender y apagar dichos segmentos independientes unos de los otros. La principal utilidad es representar un número del 0 al 9 o una letra de la A a la F.

La ventaja de usar el shift register es que sólo necesitaremos 3 pines del microcontrolador: Clock (Amarillo), Data out (Verde) y Load (Rojo).


Figura 1. Conexión de un display de 7 segmentos a un shift register.

Si en lugar de emplear un solo integrado usamos dos (Figura 2), podremos multiplexar los datos. Utilizando el segundo integrado para seleccionar el display que deseamos encender en determinado momento, aumentaremos el numero de displais de 1 a 8 con un integrado mas.

8 display de 7 segmentos conectados a 2 shift register
Figura 2. Conexión de 8 display de 7 segmentos a 2 shift register.

Si encendemos un display, lo apagamos y luego encendemos otro, y hacemos todo esto de manera muy rápida el cerebro humano lo interpreta como si en realidad todos los display permanecieran encendidos al mismo tiempo. Jugando con esto podemos ahorrarnos muchísimos pines de nuestro PIC, por ejemplo si quisiéramos hacer un reloj que indique la hora, los minutos y los segundos, y conectáramos cada segmento a cada pin de un microcontrolador necesitaríamos uno con 42 pines solo para los displais.

Sin shift register también se puede multiplexar, pero aun así utilizaríamos demasiados pines, 7 para los segmentos y 1 para cada display, para diseñar el mismo reloj del ejemplo anterior necesitaríamos un total de 13 pines contra los 4 que se necesitan implementando los registros de desplazamiento.

Para usar los dos integrados, se conectan siguiendo el diagrama de la figura 2, el segmento A de todos los displais con la Q0 del primer integrado, los segmentos B con el Q1, los C con Q2… y así sucesivamente. La salida Q0 del segundo integrado se conecta al común del primer display, el pin Q1 al segundo, el Q2 al tercero… así hasta un máximo de 8 displais.

Para controlarlo enviaremos los bits que conforman el caracter del primer display al primer integrado y el bit correspondiente a dicho display en 0, a cada pulso de reloj, cuando hayamos enviado todos los bits se manda el pulso Load y es cuando aparece el caracter en el primer display, luego se repite la operación pero con el bit del segundo display en 0, de esa forma, y si los displais son de cátodo común, encenderán de a uno. A una alta frecuencia esto crea la ilusión de estar todos encendidos.

Utilizando dos shift registers y 4 pines del microcontrolador tendremos capacidad para controlar 8 displais, y si utilizáramos un integrado y un pin del microcontrolador más, aumentaríamos la cantidad de displais a 16.

Probador de PIC16F876

¿Cuantas veces se han visto con la necesidad de probar un firmware que han programado pero no tienen el circuito hecho para probar? Bueno lo que aquí propongo es una placa con todos los componentes para hacer trabajar al PIC, pero con la particularidad de que los puertos están disponibles para el uso que queramos darle.

El diagrama es el siguiente:

Circuito del probador de PIC16F876 (haga click para agrandar)

A simple vista se ven 5 conectores que 3 corresponden a los puertos del PIC (A,B y C) 1 para la alimentación, y el 5to par el ICSP (In Circuit Serial Programming). Para los que no sepan lo que es esto, les comento que es, como lo indica su nombre en inglés, la programación serial en circuito, o sea que se puede programar (grabar el firmware) directamente en la placa sin necesidad de extraer el integrado en ningún momento.

Aunque también se podría extraer el PIC en cualquier momento gracias a la implementación de un zócalo de 28 pines, dicho sea de paso aconsejo que cada vez que se fabrique un circuito, y sobretodo si está pensado para pruebas, que cualquier integrado que vayamos a utilizar no se suelde directamente a la placa sino que se utilicen zócalos; por económico que sea un integrado no significa que lo tengamos siempre a mano, ni sea fácil de conseguir.

Volviendo a lo que es el circuito en cuestión, es muy sencillo y lo unico que tiene de particular es que facilita la puesta en marcha del PIC, sólo con colocarle los 5volts de alimentación el PIC estaría funcionando, ya que cuenta con un cristal, también se podría utilizar varias velocidades de cristales, por medio de jumpers o zócalos.

Cuenta con protección anti inversión de polaridad, muy útil para cuando estamos haciendo pruebas, como también aislamiento de tensiones para cuando está alimentado y conjuntamente se conecta el ICSP, en tal caso una tensión no interfiere con la otra. Y está incluido el circuito de reset.
Está pensado para utilizarlo en protoboards para lo cual los pines de conexión de alimentación y puertos tendrían que ser de tira de pines acodados a 90º. Por el mismo motivo no se incluyen en el diseño resistencias de pull-up ni pull-down, se supone que el prototipo que se diseñe contendrá las resistencias necesarias.

Puede ser adaptado a cualquier modelo de PIC muy facilmente, solo basta con agragar o quitar pines a los puertos amoldandolos a las necesidades y al modelo del microcontrolador, cada uno puede pensar sus circuitos dependiendo de la necesidad que tenga...

Control de un módulo LCD con PIC y CCS C

Partiendo de saber la Conexión y funciones de un módulo LCD ahora veremos como se utiliza en la práctica. Para empezar aquí les dejo el diagrama de la conexión mas básica para poder comenzar a utilizar el display.

Conexión LCD a PIC16F84A

En el diagrama utilizamos el Pin 0 del puerto A para la señal Enable del display y el Pin 1 del mismo puerto para la señal RS.

R/W lo conectamos directamente a GND ya que en este proyecto no leeremos el estado del display en ningún momento.
El puerto B lo dedicaremos enteramente al bus de datos del LCD.

Todos los puertos que no utilizaremos, asi como el RESET del PIC, los conectamos a 5V por medio de un resistor.

Alimentamos todo a 5V por medio de una fuente que puede llegar a ser la Fuente de alimentación y cargador de baterías explicada en este blog y listo.

Pero falta algo, el programa o firmware que hace que el display haga algo... Para eso debemos crear el código, compilarlo y grabarlo en el PIC para que este lo ejecute, nosotros usaremos CCS C .

La rutina que hace esto sería esta:

#include <16f84a.h>
#use delay (clock=4000000)

#define E Pin_A0
#define RS Pin_A1

/*  Declaro las funciones antes de utilizarlas en main()
    para que sean reconocidas  */

void print(int c);
void Enviar(int8 Dato, int1 Comando);
void Iniciar_LCD(void);

//Comienzo del programa

void main(void){
   Iniciar_LCD();                // Inicio el Modulo
   print ("PICROBOT");           // Escribo PICROBOT en la pantalla
}
                                       
//Funciones

void print(int8 c){
   enviar (c,1);                 // Envio caracter c con RS a 1 (dato)
                                 // CCS C se encarga de enviar uno a uno
}                                // los caracteres contenidos en c
                                     
void Enviar(int8 Dato, int1 Comando){
   output_low(E);                // E en bajo
   output_b(Dato);               // Cargo el puerto B con Dato
   output_bit(RS,comando);       // Pongo RS en 0 o 1 dependiendo si es
                                 // un comando o dato lo que estoy
                                 // enviando
   output_high(E);               // E en alto
   delay_us(1);                  // Espero a estabilizar tensión
   output_low(E);                // E en bajo
   delay_us(40);                 // Espero 40 uS a que el LCD trabaje
                                 //
   if (Dato < 4) delay_us(1600); // Si envio un Home o un Clear display
                                 // espero otros 1600 uSegundos que
                                 // sumado a los 40 uS anteriores hacen
}                                // 1.64 mS que es lo que tardan estas
                                 // operaciones

void Iniciar_LCD(void){          //
   delay_ms(15);                 // Espero a que se estabilice
                                 // la tensión.
   
   enviar(0b00000001,0);         // Envio un CLEAR DISPLAY
                                 // (Borra la pantalla)
   
   delay_ms(5);                  // Espero 5 mS
   
   enviar(0b00111000,0);         // Envio un FUNCTION SET para bus de 8
                                 // bits, 2 lineas y caracteres de 5x7
                                 // pixeles.
   
   enviar(0b00001100,0);         // Envio un DISPLAY ON/OFF CONTROL
                                 // con pantalla encendida, Cursor
                                 //  apagado y si parpadear.
   
   enviar(0b00000110,0);         // Envio un ENTRY MODE SET con 
                                 // Incrementa cursor y desplaza cursor
}

Este programa introducido en el PIC conectado al circuito anterior hace que nuestro módulo muestre en pantalla la frase:

PICROBOT

En realidad lo único que hace es inicializar el LCD y mostrar el mensaje, se puede adaptar y hacer que muestre cualquier frase cambiando simplemente la palabra PICROBOT por lo que deseen en la línea:

print ("PICROBOT");

Hay que aclarar que para que funcione hay que respetar las comillas.

Pero si arman el circuito y graban el PIC con la rutina, verán que pueden modificar la frase, pero siempre aparecerá en la primer línea. Si volvemos a Conexión y funciones de un módulo LCD y consultamos dichas funciones, veremos que hay una que se llama SET DD RAM ADDRESS; La memoria DD RAM es la que contiene los caracteres que están en pantalla. De modo que esa función se llama Establecer la dirección de la DD RAM, o sea, que lo que hace es cambiar la posición donde se almacenará el próximo caracter, por lo tanto, lugar donde aparecerá en pantalla.

Para hacer que escriba donde queramos, antes de escribir, deberemos ejecutar un SET DD RAM ADDRESS. Continuando con nuestra rutina lo podemos hacer del siguiente modo:

void locate(x,y){
   int d=128;                // Cargo d con 128 (10000000) b7 a 1
   d+=y*64;                  // si y (linea) es 1 sumo 64 a d (11000000) b6 a 1
   d+=x;                     // a todo esto le sumo la posicion de x
   enviar (d,0);             // envio todo al display con RS a 0 (comando)
}

Entonces si, por ejemplo, queremos escribir Hola en la primer línea y Mundo en la segunda, el main() de nuestra rutina se vería así:

void main(void){
   Iniciar_LCD();             // Inicio el Módulo
   locate(6,0);               // Ubico el cursor en la columna 6
                              // de la primer línea
   print ("HOLA");            // Escribo HOLA
   locate(5,1);               // Ubico el cursor en la columna 5
                              // de la segunda línea
   print ("MUNDO");           // y escribo MUNDO
}

En un display de 16 caracteres x 2 líneas se verá centrada la frase HOLA MUNDO. Cabe destacar que antes de poder utilizar la función locate() se debe declarar mediante la sentencia:

void locate(x,y);

Luego podemos simplificar el borrado de la pantalla (CLEAR DISPLAY) con la función:

void cls(void){
   enviar (1,0);              // envio 00000001 (Clear display) 
}                             // con RS a 0 (comando)

Recuerden que CLEAR DISPLAY era 00000001 que es igual a 1 en decimal. De esta forma y previamente declarado cada vez que querramos borrar la pantalla introduciremos en el código la línea cls(); Por último en la entrada Librería para el manejo de un módulo LCD en CCS C encontrarás todas las funciones y la opción para descargarla.


Tal vez le interese: