Meeste i2c libraries zijn 'blocking'. (als een van de lijnen vastzit op laag nivo loopt de hele boel vast)
Dat was voor mij niet acceptabel, dus ik probeer een nieuwe library te maken. Om de een of andere reden werkt dat niet, terwijl de (MikroBasic) code toch vrij simpel is.
Is bedoeld voor i2c port #1 op een PIC24FJ256GA108 (16MHz clock, i2c 400kHz), doe ik toch iets verkeerd? (heb de AN's van Microchip gevolgd)


module __lib_i2c_nb

Const I2CTimeOut = 100

Sub Procedure I2CInit()
Sub Function I2CStart() As Byte
Sub Function I2CRestart() As Byte
Sub Function I2CIdle() As Byte
Sub Function I2CRead(Dim pAck As Byte) As Byte
Sub Function I2CWrite(Dim pDat As Byte) As Byte
Sub Function I2CStop() As Byte

implements

'--------------------------------------------------------------------------------------------------
Sub Procedure I2Cinit()
'--------------------------------------------------------------------------------------------------
  I2CEN_bit = 1
  I2C1BRG   = 37
End Sub

'--------------------------------------------------------------------------------------------------
Sub Function I2CStart() As Byte                                     ' 
'--------------------------------------------------------------------------------------------------
  Result = I2CIdle()
  If Result Then SEN_bit = 1 End If
End Sub

'--------------------------------------------------------------------------------------------------
Sub Function I2CRestart() As Byte                                   ' 
'--------------------------------------------------------------------------------------------------
  Result = I2CIdle()
  If Result Then delay_us(10) RSEN_bit = 1 End If
End Sub

'--------------------------------------------------------------------------------------------------
Sub Function I2CIdle() As Byte                                      ' 
'--------------------------------------------------------------------------------------------------
  Dim lCnt As Byte
  For lCnt = 0 To I2CTimeOut
    If (I2C1Con And 0x001F) = 0 Then Break End If
    delay_us(5)
  Next lCnt
  Result = (I2C1Con And 0x001F) Xor 0xFFFF
End Sub

'--------------------------------------------------------------------------------------------------
Sub Function I2CRead(Dim pAck As Byte) As Byte                      ' 
'--------------------------------------------------------------------------------------------------
  If I2CIdle() Then
    delay_us(10)
    RCEN_bit = 1
    If I2CIdle() Then
      Result = I2C1RCV
      If I2CIdle() Then
        If pAck Then ACKDT_bit = 0 Else ACKDT_bit = 1 End If
        ACKEN_bit = 1
        I2CIdle()
      End If
    End If
  End If
End Sub

'--------------------------------------------------------------------------------------------------
Sub Function I2CWrite(Dim pDat As Byte) As Byte                     ' 
'--------------------------------------------------------------------------------------------------
  Result = I2CIdle()
  If Result Then delay_us(10) I2C1TRN = pDat End If
End Sub

'--------------------------------------------------------------------------------------------------
Sub Function I2CStop() As Byte                                      ' 
'--------------------------------------------------------------------------------------------------
  Result = I2CIdle()
  If Result Then delay_us(10) PEN_bit = 1 End If
End Sub

end.

Na veel testen en application notes doorworstelen de werkende non-blocking versie (Mikrobasic, 16MHz cycleclock, 400kHz i2c, PIC24FJ256GA108)
Errors zijn uit te lezen in i2cerror variabele. Plaatje ziet er goed uit:


module lib_i2c

Const I2CTimeOut = 200
Dim I2cError    As Byte
'Errorbits:     00000001 - Start
'               00000010 - Restart
'               00000100 - Stop
'               00001000 - Write
'               00010000 - Read
'               00100000 - Idle
'               01000000 - Receive buffer

Sub Procedure I2CInit()
Sub Procedure I2CStart()
Sub Procedure I2CRestart()
Sub Procedure I2CStop()
Sub Procedure I2CWrite(Dim pDat As Byte)
Sub Procedure I2CIdle()
Sub Function I2CRead(Dim pAck As Byte) As Byte

implements

'==================================================================================================
Sub Procedure I2CIdle()                                             'Wait for bus idle
'--------------------------------------------------------------------------------------------------
  Dim lCnt As Word                                                  '
  For lCnt = 0 To I2CTimeout                                        '
    If (I2C1CON And 0x1F) = 0  then break end if                    '
  Next lCnt                                                         '
  I2cError.5 = (I2C1CON And 0x1F)                                   '
End Sub                                                             '

'==================================================================================================
Sub Procedure I2Cinit()                                             'Init I2C1 peripheral (400kHz)
'--------------------------------------------------------------------------------------------------
  I2C1BRG   = 0x0025                                                '(Fcy/scl)-(Fcy/10000000) -1
  I2CEN_Bit = 1                                                     'Enable I2C1
  Delay_ms(150)                                                     '
  I2C1STAT = 0                                                      '
End Sub                                                             '

'==================================================================================================
Sub Procedure I2CStart()                                            'Start bus
'--------------------------------------------------------------------------------------------------
  Dim lCnt As Word                                                  '
  I2cError = 0
  I2CIdle()                                                         'Wait for bus idle
  SEN_Bit = 1                                                       'Set start bit
  For lCnt = 0 To I2CTimeout                                        'Wait for bit to clear
    If (SEN_Bit = 0) Then Break End If                              '
  Next lCnt                                                         '
  I2cError.0 = SEN_bit                                              'Errorstatus
End Sub                                                             '

'==================================================================================================
Sub Procedure I2CRestart()                                          'Restart bus
'--------------------------------------------------------------------------------------------------
  Dim lCnt As Word                                                  '
  I2CIdle()                                                         'Wait for bus idle
  RSEN_Bit = 1                                                      'Set repeated start bit
  For lCnt = 0 To I2CTimeout                                        'Wait for bit to clear
    If (RSEN_Bit = 0) Then Break End If                             '
  Next lCnt                                                         '
  I2cError.1 = RSEN_bit                                             'Errorstatus
End Sub                                                             '

'==================================================================================================
Sub Procedure I2CStop()                                             'Stop bus
'--------------------------------------------------------------------------------------------------
  Dim lCnt As Word                                                  '
  I2CIdle()                                                         'Wait for bus idle
  PEN_Bit = 1                                                       'Set stop bit
  For lCnt = 0 To I2CTimeout                                        'Wait for bit to clear
    If (PEN_Bit = 0) Then Break End If                              '
  Next lCnt                                                         '
  I2cError.2 = PEN_Bit                                              'Errorstatus
End Sub                                                             '

'==================================================================================================
Sub Procedure I2CWrite(Dim pDat As Byte)                            'Write byte pDat to bus
'--------------------------------------------------------------------------------------------------
  Dim lCnt As Word                                                  '
  I2CIdle()                                                         'Wait for bus idle
  MI2C1IF_Bit = 0                                                   'Clear interruptflag
  I2C1TRN = pDat                                                    'Data to send into buffer
  For lCnt = 0 To I2CTimeout                                        'Wait for interruptflag to
    If MI2C1IF_bit Then Break End If                                'get set
  Next lCnt                                                         '
  I2cError.3 = MI2C1IF_Bit Xor 1                                    'Errorstatus
End Sub

'==================================================================================================
Sub Function I2CRead(Dim pAck As Byte) As Byte                      'Read byte of bus(1=Nack/0=Ack)
'--------------------------------------------------------------------------------------------------
  Dim lCnt As Word                                                  '
  I2CIdle()                                                         'Wait for bus idle
  MI2C1IF_Bit = 0                                                   'Clear interruptflag
  RCEN_Bit = 1                                                      '
  For lCnt = 0 To I2cTimeout                                        'Wait for buffer
    If RBF_Bit Then Break End If                                    '
  Next lCnt                                                         '
  I2cError.6 = RBF_Bit Xor 1                                        'Errorstatus
  Result = I2C1RCV                                                  'Get received byte
  ACKDT_Bit = pAck                                                  'Send Ack/Nack
  ACKEN_Bit = 1                                                     '
  For lCnt = 0 To I2CTimeout                                        '
    If MI2C1IF_Bit Then Break End If                                '
  Next lCnt                                                         '
  I2cError.4 = MI2C1IF_Bit Xor 1                                    'Errorstatus
End Sub                                                             '
end.

'==================================================================================================

Bij toeval ben ik hier ook pas mee bezig geweest in C , ik heb er voor gekozen om een timer te starten en binnen de grens van de overflow te blijven. Ik heb nog niet kunnen testen of het blijft werken in combinatie met interrupts. Er komen bij mij rx,tx,ioc,tmr interrupts binnen. Ik ben aan het overwegen om ook de i2c communicatie via interrupts te laten lopen. Misschien ben je er ook al mee bezig geweest, voor mij is dit nog in studiefase. Voor de duidelijkheid, dit is voor een pic18 en nog niet compleet maar werkt wel op een fram en rtc. Moet het nog omzetten naar pointers.


void I2C_Init(void) {
    RTC_SDA_TRIS = 1;
    RTC_SCL_TRIS = 1;
    SSP2STAT = 0x00;
    SSP2CON1 = 0x28;
    SSP2CON3 = 0x00;
    SSP2ADD = 0x05;
    PIR3bits.SSP2IF = 0;

    //PIE3bits.SSP2IE = 1; //I2C interrupt enable
}

void I2C_Idle(void) {
    while ((SSP2CON2 & 0x1F) | (SSP2STATbits.R_nW)) {
        if (PIR4bits.TMR4IF == 1) {
            I2Cflag = 0;
            return;
        }
    }
}

void I2C_Start() {
    SSP2CON2bits.SEN = 1;
    while (SSP2CON2bits.SEN) {
        if (PIR4bits.TMR4IF == 1) {
            I2Cflag = 0;
            return;
        }
    }
    I2C_Idle();
}

void I2C_Stop(void) {
    SSP2CON2bits.PEN = 1;
    while (SSP2CON2bits.PEN) {
        if (PIR4bits.TMR4IF == 1) {
            I2Cflag = 0;
            return;
        }
    }
    I2C_Idle();
}

void I2C_Restart(void) {
    SSP2CON2bits.RSEN = 1;
    while (SSP2CON2bits.RSEN) {
        if (PIR4bits.TMR4IF == 1) {
            I2Cflag = 0;
            return;
        }
    }
    I2C_Idle();
}

bit I2C_Write(unsigned char data) {
    SSP2BUF = data;
    if (SSP2CON2bits.ACKSTAT)return 1;
    return 0;
}

unsigned char I2C_Read(unsigned char ack) {
    unsigned char i2cReadData;
    SSP2CON2bits.RCEN = 1;
    while (!SSP2STATbits.BF) {
        if (PIR4bits.TMR4IF == 1) {
            I2Cflag = 0;
            return 0;
        }
    }
    i2cReadData = SSP2BUF;
    if (ack) SSP2CON2bits.ACKDT = 0; // Ack
    else SSP2CON2bits.ACKDT = 1; // NAck
    SSP2CON2bits.ACKEN = 1; 
    while (SSP1CON2bits.ACKEN) {
        if (PIR4bits.TMR4IF == 1) {
            I2Cflag = 0;
            return 0;
        }
    }
    I2C_Idle();
    return ( i2cReadData);
}

Met een timer heb ik ook aan gedacht, maar dat is wat problematisch als je het geheel als losse library wilt uitvoeren.
In libraries gebruik ik indien mogelijk geen timers, omdat je vooraf dan niet weet hoeveel en welke er vrij zijn...
(Met timer is wel een elegante oplossing inderdaad...)

Op zich al vreemd dat de meeste bestaande libraries 'blocking' zijn. (als er iets mis gaat hangt de boel vast tot een reset wordt gegeven.)
Soms wordt ook de watchdog timer gebruikt voor time-outs. In de i2c routine wordt dan een flag geset.
Als de zaak dan reset kun je aan de flag zien dat er in de beftreffende routine wat mis is gegaan...

Ik kwam toevallig achter het 'blocking', doordat ik een pull-up weerstandje aan 1 kant was vergeten te solderen... :)

En wat als je de watchdog gebruikt? Ik voor mij zou een watchdog gebruiken. Heb tot nu toe echter voor I2C nooit gehad dat de boel hing. Maar hou de lees- en schrijf operaties beperkt. Als ik een I2C eeprom uitlees b.v. doe ik het word voor word en niet 1 kb in 1 keer.

[Bericht gewijzigd door vincent1971 op (77%)]

Er kan altijd wat misgaan met i2c. Kapot weerstandje, defecte slave,....
Ik wil dan wel dat het apparaat doorwerkt, zodat ik de gebruiker in ieder geval nog kan waarschuwen dat er wat mis is...