arduino / ArduinoCore-avr

The Official Arduino AVR core
https://www.arduino.cc
1.24k stars 1.05k forks source link

Wire: Inapporitate TWBR causes Integer Underflow if F_CPU < 1.8 MHz #119

Open biergaizi opened 6 years ago

biergaizi commented 6 years ago

On the Arduino development platform, the high-level TwoWire interface is provided by the Wire library, which itself depends on the underlying code utility/twi.c to manipulate the microcontroller hardware. During initialization, TWI frequency prescaler (TWBR) is calculated and programmed to set SCL clock run at 100 KHz in twi_init() and twi_setFrequency() according to this formula:

TWBR = ((F_CPU / TWI_FREQ) - 16) / 2;

Where F_CPU is CPU frequency and TWI_FREQ is the target SCL frequency, default to 100 kHz under all circumstances.

However, it is flawed. If F_CPU is at 1 MHz, a negative value, -3 will be calculated and set as the frequency prescaler. All frequencies that are slower than 1.8 MHz results an integer underflow, and program TWBR to a large value, causing extremely slow SCL frequency which affects usability of the bus.

The recommanded solution is to lower TWI_FREQ to the largest possible value under a low CPU frequency instead of trying 100 kHz, and provide a fallback if the formula gives unreasonable value, such as TWBR = 2.

We also need to ensure TWBR is not to low at other frequencies to stay within the spec, the comments stated "TWBR should be 10 or higher for master mode", but I haven't checked the datasheet for more information.

Rahulelectobuddy commented 6 years ago

This is because datasheet states that "CPU clock frequency in the Slave must be at least 16 times higher than the SCL frequency" that is we need F_CPU 16 times greater than SCL frequency.