I2C PIO not setting SDA correctly at end of every other-ish transaction

I used the PIO I2C example from the RP2040 sdk. There was one glaring bug in it that I was fortunate to find a solution for in the rpi forum.

Now I notice that it occasionally does not return the data line high correctly. It may be my fault. I see the bug and I will look into it further.

Porting I2C to the new syntax compiler so I took a look at this:

  1. Fixed how I2C is configured for read and writes. This fixed the missing ACK issue, but not the overall buggyness.
  2. It seems to hang during stop condition on the 4th byte pushed into FIFO. This is because the FIFO is full and the PIO doesn’t throw any kind of error. First, I have not yet done any kind of “look ahead” to tell the I2C SM which is the last byte to NACK, I will do that next and see if it fixes the error. Second, I’ll need to dig into the PIO code to see why it’s not returning an error if the NACK issue doesn’t fix it.
  3. With clock stretching, the I2C hangs forever if no device or pullups not enabled. Needs a timeout.

There is an auto build with my latest. It is still buggy and will hang unexpectedly.

Argh. I keep relearning the things that were so tricky in developing the first bus pirate.

  • I2C is a pain in the rear to put in a standardized protocol library
  • I2C we need to show the ACK/NACK in the terminal, but how to pass that from the protocol code back to the syntax system without hacking mode specific stuff in the post processor? I have some ideas but I don’t like any of them
  • I2C reads are a real pain in a bus pirate like system where the user can enter any arbitrary combination of commands. In an I2C read, we need to ACK each byte, except the last one - that gets a NACK. This is dandy for fixed length packets like you’d use in code, but how do we know when the user read a final byte? They might r:8 r and then continue reading bytes on the next line. This is really evident with the STM32 hardware I2C peripheral, and is why we made the FPGA version. The STM32 I2C hardware needs to know 2 bytes ahead of the final read to send the nack bit.
  • Bus Pirate v3 handled this by delaying the ACK/NACK bit until the next user command, this was all done in software. Using the PIO, we really do need to know ahead of time if we’re reading the last byte before a stop condition to send the NACK.
  • What I have done is something I’m not happy about, but it will work for now: the syntax run function now ‘looks ahead’ to the next command (if any) and passes it to the protocol read function. in the I2C read function if the next command is a stop, then that read is NACKed.

[0b1010001 r:8]

  • The syntax above reads 8 bytes from an EEPROM. The last byte read needs a NACK. since the next command after the read is STOP, the Bus Pirate now knows to NACK.

[0b10100001 r:8 d:25 a.7]

  • This syntax reads 8 bytes, delays 25us, then sets pin 7 low. The Bus Pirate will not correctly NACK the last byte because the look-ahead command is a delay, not a stop.
  • The only real way to deal with all that is to fully pre-process the entire line and mark the NACK. Sounds good, but…

>[0b10100001 r:8
…more interesting output…

  • Here, we find some interesting data in our chip that piques our interest. Then we read some more. Finally we send stop on a line of its own. Here the NACK is never sent and the EEPROM will hand on the bus outputting random states until it sees a NACK-like condition.
  • For some chips a STOP alone will shut down the transaction and free the bus, but many will be in output only mode when the STOP happens and don’t catch it.
  • To see bug in action, check out the (1) macro address scanner. It uses the unmodified code from the PICO SDK. It calls all the addresses, and just sends a stop condition. For chips like my EEPROM, they don’t release the bus and we miss addresses, as well as see ghost addresses in the scanner. I remember dealing with this on V3, and there is actually an explanation of how it works in the I2C mode manual in the docs. The solution is to read one byte and end with NACK when a chip answers the read address.

Implemented everything except how to NACK if the next command isn’t a stop condition. For that I’m thinking about adding a protocol level (pre) compiler step so the protocol has a chance to review the syntax for errors before executing. That’s a for later problem.

I have a few more places to add timeouts, then I2C is done. This took some drastic (positive) changes that will need to be implemented everywhere else.