Need help understanding TwoWire protocol in Atmega328P

Go To Last Post
17 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all. I am not sure whether to post it in Arduino section or here, but seems I am dealing with the lower level details of the AVR itself, I think it's best to be posted here.

 

Okay first, I am trying to learn the TwoWire/I2C but it is not necessarily for Arduino. I went with Arduino since it is easier to simulate and there are quite few libraries available. So, to test my simple TWI library, I hooked a 24C02C serial eeprom to the Arduino UNO. I got the datasheet for Atmega328P and 24C02C devices and start writing simple TWI based on the TWI section. The instructions are very clear. Props to the writer of the manual.

 

However, I am a little bit stuck. From the datasheet pg 267, the TWDR is an 8-bit register. The datasheet of 24C02C however states that it needs a WORD address(the location of where data to be written, not to be confused with slave address), does it mean it needs a 16-bit unsigned integer to be sent as the address? Or am I wrong?

 

Here's a snippet to send the address or data since they uses the same TWDR

uint8_t I2C_writeData(uint8_t data)
{
	// load data into data register
	TWDR = data;
	// start transmission of data
	TWCR = (1<<TWINT) | (1<<TWEN);
	// wait for end of transmission
	while( !(TWCR & (1<<TWINT)) );
	
	if( (TWSR & 0xF8) != TW_MT_DATA_ACK ){ return 1; }
	
	return 0;
}

In my main code(or sketch for you Arduino users),

#include <I2CinC.h>

#define DEVICEADDRESS 0xA0

unsigned char data;

void setup() {
  Serial.begin(9600);
  I2C_init();

  //writing
  I2C_start();
  I2C_writeAddress(DEVICEADDRESS | WRITE);
 //write upper byte
  I2C_writeData(0x01>>8); //write upper byte
  I2C_writeData(0x01); //write lower byte
  I2C_writeData('A');
  I2C_stop();

  delay(10);

  //reading
  I2C_start();
  I2C_writeAddress(DEVICEADDRESS | WRITE);
  I2C_writeData(0x01>>8); //write upper byte
  I2C_writeData(0x01);
  I2C_restart();
  I2C_writeAddress(DEVICEADDRESS | READ);
  data = I2C_read_nack();
  I2C_stop();
  Serial.print(data, HEX);
}

void loop() {
}

What should be stored is 'A' but instead '1' is stored. This means that the eeprom thinks

I2C_writeData(0x01);

is the DATA to be written.

 

What is wrong here?

 

I also posted my question in the Arduino forum but it doesn't get much response so perhaps AVR freaks have some ideas...

This topic has a solution.

Just a technician regularly cleaning up engineers' mess...

Last Edited: Tue. Oct 10, 2017 - 02:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What value of I2C pull up resistors are you using?  Is the EPROM on a shield or BB?

 

Jim

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

cyberjupiter wrote:
The datasheet of 24C02C however states that it needs a WORD (sic) address

It doesn't put it in all capitals - it just says, "word address".

 

"word" here is just the generic term for an arbitrarily-sized storage unit.

 

Some systems have a word size of just 1 byte; some have multiple bytes per word.

 

the location of where data to be written, not to be confused with slave address

you understand that distinction?

 

 

does it mean it needs a 16-bit unsigned integer to be sent as the address?

No.

 

Look at the diagram:

 

It clearly shows that it's just one byte

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Jim

I am using the internal pull up by using the Arduino insturctions "digitalWrite(SCL, 1)" and "digitalWrite(SDA, 1)". I am not running on real hardware, I am using Proteus to simulate my circuits.

 

@awneil

Thanks. That really clears me up. I didn't know whether to believe the diagram or believe that "word" means 16-bit initially since I only know that word stands for an 16-bit unsigned integer(at least in Arduino)

 

awneil wrote:
you understand that distinction?

Since the term "address" can be used interchangeably, I guess I need to add extra information. Sorry if it didn't help.

 

Anyway, that settles the problem. A few more question though,

 

1. Then for this kind of eeprom, the maximum adress will be 0xFF? EEPROM such as 24LC512 has a maximum of 0xFFFF. Correct?

 

2. If so, then I would need to write a specific library for specific type of eeprom since the address range is different.

Just a technician regularly cleaning up engineers' mess...

Last Edited: Mon. Oct 9, 2017 - 02:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cyberjupiter wrote:
 believe that "word" means 16-bit

"word" does not mean 16-bit.

 

In certain contexts, a word happens to be 16 bits.

 

 

the term "address" can be used interchangeably

There are two addresses to consider here:

  1. The address of the chip on the bus - this is the Slave address.
  2. The address of a particular "word" within the memory array.

 

 

1. Then for this kind of eeprom the maximum adress will be 0xFF? 

That particular EEPROM is 2K bits; organised as 256 eight-bit  (single-byte) words.

 

http://www.microchip.com/wwwproducts/en/24C02C

 

So, yes - its maximum address will be 0xFF

 

 EEPROM such as 24LC512 has a maximum of 0xFFFF. Correct?

That particular EEPROM is 512K bits; organised as 65536 eight-bit  (single-byte) words.

 

http://www.microchip.com/wwwproducts/en/24LC512

 

So, yes - its maximum address will be 0xFFFF

 

 

2. If so, then I would need to write a specific library for specific type of eeprom since the address range is different.

That's entirely up to you - there is always a trade-off between optimising for a particular case, and generalising for maximum applicability ...

 

 

EDIT

 

fix quote

Last Edited: Mon. Oct 9, 2017 - 02:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cyberjupiter wrote:
I am not sure whether to post it in Arduino section or here, but seems I am dealing with the lower level details of the AVR itself,

Actually, it has nothing to do with the Arduino or AVR: these are features of the EEPROM chip itself - the "host" is irrelevant.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cyberjupiter wrote:
, the maximum adress will be 0xFF?
yes, it is a 2Kb device. That is 256 bytes. So 0x00 .. 0xFF

cyberjupiter wrote:
24LC512 has a maximum of 0xFFFF. Correct?
That is a 512Kb device so has 64KB capacity. So, yes the address range will be 0x0000 to 0xFFFF
cyberjupiter wrote:
I would need to write a specific library for specific type of eeprom since the address range is different.
I don't see why that would be. As long as you use data types that cater for the largest address range you may encounter then it should be usable for all smaller devices surely? You just need something like:

#define EEPROM2048 2048
#define EEPROM512K 524288
#define DEV EEPROM2048

...

#if DEV == EEPROM2048
    send1byteaddr();
#elif DEV = EEPROM512K
    send2byteaddr()
etc.

So at the time you build the code you define "DEV" to the right part and then build the code and it will adapt accordingly.

 

You could do that at run time if you like:

#define EEPROM2048 2048
#define EEPROM512K 524288
uint32_t dev = EEPROM2048;

...

if (dev == EEPROM2048) {
    send1byteaddr();
}    
else {
    send2byteaddr();
}
etc.

but this costs more in terms of code space and time to execute as it now has the code for all variants and needs to make decisions at run time about how to handle the device.

 

As it seems likely the size of EEPROM in the design is known at compile time and cannot change later then the #define approach will likely be "better".

 

Loads of device supporting code in micros follows this kind of pattern: either a compile time or even a run time choice of what is being supported and code that takes subtly different approaches at key points to adopt to what is being controlled at a particular time. The vast majority of the code remains common for the majority of what needs to be done.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cyberjupiter wrote:
I also posted my question in the Arduino forum

So give a link so that everyone can see the complete story - and not wast time repeating stuff that's already been said elsewhere!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Don't use the internal resistors of the AVR for pull ups on the I2C...use external 2k2 or 4k7 resistors instead.

Also. Google Peter Fleury I2C....most of us here use that instead of trying to reinvent the wheel

Jim

If you want a career with a known path - become an undertaker. Dead people don't sue! - Kartman

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The "smaller" 24Cxx devices have a single byte for the memory location.  e.g. 24C01 -> 24C16

The "bigger" 24Cxx have two bytes for the memory location.   e.g. 24C32 -> 24C512 

 

You can support several devices on the bus.   e.g. by using separate hardware addresses by selecting the A0-A2 pins

Above 64kB the 24C1025 accepts two different Slave Addresses in one chip.

You can use the same software for big and small.   Just set a variable for the page size and read/write the second address byte as appropriate.

 

I suspect that you are a student.   The 24C02 is fairly obsolete.

 

Sometimes these "tiny" EEPROMs are used to store device serial numbers or configuration modes.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cyberjupiter wrote:
I am using the internal pull up by using the Arduino insturctions "digitalWrite(SCL, 1)" and "digitalWrite(SDA, 1)". 

 

Arduino documentation wrote:
If the pin is configured as an INPUT, digitalWrite() will enable (HIGH) or disable (LOW) the internal pullup on the input pin. 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The 24C02 is fairly old, so it is used primarily today for training on I2C, as you are doing. [Nowdays, a 256 byte block of data would often be stored into the AVR's on-board EEPROM.]  It has 2K bits, or 256 bytes.  There are three hardware address pins that allow multiple 24C02 ICs to be tied together with each one using the standard I2C slave address range.  Let's put all three hardware address lines at logic 0.  These three hardware address lines are incorporated into the slave address. 

 

You access this IC by sending START, then the slave address (the last three bits of the 7-bit start address are logic 0 to reflect the hardware setting) with bit 0 cleared to indicate that we wish to write to the device.  The next byte sent from the master is which of the 256 EEPROM locations that you want to read.  But in order to read from the EEPROM, you need to put the device into read mode.  You first write the address that you want to read, then do a STOP and another START with bit 0 set, which will indicate that a read is requested from the EEPROM.

  

To read from a serial EEPROM:

  Send START [SDA asserted, then SCL asserted]  

  Send Slave address plus Write

  Send the EEPROM address that you want to read from

  Send STOP

  Send START

  Send Slave address plus Read

  Master sends clock pulses on the SCK pin EEPROM will send the data from the EEPROM address that you sent previously (on the SDA pin), and automatically increment the address

  Master will send an ACK to the EEPROM after each byte received from the EEPROM

  after reading eight bytes, the master must send a new EEPROM address to the IC for reading the next eight bytes.

  

 

  Note that this EEPROM is based internally on 8-byte pages.  So if you send address 0x00 then you can read 8 bytes [0x00 to 0x07] and then the EEPROM internal address counter will roll over back to EEPROM address 0x00 and the 8th byte that you read will not be from EE address 0x08, but 0x00.  To read more than eight bytes sequentially, you send the START and slave address, read 8 bytes, then STOP.  Send another START plus slave address/write with the next byte being 0x08 in order to access the next page of eight bytes. ( More modern EEPROMs have page sizes of 128  and 256 bytes).

 

Writing to the EE is a little easier:

  Send START [SDA asserted, then SCL asserted]  

  Send Slave address plus Write

  Send the EEPROM address that you want to write to

  Send up to eight bytes of data that will be written to the EE (the EE address gets incremented automatically)

  Send STOP

  Send START plus write (bit 0 low)

  Send new EE address for the next eight bytes.

  repeat

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Rubbish. You can read a 24Cxx indefinitely. It will simply wrap around when it gets to the end.
.
Writing to the EEPROM depends on the page boundaries. Your device has an 8-byte page ( I think)
If you start at location 5, writing 8 bytes will go to location 5, 6, 7 and then 0, 1, 2, 3, 4 i.e. it wraps around to the start of the page.
.
If you want to write 8 bytes at locations 5 .. 13, you write 3 bytes to one page. Wait for page write to complete. Set location to the next page. Then write the other 5 bytes to that page.
.
Yes, it is a bit fiddly. SPI eeproms work the same. A library should look after this.
.
David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

awneil wrote:
Actually, it has nothing to do with the Arduino or AVR: these are features of the EEPROM chip itself - the "host" is irrelevant.

Well, you can't expect me to post it in Microchip forum cheeky

 

Anyway, I haven't get any input on Arduino forum up till now, so no, your time was not wasted(I hope) but If you really want it...

https://forum.arduino.cc/index.php?topic=504561.0

 

clawson wrote:

I don't see why that would be. As long as you use data types that cater for the largest address range you may encounter then it should be usable for all smaller devices surely? You just need something like:

#define EEPROM2048 2048
#define EEPROM512K 524288
#define DEV EEPROM2048

...

#if DEV == EEPROM2048
    send1byteaddr();
#elif DEV = EEPROM512K
    send2byteaddr()
etc.

So at the time you build the code you define "DEV" to the right part and then build the code and it will adapt accordingly.

 

You could do that at run time if you like:

#define EEPROM2048 2048
#define EEPROM512K 524288
uint32_t dev = EEPROM2048;

...

if (dev == EEPROM2048) {
    send1byteaddr();
}    
else {
    send2byteaddr();
}
etc.

but this costs more in terms of code space and time to execute as it now has the code for all variants and needs to make decisions at run time about how to handle the device.

 

As it seems likely the size of EEPROM in the design is known at compile time and cannot change later then the #define approach will likely be "better".

 

Loads of device supporting code in micros follows this kind of pattern: either a compile time or even a run time choice of what is being supported and code that takes subtly different approaches at key points to adopt to what is being controlled at a particular time. The vast majority of the code remains common for the majority of what needs to be done.

 

Thanks clawson. I agree with both of your method.

 

In the 1st method, the only downside is that I need to know the memory size before the compile time. In my current case that's fine. However If I were to use a different eeprom of different memory size, I would need to get my hands on the library. In the library user's perspective, this isn't really a preferable method.

 

The 2nd method will work well as long as I specify every possible eeprom sizes. I don't mind having a harcoded library, some people might disagree with me.

 

Or maybe I could create a general eeprom library that contains macros of every possible eeprom, such as this

#if defined 24C02C
#  include <24c02c.h>
#elif defined (24LC256)
#  include <24lc256.h>
//and so on...

That leaves me with a source files of every possible eeprom. What do you think?

 

jgmdesign wrote:
Don't use the internal resistors of the AVR for pull ups on the I2C...use external 2k2 or 4k7 resistors instead. Also. Google Peter Fleury I2C....most of us here use that instead of trying to reinvent the wheel Jim

Thanks for the advice Jim but I'm not reinventing any wheel here. This is solely for the purpose of learning the underlying system.

 

Just a technician regularly cleaning up engineers' mess...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

The "smaller" 24Cxx devices have a single byte for the memory location.  e.g. 24C01 -> 24C16

The "bigger" 24Cxx have two bytes for the memory location.   e.g. 24C32 -> 24C512 

 

You can support several devices on the bus.   e.g. by using separate hardware addresses by selecting the A0-A2 pins

 

Above 64kB the 24C1025 accepts two different Slave Addresses in one chip.

You can use the same software for big and small.   Just set a variable for the page size and read/write the second address byte as appropriate.

 

I suspect that you are a student.   The 24C02 is fairly obsolete.

 

Sometimes these "tiny" EEPROMs are used to store device serial numbers or configuration modes.

 

 

David.

 

Thanks David, that was really useful information except that your guess is wrong laugh. I am not using the 24C02 in any application, I just picked a small eeprom which I think is easy to understand(except that the word adrdess confused me). What I want to learn is the TWI protocol and eeprom is just a test device. I will experiment with different I2C devices to test my understanding. In the future, I might need to use different kind of I2C/TWI devices so it wouldn't hurt to learn them now, I believe it would benefit me.

 

Of course, I would use the available TWI library as pointed by @jgmdesign when designing a real embedded system. I wouldn't waste my time rewriting the TWI.

Just a technician regularly cleaning up engineers' mess...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

cyberjupiter wrote:
jgmdesign wrote: Don't use the internal resistors of the AVR for pull ups on the I2C...use external 2k2 or 4k7 resistors instead. Also. Google Peter Fleury I2C....most of us here use that instead of trying to reinvent the wheel Jim Thanks for the advice Jim but I'm not reinventing any wheel here. This is solely for the purpose of learning the underlying system.

 

Just Make sure you use the external resistors at least.  I can understand the desire to learn the interface, but I would suggest that you still use Fleurys library as its well known, and any questions you would/will have can be better answered as we would be working from a known library, as opposed to trying to decipher if your question/problem is related to the TWI interface, or your library.  As we have said to the others before you on this, use Peters first to get the understanding, then by all means roll your own if you so desire.

 

Cheers,

Jim

If you want a career with a known path - become an undertaker. Dead people don't sue! - Kartman

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jgmdesign wrote:
Just Make sure you use the external resistors at least.  I can understand the desire to learn the interface, but I would suggest that you still use Fleurys library as its well known, and any questions you would/will have can be better answered as we would be working from a known library, as opposed to trying to decipher if your question/problem is related to the TWI interface, or your library.  As we have said to the others before you on this, use Peters first to get the understanding, then by all means roll your own if you so desire.   Cheers, Jim

 

Noted about that Jim. I wouldn't want to use my poorly written TWI library too laugh, at least not until I full grasp the concept. As stated in #15, I would definitely use the one you suggested in real applications.

Just a technician regularly cleaning up engineers' mess...