Page 1 of 1

I2C and RTOS

Posted: Fri Feb 18, 2022 6:53 pm
by jed_esp32
Hi, I am seeing an anomaly with the Wire library - not sure whether to call it an issue.

My app, ported from AVR, has one or more MCP23008 expanders connected to one I2C bus, polled at 50 mS intervals. All I2C operations are performed in a single thread, using Wire. It all works fine when the polling is done from loop(), but when I call it from a FreeRTOS task, I get many read errors. Without the RTOS, there are essentially zero errors, whereas with the RTOS there can be many (varies from several an hour to several a second).

After experimenting, I find the errors can be eliminated entirely by forcing the I2C task onto the same core (#1) as loop(). FWIW, running a WiFi handler either as a task on core 0, or from loop(), or not at all, makes no difference.

The errors I get are all generated after the requestFrom() call and show a lastError() of 2 (ACK err). Since commands are buffered, I'm not certain what the ACK relates to, but write operations report no errors.

I am inclined to think this is some odd interaction between Wire and whatever is running on core 0, but I'm willing to be corrected.

So I suppose I am asking if anyone who knows more about Wire and the RTOS can suggest what the issue might be? I've looked at everything I can think of without any answer.

FYI, here is the code I am using to read and write; it is in a class derived from TwoWire:

Code: Select all

	size_t write_reg (const byte addr, const byte reg, const byte val)
	{
		size_t ret = 0;
		beginTransmission (addr); //starts talking to slave device
		if ((ret = write (reg)) != size_t (0)) //selects the register specified; unlikely to fail
			ret = write (val);
		if (endTransmission () != I2C_ERROR_OK) { //ends communication with the device
			log_e ("endTx fail");
			ret = 0;
		};
		return ret;
	};
	// read a specified device register; param reference is unchanged if error
	bool read_reg (const byte addr, const byte reg, byte &val)
	{
		bool ok = false;

		beginTransmission (addr); //starts talking to slave device
		if (write (reg) == 0)
			log_d ("reg write err %u", lastError());	// would be a I2C_ERROR_MEMORY
		else {
			uint8_t err = endTransmission (false);
			if (err != I2C_ERROR_OK) {
				log_d ("endTx error %u", err);
			} else {
				requestFrom (addr, uint8_t (1));
				if (lastError () != I2C_ERROR_OK){
					log_d ("read req err %u %s", lastError (), getErrorText (lastError ()));
				} else {
					if (available ()) {
						val = uint8_t (read ());
						ok = true;
					} else
						log_d ("nothing read");
				}
			}
		}
		return ok;

Re: I2C and RTOS

Posted: Sat Sep 30, 2023 11:47 pm
by DanMan32
You're excluding a substantial relevant part of your code.
Not sure if it would help your situation, but have you considered using an I2C library?

That said, I have a similar problem.
I am writing a program to read from an ADC1115 to calculate RMS voltage from a current sensor. Three sensors actually, having the ADC switch between 3 differential inputs, and having the ADC send an interrupt whenever a continuous conversion has valid data.
Interrupt routine signals an RTOS task through RTOS semaphore that there's data available
The RTOS task being signaled at priority 7 places the results in an RTOS queue where the main RTOS loop reads in all that's in the queue for the square part of RMS, and if designated # of samples are read in and accumulated, calculates the root mean of the results, then updates the continuous sample mode to the next MUX input, rotating between 3 of them.
For testing I have results of all 3 differential inputs printed every second.

What I found is that often enough the results from one of the inputs is registered and displayed as one of the other inputs
What I think I discovered is that when the code switches inputs, it doesn't get registered by the ADC and so it continues reading from the previously selected input.

Since I found that the read of each sample fits between each interrupt, and even calculating the final results fits if I use a sample rate of 475 SPS, verified by scoping interrupt and test pulses, I moved the code within the task running at priority 7 where I no longer seem to have the problem of input switching of the ADC input skewing from where it is being stored.

Seems either having I2C calls to the same I2C slave address across tasks messes up the I2C communications, or the main loop task is going too slow for I2C. I even went as far as modifying the Adafruit library for the ADC1115 to read the ADC1115 registry to verify the MUX change took effect and found mismatches.

So in your case, you can try either keeping all code handing the I2C communications for the device within the same task, or increase the priority of the tasks that are using the I2C.