SpenceKonde / DxCore

Arduino core for AVR DA, DB, DD, EA and future DU-series parts - Microchip's latest and greatest AVRs. Library maintainers: Porting help and adviccee is available.
Other
187 stars 49 forks source link

Wire.checkPinLevels() does not work as expected if I2C is on MVIO pins & VDDIO2 < 3V while VDD = 5V #436

Closed nomakewan closed 1 year ago

nomakewan commented 1 year ago

Not sure if there's actually a way to fix this one if I'm right about what's wrong, but going to put it here for reference anyway.

I have an AVR32DB32 running with VDD = 5V, and have MVIO enabled and VDDIO2 is at 2.5V. I use Wire.swap(2) to move I2C over to PC2/PC3 so that they're handled by MVIO. If I use checkPinLevels on this configuration, it always returns low for both data lines. My assumption is that checkPinLevels is just using the same check under the hood as digitalRead, and since 2.5V is less than 3V, it always returns low.

Otherwise Wire works just fine in this configuration. I was just messing around with checkPinLevels to try to set up a fault check for if for some strange reason the opamp were to fail and both take down my slave device's power as well as the power going to the pullup resistors on the data lines.

SpenceKonde commented 1 year ago

digitalRead() and digitalReadFast() use the same methods to read pins, yeah. It's not like there's any other way.

But that does not explain anything: digitalRead() on the MVIO pins is based on the MVIO voltage. That's the whole point of MVIO. The guaranteed high threshold is 0.8xVDDIO2, guaranteed low 0.2xVDDIO2 - just like it is 0.8VDD and 0.2VDD on the other pins. (note that I2C levels are 0.7xVDD and 0.3xVDD (or VDDIO2) - this might be significant, but I would be surprised, and besides, the voltage on the I2C pins should be = VDDIO2 when not actively transmitting data so...

What you should check: MVIO.STATUS - if this is 1, either MVIO is disabled and the voltage is assumed to be properly supplied to that power pin (they are NEVER connected internally. If MVIO isn't enabled, I didn't see any odd behavior until I got below minimum VDDIO2, at which point with MVIO enabled, they read 0 and all are tristated. With MVIO disabled, but insufficient voltage on VDDIO2, the pins behave unpredictably; this improperly configured operating regime should be avoided

if (!MVIO.STATUS) {
  Serial.println("MVIO voltage below minimum required for MVIO!")
}
analogReference(INTERNAL1V024);
analogRead(ADC_VDDIO2DIV10); // trash the first reading for good measure
int16_t reading = analogRead(ADC_VDDIO2DIV10);
// now since you presumably haven't changed analogReadResolution, you can get 1024 different values back between 0V and 1.024v, so each count is 1mv. But the source was already divided by 10, so that's 10mv.
reading *= 10; // now it's in millivolts before the div10.
Serial.print(reading);
Serial.println(" mV");

I would expect that if checkPinLevels is returning 0, that the voltage on the MVIO supply rail is under 1.8V or a malfunctioning I2C device is holding the lines low, or that you forgot to install the required pullup resistors. digitalRead() and digitalReadFast() on the pins should give the same answer (well, certainly digitalReadFast should - both that and checkPinLevels read the pins through VPORTx.IN. Now if digitalRead gives one result and digitalReadFast gives another, compare the value of VPORTC.IN and PORTC.IN. They should always be the same. If there not, congratulations, you've found a new silicon bug, welcome to the Microchip Beta Test team.

I2C always needs external pullups. The internal pullups are not, and never have been strong enough. Unlike the official core, we do not turn on the internal pullups automatically (which will work under favorable conditions, short wires, only one or two devices, that sort of thing, hiding the problem in the simple case, while still failing in more complicated cases), because it seems that most of the Arduino community is only vaguely aware of the necessity of pullups. My feeling is that if I2C doesn't have pullups, it should not work, and the best thing we can do in this case is to always fail, so that people will fix it now, when the system is simple, rather than later when they've added stuff and there are far more places to search for problems.

We provide a usePullups method to turn them on anyway - as we state in the documentation, if that fixes any I2C problems, that means you should install external pullups. Relying on the internal pullups that are almost an order of manitude too weak is a recipe for baffling, timeconsuming problems down the road - for example, you might put new wires that are longer on, you rest your elbow on the wire while using the keyboard and that increases the capacitance, or add another device, and suddenly I2C stops working, then works again as you start to investigate. It's a worst-kind-of-bug-to-debug situation.

But if you had to do usePullups() to make it work, then you know that if anything ever happens to that I2C bus, the first thing you should try is installing external pullups (or really, you should then take that line out, and install pullups immediately to prevent future problems, 4.7k-10k is appropriate.

Some breakout boards for I2C devices do have pullups (usually 10k) on them. Not all of them do. This further muddies the water for Arduino-folks.

What voltage do you measure with digital multimeter between ground and the pins in question?

nomakewan commented 1 year ago

Let me try to address a few of the assumptions here first so we can get into the meat of the problem.

First, this is a custom PCB with an AVR32DB32 and a KXTJ3-1057 accelerometer as the slave. I am using 2.1k pullup resistors on both I2C datalines as the math I ran on my traces suggested that would be within the optimal range and they were readily available. I am not using usePullups() as this is commented several times throughout the documentation as being "very very bad"(tm). Hardware pullups are always better, as you noted.

Now, on to the problem. I have this code initializing the I2C to swap over to the MVIO ports and run at 400KHz:

  Wire.swap(2);
  Wire.begin();
  Wire.setClock(400000UL);

I already have an MVIO check as well that I run before I even bother turning on I2C. It's done with a simple while(MVIO.STATUS != 1); loop, and works great.

However, if after that I then use this code to check the state of checkPinLevels, it always falls in even if I'm measuring 2.5V at VDDIO2 as expected:

if (Wire.checkPinLevels() != 0x03)

It was my understanding from the documentation that 0x03 is the "both pins are HIGH" output.

Perhaps my check is actually the problem?

SpenceKonde commented 1 year ago

Yeah 3 is what you should see. that's wacky. I'll see if I can reproduce any odd behavior with the TWI pins on PORTC. Do you know whether the VDDIO2 voltage actually matters, or if it reproduces with VDDIO2 tied to the power rail?

nomakewan commented 1 year ago

I generate 2.5V using the internal OpAmp and have that tied to the VDDIO2 pin (and my pullup resistors, and VDD for the KXTJ3-1057). The input to VDDIO2 is what determines the voltage levels for the MVIO pins, so it's fairly important.

Due to the layout of my PCB, I cannot test supplying 5V to MVIO as that would let the magic smoke out of my KXTJ3-1057.

SpenceKonde commented 1 year ago

I cannot reproduce your reported issue.

When the I2C bus is working correctly I get a 3 back from checkPinLevels() as expected with VDDIO2 = 2.5 and TWI0 on PORTC.

nomakewan commented 1 year ago

I was able to get back to my hardware and test some more code. By moving the I2C initialization to earlier in the setup process and then keeping the checkPinLevels where it is (after the MVIO check but before attempting to initialize my accelerometer), as well as changing the hex 0x03 to just 3 (not sure if that matters, but since you used an int I did the same), I now get a successful result on startup.

As such, I am considering this just a minor timing issue and not a major code problem. Thank you, and apologies for the mess.

SpenceKonde commented 1 year ago

Yeah, if it was right after the MVIO check it might not have had a chance to pull the pins high at that time - as soon as MVIO comes online, then we expect it to instantly have the pins pulled up. . Rise time of a pullup is is not 0.

On Tue, Jun 6, 2023 at 3:44 AM nomakewan @.***> wrote:

I was able to get back to my hardware and test some more code. By moving the I2C initialization to earlier in the setup process and then keeping the checkPinLevels where it is (after the MVIO check but before attempting to initialize my accelerometer), as well as changing the hex 0x03 to just 3 (not sure if that matters, but since you used an int I did the same), I now get a successful result on startup.

As such, I am considering this just a minor timing issue and not a major code problem. Thank you, and apologies for the mess.

— Reply to this email directly, view it on GitHub https://github.com/SpenceKonde/DxCore/issues/436#issuecomment-1578104128, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABTXEW4ARQGVCU4OUCIAMQ3XJ3NVZANCNFSM6AAAAAAYRQ4UHI . You are receiving this because you commented.Message ID: @.***>

--


Spence Konde Azzy’S Electronics

New products! Check them out at tindie.com/stores/DrAzzy GitHub: github.com/SpenceKonde ATTinyCore https://github.com/SpenceKonde/ATTinyCore: Arduino support for all pre-2016 tinyAVR with >2k flash! megaTinyCore https://github.com/SpenceKonde/megaTinyCore: Arduino support for all post-2016 tinyAVR parts! DxCore https://github.com/SpenceKonde/DxCore: Arduino support for the AVR Dx-series parts, the latest and greatest from Microchip! Contact: @.***

nomakewan commented 1 year ago

True, but to be clear, I did not move checkPinLevels. It's still immediately after while(MVIO.STATUS != 1); in the code. What I moved was the three initialization commands for Wire, to before firing up the Opamp. So less than the pullup rise time or MVIO check, it appears there's timing regarding Wire itself being ready involved. Again, not a big deal at all, it works perfectly now. Thank you again!