Sixty-four-byte pages
One write can carry up to 64 bytes, and costs one 5 ms wait instead of 64. The catch is the page: the memory is cut into 64-byte pages, and a write that runs past the end of one wraps round to the start of the same page and overwrites it, with no error. So cut every write at page edges, and at 30 bytes or fewer on an Uno.
Pages
The chip's 16,384 bytes are cut into 256 pages of 64: bytes 0 to 63, 64 to 127, and so on. A write sends one start address and then up to 64 bytes, and the chip counts up from the start address as they arrive.
But it only counts within the page. Slide the start to 56 and write 16 bytes as one: the first eight fill 56 to 63, and the next eight do not go to 64. They go to 0, 1, 2, on top of whatever was there. The datasheet calls it roll-over. There is no error and no warning; you find out when you read it back.
Switch to Cut at page edges and the same 16 bytes go as two writes, 8 and 8, each inside its own page. Everything lands where it should.
Why bother with pages at all
Every write, one byte or 64, costs one wait of up to 5 ms. Filling the whole chip a byte at a time is 16,384 waits: about a minute and a half at 100 kHz. In 16-byte pieces it is 1,024 waits, under ten seconds. The figure's last readout works both out.
The Uno's 32 bytes
A second limit sits on the Arduino side. The Uno's Wire library holds 32 bytes per transmission, and the two address bytes take two of them, so a write can carry 30 bytes at most. Longer ones are cut off without an error. The sketch uses pieces of 16: that fits every board's buffer and divides 64, so pieces never straddle an edge once they start on one.
What you should see
wrote: Written across a page edge, read back whole.
read: Written across a page edge, read back whole.The text starts at byte 50 and is 45 bytes long with its terminating zero,
so it crosses the edge at 64. writeBlock() sends it as 14, 16 and 15 bytes,
and it comes back whole.
The code
Stores a line of text that crosses a page edge, then reads it back. writeBlock() cuts the write at every page edge and every 16 bytes, and waits for the chip after each piece.
/*
EEPROM Memory - text across a page edge TK31 / /p/tk31
Wiring. Count from the square pad on the TinkerBlock board, parts
up, header at the bottom:
GND -> GND
VCC -> your board's logic supply: 5V on an Uno, 3V3 on an
ESP32 or ESP32-S3, 3V3(OUT) on a Pico. Never 5V beside
a 3.3 V board: the pull-ups would put 5 V on its pins.
SDA -> A4 on an Uno, GPIO 21 on an ESP32, GPIO 8 on an
ESP32-S3, GP4 on a Raspberry Pi Pico
SCL -> A5 on an Uno, GPIO 22 on an ESP32, GPIO 9 on an
ESP32-S3, GP5 on a Raspberry Pi Pico
Arduino IDE
Tools > Board your board, e.g. Arduino Uno
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library to install: Wire comes with every board.
*/
#include <Wire.h>
const int EEPROM_ADDR = 0x50;
const uint16_t PAGE = 64; // the chip's page
const uint16_t PIECE = 16; // fits every board's Wire buffer
const uint16_t START = 50; // crosses the edge at 64 on purpose
const char TEXT[] = "Written across a page edge, read back whole.";
void waitReady() {
unsigned long t0 = millis();
do {
Wire.beginTransmission(EEPROM_ADDR);
} while (Wire.endTransmission() != 0 && millis() - t0 < 20);
}
void writeBlock(uint16_t at, const uint8_t *data, uint16_t n) {
while (n > 0) {
uint16_t room = PAGE - at % PAGE; // bytes left in this page
uint16_t len = min(n, min(room, PIECE));
Wire.beginTransmission(EEPROM_ADDR);
Wire.write((uint8_t)(at >> 8));
Wire.write((uint8_t)(at & 0xFF));
Wire.write(data, len);
Wire.endTransmission();
waitReady(); // one wait per piece
at += len;
data += len;
n -= len;
}
}
void readBlock(uint16_t at, uint8_t *out, uint16_t n) {
while (n > 0) {
uint16_t len = min(n, PIECE);
Wire.beginTransmission(EEPROM_ADDR);
Wire.write((uint8_t)(at >> 8));
Wire.write((uint8_t)(at & 0xFF));
Wire.endTransmission(false);
Wire.requestFrom(EEPROM_ADDR, (int)len);
for (uint16_t i = 0; i < len; i++) {
out[i] = Wire.available() ? Wire.read() : '?';
}
at += len;
out += len;
n -= len;
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Wire.begin();
writeBlock(START, (const uint8_t *)TEXT, sizeof(TEXT));
char back[sizeof(TEXT)];
readBlock(START, (uint8_t *)back, sizeof(back));
back[sizeof(back) - 1] = '\0';
Serial.print("wrote: ");
Serial.println(TEXT);
Serial.print("read: ");
Serial.println(back);
}
void loop() {
}The text starts at byte 50, so it crosses the page edge at 64 on purpose. On an Uno never raise PIECE above 30: its Wire buffer holds 32 bytes and the address takes two. Leave writeBlock() as it is and any address and length work.
The same text in MicroPython. write_block() cuts the write at page edges and every 16 bytes; the read is one readfrom_mem, which may run across pages.
"""
EEPROM Memory - text across a page edge, MicroPython TK31 / /p/tk31
Wiring. Count from the square pad on the TinkerBlock board, parts
up, header at the bottom:
GND -> GND
VCC -> 3V3 on an ESP32 or ESP32-S3, 3V3(OUT) on a Pico.
Never 5V: the pull-ups would put 5 V on your pins.
SDA -> GPIO 21 on an ESP32, GPIO 8 on an ESP32-S3,
GP4 on a Raspberry Pi Pico
SCL -> GPIO 22 on an ESP32, GPIO 9 on an ESP32-S3,
GP5 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Nothing to install: machine and time are built in.
"""
import time
from machine import I2C, Pin
# SDA, SCL. ESP32: 21, 22. ESP32-S3: 8, 9. Pico: 4, 5.
i2c = I2C(0, sda=Pin(21), scl=Pin(22), freq=100_000)
EEPROM = 0x50
PAGE = 64 # the chip's page
PIECE = 16
START = 50 # crosses the edge at 64 on purpose
TEXT = b"Written across a page edge, read back whole."
def wait_ready():
t0 = time.ticks_ms()
while time.ticks_diff(time.ticks_ms(), t0) < 20:
try:
i2c.writeto(EEPROM, b"\x00\x00")
return
except OSError: # still writing
pass
def write_block(at, data):
while data:
room = PAGE - at % PAGE # bytes left in this page
n = min(len(data), room, PIECE)
i2c.writeto_mem(EEPROM, at, data[:n], addrsize=16)
wait_ready() # one wait per piece
at += n
data = data[n:]
write_block(START, TEXT)
back = i2c.readfrom_mem(EEPROM, START, len(TEXT), addrsize=16)
print("wrote:", TEXT.decode())
print("read: ", back.decode())MicroPython has no 32-byte limit, so PIECE could be 64 here. The page edge is the limit that stays: a write that crosses one wraps inside its page, whatever board sends it.
When it does not work
The write crossed a page edge, at a multiple of 64, and its tail wrapped to the start of the page. Cut writes where the page ends, the way writeBlock() does, and write it again.
The Uno's Wire library holds 32 bytes per transmission, and two of them are the address. Anything past 30 bytes of data is dropped without an error. Keep each write at 30 bytes or fewer; this sketch uses 16.
No. A sequential read runs on across pages, and wraps only at the very end of the chip. This sketch still reads in 16-byte pieces, because the Uno's Wire library can only take 32 bytes per request.
Yes: the ESP32's Wire buffer is larger than 66 bytes, so a full page with its two address bytes fits. Start the write exactly on a multiple of 64 and it takes one 5 ms wait for 64 bytes.
How long a byte lasts, and the habit that makes it last decades.
A million writes →Edit this page — content/books/eeprom-memory/sixty-four-byte-pages.mdx
Questions about this product
See what other owners have asked, and read their solutions.
EEPROM Memory
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.