I2C - Increasing buffer size to 258 compilation error

I need to communicate with an I2C device that requires me to read the data out in one 258 byte request.

The I2C max buffer size is 32 bytes which I have tried to increase to 258 bytes by modifying both I2C_BUFFER_LENGTH defines in i2c_hal.h. I have changed them from #define I2C_BUFFER_LENGTH 32 to #define I2C_BUFFER_LENGTH 258.

I now get a compile error when I run make clean all PLATFORM=photon from the root firmware directory.

src/stm32f2xx/i2c_hal.c: In function 'HAL_I2C_Request_Data':
src/stm32f2xx/i2c_hal.c:38:25: error: large integer implicitly truncated to unsigned type [-     Werror=overflow]
 #define BUFFER_LENGTH   (I2C_BUFFER_LENGTH)
                     ^
src/stm32f2xx/i2c_hal.c:268:20: note: in expansion of macro 'BUFFER_LENGTH'
     quantity = BUFFER_LENGTH;
                ^
cc1: all warnings being treated as errors
make[1]: *** [../build/target/hal/platform-6/./src/stm32f2xx/i2c_hal.o] Error 1
make[1]: Leaving directory `/home/joe/git/firmware/hal'
make: *** [hal] Error 2

If I reduce the buffer size to 255 the make command compiles successfully. I think this is because the quantity variable is a uint8 and therefore cannot store 258.

Is there any way I can increase the buffer size to 258 bytes?

Thanks

The i2c_hal.h and i2c_hal.c implementation has a lot of uint8_t typed length and index variables. These would need to be changed to go beyond 255 bytes. Also the Wire API assumes uint8_t counts.

uint8_t endTransmission(void);
uint8_t endTransmission(uint8_t);
uint8_t requestFrom(uint8_t, uint8_t);
uint8_t requestFrom(uint8_t, uint8_t, uint8_t);

I don’t like the Wire API and the internal buffering so I just wrote a new I2C master library. I don’t use internal buffering and allow any size transfer. Here are typical examples for read and write.

/** Read from an I2C slave
 *
 * @param[in] address Right justified 7-bit address.
 * @param[out] buf Buffer for read data.
 * @param[in] count Number of bytes to read.
 * @param[in] stop Generate stop if true.
 *
 * @returns true for success else false.
 */
bool read(uint8_t address, void* buf, size_t count, bool stop = true);

/** Write to an I2C slave
 *
 * @param[in] address Right justified 7-bit address.
 * @param[in] buf Data to send.
 * @param[in] count Number of bytes to send.
 * @param[in] stop Generate stop if true.
 *
 * @returns true for success else false.
 */
bool write(uint8_t address, const void* buf, size_t count, bool stop = true);

I also allow chained calls to write so you can send several memory blocks in one I2C write transaction. Good for sending a memory address followed by data with some devices.

I am doing final testing and cleanup and will make this library available soon. I did create a Wire compatible wrapper class so it could be used with a large buffer.

4 Likes

Thanks @whg. This sounds awesome and I would definitely like to try it out.

Is your stop flag used to generate a restart? I need an equivalent to the below:

Wire.beginTransmission(SENSOR);
Wire.write(command);
Wire.endTransmission(false); <---------- this

uint16_t available = Wire.requestFrom(SENSOR, length); //length = 258
while(Wire.available()) *data++ = Wire.read();

return available == length;

Have you done any timed tests yet - it would be really good to know whether I can expect your library to be faster or slower than the Wiring library?

Thanks for sharing!

Yes that's exactly what the stop flag is for. Here is the equivalent of the code you need.

bool readSensor(uint8_t i2cAdd, uint8_t command, void* data, size_t count) {
  return I2C.write(i2cAdd, &command, 1, false) && I2C.read(i2cAdd, data, count);
}

I have checked transfers with a logic analyzer. There are no gaps in SCL so I don't think my library will be slower. I did follow STMicroelectronics advice for polled reads to avoid missing the final NACK for a read.

The EV7 software sequence must complete before the end of the current byte transfer.In case EV7 software sequence can not be managed before the current byte end of transfer, it is recommended to use BTF instead of RXNE with the drawback of slowing the communication.

I don't think this will slow the transfer since read is in a tight loop and I use CRITICAL_SECTION_BLOCK() to prevent another thread from running. I would rather be sure the the transfer is correct than save a μs.

I have run tests at full speed for several days with no failure.

I will package the current version and put it on gitHub here.

2 Likes

OK cool, I look forward to trying this once its released.
Do you know when that might be?

Cheers

A first version is here.

I also published it as the I2cMaster library on Particle Build. It may take a while for it to appear even though I get this message.

Published Library
I2cMaster
0.0.1
An I2C Master library for Particle.

1 Like

Looks good, I will give it a go tomorrow.

Do you still need the following lines if your not using the Wire functions and are using the I2C functions instead?

// Must use macro to avoid predefined Wire object.
WireMaster WireAlt;
#define Wire WireAlt

Cheers

No. The only reason for that section is to test substituting WireMaster for Wire in a large program that used the system version of Wire. The macro save editing all the Wire calls in the program and replacing "Wire" with "WireAlt".

Does this mean I can test whether the large 258 byte read works by just including those lines (and the library etc) in my program and leave the existing wiring stuff as it is?

Cheers

That was the plan.

I didn't test WireMaster with transfers larger than 64 bytes but tired to code it for large transfers.

You will need to edit WireMaster.h and change the size of internal buffers.

#define WIRE_MASTER_BUFFER_LENGTH 32

WireMaster compiles with 300 byte buffers but I only tested it with 64 byte transfers on a DS1307.

I edited the test program and read 288 bytes from the DS1307 with WireMaster. It appeared to work given this.

Table 2 shows the address map for the DS1307 RTC and RAM registers. The RTC registers are located in address locations 00h to 07h. The RAM registers are located in address locations 08h to 3Fh. During a multibyte access, when the address pointer reaches 3Fh, the end of RAM space, it wraps around to location 00h, the beginning of the clock space.

Edit:
Looks like endTransmission() may return the wrong value. I return the number of byte written. The correct return is zero for success and non-zero for an error.

Started trying it now. I will put any points I find in this post.

Wire.setSpeed() is Wire.setClock() in your Wiring wrapper.

I can confirm endTransmission() behaves as you stated above. Is this going to be changed to match the Wiring behavior?

Other than that it all works great!

I will soon post an update so WireMaster will be compatible with Arduino Wire.

Particle is not compatible with Arduino so I will define both.

void setClock(uint32_t);  // Arduino
void setSpeed(uint32_t);  // Particle

I am also starting html Doxygen documentation.

OK great.

Have you done any testing at different/higher speeds than the ones outlined the particle documentation i.e. CLOCK_SPEED_100KHZ and CLOCK_SPEED_400KHZ.
Do you know what the maximum speed is and how the speed would affect stability?

Let me know if I can do anything to help - pull requests, testing, documentation etc.

I updated GitHub with the fixed Wire API and first cut Doxygen html docs.

Here is a sentence from the STM32F205 reference.

It supports the standard mode (Sm, up to 100kHz) and Fm mode (Fm, up to 400 kHz).

I have only run the MPU6050 at 400kHz. The DS1307 is limited to 100KHz.

Ok thanks.

@whg Just wanted to let you know that CRITICAL_SECTION_BLOCK() no longer compiles on 0.4.9. Changing to SINGLE_THREADED_BLOCK() seems to work, however I’m not sure if I should be using ATOMIC_BLOCK() instead?

I have modified I2cMaster so SINGLE_THREADED_SECTION should not be required.

I have updated GitHub and the published library.

1 Like