Skip to main content
SHobb.1
Associate III
June 17, 2022
Question

Problems with I2C register code (STM32G0x1)

  • June 17, 2022
  • 4 replies
  • 2103 views

I am attempting to write register level code to control the I2C peripheral in an STM32G0x1.

My read function is failing because the RXNE bit is never getting set. I believe I have implemented the state machine described in RM0444 (p. 958). Is there anything obviously wrong in my code?

bool i2c_I2C1_masterReceive(uint8_t addr, uint8_t *pData, uint8_t len, uint32_t timeout)
{
 uint32_t startTicks = rcc_msGetTicks();
 
 uint8_t dataIdx = 0;
 
 //Clear flags
 I2C1->ICR |= (I2C_ICR_STOPCF |I2C_ICR_NACKCF);
 
 I2C1->CR2 &= ~I2C_CR2_SADD; //Clear addr bits
 I2C1->CR2 |= ((addr | 0x01) << I2C_CR2_SADD_Pos); //Set read bit
 I2C1->CR2 &= ~(I2C_CR2_NBYTES);
 I2C1->CR2 |= (uint32_t)len << I2C_CR2_NBYTES_Pos;
 
 I2C1->CR2 |= I2C_CR2_START; //Send Start condition (automatically waits for BUSY flag)
 while(!(I2C1->ISR & I2C_ISR_BUSY)) //Wait for start condition to be recognized
 {
 //Handle timeout
 if(rcc_msGetTicks()-startTicks > timeout)
 {
 gpio_LED_writeRed(LED_ON);
 return(false);
 }
 }
 
 if(I2C1->ISR & I2C_ISR_NACKF)
 {
 //Clear NACKF
 I2C1->ICR |= I2C_ICR_NACKCF;
 printf("Received nack\r\n");
 return(false);
 }
 
 while(dataIdx < len)
 {
 while(!((I2C1->ISR & I2C_ISR_RXNE) == I2C_ISR_RXNE))
 {
 //Handle timeout -- THIS IS ALWAYS OCCURRING (RXNE NEVER GETS SET)
 if(rcc_msGetTicks()-startTicks > timeout)
 {
 gpio_LED_writeOrange(LED_ON);
 return(false);
 }
 }
 pData[dataIdx] = I2C1->RXDR;
 dataIdx++;
 }
 
 if(!(I2C1->ISR & I2C_ISR_TC))
 {
 gpio_LED_writeGreen(LED_ON);
 return(false);
 }
 
 //Generate Stop condition
 I2C1->CR2 |= I2C_CR2_STOP;
 //Wait for I2C not busy
 while((I2C1->ISR & I2C_ISR_BUSY))
 {
 if(rcc_msGetTicks() - startTicks > timeout)
 {
 //printf("I2C Timeout...\r\n");
 gpio_LED_writeBlue(LED_ON);
 return(false);
 }
 }
 
 return(true);
}

In case it matters, the my configuration code:

/**
 * @brief I2C GPIO pins configuration
 */
void i2c_I2C1_GPIO_config(void)
{
 //SCL: PB6, SDA: PB7
 RCC->IOPENR |= RCC_IOPENR_GPIOBEN; ///Enable Port B clock
 
 //Set to AF Mode
 GPIOB->MODER &= ~(GPIO_MODER_MODE7 | GPIO_MODER_MODE6);
 GPIOB->MODER |= (GPIO_MODER_MODE7_1 | GPIO_MODER_MODE6_1);
 GPIOB->OTYPER |= (GPIO_OTYPER_OT7 | GPIO_OTYPER_OT6); //Output open-drain
 //Set to high speed
 GPIOB->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED7 | GPIO_OSPEEDR_OSPEED6);
 GPIOB->OSPEEDR |= (GPIO_OSPEEDR_OSPEED7_1 | GPIO_OSPEEDR_OSPEED6_1);
 //Set to AF6 (both PB6 and PB7)
 GPIOB->AFR[0] &= ~(GPIO_AFRL_AFSEL7 | GPIO_AFRL_AFSEL6);
 GPIOB->AFR[0] |= (GPIO_AFRL_AFSEL7_2 | GPIO_AFRL_AFSEL7_1 | GPIO_AFRL_AFSEL6_2 | GPIO_AFRL_AFSEL6_1);
}
 
/**
 * @brief I2C Peripheral configuration
 */
void i2c_I2C1_config(void)
{
 RCC->APBENR1 |= RCC_APBENR1_I2C1EN; //Enable I2C Clock
 
 I2C1->CR1 &= ~I2C_CR1_PE; //Make sure peripheral is disabled
 I2C1->CR1 &= ~(I2C_CR1_ANFOFF | I2C_CR1_DNF); //Disable Analog and Digital Filters
 I2C1->CR1 |= (3UL << I2C_CR1_DNF_Pos); //Enable digital filter for 3*Ti2cclk
 //Configure timing
 I2C1->TIMINGR = 0; //Reset
 I2C1->TIMINGR |= I2C_TIMINGR_PRESC; //tpresc = (0xF + 1)*(1/64MHz) = 250ns
 I2C1->TIMINGR |= (4UL << I2C_TIMINGR_SCLDEL_Pos); //tscldel = (4+1)*250ns = 1250ns
 I2C1->TIMINGR |= (2UL << I2C_TIMINGR_SDADEL_Pos); //tsdadel = (2)*250ns = 500ns
 I2C1->TIMINGR |= (15UL << I2C_TIMINGR_SCLH_Pos); //tsclh = (15+1)*250ns = 4us
 I2C1->TIMINGR |= (19UL << I2C_TIMINGR_SCLL_Pos); //tscll = (19+1)*250ns = 5us
 
 I2C1->CR1 &= ~I2C_CR1_NOSTRETCH; //Keep clear in master mode
 
 I2C1->CR2 &= ~I2C_CR2_ADD10; //Configure for 7 bit address mode
 
 I2C1->CR1 |= I2C_CR1_PE; //Enable I2C peripheral
}

And my transmit code. Note, this is completing "successfully" (returns true), but I can't confirm the writes are actually happening until I get reads working (i.e., trying to write a register in an external component).

/**
 * @brief I2C Transmit (Master)
 */
bool i2c_I2C1_masterTransmit(uint8_t addr, uint8_t *pData, uint8_t len, uint32_t timeout)
{
 uint32_t startTicks = rcc_msGetTicks();
 
 uint8_t dataIdx = 0;
 
 //Clear flags
 I2C1->ICR |= (I2C_ICR_STOPCF |I2C_ICR_NACKCF);
 
 I2C1->CR2 &= ~I2C_CR2_SADD; //Clear addr bits
 I2C1->CR2 |= (addr << I2C_CR2_SADD_Pos); //SADD[7:1] need to have the addr. SADD[0] is R/W
 I2C1->CR2 &= ~(I2C_CR2_NBYTES);
 I2C1->CR2 |= (uint32_t)len << I2C_CR2_NBYTES_Pos;
 
 I2C1->CR2 |= I2C_CR2_START; //Send Start condition (automatically waits for BUSY flag)
 while(!(I2C1->ISR & I2C_ISR_BUSY)) //Wait for start condition to be recognized
 {
 //Handle timeout
 if(rcc_msGetTicks()-startTicks > timeout)
 {
 gpio_LED_writeRed(LED_ON);
 return(false);
 }
 }
 
 if(I2C1->ISR & I2C_ISR_NACKF)
 {
 //Clear NACKF
 I2C1->ICR |= I2C_ICR_NACKCF;
 printf("Received nack\r\n");
 return(false);
 }
 
 while(dataIdx < len)
 {
 if((I2C1->ISR & (I2C_ISR_TXIS | I2C_ISR_TXE)))
 {
 I2C1->TXDR = pData[dataIdx];
 dataIdx++;
 }
 
 //Handle timeout
 if(rcc_msGetTicks()-startTicks > timeout)
 {
 gpio_LED_writeOrange(LED_ON);
 return(false);
 }
 }
 
 while(!(I2C1->ISR & I2C_ISR_TC))
 {
 //Handle timeout
 if(rcc_msGetTicks()-startTicks > timeout)
 {
 gpio_LED_writeGreen(LED_ON);
 return(false);
 }
 }
 
 //Generate Stop condition
 I2C1->CR2 |= I2C_CR2_STOP;
 //Wait for I2C not busy
 while((I2C1->ISR & I2C_ISR_BUSY))
 {
 if(rcc_msGetTicks() - startTicks > timeout)
 {
 //printf("I2C Timeout...\r\n");
 gpio_LED_writeBlue(LED_ON);
 return(false);
 }
 }
 
 return(true);
}

Note: I realize this code isn't efficient (I should combine multiple RMW operations to the same registers). I have it this way right now for clarity and because I am still learning the registers.

This topic has been closed for replies.

4 replies

waclawek.jan
Super User
June 17, 2022

The I2C is notoriously ​quirky. Show waveforms on the bus. Read out and post registers content.

JW​

CErle.1
Visitor II
June 17, 2022

Everything looks alright on the scope? SCL & SDA timing?

SDA sends what it should, slave really ACKs?

And STM32 ACKs in case slave sends?

Maybe it's the timeout?

I can't remember right now, maybe the enable bit in CR1 must be set after setting the address in CR2?

SHobb.1
SHobb.1Author
Associate III
June 17, 2022

Thanks for the responses.

I do not have access to a scope today. I'll take some measurements next week and post...

@Community member​ : Can you explain the middle part of your comment: "And STM32 ACKs in case slave sends?" I don't expect my slave to send anything unless I send a Read command. But I don't know if I should be setting a bit to send Acks...

CErle.1
Visitor II
June 17, 2022

You're right, that master ACK comment might not really make sense.

If your STM32 master sends a read command, check if the slave acknowledges it (some NACK bit in ISR? Still don't have the manual here...).

If it doesn't the STM will probably not throw out any more SCL clocks and you won't get any data and RXNE will stay low.

But first of all, get a scope and check what's really going on.