Skip to content

Commit d02d741

Browse files
committed
feat: simple wear-leveling for pulse count
By default, we can now count a minimum of about 16 million pulses without resetting. This number can be increased by changing a constant.
1 parent 6b13733 commit d02d741

File tree

2 files changed

+66
-17
lines changed

2 files changed

+66
-17
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ read the Pico's unique 64-bit identifier as a hex string, but of course this is
1919
read-only. The idea is that the Pico's serial number can always be used to
2020
uniquely identify any device, as no two Picos have the same serial.
2121

22-
The EEPROM specifies up to 1,000,000 writes per flash cell (per 4-byte word). In
23-
theory, this means we can count up to 1 million pulses reliably, as no wear
24-
leveling is implemented at the moment. This could be added in the future to
25-
trivially multiply the maximum count.
22+
The EEPROM specifies up to 1,000,000 writes per flash cell (per 4-byte word).
23+
Using a simple wear-leveling scheme using 16 words, the theoretical minimum
24+
number of pulses that can be counted without resetting is about 16 million. This
25+
is expected to be incredibly high for most applications, but by changing
26+
a constant, this number can be trivially increased for more demanding
27+
applications.
2628

2729
| Field | Access | Description |
2830
|---|---|---|

src/main.cpp

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ static_assert(MIN_PULSE_WIDTH_US >= 10ULL,
4848
static constexpr std::uint32_t kDeviceInfoAddr{0x0};
4949
static constexpr std::uint32_t kPulseCountAddr{0x800};
5050

51+
// Number of 4-byte words used in the EEPROM for the pulse count. This is used
52+
// for wear leveling. Each word is rated for a million write cycles, so +1 here
53+
// adds a million to our upper count limit, essentially.
54+
static constexpr std::size_t kPulseCountWords{16};
55+
5156
// Board ID (this is set only once and stored here).
5257
static char board_id[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1];
5358

@@ -59,6 +64,7 @@ static AT24CM02 eeprom{I2C_INST, true};
5964

6065
static volatile std::uint32_t pulsecount;
6166
static std::uint32_t last_pulsecount;
67+
static std::uint32_t next_pulsecount_idx;
6268

6369
[[noreturn]] static void Panic() noexcept {
6470
while (1) {
@@ -91,20 +97,67 @@ static void ValidateDeviceInfo() noexcept {
9197
}
9298
}
9399

100+
inline constexpr std::uint32_t GetPulseCountAddr(std::uint32_t idx) noexcept {
101+
return kPulseCountAddr + idx * sizeof(std::uint32_t);
102+
}
103+
94104
static void StorePulseCount(std::uint32_t pc) noexcept {
95-
if (!eeprom.Write(kPulseCountAddr, reinterpret_cast<const std::uint8_t*>(&pc),
105+
const auto addr = GetPulseCountAddr(next_pulsecount_idx);
106+
next_pulsecount_idx = (next_pulsecount_idx + 1) % kPulseCountWords;
107+
if (!eeprom.Write(addr, reinterpret_cast<const std::uint8_t*>(&pc),
96108
sizeof(pc))) {
97109
Panic();
98110
}
99111
}
100112

113+
static void ResetPulseCount() noexcept {
114+
const std::uint32_t dummy[kPulseCountWords]{};
115+
if (!eeprom.Write(kPulseCountAddr,
116+
reinterpret_cast<const std::uint8_t*>(dummy),
117+
sizeof(dummy))) {
118+
Panic();
119+
}
120+
pulsecount = 0;
121+
last_pulsecount = 0;
122+
next_pulsecount_idx = 0;
123+
}
124+
101125
static void LoadPulseCount() noexcept {
102-
std::uint32_t pc;
103-
if (!eeprom.Read(kPulseCountAddr, reinterpret_cast<std::uint8_t*>(&pc),
104-
sizeof(pc))) {
126+
std::uint32_t pcs[kPulseCountWords];
127+
if (!eeprom.Read(kPulseCountAddr, reinterpret_cast<std::uint8_t*>(pcs),
128+
sizeof(pcs))) {
105129
Panic();
106130
}
107-
pulsecount = pc;
131+
132+
// Validate the pulse counts in the array and if any are all FFs, reset them
133+
// to 0 (these are blank cells).
134+
bool modified{};
135+
for (std::uint32_t i{}; i < std::size(pcs); ++i) {
136+
if (pcs[i] == 0xFFFFFFFF) {
137+
pcs[i] = 0;
138+
modified = true;
139+
}
140+
}
141+
142+
if (modified) {
143+
if (!eeprom.Write(kPulseCountAddr,
144+
reinterpret_cast<const std::uint8_t*>(pcs),
145+
sizeof(pcs))) {
146+
Panic();
147+
}
148+
}
149+
150+
// Find the next index to write a pulse count to (i.e., the first one where
151+
// the value is less than the previous one). Also, store the highest value, as
152+
// that's the actual pulse count.
153+
for (std::uint32_t i{}; i < kPulseCountWords; ++i) {
154+
const auto next_idx = (i + 1) % kPulseCountWords;
155+
if (pcs[next_idx] <= pcs[i]) {
156+
next_pulsecount_idx = next_idx;
157+
pulsecount = pcs[i];
158+
break;
159+
}
160+
}
108161
}
109162

110163
[[nodiscard]] static bool WriteLockEnabled() noexcept {
@@ -168,9 +221,7 @@ static void HandleSerialMessage(std::string_view message) noexcept {
168221
StoreDeviceInfo();
169222
}
170223
} else if (header == "RESETCOUNT"sv) {
171-
StorePulseCount(0);
172-
pulsecount = 0;
173-
last_pulsecount = 0;
224+
ResetPulseCount();
174225
}
175226
}
176227
}
@@ -212,12 +263,8 @@ int main(void) {
212263
LoadDeviceInfo();
213264
ValidateDeviceInfo();
214265

266+
// This also checks for fresh EEPROM (FFs)
215267
LoadPulseCount();
216-
// check for blank EEPROM
217-
if (pulsecount == 0xFFFFFFFF) {
218-
pulsecount = 0;
219-
StorePulseCount(0);
220-
}
221268

222269
// Get the board ID (we only need to do this once)
223270
::pico_get_unique_board_id_string(board_id, sizeof(board_id));

0 commit comments

Comments
 (0)