RTC keeps time between POWER OFF & ON, SD Card at start still needs work -- if card is in the slot, it is not readable until it is pulled on and then inserted.
This commit is contained in:
parent
a83684d0cb
commit
544d459c9b
11 changed files with 1288 additions and 6 deletions
37
exercises/04_SD_card/README.md
Normal file
37
exercises/04_SD_card/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
## Exercise 04: SD Card
|
||||
|
||||
This exercise loops forever. Each cycle:
|
||||
|
||||
1. Prints `Sleeping 10 seconds` and waits 10 seconds.
|
||||
2. Detects and mounts the SD card.
|
||||
3. Prints card and filesystem info (type/size/used).
|
||||
4. Writes `/Exercise_04_test.txt` with:
|
||||
- `This is a test`
|
||||
5. Ensures nested directories exist:
|
||||
- `/test/testsub1/testsubsub1`
|
||||
6. Writes nested file:
|
||||
- `/test/testsub1/testsubsub1/Exercise_04_test.txt`
|
||||
7. If either target file already exists, prints warning, erases it, then recreates it.
|
||||
8. Demonstrates permission behavior:
|
||||
- Notes FAT does not provide Unix `chmod/chown`.
|
||||
- Shows access behavior via `FILE_READ` vs `FILE_WRITE` modes.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
source /home/jlpoole/rnsenv/bin/activate
|
||||
pio run -e node_a
|
||||
```
|
||||
|
||||
## Upload
|
||||
|
||||
```bash
|
||||
source /home/jlpoole/rnsenv/bin/activate
|
||||
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||
```
|
||||
|
||||
## Monitor
|
||||
|
||||
```bash
|
||||
screen /dev/ttyACM0 115200
|
||||
```
|
||||
36
exercises/04_SD_card/platformio.ini
Normal file
36
exercises/04_SD_card/platformio.ini
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
; 20260213 ChatGPT
|
||||
; $Id$
|
||||
; $HeadURL$
|
||||
|
||||
[platformio]
|
||||
default_envs = node_a
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
lewisxhe/XPowersLib@0.3.3
|
||||
|
||||
; SD pins based on T-Beam S3 core pin mapping
|
||||
build_flags =
|
||||
-D SD_SCK=36
|
||||
-D SD_MISO=37
|
||||
-D SD_MOSI=35
|
||||
-D SD_CS=47
|
||||
-D IMU_CS=34
|
||||
-D I2C_SDA1=42
|
||||
-D I2C_SCL1=41
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
[env:node_a]
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"A\"
|
||||
|
||||
[env:node_b]
|
||||
build_flags =
|
||||
${env.build_flags}
|
||||
-D NODE_LABEL=\"B\"
|
||||
273
exercises/04_SD_card/src/main.cpp
Normal file
273
exercises/04_SD_card/src/main.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
// 20260213 ChatGPT
|
||||
// $Id$
|
||||
// $HeadURL$
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include <SD.h>
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <XPowersLib.h>
|
||||
|
||||
#ifndef SD_SCK
|
||||
#define SD_SCK 36
|
||||
#endif
|
||||
#ifndef SD_MISO
|
||||
#define SD_MISO 37
|
||||
#endif
|
||||
#ifndef SD_MOSI
|
||||
#define SD_MOSI 35
|
||||
#endif
|
||||
#ifndef SD_CS
|
||||
#define SD_CS 47
|
||||
#endif
|
||||
#ifndef IMU_CS
|
||||
#define IMU_CS 34
|
||||
#endif
|
||||
#ifndef I2C_SDA1
|
||||
#define I2C_SDA1 42
|
||||
#endif
|
||||
#ifndef I2C_SCL1
|
||||
#define I2C_SCL1 41
|
||||
#endif
|
||||
|
||||
static SPIClass sdSpiH(HSPI);
|
||||
static SPIClass sdSpiF(FSPI);
|
||||
static SPIClass* g_sdSpi = nullptr;
|
||||
static const char* g_sdBusName = "none";
|
||||
static uint32_t g_sdFreq = 0;
|
||||
static XPowersLibInterface* g_pmu = nullptr;
|
||||
|
||||
static const char* kRootTestFile = "/Exercise_04_test.txt";
|
||||
static const char* kNestedDir = "/test/testsub1/testsubsub1";
|
||||
static const char* kNestedTestFile = "/test/testsub1/testsubsub1/Exercise_04_test.txt";
|
||||
static const char* kPayload = "This is a test";
|
||||
|
||||
static bool initPmuForSdPower() {
|
||||
Wire1.begin(I2C_SDA1, I2C_SCL1);
|
||||
|
||||
if (!g_pmu) {
|
||||
g_pmu = new XPowersAXP2101(Wire1);
|
||||
}
|
||||
|
||||
if (!g_pmu->init()) {
|
||||
Serial.println("PMU: AXP2101 init failed (SD power rail may be off)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mirror Meshtastic tbeam-s3-core power setup needed for peripherals.
|
||||
g_pmu->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); // GNSS
|
||||
g_pmu->enablePowerOutput(XPOWERS_ALDO4);
|
||||
g_pmu->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); // LoRa
|
||||
g_pmu->enablePowerOutput(XPOWERS_ALDO3);
|
||||
g_pmu->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); // sensor/rtc path
|
||||
g_pmu->enablePowerOutput(XPOWERS_ALDO2);
|
||||
g_pmu->setPowerChannelVoltage(XPOWERS_ALDO1, 3300); // IMU/OLED path
|
||||
g_pmu->enablePowerOutput(XPOWERS_ALDO1);
|
||||
g_pmu->setPowerChannelVoltage(XPOWERS_BLDO1, 3300); // SD card rail
|
||||
g_pmu->enablePowerOutput(XPOWERS_BLDO1);
|
||||
|
||||
Serial.printf("PMU: AXP2101 ready, BLDO1(SD)=%s\r\n",
|
||||
g_pmu->isPowerChannelEnable(XPOWERS_BLDO1) ? "ON" : "OFF");
|
||||
return g_pmu->isPowerChannelEnable(XPOWERS_BLDO1);
|
||||
}
|
||||
|
||||
static const char* cardTypeToString(uint8_t type) {
|
||||
switch (type) {
|
||||
case CARD_MMC: return "MMC";
|
||||
case CARD_SD: return "SDSC";
|
||||
case CARD_SDHC: return "SDHC/SDXC";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz) {
|
||||
SD.end();
|
||||
bus.end();
|
||||
delay(10);
|
||||
|
||||
// Keep inactive devices deselected on shared bus lines.
|
||||
pinMode(SD_CS, OUTPUT);
|
||||
digitalWrite(SD_CS, HIGH);
|
||||
pinMode(IMU_CS, OUTPUT);
|
||||
digitalWrite(IMU_CS, HIGH);
|
||||
|
||||
bus.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
|
||||
delay(2);
|
||||
|
||||
Serial.printf("SD: trying bus=%s freq=%lu Hz\r\n", busName, (unsigned long)hz);
|
||||
if (!SD.begin(SD_CS, bus, hz)) {
|
||||
Serial.println("SD: mount failed (possible non-FAT format, power, or bus issue)");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t cardType = SD.cardType();
|
||||
if (cardType == CARD_NONE) {
|
||||
SD.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
g_sdSpi = &bus;
|
||||
g_sdBusName = busName;
|
||||
g_sdFreq = hz;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mountCard() {
|
||||
const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};
|
||||
|
||||
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||
if (tryMountWithBus(sdSpiH, "HSPI", freqs[i])) {
|
||||
Serial.println("SD: card detected and mounted");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||
if (tryMountWithBus(sdSpiF, "FSPI", freqs[i])) {
|
||||
Serial.println("SD: card detected and mounted");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("SD: begin() failed on all bus/frequency attempts");
|
||||
Serial.println(" likely card absent, bad format, pin mismatch, or hardware issue");
|
||||
return false;
|
||||
}
|
||||
|
||||
static void printCardInfo() {
|
||||
uint8_t cardType = SD.cardType();
|
||||
uint64_t cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL);
|
||||
uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL);
|
||||
uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL);
|
||||
|
||||
Serial.printf("SD type: %s\r\n", cardTypeToString(cardType));
|
||||
Serial.printf("SD size: %llu MB\r\n", cardSizeMB);
|
||||
Serial.printf("FS total: %llu MB\r\n", totalMB);
|
||||
Serial.printf("FS used : %llu MB\r\n", usedMB);
|
||||
Serial.printf("SPI bus: %s @ %lu Hz\r\n", g_sdBusName, (unsigned long)g_sdFreq);
|
||||
}
|
||||
|
||||
static bool ensureDirRecursive(const char* path) {
|
||||
String full(path);
|
||||
if (!full.startsWith("/")) {
|
||||
full = "/" + full;
|
||||
}
|
||||
|
||||
int start = 1;
|
||||
while (start > 0 && start < (int)full.length()) {
|
||||
int slash = full.indexOf('/', start);
|
||||
String partial = (slash < 0) ? full : full.substring(0, slash);
|
||||
|
||||
if (!SD.exists(partial.c_str())) {
|
||||
Serial.printf("Creating directory: %s\r\n", partial.c_str());
|
||||
if (!SD.mkdir(partial.c_str())) {
|
||||
Serial.printf("ERROR: mkdir failed for %s\r\n", partial.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (slash < 0) {
|
||||
break;
|
||||
}
|
||||
start = slash + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool rewriteFile(const char* path, const char* payload) {
|
||||
if (SD.exists(path)) {
|
||||
Serial.printf("WARNING: %s exists ... erasing\r\n", path);
|
||||
if (!SD.remove(path)) {
|
||||
Serial.printf("ERROR: failed to erase %s\r\n", path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
File f = SD.open(path, FILE_WRITE);
|
||||
if (!f) {
|
||||
Serial.printf("ERROR: failed to create %s\r\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t wrote = f.println(payload);
|
||||
f.close();
|
||||
|
||||
if (wrote == 0) {
|
||||
Serial.printf("ERROR: write failed for %s\r\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("Wrote file: %s\r\n", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void permissionsDemo(const char* path) {
|
||||
Serial.println("Permissions demo:");
|
||||
Serial.println(" SD/FAT does not support Unix permissions (chmod/chown).");
|
||||
Serial.println(" Access control is by open mode (FILE_READ/FILE_WRITE).");
|
||||
|
||||
File r = SD.open(path, FILE_READ);
|
||||
if (!r) {
|
||||
Serial.printf(" Could not open %s as FILE_READ\r\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf(" FILE_READ open succeeded, size=%u bytes\r\n", (unsigned)r.size());
|
||||
size_t writeInReadMode = r.print("attempt write while opened read-only");
|
||||
if (writeInReadMode == 0) {
|
||||
Serial.println(" As expected, write via FILE_READ handle was blocked.");
|
||||
} else {
|
||||
Serial.printf(" NOTE: write via FILE_READ returned %u (unexpected)\r\n", (unsigned)writeInReadMode);
|
||||
}
|
||||
r.close();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Sleeping for 5 seconds to allow Serial Monitor connection...");
|
||||
delay(5000); // Time to open Serial Monitor after reset
|
||||
|
||||
Serial.println("\r\n==================================================");
|
||||
Serial.println("Exercise 04: SD card test loop");
|
||||
Serial.println("==================================================");
|
||||
Serial.printf("Pins: CS=%d SCK=%d MISO=%d MOSI=%d\r\n", SD_CS, SD_SCK, SD_MISO, SD_MOSI);
|
||||
Serial.printf("PMU I2C: SDA1=%d SCL1=%d\r\n", I2C_SDA1, I2C_SCL1);
|
||||
Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library.\r\n");
|
||||
|
||||
initPmuForSdPower();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Serial.println("Sleeping 10 seconds");
|
||||
delay(10000);
|
||||
|
||||
if (!mountCard()) {
|
||||
Serial.println("SD step skipped this cycle.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printCardInfo();
|
||||
|
||||
if (!rewriteFile(kRootTestFile, kPayload)) {
|
||||
SD.end();
|
||||
Serial.println("Cycle ended due to root file error.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ensureDirRecursive(kNestedDir)) {
|
||||
SD.end();
|
||||
Serial.println("Cycle ended due to directory creation error.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rewriteFile(kNestedTestFile, kPayload)) {
|
||||
SD.end();
|
||||
Serial.println("Cycle ended due to nested file error.\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
permissionsDemo(kRootTestFile);
|
||||
SD.end();
|
||||
Serial.println("Cycle complete.\r\n");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue