Skip to main content

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

ChipsetConfiguration storageOTP storage
SC20100, SC20260128 KB (4096 × 32 B blocks)64 KB (2048 × 32 B blocks)
SC130488 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 possibleErase() 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.

warning

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

NamespaceDescription
GHIElectronics.TinyCLR.Devices.SecureStorageSecure one-time-programmable storage classes