2025-10-08 10:00 PM
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
2025-10-08 11:40 PM
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 ?
2025-10-09 12:07 AM
@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 ?