sunnuntai 5. heinäkuuta 2015

Using serial (I2C) memories


Occasionally you will need some small-ish amount of non-volatile memory (that is, memory contents are retained even after power outage). If the amount of memory needed isn't huge, small EEPROMs with I2C interface might be useful - they're small (SO8 package being fairly typical, also DIP8 packages should be available if you are more comfortable with them), cheap (few euros in low quantities, depending mainly on memory size and type) and easy(-ish) to use - just two wires needed. For example Microchip's 24AA256, but there are numerous others too.

History of PROMs, EPROMs and EEPROMs - and especially development towards more user-friendly (err, in this context "developer-friendly" might be better phrase) is fairly interesting topic, but I'll skip that for now.

For now it's sufficient to know the drawbacks; EEPROMs have finite amount of write cycles - typical nowadays is around 1 million writes (that is, each byte of memory can be overwritten about 1 million times before becoming defuct). There are other pin-compatible memory types that don't have that issue, like FRAMs (link as an example) that can be written over practically infinitely but often those have been smaller (in memory density) and more expensive (5 eur instead of 50 cents, so not earth-shattering but still).
Another small drawback is that EEPROMs typically require that writes are done in 64-byte page boundaries, and after write there is a small delay (several milliseconds) before memory can be accessed again. This is really no issue and is easy to work around as long as you are aware of it. (FRAMs don't have these drawback either. Do I need to say I like FRAMs a lot, price aside?)

So, how you use one if these then? I borrowed below figure from datasheet of FM24C256 - 256kbit (32kByte) EEPROM from Fairchild. Mostly this because it just happened to be first datasheet I found from my library, but they're pretty much same so no matter.
This device is old - datasheet is from 2001 - so it seems a bit more limited; only 100k writes, but it also works with 3,3v and 5v voltages, which is nice. Some chips are less forgiving, so be careful when selecting part you wish to use. (let's just say that I wasn't very happy when I found out that somehow 5v EEPROM was placed on board that operated with 3,3v. It worked - mostly - due to low clock speed used, but exhibited some very mysterious failures on field...)

So what's needed to use these? SDA and SCL are data and clock lines, respectively. SDA is bi-directional line so optimally you'd want to use open drain output on MCU with pull-up resistor (4k7 is often recommended; internal pull-ups in MCU are often in 20k range which can work too, just don't try to use maximum clock speed then). If your MCU doesn't have open drain capability you'll need to fiddle between output and input mode; it's doable, but it's very easy to cause some glitches during switch that will interfere with communication.

VCC and VSS, supply and ground, of course are needed. WP, write protect, will prevent writes if pulled high. Most of the time it's easier to just connect it to GND (so no protection) unless you have good reason to do otherwise.

Address (Ax) modify the device address used to communicate with it, allowing you to connect multiple chips in same data lines in parallel. All chips must of course have different bus addresses, set by wiring these pins either high or low. For single-chip case it's easiest to wire them all to GND.

I2C bus is strictly master-slave bus, which means that the master (usually MCU) must initiate and control all data transfers. Some types of devices have interrupt lines that are used to signal MCU that they need to communicate with it ("hey I got a new result for you" or whatever). Memories just sit there until needed so they don't have that.

And that's it on hardware front.

I won't go deeply in software here, as writing your own I2C library might be tricky .. Nay, scratch that, it will be tricky. How thicky exactly depends on MCU and its pin capabilities. Instead I'll just point towards Wire library of Arduino that has I2C capabilities built in. I haven't used it myself so I don't have an example of it but there seem to be plenty of example around already.

The above example doesn't really explain the reasons why it does what it does so I'll explain a bit;

All communications with I2C device start with Start Bit and end with Stop Bit. These are mostly hidden behind beginTransmission() and endTransmission() functions.

After starting transmission, first byte written is the device address. Devices have often their own 7-bit address, possibly modified with Ax pins (in software I use often shifted 8-bit form where lowest bit of address is always zero; in case of above FM24C256 address is in binary 1010000 (7bit) or 0xA0 (8bit; shifted one left). In 8-bit form the lowest bit indicates read (1) or write (0) operation. When starting new operation you always specify write operation first to write data address:

After device address with write comes the data address. Depending on chip it can be one or two (or three) bytes. Number of bytes depends on size of memory; if 256 bytes or less, there is one address byte; for 512..65536 sizes two bytes and so on.

If you are writing to the memory, the data address is immediately followed by data itself, terminated by stop bit after which write operation starts on chip (remember that you must respect 64-byte pages and wait a bit for this to finish!).

If you are reading from memory it's a bit different though; after data address comes immediately new start bit (and no, there should not be stop bit first!) and device address again, only with Read operation set (bit 0 set to 1, so 0xA1).

Here the example linked above looks very wrong to me; it seems to be sending stop bit, and only then new start bit. Seems Very Wrong to me, but I don't know enough of internals of wire library to definitely call it an error. Even if it does stop bit first it actually might work on many chips, but technically that is violation of I2C spec so some chips might actually not work properly (for example if chip goes to low-power more after stop bit, forgetting previously set address).

After device address with Read operation is sent you can start reading data out. Read operation is nicer than write in the sense that you do not need to care about delays or page boundaries; if you want to, you can read contents of entire chip at one go. Or send NAK and stop bit to end transfer when you have received enough data (this part looks a bit wrong to me too, specifically the NAK sending part, but stop bit will stop operation anyway so that's less serious).

So, just to open up the read operation a bit, it should look a bit like this (with lots of error checking added, of course):

startBit();
writeByte(deviceAddress | 0); // 0xA0 - write operation
writeByte(byteAddressHigh);
writeByte(byteAddressLow);
startBit();
writeByte(deviceAddress | 1); // 0xA1 - read operation
while (bytesToRead--)
  { data[ofs++] = readByte();
  }
stopBit();

This doesn't check ACKs when writing bytes, nor does it send NAK properly at the end of transfer,  but I'm justifying this by claiming that leaving those out makes this example easier to understand.






Ei kommentteja:

Lähetä kommentti