jueves, 16 de mayo de 2013

74HC595. y un poco de codigo para un PIC.



--- Introducción ---

Muchas veces tenemos un microcontrolador que anda perfecto para una tarea pero lamentablemente no posee la cantidad de pines que uno necesita.
Una solución es comprar un microcontrolador con mas pines. Esta solución muchas veces presenta el problema de que la diferencia en precio es grande. Otras veces uno puede estar experimentando en fase de aprender y por eso, como yo, se decanta por otro método.

En mi caso estoy trabajando con un PIC12f675 el cual posee solo 5 pines I/O y 1 que solo es entrada. Como se ve la cantidad de pines disponible no es mucha.

En el circuito que uso necesito disponer de 8 salidas para comandar una red DAC R-2R. Acá es donde generalmente uno de afuera diría: usa un PIC16f628 o, si necesitas un ADC, un PIC16F88.

Supongamos ahora, para el que aconseja eso, que mi circuito es un POV LED que tiene que comandar 36 LEDS. ¿ Irse a un PIC32 ? ¿ un FPGA ?.

Es acá donde un poco de conocimiento sobre los expansores de puertos puede ser útil y es a lo que apunta esta guía.


---- El registro de desplazamiento 74HC595 ---

Como se sospecha usare el registro de desplazamiento 74HC595 el cual posee entrada serial y salida paralela.


El diagrama parece complicado pero no es tan así, veremos que es mas fácil de manejar de lo que parece.

Tenemos:

1 Linea de entrada de datos serial.
4 Lineas de control.
1 Linea para hacer cascadas (se pueden conectar "infinitos" circuitos).
8 Salidas de datos.

Pin a pin que hace cada cosa:

PIN desde el 1 al 7 y el 15: Empezando por lo mas fácil estos ocho pines serán por donde uno obtendrá la salida paralela. Acá se conectaran los LEDs, motores, circuitos, etc, que uno quiere comandar.
El pin 15 sera el de menos peso -LSB- y el 7 el de mayor peso o -MSB- (esto para tener en cuanta cuando se envíe los datos serialmente, luego debido a que es uno el que controla que dato aparece en cada salida el dato de menor peso o mayor peso podrá ser cualquiera).

PIN 10: Siguiendo por lo fácil, el pin 10 es el de reset. Este pin resetea la cuenta que lleva el dispositivo en el desplazamiento como así también limpia las salidas que pasaran a 0.
En el caso de no disponer de los suficientes pines en el micro habrá que poner este pin  (pin 10) a Vcc para dejar al registro sin resetear.

PIN 13: Habilitador de salidas. Este pin "conecta" las salidas. En caso de que este a Vcc -salidas desconectadas- las mismas estarán flotantes o también llamado en HI-Z alta impedancia.
En caso de conectarlo a GND las salidas estarán "conectadas" con lo que ahora los pines están a 0 o 1 dependiendo de lo que se le haya enviado al registro a través del micro.
Como en el caso anterior si no se dispone de los suficientes pines en el micro este pin puede conectarse permanentemente a Vcc y así dejar las salidas permanentemente conectadas.

PIN14: Entrada serial de datos. Por este pin ingresan, uno a uno, los bits que compondrán la salida. Para validar y guardar cada bit habrá que hacer uso del siguiente pin:

PIN11: Pulsos de reloj para validar los bits de entrada. Al recibir este pin un flanco de subida desplaza el contenido del registro hacia la derecha almacenando el valor en el bit 0 del registro de desplazamiento.

PIN12: Una vez se haya enviado los 8 bit que compondrán la salida, se envía un pulso a este pin para que en el flanco de subida pase todo lo que este guardado en el registro de desplazamiento hacia las salidas (Registro de almacenamiento). La salida permanecerá inalterable aun cuando se vuelva a ingresar datos y solo cambiara cuando, mediante este pin (pin 12) se vuelva a dar un pulso para actualizar las salidas con el segundo valor que se cargo.

PIN9: Por ultimo, como dijimos que se pueden colocar mas registros en cascada el micro deberá enviar no ya 8 bits sino 16 bits (por ejemplo si tenemos solo 2 registros en cascada).
Al ingresar el 9no bit, el primero que se ingreso saldrá por este pin (pin 9) y entrara al pin 14 del siguiente registro para acumularse ahí.
Si es complicado de entender supongase lo siguiente:  hay una mesa en donde entran 4 cajas; coloco una caja, luego y empujando a esta (desplazándola) coloco otra y así hasta que en la mesa haya 4 cajas. Ahora si coloco otra caja, al no entrar mas, la de la punta caerá al suelo perdiéndose.
En una cascada el pin 9 vendría a ser un puente por donde se deslizaría esta caja que en el otro caso se perdería e iría a parar a una segunda mesa. Obviamente cuando coloque 8 cajas terminare con las dos mesas llenas con 4 cajas cada una (osea dos registros de desplazamiento de 4 bits llenos y listo para transferir ese contenido a las salidas).

En el caso de una cascada todos los pines 10, 11, 12 y 13 estarían conectados a los respectivos pines 10, 11, 12 y 13 de los otros registros mientras que el pin 9 del primer registro estaría conectado al pin 14 del segundo, a su vez el pin 9 del segundo estaría conectado al pin 14 del tercero y así sucesivamente.


--- Código ASM para PIC y algo de optimización ---


El código para manejar un 74HC595 no es tan difícil:

Envia595
        movlw    0x08
        movwf    Conta        ; Cargamos un contador para contar los 8 bits.

A1
        bcf    GPIO, DS
        btfsc    Dato, 7
        bsf    GPIO, DS    ; Ponemos en la salida el bit correspondiente MSB primero.

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP    ; Damos un pulso para validar el bit que acabamos de colocar.
       
        rlf    Dato, F        ; Desplazo para colocar el siguiente bit a sacar.

        decfsz    Conta, f    ; Compruebo si ya saque los 8 bits.
        goto    A1   

        bsf    GPIO, STCP
        bcf    GPIO, STCP    ; Doy un pulso para pasar los 8 bit almacenados a la salida.

        return

Ahora bien, hagamos la cuenta de cuanto tardara en enviar los 8 bits suponiendo un tiempo de ejecución de cada instrucción de 1us (para hacer cálculos fáciles). Dado que es una función tengo:

1. 2us para ir, 2us para volver = 4us.
2. Cargar "Conta" con 8 = 2us.
3. Leer que valor es el bit y sacarlo = 3us.
4. Darle el pulso al registro para almacenar el bit = 2us.
5. Rotar el dato = 1us
6. Comprobar si ya se enviaron los 8 bits = 3us.
7. Dado que del paso 3 al 6 lo tengo que hacer 8 veces seria (3+2+1+3) * 8 = 72us
8. Por ultimo darle el pulso para pasar los datos guardados en el registro a las salidas = 2us.

Ahora si hacemos cálculos nos da que todo eso tarda: 4 + 2 + 72 + 2 = 80us para enviar un byte. O, lo que es lo mismo, actualizar la salida unas 12500 veces por segundo... No me gusta... MMM... me pregunto si se podrá hacer de otra forma... Veamos:

Envia595
        bcf    GPIO, DS
        btfsc  Dato, 7
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bcf    GPIO, DS
        btfsc  Dato, 6
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bcf    GPIO, DS
        btfsc  Dato, 5
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bcf    GPIO, DS
        btfsc  Dato, 4
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bcf    GPIO, DS
        btfsc  Dato, 3
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bcf    GPIO, DS
        btfsc  Dato, 2
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bcf    GPIO, DS
        btfsc  Dato, 1
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bcf    GPIO, DS
        btfsc  Dato, 0
        bsf    GPIO, DS

        bsf    GPIO, SHCP
        bcf    GPIO, SHCP
       
        bsf    GPIO, STCP        ; Pasa el valor alamacenado a las salidas.
        bcf    GPIO, STCP

        return

Ahora bien, alguien que vea el código podrá pensar que tarda mas pero es lo mismo que preguntar: ¿ que vale mas un billete de 10 pesos o 10 de 1 peso ?. Por un lado tiene el problema de que ocupa mas código y cuando estamos apretados ya con el resto del código quizás no sea viable usarlo, pero en mi caso prefiero optimizar velocidad a código, así que hagamos los cálculos a ver cuanto tarda en enviar los 8 bits:

1. Como también es una función tendré 2us en llamarla y 2us en volver de ella = 4us.
2. Enviar un bit al pin del micro 3us.
3. Enviar el pulso al registro para validarlo = 2us.
4. Como se ve ese pedazo de código (paso 2 y 3) se repite 8 veces para enviar los 8 bits por lo que tardara (3 + 2) * 8 = 40us.
5. Enviar el pulso para pasar los datos guardados en el registro a las salidas = 2us.

Si hacemos las cuentas, en este caso, para enviar los 8 bits, tardara 4 + 40 + 2 = 46us o lo que es lo mismo, actualizara la salida cada 21739 veces... Pequeña diferencia ¿ No ?


--- Conclusión ---

Como se ve no es difícil usar el registro de desplazamiento y se obtienen varias ventajas.

Por el lado del software vimos que en cierto punto uno debe pararse y evaluar que es lo que necesita: ¿ Optimizar código ? u ¿ Optimizar velocidad ? es acá cuando saber un poco de ensamblador puede ser de una gran ventaja ya que un compilador no importa si fuese C, basic, pascal, habrá terminado con la primera aproximación que como vimos es la mas lenta. Desde luego los compiladores poseen opciones para incrustar pedazos de código en ensamblador y es ahí donde sobresaldremos si tenemos los conocimientos necesarios al usar las herramientas en tareas finas.

También hay que tener ne cuenta que si bien se pueden colocar "infinitos" registros en cascada para "llenarlos" se tardara "infinito" tiempo xD. La cuenta es fácil, en una cascada de X registros se tardara X veces lo que se tarda en cargar un solo registro.
Se puede pensar que con cargar mas rápido los registros se puede obtener mejores tiempo y eso es cierto hasta un determinado punto ya que el integrado 74HC595 tiene un limite de funcionamiento de 100Mhz (de todas formas, como se ve, es bastante rápido).

Saludos.

2 comentarios:

  1. Podrías subir un programa .asm completo.

    ResponderEliminar
  2. Hola.

    No es tan dificil usar el pedazo de codigo ya que es una funcion.

    1. Configuro el micro. (fuses, pines, reloj, etc).
    2. Limpio los pines de salida.

    bcf GPIO, DS
    bcf GPIO, SHCP
    bcf GPIO, STCP

    3. Cargo el registro "Dato" con el valor que quiero sacar .

    movlw 0x3F ; O el valor que se quiera sacar.
    movwf Dato

    3. Llamo a la funcion.

    call Envia595

    4. Sigue el resto del programa.

    Solo hay que pegar uno de los dos trozo de codigo arriba (el que se ajuste a lo que necesitas -poco codigo pero lento- o -mucho codigo pero rapido-) en algun lado del programa.

    ResponderEliminar