how to test by i2c scanner at different Wire.setClock(i2cclock) reliably

hello,
I want to test different i2c devices on different boards if they work with i2cclock speeds of 10000, 100000, 400000, and 1000000 Hz, theoretically perhaps even at 3400000.

Some devices support 400000 and even faster (e.g., MCP23017), some do not (e.g., PCF8591), and some boards support also faster than 400000 or just as slow as 10000, and some do not.

As Wire.setClock() does not return an error if an arbitrary clock speed is not supportet and cannot be set, how can I be sure then that the i2c clock actually had run at the desired speed and was performed correctly?

Looking at the library:
Wire.cpp

void TwoWire::setClock(uint32_t clock)
{
  twi_setFrequency(clock);
}

twi.c

/* 
 * Function twi_setClock
 * Desc     sets twi bit rate
 * Input    Clock Frequency
 * Output   none
 */
void twi_setFrequency(uint32_t frequency)
{
  TWBR = ((F_CPU / frequency) - 16) / 2;
  
  /* twi bit rate formula from atmega128 manual pg 204
  SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
  note: TWBR should be 10 or higher for master mode
  It is 72 for a 16mhz Wiring board with 100kHz TWI */
}

TWBR is an 8-bit unsigned register so it can contain anything from 0 to 255. You could skip the library and write a byte directly into the TWBR. That would let you step through those 256 values (or a subset) and use the formula above to calculate the actual clock rate for display.

tbh I do not understand quite correctly:
1st, imagine I am using the following code from the i2c scanner:
I now want that code to run with different initial Wire.setClock(i2cclock) values:

(code edited, reason: c+p mistake)

#include <Wire.h>


void setup()
{
 Wire.begin();

 Serial.begin(9600);
 while (!Serial);             // Leonardo: wait for serial monitor
 Serial.println("\nI2C Scanner");
}


void loop()
{
 byte error, address;
 int nDevices;

 Wire.setClock(100000UL);

 Serial.println("Scanning...");

 nDevices = 0;
 for(address = 1; address < 127; address++ )
 {
   // The i2c_scanner uses the return value of
   // the Write.endTransmisstion to see if
   // a device did acknowledge to the address.
   Wire.beginTransmission(address);
   error = Wire.endTransmission();

   if (error == 0)
   {
     Serial.print("I2C device found at address 0x");
     if (address<16)
       Serial.print("0");
     Serial.print(address,HEX);
     Serial.println("  !");

     nDevices++;
   }
   else if (error==4)
   {
     Serial.print("Unknown error at address 0x");
     if (address<16)
       Serial.print("0");
     Serial.println(address,HEX);
   }    
 }
 if (nDevices == 0)
   Serial.println("No I2C devices found\n");
 else
   Serial.println("done\n");

 delay(5000);           // wait 5 seconds for next scan
}

2nd,
now imagine I have different boards (and different sensor devices) to test it with, e.g.
Uno, Mega, Zero, M0 pro, Due, Teensy3.1, Teensy3.2, Leonardo, Galileo, MRK1000, Yun, ESP8266, ESP32, STM32
will your code work with all boards properly and either set the required i2c bus speed reliably or otherwise signalize in case it failed?

Put that scanning in a loop and loop over all the speeds you want to test. Load the correct speed at the start of the loop

#define NUMOF(x) sizeof(x)/sizeof(x[0])
uint32_t I2CSpeeds[] = {10000, 100000, 400000, 1000000};

void loop(){
  for(byte i = 0; i < NUMOF(I2CSpeeds); i++){
    Wire.setClock(I2CSpeeds[i]);
    
    runI2CScan();
}

tito-t:
2nd,
now imagine I have different boards (and different sensor devices) to test it with, e.g.
Uno, Mega, Zero, M0 pro, Due, Teensy3.1, Teensy3.2, Leonardo, Galileo, MRK1000, Yun, ESP8266, ESP32, STM32
Will your code work with all boards properly and either set the required i2c bus speed reliably or otherwise signalize in case it failed?

No. The TWBR (Two Wire Interface Bit Rate Register) is specific to the AVR processors. You would have to look into the Wire libraries and processor datasheets for all of the other boards to see exactly how the bit rate is set and then calculate the valid range of values from that.

septillion:
Put that scanning in a loop and loop over all the speeds you want to test. Load the correct speed at the start of the loop

#define NUMOF(x) sizeof(x)/sizeof(x[0])

uint32_t I2CSpeeds[] = {10000, 100000, 400000, 1000000};

void loop(){
 for(byte i = 0; i < NUMOF(I2CSpeeds); i++){
   Wire.setClock(I2CSpeeds[i]);
   
   runI2CScan();
}

no, that does not work.
To my observations the boards claim to set the clock speed (as they dont throw an error message) but do not actually do it.
e.g., if I run that code with an OLED device at 3400000 Hz there is no error although the board never achieved to run the bus at 3400000 actually.

johnwasser:
No. The TWBR (Two Wire Interface Bit Rate Register) is specific to the AVR processors. You would have to look into the Wire libraries and processor datasheets for all of the other boards to see exactly how the bit rate is set and then calculate the valid range of values from that.

I would need a code which will work reliably for all boards (more or less) which are supported by the Arduino IDE.

tito-t:
As Wire.setClock() does not return an error if an arbitrary clock speed is not supportet and cannot be set, how can I be sure then that the i2c clock actually had run at the desired speed and was performed correctly?

You can't.
This is a problem for MANY of the APIs that team arduino created.
They didn't define the API with return status so you can't tell when things work/don't-work/fail.

There is no work around for this in s/w.
You have some options and IMO, they all kind of suck.

  • You can try to see what is supported by looking at the i2c code.
    The issue with this is that each core has its own code and they don't all have the same capabilities or support the same rates or behave the same way for unsupported clock rates.
    A core may pick a clock that is the highest rate supported that does not exceed the requested rate.
    Another core may not change the clock at all if it isn't one of the specific ones supported.
    I have seen both of these behaviors in various cores.
    I even saw one core that picked the closest one supported even if it was over the requested rate.
    IMO, this last one is just plain wrong, and I argued with the authors about it, but they refused to change it.

So what might work for AVR, may not work for ARM, ESP8266, or pic32.
Even worse, some microcontrollers have Arduino core code provided by more than one vendor.
For example, there are multiple boards that use ARM controllers but they use different Arduino cores.
i.e. DUE and Teensy3 are both ARM but they each use their own Arduino core code and board package libraries.

  • You can hook up a logic analyzer or scope and watch the signals.
    This is the only true way to see what clock rate the bus is actually using.
    Unfortunately, there is no way to automate this with a s/w test.

  • You could spin through the possible frequencies you want to test and see if they appear to "work".
    This issue with this, is that testing for reliability when overclocking is not a simple task.
    It requires more than just a simple "hey, look, Ma, it works" type of test that merely does a handshake or moves a bit of small bit of data.

In the larger picture, the maximum reliable clock rate used for a slave is determined by the chipset used by the slave. You can find this by looking at the datasheet for the chipset used by the slave device.

If you are wanting to push the slave beyond its specified ratings, then determining what is "reliable" is more complex than just a simple test.
You have test across temperature ranges, voltage ranges, and various pullup values AND you have to do this with all the slaves attached as there can be interaction between them as the slaves get pushed beyond their specs.
So even if the Wire library setClock() API did provide a means for determining if a desired clock frequency was actually supported, it isn't as simple as doing a simple handshake or small data transfer to determine if a frequency is reliable for the slave, particularly when over-clocking the slave on a bus with multiple slaves.

Also keep in mind that there are various issues in i2c h/w and s/w which can make Arduino libraries or i2c h/w unstable and lock-up when unexpected things happen on the i2c buss.
(And unexpected things will happen when overclocking slaves)
I have seen these types of issues in several cores including the AVR Wire library code.
That core can lock up if there is a glitch on the SDA signal right on the final status bit.
It will confuse the AVR Master i2c h/w into thinking that there is another Master on the bus so it backs off waiting for the other Master to complete. But since there is not another master and the s/w doesn't account for this, the s/w is waiting for interrupt from the other Master completing its transaction that never occurs.
At this point the Wire code is hung in a loop and will never return back to the sketch.

The pic32 core has an issue that if you don't wait long enough between the end and the beginning of a transaction, the i2c h/w will lockup. The period of time that you must wait is based on the clock frequency used.
The easy thing in that case is to simply wait 20us as that works on the low end so it will also work on the higher frequencies.

These are the two cores I've tested the most, but I'm sure other cores probably have similar issues.

For maximum reliability, my recommendation would be look at the datasheets for all your slaves that you intend to use at the same time.
Pick the lowest maximum i2c frequency specified in the datasheets by all the slaves being used, and use that with setClock()
That will give the highest reliable i2c frequency for all the slaves being used.

100k is supported by pretty much everything.
If you avoid older chips you can move up to 400k.
Beyond that, it really starts to thin out.

--- bill

thank you very much for your elaborated reply!
After all, that is very disappointing.

Having once set the bus speed correctly and reliably, then the i2c addr test for the final hardware setup was supposed to print errors for a correct i2c dev detection - or even not; but first comes the required bus speed. And then perhaps more tests need to be done.

So do you think that a reworked Wire lib providing return codes for correctly initialized speed- and bus-open function could achieve that test or will that correct bus speed init check be completely impossible to be resolved eventually?

tito-t:
Do you think that a reworked Wire lib providing return codes for correctly initialized speed- and bus-open function couls achive that test or will that correct bus speed init check be completely impossible to be resolved eventually?

This thread is very timely to what I'm currently in the middle of working on.
I'm in the middle of providing an update to the Wire code in the chipKit pic32 core.
That Wire library code currently doesn't have a setClock() function at all.
This is why I'm currently very familiar with the setClock() code on various cores.

There are many issues with the way the Wire library API is defined and works.
What would be useful would be tot have a way for slave library to register its maximum supported frequency and then have Wire library automatically pick and use the highest rate supported by all the slaves.

But that would require a major change to the API and it would also require changes to the slave libraries as well to be able to use it.

Arduino is pretty much a toy and what you are seeing is some of the issues that crop up when you try to really press it into doing something serious.

Like I said previously, while having a return status to tell if a clock rate is supported by setClock()
is a good thing, it wouldn't help for what it sounds like you want to do.

It sounds like you want to determine the maximum clock rate a chip supports by ramping up the clock and overclocking the slave until it fails.
That has many issues as I pointed out earlier.
You can't simply crank up the clock on a slave to easily determine it maximum clock rate.
This is because slaves respond when overclocked and at some point when the clock is high enough will start to get flaky. They don't just stop working at their maximum rated frequency.
And that is the problem, when the get flaky the Arduino master side will start misbehaving, including locking up.
To complicate things, the overclocked frequency where things start to get flaky is dependent on the temperature, the voltage, and the pullup resistor used, the actual wiring used, and the length of the wires.

BTW, a good option for setClock() would be to return the resulting clock rate used rather than just a status.
That way the caller can tell not only if it "worked" but if the resulting clock is different from what was requested.

--- bill

I completely agree, having once run a first test at e.g. 400kHz or 1 MHz will not mean that it will run safely also in either final hardware setup - but it's meant to be a first test, provided one could rely on the bus speed of 400 kHz or 1 MHz, initially set by the i2c bus open function !!

I think it would be better/simpler to lookup the max clock rate in the datasheet for the chip used on the slave devices and use the lowest one if it is higher than the default 100kHZ.

It depends on the goal
Maximum reliability, without exceeding the chip manufacturers specs, or trying to figure out a "reliable" overclocking speed.

Determining a reliable overclocking speed is difficult and I'm not sure how you could automate it in an Arduino environment.

And like I've said a few times, there are issues in the i2c h/w and s/w that can cause lockups when the i2c bus glitches. When overclocking, you increase the risk of this happening.

--- billl

why do you talk about "overclocking"? That is not my goal, just clocking at the desired clock speed of 400kHz and perhaps even faster.
Finally those clock speeds are supported both by some MCU boards and also by some devices.
My goal is to see, which i2c masters will work with which devices at which speed. But for that goal I need to have reliable clock speeds first, and to know, if the speed is set to n Hz and works at n Hz reliably if I had initially set Wire.setClock(n) , apart from having to evaluate tons of data sheets.

tito-t:
why do you talk about "overclocking"? That is not my goal, just clocking at the desired clock speed of 400kHz and perhaps even faster.

Because you said this:

tito-t:
I want to test different i2c devices on different boards if they work with i2cclock speeds of 10000, 100000, 400000, and 1000000 Hz, theoretically perhaps even at 3400000.

You haven't really said what you are really trying to do or what core you are working with so it is a bit of a XY problem, leaving us to guess details about what are trying to accomplish.
Given you seem to be wanting to test a slave with multiple clock rates,
I had assumed that you are trying to test different i2c clock speeds to determine what works and what doesn't, to create something in s/w that can automatically determine the maximum supported clock rate for a i2c slave that "works".
Others like septillion seem to have made the same assumption.

If that is what you are trying to do, you will end up overclocking the slave since the s/w wouldn't know the maximum clock rate of the slave and would keep increasing the clock until it overclocks the slave and something stops working or locks up.

Finally those clock speeds are supported both by some MCU boards and also by some devices.
My goal is to see, which i2c masters will work with which devices at which speed. But for that goal I need to have reliable clock speeds first, and to know, if the speed is set to n Hz and works at n Hz reliably if I had initially set Wire.setClock(n) , apart from having to evaluate tons of data sheets.

The datasheet is what you tells you the maximum clock rate the slave chip is specified to handle so you will need to be looking at datasheets.

Pretty much any Arduino core can support 100k, 400k, and 1M clocks so if the slave supports the clock rate, and the wiring isn't crazy and proper pullups are used, it is going to work.

Often chips can be overclocked much faster than what is specified in their datasheet. For example, PCF8574 chips are specified to only go to 100Khz, but they do work reliably at 400Khz at room temperatures at 5v.

But keep in mind that slaves are often not reliable when overclocked and can misbehave when overclocked.
i.e. they don't hard fail when you get to the magic tip over point; they often start misbehaving which can confuse the master h/w or s/w.

Doing a scan like what the scanner sketch does, is not a good test if the slave supports the i2c clock rate being used.
All it does is select the slave and then disconnect and look for the ack from the slave.
It doesn't actually move any data.
A PCF8574 chip will usually pass that test with a 1MHZ clock but will fail to reliably transfer actual data at 1Mhz.


When playing at this level, you are likely going to have to start digging into the actual library code.
And each core is a bit different in how it works so if you intend to run on multiple cores, you will have to closely examine each core you are planing on using.

In pretty much all the cores, when you call Wire.begin() it initializes the i2c hardware for master mode which includes setting the master clock to its default rate.
The AVR core uses TWI_FREQ for the default i2c clock which is defined to be 100000L or 100Khz in twi.h, but all the cores use 100k as the default clock.

If you want to change the i2c clock from its default you must call setClock() after you call begin() since begin() will set it to the default clock rate (100k) when it initializes the i2c h/w.

i.e. call begin() then you can call setClock() at any time to change the master clock.
There is no need to call begin() again when changing clock rates.

--- bill

yes, thank you, that's of course the way I already did it so far:
Wire.begin() in setup(), and .setClock() in loop() arbitrarily.
But IIUYC, the setClock() function is supposed to work (mostly) properly with boards like AVRs, the Due, nodeMCU, a.s.o. - I actually doubted that so far, because the scanner always returned correct addresses up to 1 MHz even when it was documented that that wasn't supported by the device (e.g., PCF8591), acc to data sheets.
So what is the value of the data sheets and reading all that, if they do not apply in the end, and how can I trust the clock speed, if I don't get a feedback from the board about the actual speed?
I will have to assume that it does it all as fast as it claims, but still some doubt remains - IMO, I'm forced into sort of a "do it or die" attitude, but well, I'll do it then as I don't want to leave it. :-/

tito-t:
yes, thank you, that's of course the way I already did it so far:
Wire.begin() in setup(), and .setClock() in loop() arbitrarily.

That is not what is in the code you showed us in post #2
The code in that post calls setClock() before begin()
But maybe now you are running modified code that you haven't shown us.

But IIUYC, the setClock() function is supposed to work (mostly) properly with boards like AVRs, the Due, nodeMCU, a.s.o. - I actually doubted that so far, because the scanner always returned correct addresses up to 1 MHz even when it was documented that that wasn't supported by the device (e.g., PCF8591), acc to data sheets.

And setClock() probably is working as expected for at least 100k, 400k and 1M.
To tell for sure, you will need to dig down into the code and actually look at it to see how it handles the value that is passed to it.

At 1Mhz on the PCF8591, you are overclocking the slave.
I have seen slave devices respond correctly to an address select when overclocked at a frequency that won't transfer data reliably because it is too fast for the other internal operations inside the chip.
Like I said before, the test that you are using to try to detect the maximum supported clock frequency is too simplistic to actually detect the maximum i2c clock rate that can be reliably used by the slave.

So what is the value of the data sheets and reading all that, if they do not apply in the end, and how can I trust the clock speed, if I don't get a feedback from the board about the actual speed?

The value of the datasheet is that it specifies all the characteristics for the part.
Voltages, currents, frequencies, bus timings, etc..
The datasheet should be trusted.
The fact that you can't easily verify these characteristics using an Arduino and the Arduino Wire library is another matter.
And again, the datasheet is providing guaranteed parameters.
They are values that are guaranteed to work. And to ensure that they work, they obviously have margin in them.
And that is why you can overclock the parts to some extent and they can still work up to a point.
Where that point is often difficult to detect since there are many variables involved such as temperature, voltage, and in the case of i2c SDA/SCL wiring and pullup values and individual chip dies.
This nothing unique to i2c devices. All the parts in the electronic industry work this way.

I will have to assume that it does it all as fast as it claims, but still some doubt remains - IMO, I'm forced into sort of a "do it or die" attitude, but well, I'll do it then as I don't want to leave it. :-/

If you want to see what is really happing you should hook up a logic analyzer so you can see the actual signals.
That will tell you with absolute certainty what is happing on the bus.
If you want to play at this level this is the type of tools you will need.

The biggest rule is to trust the datasheet.
When exceeding datasheet parameters, you are pushing the device beyond it guaranteed ratings.
While chips often do run beyond their guaranteed ratings, it can cause issues some of which can confuse the Master.
There are many factors that go into determining those chip characteristic ratings.
On I2c, it would include temperature, primary Voltage, pullup voltage, length of wires, capacitance of wires, value of pullup resistors, etc..
i.e. while it may work at room temperature at 5v it might not work when hot or cold at 4.5v or if you change the wires.
The value of pullup can dramatically affect the bus operation particularly when trying to go above 400k as the slew rate for larger resistors can be too shallow/too long to get the signal highs snapping back up to the bus voltage. This can cause issues.


You still haven't really fully described what you are really trying to do.
Maybe we could circle back to that.

--- bill

thank you for pointing out the mistake which I made in my code in #2 - it was a c+p error, I corrected that.
(My actual code is different but it was too long to post (forum's code size limitation), so I had to cut it down by several different sources.)

Nonetheless, as I cannot see if my boards are actually running at the speed which was set, IMO it would be better if a API function could return an IO result if the bus speed had been correctly applied or not, according to the values which had been passed to, e.g.

int Wire.setClock(uint32_t freq) // returns OK=0 , or -1 if error

or perhaps a 2nd function, which just tests that.

tito-t:
Nonetheless, as I cannot see if my boards are actually running at the speed which was set,

You can with logic analyzer or scope.

IMO it would be better if a API function could return an IO result if the bus speed had been correctly applied or not, according to the values which had been passed to, e.g.

int16_t Wire.setClock(uint32_t freq) // returns OK=0 , or -1 if error

I agree that the current setClock() API was not good and that from an API perspective yes a return status would be useful to make the interface more robust.
However, like I mentioned previously a pass/fail return status isn't good enough since the clock speed actually used could be modified to a value other than what you requested. In fact that can and will happen quite often due to rounding or h/w clock limitations.
Also Wire library implementers have to decide how to handle requests that are outside the supported values since the API is defined as an absolute frequency vs a set of enums for specific frequencies.
For example, if you request 150 Khz and the h/w can only support 100k or 400k or 1M, what should the code do?
Fail the request or give you 100k? Typically routines that handle h/w clocking or delays give you the closest possible without going over for a clock rate. If that is the case you would get 100k.
Other hardware might be able to actually support 150k or maybe it can't do 150k but could do 140k or 148k
Now you can see why having setClock() return the clock rate actually being used rather than a simple pass/fail is needed.

When playing at this level the only way to really see what is happening is to use a logic analyzer to see what is really happening on the bus signals.
You seem to be headed down the path of overclocking and pushing chips beyond their specs.
When entering that world, you need to use better tools.


Back to a question I have asked nearly every response:
What exactly are you really trying to do?
I am starting to suspect that we have been lead down the XY problem path.

--- bill

I simply want to know if i2c is running at the required clock speed or not, as passed to Wire.setClock(uint32_t freq), and I don't have logic analyzers or oscilloscopes.

e.g., it should work at least like this:

int16_t Wire.setClock(uint32_t freq) // returns OK=0 , or -1 if error

if it fails, setClock() simply should change nothing, leave it all as it is, return -1
if it works, run at the required freq., and return 0.

I think are you are not fully understanding the complexity of the issue or the math involved.
The clock used might by slightly different than what you requested, especially if using values other than
100000, 400000, 1000000
Or depending on the processor clock frequency.

But is it really doesn't matter since the existing setClock() function is a void and communicates no information back to the caller.


I'll ask one more time before I drop away from this thread.
What the heck are you really trying to do? You still haven't told us.

--- bill