cancel
Showing results for 
Search instead for 
Did you mean: 

How to send another START bit during an I2C transmission

P M
Associate II

Hi,

I am trying to set the FRAM memory mb85rc64ta in sleep mode (Datasheet: I cannot copy and paste a link, but it is from Fujitsu and the pdf file is called MB85RC64TA-DS501-00044-4v1-E page 11)

It requires 2 START condition but no STOP condition before the second one. I have tried modifying a HAL function to do this but always I get the second START bit condition, I get a STOP one right before.

How should I do this?

10 REPLIES 10

Which STM32?

The described condition is called Repeated Start, and generally you achieve it by setting the START bit during the acknowledge phase. Details may depend on the particular I2C module (there are two distinct I2C implementations across STM32 families), e.g.:

0693W00000Nruq6QAB.png 

JW

P M
Associate II

Sorry I forgot. I am using an STM32L431CBU.

what I see is that I cannot generate a second START bit and send data if I do not generate a STOP bit before.

Harvey White
Senior III

There is already a HAL function used for memory access which performs a repeated start. Look at those examples. If you are using the HAL drivers, look for the memory read and write code, since the repstart code is needed to set the memory address for reading.

Hi,

I have tried with HAL_I2C_Mem_Write function. In fact, I am using that function to write data to the fram. But I do not see the second start bit you mention:

0693W00000Ns3o9QAB.png 

I see the device address (First byte right after the START bit), 2 bytes for the place where the data will be stored and the data. After the data, the STOP bit signal.

Harvey White
Senior III

Memory write for an I2C chip sends the chip address, then the 2 or 3 byte address of the data, then the data. You will not see a repeated start for this situation.

For a memory read, you must send the chip address, then the 2 or 3 byte address of the data, then a repeated start bit with the address of the chip, then you will read data.

If you used the memory write, the repeated start is not required nor will you see one.

The code I use (for my system) follows:

uint32_t AT24CXXX::read_ROM (int mem_address, int length, uint8_t* data )
{
	return hal_i2c[interface]->Restart_Read(address, mem_address,I2C_MEMADD_SIZE_16BIT, data, length, NO_WAIT);
}
 
 

The part to note here is the Restart_Read which follows: Note that this code wraps the hal functions and includes a semaphore because the code is intended to be used with FreeRTOS. Note also that the code uses pointers to access the I2C programming and is protective of bad pointers.

//#define HAL_I2C_ERROR_NONE      (0x00000000U)    /*!< No error              */
//#define HAL_I2C_ERROR_BERR      (0x00000001U)    /*!< BERR error            */
//#define HAL_I2C_ERROR_ARLO      (0x00000002U)    /*!< ARLO error            */
//#define HAL_I2C_ERROR_AF        (0x00000004U)    /*!< ACKF error            */
//#define HAL_I2C_ERROR_OVR       (0x00000008U)    /*!< OVR error             */
//#define HAL_I2C_ERROR_DMA       (0x00000010U)    /*!< DMA transfer error    */
//#define HAL_I2C_ERROR_TIMEOUT   (0x00000020U)    /*!< Timeout error         */
//#define HAL_I2C_ERROR_SIZE      (0x00000040U)    /*!< Size Management error */
//#define HAL_I2C_ERROR_DMA_PARAM (0x00000080U)    /*!< DMA Parameter Error   */
 
 
// used for chips that need write/address/restart/read
// use for EEPROM as well
 
uint32_t HAL_I2C::Restart_Read(uint16_t address, uint16_t mem_address,
		uint16_t address_size, uint8_t *data, uint16_t size, bool waiting)
{
	uint32_t				result;
 
	I2C_HandleTypeDef*			hi2c;
 
 
	hi2c = (I2C_HandleTypeDef*)nullptr;
 
	switch (interface)
	{
		case 0: return COMM_ERROR_INVALID_PARAM;
#ifdef _I2C1
		case 1: hi2c = &hi2c1; break;
#endif
#ifdef _I2C2
		case 2: hi2c = &hi2c2; break;
#endif
#ifdef _I2C3
		case 3: hi2c = &hi2c3; break;
#endif
#ifdef _I2C4
		case 4: hi2c = &hi2c4; break;
#endif
#ifdef _I2C5
		case 5: hi2c = &hi2c5; break;
#endif
#ifdef _I2C6
		case 6: hi2c = &hi2c6; break;
#endif
		default:
			return COMM_ERROR_INVALID_PARAM;
	}
 
 
 
	//	uint16_t					interface;
//
//	if (hi2c == nullptr) return HAL_ERROR;
//	interface = 0;
//#ifdef _I2C1
//	if (hi2c == &hi2c1) interface = 1;
//#endif
//#ifdef _I2C2
//	if (hi2c == &hi2c2) interface = 2;
//#endif
//#ifdef _I2C3
//	if (hi2c == &hi2c3) interface = 3;
//#endif
//#ifdef _I2C4
//	if (hi2c == &hi2c4) interface = 4;
//#endif
//#ifdef _I2C5
//	if (hi2c == &hi2c5) interface = 5;
//#endif
//#ifdef _I2C6
//	if (hi2c == &hi2c6) interface = 6;
//#endif
//	if (interface == 0) return HAL_ERROR;
 
	address = address << 1;
#ifdef _I2C_SEMAPHORE
#ifdef _FREERTOS
	xSemaphoreTake(I2C_semaphore[interface], portMAX_DELAY);
#endif
	result = HAL_I2C_Mem_Read(hi2c, address, mem_address, address_size,  data,size, 100);
//	ERROR = result;
	ERROR_CODE = hi2c->ErrorCode;
 
#ifdef _FREERTOS
	ERROR_REASON = (enum COMM_ERROR_TYPE) ERROR_CODE;
	xSemaphoreGive(I2C_semaphore[interface]);
#endif
	#else
	result = HAL_I2C_Mem_Read(hi2c, address, mem_address, address_size,  data,size, 100);
#endif
	switch (result)
	{
		case HAL_ERROR:    					//= 0x01U,
		{
			break;
		}
		case HAL_BUSY:     					//= 0x02U,
		{
			break;
		}
		case HAL_TIMEOUT:  					//= 0x03U
		{
//					HAL_I2C_Slave_reset_bus(interface);
			break;
		}
		case HAL_OK:
		{
			// was OK
			break;
		}
	}
	  return ERROR_CODE;
}
 
 

Look at line 90, which is a call to the ST supplied HAL driver, which is in the hal code, and is:

HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,
                                   uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)

Depending on how you call the base HAL routine, you will want to call this routine to see the repeated start and read the memory. A universal routine would be a driver that could adapt for different page sizes when writing a block of memory (not needed for reading), but would work for EEPROM and any other similar types of memory.

P M
Associate II

Thank you,

I think I am almost there but there is something I cannot get right.

In case I use a READ operation, I see the sequence fine but I receive a NACK at the end because of the bit flag changeg for the last byte:

0693W00000NsIy4QAF.pngAfter changing the READ operation by a WRITE operation:

0693W00000NsIyJQAV.png 

The device sends the STOP bit before receiving the ACK from the slave. I cannot understand why this happens. Any idea? I am pretty sure it has to be a very silly issue.

Ok sequence for READ:

MODIFY_REG(hi2c->Instance->CR2, ((I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RELOAD | I2C_CR2_AUTOEND | (I2C_CR2_RD_WRN & (uint32_t)(I2C_GENERATE_START_READ >> (31U - I2C_CR2_RD_WRN_Pos))) | I2C_CR2_START | I2C_CR2_STOP)), \
					(uint32_t)(((uint32_t)sleep_command_2) | (((uint32_t)hi2c->XferSize << I2C_CR2_NBYTES_Pos) & I2C_CR2_NBYTES) | (uint32_t)I2C_AUTOEND_MODE | (uint32_t)I2C_GENERATE_START_READ));

Wrong sequence for WRITE:

MODIFY_REG(hi2c->Instance->CR2, ((I2C_CR2_SADD | I2C_CR2_NBYTES | I2C_CR2_RELOAD | I2C_CR2_AUTOEND | (I2C_CR2_RD_WRN & (uint32_t)(I2C_GENERATE_START_READ >> (31U - I2C_CR2_RD_WRN_Pos))) | I2C_CR2_START | I2C_CR2_STOP)), \
					(uint32_t)(((uint32_t)sleep_command_2) | (((uint32_t)hi2c->XferSize << I2C_CR2_NBYTES_Pos) & I2C_CR2_NBYTES) | (uint32_t)I2C_AUTOEND_MODE | (uint32_t)I2C_GENERATE_START_WRITE));

Harvey White
Senior III

Is there a reason you want to effectively write your own drivers at the register level? Doing so requires a lot of work and often some tricky timing. It also can vary per chip family depending on register names and how the hardware is implemented.

I personally cannot recommend this.

The HAL level calls I use (example line 81 and example line 90 - identical) do work. If you do not like the HAL structure, then I'd suggest you go to the configuration page for the I2C, and change from HAL to LL drivers. I don't use them, but they are considerably simpler and I think provide the same functionality.

Again, I do not know the rest of your structure (in terms of system design), but since I2C is a bus design, you can have multiple parts of your program access the interface using different addresses. This can specifically happen if you use an RTOS.

If you must write your own drivers at the bit level, modifying the registers, then I strongly suggest that you find the exact code in the HAL level driver, and copy the register bit manipulation part from that. It' apparent that the write code you use is wrong, but I am not sure what. One thing may possibly be the autoend mode, but since I don't deal with the chip at this level (and I have, other chip families and environments), I can only guess.

Thinking about what you are doing:

Unless this is a RAM chip, where data writing is instantaneous, when you write data the following occurs:

  1. write data string to chip
  2. chip goes off line (does not respond to address) while internally writing data
  3. when chip is ready for new commands, chip will respond to address

So the proper sequence is:

  1. write data to chip
  2. poll chip by sending address
  3. get NAK or ACK (sending address, getting ACK, and then a stop bit doesn't bother the chip)
  4. check for timeout if desired
  5. Loop while NACK back to 2
  6. Chip is now available.

You could be having issues here, too.

The proper sequence for reading from a chip is to send a data write, followed by the full address in the chip (not one byte for low addresses, you must send the full 16 or 24 bit address unless your chip only has 256 locations) then a stop. This sets the address registers in the chip. A read operation will then use this address to fetch data.

It is important to look at the timing and sequencing diagrams in the chip data sheet to see what the chip expects. While the code you write may be perfectly functional, if it doesn't match what the chip needs, it won't work the way you expect.

Been there, and done that.

P M
Associate II

Hi Harvey,

I would prefer using the HAL functions. All the software architecture we use relies on them and it has worked fine so far.

Said so, I find I cannot use the HAL functions as they are provided for this issue. Maybe there is something I am missing.

In case I use the HAL_I2C_Mem_Read function, I have to provide the device address and the register I want to read. That's fine. I can use the 0xF8 as the device address and the real device address as the register I want to read.

After this, I have to use another address as the sleep command. If you check the documentation, the command after the restart bit is a 0x86.

Restart bit - 0x86 - ACK - STOP bit.

This implies 2 things.

First, I have to read 0 bytes using HAL_I2C_Mem_Read function.

Second, I have to change the address of the device by another one.

HAL_I2C_Mem_Read function has this condition on the beginning:

if ((pData == NULL) || (Size == 0U))
		{
			hi2c->ErrorCode = HAL_I2C_ERROR_INVALID_PARAM;
			return  HAL_ERROR;
		}

So I have to create another function to avoid this piece of code. I can also include the 0x86 as parameter so I can replace the address when I need it. No problem here.

Current issue and what I do not know how to solve is that the reading operation requires that the bit 0 is a 1 (reading operation) which implies that I cannot send a 0x86 (10000110). In case I use the WRITE operation, I can send the 0x86 but I get the STOP bit before the ACK.

I posted the MODIFY_REG macro because it is the lowest I can go trying to avoid the replacement of the read/write bit.

This is not a normal read, so trying to coax Cube/HAL functions to do a non-standard sequence may not lead to the required result.

You may want to bit-bang this sequence.

JW