0
\$\begingroup\$

I am working with SPH0645LM4H-B MEMS microphone with I2S output. I successfully set up the SPI on dsPIC33CK256MP503 to the point, that it outputs the I2S clock at a rate 2MHZ, and WS control signal. The microphone seems to return proper data:

enter image description here

My issue is with getting Interrupt for each received I2S word, so I can process the data. I suspect that my setting the SPI is not correct, but cannot figure it out.

Within my MPLAB X IDE v5.35, initially I used the MCC to generate the code, then I wrote some comments. Here is my SPI initialization:

/**
 * To set up the SPIx module for Audio mode:
 * 
 * 1. Clear the SPIxBUFL and SPIxBUFH registers.
 * 
 * 2. If using interrupts:
 *  a) Clear the interrupt flag bits in the respective IFSx register.
 *  b) Set the interrupt enable bits in the respective IECx register.
 *  c) Write the SPIxIP bits in the respective IPCx register to set the interrupt priority.
 * 
 * 3. Write the desired settings to the SPIxCON1L, SPIxCON1H and SPIxCON2L registers with AUDEN (SPIxCON1H[15]) = 1.
 * 
 * 4. Clear the SPIROV bit (SPIxSTATL[6]).
 * 
 * 5. Enable SPIx operation by setting the SPIEN bit (SPIxCON1L[15]).
 * 
 * 6. Write the data to be transmitted to the SPIxBUFL and SPIxBUFH registers. 
 *    Transmission (and reception) will start as soon as data are written to the SPIxBUFL and SPIxBUFH registers.
 */
void SPI1_Initialize (void)
{
    /*
     * AUDEN enabled; 
     * FRMEN disabled; 
     * AUDMOD I2S; 
     * FRMSYPW One clock wide; 
     * AUDMONO mono; 
     * FRMCNT 0; 
     * MSSEN disabled; 
     * FRMPOL disabled; 
     * IGNROV disabled; 
     * SPISGNEXT not sign-extended; 
     * FRMSYNC disabled; 
     * URDTEN disabled; 
     * IGNTUR disabled; 
     */ 
    SPI1CON1H = 0x8800;

// WLENGTH 0; 
SPI1CON2L = 0x00;

// SPIROV disabled; FRMERR disabled; 
SPI1STATL = 0x00;

// SPI1BRGL 0; 
SPI1BRGL = 0x00;

/*
 * SPITBFEN disabled; 
 * SPITUREN disabled; 
 * FRMERREN disabled; 
 * SRMTEN disabled; 
 * SPIRBEN disabled; 
 * BUSYEN disabled; 
 * SPITBEN disabled; 
 * SPIROVEN disabled; 
 * SPIRBFEN disabled; 
 */ 
SPI1IMSKL = 0x00;

// RXMSK 0; TXWIEN disabled; TXMSK 0; RXWIEN disabled; 
SPI1IMSKH = 0x00;

// SPI1URDTL 0; 
SPI1URDTL = 0x00;
// SPI1URDTH 0; 
SPI1URDTH = 0x00;

/*
 * THIS MUST BE LAST, because the above initialization requires the SPIEN=0 for their access
 * 
 * SPIEN enabled; 
 * DISSDO: SDOx pin is not used by the module; pin is controlled by the port function; 
 * MCLKEN FOSC/2; 
 * CKP Idle:High, Active:Low; 
 * SSEN disabled; 
 * MSTEN Master; 
 * MODE32 enabled; | these two together select I2S: 24-bit data, 32-bit FIFO, 32-bit channel/64-bit frame
 * MODE16 enabled; |
 * SMP Middle; 
 * DISSCK CONTROLLED BY THE MODULE; 
 * SPIFE Frame Sync pulse precedes; 
 * CKE Idle to Active; 
 * SPISIDL disabled; 
 * ENHBUF disabled; 
 * DISSDI: SDIx pin is controlled by the module; 
 */ 
SPI1CON1L = 0x1C60;
}

I was hoping to get interrupt after each single sampe received, but it acts strangely. I do get into interrupt routine, but code doesn't get to the middle of the routine, or it gets there but only 5 times or so.

Here is my Interrupt priority table:

    void INTERRUPT_Initialize (void)
    {
    //    SPIRXI: SPI1 RX
    //    Priority: 4 ??
    IPC2bits.SPI1RXIP = 4;

    //    UEVTI: UART1 Event
    //    Priority: 1
    IPC47bits.U1EVTIP = 1;

    //    UTXI: UART1 TX
    //    Priority: 1
    IPC3bits.U1TXIP = 1;

    //    UEI: UART1 Error
    //    Priority: 1
    IPC12bits.U1EIP = 1;

    //    URXI: UART1 RX
    //    Priority: 1
    IPC2bits.U1RXIP = 1;

    //    TI: Timer 1
    //    Priority: 1
    IPC0bits.T1IP = 1;
    }

And finally, here is my test interrupt routine:

void __attribute__ ( ( interrupt, no_auto_psv ) ) _SPI1RXInterrupt ( void )
{
    IFS0bits.SPI1RXIF = 0;

    while ( SPI1STATLbits.SPIRBE == true) // Automatically cleared in hardware when SPIx transfers data from SPIxRXSR to SPIxRXB.
    {
    
    }

    /*
     * Received data looks like 32 bit, but only top 18 bits are valid from microphone.
     * By reading the H word only, we loose only the bottom 2 bit resolution, making the audio sample 16-bit wide
     */
    *(audio.tail_ptr) = SPI1BUFL;
    *(audio.tail_ptr+2) = SPI1BUFH;
    //*(audio.tail_ptr) = 0x64;//SPI1BUFL;

    audio.i++;
    if (audio.i >= 40)
    {
        audio.i++;
    }

    // Will the increment not result in a wrap and not result in a pure collision?
    // This is most often condition so check first
    if ( ( audio.tail_ptr    != (audio.buffer + AUDIO_BUFFER_SIZE-2)) &&
         ((audio.tail_ptr+2) != audio.head_ptr) )
    {
        audio.tail_ptr++;

    } 
    else if ( (audio.tail_ptr == (audio.buffer + AUDIO_BUFFER_SIZE-2)) &&
              (audio.head_ptr !=  audio.buffer) )
    {
        // Pure wrap no collision
        audio.tail_ptr = audio.buffer;
    } 
    else // must be collision
    {
        audio.overflowed = true;
    }
    
    SPI1BUFL = 0;
    SPI1BUFH = 0;
}
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

Since I2S is not documented well by Microchip, and since I solved my problem, I am posting it here in case it helps someone.

I am putting here the chunks of code, which are working. I2S clock (SPI) is set to 1.1111MHz, slower is not allowed by the microphone.

After each 32-bit chunk is received by PIC, it calls interrupt, and now it does. (The next step I am going to move the data via DMA, but that was not part of this question). There is interrupt for left microphone (I only have one microphone), and there is also interrupt for non-existent right microphone - SPI still generates 32 clocks for it.

The code is a test code, so please do not comment on the content style...

SPI initialization (that was the hardest to reverse-engineer), plus test interrupt routines:

/**
 * To set up the SPIx module for Audio mode:
 * 
 * 1. Clear the SPIxBUFL and SPIxBUFH registers.
 * 
 * 2. If using interrupts:
 *  a) Clear the interrupt flag bits in the respective IFSx register.
 *  b) Set the interrupt enable bits in the respective IECx register.
 *  c) Write the SPIxIP bits in the respective IPCx register to set the interrupt priority.
 * 
 * 3. Write the desired settings to the SPIxCON1L, SPIxCON1H and SPIxCON2L registers with AUDEN (SPIxCON1H[15]) = 1.
 * 
 * 4. Clear the SPIROV bit (SPIxSTATL[6]).
 * 
 * 5. Enable SPIx operation by setting the SPIEN bit (SPIxCON1L[15]).
 * 
 * 6. Write the data to be transmitted to the SPIxBUFL and SPIxBUFH registers. 
 *    Transmission (and reception) will start as soon as data are written to the SPIxBUFL and SPIxBUFH registers.
 */
void SPI1_Initialize (void)
{
    /*
     * AUDEN enabled; 
     * FRMEN disabled; 
     * AUDMOD I2S; 
     * FRMSYPW One clock wide; 
     * AUDMONO mono; 
     * FRMCNT 0; 
     * MSSEN disabled; 
     * FRMPOL disabled; 
     * IGNROV disabled; 
     * SPISGNEXT not sign-extended; 
     * FRMSYNC disabled; 
     * URDTEN disabled; 
     * IGNTUR disabled; 
     */ 
    SPI1CON1H = 0x8800;
    
    // WLENGTH 0; 
    SPI1CON2L = 0x00;
    
    // SPIROV disabled; FRMERR disabled; 
    SPI1STATL = 0x00;
    
    // SPI1BRGL 8 = 1.11111MHz; 
    SPI1BRGL = 0x08;
    
    /*
     * SPITBFEN disabled; 
     * SPITUREN disabled; 
     * FRMERREN disabled; 
     * SRMTEN disabled; 
     * SPIRBEN disabled; 
     * BUSYEN disabled; 
     * SPITBEN disabled; 
     * SPIROVEN disabled; 
     * SPIRBFEN disabled; 
     */ 
    SPI1IMSKL = 0x00;
    
    // RXMSK 0; TXWIEN disabled; TXMSK 0; RXWIEN disabled; 
    SPI1IMSKH = 0x00;
    
    // SPI1URDTL 0; 
    SPI1URDTL = 0x00;
    // SPI1URDTH 0; 
    SPI1URDTH = 0x00;
    
    /*
     * THIS MUST BE LAST, because the above initialization requires the SPIEN=0 for their access
     * 
     * SPIEN enabled; 
     * DISSDO: SDOx pin is not used by the module; pin is controlled by the port function; 
     * MCLKEN FOSC/2; 
     * CKP Idle:High, Active:Low; 
     * SSEN disabled; 
     * MSTEN Master; 
     * MODE32 enabled; | these two together select I2S: 24-bit data, 32-bit FIFO, 32-bit channel/64-bit frame
     * MODE16 enabled; |
     * SMP Middle; 
     * DISSCK CONTROLLED BY THE MODULE; 
     * SPIFE Frame Sync pulse precedes; 
     * CKE Idle to Active; 
     * SPISIDL disabled; 
     * ENHBUF disabled; 
     * DISSDI: SDIx pin is controlled by the module; 
     */ 
    SPI1CON1L = 0x1C60;
}

Interrupt routine:


/**
 * Receive interrupts are signaled by SPIxRXIF. This event occurs when:
 * - RX watermark interrupt
 * - SPIROV = 1
 * - SPIRBF = 1 - SPIRBE = 1
 * provided the respective mask bits are enabled in SPIxIMSKL/H.
 * 
 * Flag:        IFS0[9]
 * Enable:      IEC0[9]
 * Priority:    IPC2[6:4]
 */ 
void __attribute__ ( ( interrupt, no_auto_psv ) ) _SPI1RXInterrupt ( void )
{
    unsigned int dataH, dataL;
    
    IFS0bits.SPI1RXIF = 0;

    if (SPI1STATLbits.SPIROV == 1)
    {
        SPI1STATLbits.SPIROV = 0;   // clear the Overflow flag
    }

    while (SPI1STATLbits.SPIRBE == true) // Automatically cleared in hardware when SPIx transfers data from SPIxRXSR to SPIxRXB.
    {
        ;
    }

    /*
     * Received data looks like 32 bit, but only top 18 bits are valid from microphone.
     * To save memory, we use only the top 16 bits.
     */
    dataL = SPI1BUFL;
    dataH = SPI1BUFH;
    
    {
        dataL = dataL >> 8; // removes both 6 unused zeros plus 2 data bits we are chopping off
        dataH = dataH << 8; // prepare it to be merged into one 16-bit number

        *(audio.tail_ptr) = dataH | dataL;

        // Will the increment not result in a wrap and not result in a pure collision?
        // This is most often condition so check first
        if ( ( audio.tail_ptr    != (audio.buffer + AUDIO_BUFFER_SIZE-1)) &&
             ((audio.tail_ptr+1) != audio.head_ptr) )
        {
            audio.tail_ptr++;
        } 
        else if ( (audio.tail_ptr == (audio.buffer + AUDIO_BUFFER_SIZE-1)) &&
                  (audio.head_ptr !=  audio.buffer) )
        {
            // Pure wrap no collision
            audio.tail_ptr = audio.buffer;
        } 
        else // must be collision
        {
            audio.overflowed = true;
        }
    }
}

And finally, the CPU clock was sped up using PLL from 8MHz to 40MHz:

void CLOCK_Initialize(void)
{
    // FRCDIV FRC/1; PLLPRE 1; DOZE 1:8; DOZEN disabled; ROI disabled; 
    CLKDIV = 0x3001;
    // PLLFBDIV 100; 
    PLLFBD = 0x64;
    // TUN Center frequency; 
    OSCTUN = 0x00;
    // POST1DIV 1:5; VCODIV FVCO/4; POST2DIV 1:2; 
    PLLDIV = 0x52;
    // APLLEN disabled; FRCSEL FRC; APLLPRE 1:1; 
    ACLKCON1 = 0x101;
    // APLLFBDIV 150; 
    APLLFBD1 = 0x96;
    // APOST1DIV 1:4; APOST2DIV 1:1; AVCODIV FVCO/4; 
    APLLDIV1 = 0x41;
    // CANCLKEN disabled; CANCLKSEL No Clock Selected; CANCLKDIV Divide by 1; 
    CANCLKCON = 0x00;
    // ROEN disabled; ROSWEN disabled; ROSLP disabled; ROSEL FOSC; ROOUT disabled; ROSIDL disabled; 
    REFOCONL = 0x00;
    // RODIV 0; 
    REFOCONH = 0x00;
    // ROTRIM 0; 
    REFOTRIMH = 0x00;
    // IOLOCK disabled; 
    RPCON = 0x00;
    // PMDLOCK disabled; 
    PMDCON = 0x00;
    // ADC1MD enabled; T1MD enabled; U2MD enabled; U1MD enabled; SPI2MD enabled; SPI1MD enabled; QEIMD enabled; C1MD enabled; PWMMD enabled; I2C1MD enabled; 
    PMD1 = 0x00;
    // CCP2MD enabled; CCP1MD enabled; CCP4MD enabled; CCP3MD enabled; CCP7MD enabled; CCP8MD enabled; CCP5MD enabled; CCP6MD enabled; CCP9MD enabled; 
    PMD2 = 0x00;
    // I2C3MD enabled; U3MD enabled; QEI2MD enabled; CRCMD enabled; I2C2MD enabled; 
    PMD3 = 0x00;
    // REFOMD enabled; 
    PMD4 = 0x00;
    // DMA1MD enabled; SPI3MD enabled; DMA2MD enabled; DMA3MD enabled; DMA0MD enabled; 
    PMD6 = 0x00;
    // CMP3MD enabled; PTGMD enabled; CMP1MD enabled; CMP2MD enabled; 
    PMD7 = 0x00;
    // DMTMD enabled; CLC3MD enabled; OPAMPMD enabled; BIASMD enabled; CLC4MD enabled; SENT2MD enabled; SENT1MD enabled; CLC1MD enabled; CLC2MD enabled; 
    PMD8 = 0x00;
    // CF no clock failure; NOSC FRCPLL; CLKLOCK unlocked; OSWEN Switch is Complete; 
    __builtin_write_OSCCONH((uint8_t) (0x01));
    __builtin_write_OSCCONL((uint8_t) (0x01));
    // Wait for Clock switch to occur
    while (OSCCONbits.OSWEN != 0);
    while (OSCCONbits.LOCK != 1);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.