Removing all error checking, the code looks more or less like this:
Code: Select all
// initialize the I2C bus with i2c_param_config and i2c_driver_install,
// then in a loop, use the following code to read individual UBX messages
uint8_t *buffer = ...;
uint8_t reg = 0xfd;
// write 0xfd to the chip (select register 0xfd) and read two bytes
i2c_master_write_read_device(I2C_PORT, ZEDF9P_ADDR, ®, 1, buffer, 2, I2C_TIMEOUT);
// buffer[0] now contains the value in register 0xfd and buffer[1] the value in register 0xfe,
// the number of available bytes is big endian encoded
uint16_t available = (buffer[0] << 8) | buffer[1];
if (available > 6) { // UBX messages are longer than 6 bytes
// the register pointer is now at 0xff, so we can read actual data without requesting a specific register
// first read the 6 byte UBX header:
i2c_master_read_from_device(I2C_PORT, ZEDF9P_ADDR, buffer, 6, I2C_TIMEOUT);
// buffer[0] and buffer[1] will now contain the UBX prefix 0xb562
// buffer[2] and buffer[3] will contain the message type (class and id)
// buffer[4] and buffer[5] the (now little endian encoded) length of the actual message data:
uint16_t length = buffer[4] | (buffer[5] << 8);
// read the remaining part of the UBX message and two additional bytes with checksum data:
i2c_master_read_from_device(I2C_PORT, ZEDF9P_ADDR, buffer + 6, length + 2, I2C_TIMEOUT);
// the buffer should now contain one complete UBX message with header, payload data and checksum bytes
}
- the first call to i2c_master_write_read_device would succeed and indicate that 100 bytes of data are available
- the second call to i2c_master_read_from_device (reading the 6 bytes UBX header) would fail with ESP_ERR_TIMEOUT within milliseconds, although a much higher timeout was specified with I2C_TIMEOUT
- reading the number of available bytes from register 0xfd and 0xfe again would show that at least some number of bytes or even all 6 bytes have actually been read although the read method returned ESP_ERR_TIMEOUT
Considering that I may have a glitch, perhaps even somewhere else in the software, I reimplemented the core logic for I2C communication in a tiny project in Arduino IDE using the Wire library abstraction. The library simplifies the code and leaves less room for own mistakes and I could actually confirm, that when implemented with the Wire library, the communication worked as expected also on the ESP32.
Digging through the implementation of the Wire library for ESP32, the only obviously relevant difference I could find is that in the function i2cInit in esp32-hal-i2c.c the function i2c_set_timeout is called like this:
Code: Select all
i2c_set_timeout((i2c_port_t)i2c_num, I2C_LL_MAX_TIMEOUT);
Adding this function call to my software solved the problem there as well. I can now run the I2C bus at 400 kbps (the fastest speed supported by the u-blox chip) without running into the problems with the ESP_ERR_TIMEOUT error.
Just to again clarify: Using i2c_set_timeout is only required when the code runs on the ESP32 chip. When the code runs on the ESP32-S3 chip, it works without using i2c_set_timeout.
Since the documentation is very weak at this point, here are two questions:
What is the difference between the timeout set with i2c_set_timeout and the timeout passed as the last argument to the other read and write functions? In the Wire library, the value passed to i2c_set_timeout (0xFFFFF) seems to translate to about 13ms (it is documented as the number of 80 MHz ticks) and the default value of the timeout passed to the read/write functions is set to 50ms. This does not make obvious sense to me.
Why is the usage of i2c_set_timeout only required on the ESP32 chip and not on the ESP32-S3 chip? Looking through the code examples linked in the Espressif documentation, I find no actual usage of the i2c_set_timeout function and I don't get the impression that it is supposed to be required.