microReticulumTbeam/exercises/16_PSRAM/src/main.cpp

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();
}