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
Solved! Go to Solution.
2025-10-13 8:54 PM - edited 2025-10-13 8:56 PM
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
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 ?
2025-10-09 12:29 PM
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
2025-10-10 12:03 AM
> 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.
2025-10-12 3:15 PM
Hooking my code to a logic analyzer I can see that the master device sends the correct address, 0x50. But followup data is ignored. (I changed the I2C address in the code from 0x08 to 0x50.)
I used the following command line to send data to the device:
i2cset -y 8 0x50 0x01 0x02 0x03 i
and I receive
"Error: Write failed"
I know I'm missing something in my code. Any suggestions would be appreciated.
-A
2025-10-13 2:42 AM - edited 2025-10-13 2:45 AM
@Archadious wrote:Hooking my code to a logic analyzer I can see that the master device sends the correct address, 0x50.
As @Ozone said, are you sure that 0x50 is the correct 7-bit address?
More on common & widespread confusion & misrepresentation of I2C addresses:
@Archadious wrote:But followup data is ignored.
Which very much suggests that the address is not what the slave is expecting.
Have you tried just doing a scan of all possible I2C addresses?
Again, Have you looked at ST's examples for the stm32g0 ?
Have you used an oscilloscope to verify that the signal is good in the analogue domain? If it's not, the LA won't be giving useful information.
2025-10-13 3:07 AM
> Have you used an oscilloscope to verify that the signal is good in the analogue domain? If it's not, the LA won't be giving useful information.
I'm not sure if a scope would reveal more than an logic analyser.
If your STM slave device device does not respond, this can be for physical/signal reasons (it can't pull down SDA), or because it does not feel addressed - literally. In the latter case, a scope would not reveal anything new.
I would suggest either try both variants (shifted and unshifted address) with your slave device.
Alternatively, you could try to init it as master and do a transmission, to check the bus works fine electrically .
2025-10-13 3:13 AM
@Ozone wrote:I'm not sure if a scope would reveal more than an logic analyser.
It could show things like excessively slow edges (wrong pullups), wrong voltage levels, noise, etc - things which could cause a LA to give misleading results.
2025-10-13 8:54 PM - edited 2025-10-13 8:56 PM
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