Skip to content

Incorrect I2C SCL frequency #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ghost opened this issue Sep 4, 2017 · 9 comments
Closed

Incorrect I2C SCL frequency #259

ghost opened this issue Sep 4, 2017 · 9 comments
Assignees

Comments

@ghost
Copy link

ghost commented Sep 4, 2017

I observed that the I2C SCL clock in fast mode (400kHz) is only running at 357kHz, despite using 2k2 pull-up resistors that provide a relatively fast rise time.

After some investigation it turns out that the SERCOM::InitMasterWire() function (in the file SERCOM.cpp) is using the incorrect formula for the BAUD register.

The current fomula appears to be taken from Section 25 SERCOM - Serial Communication Interface in the SAMD21 datasheet:

BAUD = SystemCoreClock (48MHz) / (2 * baud rate) - 1

...however, the correct formula should be the one from Section 28 SERCOM - Inter-Integrated Circuit:

BAUD = SystemCoreClock (48MHz) / (2 * SCL_Frequency) - 5 - (SystemCoreClock (48MHz) * T(rise) / 2)

...where T(rise) is the time taken for the pull-up resistor to pull the SCL line high, (and will very much depend on the resistor values used).

Using 2k2 pull-up resistors I measured the rise time as 90ns, therefore the BAUD value in this instance should be:

BAUD = 48MHz / (2 * 400000) - 5 - (48MHz * 90ns / 2) = 52.84

Using a BAUD value around 52 or 53 gives a SCL frequency close to 400kHz.

@Adminius
Copy link
Contributor

do you have also checked if other frequencys are ok? (e.g. 1MHz? )

@ghost
Copy link
Author

ghost commented Sep 11, 2017

The SAMD21 datasheet states that for standard I2C fast mode plus at 1MHz, "requires a nominal high to low SCL ratio 1:2". This involves setting the BAUDLOW bitfield in the BAUD register.

In this case the SCL frequency is determined by the formula:

SCL_Frequency = SystemCoreClock (48MHz) / (10 + BAUDLOW + BAUD + (SystemCoreClock (48Mhz) * T(rise)))

As it currently stands the Wire library doesn't take this into account.

@deladriere
Copy link

@Adminius
I measure :
350 kHz when I set the clock to 400 kHz
735 kHz when I set the clock to 1 MHz
1.16 MHz when I set the clock to 2 MHz
1.45 MHz when I set the clock to 3 MHz
using Wire.setClock(freq);

@sandeepmistry
Copy link
Contributor

Fixed via #287.

@sandeepmistry sandeepmistry added this to the Release 1.6.18 milestone Jan 25, 2018
@patryk02
Copy link

patryk02 commented Sep 6, 2018

I have a similar problem with my MKRzero board.
I want to lower my I2C bus clock to a range of 10kHz - 25kHz with the "Wire.setClock(10000);" function.
What I get under the oscilloscope is a clock signal of 72kHz.

Does somebody know a different way to set a I2C clock?

@ghost
Copy link
Author

ghost commented Sep 6, 2018

The issue is that the I2C's BAUD register is only 8-bits wide and can therefore only store values between 0 and 255.

As this BAUD value is used to divide the 48MHz system clock as described in the formula above, it's not possible to get low I2C frequencies using the Wire.setClock() function. The only way to get a lower frequency is to clock the I2C SERCOM (Serial Communications) module with a slower generic clock (GCLK), instead of the default 48MHz on GCLK0.

This involves setting up a slower GCLK and attaching it as the clock source of I2C SERCOM. A slower GCLK however will cause the whole SERCOM module, including registers access and synchronization to run slower.

@ghost
Copy link
Author

ghost commented Sep 6, 2018

Hi patryk02,

Here's some code that'll run the I2C at 10kHz:

// Sketch to run the I2C at 10kHz

#include <Wire.h>

void setup() 
{
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(12) |          // Divide the 48MHz clock source by divisor 12: 48MHz/12=4MHz
                     GCLK_GENDIV_ID(3);             // Select Generic Clock (GCLK) 3
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |            // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |          // Enable GCLK3
                      GCLK_GENCTRL_SRC_DFLL48M |    // Set the 48MHz clock source
                      GCLK_GENCTRL_ID(3);           // Select GCLK3
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization

  SerialUSB.begin(115200);                          // Set-up the native USB port
  while(!SerialUSB);                                // Wait until the native USB port is ready

  Wire.begin();                                     // Set-up the I2C port
  sercom3.disableWIRE();                            // Disable the I2C SERCOM
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |          // Enable GCLK3 as clock soruce to SERCOM3
                      GCLK_CLKCTRL_GEN_GCLK3 |      // Select GCLK3
                      GCLK_CLKCTRL_ID_SERCOM3_CORE; // Feed the GCLK3 to SERCOM3
  while (GCLK->STATUS.bit.SYNCBUSY);                // Wait for synchronization  
  SERCOM3->I2CM.BAUD.bit.BAUD = 4000000 / (2 * 10000) - 1;   // Set the I2C clock rate to 10kHz
  sercom3.enableWIRE();                             // Enable the I2C SERCOM  
}

void loop() 
{
  Wire.beginTransmission(0x68);                     // Send command to MPU6050
  Wire.write(0x75);                                 // Set sub address to the WHO_AM_I register
  Wire.endTransmission(false);                      // Transfer request
  Wire.requestFrom(0x68, 1);                        // Read the WHO_AM_I register
  SerialUSB.println(Wire.read(), HEX);              // Send the result to the console
  delay(1000);                                      // Wait 1 second
}

On the MKRZero just change the "sercom3" and "SERCOM3" references to "sercom0" and "SERCOM0". I tested the code on my Arduino Zero.

@patryk02
Copy link

patryk02 commented Sep 7, 2018 via email

boseji pushed a commit to go-ut/combined-ArduinoCore-samd that referenced this issue Nov 23, 2020
@noamyogev84
Copy link

noamyogev84 commented Aug 2, 2023

Hi @martinl1
Thanks a lot for this example above.
I'm trying to set my MKR Zero to read/write from/to I2C slave in 3.4Mhz.
my device supports high speed and configured for that.
How do I do that?
using your example above doesn't work for me :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants