Secure Storage
SITCore exposes two secure storage regions inside the chip's internal flash — separate from the deployment region and not accessible externally. Both are addressed in 32-byte blocks through the same SecureStorageController API.
- Configuration storage — mutable. Read, write, erase. Wiped on
Erase All. Use for runtime configuration that needs to persist across reboots but can change over time (network settings, calibration data, device serial numbers). - OTP storage — write-once. Survives
Erase All. Use for immutable per-device data set at manufacturing (factory keys, customer IDs, hardware revision).
NuGet package: GHIElectronics.TinyCLR.Devices.SecureStorage.
Sizes
| Chipset | Configuration storage | OTP storage |
|---|---|---|
| SC20100, SC20260 | 128 KB (4096 × 32 B blocks) | 64 KB (2048 × 32 B blocks) |
| SC13048 | 8 KB (256 × 32 B blocks) | 2 KB (64 × 32 B blocks) |
Configuration storage
Configuration storage can be read, written, and erased. Because the entire region is one flash sector, partial erases aren't possible — Erase() wipes the whole region.
The example below queries the size, verifies the region is blank (erasing if not), writes a test pattern, and reads it back to verify:
using GHIElectronics.TinyCLR.Devices.SecureStorage;
using System;
using System.Diagnostics;
using System.Threading;
var configStorage = new SecureStorageController(SecureStorage.Configuration);
Debug.WriteLine("Total size: " + configStorage.TotalSize + " bytes");
Debug.WriteLine("Block size: " + configStorage.BlockSize + " bytes");
var totalBlocks = configStorage.TotalSize / configStorage.BlockSize;
// Check if every block is blank.
var isBlank = true;
for (uint block = 0; block < totalBlocks; block++) {
if (!configStorage.IsBlank(block)) {
isBlank = false;
break;
}
}
if (!isBlank) {
Debug.WriteLine("Erasing...");
configStorage.Erase();
}
// Fill the first 32 KB of the region with a known pattern.
const uint testBytes = 32 * 1024;
var data = new byte[testBytes];
for (var i = 0; i < data.Length; i++)
data[i] = (byte)(i % 255);
// Write in 32-byte chunks.
var chunk = new byte[configStorage.BlockSize];
for (uint block = 0; block < data.Length / configStorage.BlockSize; block++) {
Array.Copy(data, (int)(block * configStorage.BlockSize), chunk, 0, (int)configStorage.BlockSize);
configStorage.Write(block, chunk);
}
// Read back and verify.
for (uint block = 0; block < data.Length / configStorage.BlockSize; block++) {
configStorage.Read(block, chunk);
for (var i = 0; i < configStorage.BlockSize; i++) {
if (data[block * configStorage.BlockSize + i] != chunk[i]) {
Debug.WriteLine("Mismatch — data is corrupted");
Thread.Sleep(Timeout.Infinite);
}
}
}
Debug.WriteLine("Verified");
One-time programmable (OTP) storage
OTP blocks cannot be erased or rewritten once written. There's no way to undo a write — not from your application, not from TinyCLR Config's Erase All, not from the bootloader. This is what makes OTP useful for per-device identity values you want to fix at manufacturing.
The API is identical to configuration storage except for the constructor argument and the absence of Erase():
using GHIElectronics.TinyCLR.Devices.SecureStorage;
var otpStorage = new SecureStorageController(SecureStorage.Otp);
// Read and Write work the same as configuration storage.
otpStorage.Write(block: 0, data: factoryKey);
Per-chip differences
SC20100 / SC20260 — a block containing all 0xFF is treated as unused and can be written again. Practically, this means writing all-0xFF to a block doesn't burn it, so accidental writes of empty buffers don't lose a block.
SC13048 — every Write() call burns the block, even if the data being written is all 0xFF. There's no safety net here; check your data before writing.
OTP writes are permanent. Verify your code, double-check your inputs, and consider writing to a non-OTP block first as a dry run before committing the value to OTP.
API reference
| Namespace | Description |
|---|---|
| GHIElectronics.TinyCLR.Devices.SecureStorage | Secure one-time-programmable storage classes |