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