Reading/Writing EEPROM in interrupt routine (Tiny85)

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

I've a Tiny85 set up as a I2C slave. I'd like it to act like an I2C EEPROM (in addition to the other stuff it is doing), and the easy way would be to write the EEPROM directly from the I2C ISR (say up to 16 bytes worth). Looking at the docs, all the really seem to say is to turn off interrupts, but otherwise aren't so helpful on the advisability of this.

 

TL;DR -- is it sane to write up to 16 bytes of EEPROM directly from an ISR

 

Is this crazy? There is no hard real time issues (this is seconds, not microseconds, app).

This topic has a solution.
Last Edited: Thu. Aug 2, 2018 - 10:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Writing a byte to the EEPROM needs a minimum of 1.8 ms and (more likely) 3.4 ms.
Interrupts of that duration might upset "... the other stuff it is doing".

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

Why doesn't the ISR simply set a flag to say "write EEPROM now" then the loop in main() services this flag in interrupts enabled mode?

 

(of course, because of the duration involved, I guess this opens the possibility of new requests while previous are still being managed so you'll need to consider how to handle that - buffering presumably).

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

mikech wrote:
Writing a byte to the EEPROM needs a minimum of 1.8 ms and (more likely) 3.4 ms.

 

Ugh, that is way too long, I'd start losing I2C frames. I didn't come up with that number early, only the number of cycles to write, but of course you have to wait to be ready and the part I was reading didn't give hints on how long *that* would take.

 

clawson wrote:
I guess this opens the possibility of new requests while previous are still being managed so you'll need to consider how to handle that - buffering presumably

 

Yah, exactly. I do as you suggested for writing parameters; Since I have a "RAM" version of each param, I just set a flag and write them in the main loop. But for EEPROM the buffering could get a lot deeper. If I want to provide, say, 128 bytes of EEPROM to the master, I think I'd need pretty much 128 bytes of RAM, basically shadow the whole thing, if the master decided to burst out the whole kit and caboodle. I'm presuming I could get real fancy with clock stretching and the such, but that starts to get ugly. I guess I'll review how much free RAM I got (I'm not using much, so I can probably do it). 

 

I wonder how those bigger I2C EEPROM chips handle the fact that I2C can mostly write a lot faster then cheap-assed EEPROM.

 

Thanks!

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

I2c eeproms have a ram buffer. The size of this varies with the size of the eeprom. Off the top of my head it can be between 8 and 64 bytes. Nevertheless, the i2c eeproms require you to wait whilst they write.

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

Kartman wrote:
I2c eeproms have a ram buffer. The size of this varies with the size of the eeprom. Off the top of my head it can be between 8 and 64 bytes. Nevertheless, the i2c eeproms require you to wait whilst they write.

 

Did some reading up on this; I'd like my device to operate mostly like common EEPROM's so that the same master lib can be re-used, also to make swapping in a 'real' EEPROM if necessary would be easy.

 

Most (ok, many?) seem to like using ACK polling, which looks like a reasonable design choice. The Arduino Wire library is somewhat over simplified so I think I'll take its core routines and put together something in my slave that can do ack polling, and streamline some of the buffering while I am at it.

 

Heh, the master only needs like 20 bytes of EEPROM or so (why I'm squeezing it into the 85 which is there for other things), damn, small EEPROMs are cheap!

 

Edit: To clarify, I'll still write the buffered bytes in the main loop (with interrupts on), but when the buffer is full I'll have the ISR stop ack'ing so the master can ack poll. 

Last Edited: Mon. Jul 30, 2018 - 02:12 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Mr.Paul wrote:
Heh, the master only needs like 20 bytes of EEPROM or so (why I'm squeezing it into the 85 which is there for other things), damn, small EEPROMs are cheap!
I must be missing something. If you only need 20 bytes of EEPROM why are you adding external I2C to the tiny85? The AVR itself already comes with 512 bytes of EEPROM built in??

 

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

He is emulating the external 24xxx chip.

 

The first design choice is which particular 24xxx chip.   He could emulate a 512byte device like 24C04 or 128 byte device like 24C01.

The 24C04 has a 16 byte page and the 24C01 has an 8 byte page.

 

The 24Cxxx devices always write a whole page at a time.   Your Tiny85 can only write one byte at a time.

 

It is very important to specify how your emulation is going to behave.    You do this at the design stage.

 

Note that your Tiny85 will be much slower because it can only write one byte at a time.    You must buffer a "page" in RAM.   And only use the NAK-while-busy during a "page-write".

 

David.

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

Yes, david has the idea. I was rambling and thus communicated poorly. The Tiny85 is emulating EEPROM for another processor (which has none). I'm still exploring the "right" way to make it work and present itself, but emulation seems like a good idea, protocol wise. I think I have a plan!

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

It is simple now that you have confirmed it is an emulation.
Just design it to follow the 24c04 datasheet.
.
It is easy to implement the NAK-busy. Most masters will test for NAK and not use a blind timeout.
.
In practice you will often find that you do not need to update every byte in a page. So you will probably match typical 24C04 throughput.
.
David.
.
+100 for emulating a documented chip. Most punters never know what to do.

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

Yah. So, the Attiny85 core Wire implementation basically uses AVR312 implementation. I tossed the Wire library, but took their low-level stuff (which is very close, or identical to AVR312). I added a new routine to set/clear a busy flag, then hacked up USI_Counter_Overflow_ISR(void) when it does USE_SLAVE_CHECK_ADDRESS; instead of sending SET_USI_TO_SEND_ACK() blindly I check my busy flag and send SET_USI_TO_SEND_NACK() instead if we are busy. A test app that allows you to put it into busy when you press a button (using our modified lib):

 

 

extern "C" {
#include "USI_TWI_Slave.h"
	};

void yay_data(int amount)
{
	while (USI_TWI_Data_In_Receive_Buffer())
	{
		byte val = USI_TWI_Receive_Byte();
		if (val == 0 || val == '0')
			digitalWrite(4, HIGH);
		else
			digitalWrite(4, LOW);
	}
	return;
}

void yay_write()
{
}

void initial_i2c()
{
	USI_TWI_Slave_Initialise(0x20);
	USI_TWI_On_Slave_Receive = yay_data;
	USI_TWI_On_Slave_Transmit = yay_write;
}

void setup()
{

	pinMode(3, OUTPUT);
	pinMode(4, OUTPUT);
	digitalWrite(3, HIGH);
	digitalWrite(4, HIGH);
	initial_i2c();
}
bool busy = false;

void loop()
{
	bool old_busy = busy;
	busy = digitalRead(1)==0 ? true : false;
	digitalWrite(3, busy ? LOW : HIGH);
	if (old_busy != busy)
	{
		if (busy)
			USI_TWI_Busy(1);
		else
			USI_TWI_Busy(0);
	}
	delay(50);
}

 

Yes, no switch debounce or anything :) (note the LED's are reversed, LOW lights them) Ran a master against that which sends 0/1/0/1 and backs off and ack-polls if a write fails, and it all works very nicely; press the switch, the master backs off and polls, let go the switch and it all works nicely. Setting busy when I have a pending EEPROM write will work out perfectly.

 

Thanks for the ideas and pointer, it got me going in the write (*) direction!

 

PK

(*) Sorry, I couldn't help it.

 

For posterity, if you happen to use circuit python, here is a test master and some quick and dirty proof of concept library files are attached.:

import busio
import board
import time

i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)

def ack_poll(i2c):
    i2c.writeto(32, b'')

val = 0
def toggle(i2c):
    global val
    i2c.writeto(32, bytes([val]))
    val = 1 if val == 0 else 0

print("Hi there")
while not i2c.try_lock():
    pass
while True:
    try:
        toggle(i2c)
    except OSError:
        print("Boom, will poll for a bit")
        while True:
            # TODO: Add timeout
            try:
                ack_poll(i2c)
                print("We're back, baby")
                break
            except OSError:
                # TODO: Shorter...
                time.sleep(0.1)
                pass

    time.sleep(0.5)
i2c.unlock()

 

None of this is production, of course, but might help someone get on the write foot.

Attachment(s): 

Last Edited: Thu. Aug 2, 2018 - 10:26 PM