martes, 18 de julio de 2017

Ensamblador vs. C

Hola a todos. Esta primera entrada he decidido dedicarla a comparar ensamblador con C. Para ello, tengo dos programas que hacen exactamente lo mismo, en el mismo microcontrolador (PIC16F84 o PIC16F84A).

ENUNCIADO:

La figura muestra un PIC16F84 al que estan conectados un convertidor analogico-digital ADC0801, y dos displays de 7 segmentos TDS11, mediante dos latch tipo D 74HC374:


Se desea muestrear la señal analogica a una frecuencia de 1 KHz y mostrar en los displays el valor de la conversion A/D en hexadecimal. Para la temporizacion debemos usar interrupciones.

Nota: el ADC no es mas que un conversor analogico-digital de 8 bits, que recibe una señal de entrada de tension entre 0V y Vcc y muestra una salida en funcion del valor de entrada. El 74HC374 no es mas que un conversor binario-BCD, para sacar el valor de entrada por el display de 7 segmentos. Podeis descargar la documentacion al final de esta entrada.

SOLUCION EN ENSAMBLADOR:


ENSAMBLADOR:

   LIST p=16F84A
   INCLUDE "P16F84A.INC"
 
   WTEMP   EQU    0x0C
   VALOR   EQU    0x0D
 
   ORG    0x00
   GOTO    INICIO
   ORG    0X04 ;El vector de interrupcion, que esta en la pos. de memoria 4
   GOTO    INTMR0


 
 
INICIO    bcf    STATUS,RP0 ;banco 0
   CLRF    PORTA
   CLRF    PORTB
 
   movlw   b'00000011'
   movwf   PORTA
 
   bsf    STATUS,RP0 ;banco 1
 
   clrf    TRISA ;salida
   clrf    TRISB ;salida
 
   bsf    TRISA,RA2 ;ra2=entrada
 
   movlw   b'00000001'
   movwf   OPTION_REG ;Prescaler 1:4
 
   bcf    STATUS,RP0 ;banco 0 (porque el tmr0 esta en este banco)
 
   movlw   0x05 ;250*4=1000 ciclos de instruccion
;1 ciclo de instruccion=1 micro segundo
;Valor inicial del timer a 5 para que salte a los 10ms
   movwf   TMR0
 
   movlw   b'10100000'
   movwf   INTCON
 
LOOP  
   movlw   VALOR
   andlw   0x0F ;quitamos la parte alta del numero
   call    DISPLAY
   movwf   PORTB ;parte baja al bus
 
   bsf    PORTA,RA4 ;almacenamos en el latch el valor
   bcf    PORTA,RA4
 
 
 
   swapf   VALOR,W ;dejamos la parte alta del valor
   andlw   0x0F
   call    DISPLAY
   movwf   PORTB ;parte baja al bus
 
   bsf    PORTA,RA3 ;almacenamos en el latch el valor
   bcf    PORTA,RA3
 
 
   GOTO LOOP
 
 
DISPLAY  
            addwf   PCL,F
            retlw   b'00111111'     ;valor 0
            retlw   b'00000110'     ;valor 1
            retlw   b'01011011'     ;valor 2
            retlw   b'01001111'     ;valor 3
            retlw   b'01100110'     ;valor 4
            retlw   b'01101101'     ;valor 5
            retlw   b'01111101'     ;valor 6
            retlw   b'00000111'     ;valor 7
            retlw   b'01111111'     ;valor 8
            retlw   b'01100111'     ;valor 9
   retlw   b'01110111'     ;valor A
   retlw   b'01111100'     ;valor B
   retlw   b'00111001'     ;valor C
   retlw   b'01011110'     ;valor D
   retlw   b'01111001'     ;valor E
   retlw   b'01110001'     ;valor F
 
 
INTMR0
   movwf   WTEMP ;Guardamos el W a un registro temporal (mover a registros generales)
 
   bcf    STATUS,RP0 ;banco 0
 
   bcf    PORTA,RA0 ;iniciamos conversion
   bsf    PORTA,RA0
 
CONV
   BTFSC   PORTA,RA2 ;mientras ra2 sea 1, no saltamos
   GOTO    CONV
 
   BCF    PORTA,RA1 ;iniciamos lectura
 
   BSF    STATUS,RP0 ;banco 1
   COMF    TRISB ;el comp hace el complementario (cambia unos por ceros y viceversa)
;puerto b como entrada (es otra forma de hacerlo)
 
   bcf    STATUS,RP0 ;BANCO 0
   movlw   PORTB
   movwf   VALOR
 
   BSF    PORTA,RA1 ;acabamos lectura
 
   BSF    STATUS,RP0 ;banco 1
   COMF    TRISB ;puerto b como salida
 
   BCF    STATUS,RP0 ;banco 0
   movlw   0x05
   movwf   TMR0
 
   bcf    INTCON,T0IF
 
   movlw   WTEMP
 
 
   RETFIE

 
 
    end





SOLUCION EN C:

/*

Solucion en C
Compilador utilizado: XC8

 */

//podemos usar cualquiera de las dos cabeceras siguientes:
//#include <xc.h>
#include <pic16f84a.h>

//variables globales
unsigned char VALOR=0;

//rutina de servicio a la interrupcion
//un void no tiene retorno
void interrupt rsi()
{
    //iniciamos la conversion
    RA0=0;
    RA0=1;
 
    //bucle: esperamos a que acabe la conversion
    while(RA2==1)
    {
     
    }
 
    //iniciamos la lectura
    RA1=0;
    //pueto b como entrada
    TRISB=0b11111111; //TRISB=0xFF; //TRISB=255;
    VALOR=PORTB;
    //finalizamos la lectura
    RA1=1;
    TRISB=0b00000000;
    //reseteamos TMR0
    TMR0=0x05;
    T0IF=0;
    //otra forma de hacer lo anterior:
    //INTCONbits.T0IF=0;
    return;
}

//rutina del display (entrada: dato; salida: char)
unsigned char display7s(unsigned dato)
{
    unsigned char Result;
    //aqui haremos cosas
    switch(dato)
    {
        case 0: Result=0b00111111; break; //digito 0
        case 1: Result=0b00000110; break; //digito 1
        case 2: Result=0b01011011; break; //     ;valor 2
        case 3: Result=0b01001111; break; //     ;valor 3
        case 4: Result=0b01100110; break; //     ;valor 4
        case 5: Result=0b01101101; break; //     ;valor 5
        case 6: Result=0b01111101; break; //     ;valor 6
        case 7: Result=0b00000111; break; //     ;valor 7
        case 8: Result=0b01111111; break; //     ;valor 8
        case 9: Result=0b01100111; break; //     ;valor 9
   case 10: Result=0b01110111; break; //     ;valor A
   case 11: Result=0b01111100; break; //     ;valor B
   case 12: Result=0b00111001; break; //     ;valor C
   case 13: Result=0b01011110; break; //     ;valor D
   case 14: Result=0b01111001; break; //     ;valor E
   case 15: Result=0b01110001; break; //     ;valor F
        default: Result=0; break;
    }
 
    return Result;
}

//rutina principal
void main(void) {
    //variables locales
    unsigned char aux;
 
    //configuramos el PIC
    PORTA=0b00000011;
    PORTB=0b00000000;
 
    //a partir de aqui lo he hecho yo (no fiarse)
    TRISA=0b00000100;
    TRISB=0b00000000;
 
    OPTION_REG=0b00000001;  //otra forma: OPTION_REGbits.PS0=1
    TMR0=0x05;
    INTCON=0b10100000;
 
    //loop
    while(1)
    {
        aux=VALOR;
        aux=aux&0x0F; //dejamos la parte baja del bit
        PORTB=display7s(aux);
 
        PORTAbits.RA4=1;    //almacenamos en el latch el valor
        PORTAbits.RA4=0;
 
        //parte alta
   aux=VALOR;
        aux=aux>>4;     //movemos los bits a la parte baja
        aux=aux&0x0F; //dejamos la parte baja del bit
        PORTB=display7s(aux);
 
        PORTAbits.RA3=1;    //almacenamos en el latch el valor
        PORTAbits.RA3=0;       //tambien vale RA3=0;
    }
    return;
}

//Fin del programa

Para un esquema de conexion, habria que conectarlo asi:


A primera vista, parece algo complicado de ver, ya que el simulador en el que lo he conectado tiene algunas limitaciones, pero basta con seguir el esquema del enunciado para ver como habria que conectarlo correctamente. Ademas, hay un pequeño detalle que hay que tener en cuenta a la hora de conectarlo y que a mi me dio muchos problemas. Para poder usar el pin RA4 como una salida, hay que conectarle una resistencia de pull-up (en este caso R2), ya que el PIC16F84 (o el PIC16F84A) no lo tiene incorporado por defecto.

¿ASSEMBLER O C?
Pues en mi opinion, la decision no es dificil. Para el 99% de los casos, lo recomendable seria usar C, ya que es mas rapido, facil y sirve para multitud de microcontroladores distintos. Sin embargo, si lo que quieres es aprovechar los recursos de un microcontrolador al 100%, no te queda mas remedio que programarlo en ensamblador, aunque esto no suele ser necesario casi nunca, siempre depende de lo que quieras hacer con el chip.


Enlaces de descarga de la documentacion y los programas:
https://drive.google.com/drive/folders/0B20kHfrc3NnpLWdHcDlza3JnQ3c?usp=sharing