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

 

1 ACCEPTED SOLUTION

Accepted Solutions
Archadious
Associate III

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

View solution in original post

10 REPLIES 10
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.

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

 

> 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.

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.)
i2c.logic.png
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

 


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

https://community.st.com/t5/imaging-sensors/vl53l8cx-not-working-on-nrf9151-and-getting-naks/m-p/814452/highlight/true#M5697

 


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

 

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.

> 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 .


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

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.
Archadious
Associate III

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