AT25040 Driver
API reference
Loading...
Searching...
No Matches
at25040.hpp
Go to the documentation of this file.
1#pragma once
12#include "at25040_registers.hpp"
14#if __has_include("at25040_version.h")
15#include "at25040_version.h"
16#else
17#define HF_AT25040_VERSION_STRING "1.0.0"
18#endif
19
20#include <cstddef>
21#include <cstdint>
22
23namespace at25040 {
24
25enum class Status : uint8_t {
26 Ok = 0,
27 Timeout = 1,
28 Absent = 2,
29 Busy = 3,
30 Arg = 4,
31};
32
38enum class BlockProtect : uint8_t {
39 None = 0,
40 UpperQuarter = 1,
41 UpperHalf = 2,
42 All = 3,
43};
44
45template <typename Spi>
46class Device {
47public:
48 explicit Device(Spi& spi) noexcept : spi_(spi) {}
49
50 Device(const Device&) = delete;
51 Device& operator=(const Device&) = delete;
52
54 static constexpr uint16_t kReadChunkBytes = 16;
55
57 static constexpr uint32_t kWriteCycleTimeoutMs = 10;
58
60 static constexpr uint32_t kPollIntervalUs = 100;
61
67 static constexpr uint32_t kPollsPerMsWithoutDelay = 1112;
68
69 [[nodiscard]] uint8_t LastRdsr() const noexcept {
70 return last_rdsr_;
71 }
72
73 Status ReadStatus(uint8_t& sr) noexcept {
74 uint8_t tx[2] = {kCmdRdsr, 0x00};
75 uint8_t rx[2] = {0xFF, 0xFF};
76 if (!spi_.transfer(tx, rx, 2, kTimeoutMs)) {
77 last_rdsr_ = 0xFF;
78 return Status::Timeout;
79 }
80 sr = rx[1];
81 last_rdsr_ = sr;
82 return Status::Ok;
83 }
84
89 Status Probe() noexcept {
90 uint8_t a = 0xFF;
91 uint8_t b = 0xFF;
92 if (ReadStatus(a) != Status::Ok) {
93 return Status::Absent;
94 }
95 if (ReadStatus(b) != Status::Ok) {
96 return Status::Absent;
97 }
98 last_rdsr_ = a;
99 if (a != b || a == 0xFFu || a == kCmdRdsr) {
100 return Status::Absent;
101 }
102 if ((a & kSrWip) != 0u) {
103 return Status::Busy;
104 }
105 return Status::Ok;
106 }
107
121 Status Read(uint16_t addr, uint8_t* dst, uint16_t len) noexcept {
122 if (dst == nullptr || len == 0u || addr >= kCapacityBytes ||
123 len > static_cast<uint16_t>(kCapacityBytes - addr)) {
124 return Status::Arg;
125 }
126 const Status ready = CheckReady();
127 if (ready != Status::Ok) {
128 return ready;
129 }
130 while (len > 0u) {
131 const uint16_t n = len < kReadChunkBytes ? len : kReadChunkBytes;
132 uint8_t tx[2 + kReadChunkBytes] = {};
133 uint8_t rx[2 + kReadChunkBytes] = {};
134 tx[0] = Opcode(kCmdRead, addr);
135 tx[1] = static_cast<uint8_t>(addr & 0xFFu);
136 if (!spi_.transfer(tx, rx, static_cast<std::size_t>(2u + n), kTimeoutMs)) {
137 return Status::Timeout;
138 }
139 for (uint16_t i = 0; i < n; ++i) {
140 dst[i] = rx[2u + i];
141 }
142 addr = static_cast<uint16_t>(addr + n);
143 dst += n;
144 len = static_cast<uint16_t>(len - n);
145 }
146 return Status::Ok;
147 }
148
161 Status WaitReady(uint32_t timeout_ms = kWriteCycleTimeoutMs) noexcept {
162 constexpr bool kHasDelay = detail::HasDelayUs<Spi>::value;
163 const uint32_t polls = kHasDelay ? (timeout_ms * 1000u) / kPollIntervalUs + 1u
164 : timeout_ms * kPollsPerMsWithoutDelay + 1u;
165 for (uint32_t i = 0; i < polls; ++i) {
166 uint8_t sr = 0xFF;
167 if (ReadStatus(sr) != Status::Ok) {
168 return Status::Timeout;
169 }
170 if ((sr & kSrWip) == 0u) {
171 return Status::Ok;
172 }
173 if constexpr (kHasDelay) {
174 if (i + 1u < polls) {
175 spi_.delay_us(kPollIntervalUs);
176 }
177 }
178 }
179 return Status::Timeout;
180 }
181
188 Status WriteEnable() noexcept {
189 const Status ready = CheckReady();
190 return ready != Status::Ok ? ready : SendInstruction(kCmdWren);
191 }
192
198 Status WriteDisable() noexcept {
199 const Status ready = CheckReady();
200 return ready != Status::Ok ? ready : SendInstruction(kCmdWrdi);
201 }
202
215 const auto bits = static_cast<uint8_t>(level);
216 if (bits > 3u) {
217 return Status::Arg;
218 }
219 const Status ready = CheckReady();
220 if (ready != Status::Ok) {
221 return ready;
222 }
223 if (SendInstruction(kCmdWren) != Status::Ok) {
224 return Status::Timeout;
225 }
226 const uint8_t tx[2] = {kCmdWrsr, static_cast<uint8_t>(bits << 2)};
227 uint8_t rx[2] = {0xFF, 0xFF};
228 if (!spi_.transfer(tx, rx, 2, kTimeoutMs)) {
229 return Status::Timeout;
230 }
231 return WaitReady();
232 }
233
240 uint8_t sr = 0xFF;
241 if (ReadStatus(sr) != Status::Ok) {
242 return Status::Timeout;
243 }
244 if ((sr & kSrWip) != 0u) {
245 return Status::Busy;
246 }
247 level = static_cast<BlockProtect>((sr & (kSrBp1 | kSrBp0)) >> 2);
248 return Status::Ok;
249 }
250
264 Status WritePage(uint16_t addr, const uint8_t* src, uint16_t len) noexcept {
265 if (src == nullptr || len == 0u || len > kPageSizeBytes) {
266 return Status::Arg;
267 }
268 const uint16_t page_off = static_cast<uint16_t>(addr % kPageSizeBytes);
269 if ((page_off + len) > kPageSizeBytes) {
270 return Status::Arg;
271 }
272 if ((static_cast<uint32_t>(addr) + len) > kCapacityBytes) {
273 return Status::Arg;
274 }
275 const Status ready = CheckReady();
276 if (ready != Status::Ok) {
277 return ready;
278 }
279 if (SendInstruction(kCmdWren) != Status::Ok) {
280 return Status::Timeout;
281 }
282 uint8_t tx[2 + kPageSizeBytes] = {};
283 uint8_t rx[2 + kPageSizeBytes] = {};
284 tx[0] = Opcode(kCmdWrite, addr);
285 tx[1] = static_cast<uint8_t>(addr & 0xFFu);
286 for (uint16_t i = 0; i < len; ++i) {
287 tx[2u + i] = src[i];
288 }
289 if (!spi_.transfer(tx, rx, static_cast<std::size_t>(2u + len), kTimeoutMs)) {
290 return Status::Timeout;
291 }
292 return WaitReady();
293 }
294
303 Status Write(uint16_t addr, const uint8_t* src, uint16_t len) noexcept {
304 if (src == nullptr || len == 0u || addr >= kCapacityBytes ||
305 len > static_cast<uint16_t>(kCapacityBytes - addr)) {
306 return Status::Arg;
307 }
308 while (len > 0u) {
309 const uint16_t room = static_cast<uint16_t>(kPageSizeBytes - (addr % kPageSizeBytes));
310 const uint16_t n = len < room ? len : room;
311 const Status st = WritePage(addr, src, n);
312 if (st != Status::Ok) {
313 return st;
314 }
315 addr = static_cast<uint16_t>(addr + n);
316 src += n;
317 len = static_cast<uint16_t>(len - n);
318 }
319 return Status::Ok;
320 }
321
322private:
323 static constexpr uint32_t kTimeoutMs = 20;
324
326 static constexpr uint8_t Opcode(uint8_t cmd, uint16_t addr) noexcept {
327 return ((addr & 0x100u) != 0u) ? static_cast<uint8_t>(cmd | kAddrA8Mask) : cmd;
328 }
329
334 Status CheckReady() noexcept {
335 uint8_t sr = 0xFF;
336 if (ReadStatus(sr) != Status::Ok) {
337 return Status::Timeout;
338 }
339 return (sr & kSrWip) != 0u ? Status::Busy : Status::Ok;
340 }
341
343 Status SendInstruction(uint8_t cmd) noexcept {
344 uint8_t rx = 0xFF;
345 return spi_.transfer(&cmd, &rx, 1, kTimeoutMs) ? Status::Ok : Status::Timeout;
346 }
347
348 Spi& spi_;
349 uint8_t last_rdsr_{0xFF};
350};
351
352inline const char* GetAt25040DriverVersion() noexcept {
354}
355
356} // namespace at25040
#define HF_AT25040_VERSION_STRING
Definition at25040.hpp:17
AT25010/020/040B SPI EEPROM opcodes and status bits (Microchip).
CRTP SPI contract for the AT25040 driver.
Definition at25040.hpp:46
Device(Spi &spi) noexcept
Definition at25040.hpp:48
static constexpr uint32_t kPollIntervalUs
Definition at25040.hpp:60
Status WritePage(uint16_t addr, const uint8_t *src, uint16_t len) noexcept
WREN + WRITE of 1 to 8 bytes inside one 8-byte page, then wait for the write cycle.
Definition at25040.hpp:264
static constexpr uint32_t kPollsPerMsWithoutDelay
Definition at25040.hpp:67
Status WaitReady(uint32_t timeout_ms=kWriteCycleTimeoutMs) noexcept
Poll RDSR until RDY (bit 0) reads 0: the internal write cycle has ended.
Definition at25040.hpp:161
static constexpr uint32_t kWriteCycleTimeoutMs
Definition at25040.hpp:57
Status WriteEnable() noexcept
WREN: set the write-enable latch (datasheet Table 6-1), before an instruction you send through your p...
Definition at25040.hpp:188
Status ReadStatus(uint8_t &sr) noexcept
Definition at25040.hpp:73
static constexpr uint16_t kReadChunkBytes
Definition at25040.hpp:54
Status WriteDisable() noexcept
WRDI: clear the write-enable latch (datasheet Table 6-1), so no WRITE or WRSR is accepted until the n...
Definition at25040.hpp:198
Device(const Device &)=delete
Status Write(uint16_t addr, const uint8_t *src, uint16_t len) noexcept
Write any range inside the array: split at 8-byte page boundaries, one WritePage() (and one write cyc...
Definition at25040.hpp:303
Device & operator=(const Device &)=delete
Status GetBlockProtection(BlockProtect &level) noexcept
RDSR, decoded: the current block protection level (datasheet Table 6-4).
Definition at25040.hpp:239
Status Probe() noexcept
Double RDSR. Present if both match, not 0xFF, not the 0x05 echo, and WIP is clear.
Definition at25040.hpp:89
Status SetBlockProtection(BlockProtect level) noexcept
WREN + WRSR: set BP1:BP0 (datasheet section 6, Write Status Register, Table 6-4), then wait for the w...
Definition at25040.hpp:214
uint8_t LastRdsr() const noexcept
Definition at25040.hpp:69
Status Read(uint16_t addr, uint8_t *dst, uint16_t len) noexcept
READ len bytes starting at addr (sequential read, any length).
Definition at25040.hpp:121
Definition at25040.hpp:23
constexpr uint8_t kSrBp1
Definition at25040_registers.hpp:23
constexpr uint8_t kSrBp0
Definition at25040_registers.hpp:22
constexpr uint8_t kCmdWren
Definition at25040_registers.hpp:10
Status
Definition at25040.hpp:25
const char * GetAt25040DriverVersion() noexcept
Definition at25040.hpp:352
constexpr uint8_t kAddrA8Mask
Definition at25040_registers.hpp:18
constexpr uint16_t kCapacityBytes
Definition at25040_registers.hpp:26
constexpr uint8_t kSrWip
Definition at25040_registers.hpp:20
constexpr uint8_t kCmdWrdi
Definition at25040_registers.hpp:11
BlockProtect
Block write protection level, BP1:BP0 in the status register (datasheet Table 6-4).
Definition at25040.hpp:38
@ UpperQuarter
01: 0x180-0x1FF read-only
@ None
BP1:BP0 = 00: nothing protected.
@ All
11: 0x000-0x1FF read-only
@ UpperHalf
10: 0x100-0x1FF read-only
constexpr uint8_t kCmdRead
Definition at25040_registers.hpp:14
constexpr uint8_t kCmdWrite
Definition at25040_registers.hpp:15
constexpr uint16_t kPageSizeBytes
Definition at25040_registers.hpp:27
constexpr uint8_t kCmdRdsr
Definition at25040_registers.hpp:12
constexpr uint8_t kCmdWrsr
Definition at25040_registers.hpp:13
Definition at25040_spi_interface.hpp:40