Safety, testing Exercise 21 README internal linking on Forgejo
This commit is contained in:
parent
e5d30469bc
commit
1d0a29f2a3
18 changed files with 2098 additions and 1 deletions
136
exercises/19_SD_Card_diag/README.md
Normal file
136
exercises/19_SD_Card_diag/README.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Exercise 19: SD Card Diagnostics
|
||||
|
||||
This exercise exists to develop and test a reusable SD card diagnostics library, `sdcard_diag`, under a simple sketch that prints the current card status to the serial console.
|
||||
|
||||
The immediate goal is operational: when a board in the field has an SD-related problem, the serial output from this exercise should help identify which card was installed and what the board could read from it at the time of the problem.
|
||||
|
||||
## What It Reports
|
||||
|
||||
The sketch prints:
|
||||
|
||||
- PMU and SD rail status
|
||||
- SD pin map and pin levels
|
||||
- SPI idle probe results for `HSPI` and `FSPI`
|
||||
- Mount result and working SPI bus/frequency
|
||||
- Card type and capacity
|
||||
- Filesystem total, used, and free space
|
||||
- Root directory summary and a few sample entries
|
||||
- CID-derived identity fields:
|
||||
- `mid`
|
||||
- `manufacturer_guess`
|
||||
- `oem`
|
||||
- `product`
|
||||
- `revision`
|
||||
- `serial`
|
||||
- `date`
|
||||
- `CID raw`
|
||||
|
||||
## Important Certainty Boundary
|
||||
|
||||
The following fields are read from the card and should be treated as authoritative:
|
||||
|
||||
- `mid`
|
||||
- `oem`
|
||||
- `product`
|
||||
- `revision`
|
||||
- `serial`
|
||||
- `date`
|
||||
- `CID raw`
|
||||
|
||||
The `manufacturer_guess` field is not authoritative. It is a best-effort interpretation of `mid` using community-observed mappings. This matters because:
|
||||
|
||||
- the public SD documentation describes the `MID` field, but does not provide a public authoritative `MID -> vendor name` table
|
||||
- counterfeit or reprogrammed cards may present misleading identity data
|
||||
- some real cards may use manufacturer IDs not covered by the current lookup table
|
||||
|
||||
Also note the difference between a retail brand and a CID-reported card identity:
|
||||
|
||||
- the label printed on the card or the Amazon listing may identify a reseller, storefront brand, or private-label marketer
|
||||
- the CID fields identify what the card itself reports electronically
|
||||
- those two may match, but they do not have to match
|
||||
- for troubleshooting, trust the printed `CID:` line and `CID raw:` more than marketplace branding
|
||||
|
||||
For issue work, always keep the full `CID:` line and the `CID raw:` line.
|
||||
|
||||
## Typical Output
|
||||
|
||||
```text
|
||||
CID: mid=0x03 manufacturer_guess=SanDisk oem=SD product=SD32G revision=8.5 serial=0x2CC7ADB4 date=2025-12
|
||||
CID raw: 03 53 44 53 44 33 32 47 85 2C C7 AD B4 01 9C 6B
|
||||
```
|
||||
|
||||
That output usually tells you much more than a retail package label. Even if `manufacturer_guess` is `Unknown`, the remaining fields are still valuable for correlation.
|
||||
|
||||
## Build
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
source /home/jlpoole/rnsenv/bin/activate
|
||||
pio run -d exercises/19_SD_Card_diag/ -e cy
|
||||
```
|
||||
|
||||
## Upload
|
||||
|
||||
```bash
|
||||
source /home/jlpoole/rnsenv/bin/activate
|
||||
pio run -d exercises/19_SD_Card_diag/ -e cy -t upload --upload-port /dev/ttytCY
|
||||
```
|
||||
|
||||
## Monitor
|
||||
|
||||
```bash
|
||||
date
|
||||
source /home/jlpoole/rnsenv/bin/activate
|
||||
pio device monitor -b 115200 --port /dev/ttytCY
|
||||
```
|
||||
|
||||
## If `manufacturer_guess` Is `Unknown`
|
||||
|
||||
Use the following process:
|
||||
|
||||
1. Capture the full `CID:` line and `CID raw:` line from the serial log.
|
||||
2. Use `mid`, `oem`, and `product` together when searching. `mid` alone is often not enough.
|
||||
3. Compare the card against the references below.
|
||||
4. If the card still cannot be identified, record the raw CID in the issue so the lookup table in `sdcard_diag` can be extended later.
|
||||
|
||||
Suggested search terms:
|
||||
|
||||
```text
|
||||
SD CID MID 0xNN OID XX product YYYYY
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```text
|
||||
microSD CID 03 53 44 53 44 33 32 47 ...
|
||||
```
|
||||
|
||||
## Reference URLs
|
||||
|
||||
These pages are useful when investigating an unknown or suspicious card.
|
||||
|
||||
Official SD/SD-3C references:
|
||||
|
||||
- SD Association Physical Layer Simplified Specification downloads page:
|
||||
`https://www.sdcard.org/downloads/pls/`
|
||||
- SD Association simplified specification PDF referenced during development:
|
||||
`https://www.sdcard.org/cms/wp-content/themes/sdcard-org/dl.php?f=Part1_Physical_Layer_Simplified_Specification_Ver6.00.pdf`
|
||||
- SD-3C home page:
|
||||
`https://www.sd-3c.com/Default.aspx`
|
||||
|
||||
Community and field-reference pages:
|
||||
|
||||
- IT-SD secure digital card registers:
|
||||
`https://www.it-sd.com/articles/secure-digital-card-registers/`
|
||||
- Camera Memory Speed CID notes:
|
||||
`https://www.cameramemoryspeed.com/sd-memory-card-faq/reading-sd-card-cid-serial-psn-internal-numbers/`
|
||||
- Zero Alpha SD technology explainer:
|
||||
`https://zeroalpha.com.au/services/data-recovery-blog/sd/secure-digital-sd-memory-card-technology-explained`
|
||||
|
||||
## Notes For Issue Assignees
|
||||
|
||||
- Treat `manufacturer_guess` as a clue, not proof.
|
||||
- Treat `CID raw` as evidence.
|
||||
- If the board mounts the card but CID parsing fails, retain the mount and filesystem details anyway.
|
||||
- If a new legitimate `MID` is identified, update the lookup table in `lib/sdcard_diag/SdCardDiag.cpp` and add provenance for the new mapping.
|
||||
531
exercises/19_SD_Card_diag/lib/sdcard_diag/SdCardDiag.cpp
Normal file
531
exercises/19_SD_Card_diag/lib/sdcard_diag/SdCardDiag.cpp
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
#include "SdCardDiag.h"
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "tbeam_supreme_adapter.h"
|
||||
|
||||
SdCardDiag::SdCardDiag(Print& out) : out_(out) {}
|
||||
|
||||
bool SdCardDiag::begin() {
|
||||
printDivider();
|
||||
logf("sdcard_diag begin");
|
||||
printPinMap();
|
||||
|
||||
pmuReady_ = initPmu();
|
||||
printRailStatus();
|
||||
return pmuReady_;
|
||||
}
|
||||
|
||||
void SdCardDiag::printReport() {
|
||||
printDivider();
|
||||
logf("starting SD card diagnostic cycle");
|
||||
|
||||
forceSpiDeselected();
|
||||
printRailStatus();
|
||||
printPinLevels();
|
||||
|
||||
cycleSdRail();
|
||||
delay(1200);
|
||||
printRailStatus();
|
||||
|
||||
idleProbe(hspi_, "HSPI");
|
||||
idleProbe(fspi_, "FSPI");
|
||||
|
||||
if (!mountCard()) {
|
||||
logf("result: no mountable SD card found");
|
||||
return;
|
||||
}
|
||||
|
||||
printCardDetails();
|
||||
}
|
||||
|
||||
void SdCardDiag::logf(const char* fmt, ...) {
|
||||
char msg[224];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||
va_end(args);
|
||||
out_.printf("[%10lu][%06lu] %s\r\n",
|
||||
(unsigned long)millis(),
|
||||
(unsigned long)logSeq_++,
|
||||
msg);
|
||||
}
|
||||
|
||||
void SdCardDiag::printDivider() {
|
||||
out_.println("------------------------------------------------------------");
|
||||
}
|
||||
|
||||
void SdCardDiag::forceSpiDeselected() {
|
||||
pinMode(tbeam_supreme::sdCs(), OUTPUT);
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
pinMode(tbeam_supreme::imuCs(), OUTPUT);
|
||||
digitalWrite(tbeam_supreme::imuCs(), HIGH);
|
||||
}
|
||||
|
||||
bool SdCardDiag::initPmu() {
|
||||
if (!tbeam_supreme::initPmuForPeripherals(pmu_, &out_)) {
|
||||
logf("PMU init failed; SD rail power control unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
logf("PMU init ok");
|
||||
return true;
|
||||
}
|
||||
|
||||
void SdCardDiag::cycleSdRail(uint32_t offMs, uint32_t onSettleMs) {
|
||||
if (!pmu_) {
|
||||
logf("rail cycle skipped: pmu unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
forceSpiDeselected();
|
||||
SD.end();
|
||||
hspi_.end();
|
||||
fspi_.end();
|
||||
|
||||
pmu_->disablePowerOutput(XPOWERS_BLDO1);
|
||||
delay(offMs);
|
||||
pmu_->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
|
||||
pmu_->enablePowerOutput(XPOWERS_BLDO1);
|
||||
delay(onSettleMs);
|
||||
|
||||
logf("cycled SD rail off=%lums settle=%lums",
|
||||
(unsigned long)offMs,
|
||||
(unsigned long)onSettleMs);
|
||||
}
|
||||
|
||||
void SdCardDiag::printRailStatus() {
|
||||
if (!pmu_) {
|
||||
logf("PMU: unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
const float vbusV = pmu_->getVbusVoltage() / 1000.0f;
|
||||
const float battV = pmu_->getBattVoltage() / 1000.0f;
|
||||
const bool battPresent = pmu_->isBatteryConnect();
|
||||
const bool sdRailOn = pmu_->isPowerChannelEnable(XPOWERS_BLDO1);
|
||||
|
||||
logf("PMU: SD_BLDO1=%s VBUS=%.3fV BATT=%.3fV battery=%s",
|
||||
boolToOnOff(sdRailOn),
|
||||
vbusV,
|
||||
battV,
|
||||
battPresent ? "present" : "absent");
|
||||
}
|
||||
|
||||
void SdCardDiag::printPinMap() {
|
||||
logf("Pins: SD_CS=%d SD_SCK=%d SD_MISO=%d SD_MOSI=%d IMU_CS=%d",
|
||||
tbeam_supreme::sdCs(),
|
||||
tbeam_supreme::sdSck(),
|
||||
tbeam_supreme::sdMiso(),
|
||||
tbeam_supreme::sdMosi(),
|
||||
tbeam_supreme::imuCs());
|
||||
}
|
||||
|
||||
void SdCardDiag::printPinLevels() {
|
||||
logf("Levels: CS=%d SCK=%d MISO=%d MOSI=%d",
|
||||
gpio_get_level((gpio_num_t)tbeam_supreme::sdCs()),
|
||||
gpio_get_level((gpio_num_t)tbeam_supreme::sdSck()),
|
||||
gpio_get_level((gpio_num_t)tbeam_supreme::sdMiso()),
|
||||
gpio_get_level((gpio_num_t)tbeam_supreme::sdMosi()));
|
||||
}
|
||||
|
||||
SdCardDiag::ProbeBytes SdCardDiag::idleProbe(SPIClass& bus, const char* busName) {
|
||||
ProbeBytes out;
|
||||
|
||||
SD.end();
|
||||
bus.end();
|
||||
delay(5);
|
||||
forceSpiDeselected();
|
||||
|
||||
bus.begin(tbeam_supreme::sdSck(),
|
||||
tbeam_supreme::sdMiso(),
|
||||
tbeam_supreme::sdMosi(),
|
||||
tbeam_supreme::sdCs());
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
delay(1);
|
||||
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
const uint8_t value = bus.transfer(0xFF);
|
||||
out.bytes[i] = value;
|
||||
if (value == 0xFF) out.ffCount++;
|
||||
else if (value == 0x00) out.zeroCount++;
|
||||
else out.otherCount++;
|
||||
}
|
||||
|
||||
logf("probe %s: ff=%u zero=%u other=%u bytes=%02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
busName,
|
||||
(unsigned)out.ffCount,
|
||||
(unsigned)out.zeroCount,
|
||||
(unsigned)out.otherCount,
|
||||
out.bytes[0],
|
||||
out.bytes[1],
|
||||
out.bytes[2],
|
||||
out.bytes[3],
|
||||
out.bytes[4],
|
||||
out.bytes[5],
|
||||
out.bytes[6],
|
||||
out.bytes[7]);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool SdCardDiag::tryMount(SPIClass& bus, const char* busName, uint32_t hz) {
|
||||
SD.end();
|
||||
bus.end();
|
||||
delay(5);
|
||||
forceSpiDeselected();
|
||||
|
||||
bus.begin(tbeam_supreme::sdSck(),
|
||||
tbeam_supreme::sdMiso(),
|
||||
tbeam_supreme::sdMosi(),
|
||||
tbeam_supreme::sdCs());
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
delay(1);
|
||||
for (uint8_t i = 0; i < 10; ++i) {
|
||||
bus.transfer(0xFF);
|
||||
}
|
||||
|
||||
const uint32_t startMs = millis();
|
||||
const bool ok = SD.begin(tbeam_supreme::sdCs(), bus, hz);
|
||||
const uint32_t elapsedMs = millis() - startMs;
|
||||
|
||||
if (!ok) {
|
||||
logf("mount fail bus=%s hz=%lu dt=%lums",
|
||||
busName,
|
||||
(unsigned long)hz,
|
||||
(unsigned long)elapsedMs);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t type = SD.cardType();
|
||||
if (type == CARD_NONE) {
|
||||
SD.end();
|
||||
logf("mount fail bus=%s hz=%lu dt=%lums cardType=NONE",
|
||||
busName,
|
||||
(unsigned long)hz,
|
||||
(unsigned long)elapsedMs);
|
||||
return false;
|
||||
}
|
||||
|
||||
activeSpi_ = &bus;
|
||||
activeBusName_ = busName;
|
||||
activeHz_ = hz;
|
||||
|
||||
logf("mount ok bus=%s hz=%lu dt=%lums type=%s",
|
||||
busName,
|
||||
(unsigned long)hz,
|
||||
(unsigned long)elapsedMs,
|
||||
cardTypeToString(type));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SdCardDiag::mountCard() {
|
||||
activeSpi_ = nullptr;
|
||||
activeBusName_ = "none";
|
||||
activeHz_ = 0;
|
||||
|
||||
const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};
|
||||
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||
if (tryMount(hspi_, "HSPI", freqs[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||
if (tryMount(fspi_, "FSPI", freqs[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SdCardDiag::printCardDetails() {
|
||||
const uint8_t type = SD.cardType();
|
||||
const uint64_t cardBytes = SD.cardSize();
|
||||
const uint64_t totalBytes = SD.totalBytes();
|
||||
const uint64_t usedBytes = SD.usedBytes();
|
||||
|
||||
logf("card: type=%s size=%llu MB fs_total=%llu MB fs_used=%llu MB fs_free=%llu MB",
|
||||
cardTypeToString(type),
|
||||
(unsigned long long)(cardBytes / (1024ULL * 1024ULL)),
|
||||
(unsigned long long)(totalBytes / (1024ULL * 1024ULL)),
|
||||
(unsigned long long)(usedBytes / (1024ULL * 1024ULL)),
|
||||
(unsigned long long)((totalBytes >= usedBytes ? (totalBytes - usedBytes) : 0) / (1024ULL * 1024ULL)));
|
||||
logf("mount: bus=%s hz=%lu",
|
||||
activeBusName_,
|
||||
(unsigned long)activeHz_);
|
||||
printCardIdentity();
|
||||
|
||||
File root = SD.open("/");
|
||||
if (!root || !root.isDirectory()) {
|
||||
logf("root open failed after successful mount");
|
||||
if (root) {
|
||||
root.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DirSummary summary;
|
||||
summarizeDirectory(root, summary, 0);
|
||||
root.close();
|
||||
|
||||
logf("root summary: files=%lu dirs=%lu bytes=%llu max_depth=%u",
|
||||
(unsigned long)summary.fileCount,
|
||||
(unsigned long)summary.dirCount,
|
||||
(unsigned long long)summary.fileBytes,
|
||||
(unsigned)summary.maxDepth);
|
||||
|
||||
root = SD.open("/");
|
||||
if (!root || !root.isDirectory()) {
|
||||
logf("root reopen failed for sample listing");
|
||||
if (root) {
|
||||
root.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
printSampleEntries(root, 8);
|
||||
root.close();
|
||||
}
|
||||
|
||||
bool SdCardDiag::readCardIdentity(CardIdentity& identity) {
|
||||
identity = CardIdentity{};
|
||||
|
||||
if (!activeSpi_) {
|
||||
logf("CID read skipped: no active SPI bus");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t spiHz = (activeHz_ > 0 && activeHz_ < 4000000UL) ? activeHz_ : 4000000UL;
|
||||
uint8_t cmd[6] = {0x40 | 10, 0x00, 0x00, 0x00, 0x00, 0x01};
|
||||
cmd[5] = (uint8_t)((crc7(cmd, 5) << 1) | 0x01);
|
||||
|
||||
activeSpi_->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE0));
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
activeSpi_->transfer(0xFF);
|
||||
digitalWrite(tbeam_supreme::sdCs(), LOW);
|
||||
|
||||
if (!waitForSpiByte(0xFF, 250)) {
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
activeSpi_->transfer(0xFF);
|
||||
activeSpi_->endTransaction();
|
||||
logf("CID read failed: card never became ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < sizeof(cmd); ++i) {
|
||||
activeSpi_->transfer(cmd[i]);
|
||||
}
|
||||
|
||||
uint8_t r1 = 0xFF;
|
||||
uint32_t startMs = millis();
|
||||
while ((millis() - startMs) < 250) {
|
||||
r1 = activeSpi_->transfer(0xFF);
|
||||
if ((r1 & 0x80) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r1 != 0x00) {
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
activeSpi_->transfer(0xFF);
|
||||
activeSpi_->endTransaction();
|
||||
logf("CID read failed: CMD10 R1=0x%02X", r1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!waitForSpiByte(0xFE, 250)) {
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
activeSpi_->transfer(0xFF);
|
||||
activeSpi_->endTransaction();
|
||||
logf("CID read failed: no data token");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < sizeof(identity.raw); ++i) {
|
||||
identity.raw[i] = activeSpi_->transfer(0xFF);
|
||||
}
|
||||
activeSpi_->transfer(0xFF);
|
||||
activeSpi_->transfer(0xFF);
|
||||
|
||||
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||
activeSpi_->transfer(0xFF);
|
||||
activeSpi_->endTransaction();
|
||||
|
||||
identity.mid = identity.raw[0];
|
||||
identity.oid[0] = (char)identity.raw[1];
|
||||
identity.oid[1] = (char)identity.raw[2];
|
||||
identity.oid[2] = '\0';
|
||||
memcpy(identity.pnm, &identity.raw[3], 5);
|
||||
identity.pnm[5] = '\0';
|
||||
identity.prvMajor = (identity.raw[8] >> 4) & 0x0F;
|
||||
identity.prvMinor = identity.raw[8] & 0x0F;
|
||||
identity.psn = ((uint32_t)identity.raw[9] << 24) |
|
||||
((uint32_t)identity.raw[10] << 16) |
|
||||
((uint32_t)identity.raw[11] << 8) |
|
||||
(uint32_t)identity.raw[12];
|
||||
const uint16_t mdt = (uint16_t)(((identity.raw[13] & 0x0F) << 8) | identity.raw[14]);
|
||||
identity.mdtYear = 2000 + ((mdt >> 4) & 0xFF);
|
||||
identity.mdtMonth = mdt & 0x0F;
|
||||
identity.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SdCardDiag::printCardIdentity() {
|
||||
CardIdentity identity;
|
||||
if (!readCardIdentity(identity)) {
|
||||
logf("CID: unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
logf("CID: mid=0x%02X manufacturer_guess=%s oem=%s product=%s revision=%u.%u serial=0x%08llX date=%04u-%02u",
|
||||
identity.mid,
|
||||
manufacturerNameFromMid(identity.mid),
|
||||
identity.oid,
|
||||
identity.pnm,
|
||||
(unsigned)identity.prvMajor,
|
||||
(unsigned)identity.prvMinor,
|
||||
(unsigned long long)identity.psn,
|
||||
(unsigned)identity.mdtYear,
|
||||
(unsigned)identity.mdtMonth);
|
||||
logf("CID raw: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
identity.raw[0],
|
||||
identity.raw[1],
|
||||
identity.raw[2],
|
||||
identity.raw[3],
|
||||
identity.raw[4],
|
||||
identity.raw[5],
|
||||
identity.raw[6],
|
||||
identity.raw[7],
|
||||
identity.raw[8],
|
||||
identity.raw[9],
|
||||
identity.raw[10],
|
||||
identity.raw[11],
|
||||
identity.raw[12],
|
||||
identity.raw[13],
|
||||
identity.raw[14],
|
||||
identity.raw[15]);
|
||||
}
|
||||
|
||||
void SdCardDiag::summarizeDirectory(File dir, DirSummary& summary, uint8_t depth) {
|
||||
File entry = dir.openNextFile();
|
||||
while (entry) {
|
||||
if (depth > summary.maxDepth) {
|
||||
summary.maxDepth = depth;
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
summary.dirCount++;
|
||||
summarizeDirectory(entry, summary, depth + 1);
|
||||
} else {
|
||||
summary.fileCount++;
|
||||
summary.fileBytes += entry.size();
|
||||
if (depth > summary.maxDepth) {
|
||||
summary.maxDepth = depth;
|
||||
}
|
||||
}
|
||||
|
||||
entry.close();
|
||||
entry = dir.openNextFile();
|
||||
}
|
||||
}
|
||||
|
||||
void SdCardDiag::printSampleEntries(File dir, uint8_t maxEntries) {
|
||||
uint8_t index = 0;
|
||||
File entry = dir.openNextFile();
|
||||
if (!entry) {
|
||||
logf("root entries: <empty>");
|
||||
return;
|
||||
}
|
||||
|
||||
while (entry && index < maxEntries) {
|
||||
logf("root[%u]: %s %s %llu bytes",
|
||||
(unsigned)index,
|
||||
entry.isDirectory() ? "DIR " : "FILE",
|
||||
entry.name(),
|
||||
(unsigned long long)entry.size());
|
||||
entry.close();
|
||||
++index;
|
||||
entry = dir.openNextFile();
|
||||
}
|
||||
|
||||
if (entry) {
|
||||
entry.close();
|
||||
logf("root entries: showing first %u only", (unsigned)maxEntries);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t SdCardDiag::crc7(const uint8_t* data, size_t len) const {
|
||||
uint8_t crc = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
uint8_t value = data[i];
|
||||
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||
crc <<= 1;
|
||||
if (((value & 0x80) ^ (crc & 0x80)) != 0) {
|
||||
crc ^= 0x09;
|
||||
}
|
||||
value <<= 1;
|
||||
}
|
||||
}
|
||||
return crc & 0x7F;
|
||||
}
|
||||
|
||||
bool SdCardDiag::waitForSpiByte(uint8_t expected, uint32_t timeoutMs) {
|
||||
const uint32_t startMs = millis();
|
||||
while ((millis() - startMs) < timeoutMs) {
|
||||
if (activeSpi_->transfer(0xFF) == expected) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* SdCardDiag::manufacturerNameFromMid(uint8_t mid) const {
|
||||
// Provenance:
|
||||
// - Official SD CID field layout comes from the SD Association simplified spec:
|
||||
// "Part 1 Physical Layer Simplified Specification", CID table section.
|
||||
// MID is an 8-bit manufacturer ID assigned by SD-3C, LLC.
|
||||
// - SD-3C does not publish a public authoritative MID->vendor table.
|
||||
// - The mapping below is therefore best-effort and community-derived from
|
||||
// observed card CIDs, cross-checked 2026-04-13 against:
|
||||
// https://www.it-sd.com/articles/secure-digital-card-registers/
|
||||
// https://www.cameramemoryspeed.com/sd-memory-card-faq/reading-sd-card-cid-serial-psn-internal-numbers/
|
||||
// https://zeroalpha.com.au/services/data-recovery-blog/sd/secure-digital-sd-memory-card-technology-explained
|
||||
// Use the printed MID/OID/PNM/CID raw values as the authoritative log data.
|
||||
switch (mid) {
|
||||
case 0x01: return "Panasonic";
|
||||
case 0x02: return "Toshiba/Kioxia";
|
||||
case 0x03: return "SanDisk";
|
||||
case 0x1B: return "Samsung";
|
||||
case 0x1D: return "ADATA";
|
||||
case 0x1F: return "Kingston";
|
||||
case 0x27: return "Phison";
|
||||
case 0x28: return "Lexar";
|
||||
case 0x31: return "Silicon Power";
|
||||
case 0x41: return "Kingston/ATP";
|
||||
case 0x74: return "Transcend";
|
||||
case 0x76: return "Patriot";
|
||||
case 0x82: return "Sony";
|
||||
case 0x89: return "Delkin";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* SdCardDiag::cardTypeToString(uint8_t type) const {
|
||||
switch (type) {
|
||||
case CARD_MMC:
|
||||
return "MMC";
|
||||
case CARD_SD:
|
||||
return "SDSC";
|
||||
case CARD_SDHC:
|
||||
return "SDHC/SDXC";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* SdCardDiag::boolToOnOff(bool value) const {
|
||||
return value ? "ON" : "OFF";
|
||||
}
|
||||
74
exercises/19_SD_Card_diag/lib/sdcard_diag/SdCardDiag.h
Normal file
74
exercises/19_SD_Card_diag/lib/sdcard_diag/SdCardDiag.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SD.h>
|
||||
#include <SPI.h>
|
||||
#include <XPowersLib.h>
|
||||
|
||||
class SdCardDiag {
|
||||
public:
|
||||
explicit SdCardDiag(Print& out = Serial);
|
||||
|
||||
bool begin();
|
||||
void printReport();
|
||||
|
||||
private:
|
||||
struct ProbeBytes {
|
||||
uint8_t ffCount = 0;
|
||||
uint8_t zeroCount = 0;
|
||||
uint8_t otherCount = 0;
|
||||
uint8_t bytes[8] = {0};
|
||||
};
|
||||
|
||||
struct DirSummary {
|
||||
uint32_t fileCount = 0;
|
||||
uint32_t dirCount = 0;
|
||||
uint64_t fileBytes = 0;
|
||||
uint8_t maxDepth = 0;
|
||||
};
|
||||
|
||||
struct CardIdentity {
|
||||
bool valid = false;
|
||||
uint8_t mid = 0;
|
||||
char oid[3] = {0};
|
||||
char pnm[6] = {0};
|
||||
uint8_t prvMajor = 0;
|
||||
uint8_t prvMinor = 0;
|
||||
uint32_t psn = 0;
|
||||
uint16_t mdtYear = 0;
|
||||
uint8_t mdtMonth = 0;
|
||||
uint8_t raw[16] = {0};
|
||||
};
|
||||
|
||||
void logf(const char* fmt, ...);
|
||||
void printDivider();
|
||||
void forceSpiDeselected();
|
||||
bool initPmu();
|
||||
void cycleSdRail(uint32_t offMs = 250, uint32_t onSettleMs = 700);
|
||||
void printRailStatus();
|
||||
void printPinMap();
|
||||
void printPinLevels();
|
||||
ProbeBytes idleProbe(SPIClass& bus, const char* busName);
|
||||
bool tryMount(SPIClass& bus, const char* busName, uint32_t hz);
|
||||
bool mountCard();
|
||||
void printCardDetails();
|
||||
bool readCardIdentity(CardIdentity& identity);
|
||||
void printCardIdentity();
|
||||
void summarizeDirectory(File dir, DirSummary& summary, uint8_t depth);
|
||||
void printSampleEntries(File dir, uint8_t maxEntries);
|
||||
uint8_t crc7(const uint8_t* data, size_t len) const;
|
||||
bool waitForSpiByte(uint8_t expected, uint32_t timeoutMs);
|
||||
const char* manufacturerNameFromMid(uint8_t mid) const;
|
||||
const char* cardTypeToString(uint8_t type) const;
|
||||
const char* boolToOnOff(bool value) const;
|
||||
|
||||
Print& out_;
|
||||
SPIClass hspi_{HSPI};
|
||||
SPIClass fspi_{FSPI};
|
||||
SPIClass* activeSpi_ = nullptr;
|
||||
const char* activeBusName_ = "none";
|
||||
uint32_t activeHz_ = 0;
|
||||
XPowersLibInterface* pmu_ = nullptr;
|
||||
uint32_t logSeq_ = 0;
|
||||
bool pmuReady_ = false;
|
||||
};
|
||||
12
exercises/19_SD_Card_diag/lib/sdcard_diag/library.json
Normal file
12
exercises/19_SD_Card_diag/lib/sdcard_diag/library.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "sdcard_diag",
|
||||
"version": "0.1.0",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "XPowersLib"
|
||||
},
|
||||
{
|
||||
"name": "Wire"
|
||||
}
|
||||
]
|
||||
}
|
||||
58
exercises/19_SD_Card_diag/platformio.ini
Normal file
58
exercises/19_SD_Card_diag/platformio.ini
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
; 20260413 ChatGPT
|
||||
; Exercise 19_SD_Card_diag
|
||||
|
||||
[platformio]
|
||||
default_envs = guy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.partitions = default_8MB.csv
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
Wire
|
||||
lewisxhe/XPowersLib@0.3.3
|
||||
|
||||
build_flags =
|
||||
-I ../../shared/boards
|
||||
-I ../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:amy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"AMY\"
|
||||
|
||||
[env:bob]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"BOB\"
|
||||
|
||||
[env:cy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"CY\"
|
||||
|
||||
[env:dan]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"DAN\"
|
||||
|
||||
[env:ed]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"ED\"
|
||||
|
||||
[env:guy]
|
||||
extends = env
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"GUY\"
|
||||
41
exercises/19_SD_Card_diag/src/main.cpp
Normal file
41
exercises/19_SD_Card_diag/src/main.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// 20260413 ChatGPT
|
||||
// Exercise 19: SD Card Diagnostics
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "SdCardDiag.h"
|
||||
|
||||
#ifndef NODE_LABEL
|
||||
#define NODE_LABEL "SDDIAG"
|
||||
#endif
|
||||
|
||||
static const uint32_t kSerialDelayMs = 1500;
|
||||
static const uint32_t kLoopDelayMs = 25;
|
||||
static const uint32_t kReportEveryMs = 10000;
|
||||
|
||||
static SdCardDiag g_diag(Serial);
|
||||
static uint32_t g_lastReportMs = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(kSerialDelayMs);
|
||||
|
||||
Serial.println();
|
||||
Serial.println("============================================================");
|
||||
Serial.printf("Exercise 19 SD Card Diagnostics [%s]\r\n", NODE_LABEL);
|
||||
Serial.println("============================================================");
|
||||
|
||||
g_diag.begin();
|
||||
g_diag.printReport();
|
||||
g_lastReportMs = millis();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
const uint32_t now = millis();
|
||||
if ((uint32_t)(now - g_lastReportMs) >= kReportEveryMs) {
|
||||
g_lastReportMs = now;
|
||||
g_diag.printReport();
|
||||
}
|
||||
|
||||
delay(kLoopDelayMs);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue