cancel
Showing results for 
Search instead for 
Did you mean: 

Slave I2C fails to connect on stm32g030

Archadious
Associate III

 

I'm attempting to use a stm32g030 as an I2C slave device however the mcu fails to accept connections with the master device.  I have run this code on several different board including several using a stm32g031 but experience the same issue so I am tentatively ruling out a hardware error.  I use physical pull-up registers on SDA and SCL.

I have printed out several important registers after calling 'i2c_slave_init'.

GPI0A->AFR[1]: 0x00066000
I2C2->OAR1: 0x00008010
I2C2->CR1: 0x000000bd
I2C2->IIMINGR: 0x10b17db5
PCLK1 = 64000000 Hz

There must be something I'm failing to do in my code but I've been staring at code for so long I simply don't see it.  Any suggestions would be greatly appreciated.

-A

// Rrequired Header Files
//-----------------------------------------------------------------------------
#include "stm32g030xx.h"
#include "gpio.hh"


#define slave_address 0x08

/**
 * @brief Initialize I2C2 as a 7-bit slave on PA11=SCL and PA12=SDA.
 * @PAram slave_address  7-bit slave address (0x08–0x7F)
 */
//-----------------------------------------------------------------------------
void i2c2_slave_init( ) {

  // --- Enable Clocks ---
  RCC->IOPENR |= RCC_IOPENR_GPIOAEN;   // Enable GPIOA clock
  RCC->APBENR1 |= RCC_APBENR1_I2C2EN;  // Enable I2C2 clock

  // --- Configure PA11 (SCL) and PA12 (SDA) for I2C2 ---
  GPIOA->MODER &= ~(GPIO_MODER_MODE11_Msk | GPIO_MODER_MODE12_Msk);
  GPIOA->MODER |=  (2U << GPIO_MODER_MODE11_Pos) | (2U << GPIO_MODER_MODE12_Pos); // AF mode
  GPIOA->OTYPER |= (GPIO_OTYPER_OT11 | GPIO_OTYPER_OT12); // Open-drain
  GPIOA->OSPEEDR |= (3U << GPIO_OSPEEDR_OSPEED11_Pos) | (3U << GPIO_OSPEEDR_OSPEED12_Pos); // High speed
  GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD11_Msk | GPIO_PUPDR_PUPD12_Msk ); // No pull-up/down (use external)
  GPIOA->PUPDR |= ( GPIO_PUPDR_PUPD11_0 | GPIO_PUPDR_PUPD12_0 );

  GPIOA->AFR[ 1 ] &= ~( GPIO_AFRH_AFSEL11_Msk | GPIO_AFRH_AFSEL12_Msk );
  GPIOA->AFR[ 1 ] |= ( GPIO_AFRH_AFSEL11_1 | GPIO_AFRH_AFSEL11_2 );
  GPIOA->AFR[ 1 ] |= ( GPIO_AFRH_AFSEL12_1 | GPIO_AFRH_AFSEL12_2 );

  // --- Reset I2C2 peripheral ---
  RCC->APBRSTR1 |=  RCC_APBRSTR1_I2C2RST;
  RCC->APBRSTR1 &= ~RCC_APBRSTR1_I2C2RST;

  // --- Configure I2C2 ---
  I2C2->CR1 &= ~I2C_CR1_PE; // Disable before configuring

  /*
   * I2C timing at 64MHz peripheral clock
   * We’ll target 100kHz standard mode.
   * Using STM32Cube timing tool or RM0444 formula:
   * PRESC=7, SCLDEL=4, SDADEL=2, SCLH=0x13, SCLL=0x2F
   * => 0x7031042F
   */
  I2C2->TIMINGR = 0x10B17DB5;  // 100 KHz @ 64 MHz PCLK
  //  I2C2->TIMINGR = 0x7031042F; // 100 kHz @ 64 MHz PCLK
  //  I2C2->TIMINGR = 0x10310413; // 400 kHz @ 64 MHz PCLK
  //  I2C2->TIMINGR = 0x0021020B; //   1 MHz @ 64 MHz PCLK

  // --- Set own slave address ---
  I2C2->OAR1 = 0;
  I2C2->OAR1 = (slave_address << 1);
  I2C2->OAR1 |= I2C_OAR1_OA1EN;

  // Set analog noise filter
  I2C2->CR1 &= ~I2C_CR1_ANFOFF;  // analog filter
  I2C2->CR1 &= ~I2C_CR1_DNF;  // digital filter
  I2C2->CR1 |= ( 0x7 << I2C_CR1_DNF_Pos );


  // --- Enable interrupts ---
  I2C2->CR1 |= I2C_CR1_ERRIE   // Error interrupt
    | I2C_CR1_STOPIE  // Stop detection
    | I2C_CR1_NACKIE  // NACK detection
    | I2C_CR1_ADDRIE  // Address match
    | I2C_CR1_RXIE;    // Receive
  //    | I2C_CR1_TXIE;   // Transmit

  NVIC_EnableIRQ(I2C2_IRQn);
  NVIC_SetPriority(I2C2_IRQn, 1);

  // --- Enable I2C2 peripheral ---
  I2C2->CR1 |= I2C_CR1_PE;

};  // end init


/**
 * @brief Simple I2C2 interrupt handler for slave communication
 */
//-----------------------------------------------------------------------------
volatile uint8_t i2c_rx_data = 0;
volatile uint8_t i2c_tx_data = 0;
volatile bool i2c_ready = false;


//-----------------------------------------------------------------------------
void I2C2_IRQHandler( void ) {

  uint32_t isr = I2C2->ISR;

  ORANGE_OFF; // Turn on an LED

  // --- Address match event ---
  if (isr & I2C_ISR_ADDR) {
    //    (void)I2C2->ISR;              // Dummy read
    I2C2->ICR = I2C_ICR_ADDRCF;  // Clear flag
  }

  // --- Receive event ---
  if (isr & I2C_ISR_RXNE) {
    i2c_rx_data = (uint8_t)I2C2->RXDR;
    i2c_ready = true;
    BLUE_ON;
  }

  // --- Transmit event ---
  if (isr & I2C_ISR_TXIS) {
    I2C2->TXDR = i2c_tx_data; // Send one byte (modify as needed)
  }

  // --- Stop condition detected ---
  if (isr & I2C_ISR_STOPF) {
    I2C2->ICR = I2C_ICR_STOPCF; // Clear stop flag
    // Optional: handle transaction complete
  }

  // --- NACK received ---
  if (isr & I2C_ISR_NACKF) {
    I2C2->ICR = I2C_ICR_NACKCF;
  }

  // --- Error flags ---
  if (isr & (I2C_ISR_BERR | I2C_ISR_ARLO | I2C_ISR_OVR)) {
    I2C2->ICR = (I2C_ICR_BERRCF | I2C_ICR_ARLOCF | I2C_ICR_OVRCF);
    // You might want to set an error flag here
  }

};  // end irqhandler

 

2 REPLIES 2
Ozone
Principal II

What does a scope / logic analyzer say, do you see any ACK from the slave ?

  // --- Set own slave address ---
   ...
  I2C2->OAR1 = (slave_address << 1);

I don't have experience with the STM32G devices.
Have you checked with the reference manual that the peripheral does not do another (internal) shift ?

Andrew Neil
Super User

@Archadious wrote:

I use physical pull-up registers on SDA and SCL.


You mean pull-up resistors ?

What value are they?

You'll also need a common GND.

What is the Master?

 

As @Ozone said, have you looked at the lines with an oscilloscope or logic analyser ?

Use a 'scope first to check that the signals are clear, levels & edges OK, etc;

Then use an analyser to verify protocol.

 

Have you looked at ST's examples for the stm32g0 ?

A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.