2022-06-16 07:12 PM
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.
2022-06-16 11:24 PM
The I2C is notoriously quirky. Show waveforms on the bus. Read out and post registers content.
JW
2022-06-16 11:27 PM
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?
2022-06-17 05:27 AM
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...
2022-06-17 06:59 AM
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.