Skip to main content
Associate
October 8, 2025
Solved

STM32F030 Nucleo: Bit-Banging I²C to DS1338 RTC returning 0xFFFF

  • October 8, 2025
  • 2 replies
  • 801 views

Hi Guys, for an internship I am making a dual channel datalogger. So for that datalogger, I am using DS1338 RTC. Now instead of doing I2C normally, I am doing Bit-banging.

 

I am using Nucleo-F030R8 board for my prototyping and I am using PA0(SDA) and PA1(SDA). 

 

After using DSO I was able to verify start condition, stop condition and slave address transfer. Also I tried to test ack by manually pulling the sda down and the output was as expected. 

 

Now the issue is I am not able to write or read the RTC and I am getting FFFF while reading it. And there are no hardware issues as I have tested the RTC. This makes me think there is problem with my I2C read function.

 

Following is the snippet of code I am using for this purpose -

#define I2C1_SDA_HIGH() (GPIOA->BSRR = (1U << SDA1_PIN))
#define I2C1_SDA_LOW() (GPIOA->BSRR = (1U << (SDA1_PIN + 16)))

#define I2C1_SCL_HIGH() (GPIOA->BSRR = (1U << SCL1_PIN))
#define I2C1_SCL_LOW() (GPIOA->BSRR = (1U << (SCL1_PIN + 16)))

#define I2C1_SDA_READ() ((GPIOA->IDR >> SDA1_PIN) & 0x1)


#define I2C1_SDA_INPUT() (GPIOA->MODER &= ~(3U << (SDA1_PIN * 2))) // Input mode = 00
#define I2C1_SDA_OUTPUT() (GPIOA->MODER = (GPIOA->MODER & ~(3U << (SDA1_PIN * 2))) | (1U << (SDA1_PIN * 2)))

UINT8 i2c1_getdata(void)
{
 UINT8 dat = 0;

 I2C1_SDA_HIGH(); 
 I2C1_SDA_INPUT(); 

 for(UINT8 i = 0; i < 8; i++)
 {
 dat <<= 1;

 I2C1_SCL_HIGH();
 delay_us(I2C1_DELAY);

 if(I2C1_SDA_READ()) dat |= 0x01;

 I2C1_SCL_LOW();
 delay_us(I2C1_DELAY);
 }

 I2C1_SDA_OUTPUT(); 
 return dat;
}

void i2c1_opdata(UINT8 dat)
{
 for(UINT8 i = 0; i < 8; i++)
 {
 if(dat & 0x80) I2C1_SDA_HIGH();
 else I2C1_SDA_LOW();

 I2C1_SCL_HIGH(); 
 delay_us(I2C1_DELAY);
 I2C1_SCL_LOW(); 
 delay_us(I2C1_DELAY);

 dat <<= 1;
 }

 
 i2c1_waitack();
}
void i2c1_waitack(void)
{
 I2C1_SDA_HIGH(); 
 I2C1_SDA_INPUT(); 

 i2c1_wait_tout = I2C1_WAIT_TOUT;

 I2C1_SCL_LOW();
 delay_us(I2C1_DELAY);

 I2C1_SCL_HIGH(); 
 delay_us(I2C1_DELAY);

 while (I2C1_SDA_READ()) 
 {
 if (i2c1_wait_tout-- == 0) break; // Timeout
 delay_us(5);
 }

 I2C1_SCL_LOW(); 
 delay_us(I2C1_DELAY);

 I2C1_SDA_OUTPUT(); 
}

Help will be really appreciated!

 

Footnotes-

 

1)delay_us() is a software delay, hence not very precise, but I have fine tuned the I2C1_delay to get 100khz SCL

 

2)The time out for ack after calculating came out to be almost 5 us, considering 48Mhz sysclock.

 

Best answer by niranukroshah

Hey Guys! Just wanted to post that the issue has been resolved. The problem was mainly in how I was handling the ACK function. 
For my RTC functions I had following order-

void ds1338_rtc_init( void )
{
	RTC_I2C_START() ;
	RTC_I2C_OPDATA( 0xd0 ) ;
	RTC_I2C_WAITACK() ;

	RTC_I2C_OPDATA( 0x07 ) ;
	RTC_I2C_WAITACK() ;

	RTC_I2C_OPDATA( 0x90 ) ;
	RTC_I2C_WAITACK() ;
	RTC_I2C_STOP() ;
}

 But in my OPDATA function I was already calling the ACK function, which caused double usage of it, causing problem.

void i2c1_opdata(UINT8 dat)
{
 for(UINT8 i = 0; i < 8; i++)
 {
 if(dat & 0x80) I2C1_SDA_HIGH();
 else I2C1_SDA_LOW();

 I2C1_SCL_HIGH(); 
 delay_us(I2C1_DELAY);
 I2C1_SCL_LOW(); 
 delay_us(I2C1_DELAY);

 dat <<= 1;
 }

 
 i2c1_waitack();
}

After removing the i2c1_waitack() function at the end the bit-banging worked.


2 replies

TDK
Super User
October 8, 2025

Are pins in open-drain mode?

Can you show a logic analyzer capture of what's happening? Do you get an ACK?

You show functions, but not the larger context that calls them.

"If you feel a post has answered your question, please click ""Accept as Solution""."
Associate
October 8, 2025

Yes, I have configured the pins as open drain, so that isn't the problem. 

Following are the outputs i observed on the dso-

1000091364.jpg

This is the start condition 

1000091365.jpg

And this is the slave address.

And about ACK, i manually pulled the sda down but I didn't get time to check for RTC. But it is working as the HAL code was functional.

 

 

And for the larger context, following is the flow(I am unable to provide full code as my mentor has placed some restrictions)-

  • I am using timer3 to produce interrupt every 1 second, and hence the reading of RTC is done every 1sec.
  • The RTC is initialized by start condition - sending it's address 0xd0(write mode) and then I am waiting for ack. Then I am accessing 07h control register and writing 09h.
  • After this initialization i am just reading from 00h register of RTC. I am handling read and write bit properly so that is not causing any issue.

Please do point out if I am making any mistake here.

 

TDK
Super User
October 8, 2025

You should put external pullups so SDA/SCL are idle high. At the start of your start condition, both are low, which is not a valid state to start off with. May or may not be okay.

At the end of your start condition, you have SDA low and SCL low, which is good.

At the start of your slave address, SCL is now somehow low again. That's a problem and suggests other things are happening between start condition and sending the address.

Ignoring/hardcoding ACK isn't ideal and robs you of knowing whether or not the slave is responding.

"If you feel a post has answered your question, please click ""Accept as Solution""."
niranukroshahAuthorBest answer
Associate
October 21, 2025

Hey Guys! Just wanted to post that the issue has been resolved. The problem was mainly in how I was handling the ACK function. 
For my RTC functions I had following order-

void ds1338_rtc_init( void )
{
	RTC_I2C_START() ;
	RTC_I2C_OPDATA( 0xd0 ) ;
	RTC_I2C_WAITACK() ;

	RTC_I2C_OPDATA( 0x07 ) ;
	RTC_I2C_WAITACK() ;

	RTC_I2C_OPDATA( 0x90 ) ;
	RTC_I2C_WAITACK() ;
	RTC_I2C_STOP() ;
}

 But in my OPDATA function I was already calling the ACK function, which caused double usage of it, causing problem.

void i2c1_opdata(UINT8 dat)
{
 for(UINT8 i = 0; i < 8; i++)
 {
 if(dat & 0x80) I2C1_SDA_HIGH();
 else I2C1_SDA_LOW();

 I2C1_SCL_HIGH(); 
 delay_us(I2C1_DELAY);
 I2C1_SCL_LOW(); 
 delay_us(I2C1_DELAY);

 dat <<= 1;
 }

 
 i2c1_waitack();
}

After removing the i2c1_waitack() function at the end the bit-banging worked.