cat25040
A no_std, async Rust driver for the ON Semiconductor CAT25040 (4-Kbit SPI EEPROM), built on embedded-hal-async traits.
Works with any platform that implements the driver's Spi and HardwareInterface traits — no framework lock-in.
Features
- Byte read/write with automatic write-only-if-changed optimization
- Page write (16 bytes per page, single write cycle)
- Sequential read of arbitrary length
- 9-bit addressing — handles the A8 bit encoding in opcodes automatically
- Busy polling after writes with configurable delay
- Software block protection — protect memory regions (quarter, half, or full array)
- Power-up initialization — proper timing delays per datasheet (tPUW = 1ms)
- Type-safe register access — uses
device-drivercrate for compile-time safe register operations - YAML-defined device model — register and command definitions in declarative YAML
- Optional
defmtlogging (enable thedefmtfeature)
Usage
use ;
// Provide your SPI and delay implementations via custom traits
let mut eeprom = new;
// Initialize after power-on (waits required tPUW = 1ms)
eeprom.init.await.unwrap;
// Read 16 bytes starting at address 0x00
let mut buf = ;
eeprom.read.await.unwrap;
// Write a single byte (skips if value already matches)
eeprom.write_byte.await.unwrap;
// Write a full 16-byte page (address must be page-aligned)
let page = *b"EEPROM data here";
eeprom.write_page.await.unwrap;
// Type-safe status register access
let status = eeprom.read_status.await.unwrap;
if status.busy
if status.wel
// Protect upper half of memory (addresses 0x100-0x1FF)
eeprom.set_block_protection.await.unwrap;
// Check current protection level
let level = eeprom.get_block_protection.await.unwrap;
// Clear all protection
eeprom.clear_block_protection.await.unwrap;
// Send write enable/disable commands
eeprom.write_enable.await.unwrap;
eeprom.write_disable.await.unwrap;
API
| Method | Description |
|---|---|
new(spi, hardware_interface) |
Create a new driver instance |
init() |
Initialize device after power-on (waits tPUW = 1ms) |
read(address, buf) |
Read buf.len() bytes starting at address |
write_byte(data, address) |
Write a single byte (skips if unchanged) |
write_page(data, address) |
Write exactly 16 bytes to a page-aligned address |
read_status() |
Read the EEPROM status register (returns type-safe Status) |
is_busy() |
Check if a write cycle is in progress (WIP bit) |
write_enable() |
Send the WREN command (uses device-driver) |
write_disable() |
Send the WRDI command (uses device-driver) |
get_block_protection() |
Get current block protection level |
set_block_protection(level) |
Set block protection (None, UpperQuarter, UpperHalf, Full) |
clear_block_protection() |
Clear all block protection (convenience method) |
device() |
Access underlying device-driver device for advanced usage |
Architecture
Device-Driver Integration
This driver uses the device-driver crate for type-safe register and command operations. Key components:
YAML Device Model (src/cat25040.yaml):
- Defines registers (STATUS_REG) with named fields (BUSY, WEL, BP0, BP1)
- Defines commands (WREN, WRDI, WRSR) with opcodes
- Auto-generates type-safe Rust code at build time
SpiRegisterInterface (src/register_interface.rs):
- Bridges the driver's
Spitrait with device-driver'sRegisterInterfaceandCommandInterfacetraits - Handles opcode-based register access specific to CAT25040
- Enables compile-time verification of register operations
Generated Device API:
Cat25040Deviceprovides the low-level device APIStatus(alias forStatusReg) provides type-safe field access- Commands like
wren()andwrdi()are generated from YAML
Custom Traits
The driver defines its own traits to avoid framework lock-in:
Spi trait - Wraps embedded_hal_async::spi::SpiDevice:
HardwareInterface trait - Provides delay functionality:
These traits can be easily implemented for any platform. See the spi_device module for an example implementation using embedded-hal-async directly.
Block Protection
The CAT25040 supports software-controlled write protection for different memory regions using the BP0 and BP1 bits in the status register:
| Level | Protected Addresses | Protected Size | Description |
|---|---|---|---|
None |
None | 0 bytes | No protection (BP1=0, BP0=0) |
UpperQuarter |
0x180-0x1FF | 128 bytes | Upper 1/4 protected (BP1=0, BP0=1) |
UpperHalf |
0x100-0x1FF | 256 bytes | Upper 1/2 protected (BP1=1, BP0=0) |
Full |
0x000-0x1FF | 512 bytes | Entire array protected (BP1=1, BP0=1) |
Hardware Override: The WP (Write Protect) pin can override software protection. When WP is low, all writes are inhibited regardless of BP0/BP1 settings. When not using the WP pin, tie it to VCC.
Use Cases:
- Protect calibration data, configuration, or factory settings from accidental overwrites
- Reserve upper memory for read-only parameters while allowing writes to lower addresses
- Implement multi-level security by combining hardware (WP pin) and software protection
Cargo Features
std(default) — enables standard library support for testingdefmt— enables debug logging viadefmt
Hardware Pins
The CAT25040 has several hardware control pins that affect driver behavior:
- CS (Chip Select): Managed by your SPI implementation. CS must go high to start internal write cycles.
- WP (Write Protect): Hardware write protection. When low, all writes are inhibited regardless of software BP0/BP1 settings. Tie to VCC when not used.
- HOLD: Pauses SPI communication when taken low (while SCK is low). Allows the master to service higher-priority interrupts. Tie to VCC when not used.
- SCK, SI, SO: Standard SPI signals (clock, data in, data out).
Important: This driver does not directly control WP or HOLD pins. These are hardware features that should be managed by your platform's GPIO implementation if needed.
Power-On Timing
After power-on, the device requires up to 1ms before it's ready for operations (tPUW per datasheet). Always call init() after creating the driver instance:
let mut eeprom = new;
eeprom.init.await.unwrap; // Waits 1ms and verifies device is ready
Implementing for Your Platform
To use this driver, implement the Spi and HardwareInterface traits for your platform:
use ;
use SpiDevice;
use DelayNs;
// Wrapper for SpiDevice
// Wrapper for DelayNs
// Use with your platform (e.g., embassy)
let spi = MySpi ;
let hw = MyHardwareInterface ;
let mut eeprom = new;
The spi_device module in this crate provides a ready-to-use implementation for platforms using embedded-hal-async.
Device Compatibility
Designed for the CAT25040 but should work with other CAT250xx family EEPROMs that use the same SPI command set and 9-bit addressing (e.g., CAT25020). The driver handles the A8 address bit encoding automatically by setting bit 3 of the opcode when addressing the upper 256 bytes.
Project Structure
src/
├── lib.rs # Main driver implementation
├── cat25040.yaml # Device model (registers, commands)
├── register_interface.rs # SpiRegisterInterface implementation
└── spi_device.rs # Example SPI trait implementations
build.rs # Code generation from YAML
The build script uses device-driver-generation to generate Rust code from the YAML manifest. Generated code is placed in target/<profile>/build/cat25040-*/out/cat25040_device.rs.
Testing
The test suite includes 26 tests covering:
- 9-bit address encoding (A8 bit in opcode)
- Read/write operations at various addresses
- Page writes with alignment validation
- Status register access with type-safe fields
- Block protection configuration
- Power-up initialization
Tests use mock Spi and HardwareInterface implementations — no hardware required.
License
See the repository root for license information.