cancel
Showing results for 
Search instead for 
Did you mean: 

I2C slave clock stretching to allow time to prepare response data

i2ci2ci2ci2c
Associate II

I have the following setup:    Master<---------I2C------------>STM slave

The master communicates like this:

1) Master I2C write: [Start bit, I2C address (from master)], [Req Data1 (from master)]

2) Master I2C read: [Start bit, I2C address (from master)], [Resp Data1 (from slave)],......,[Resp DataN (from slave)]

With a "vanilla" STM slave implementation there is a problem because the STM slave requires time in between (1) and (2) to decode the "request type" byte and prepare the response data. However the master has no delay between (1) and (2), so the result is that the initial response bytes are invalid.

Normally this sort of issue is handled on slave I2C devices by clock stretching. In the STM32F401RE user manual the description of the clock stretching implementation seems to suggest that it operates on a single I2C transfer, not between I2C transfers. Is that correct? For example from the diagram below is it possible to hold clock stretching beyond EV4?

Screenshot 2023-06-28 at 10.35.31.png I've had trouble finding STM I2C slave example code (I guess normally it is the master) and the examples that I have found don't have custom clock stretching. 

Can someone explain to me how to implement clock stretching between I2C transfers? Or perhaps there is a better way to prevent the master clocking the slave data out before it is ready?

1 ACCEPTED SOLUTION

Accepted Solutions
i2ci2ci2ci2c
Associate II

Thanks for your responses. I've implemented like this which seems to work.

void I2C1_EV_IRQHandler(void)
{
	uint32_t SR1_register = I2C1->SR1;
	uint32_t SR2_register = I2C1->SR2;

	if (SR1_register & I2C_SR1_ADDR)						// EV1
	{
		// Only clear ADDR flag for master write operations.
		// Otherwise leave it set to hold the SCL line low for clock stretching.
		if (SR2_register & I2C_SR2_TRA)
		{
			// Matching I2C address received from master READ
			if (waiting_for_response_data_ready)
			{
				// DON'T clear the ADDR flag so clock stretching is started
				// Instead disable the I2C1_EV interrupt until the response data is ready
				DISABLE_I2C1_EVENT_INTERRUPT();
			}
			else
			{
				// Response data already prepared, clear the ADDR flag as usual
				__I2C_CLEAR_ADDRFLAG(I2C1);
			}
		}
		else
		{
			// Matching I2C address received from master WRITE
			// Clear ADDR flag
			__I2C_CLEAR_ADDRFLAG(I2C1);
		}
	}

	if (SR1_register & I2C_SR1_RXNE)						// EV2
	{
		// Data byte received from master
		request_data_byte = I2C1->DR;

		waiting_for_response_data_ready = true;
	}


	if (SR1_register & I2C_SR1_STOPF)						// EV4
	{
		// Stop received from master
		// Clear STOPF flag
		__I2C_CLEAR_STOPFLAG(I2C1);
	}
}

View solution in original post

6 REPLIES 6
Pavel A.
Evangelist III

The slave can simply not respond while it is not ready. Many devices behave this way, such as eeproms.

i2ci2ci2ci2c
Associate II

What do you mean by "not respond"? If the STM slave doesn't drive the SDA line when the master is clocking the SCL line, then the master will get invalid data won't it?

TDK
Guru

> Can someone explain to me how to implement clock stretching between I2C transfers? Or perhaps there is a better way to prevent the master clocking the slave data out before it is ready?

This is not possible since the slave does not have control of the clock between transfers. It only controls it after the ACK, and that is where clock stretching is implemented in the STM32. This is the proper place to implement it when the slave needs time to process data before responding.

If you feel a post has answered your question, please click "Accept as Solution".
Bob S
Principal

You have two opportunities to slow down the transfer.  The first is to delay ACK-ing the data byte in the 1st sequence (i.e. read the data byte, process it, gather the data for your response, then tell the STM32 to ACK).  Not sure this is possible with the HAL library.  The second place is to clock stretch the ACK to the address byte in the 2nd (read) sequence.  You can use the HAL_I2C_AddressCallback() for this.

Pavel A.
Evangelist III

What do you mean by "not respond"?

Not ACKing the device address. Pretending not being there.

i2ci2ci2ci2c
Associate II

Thanks for your responses. I've implemented like this which seems to work.

void I2C1_EV_IRQHandler(void)
{
	uint32_t SR1_register = I2C1->SR1;
	uint32_t SR2_register = I2C1->SR2;

	if (SR1_register & I2C_SR1_ADDR)						// EV1
	{
		// Only clear ADDR flag for master write operations.
		// Otherwise leave it set to hold the SCL line low for clock stretching.
		if (SR2_register & I2C_SR2_TRA)
		{
			// Matching I2C address received from master READ
			if (waiting_for_response_data_ready)
			{
				// DON'T clear the ADDR flag so clock stretching is started
				// Instead disable the I2C1_EV interrupt until the response data is ready
				DISABLE_I2C1_EVENT_INTERRUPT();
			}
			else
			{
				// Response data already prepared, clear the ADDR flag as usual
				__I2C_CLEAR_ADDRFLAG(I2C1);
			}
		}
		else
		{
			// Matching I2C address received from master WRITE
			// Clear ADDR flag
			__I2C_CLEAR_ADDRFLAG(I2C1);
		}
	}

	if (SR1_register & I2C_SR1_RXNE)						// EV2
	{
		// Data byte received from master
		request_data_byte = I2C1->DR;

		waiting_for_response_data_ready = true;
	}


	if (SR1_register & I2C_SR1_STOPF)						// EV4
	{
		// Stop received from master
		// Clear STOPF flag
		__I2C_CLEAR_STOPFLAG(I2C1);
	}
}