342 lines
8.8 KiB
C++
342 lines
8.8 KiB
C++
// 20260403 ChatGPT
|
|
// Exercise 16_PSRAM - Extended Exercise 15_RAM with PSRAM support
|
|
|
|
#include <Arduino.h>
|
|
#include <Wire.h>
|
|
#include <U8g2lib.h>
|
|
#include <time.h>
|
|
#include <esp_heap_caps.h>
|
|
|
|
#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 <text> - write text to PSRAM buffer");
|
|
Serial.println(" append <text> - 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();
|
|
}
|