Interrupciones en XC8

#1
Hola, alguien podría decirme la forma correcta de hacer una interrupción con el compilador XC8 en un 16F84A??

Quiero pulsar un botón (sin usar RB0/INT) y que el programa me cambie de secuencia de leds al pulsar.

Voy averiguando algo pero doy palos de ciego y estoy más que mareado, os lo agradecería.

Si de paso me podéis explicar también como hacerlo con TIMER0 y RB0/INT sería genial, pero me urge el poderlo hacer desde cualquier puerto (si lo permite el 16F84A).

No entiendo como no se anima más gente a usar el compilador de Microchip (apenas hay información en castellano), yo no tengo ni idea de programación y voy avanzando poco a poco y para empezar con "sucedáneos" creo que es mejor usar lo "oficial".

Saludos
 
#2
hola, te dejo un ejemplo usando RA0 como entrada y todo el puerto B como salida.
utiliza el timer 0 para retrasos de tiempo y control de rebotes. puse dos secuencias sencillas como ejemplo y las conexiones son las de el adjunto.

PHP:
#include <xc.h>
#include <stdint.h>

#define retraso(x) TMR0 = 0; while(TMR0 < x);
#pragma config FOSC = XT, WDTE = OFF, PWRTE = OFF, CP = OFF

bit estado_boton = 0b0;
uint8_t caso_cnt = 0;
uint8_t cnt = 0;
uint8_t sOUT1 = 0, sOUT2 = 0, sOUT3 = 0;

int main(void){
    PORTB = 0x00;
    TRISA = 0b11111;
    TRISB = 0x00;
    OPTION_REG = 0b11011111;

    while (1){
        if (!PORTAbits.RA0){
            retraso(150);
            if (!PORTAbits.RA0)  estado_boton = 0b1;
        }
        if (PORTAbits.RA0){
            retraso(150);
            if (PORTAbits.RA0 && estado_boton){
                    caso_cnt++;
                    estado_boton = 0b0;
                 }
        }
        switch (caso_cnt){
            case 0 : PORTB = 0x00; break;
            case 1 :
                sOUT1 = 0b10000000;
                for (cnt = 0; cnt < 20; cnt++){
                    PORTB = (sOUT1 | sOUT2 | sOUT3);
                    sOUT3 = sOUT2;
                    sOUT2 = sOUT1;
                    sOUT1 = (cnt < 10) ? sOUT1 >> 1: sOUT1 << 1;
                    if (cnt == 10) sOUT1 = 0b00000001;
                    retraso(250);
                }
                break;
            case 2 :   
                PORTB = 0x00;
                retraso(250);
                PORTB = 0xFF;
                retraso(250);
                break;
            default: caso_cnt = 0;
        }
    }
}
Saludos
 

Adjuntos

#3
Hola carferper, gracias por el interés.

Hubiera agradecido comentarios en el programa para comprenderlo ya que se me escapan algunas cosas. Aún así, no se si es lo que busco.

Yo simplemente quiero que el pic detecte la activación de un pulsador en RB5 y que cambie de una secuencia de luces a otra y al finalizar vuelva a la primera secuencia de luces hasta que no se vuelva a pulsar el botón.

Mi código hace la primera secuencia de leds en cuanto se programa el chip y luego pasa automáticamente a la segunda sin salir de ahí (aunque pulse el botón de reset), tanto pulse como si no el pulsador en RB5... no hace nada.

El código que llevo hecho es el siguiente:

Código:
#include <xc.h> //Librería del compilador de Microchip, en este caso el XC8.
//#include <stdint.h> // Librería interrupciones ¿?

#define _XTAL_FREQ 4000000 //Frecuencia del Cristal a 4MHz.

#pragma config FOSC=XT, WDTE=OFF, PWRTE=ON, CP=OFF // Definición Fuses del PIC:
                                                   // Osc. tipo XT, P.Guardián OFF,
                                                   // PWRTE ON para dar tiempo a que se estabilice la tensión,
                                                   // Protección de Código OFF
//#pragma interrupt_level 1
//
//
//

void interrupt pulsador(void) {  // Función llamada "pulsador" de interrupción.
                                 // Si no se especifica es high_priority.

    if (RBIF=1) {       // Si detecta cambio en RB4-RB7.

            __delay_ms(150);  // Retardo para antirrebote para el pulsador.

            if (RBIF=1) {

                __delay_ms(1000); // Retardo
                PORTB=0b00011111;
                __delay_ms(1000); // Retardo
                PORTB=0b00011011;
                __delay_ms(1000); // Retardo
                PORTB=0b00000100;
                __delay_ms(1000); // Retardo
                PORTB=0b00011000;
                __delay_ms(1000); // Retardo
                PORTB=0b00001100;
                __delay_ms(1000); // Retardo
                PORTB=0b00000110;
                __delay_ms(1000); // Retardo
                PORTB=0b00000011;
                __delay_ms(1000); // Retardo
                PORTB=0b00001010;
                __delay_ms(1000); // Retardo
                PORTB=0b00011111;
                __delay_ms(200); // Retardo
                PORTB=0b00000000;
                __delay_ms(200); // Retardo
                PORTB=0b00011111;
                __delay_ms(200); // Retardo

        INTCONbits.RBIF=0; // Se inicializa a 0 el Flag de interrupción en PUERTO B.
        
            }
        
        }
        return; // necesario ¿?
        
}



void main(void) {

    
    TRISB=0b00100000;   // Definición PUERTO B: Todos salidas menos RB5 que es entrada.
    PORTB=0;            // Salidas PUERTO B a 0 voltios.
    
    

    //HABILITAR INTERRUPCIONES:

    INTCONbits.GIE=1;   // Habilita TODAS las interrupciones. Para permitir interrupciones hay q habilitarlas
                        // tanto Globalmente como... Individualmente ¿?.

    //CONFIGURACIÓN INTERRUPCIÓN EXTERNA:

    
    //OPTION_REGbits.INTEDG=0; // Se activa la interrupción por flanco: 0=descendente, 1=ascendente.
                             // Para RB0/INT.

    INTCONbits.RBIE=1; // Se habilita detección de interrupción en PUERTO B.
                       // No se si con configurar un pin del Puerto B como entrada ya se autoconfigura.
    

    //ei ();              // Habilita las interrupciones Globales.

    //di();             // Deshabilita todas las interrupciones.
    //INTCON=0b10001001;    // El Bit 3 me parece que es para habilitar cualquier interrupción en RB
    
    while (1) {

        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00000001; // RB0 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00000010; // RB1 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00000100; // RB2 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo a 600 milisegundos.
        PORTB=0b00001000; // RB3 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00010000; // RB4 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00011111; // RB0-RB4 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00010001; // RB0 Y RB4 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00001010; // RB1 y RB3 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo a 600 milisegundos.
        PORTB=0b00000100; // RB2 a 5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.
        PORTB=0b00010101; //RB0, RB2 y RB4  5 voltios y el resto a 0 voltios.
        __delay_ms(800); // Retardo de 600 milisegundos.

        
    }


}
Comentar que no se si tengo que llamar a la función de interrupción (que he llamado pulsador) o si al ser una función de interrupción se invoca sola.

A ver si veis que tengo mal o que me falta.

Saludos!!
 
#4
hola, te aclaro algo del codigo que puse antes:

la libreria <stdint.h> hace que se pueda declarar las variables uint8_t que son parte del estandar de C99 y que se recomienda usar por motivos de portabilidad. En este caso significa "unsigned integer 8 bits".

#define retraso(x) TMR0 = 0; while(TMR0 < x);

es un macro que hace mas facil escribir el programa, en realidad hace que se escriban menos lineas de codigo. y trabaja similarmente al __delay_ms(). La diferencia es que no utiliza espacio en la memoria sino mas bien usa el Timer 0 para control de tiempos.

El Timer 0 se configura con: OPTION_REG = 0b11011111;

El programa empieza con todos los LEDs apagados. Al pulsar el boton conectado al pin RA0 se ejecuta una secuencia de encendido/apagado. Si pulsas nuevamente, se ejecuta otra secuencia. Y asi sucesivamente. Estas secuencias puedes cambiarlas segun tus requerimientos.

Este segmento:
PHP:
        if (!PORTAbits.RA0){ 
            retraso(150); 
            if (!PORTAbits.RA0)  estado_boton = 0b1; 
        } 
        if (PORTAbits.RA0){ 
            retraso(150); 
            if (PORTAbits.RA0 && estado_boton){ 
                    caso_cnt++; 
                    estado_boton = 0b0; 
                 } 
        }
elimina rebotes y determina que secuencia se ejecuta. Esto se hace simplemente incrementando el contador "caso_cnt".

Finalmente, la estructura switch es como sigue:
PHP:
       switch (caso_cnt){ 
            case 0 : 			/* no pulsaciones */
		/*secuencia 0 */
		 break; 
            case 1 : 			/* 1 pulsacion */
		/*secuencia 1 */ 
		break; 
            case 2 :   			/* otra pulsacion */ 
                /*secuencia 2 */ 
		break; 
            default: caso_cnt = 0;      /* repite todo */ 
        }
Como ves este programa no utiliza interrupciones. Si los tiempos de espera entre encendido y apagado de LEDs en una secuencia es muy grande, va a presentar problemas y necesariamente te conviene usar interrupciones.

Lo mas facil es usar RB0 como entrada y generar una interrupcion por flanco de subida. Por ejemplo y en forma muy simplificada se puede escribir:

PHP:
#include <xc.h>
#include <stdint.h>

#define _XTAL_FREQ 4000000
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))

#pragma config FOSC = XT, WDTE = OFF, PWRTE = OFF, CP = OFF

uint8_t caso_cnt = 0;

void main(void) {
    TRISB = 0b00000001;
    PORTB = 0;

    OPTION_REGbits.nRBPU = 0b0;   // activa "weak pull-ups"
    OPTION_REGbits.INTEDG = 0b1;  // flanco de subida en RB0
    
    INTCONbits.INTE = 0b1;  // habilita interrupciones en RB0
    ei();	// habilita interrupciones 

    while (1) {
       switch (caso_cnt){
            case 0 : 
                PORTB = 0x00;  // primera secuencia 
                break;
            case 1 :
                PORTB = ~PORTB;  // segunda secuencia
                __delay_ms(1000);
                break;
            case 2:
                PORTB = 0xFF;  // tercera secuencia
                break;
           case 3:
               caso_cnt = 0;  // repite secuencias desde case 0
       }
    }
}

void interrupt isr(void){  // rutina de servicio de interrupciones
    INTCONbits.INTF = 0b0;  // habilita interrupciones RB0 nuevamente
    caso_cnt++;             // determina que secuencia se ejecuta
    __delay_ms(10);         // retraso para esperar rebotes
}
Para interrupciones usando cambios en RB4 a RB7, debes hacer el control de rebotes mediante hardware y el pulso debe ser al menos uno o dos ciclos. Es mas complejo por eso rara vez se usa con botones pulsadores.

Saludos
 
#5
carferper, gracias nuevamente por la explicación y por los criterios de programación que me comentas.

He podido entenderlo algo mejor, ten en cuenta que justo estoy empezando. Aún así tu programa es mucho más de lo que necesito.

Finalmente me di cuenta que tenía un error tonto de MPLAB y no compilaba lo que tenía que compilar jeje.

Finalmente he conseguido crear interrupciones externas por RB0/INT y por puerto B (RB4-RB7), concretamente por RB5.

Adjunto el programa con interrupción externa en RB5 por si pudiera servir de utilidad/comprensión:


PHP:
/* 
 * File:   main.c
 * Author: nimio
 *
 * Programa para encender 2 secuencias de 5 Leds en el Puerto B (RB0-RB4)
 * Con pulsador haciendo uso de las interrupciones en RB5.
 *
 * Created on 3 de noviembre de 2012, 21:20
 */

#include <xc.h> //Librería del compilador de Microchip, en este caso el XC8.

#define _XTAL_FREQ 4000000 //Frecuencia del Cristal a 4MHz.

#pragma config FOSC=XT, WDTE=OFF, PWRTE=ON, CP=OFF // Definición Fuses del PIC:
                                                   // Osc. tipo XT, P.Guardián OFF,
                                                   // PWRTE ON para dar tiempo a que se estabilice la tensión,
                                                   // Protección de Código OFF
//
//
//
//

void interrupt pulsador(void) {  // Función llamada "pulsador" de interrupción.
                                 // Si no se especifica es high_priority.
    //INTCONbits.GIE=0; // Se deshabilitan las interrupciones Globales para que no se
                        // produzca otra interrupción mientras se atiende esta.

    if (RBIF) {       // Si detecta cambio en RB4-RB7.

            __delay_ms(150);  // Retardo para antirrebote para el pulsador.

            if (RBIF) {

                __delay_ms(2000); // Retardo
                PORTB=0b00011111;
                __delay_ms(2000); // Retardo
                PORTB=0b00000000;
                __delay_ms(100); // Retardo
                PORTB=0b00000001;
                __delay_ms(100); // Retardo
                PORTB=0b00000010;
                __delay_ms(100); // Retardo
                PORTB=0b00000100;
                __delay_ms(100); // Retardo
                PORTB=0b00001000;
                __delay_ms(100); // Retardo
                PORTB=0b00010000;
                __delay_ms(100); // Retardo
                PORTB=0b00000000;
                __delay_ms(100); // Retardo
                PORTB=0b00011111;
                __delay_ms(200); // Retardo
                PORTB=0b00000000;
                __delay_ms(200); // Retardo
                PORTB=0b00011111;
                __delay_ms(200); // Retardo

                INTCONbits.RBIF=0; // Se inicializa a 0 el Flag de interrupción en PUERTO B.
        
            }
        
        }      
}



void main(void) {

    
    TRISB=0b00100000;   // Definición PUERTO B: Todos salidas menos RB5 que es entrada.
    PORTB=0;            // Salidas PUERTO B a 0 voltios.

    INTCONbits.RBIE=1; // Se habilita detección de interrupción en PUERTO B.
                       // No se si con configurar un pin del Puerto B como entrada ya se autoconfigura.
    INTCONbits.RBIF=0; // Se pone a 0 el Flag indicador de interrupción en RB4-RB7.
                       // Es conveniente ponerlo a 0 ya que RB4-RB7 sólo detecta cambio de estado
                       // y puede dar problemas si no se pone.

    INTCONbits.GIE=1;   // Habilita TODAS las interrupciones. Para permitir interrupciones hay q habilitarlas
                        // tanto Globalmente como... Individualmente ¿?.
    
    while (1) {

        __delay_ms(800); // Retardo
        PORTB=0b00000000; 
        __delay_ms(800); // Retardo
        PORTB=0b00011111; 
        __delay_ms(800); // Retardo
        PORTB=0b00000000;
        __delay_ms(800); // Retardo
        PORTB=0b00011111;
        __delay_ms(800); // Retardo 
        PORTB=0b00000000; 
        __delay_ms(800); // Retardo
        PORTB=0b00011111; 
        __delay_ms(800); // Retardo
        PORTB=0b00000000;
        __delay_ms(800); // Retardo
        PORTB=0b00011111;
        __delay_ms(800); // Retardo 
        PORTB=0b00000000; 
        __delay_ms(800); // Retardo
        PORTB=0b00011111;
        __delay_ms(800); // Retardo

        
    }


}
Ahora supongo que probaré suerte con el TIMER0

Muy agradecido por todo.

Saludos
 
#6
Hola en todos los ejemplos que encuentro por san google solo veo que usen una sola interrupcion para el MPLAB XC8 y se usa el formato :

void interrupt ISR(void) {
......
}

en el caso de tener que hacer 2 interrupciones como se llama a cada una ? se le puede agregar un nombre ? por ejemplo si quisiera hacer 2 interrupciones una por TMR0 y otra Externa se puede poner asi ?

void TMR0_interrupt ISR(void) {
.....
}


void EXT_interrupt ISR(void) {
.......
}

a esto te lo reconoce el compilador ? o no se puede darle otro nombre y tenemos que luego de realizada la interrupcion determinar cual fue ?

gracias
 
#7
Hola.
void interrupt... es un calificador (palabra privada) por lo que no puedes poner otros textos por delante.
Es simple.
Para los PIC que tienen un vector de interrupción como los 16F, solo es necesario una función de interrupción e internamente se determina verificando los flags...

Ejemplo de la documentación del XC8.
Código:
void interrupt myIsr(void)
{
// only process timer-triggered interrupts
if([B]INTCONbits.TMR0IE[/B] && [B]INTCONbits.TMR0IF[/B]) {
portValue++;
INTCONbits.TMR0IF = 0; // clear this interrupt condition
}
}
Para PICs de dos a más vectores de interrupción, se determina la dirección de interrupción (generalmente por defeto pero se puede modificar) y las prioridades de interrupción (por cada interrupión que se quiera usar).

Ejemplo 18F, interrupción de baja prioridad, vector 0x18 por defecto 'creo'.
Código:
void [B]interrupt low_priority[/B] tc_clr(void) {
if ([B]TMR1IE && TMR1IF[/B]) {
TMR1IF=0;
tick_count = 0;
return;
}
// process any other low priority sources here
}
Saludos
 
#8
gracias by Axel tenia la duda porque en el comppilador CCS le ponia diferentes nombres a las interrupciones y entraba a la adecuada, aca parece que hay que chequear los flags :( , me parece un punto a favor para seguir usando el CCS, aunque me han dicho que el MPLAB XC8 es mejor
 
#9
Al final es lo mismo, los vectores de interrupción son los mismos solo que CCS tiene esa verificación internamente, de ese modo cuando corresponde una función de interrupción declarado por el usuario, internamente compara y salta al correspondiente.
XC8 es totalmente bajo el estandard ANSI C, así no hay nada que hacer... y en cierto modo se tiene mayor control del PIC... claro eso equivale a escribir un poco más :D .
 
#10
hola expertos !

estuve buscando por el foro y por sanGoogle algun ejemplo para programar el ADC con interrupciones en MPLAB XC8 y lo unico que encontre fueron ejemplos pero sin interrupciones
si alguien tiene algun ejemplo que me pueda salvar el pellejo?

sobre todo para el pic 16f887 que es con el que estoy aprendiendo a usar el XC8, gracias!
 
#11
Buenas amigos,

vengo con un problema en un ejemplo sencillo de lo que sería el uso de las interrupciones utilizando el compilador XC8 en el entorno MPLABX de Microchip. Estoy utilizando el PIC16F876A también de Microchip.

Pues bien, lo que pretendo es cambiar el estado de un LED (D1) cada vez que se produzca una interrupción externa (RB0/INT). Mientras tanto el en el programa principal un LED (D2) se prende de manera intermitente. De esta forma cuando se produzca una interrupción D1 pasará a estado bajo si se encontraba en estado alto y a alto si se encontraba en estado bajo. Gracias al uso de la interrupción en ningún momento el diodo D2 dejará de parpadear puesto que la rutina de la interrupción se realiza de manera independiente a la rutina del programa principal.

Aquí os dejo el código del programa, y a continuación comentaré los resultados que obtengo:

PHP:
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "FUSES.h"
#include <stdint.h>

#define _XTAL_FREQ 4000000



int main (void)
{
   
    TRISB = 0b00000001;
    PORTB =0;
    TRISA = 0;
    


    INTCON = 0;                         // Limpiamos el registro INTCON
    INTCONbits.INTE = 1;            // Habilitamos la Int. externa hablitando el bit INTE
    INTCONbits.GIE = 1;             // Hablitamos la Int. global habilitando el bit GIE
    OPTION_REGbits.INTEDG = 0;     // Configuramos el flanco de alto a bajo para la interrupción externa


    while(1)
    {
        PORTAbits.RA1=0;
        __delay_ms(500);
        PORTAbits.RA1=1;
        __delay_ms(500);
    }

    return 0;
}

void interrupt isr (void)
{
  
    if(INTCONbits.INTF)
    {
        if(PORTAbits.RA0)
        {
            PORTAbits.RA0=0;
        }
        else PORTAbits.RA0 = 1;
    }

    INTCONbits.INTF=0;
    __delay_ms(10);

}
Pues bien, simulando este código en el circuito de la imagen adjunta con PROTEUS, observo como efectivamente D2 parpadea intermitentemente pero al pulsar el botón y forzar así la interrupción por RB0 el LED D1 sólo se prende si D2 está apagado y no mantiene el estado, es decir, una vez se enciende D2, D1 se apaga.

Me gustaría que le echaran un vistazo al código y me dijeran si ven algún fallo que a mi se me escapa.

Otra duda que tiene que ver con el compilador XC8:
He observado en foros y videos de youtube como gente utiliza comandos en XC8 para prender un led de manera intermitente y les funciona perfectamente, como por ejemplo:

PHP:
PORTAbits.RA0 =! PORTAbits.RA0;
PORTAbits.RA0 = ~PORTAbits.RA0;
Pues bien, si you utilizo estos comandos el programa compila pero no realiza lo que quiero, es decir, no prende el led de manera intermitente y tengo que usar la manera tradicional.

Algo similar me ocurre con los registros:

PHP:
ANSEL = 0;
ANSELH =0;
El programa me da error al compilar.

No se si todos estos errores se deben a nuevas actualizaciones o son error mío por algo que se me escapa. Me gustaría que me ayudaran tanto en el problema principal que os he expuesto y en las dudas que tengo acerca de dichos comandos. Gracias y un saludo a todos.
 

Adjuntos

#12
Galix dijo:
... El programa me da error al compilar.
¿Que error? (copia y pega la salida de la ventana de compilación).

PORTAbits.RA0 =! PORTAbits.RA0;

Me parece medio raro, ! es el operador lógico not, "!=" o "!", sí se usa en if para comparar por distinto:

if(variable!= 1)
//hacer algo si variable no es igual a 1
else
//hacer otra cosa si variable = 1

Lo anterior es operativamente igual a

if(!(variable ==1))

PORTAbits.RA0 = ~PORTAbits.RA0;

~ es el operador complemento. Invierte unos por ceros y viceversa.
Dada una variable a = 0b01010011 será ~a = 0b10101100

El operador ~ puede andar para variables enteras, pero desconozco si se puede aplicar a campos de bits (creo que eso esta fuera del C estandar). Podes probar haciendo una xor:
PORTAbits.RA0 ^= PORTAbits.RA0;

Por ese tipo de dilemas hace tiempo uso máscara de bits en vez de campos de bits...

Otra cosa: no se como manejará las cosas elmplabx ide, pero ¿no debería haber arriba de todo un

#include <PIC16F876A.h>

o algo así?. ¿O al configurar el proyecto ya se le indica el micro utilizado?
 
#14
El microcontrolador PIC16F876A no tiene el registro ANSEL ni ANSELH.

Te recomiendo que quites

__delay_ms(10)

de la interrupción, mientras haces pruebas. Sé que está puesto para evitar rebotes, pero para hacer pruebas con el Proteus, te vale.
 
#15
Muchas gracias por tu respuesta Ardogan y JoaquinFerrero, ahora entiendo por qué no me funcionaban esos comandos. Tengo una pregunta Ardogan: A qué te refieres con máscaras de bits en lugar de campos de bits?
y en referencia a tu duda acerca del #include<16...>, el PIC ya se elige al crear el proyecto y no hace falta incluirlo.

JoaquinFerrero, he quitado el delay pero no se ha solucionado el problema. No sé exactamente el por qué la interrupción no funciona correctamente. Cuando fuerzo la interrupción el LED no cambia su estado permanentemente, sólo de forma momentánea y nunca coinciden los dos encendidos a la vez.
 
#16
Según he leído en los foros de Microchip, hay que indicar que el PORTA sea digital, no analógico. Eso podría influir en que una patilla afecta a la otra (no sé por qué, pero eso es lo que dice).

PORTAbits.RA1 = !PORTAbits.RA1;

Lo que hace esa línea es aplicar el operador !, que es la negación lógica del argumento, así que lo que hace es, efectivamente, invertir el bit de RA1, y asignar el resultado al propio RA1.
 
#17
JoaquinFerrero, efectivamente el problema venía de no declarar que el puerto A era digital. He cambiado de puerto al C y funciona perfectamente, tanto el programa como el comando:

PORTCbits.RC0 = !PORTCbits.RC0;

Sabed que si quereis usar dicho comando en el puerto A debéis declarar que el puerto es digital, si no, tendréis problemas. Muchas gracias por vuestra ayuda. Un saludo a todos.
 
#18
Ups... respondí mal lo anterior, con
PORTAbits.RA0 ^= PORTAbits.RA0;
no sirve para conmutar la salida (más bien siempre va a fijarla en cero).

Y ese error lo cometí por pensar en máscara de bits.
Máscara de bits es una forma alternativa de trabajar con bits individuales sin definir estructuras. Antiguamente un compilador al encontrarse con una línea como la de arriba generaba una cantidad de código innecesariamente grande (en assembler hace un shift, luego máscara, luego escribe...)
¿Entonces, como sería conmutar la salida con máscara de bits?. En algún lugar se define
#define BIT0 1 //o 0x01, o 0b00000001
#define BIT1 2 //o 0x02, o 0b00000010
#define BIT2 4 //o 0x04, o 0b00000100
...
ahora "está de moda" poner
#define BIT0 (1U<<0)
#define BIT1 (1U<<1)
#define BIT2 (1U<<2)
...

Luego en el programa se puede escribir
#define RA0 BIT0
#define RA1 BIT1
#define RA2 BIT2

PORTA |= RA0; //salida RA0 = 1, los otros bits no se alteran
PORTA &= ~RA0; //salida RA0 = 0
PORTA ^= RA0; // RA0 = ~RA0 conmutar

¿Por que algunos preferimos máscara de bits y no campos de bits? (bitmask vs bitfield).

  • Porque las operaciones sobre campo de bits no están bien definidas en C estandar. ¿Cual estandar? no recuerdo, pero cuando lo busqué decía que era "implementation defined" eso significa que cada compilar lo interpreta a su manera, con lo que el código escrito corre riesgo de no ser portable
  • Con máscara de bits puede operar sobre múltiples pines a la vez. Si quiero conmutar tres salidas al mismo tiempo hago:
    PORTA ^= (RA0 + RA1 + RA2);
    en vez de
    PORTAbits.RA0 = ~PORTAbits.RA0;
    PORTAbits.RA1 = ~PORTAbits.RA1;
    PORTAbits.RA2 = ~PORTAbits.RA2;
  • Código más eficiente: depende... se supone que si el compilador te lo da el fabricante entonces tiene la inteligencia para optimizar las 3 líneas de arriba como 1 sola... pero eso depende del fabricante
  • Puede que no pase al principio por costumbre, pero al usar máscara de bits el código me parece más amigable y breve => subjetivo, cada cual dirá
 
Última edición:
#19
Muchas gracias Ardogan, no tenía ni idea de todo este tema de la máscara de bits.
Voy a investigar sobre el tema y lo empezaré a usar a partir de ahora en mis proyectos. Gracias!
 

Temas similares

Arriba