cancel
Showing results for 
Search instead for 
Did you mean: 

I2c Restart on STM32L using Standard Peripheral Library (not HAL)

Burns
Associate II

Hi,

I've been using an STM32L151 for a number of years...long enough ago that the firmware I wrote uses the SPL, not HAL, and I'm really not interested in re-writing the firmware!

My problem is that a new I2c device I am connecting requires a RESTART between writing a command and reading the data.  I have seen this problem from HAL users, and they are told to use the memory access routines rather than the standard routines.  There are no such things in the SPL.

I have removed the call to I2C_GenerateSTOP following the transmission of the command, but now I get an interrupt that says there was no ACK.

Can anyone either make a suggestion, or point me to some example code?  I guess I should say that I am using a real-time OS with devices working with interrupts, so code or suggestions that say "loop until bit x is set" don't help too much unless bit x being set can generate an interrupt.

Thanks!
P.S. I have had an account and conversations in the forum over several years, but apparently ST disabled my account so I look like a newbie here, but have been around for a while.  Thanks!

1 ACCEPTED SOLUTION

Accepted Solutions
TDK
Guru

From experience, the I2C peripheral is extremely complicated on the STM32F4 series and writing an interrupt handler to handle every situation requires some skill. By comparison, the STM32H7 has a much easier to work with peripheral.

The STM32L1 series looks to have the same peripheral as the STM32F4, except it's missing the FLTR register. Perhaps it's slightly different, but it looks pretty similar so the below should help.

This is a flowchart for how to write the event handler you're looking for on the STM32F4 and will likely be similar for you.

TDK_0-1701192768715.png

The code you posted has multiple bugs associated in it, but if you follow the flowchart to correct it, it should work. Good luck.

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

View solution in original post

7 REPLIES 7
TDK
Guru

To generate a repeated start, follow the EEPROM example in the SPL examples library. Can't see your code, so I don't know what part you're missing.

In particular, the repeated start event itself is initiated with I2C_GenerateSTART();

/**
  * @brief  Reads a block of data from the EEPROM.
  * @PAram  pBuffer : pointer to the buffer that receives the data read from 
  *         the EEPROM.
  * @PAram  ReadAddr : EEPROM's internal address to start reading from.
  * @PAram  NumByteToRead : pointer to the variable holding number of bytes to 
  *         be read from the EEPROM.
  * 
  *        @note The variable pointed by NumByteToRead is reset to 0 when all the 
  *              data are read from the EEPROM. Application should monitor this 
  *              variable in order know when the transfer is complete.
  * 
  * @note When number of data to be read is higher than 1, this function just 
  *       configures the communication and enable the DMA channel to transfer data.
  *       Meanwhile, the user application may perform other tasks.
  *       When number of data to be read is 1, then the DMA is not used. The byte
  *       is read in polling mode.
  * 
  * @retval sEE_OK (0) if operation is correctly performed, else return value 
  *         different from sEE_OK (0) or the timeout user callback.
  */
uint32_t sEE_ReadBuffer(uint8_t* pBuffer, uint16_t ReadAddr, uint16_t* NumByteToRead)
{  
  /* Set the pointer to the Number of data to be read. This pointer will be used 
      by the DMA Transfer Completer interrupt Handler in order to reset the 
      variable to 0. User should check on this variable in order to know if the 
      DMA transfer has been complete or not. */
  sEEDataReadPointer = NumByteToRead;
  
  /*!< While the bus is busy */
  sEETimeout = sEE_LONG_TIMEOUT;
  while(I2C_GetFlagStatus(sEE_I2C, I2C_FLAG_BUSY))
  {
    if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
  }
  
  /*!< Send START condition */
  I2C_GenerateSTART(sEE_I2C, ENABLE);
  
  /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */
  sEETimeout = sEE_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(sEE_I2C, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
  }
  
  /*!< Send EEPROM address for write */
  I2C_Send7bitAddress(sEE_I2C, sEEAddress, I2C_Direction_Transmitter);

  /*!< Test on EV6 and clear it */
  sEETimeout = sEE_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(sEE_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
  } 

#ifdef sEE_M24C08  
  
  /*!< Send the EEPROM's internal address to read from: Only one byte address */
  I2C_SendData(sEE_I2C, ReadAddr);  

#elif defined (sEE_M24C64_32) || defined (sEE_M24LR64)

  /*!< Send the EEPROM's internal address to read from: MSB of the address first */
  I2C_SendData(sEE_I2C, (uint8_t)((ReadAddr & 0xFF00) >> 8));    

  /*!< Test on EV8 and clear it */
  sEETimeout = sEE_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(sEE_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
  {
    if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
  }

  /*!< Send the EEPROM's internal address to read from: LSB of the address */
  I2C_SendData(sEE_I2C, (uint8_t)(ReadAddr & 0x00FF));    
  
#endif /*!< sEE_M24C08 */

  /*!< Test on EV8 and clear it */
  sEETimeout = sEE_FLAG_TIMEOUT;
  while(I2C_GetFlagStatus(sEE_I2C, I2C_FLAG_BTF) == RESET)
  {
    if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
  }
  
  /*!< Send STRAT condition a second time */  
  I2C_GenerateSTART(sEE_I2C, ENABLE);
  
  /*!< Test on EV5 and clear it (cleared by reading SR1 then writing to DR) */
  sEETimeout = sEE_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(sEE_I2C, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
  } 
  
  /*!< Send EEPROM address for read */
  I2C_Send7bitAddress(sEE_I2C, sEEAddress, I2C_Direction_Receiver);  
  
  /* If number of data to be read is 1, then DMA couldn't be used */
  /* One Byte Master Reception procedure (POLLING) ---------------------------*/
  if ((uint16_t)(*NumByteToRead) < 2)
  {
    /* Wait on ADDR flag to be set (ADDR is still not cleared at this level */
    sEETimeout = sEE_FLAG_TIMEOUT;
    while(I2C_GetFlagStatus(sEE_I2C, I2C_FLAG_ADDR) == RESET)
    {
      if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
    }     
    
    /*!< Disable Acknowledgement */
    I2C_AcknowledgeConfig(sEE_I2C, DISABLE);   
    
    /* Clear ADDR register by reading SR1 then SR2 register (SR1 has already been read) */
    (void)sEE_I2C->SR2;
    
    /*!< Send STOP Condition */
    I2C_GenerateSTOP(sEE_I2C, ENABLE);
    
    /* Wait for the byte to be received */
    sEETimeout = sEE_FLAG_TIMEOUT;
    while(I2C_GetFlagStatus(sEE_I2C, I2C_FLAG_RXNE) == RESET)
    {
      if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
    }
    
    /*!< Read the byte received from the EEPROM */
    *pBuffer = I2C_ReceiveData(sEE_I2C);
    
    /*!< Decrement the read bytes counter */
    (uint16_t)(*NumByteToRead)--;        
    
    /* Wait to make sure that STOP control bit has been cleared */
    sEETimeout = sEE_FLAG_TIMEOUT;
    while(sEE_I2C->CR1 & I2C_CR1_STOP)
    {
      if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
    }  
    
    /*!< Re-Enable Acknowledgement to be ready for another reception */
    I2C_AcknowledgeConfig(sEE_I2C, ENABLE);    
  }
  else/* More than one Byte Master Reception procedure (DMA) -----------------*/
  {
    /*!< Test on EV6 and clear it */
    sEETimeout = sEE_FLAG_TIMEOUT;
    while(!I2C_CheckEvent(sEE_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
    {
      if((sEETimeout--) == 0) return sEE_TIMEOUT_UserCallback();
    }  
    
    /* Configure the DMA Rx Channel with the buffer address and the buffer size */
    sEE_LowLevel_DMAConfig((uint32_t)pBuffer, (uint16_t)(*NumByteToRead), sEE_DIRECTION_RX);
    
    /* Inform the DMA that the next End Of Transfer Signal will be the last one */
    I2C_DMALastTransferCmd(sEE_I2C, ENABLE); 
    
    /* Enable the DMA Rx Channel */
    DMA_Cmd(sEE_I2C_DMA_CHANNEL_RX, ENABLE);    
  }
  
  /* If all operations OK, return sEE_OK (0) */
  return sEE_OK;
}

 

If you feel a post has answered your question, please click "Accept as Solution".
Burns
Associate II

Thanks.  I'll double check.  Yes, we are already doing the start on the read that follows the write.  I'm having trouble with NOT enabling stop after the first write.  As I say, I'll read this code more carefully to see if it can be adapted to my interrupt based code.  Thanks!

Piranha
Chief II

My problem is that a new I2c device I am connecting requires a RESTART between writing a command and reading the data.  I have seen this problem from HAL users, and they are told to use the memory access routines rather than the standard routines.  There are no such things in the SPL.

With a decent API everything can be done with two functions similar to those HAL memory access functions. The HAL "standard" ones and especially the ones provided by SPL and LL are basically useless as they do not provide any useful functionality. 

Post edited to adhere to community guidelines. 

Burns
Associate II

Thank you.  I do have the restart working, but now the stop at the end of the read is intermittent.  It seems to depend on what came before on the bus.  A couple things though:  The example above uses DMA.  The driver I am working on (written by someone else!) uses per-byte interrupts.  I understand the driver enough to have turned off the stop enable on the write section of the transaction, but to be honest I don't understand when he is enabling stop in the read section (sometimes when there are 3 bytes left, sometimes fewer!).   Here is the code (I'm pretty sure the original writer did not understand either and just messed with it till it worked).  This is the interrupt section where most of the work is done.  (Start is enabled in the main code).  Thanks again!

	else /* MASTER_MODE_RECEIVER */
	{
		ReportInterruptRoutine(Int_I2C1_1);
		/* Check on EV5 */
		if (I2C_GetITStatus(I2C1, I2C_IT_SB ) == SET) {
			/* Send slave Address for read */
			I2C_Send7bitAddress(I2C1, I2C1data.addr, I2C_Direction_Receiver );
			if (I2C1data.NumberOfBytesToReceive == 0x03) {
				/* Disable buffer Interrupts */
				I2C_ITConfig(I2C1, I2C_IT_BUF, DISABLE);
			} else {
				/* Enable buffer Interrupts */
				I2C_ITConfig(I2C1, I2C_IT_BUF, ENABLE);
			}
		}

		else if (I2C_GetITStatus(I2C1, I2C_IT_ADDR ) == SET) {
			if (I2C1data.NumberOfBytesToReceive == 1) {
				// Only a single byte to receive.  Make sure position is set
				// to current (might have been left on next on a previous transaction
				// and tell it not to ack (i.e. to Nak).
				I2C_NACKPositionConfig(I2C1, I2C_NACKPosition_Current);
				I2C_AcknowledgeConfig(I2C1, DISABLE);
			}
			/* Clear ADDR Register */
			(void) (I2C1 ->SR1);
			(void) (I2C1 ->SR2);
			if (I2C1data.NumberOfBytesToReceive == 1) {
				I2C_GenerateSTOP(I2C1, ENABLE);
			}

			if (I2C1data.NumberOfBytesToReceive == 2) {
				// So here we are going to read two bytes and NAK the 2nd one
				// which is the whole bit about "next" below.
				I2C_AcknowledgeConfig(I2C1, DISABLE);
				I2C_NACKPositionConfig(I2C1, I2C_NACKPosition_Next );
				/* Disable buffer Interrupts */
				// I think we don't want buffer interrupts because there is room
				// for two so we don't need to here about the first one.  We still
				// get an event interrupt when all is done.
				I2C_ITConfig(I2C1, I2C_IT_BUF, DISABLE);
			}
		} else if ((I2C_GetITStatus(I2C1, I2C_IT_RXNE ) == SET)
				&& (I2C_GetITStatus(I2C1, I2C_IT_BTF ) == RESET)) {
			/* Store I2C received data */
			I2C1data.RxBuffer[I2C1data.Rx_Idx++] = I2C_ReceiveData(I2C1 );
			I2C1data.NumberOfBytesToReceive--;

			if (I2C1data.NumberOfBytesToReceive == 0x03) {
				/* Disable buffer Interrupts */
				I2C_ITConfig(I2C1, I2C_IT_BUF, DISABLE);
			}

			if (I2C1data.NumberOfBytesToReceive == 0x00) {
				/* Disable Error and Buffer Interrupts */
				I2C_ITConfig(I2C1, (I2C_IT_EVT | I2C_IT_BUF ), DISABLE);
			}
		}
		/* BUSY, MSL and RXNE flags */
		// I think we get here when there are more than 3 byte to read initially
		else if (I2C_GetITStatus(I2C1, I2C_IT_BTF ) == SET) {
			/* if Three bytes remaining for reception */
			if (I2C1data.NumberOfBytesToReceive == 3) {
				// Really?  Shouldn't this be on 2?  I bet it is here because
				// position was incorrect set to next rather than current.  We'll
				// have to debug if we ever read 3 at a time.
				I2C_AcknowledgeConfig(I2C1, DISABLE);
				/* Store I2C received data */
				I2C1data.RxBuffer[I2C1data.Rx_Idx++] = I2C_ReceiveData(I2C1 );
				I2C1data.NumberOfBytesToReceive--;
			} else if (I2C1data.NumberOfBytesToReceive == 2) {
				I2C_GenerateSTOP(I2C1, ENABLE);

				/* Store I2C received data */
				I2C1data.RxBuffer[I2C1data.Rx_Idx++] = I2C_ReceiveData(I2C1 );
				I2C1data.NumberOfBytesToReceive--;
				/* Store I2C received data */
				I2C1data.RxBuffer[I2C1data.Rx_Idx++] = I2C_ReceiveData(I2C1 );
				I2C1data.NumberOfBytesToReceive--;
				/* Disable Error and Buffer Interrupts */
				I2C_ITConfig(I2C1, (I2C_IT_EVT | I2C_IT_BUF ), DISABLE);
			} else {
				/* Store I2C received data */
				I2C1data.RxBuffer[I2C1data.Rx_Idx++] = I2C_ReceiveData(I2C1 );
				I2C1data.NumberOfBytesToReceive--;
			}
		}
	}

 

TDK
Guru

From experience, the I2C peripheral is extremely complicated on the STM32F4 series and writing an interrupt handler to handle every situation requires some skill. By comparison, the STM32H7 has a much easier to work with peripheral.

The STM32L1 series looks to have the same peripheral as the STM32F4, except it's missing the FLTR register. Perhaps it's slightly different, but it looks pretty similar so the below should help.

This is a flowchart for how to write the event handler you're looking for on the STM32F4 and will likely be similar for you.

TDK_0-1701192768715.png

The code you posted has multiple bugs associated in it, but if you follow the flowchart to correct it, it should work. Good luck.

If you feel a post has answered your question, please click "Accept as Solution".
Burns
Associate II

Wow!  That chart is amazing!   That actually makes some stuff a lot more clear!  And it does look as though it probably matches the STM32L peripheral.  Thank you!

Also, I'm not at all surprised the code is buggy.  No doubt it works for the few peripherals we were using, but has bugs that keep it from being more general.

Burns
Associate II

I have solved my problem, not exactly by following the chart to the code specified, but the code and chart did help me to better understand how the peripherals work.  The fix can be described in the chart, or in my code.  In both cases, there is a read transaction going on and there are exactly three bytes left when the BTF flag is set (line 68 in my code).  The event chart shows that one should clear CR1_ACK and then read a byte from the DR.  This is what "my" code did.  But what I had to do was add a call to I2C_NACKPositionConfig(I2C1, I2C_NACKPosition_Current ), which essentially clears CR1_POS.  I'm not entirely sure why it was not clear in the first place (as the chart clearly assumes) but that made the difference.  It appears that it should have been set in this code only if there were exactly two bytes to receive.  Perhaps it was left set by a previous 2-byte transaction (in which case, the chart really should do it as well). 

It appears to me that there are a couple difference between the STM32F and STM32L...at least the documentation seemed to imply a few differences from what the chart does.  But in any case, I did not want to re-write the entire driver (since someone else "owns" it) but this change fixes the problem.

Thanks for all your help.  I'll mark the chart as "accept as solution" to get it off your books; besides, it was really helpful if not the exactly solution.