Problems with I2C register code (STM32G0x1)
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.
