Quickstart¶
Store four bytes in an AT25040B and read them back: wire six signals, write a one-method port class, call four driver functions.
1. Wire it¶
| MCU | AT25040B pin | Notes |
|---|---|---|
| SPI SCLK | SCK (6) | Mode 0 or 3; start at 1 MHz |
| SPI MOSI | SI (5) | |
| SPI MISO | SO (2) | High impedance while CS is high |
| GPIO or hardware CS | CS (1) | Active low; one transaction per CS-low period |
| 3.3 V (1.8 V to 5.5 V) | VCC (8) | Decouple with 100 nF close to the pin |
| 3.3 V or GPIO | WP (3) | High to allow writes; low blocks every write |
| 3.3 V | HOLD (7) | Keep high unless you pause transfers |
| GND | GND (4) | Common ground with the MCU |
The Hardware page has the bus timing, the other packages and the protection options.
2. Add the driver¶
Add the repository as a submodule and link the hf_at25040 CMake target (or add inc/ to your include path and
build with C++17). Installation covers CMake and ESP-IDF.
3. Write the port¶
The port is the only platform code: one method that runs one SPI transaction. Replace the platform_* call with
your SDK's.
my_spi.hpp
#include "at25040_spi_interface.hpp"
class MySpi : public at25040::SpiInterface<MySpi> {
public:
// One transaction: CS low, `len` bytes full duplex (SPI mode 0 or 3, MSB first), CS high.
// rx[i] is the byte clocked in while tx[i] went out. Return false only on a bus error or timeout.
bool transfer(const uint8_t* tx, uint8_t* rx, std::size_t len, uint32_t timeout_ms) noexcept {
return platform_spi_transfer(tx, rx, len, timeout_ms);
}
// Optional: sleep at least `us` microseconds. With it, WaitReady() (and every write) sleeps 100
// us between status polls; without it, the driver polls back to back for a bounded count.
void delay_us(uint32_t us) noexcept {
platform_delay_us(us);
}
};
Porting explains the contract and shows how to check a port.
4. Store and read back¶
main.cpp
#include "at25040.hpp"
int store_and_read_back(MySpi& spi) {
at25040::Device<MySpi> eeprom(spi);
if (eeprom.Probe() != at25040::Status::Ok) { // 1. a chip is there and idle
return -1; // wiring, CS, power or SPI mode
}
const uint8_t serial[4] = {0x12, 0x34, 0x56, 0x78};
if (eeprom.WritePage(0x010, serial, sizeof(serial)) != at25040::Status::Ok) { // 2. one page,
return -2; // 3. and its write cycle: WritePage() returns when the chip is ready again
}
uint8_t back[4] = {}; // 4. read it back
if (eeprom.Read(0x010, back, sizeof(back)) != at25040::Status::Ok) {
return -4;
}
return back[0] == serial[0] ? 0 : -5;
}
What each step does:
Probe()reads the status register twice. It returnsOkonly when both reads agree, look like a real status byte and show no write in progress.WritePage()sends WREN, then WRITE with the address and up to eight bytes inside one page. The chip starts its internal write cycle when CS goes high.WritePage()then polls the status register until bit 0 (RDY) clears, at most 10 ms: during the write cycle the chip ignores everything except RDSR.Read()sends READ and the address and returns the bytes asked for, from any range inside the array.
This example is built by the host CI, so it compiles against the current headers.
If it does not work¶
| Symptom | First thing to check |
|---|---|
Probe() returns Absent, LastRdsr() is 0xFF |
MISO wiring, CS reaching pin 1, VCC; a write cycle still running |
Probe() returns Absent, LastRdsr() is 0x05 |
MISO is connected to MOSI, or the wrong CS is low |
Writes return Ok but the data does not change |
WP is low, the address is block-protected, or you read before the write cycle ended |
Data at 0x100 and up appears at 0x000 |
The part is an AT25010B or AT25020B, not an AT25040B |
Troubleshooting has the full list.