Saving a configuration value may require only a few bytes, but the underlying operation might need to erase several KiB. During programming, instruction fetches may be blocked, and a power loss could leave a half-written record. Choosing storage media should not rely solely on comparing capacity and read/write speeds; first ask: what are the read, program, and erase granularities? When is a write considered persistently successful? And who manages the worst-case write latency and endurance limits?
Data Use Case
Primary Consideration
Conditions Not to Omit
Runtime Buffer
SRAM, External RAM
DMA accessibility, timing, volatility
Code & Resources
Internal or External Flash
XIP support, access during erase/program, boot path
Small Configurations
EEPROM, Log-style Flash, Key-Value Store
Checksums, versioning, update atomicity
High-Frequency Small Records
F-RAM or suitable logging scheme
Bus throughput, limited endurance, multi-byte consistency
Large Records / Export
SD, eMMC, etc. (Managed Storage)
Tail latency, controller behavior, file system sync
View Media, Bus, Controller, and File System in Layers
SRAM/DRAM describe storage cell types, SPI/QSPI describe interfaces, SD/eMMC are storage form factors that include protocols and management functions, and FAT/littlefs organize data. Names from different layers cannot be directly placed in the same "who is faster" ranking; read latency, sustained bandwidth, programming time, and erase time are not the same speed metrics.
Media or Form Factor
Typical Organization
What the Host Needs to Care About
SRAM / DRAM
Volatile random access; DRAM requires refresh
Power supply, controller, timing, and bus access
NOR Flash
Random read, programmed in specified units, erased in blocks
1→0 programming, erase/rewrite limits, XIP support
Raw NAND
Page read/write, block erase
ECC, bad blocks, read disturbance, wear management
Cache/sync, tail latency, and power-loss recovery capability
XIP also requires the processor's memory mapping interface, boot configuration, and possibly cache support; it is not as simple as connecting any SPI Flash and executing code directly. Some platforms also copy code to RAM first. Whether internal Flash can read another region while erasing/programming depends on banks, buses, and chip limitations.
Why NOR Programming and Erase Are Separate
Typical NOR Flash is all 1s after erasure; programming changes selected bits to 0; restoring 0s to 1s requires erasing the containing block. Therefore, appending new data to a blank record area can be done by programming without immediate erasure; changing arbitrary bytes in place may trigger an erase and data migration. Specific devices have minimum programming units, page boundaries, alignment requirements, internal ECC, and repeated programming limits; allowed operations cannot be inferred solely from bitwise logic.
Assuming a device programming page size of 256 B, writing 20 bytes starting at offset 250 within a page should be split according to driver requirements into 6 bytes for the current page and 14 bytes for the next page. Writing beyond the boundary may cause page-internal wrapping on some devices; do not assume automatic continuation. Typical command flows also include write enable, issuing program/erase commands, polling for busy status, and error handling; the end of an SPI transmission does not mean the internal operation is complete.
AT25SF041 is a 4 Mbit SPI Flash. Distinguish between "bit" and "Byte" in device names. Renesas AT25SF041 Datasheet
EEPROM and F-RAM: Fast Write Does Not Mean Automatic Transaction Atomicity
Taking the 24LC256 as an example, it is a 256 Kbit, i.e., 32 KiB EEPROM, with a 64 B page write buffer. Page writes must respect boundaries; ready detection and ACK semantics during the internal write cycle are implemented according to the datasheet; bus ACK cannot be treated as data being permanently written. Microchip 24LC256
F-RAM does not require NOR-style block erasure and is suitable for certain frequent small-write tasks, but endurance is still a limited and conditional specification. Fast internal chip writes do not mean I²C/SPI serial transfers complete instantly; a business record composed of multiple bytes may still only be partially updated if power is lost mid-transfer. FM25V02A is 256 Kbit, 32 KiB, not 2 Mbit. Infineon FM25V02A
Endurance Calculated Based on Actual Erase/Write Amplification
If the same erase block is erased every 10 seconds, a rated 100,000 erase cycles correspond to a count budget of approximately 11.57 days. The key condition is "erasing the same block every time a save occurs," not "one business write every 10 seconds." If multiple records are appended to a blank area before erasing, the erase frequency can be significantly reduced.
Assuming 4096 B per block and 64 B per complete record, one block ideally holds 64 records; with 16 blocks rotating evenly, each block experiences one erase only after every 1024 records are written. At 100,000 cycles, this yields an ideal throughput budget of approximately 102.4 million records, but metadata, garbage collection, reserved space, bad blocks, and specific programming limits will reduce this. This is not a guarantee of product service life; retention time, temperature, and other device endurance limits may be reached first.
Dynamic wear leveling disperses wear across participating blocks, and static wear leveling may migrate long-inactive data to utilize more blocks. Algorithms also generate write amplification; measure actual physical operation counts rather than assuming all addresses endure the same wear just because the file system claims "wear leveling."
Dual-Bank Record Selection After Power Loss
The two areas should be independently erasable. When preparing a new record, retain the old one; first write the format version, sequence number, length, body, and checksum. After confirming the order and completion conditions, write the commit marker according to the media's allowed independent programming granularity. On startup, reject uncommitted, out-of-bounds length, malformed, or checksum-failed records; select the newer sequence number among valid records; reclaim the old area only after the new record is reliably committed.
Power Loss Point
Recovery View
Action to Take
During new block erase or body write
Old area valid, new area incomplete
Retain old version
Checksum written but not committed
New area is not yet a committed record
Retain old version
Commit complete, old area not erased
Both areas valid
Select new sequence number
During old area reclamation
New area valid
Use new version
The commit write itself may also be interrupted; its valid encoding and checksum rules must be defined; CRC can detect many common errors, but it is not an absolute guarantee against arbitrary corruption mathematically, nor does it provide authentication against malicious tampering. Sequence number rollover, format upgrades, and whether power loss might affect data outside the target area must also be handled. The simple dual-bank model cannot directly cover all failure modes within an SD controller.
Below enumerates each breakpoint of writing the new record byte-by-byte to verify that a half-written new record is never selected. The model assumes the old area is independent and undamaged, writes only form specified prefixes, and sequence numbers do not rollover; actual Flash minimum programming units and tearing write behavior still require separate verification.
importstructimportzlibdefencode(seq,payload):body=struct.pack('<IH',seq,len(payload))+payloadreturnbody+struct.pack('<I',zlib.crc32(body))+b'DONE'defdecode(blob):iflen(blob)<14:returnNoneseq, size=struct.unpack('<IH',blob[:6])end=6+sizeifend+8>len(blob)orblob[end+4:end+8]!=b'DONE':returnNoneifzlib.crc32(blob[:end])!=struct.unpack('<I',blob[end:end+4])[0]:returnNonereturnseq, blob[6:end]old=encode(7,b'old configuration')new=encode(8,b'new configuration')forcutinrange(len(new)+1):target=new[:cut]+bytes([255])*(len(new)-cut)valid=[xforxin(decode(old),decode(target))ifxisnotNone]recovered=max(valid,key=lambdax:x[0])assertrecovered[0]==(8ifcut==len(new)else7)assertdecode(b'bad')isNoneassertdecode(old)[1]==b'old configuration'print('All prefix power-loss points: recover old record before commit, select new record after commit')
Which version survives a cut at each byte?
Moving the cut from 11 to 12 writes only the commit byte but changes recovery. Corrupting payload rejects the new record even with a commit. Removing the old region exposes premature reclamation: an uncommitted new record cannot replace a deleted old one.
Preparing the visual
Which version survives a cut at each byte?
A completed checksum is not a commit. Keeping an independent old region enables recovery from uncommitted or corrupt records.
{"id":"flash-prefix-commit","title":"Which version survives a cut at each byte?","summary":"A completed checksum is not a commit. Keeping an independent old region enables recovery from uncommitted or corrupt records.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"prefix\" data-i18n=\"prefix\"></label><output for=\"prefix\" id=\"prefix-value\"></output><input id=\"prefix\" type=\"range\" min=\"0\" max=\"12\" value=\"7\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"old\" checked><span data-i18n=\"old\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"corrupt\"><span data-i18n=\"corrupt\"></span></label><div class=\"track-row\"><span data-i18n=\"bytes\"></span><div id=\"bytes\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"commit\"></span><strong id=\"commit\"></strong></div><div><span data-i18n=\"crc\"></span><strong id=\"crc\"></strong></div><div><span data-i18n=\"chosen\"></span><strong id=\"chosen\"></strong></div></div><p id=\"eq\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:550 20px ui-monospace,monospace}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px ui-monospace,monospace;background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px ui-monospace,monospace}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 ui-monospace,monospace;white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:ui-monospace,monospace}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px ui-monospace,monospace;padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 ui-monospace,monospace;overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.chart-wrap{margin:18px 0}.chart-label{font-size:12px;color:var(--muted);min-height:3.2em}.curve{display:block;width:100%;height:auto;max-height:320px;overflow:visible}.curve text{font:11px ui-monospace,monospace;fill:var(--muted)}.gridline{stroke:var(--rule);stroke-width:1}.metrics strong{font-size:20px;min-height:3.2em}.formula{font:12px/1.8 ui-monospace,monospace;min-height:9em}.readout{min-height:8em}.presets button{flex-basis:30%}.scope p{overflow-wrap:anywhere} .unit-note{font-size:12px;color:var(--muted);margin:10px 0}\n\n.metrics span{word-break:break-all;line-break:strict}.metrics>div{padding-right:6px}\n\n#bytes{display:grid;grid-template-columns:repeat(6,1fr)}#chosen{min-height:5em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function crc32(bytes){let c=0xffffffff;for(const b of bytes){c^=b;for(let i=0;i<8;i++)c=(c>>>1)^((c&1)?0xedb88320:0);}return(c^0xffffffff)>>>0}function draw(){let p=n('prefix'),head=[1,8,4,8,16,32,64],crc=crc32(head),record=[...head,...[0,8,16,24].map(s=>(crc>>>s)&255),165],mem=record.map((v,i)=>i<p?v:255);if($('#corrupt').checked&&p>3)mem[3]^=1;const stored=(mem[7]|mem[8]<<8|mem[9]<<16|mem[10]<<24)>>>0,ok=crc32(mem.slice(0,7))===stored,commit=mem[11]===165,valid=commit&&ok&&mem[0]===1&&mem[2]===4,chosen=valid?'new':$('#old').checked?'previous':'none';show('prefix-value',p);cells('bytes',mem.map((v,i)=>({text:v.toString(16).padStart(2,'0').toUpperCase(),cls:i>=p?'empty':i===11?'gold':'on'})));set('commit',+commit);set('crc',+ok);set('chosen',t(chosen));$('#chosen').dataset.state=chosen;set('eq','CRC32 = 0x'+crc.toString(16).padStart(8,'0')+'\\nvalid = format ∧ length ∧ CRC ∧ commit\\nsequence(new) = 8; sequence(old) = 7');}inputs(draw);","audio":false,"strings":{"bytes":"New region: 12 bytes (hex)","chosen":"Recovery choice","commit":"Valid commit marker","corrupt":"Corrupt one new data bit","crc":"CRC matches","heading":"Which version survives a cut at each byte?","intro":"Choose a power-cut prefix; check commit and CRC, then old-region availability.","new":"New version 8","none":"No valid record","old":"Keep valid old record","prefix":"Bytes completed before power cut","previous":"Old version 7","reset":"Reset","scope":"12-byte record: version, sequence, length, four data bytes, CRC32, commit A5. Prefix byte writes; independent valid old region, no sequence wrap or real torn-write extent.","scopeLabel":"Model scope and assumptions"}}
SD / eMMC: Behind Logical Blocks Lies the Controller
Managed storage encapsulates many NAND details; the host faces logical block read/write, but internally there are still caches, mapping, garbage collection, and wear management. Writes of the same size may result in different busy times; buffers for continuous acquisition must cover the longest acceptable pause and define timeout and drop-sample policies.
For example, a 160 kB/s data stream encountering a 200 ms write pause requires at least 32,000 B of additional buffer, not accounting for scheduling and other margins. Looking only at the card's peak MB/s does not guarantee zero sample drops. SD storage buses and SDIO extension functions should not be conflated into the same term; whether to use SPI or native bus depends on card and host controller support and initialization requirements, not relying on fixed "send a few more clocks" patches.
Block devices can store custom raw formats; using a file system is not mandatory. If file exchange with a PC is needed, choose an implementation supporting the target format; voltage, pull-ups, and decoupling should be designed according to the respective interface and hardware documentation; do not uniformly add pull-ups to all signal lines.
File System Consistency Does Not Equal Business Commit Completion
FAT provides broad file exchange capabilities, but multiple metadata updates and power-loss semantics require careful application handling. littlefs is designed for constrained devices and Flash power-loss recovery, using corresponding metadata and copy-on-write designs; it still requires correct underlying read, program, erase, and sync contracts. littlefs Design
For example, after writing a new configuration file, sync/close according to the API and use a suitable atomic replacement process; separate successful updates of two files do not automatically constitute a cross-file transaction. littlefs documentation explicitly requires cached storage drivers to implement sync during sync, otherwise data integrity cannot be guaranteed. Successful mounting does not mean the last unsynced record is definitely retained. littlefs Interface and Sync Notes
Power-hold capacitors can be part of the design, but first detect voltage drop, stop new transactions, and reserve energy for the worst-case pending operation. Under constant current simplification, C ≥ I·Δt/ΔV; for example, 50 mA, 10 ms, allowing a 0.3 V drop yields approximately 1667 μF; ESR, tolerance, conversion efficiency, and minimum write voltage must also be considered. Unknown long-tail write times cannot be guaranteed by arbitrarily adding a capacitor.
Recalculating Granularity and Budgets
frommathimportiscloseassert256*1024//8==32768page, offset, count=256, 250, 20first=min(count,page-offset%page)assert(first,count-first)==(6,14)assertisclose(100000*10/86400,11.574074074074074)records=(4096//64)*16*100000assertrecords==102400000assert160000*0.2==32000assertisclose(0.05*0.010/0.3*1e6,1666.6666666666667)print('Capacity units, cross-page splitting, erase budget, buffer, and hold capacitor calculations passed')
This article verifies the model and breakpoint recovery logic; it did not erase/write actual storage media, nor perform real power-loss tests.