2024-09-12 12:51 AM - last edited on 2024-09-12 12:57 AM by SofLit
Hi,
I need to use the MCP79410 rtc (or alikes: DS1307, PCF8583, etc) but the STM32CubeIDE I2C seems that can't handle the read/write protocol for these devices.
For example, as the below image shows, one needs to send the control byte (writing) followed by the address byte and, without emitting the stop signal, but a start instead, to send the control byte again, but for reading, and finally the stop signal:
I'm using the HAL_I2C_Master_Receive() function:
ret_val = HAL_I2C_Master_Receive( &hi2c2, 0x6f<<1, mytime, 3, 1000 );
It's obvious that this function alone can't handle the rtc protocol and my question is: which STM32 I2C HAL function, or function combination, should I use to comply with such protocol? Any ideas or an example?
BTW, I2C is working because I can see both signals, SDA and SCL in the oscilloscope. The uC is STM32L073. It includes a RTC, but for the current application I need to use an external one (the project is power hungry, and this chip doesn't include a VBatt terminal so I'd need to create an intrincate power supplies scheme). Anyway, there must be a way to drive these rtc chips (I hope so).
Thank you in advanced!
Solved! Go to Solution.
2024-09-24 03:39 PM - edited 2024-09-24 07:40 PM
Workaround:
Split the command into two operations. According to the Fig. 6.4 above:
1. Emmit a write command to set the reading start address (RTC, RAM or EEPROM), and of course, the device address (called "the control byte").
2. Emmit a read command to get as many bytes as needed.
void read_time_( RTC_TimeDef& time )
{
uint8_t buf[3];
this->error_code = RTC_Status::ERROR;
buf[0] = MCP79410_RTC_START_ADDRESS;
auto ret_val = i2c->master_write( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 1 ); // (1)
if( ret_val )
{
ret_val = i2c->master_read( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 3 ); // (2)
if( ret_val )
{
time.seconds = bcd2dec( buf[0] & 0x7f );
time.minutes = bcd2dec( buf[1] & 0x7f );
time.hours = bcd2dec( buf[2] & 0x3f );
this->error_code = RTC_Status::OK;
}
}
}
These instructions:
i2c->master_write( /*...*/ );
i2c->master_read( /*...*/ );
are an abstraction for the I2C bus. You can use directly the HAL's API just adjusting the parameters to your own needs:
bool I2CL073::master_write( uint8_t device_address, uint8_t* buffer, uint16_t buffer_len )
{
return HAL_I2C_Master_Transmit( this->i2c, device_address, buffer, buffer_len, this->timeout ) == HAL_OK;
}
For writing you can bundle the whole command into a single write command. However, in the case of the RTC I first needed to stop the clock before I write into the registers:
void write_time_( const RTC_TimeDef& time )
{
uint8_t buf[4];
this->error_code = RTC_Status::ERROR;
buf[0] = MCP79410_RTC_START_ADDRESS;
buf[1] = 0; // stop the clock
auto ret_val = i2c->master_write( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 2 );
if( ret_val )
{
buf[0] = MCP79410_RTC_START_ADDRESS;
buf[1] = dec2bcd( time.seconds ) | RTC_ST_BIT_MASK;
buf[2] = dec2bcd( time.minutes );
buf[3] = dec2bcd( time.hours );
ret_val = i2c->master_write( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 4 );
if( ret_val ) this->error_code = RTC_Status::OK;
}
}
Hope this information helps fellows out there that are also struggling with the I2C HAL implementation.
2024-09-12 01:54 AM
Maybe this thread will give you an idea?
Regards
/Peter
2024-09-12 12:48 PM
Hi Peter,
I had already read that same post before my post, but in a naive and quick-and-dirty implementation that didn't work for me:
ret_val = HAL_I2C_Mem_Read(&hi2c2, 0x6f<<1, 0, 8, mytime, 3, 1000 );
I'll give it a try once more and I'll keep you post.
Greetings!
2024-09-24 03:39 PM - edited 2024-09-24 07:40 PM
Workaround:
Split the command into two operations. According to the Fig. 6.4 above:
1. Emmit a write command to set the reading start address (RTC, RAM or EEPROM), and of course, the device address (called "the control byte").
2. Emmit a read command to get as many bytes as needed.
void read_time_( RTC_TimeDef& time )
{
uint8_t buf[3];
this->error_code = RTC_Status::ERROR;
buf[0] = MCP79410_RTC_START_ADDRESS;
auto ret_val = i2c->master_write( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 1 ); // (1)
if( ret_val )
{
ret_val = i2c->master_read( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 3 ); // (2)
if( ret_val )
{
time.seconds = bcd2dec( buf[0] & 0x7f );
time.minutes = bcd2dec( buf[1] & 0x7f );
time.hours = bcd2dec( buf[2] & 0x3f );
this->error_code = RTC_Status::OK;
}
}
}
These instructions:
i2c->master_write( /*...*/ );
i2c->master_read( /*...*/ );
are an abstraction for the I2C bus. You can use directly the HAL's API just adjusting the parameters to your own needs:
bool I2CL073::master_write( uint8_t device_address, uint8_t* buffer, uint16_t buffer_len )
{
return HAL_I2C_Master_Transmit( this->i2c, device_address, buffer, buffer_len, this->timeout ) == HAL_OK;
}
For writing you can bundle the whole command into a single write command. However, in the case of the RTC I first needed to stop the clock before I write into the registers:
void write_time_( const RTC_TimeDef& time )
{
uint8_t buf[4];
this->error_code = RTC_Status::ERROR;
buf[0] = MCP79410_RTC_START_ADDRESS;
buf[1] = 0; // stop the clock
auto ret_val = i2c->master_write( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 2 );
if( ret_val )
{
buf[0] = MCP79410_RTC_START_ADDRESS;
buf[1] = dec2bcd( time.seconds ) | RTC_ST_BIT_MASK;
buf[2] = dec2bcd( time.minutes );
buf[3] = dec2bcd( time.hours );
ret_val = i2c->master_write( MCP79410_RTC_7BITS_ADDRESS<<1, buf, 4 );
if( ret_val ) this->error_code = RTC_Status::OK;
}
}
Hope this information helps fellows out there that are also struggling with the I2C HAL implementation.