I2C communication with TINY RTC MODULE

Hi everyone,

I try since few weeks now to communicate between a homemade board with ATMEGA328P running at 20MHZ

and a TINY RTC MODULE bought on ebay for few dollars.

I checked twice my wiring (SCL and SDA, VCC and GND, pullup resistors etc)

I’ve already try :

  1. Cut the charging circuit to work with CR2032 instead of LIR2032

  2. Change the crystal oscillator by one picked up in a wall clock

  3. I made a SMT board home with the essential : DS1307, quartz and battery holder

My display always show the DS1307 adress instead of hour, minute, seconds :

68/68/68

68h68m.68s

I’m quite sure the problem comes from the code. I post it so that everyone can see it

#define F_SCL 100000L // I2C clock speed 100 KHz
#define READ 1
#define TW_START 0xA4							//condition de départ (TWINT,TWSTA,TWEN)
#define TW_STOP 0x94							//condition d'arret (TWINT,TWSTO,TWEN)
#define TW_ACK 0xC4								//Retour de l'ACK (acknowledgment) a l'esclave
#define TW_NACK 0x84							//Pas de retour de l'ACK (acknowledgment) a l'esclave
#define TW_SEND 0x84							//Commande d'envoi de donnée (TWINT,TWEN)
#define TW_READY (TWCR & 0x80)					// Prêt quand TWINT repasse a 1 (registre de controle TWCR)
#define TW_STATUS (TWSR & 0xF8)					// Valeur retournée au registre de status TWSR
#define I2C_Stop() TWCR = TW_STOP				// Macro de stop

//**********************************DS1307 RTC********************************
#define DS1307 0x68								//adresse DS1307
#define SECONDS_REGISTER 0x00
#define MINUTES_REGISTER 0x01
#define HOURS_REGISTER 0x02
#define DAYOFWK_REGISTER 0x03
#define DAYS_REGISTER 0x04
#define MONTHS_REGISTER 0x05
#define YEARS_REGISTER 0x06
#define CONTROL_REGISTER 0x07
#define RAM_BEGIN 0x08
#define RAM_END 0x3F
void dateheure(){
	char* Affjour="  ";
	char* Affmois="  ";
	char* Affannee="  ";
	char* Affheure="  ";
	char* Affminute="  ";
	char* Affseconde="  ";

	sortie=1;
	lcd_clrscr();
	lcd_gotoxy(3,0);
	lcd_puts("DATE ET HEURE");

	DDRC|= (1<<PC4);
	PORTC |=(1<<PC4);
	_delay_ms(10);
	PORTC&=~ (1<<PC4);
	_delay_ms(10);
	DDRC&=~(1<<PC4)|(1<<PC5);

	while (sortie !=0){
		keyboard=kbd();
		switch (keyboard){
			case 0 : main();
			break;

			case 13 :
				I2C_Init();
				DS1307_GetDate(&months,&days,&years);
				DS1307_GetTime(&hours,&minutes,&seconds);
				sprintf(Affjour, "%i", bcdToDec(days));
				sprintf(Affmois, "%i", bcdToDec(months));
				sprintf(Affannee, "%i", bcdToDec(years));
				sprintf(Affheure, "%i", bcdToDec(hours));
				sprintf(Affminute, "%i", bcdToDec(minutes));
				sprintf(Affseconde, "%i", bcdToDec(seconds));
				lcd_gotoxy(0,1);
				lcd_puts(Affjour);
				lcd_puts("/");
				lcd_puts(Affmois);
				lcd_puts("/");
				lcd_puts(Affannee);
				lcd_gotoxy(0,2);
				lcd_puts(Affheure);
				lcd_puts("h");
				lcd_puts(Affminute);
				lcd_puts(".");
				lcd_puts(Affseconde);
				break;
		}

	}
}

byte decToBcd(byte val){
	// Convert normal decimal numbers to binary coded decimal
	return ( (val/10*16) + (val%10) );
}
byte bcdToDec(byte val){
	// Convert binary coded decimal to normal decimal numbers
	return ( (val/16*10) + (val%16) );
}

// ---------------------------------------------------------------------------
// I2C (TWI) ROUTINES
//
// On the AVRmega series, PA4 is the data line (SDA) and PA5 is the clock (SCL
// The standard clock rate is 100 KHz, and set by I2C_Init. It depends on the AVR osc. freq.

void I2C_Init()
// TWBR=16/(16+2(TWBR)), prescalar = 0.
// Pour 100KHz SCL, TWBR = ((F_CPU/F_SCL)-16)/2 = ((16/0.1)-16)/2 = 184/2 = 92.
{
	//PORTC&=~(1<<PC4)|(1<<PC5);
	TWSR = 0; // set prescalar to zero
	//TWBR = ((F_CPU/F_SCL)-16)/2; // set SCL frequency in TWI bit register
	TWBR =0x5c;
}

byte I2C_Start (byte slaveAddr)
{
	TWCR = TW_START;									//condition de depart
	while (!TW_READY);									//Attente
	TWDR = slaveAddr;									//on charge l'adresse de l'esclave
	TWCR = TW_SEND;										//on lance l'envoi
	while (!TW_READY);									//Et on attent
	return (TW_STATUS==0x18); // return 1 if found; 0 otherwise
}

byte I2C_Write (byte data)								//envoi de donnees a l'esclave
{
	TWDR = data;										//On charge la donnée dans le registre de données
	TWCR = TW_SEND;										//on lance l'envoi
	while (!TW_READY);									//Attente
	return (TW_STATUS!=0x28);
}
byte I2C_ReadACK () // reads a data byte from slave
{
	TWCR = TW_ACK; // ack = will read more data
	while (!TW_READY); // wait
	return TWDR;
	//return (TW_STATUS!=0x28);
}
byte I2C_ReadNACK () // reads a data byte from slave
{
	TWCR = TW_NACK; // nack = not reading more data
	while (!TW_READY); // wait
	return TWDR;
	//return (TW_STATUS!=0x28);
}
void I2C_WriteByte(byte busAddr, byte data)
{
	I2C_Start(busAddr); // send bus address
	I2C_Write(data); // then send the data byte
	I2C_Stop();
}

void I2C_WriteRegister(byte busAddr, byte deviceRegister, byte data)
{
	I2C_Start(busAddr); // send bus address
	I2C_Write(deviceRegister); // first byte = device register address
	I2C_Write(data); // second byte = data for device register
	I2C_Stop();
}
byte I2C_ReadRegister(byte busAddr, byte deviceRegister)
{
	byte data = 0;
	I2C_Start(busAddr); // send device address
	I2C_Write(deviceRegister); // set register pointer
	I2C_Start(busAddr+READ); // restart as a read operation
	data = I2C_ReadACK(); // read the register data
	I2C_Stop(); // stop
	return data;
}

void DS1307_GetTime(byte *hours, byte *minutes, byte *seconds)
// returns hours, minutes, and seconds in BCD format
{
	*hours = I2C_ReadRegister(DS1307,HOURS_REGISTER);
	*minutes = I2C_ReadRegister(DS1307,MINUTES_REGISTER);
	*seconds = I2C_ReadRegister(DS1307,SECONDS_REGISTER);
	if (*hours & 0x40) // 12hr mode:
	*hours &= 0x1F; // use bottom 5 bits (pm bit = temp & 0x20)
	else *hours &= 0x3F; // 24hr mode: use bottom 6 bits

}
void DS1307_GetDate(byte *months, byte *days, byte *years)
// returns months, days, and years in BCD format
{
	*months = I2C_ReadRegister(DS1307,MONTHS_REGISTER);
	*days = I2C_ReadRegister(DS1307,DAYS_REGISTER);
	*years = I2C_ReadRegister(DS1307,YEARS_REGISTER);
}
void SetTimeDate()
// simple, hard-coded way to set the date.
{
	I2C_WriteRegister(DS1307,MONTHS_REGISTER, 0x08);
	I2C_WriteRegister(DS1307,DAYS_REGISTER, 0x31);
	I2C_WriteRegister(DS1307,YEARS_REGISTER, 0x13);
	I2C_WriteRegister(DS1307,HOURS_REGISTER, 0x08+0x40); // add 0x40 for PM
	I2C_WriteRegister(DS1307,MINUTES_REGISTER, 0x51);
	I2C_WriteRegister(DS1307,SECONDS_REGISTER, 0x00);
}

Is that supposed to be a regular C-project, or based on the arduino code structure? Eitherway, I can’t find a main function that ties it all together. Or even a setup or loop function in it to do the same. So how did you compile this to something working?

Hi,

First of all, thanks for your help.

I work with ATMEL studio 6 and my all code is too big to fit the forum.

I put just the display function and the DS1307 routine.

http://nsm08.casimages.com/img/2016/01/ … 888071.jpg

Post code that will actually compile and run.

What is this supposed to do?

case 0 : main();

To compile my code, all you have to do is to put the “void dateheure()” function content into the main() and modify it using your LCD library (Mine has been modifyed to fit my board).

It should compile without error.

For your question, my keyboard function return 13 when no keypress.

If i press 0 , it returns to the main() (main menu).

And what comes out on the serial port?

Serial port ? Do you mean UART ? I plan to send data by a bluetooth module to a tablet.

My I2Cseems to work because my display shows me something, that means the program pass through the wait statements (while (!TW_READY))

What do you think ?

The only thing that hasn’t been done is to change the AVR by a new one …

the I2C pins are burned ?

I don’t know what to do

Sorry, I saw sprintf and thought serial debug output. My bad.

Still in order to disprove or prove that anything is damaged you would send out debug data to the pc as soon as it was received or calculated. Before you do further datamanipulation of what is received I would put more debugging commands in the I2C_Write, I2C_Start, I2C_ReadACK and I2C_ReadNACK functions to see if something goes wrong there in the code, or in the I2C silicon. If you rely on only looking at the LCD then you have more processing/conversion code that might have a hand in obscuring the bug.

Even better would be looking at the signal with an oscilloscope, logic-analyser or dedicaded I2C sniffer.

Ok, i’m french, so, it’s hard to decode technical english fast…

What kind of manipulation would you put ? Break point with keyboard entry ?

I have 10 or 11 ATMEGA328P, it will take me few minutes to change it, i just need to find or recalculate the FUSES bytes.

I won’t work on this before wednesday morning.

I’ll post results

Same goes for my French. Je ne parlez pas francais. :wink:

I just changed and program a new chip and the issue is the same.

It drives me crazy now .

I put breakpoints in each sub function of I2C routine and the structure is good but not the functionning.

I don’t know what would be interresting to show on the display during the transfer.

Can you look into the registers of the AVR as you step through the code?

I’m trying to display the return data of each function but i always get 186 character of extended ascii table

which looks like this : ||

I tryed to change the DS1307 adress to 0x44 and the program still runs. It should stop on waiting statement of the first START function ?

Just to know, what happens if you execute without any RTC chip?

Get yourself a logic analyser or oscilloscope to see what is going on with the pulses.

I have an oscilloscope but it’s a very old one without memory, i will have trouble to analyse anything with that but i’ll see the pulses.

I’ll try this tonight

Write a smaller program that repeatedly reads only a single register in the i2c chip. Only days for example. Then you should get a stable view if you use the proper horizontal timebase, triggering and hold-off setting. You certainly should be able to see the shapes of the pulses.

The code seems correct for you ?

Maybe i’m doing something wrong with the fuses ? I’ll check these too.

Thanks a lot for your precious advices

I’m not an expert on working with I2C on the register level. But I think your code works different than what is explained how to do it in the Atmega328p datasheet ([on page 226) . You use hard-coded values like TW_START, TW_READY in define statements. Those are constant values that are resolved to numbers when the program is compiled, I think. (I’m quite tired now, I may be mistaken.) The code in the datasheet is resolved at runtime using the actual register content at that time. So I wouldn’t be surprised it that is the cause. I suggest you use the c-code steps on page 226 and see if it works better.](http://www.atmel.com/images/doc8161.pdf)