dsPIC33E - El indicador de interrupción maestra I2C nunca se establece

0

Estoy intentando diseñar un programa que sondea un reloj en tiempo real (DS1307 +) cada segundo a través de I2C. El microcontrolador utilizado es dsPIC33EP64GS506. Utilizo un indicador de interrupción maestra para esperar hasta que finalice una tarea (condición de inicio o detención, envío de bytes, etc.). Sin embargo, parece que el indicador de interrupción maestra nunca se establece. Aquí hay un ejemplo mínimo (que no funciona) (al final de la publicación). Pasé por varias notas de aplicación, hojas de datos, etc., desafortunadamente, no logré resolver el problema.

El programa está bloqueado en la primera llamada de la función I2C1_Wait_While_Busy() , que utiliza un indicador de interrupción maestra. Esta función se usa primero después de enviar una condición de ARRANQUE. Sin embargo, hay otra forma de verificar si la condición de INICIO finalizó:

while (I2C1CON1bits.SEN==1);

ya que el hardware borra el bit SEN después de que finaliza la condición de INICIO Si uso esto, entonces el programa salta a la siguiente línea, lo que indica que la condición de inicio finalizó independientemente del indicador de interrupción principal. En otras palabras, el programa está bloqueado en la próxima llamada de la función I2C1_Wait_While_Busy() .

#include <xc.h>

// CPU operates at 120 MHz (60 MIPS)
#define FCY     60000000UL
#include <libpic30.h>

/* INITIALIZATION ROUTINES */

// Primary oscillator configuration
_FOSCSEL(FNOSC_FRC & IESO_OFF);
_FOSC(PLLKEN_ON & FCKSM_CSECMD & OSCIOFNC_OFF & POSCMD_XT);

// ALTI2C1_ON - I2C1 mapped to ASDA1/ASCL1 instead of SDA1/SCL1
// ALTI2C2_ON - I2C2 mapped to ASDA2/ASCL2 instead of SDA2/SCL2
// DBCC (not used)
_FDEVOPT(ALTI2C1_ON & ALTI2C2_ON);

void Init_Oscillator(void) {

    /* Configure primary oscillator */
    // FOSC = FIN/N1*M/N2

    // FPLLI = FIN/N1
    // N1 = PLLPRE+2, N1 in [2,33]
    short N1 = 2;
    CLKDIVbits.PLLPRE = N1-2; // N1=PLLPRE+2=2

    // FVCO = FPLLI*M
    // M = PLLDIV+2, M in [2,513]
    short M = 48;
    PLLFBD = M-2;

    // FPLLO = FVCO/N2 (FPLLO=FOSC, FCY=FOSC/2)
    // N2 = 2*(PLLPOST+1), N2 is {2,4,8}
    short N2 = 2; // 2, 4 or 8
    CLKDIVbits.PLLPOST = N2/2-1;

    /* Initiate Clock Switch */

    // Primary Oscillator with PLL (XTPLL, HSPLL, ECPLL)
    __builtin_write_OSCCONH(0x03); // 0b011 -> NOSC<2:0> (OSCCON<10:8>)

    // Request oscillator switch to selection specified by the NOSC<2:0> bits
    __builtin_write_OSCCONL(OSCCON | 0x01); // 0b1 -> OSWEN<0> (OSCCON<0>)

    // Wait for Clock switch to occur
    // Current Oscillator Selection bits (read-only): COSC<2:0> (OSCCON<15:13>)
    while (OSCCONbits.COSC!=0b011);

    // Wait for PLL to lock
    // PLL Lock Status bit (read-only): LOCK<0> (OSCCON<5>)
    while (OSCCONbits.LOCK!=1);

    /* Configure auxiliary oscillator */
    // 120 MHz for proper PWM and ADC operation
    // 7.37 MHz * 16 = 117.92 MHz

    // Configure source to fast RC with APLL
    ACLKCONbits.ASRCSEL = 0; // Don't use primary oscillator
    ACLKCONbits.FRCSEL = 1; // Use fast RC oscillator as source
    ACLKCONbits.SELACLK = 1; // Don't use primary PLL (FVCO)
    ACLKCONbits.APSTSCLR = 0b111; // Divide-by-1 for PWM
    ACLKCONbits.ENAPLL = 1; // Enable 16x APLL

    // Wait for auxiliary PLL to lock
    while (ACLKCONbits.APLLCK!=1);

}

void Init_Ports(void) {

    // Set all ports to digital
    ANSELA = 0x00;
    ANSELB = 0x00;
    ANSELC = 0x00;
    ANSELD = 0x00;

    // Set all ports to low
    LATA = 0x00;
    LATB = 0x00;
    LATC = 0x00;
    LATD = 0x00;

}

void Init_I2C(void) {

    // Set baud rate (100 kHz)
    I2C1BRG = 291; // FSCL=100kHz, Tdelay=250ns, FP/2=30MHz

//    // Enable master interrupts
//    IPC4bits.MI2C1IP = 1; // interrupt priority
//    IEC1bits.MI2C1IE = 1; // enable interrupt
//    IFS1bits.MI2C1IF = 0; // clear interrupt flag

    // Enable I2C1
    I2C1CON1bits.I2CEN = 1;

}

/* I2C ROUTINES */

void I2C1_Send_Byte(char byte) {
    // Load I2C1 transmit register
    I2C1TRN = byte;
}

void I2C1_Wait_While_Busy() {
    // Check if I2C1 operation is done
    while(IFS1bits.MI2C1IF==0);

    // I2C1 is ready, clear interrupt flag
    IFS1bits.MI2C1IF = 0;
}

void I2C1_Check_Ack_Status() {
    if (I2C1STATbits.ACKSTAT==1) {
        // NACK, do nothing
    } else {
        // ACK, do nothing
    }
}

void I2C_RTC_Get_Date_and_Time() {

    int b;
    char data[7];

    // Reset master interrupt flag
    IFS1bits.MI2C1IF = 0;

    // Wait for idle state
    while(I2C1STATbits.TRSTAT);

    /* MASTER TRANSMITTER / SLAVE RECEIVER */

    // Send START condition
    I2C1CON1bits.SEN = 1;
//    I2C1_Wait_While_Busy();
    while (I2C1CON1bits.SEN==1);

    // Call the RTC in receive mode
    I2C1_Send_Byte(0xD0); // 1101000|0
    I2C1_Wait_While_Busy();
    I2C1_Check_Ack_Status();

    // Send the first RTC register address
    I2C1_Send_Byte(0x00);
    I2C1_Wait_While_Busy();
    I2C1_Check_Ack_Status();

    /* MASTER RECEIVER / SLAVE TRANSMITTER */

    do {
        // Send RESTART condition
        I2C1CON1bits.RSEN = 1;
        I2C1_Wait_While_Busy();

        // Reverse direction (RTC is transmitter)
        I2C1_Send_Byte(0xD1); // 1101000|1
        I2C1_Wait_While_Busy();
    } while (I2C1STATbits.ACKSTAT==1);

    // Set to ACK bit
    I2C1CON1bits.ACKDT = 0;

    // Receive 7 bytes of data from RTC
    for (b=0; b<7; b++) {
        // Enable receive mode
        I2C1CON1bits.RCEN = 1;
        I2C1_Wait_While_Busy();

        // Get byte from buffer
        data[b] = I2C1RCV;

        // Set to NACK bit after last received byte
        if (b==6)
            I2C1CON1bits.ACKDT = 1;

        // Master acknowledge
        I2C1CON1bits.ACKEN = 1;
    }

    // Send STOP condition
    I2C1CON1bits.PEN = 1;
    I2C1_Wait_While_Busy();

}

/* MAIN ROUTINE */

int main(void) {

    Init_Oscillator();

    Init_Ports();

    Init_I2C();

    while(1) {
        // Poll RTC every second
        I2C_RTC_Get_Date_and_Time();
        __delay_ms(1000);
    }

}
    
pregunta Marko Gulin

1 respuesta

2

Logré resolver este problema.

Cuando detiene el procesador en medio de la comunicación I2Cx con un esclavo, el esclavo puede mantener baja la línea SDAx en el próximo inicio (o reiniciar), de ese modo bloqueando cualquier otra comunicación. Esto es exactamente lo que me sucedió con el reloj en tiempo real DS1307 +.

Esto se puede resolver generando 10 períodos de reloj (en ese caso, SCLx está configurado como una salida digital) antes de habilitar I2Cx. Después de eso, el esclavo liberará la línea SDAx. Algo como esto:

void Generate_10_Clock_Pulses_I2C1(void) {

    char i;

    // Set initial clock level to HIGH
    I2C1_SCL = 1;
    __delay_us(5);

    // Generate 10 clock periods
    // Frequency: 100 kHz (10 us)
    for (i=0; i<20; i++) {
        I2C1_SCL = ~I2C1_SCL; // Macro for LATCbits.LATC8 (ASCL1)
        __delay_us(5); // 50%, 100 kHz
    }

}
    
respondido por el Marko Gulin

Lea otras preguntas en las etiquetas