Skip to main content
Visitor II
October 9, 2025
Solved

Slave I2C fails to connect on stm32g030

  • October 9, 2025
  • 3 replies
  • 518 views

 

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

 

    This topic has been closed for replies.
    Best answer by Archadious

    I must be the dumbest programmer to get his hands on arm chips from ST.

     

    In my initial post I suggested that the problem was most likely something simple that I was overlooking.  Well, guess what.  I failed to mention my development environment in any of my posts.  I compile with g++ not gcc.

    The c++ compiler mangles function names.  In my code the IRQ handler was being manged and no longer matched the weakly defined default handler for I2C2.  Adding 'extern "C"' as a qualifier to my handler solved my code issue.  This prevents the c++ compiler from mangling the handlers name and allows it to override the default handler.

     

    A simple solution for a very simple programmer.  

     

    But thanks to everyone who made thoughtful suggestions.  I had not previously made much use of a logic analyzer for debugging and learning to do so was time well spent.   Thank you.

    -A

    3 replies

    Explorer
    October 9, 2025

    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 ?

    Super User
    October 9, 2025

    @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 ?

    Visitor II
    October 9, 2025

    Thank you for your reply.

     

    Yes resistors, typo.  Value of 4.7K pulling SDA and SCL to 3v3.
    There is a common GND.

    The Master is a CP2112 module and I have use it successfully with several other I2C slave devices.

    I will hook up a logic analyser and check to see what is being received by the STM32G pins.

    I have been working with ST examples for the MCU.  I can't help think there is a simply error I'm making in my code but because I've been so close to it I'm unable to notice.

     

    -A

     

    Explorer
    October 10, 2025

    > I have been working with ST examples for the MCU.  I can't help think there is a simply error I'm making in my code but because I've been so close to it I'm unable to notice.

    As I earlier alluded to, the internal definition of the 7-bit "I2C address" is not consistent accross vendors.
    In the end, it shows up in bits 8...1, with the LSB defining read or write access.

    Some understand it as an 8-bit value that has to be written (or ORed) directly into the config register, other imply a shift either before the config write, or an automatic shift by the peripheral hardware.

    A logic analyser (or scope) can resolve the issue.
    Or trial & error, i.e. check if the unshifted address works.

    ArchadiousAuthorAnswer
    Visitor II
    October 14, 2025

    I must be the dumbest programmer to get his hands on arm chips from ST.

     

    In my initial post I suggested that the problem was most likely something simple that I was overlooking.  Well, guess what.  I failed to mention my development environment in any of my posts.  I compile with g++ not gcc.

    The c++ compiler mangles function names.  In my code the IRQ handler was being manged and no longer matched the weakly defined default handler for I2C2.  Adding 'extern "C"' as a qualifier to my handler solved my code issue.  This prevents the c++ compiler from mangling the handlers name and allows it to override the default handler.

     

    A simple solution for a very simple programmer.  

     

    But thanks to everyone who made thoughtful suggestions.  I had not previously made much use of a logic analyzer for debugging and learning to do so was time well spent.   Thank you.

    -A

    Explorer
    October 14, 2025

    Well, that happens.
    Glad you could resolve it.

    Not sure why you want C++ in the first place, except for porting C++-based libraries/applications to this platform.