// 20260403 ChatGPT // Exercise 16_PSRAM - Extended Exercise 15_RAM with PSRAM support #include #include #include #include #include #ifndef NODE_LABEL #define NODE_LABEL "PSRAM" #endif #ifndef OLED_SDA #define OLED_SDA 17 #endif #ifndef OLED_SCL #define OLED_SCL 18 #endif #ifndef OLED_ADDR #define OLED_ADDR 0x3C #endif static U8G2_SH1106_128X64_NONAME_F_HW_I2C g_oled(U8G2_R0, U8X8_PIN_NONE); static const size_t kTmpFileCapacity = 2097152; // 2MB in PSRAM static char *g_tmpFileBuffer = nullptr; static size_t g_tmpFileSize = 0; static unsigned g_tmpLineNumber = 0; static void oledShowLines(const char *l1, const char *l2 = nullptr, const char *l3 = nullptr, const char *l4 = nullptr, const char *l5 = nullptr) { g_oled.clearBuffer(); g_oled.setFont(u8g2_font_5x8_tf); if (l1) g_oled.drawUTF8(0, 12, l1); if (l2) g_oled.drawUTF8(0, 24, l2); if (l3) g_oled.drawUTF8(0, 36, l3); if (l4) g_oled.drawUTF8(0, 48, l4); if (l5) g_oled.drawUTF8(0, 60, l5); g_oled.sendBuffer(); } static size_t getAvailableRamBytes() { return ESP.getFreeHeap(); } static size_t getTotalRamBytes() { return ESP.getHeapSize(); } static size_t getPSRAMFreeBytes() { size_t freeBytes = heap_caps_get_free_size(MALLOC_CAP_SPIRAM); if (freeBytes == 0 && ESP.getFreePsram() > 0) { freeBytes = ESP.getFreePsram(); } return freeBytes; } static size_t getPSRAMTotalBytes() { size_t totalBytes = heap_caps_get_total_size(MALLOC_CAP_SPIRAM); if (totalBytes == 0 && ESP.getPsramSize() > 0) { totalBytes = ESP.getPsramSize(); } return totalBytes; } static void getTimestamp(char *out, size_t outSize) { const time_t now = time(nullptr); if (now > 1700000000) { struct tm tmNow; localtime_r(&now, &tmNow); snprintf(out, outSize, "%04d-%02d-%02d %02d:%02d:%02d", tmNow.tm_year + 1900, tmNow.tm_mon + 1, tmNow.tm_mday, tmNow.tm_hour, tmNow.tm_min, tmNow.tm_sec); return; } const uint32_t sec = millis() / 1000; const uint32_t hh = sec / 3600; const uint32_t mm = (sec % 3600) / 60; const uint32_t ss = sec % 60; snprintf(out, outSize, "uptime %02u:%02u:%02u", (unsigned)hh, (unsigned)mm, (unsigned)ss); } static void appendTimestampLine() { if (!g_tmpFileBuffer) return; char timestamp[32]; getTimestamp(timestamp, sizeof(timestamp)); char line[96]; const int written = snprintf(line, sizeof(line), "%u, %s\r\n", g_tmpLineNumber + 1, timestamp); if (written <= 0) { return; } const size_t lineLen = (size_t)written; if (g_tmpFileSize + lineLen > kTmpFileCapacity - 1) { Serial.println("Warning: PSRAM log full, stopping writes"); return; } memcpy(g_tmpFileBuffer + g_tmpFileSize, line, lineLen); g_tmpFileSize += lineLen; g_tmpLineNumber++; } static void printRamStatus() { const size_t freeRam = getAvailableRamBytes(); const size_t totalRam = getTotalRamBytes(); const size_t maxAllocRam = ESP.getMaxAllocHeap(); const size_t freePSRAM = getPSRAMFreeBytes(); const size_t totalPSRAM = getPSRAMTotalBytes(); Serial.printf("RAM total=%u free=%u maxAlloc=%u | PSRAM total=%u free=%u\r\n", (unsigned)totalRam, (unsigned)freeRam, (unsigned)maxAllocRam, (unsigned)totalPSRAM, (unsigned)freePSRAM); char line1[32]; char line2[32]; char line3[32]; char line4[32]; char line5[32]; snprintf(line1, sizeof(line1), "Exercise 16 PSRAM"); snprintf(line2, sizeof(line2), "Node: %s", NODE_LABEL); // Display format: "Free XXXKb/8.0Mbs" const float psramMb = totalPSRAM / (1024.0f * 1024.0f); const size_t ramKb = freeRam / 1024U; snprintf(line3, sizeof(line3), "Free %uKb/%.1fMbs", (unsigned)ramKb, psramMb); snprintf(line4, sizeof(line4), "PSRAM: %u KB", (unsigned)(freePSRAM / 1024U)); snprintf(line5, sizeof(line5), "Lines: %u", (unsigned)g_tmpLineNumber); oledShowLines(line1, line2, line3, line4, line5); } static void showHelp() { Serial.println("PSRAM command list:"); Serial.println(" help - show this menu"); Serial.println(" stat - show PSRAM buffer state"); Serial.println(" read - read PSRAM buffer contents"); Serial.println(" clear - clear PSRAM buffer contents"); Serial.println(" write - write text to PSRAM buffer"); Serial.println(" append - append text to PSRAM buffer"); } static void printPSRAMFileStat() { Serial.printf("PSRAM Buffer Capacity: %u bytes\r\n", (unsigned)kTmpFileCapacity); Serial.printf("Current Size: %u bytes\r\n", (unsigned)g_tmpFileSize); Serial.printf("Lines: %u\r\n", (unsigned)g_tmpLineNumber); Serial.printf("PSRAM Total: %u bytes (%.2f MB)\r\n", (unsigned)getPSRAMTotalBytes(), getPSRAMTotalBytes() / (1024.0f * 1024.0f)); Serial.printf("PSRAM Free: %u bytes\r\n", (unsigned)getPSRAMFreeBytes()); } static void printPSRAMFileContents() { if (!g_tmpFileBuffer) { Serial.println("PSRAM buffer not allocated"); return; } if (g_tmpFileSize == 0) { Serial.println("PSRAM buffer is empty"); return; } Serial.print("PSRAM contents: "); Serial.write((const uint8_t *)g_tmpFileBuffer, g_tmpFileSize); if (g_tmpFileBuffer[g_tmpFileSize - 1] != '\n') Serial.println(); } static void setPSRAMFileContent(const char *text) { if (!g_tmpFileBuffer) { Serial.println("Error: PSRAM buffer not allocated"); return; } if (!text) { g_tmpFileSize = 0; g_tmpLineNumber = 0; return; } const size_t newLen = strlen(text); if (newLen > kTmpFileCapacity - 1) { Serial.printf("Error: content too large (%u/%u)\r\n", (unsigned)newLen, (unsigned)kTmpFileCapacity); return; } memcpy(g_tmpFileBuffer, text, newLen); g_tmpFileSize = newLen; g_tmpLineNumber = 0; } static void appendPSRAMFileContent(const char *text) { if (!g_tmpFileBuffer) { Serial.println("Error: PSRAM buffer not allocated"); return; } if (!text || text[0] == '\0') return; const size_t textLen = strlen(text); if (g_tmpFileSize + textLen > kTmpFileCapacity - 1) { Serial.printf("Error: append would exceed %u bytes\r\n", (unsigned)kTmpFileCapacity); return; } memcpy(g_tmpFileBuffer + g_tmpFileSize, text, textLen); g_tmpFileSize += textLen; } static void processSerialCommand(const char *line) { if (!line || line[0] == '\0') return; char tmp[384]; strncpy(tmp, line, sizeof(tmp) - 1); tmp[sizeof(tmp) - 1] = '\0'; char *cmd = strtok(tmp, " \t\r\n"); if (!cmd) return; if (strcasecmp(cmd, "help") == 0) { showHelp(); return; } if (strcasecmp(cmd, "stat") == 0) { printPSRAMFileStat(); return; } if (strcasecmp(cmd, "read") == 0) { printPSRAMFileContents(); return; } if (strcasecmp(cmd, "clear") == 0) { g_tmpFileSize = 0; g_tmpLineNumber = 0; Serial.println("PSRAM buffer cleared"); return; } if (strcasecmp(cmd, "write") == 0 || strcasecmp(cmd, "append") == 0) { const char *payload = line + strlen(cmd); while (*payload == ' ' || *payload == '\t') payload++; if (strcasecmp(cmd, "write") == 0) setPSRAMFileContent(payload); else appendPSRAMFileContent(payload); Serial.printf("%s: %u bytes\r\n", cmd, (unsigned)g_tmpFileSize); return; } Serial.println("Unknown command (help for list)"); } void setup() { Serial.begin(115200); delay(800); Serial.println("Exercise 16_PSRAM boot"); // Boot-time PSRAM diagnostics Serial.printf("Boot PSRAM size: %u bytes\r\n", (unsigned)ESP.getPsramSize()); Serial.printf("Boot PSRAM free: %u bytes\r\n", (unsigned)ESP.getFreePsram()); // Allocate PSRAM buffer g_tmpFileBuffer = (char *)heap_caps_malloc(kTmpFileCapacity, MALLOC_CAP_SPIRAM); if (!g_tmpFileBuffer) { Serial.println("ERROR: Failed to allocate PSRAM buffer!"); oledShowLines("Exercise 16_PSRAM", "Node: " NODE_LABEL, "PSRAM alloc FAILED"); } else { Serial.printf("PSRAM buffer allocated: %u bytes\r\n", (unsigned)kTmpFileCapacity); } Wire.begin(OLED_SDA, OLED_SCL); g_oled.setI2CAddress(OLED_ADDR << 1); g_oled.begin(); oledShowLines("Exercise 16_PSRAM", "Node: " NODE_LABEL, "Booting..."); delay(1000); } void loop() { static uint32_t lastMs = 0; const uint32_t now = millis(); // check serial commands at all times static char rxLine[384]; static size_t rxLen = 0; while (Serial.available()) { int c = Serial.read(); if (c <= 0) continue; if (c == '\r' || c == '\n') { if (rxLen > 0) { rxLine[rxLen] = '\0'; processSerialCommand(rxLine); rxLen = 0; } } else if (rxLen + 1 < sizeof(rxLine)) { rxLine[rxLen++] = (char)c; } } if (now - lastMs < 1000) { delay(10); return; } lastMs = now; if (g_tmpFileBuffer) { appendTimestampLine(); } printRamStatus(); }