cancel
Showing results for 
Search instead for 
Did you mean: 

Differences between the I2C1 and I2C2 modules on STM32L0

Dim N
Associate II

I am trying to get a simple I2C connection running and the I2C1 peripheral on a STM32L052 is giving me hell. Here is what I have done so far:

I have written a simple driver for STM32L053 using I2C2 with interrupts. I have tried it on a couple of different slaves - it works with no issues, both reading and writing. I am using 4.7kOhm pull-ups, SCL clock is 100kHz, master and slave are both powered from 3v3 LDO, clock is HSI@16MHz. There is a 5us delay between successive I2C transfers (required by a slave device). Here is the code:

uint8_t *i2c_txBuffer;
uint8_t *i2c_rxBuffer;
 
void I2C_Init()
{
	RCC->IOPENR  |= RCC_IOPENR_GPIOBEN;
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE10 | GPIO_MODER_MODE11, GPIO_MODER_MODE10_0 | GPIO_MODER_MODE11_0);
	SET_BIT(GPIOB->OTYPER, GPIO_OTYPER_OT_10 |  GPIO_OTYPER_OT_11);
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL10 | GPIO_AFRH_AFSEL11, 0x00006600);
 
	RCC->APB1ENR |= RCC_APB1ENR_I2C2EN;
 
	I2C2->CR1 |= I2C_CR1_TXIE | I2C_CR1_RXIE;
	I2C2->TIMINGR = 0x30420F13;
 
	NVIC_SetPriority(I2C2_IRQn, 2);
	NVIC_EnableIRQ(I2C2_IRQn);
 
	I2C2->CR1 |= I2C_CR1_PE;
	while((I2C2->CR1 & I2C_CR1_PE) == 0);
}
 
uint8_t I2C_Transmit(uint8_t *buffer, uint8_t length, uint8_t i2c_Address)
{
	i2c_txBuffer = buffer;
 
	I2C2->TXDR = *i2c_txBuffer++;
	I2C2->CR2 = I2C_CR2_AUTOEND | (length << I2C_CR2_NBYTES_Pos) | i2c_Address | I2C_CR2_START;
 
	while(I2C2->ISR & I2C_ISR_BUSY);
	return (I2C2->ISR & I2C_ISR_NACKF) ? 0 : 1;
}
 
void I2C2_IRQHandler(void)
{
	///====================== [Transmit buffer empty & ACK received] ======================///
	if(I2C2->ISR & I2C_ISR_TXIS)
	{
		I2C2->TXDR = *i2c_txBuffer++;
	}
	///===================== [Receive buffer NOT empty] ====================///
	if(I2C2->ISR & I2C_ISR_RXNE)
	{
		*i2c_rxBuffer++ = I2C2->RXDR;
	}
}

Then I tried porting it to STM32L052 and I2C1 on a different board. On it the slave power is controlled by a load switch. The rise time for VCC is 15us and SCL and SDA rise to 3v3 after around 150us. Those values were measured with a scope. Again, 4.7kOhm pull-ups, clock is 16MHz. And here is where the nightmare begins:

Literally the SAME code (with the exception of some 100ms delay for voltage stabilization after the load switch is turned on, references to I2C2 changed to I2C1 and proper GPIO configuration) hangs on the BUSY flag. I see nothing on the SCL and SDA lines with the scope (both pulled up). I found out that for a different family (STM32F1) there is a mention in the errata about proper I2C initialization - something about analog filters getting stuck. I tried the suggested workaround - nothing changed. So I reviewed the errata for STM32L0 - nothing about such an issue. In a different forum topic somebody mentioned, that the GPIOs should be configured after the I2C peripheral. Out of desperation I tried that - and it worked! I saw both SCL and SDA toggle with the data I was trying to send. So I hooked up a slave device. The slave was responding with ACKs, however something was off. I was sending 3 bytes, but on the line I saw the first one ACKed, then a pause with SCL LOW and after some time the other 2 bytes, both ACKed. It turned out, that the BUSY flag was cleared after the first byte was sent and I was exiting the while loop earlier. According to the datasheet the BUSY flag is set after detecting a START condition and cleared after a STOP or PE bit in CR1 set to 0. This PE bit also restarts the internal state machine and clears all flags, so I added this to the initialization, for the sake of completion. I also modified the transmit function to wait for the STOP flag to be set (instead of BUSY to be cleared), clear it and then continue with the rest. And it finally worked. Sort of.

The next problem came when I tried doing this in a loop after putting the MCU in stop mode and waking it up with the RTC. The result was this: load switch turned on, 100ms delay, PE=0, initialize I2C1, set PE=1, initialize GPIOs, start communication - all well. Set PE=0, disable RCC clock, set GPIOs to analog mode. Go to stop mode. Exit stop mode on RTC interrupt, configure again clocks, perform the exact same init procedure - the MCU gets stuck on the check for the STOP flag on the first transfer. On the SDA line I see the address and the first byte being ACKed. The rest 2 bytes do not get sent at all, as SCL remains LOW. If I try the same thing, but on I2C2 with check of the BUSY flag according to the original code, I get the same: the first run it works, then SCL goes LOW after the first byte is ACKed. What am I doing wrong?

- Why is there a difference in the way that I2C1 and I2C2 should be initialized?

- Why does polling the BUSY flag for I2C1 does not work the same as with I2C2?

- Could this SCL LOW after the first RTC wake-up be a result of the slave stretching the clock?

- Has anyone experienced any of these differences between the two I2C modules?

I assume that the problem is in the I2C code/peripheral, because the power reset through the load switch should guarantee that the state machine of the slave is always properly initialized.

1 ACCEPTED SOLUTION

Accepted Solutions
Dim N
Associate II

The package is 48-pin LQFP (STM32L052C8T6). You are right - this code cannot set GPIOB->AFRH to 0x44. But there is a typo in the code I pasted - at the moment I copied this part I was restoring the code from tests with I2C2, which uses other pins and the error slipped through. So to confirm - the debugger values are correct, pins 8 and 9 were set as AF4 in all tests (otherwise I would have never seen any action on SDA, which I did with the scope). The reason for TXIS and TXE I explained in my previous message - the interrupt priority for I2C1 was set too low to trigger the sending of the rest of the buffer - I corrected that, so now the periodic wake-up from stop mode works fine.

More importantly I found out what was wrong with the BUSY flag: its status is checked immediately after writing to CR2, before the START condition is detected on the bus. So naturally, it exits the while loop and continues to other stuff, messing everything up. This is how I got it working:

uint8_t I2C_Transmit(uint8_t *buffer, uint8_t length, uint8_t i2c_Address)
{
	i2c_txBuffer = buffer;
 
	I2C1->TXDR = *i2c_txBuffer++;
	I2C1->CR2 = I2C_CR2_AUTOEND | (length << I2C_CR2_NBYTES_Pos) | i2c_Address | I2C_CR2_START;
 
	///Wait for the START condition detection
	while((I2C1->ISR & I2C_ISR_BUSY) == 0);
	///Then check when BUSY flags gets cleared (STOP detected)
	while(I2C1->ISR & I2C_ISR_BUSY);
	return (I2C1->ISR & I2C_ISR_NACKF) ? 0 : 1;
}

Checking the STOPF goes around the issue and worked, as it gets set after the last byte is sent, so I stayed in the loop, not leaving it prematurely. It is strange that it worked for I2C2 without the delay though. There is a mention of wait cycles for subsequent writes to I2Cx->CR2 in the reference manual, however I write to it only once. I guess that other libraries by adding some overhead to the code mask the issue, as the check of the BUSY flag is performed a bit later, so that the START condition is detected by the peripheral.

View solution in original post

7 REPLIES 7

This all sounds wrong. There's no reason to initialize GPIOs after I2C peripheral and I don't see the reason why this would help.

Which STM32 exactly? Read out and check/compare/post content of all GPIO registers (I am mainly curious about all I2C pins, not just those you've explicitly set for I2C), I2C registers and RCC_CCIPR, for the original code with I2C2, and the first modification "exact same only I2C1" version.

JW

Dim N
Associate II

Here is the code for I2C1:

void I2C_Init()
{
	///Enable load switch
	RCC->IOPENR  |= RCC_IOPENR_GPIOBEN;
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE11, GPIO_MODER_MODE11_0);
	GPIOB->BSRR = GPIO_BSRR_BS_11;
	msDelay(100);
 
	///Enable I2C peripheral
	RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
	I2C1->CR1 &= ~I2C_CR1_PE;
	while(I2C1->CR1 & I2C_CR1_PE);
	I2C1->CR1 |= I2C_CR1_TXIE | I2C_CR1_RXIE;
	I2C1->TIMINGR = 0x30420F13;
 
	NVIC_SetPriority(I2C1_IRQn, 2);
	NVIC_EnableIRQ(I2C1_IRQn);
 
	I2C1->CR1 |= I2C_CR1_PE;
	while((I2C1->CR1 & I2C_CR1_PE) == 0);
 
	GPIOB->OTYPER |= GPIO_OTYPER_OT_8 |  GPIO_OTYPER_OT_9;
	MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL8 | GPIO_AFRH_AFSEL8, 0x00000044);
	MODIFY_REG(GPIOB->MODER, GPIO_MODER_MODE8 | GPIO_MODER_MODE9, GPIO_MODER_MODE8_1 | GPIO_MODER_MODE9_1);
}
uint8_t I2C_Transmit(uint8_t *buffer, uint8_t length, uint8_t i2c_Address)
{
	i2c_txBuffer = buffer;
 
	I2C1->TXDR = *i2c_txBuffer++;
	I2C1->CR2 = I2C_CR2_AUTOEND | (length << I2C_CR2_NBYTES_Pos) | i2c_Address | I2C_CR2_START;
 
	while((I2C1->ISR & I2C_ISR_STOPF) == 0);
	I2C1->ICR |= I2C_ICR_STOPCF;
	return (I2C1->ISR & I2C_ISR_NACKF) ? I2C_NACK : I2C_ACK;
}
void I2C1_IRQHandler(void)
{
	///====================== [Transmit buffer empty & ACK received] ======================///
	if(I2C1->ISR & I2C_ISR_TXIS)
	{
		I2C1->TXDR = *i2c_txBuffer++;
	}
	///===================== [Receive buffer NOT empty] ====================///
	if(I2C1->ISR & I2C_ISR_RXNE)
	{
		*i2c_rxBuffer++ = I2C1->RXDR;
	}
}

This is the content of some of the registers as shown by the debugger:

After the Init procedure:

I2C1->CR1 = 0x0000007

I2C1->CR2 = 0x0000000

I2C1->TIMINGR = 0x30420F13

I2C1->ISR = 0x0000001

GPIOB->MODER = 0xFF7AFFC7

GPIOB->OTYPER = 0x00000300

GPIOB->AFRH = 0x00000044

GPIOB->PUPDR = 0x00000000

RCC->CCIPR = 0x00000000

I2C1->CR2 = 0x02030078 (after the first call to I2C_Transmit, before data is being sent)

I2C1->ISR = 0x00008003 (when the code hangs on the wait for the STOPF the second time)

The content of all of those registers is the same the first time, when the communication is fine and the second time, when it hangs after the first byte is sent. The MCU is STM32L052. I have tested the first driver for I2C2 with the BUSY flag on both STM32L053 and STM32L052 and it works for both boards, with the exception of the wake-up behavior, mentioned in my first post. The new code for the I2C1 was tested only on STM32L052. I am using the default APB clock for I2C1, which is 16MHz and the suggested values for I2C->TIMINGR. The SCL clock setting is also confirmed by the scope. I have tried different clock sources (HSE, HSI, PLL) for HCLK, delays up to 2s for the load switch. The I2C lines are less than 10cm, overall power consumption is less than 6mA, the I2C waveforms at the slave seem fine - good rise/fall time, no interference or glitches. I double checked with the debugger that the main clock is set properly to 16MHz after waking up from stop mode, so that the PCLK frequency and the delay for the load switch are correct. You did not mention anything about the configuration of the I2C and the BUSY flag polling, so I guess this part of the code is alright?

Dim N
Associate II

Just a quick update - the communication on wake-up stops after the first byte because of my mistake. I have configured the RTC IRQ priority higher than the one for I2C. So I send the first byte, flags get updated in I2C1->ISR as you can see in my previous post, but the routine is never called, as its priority is lower. I cleared this up, however the issue with the initialization and flags remains. I still would like to know the proper way of dealing with I2C1, I2C2 instead of just stumbling upon a working configuration by chance, as I did with my first driver.

> The MCU is STM32L052.

Yes, but which package?

> MODIFY_REG(GPIOB->AFR[1], GPIO_AFRH_AFSEL8 | GPIO_AFRH_AFSEL8, 0x00000044);

This works and results in GPIOB->AFRH = 0x00000044? How?

Did you compare these registers with registers for the I2C2 case?

> You did not mention anything about the configuration of the I2C and the BUSY flag polling, so I guess this part of the code is alright?

I said, that setting GPIO after initializing I2C does not sound right, and also that I don't know why wouldn't it work the other way round. BUSY flag polling looks OK.

> I2C1->ISR = 0x00008003 (when the code hangs on the wait for the STOPF the second time)

So, you have TXIS set in ISR, and TXIE set in CR1, and this causes no interrupt? Then you should investigate this.

I am not ST and I have only casual experience with 'L0 and its I2C.

JW

Dim N
Associate II

The package is 48-pin LQFP (STM32L052C8T6). You are right - this code cannot set GPIOB->AFRH to 0x44. But there is a typo in the code I pasted - at the moment I copied this part I was restoring the code from tests with I2C2, which uses other pins and the error slipped through. So to confirm - the debugger values are correct, pins 8 and 9 were set as AF4 in all tests (otherwise I would have never seen any action on SDA, which I did with the scope). The reason for TXIS and TXE I explained in my previous message - the interrupt priority for I2C1 was set too low to trigger the sending of the rest of the buffer - I corrected that, so now the periodic wake-up from stop mode works fine.

More importantly I found out what was wrong with the BUSY flag: its status is checked immediately after writing to CR2, before the START condition is detected on the bus. So naturally, it exits the while loop and continues to other stuff, messing everything up. This is how I got it working:

uint8_t I2C_Transmit(uint8_t *buffer, uint8_t length, uint8_t i2c_Address)
{
	i2c_txBuffer = buffer;
 
	I2C1->TXDR = *i2c_txBuffer++;
	I2C1->CR2 = I2C_CR2_AUTOEND | (length << I2C_CR2_NBYTES_Pos) | i2c_Address | I2C_CR2_START;
 
	///Wait for the START condition detection
	while((I2C1->ISR & I2C_ISR_BUSY) == 0);
	///Then check when BUSY flags gets cleared (STOP detected)
	while(I2C1->ISR & I2C_ISR_BUSY);
	return (I2C1->ISR & I2C_ISR_NACKF) ? 0 : 1;
}

Checking the STOPF goes around the issue and worked, as it gets set after the last byte is sent, so I stayed in the loop, not leaving it prematurely. It is strange that it worked for I2C2 without the delay though. There is a mention of wait cycles for subsequent writes to I2Cx->CR2 in the reference manual, however I write to it only once. I guess that other libraries by adding some overhead to the code mask the issue, as the check of the BUSY flag is performed a bit later, so that the START condition is detected by the peripheral.

Thanks for coming back with the solution. Please select your post as Best so that the thread is marked as Solved.

> It is strange that it worked for I2C2 without the delay though.

Indeed. And, as you've said, you've used exactly same code for both I2C1 and I2C2, so you can't explain the difference by wait states (which, as you've remarked, shouldn't apply), nor library calls you did not make. According to DS/RM, these two modules indeed do differ in that I2C1 does have SMBus implemented, that might imply subtle differences in behaviour. This would be interesting to pursue, but I'm afraid it would take up much time.

JW

Dim N
Associate II

My guess would be that the difference comes from the way the peripherals are clocked. I2C2 is clocked directly by PCLK, while the clock for I2C1 goes through a MUX, as its clock source can be changed. I think that testing this by varying the core clock frequency and the peripheral frequency could provide some insight.