Shop OBEX P1 Docs P2 Docs Learn Events
I2C Question — Parallax Forums

I2C Question

I am working on a 30105 driver(Spec Sheet). Looking at the Arduino driver as a starting point. I have used Johnny Macs jm_i2c driver. However, the Arduino has hardware i2c and I am stuck on the following code:

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint32_t iaddress, uint8_t isize, uint8_t sendStop)
{
  if (isize > 0) {
  // send internal address; this mode allows sending a repeated start to access
  // some devices' internal registers. This function is executed by the hardware
  // TWI module on other processors (for example Due's TWI_IADR and TWI_MMR registers)

  beginTransmission(address);

  // the maximum size of internal address is 3 bytes
  if (isize > 3){
    isize = 3;
  }

  // write internal register address - most significant byte first
  while (isize-- > 0)
    write((uint8_t)(iaddress >> (isize*8)));
  endTransmission(false);
  }

  // clamp to buffer length
  if(quantity > BUFFER_LENGTH){
    quantity = BUFFER_LENGTH;
  }
  // perform blocking read into buffer
  uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop);
  // set rx buffer iterator vars
  rxBufferIndex = 0;
  rxBufferLength = read;

  return read;
}

I may be overthinking this, but I don't see an easy way to implement this.

Here is the calling code that uses that method. It is mostly ported to spin2 but unproven and untested at this point

PUB check(): numberOfSamples |  byte readPointer, byte writePointer,  bytesLeftToRead, toGet, byte temp[32], tempLong

  ' Read register FIDO_DATA in (3-byte * number of active LED) chunks
  ' Until FIFO_RD_PTR = FIFO_WR_PTR

  readPointer := getReadPointer()
  writePointer := getWritePointer()

  numberOfSamples := 0

  debug(udec(readPointer))
  debug(udec(writePointer))

  ' Do we have new data?
  if (readPointer <> writePointer)

    ' Calculate the number of readings we need to get from sensor
    numberOfSamples := writePointer - readPointer
    if (numberOfSamples < 0)
      numberOfSamples += 32 ' Wrap condition

    ' We now have the number of readings, now calc bytes to read
    ' For this example we are just doing Red and IR (3 bytes each)
    bytesLeftToRead := numberOfSamples * activeLEDs * 3

    ' Get ready to read a burst of data from the FIFO register
    i2c.start()
    i2c.write(MAX30105_ADDRESS)
    i2c.write(FIFODATA)
    i2c.stop()

    ' We may need to read as many as 288 bytes so we read in blocks no larger than I2C_BUFFER_LENGTH
    ' I2C_BUFFER_LENGTH changes based on the platform. 64 bytes for SAMD21, 32 bytes for Uno.
    ' Wire.requestFrom() is limited to BUFFER_LENGTH which is 32 on the Uno

    debug(udec(bytesLeftToRead))

    repeat while (bytesLeftToRead > 0)

      toGet := bytesLeftToRead
      if (toGet > I2C_BUFFER_LENGTH)

        ' If toGet is 32 this is bad because we read 6 bytes (Red+IR * 3 = 6) at a time
        ' 32 % 6 = 2 left over. We don't want to request 32 bytes, we want to request 30.
        ' 32 % 9 (Red+IR+GREEN) = 5 left over. We want to request 27.

        toGet := I2C_BUFFER_LENGTH - (I2C_BUFFER_LENGTH +// (activeLEDs * 3)) ' Trim toGet to be a multiple of the samples we need to read

      bytesLeftToRead -= toGet

      ' Request toGet number of bytes from sensor
      **_i2cPort->requestFrom(MAX30105_ADDRESS, toGet)**

      repeat while (toGet > 0)

        head++ ' Advance the head of the storage struct
        head +//= STORAGE_SIZE ' Wrap condition

        ' Burst read three bytes - RED
        temp[3] := 0
        temp[2] := i2c.read(i2c.ack)
        temp[1] := i2c.read(i2c.ack)
        temp[0] := i2c.read(i2c.nak)
         waitms(1000)
        ' Convert array to long
        longmove(@tempLong, @temp, 32)

        tempLong &= $3FFFF ' Zero out all but 18 bits

        red[head] := tempLong ' Store this reading into the sense array

        if (activeLEDs > 1)

          '/Burst read three more bytes - IR
          temp[3] := 0
          temp[2] := i2c.read(i2c.ack)
          temp[1] := i2c.read(i2c.ack)
          temp[0] := i2c.read(i2c.nak)

          ' Convert array to long
          longmove(@tempLong, @temp, 32)

          tempLong &= $3FFFF ' Zero out all but 18 bits

          IR[head] := tempLong


        if (activeLEDs > 2)

          ' Burst read three more bytes - Green
          temp[3] := 0
          temp[2] := i2c.read(i2c.ack)
          temp[1] := i2c.read(i2c.ack)
          temp[0] := i2c.read(i2c.nak)

          ' Convert array to long
          longmove (@tempLong, @temp, 32)

          tempLong &= $3FFFF  ' Zero out all but 18 bits

          green[head] := tempLong

        toGet -= activeLEDs * 3

  return numberOfSamples ' Let the world know how much new data we found

Comments

  • The Two Wire Arduino code uses begin Transmission and end Transmission to signal the Start and End I2C codes.

    The function looks like a normal read bytes from the unit and place it in a buffer and then return the number of bytes actually read.

    Mike

  • JonnyMacJonnyMac Posts: 8,924
    edited 2021-03-24 19:20
    1. There is no H in my name
    2. I2C is so easy, even the Arduino can handle it! :D

    Ignore Arduino examples (I have found bugs in more than one) and go right to the docs. The best guidance comes from the transaction diagrams that nearly every I2C spec doc includes. Figures 10 and 11 show the sequence of reading one byte or reading a bunch of bytes. Here's how those translate to Spin using my library.

    pub read_byte(addr) : value                                     ' Figure 10
    
    '' Read one byte from MAX30105
    '' -- addr is the register address
    
      i2c.start()                                                   ' start transaction
      i2c.write($AE)                                                ' slave id (write)
      i2c.write(addr)                                               ' address to read from
      i2c.start()                                                   ' restart
      i2c.write($AF)                                                ' slave id (read)  
      value := i2c.read(i2c.NAK)                                    ' get one byte
      i2c.stop()                                                    ' end transaction
    
    
    pub read_to_buf(addr, count, p_buf)                             ' Figure 11
    
    '' Read multiple bytes from MAX30105
    '' -- addr is the starting register address
    '' -- count is the number of bytes to read
    '' -- p_buf is hub address of buffer (byte array)
    
      i2c.start()
      i2c.write($AE)
      i2c.write(addr)
      i2c.start()
      i2c.write($AF)
      repeat count-1
        byte[p_buf++] := i2c.read(i2c.ACK)                          ' all but last byte gets ACK
      byte[p_buf++] := i2c.read(i2c.NAK)                            ' final byte gets NAK
      i2c.stop()
    
    

    Finally... there is no H in my name! :)

    A few minutes later...

    I found a simple library for the MAX30102 pulse/ox sensor that seems to have similar characteristics to the 30105; maybe some of this code will help. I wrote it for a friend and he got things working, but I never tested it.

  • But the H comes so naturally! Thanks for the input! I know I2C is pretty simple, but I always tend to way overthink things!

  • Had to put this aside for a bit, but back on it now. I have used some of the code that Jon provided.

    The I2C communications fails pretty consistently around the same spot. I can't seem to figure out why. I have attached the code and snippet of the debug output.

    402 x 735 - 27K
  • RaymanRayman Posts: 13,860
    edited 2021-06-16 21:52

    I had an problem with the I2C code above when using a Nunchuck controller...

    The fix for me was to add in a delay in i2c_Read function.
    The code has since been converted to C, but I think you can see it's very similar to the Spin2 version except for the extra delays.
    Maybe adding these delays would help you too?

    int i2c_Read(i2c_Port* pPort, int ackbit)
    {// Read byte from I2C bus
        // -- ackbit is state of ack bit
        //    * usually NAK for last byte read
    
        int i2cbyte, scl, sda, tix;
    
        //copy pins & timing
        scl = pPort->sclpin;
        sda = pPort->sdapin;
        tix = pPort->clktix;
    
    
        __asm
        {
                    drvh      sda                                   // pull-up sda
                    waitx    tix        //RJA added for Nunchuk
                    waitx    tix        //RJA added for Nunchuk
                    waitx    tix        //RJA added for Nunchuk
                    waitx    tix        //RJA added for Nunchuk
    
    .rd_byte        rep       #9, #8                                // read 8 bits, msb first
                    waitx    tix
                    drvh     scl                                   // scl high
                    waitx    tix
                    testp    sda                           wc      // sample sda
                    shl      i2cbyte, #1                           // make room for new bit
                    muxc     i2cbyte, #1                           // sda --> i2cbyte.[0]
                    waitx    tix
                    drvl     scl                                   // scl low
                    waitx    tix
    
    .put_ack        testb     ackbit, #0                    wc      // ackbit.[0] --> c
                    drvc      sda                                   // c --> sda
                    waitx     tix                                   // let sda settle
                    drvh      scl                                   // scl high
                    waitx     tix
                    waitx     tix
                    drvl      scl                                   // scl low
                    waitx     tix
                    waitx     tix
                    end
        }
        return i2cbyte;
    }
    
    
  • @Rayman Just tried it. No go. I think something else is happening here

  • I have made huge progress on this hopefully very close to functional. I am seeing some weirdness in the debug output. What does the exclamation point signify ? And second why is cog 1 showing up when it should be cog 0 ?

  • For things like this, I highly recommend a cheap logic analyzer. If you search Amazon or eBay for a 24MHz usb logic analyzer, expecting to pay ~10usd, you'll find lots of saleae clones that work perfectly with PulseView from the sigrok project. Captures in PulseView are exceptionally helpful to debugging i2c issues.

  • JonnyMacJonnyMac Posts: 8,924
    edited 2021-07-07 01:56

    I can second that -- I use this little dude all the time with PulseView.
    -- https://www.amazon.com/gp/product/B077LSG5P2

    It's the best $13 I've ever spent, and helped me solve a problem with my I2C object a while back. After a forums discussion with Peter Jakacki and others, we all agreed that supporting clock stretching in our I2C code was no longer necessary. Then, a friend asked me to create an object for this device:
    -- https://www.sparkfun.com/products/15083

    I couldn't get it to work with the P2. I connected it to a Schmarschmino board and it did work (though I did find a bug in one of their driver functions). Since it worked, I connected the $13 logic analyzer, and there it was, big as day: clock stretching. You can see it in the attached image capture from PulseView. Note that at about the 460us mark, the clock line stays low for a long time -- this is the device telling the master it's busy. Once the clock line is released, the master is free to read the response. I added clock stretch support back into my driver and get the code working for the Qwiic Twist module.

    I've also added my P2 I2C driver that has clock stretching in case it's helpful to anyone.

    1860 x 439 - 39K
  • I do have one. I use it all the time The problem now is trying to determine where the logic is failing. i guess I should have started a new thread for this one. I still do have some issues with the I2C but at this point I think it is more logic. I am stumped with the output from debug as it is not correct.

  • This is definitely a case where I'd be using the logic analyzer. Add stacked decoders, and it should help visualize what registers are being written and read.

  • Again, I2C is mostly working now. Dumping some variables because the logic is wrong. However debug output is faulty as seen in the attached image. The ! and output coming from cog1. Not sure what would cause that.

  • Hi, JonnyMac and anybody:
    Do you have a C code example for I2C slave? The master side is OK but the receiving side need to study more. The slave in my project is just simply received the data and display it.

    Thanks.

  • I don't use C to program the Propeller, and I've never needed an I2C slave object.

    I did find the attached file (Spin1/PASM) on my computer, but I haven't used it.

  • evanhevanh Posts: 15,187

    That can be used verbatim in FlexC. eg:

    struct __using( "I2C slave v1.2.spin" ) i2cs;
    
    
    main()
    {
        ...
        i2cs.start(28,29,0x42);    // clk_pin, data_pin, slave_address
        ...
    }
    

    It still of course needs ported from prop1 to prop2.

Sign In or Register to comment.