I noticed that the Wire library's setClock() function was only giving me 357kHz instead of the 400kHz that I expected. After some investigation it turns out that the Wire library's baud rate formula for the Arduino Zero is incorrect.
The incorrect formula has been taken from the SERCOM Serial Communication Interface in section 25 of the datasheet:
BAUD (register value) = SystemClock (48MHz) / (2 * baud rate frequency) - 1
...however the correct formula is in the SERCOM I2C in section 28:
BAUD (register value) = SystemClock (48MHz) / (2 * SCL frequency) - 5 - (SystemClock (48MHz) * T(rise) / 2)
The T(rise) is the SCL signal rise time. On my custom board using 2k2 pull-up resistors, I measured this as 90ns, (0.00000009s).
The system clock is:
48000000 / 32768 (crystal) = 1464.84375
32768 * 1464 (integer multiplier) = 47972352Hz
Therefore that BAUD register value for a SCL frequency of 400kHz is:
BAUD = 47972352 / (2 * 400000) - 5 - (47972352 * 0.00000009 / 2)
BAUD = 47972352 / (2 * 400000) - 7 = 52
With the BAUD register loaded with 52 the I2C SCL signal frequency is pretty much spot on 400kHz.
To set the I2C frequency to 400kHz just include the following code after Wire.begin():
Wire.begin(); // Set-up I2C communication
sercom3.disableWIRE(); // Disable the I2C bus
SERCOM3->I2CM.BAUD.bit.BAUD = /*SystemCoreClock*/47972352 / (2 * 400000) - 7; // Set the I2C SCL frequency to 400kHz
sercom3.enableWIRE(); // Restart the I2C bus
Wow, that's pretty big error... Kind of surprised it's gone undiscovered for this long.
I've flagged it as an issue in the ArduinoCore-samd repository on Github: https://github.com/arduino/ArduinoCore-samd/issues/259 (https://github.com/arduino/ArduinoCore-samd/issues/259)
When I try this on a MKR Zero, I still get a SCL of 361kHz. I use sercom0 (not sercom3) as sercom0 is used for I2C on the MKR Zero (checked in variants.cpp file).
Is the SystemCoreClock different on the MKR Zero? Where can I check this?
Edit: The cpu speed of the MKR Zero should be the same as the normal Zero, I found this in the boards.txt file.
I check the I2C clock speed using a logical analyzer, and the clock speed is not stable, it can vary with 30-40kHz every time I do an I2C read.
The I2C SCL frequency has been fixed, so you should be getting around 400kHz. Are you using the latest Arduino SAMD core? It's currently at 1.6.19.
It also shouldn't matter which Sercom you're using.
You can check this in the Arduino SAMD core file "SERCOM.cpp", in the initMasterWire() function:
// Synchronous arithmetic baudrate
sercom->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000));