After various memory exercises, then back to GPS to get UBlox working. UBlox is working. TODO: start libraries, downplay SD Card as we can use memory for interim logging
This commit is contained in:
parent
c7646e169e
commit
41c1fe6819
16 changed files with 2016 additions and 71 deletions
32
exercises/16_PSRAM/README.md
Normal file
32
exercises/16_PSRAM/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Exercise 16: PSRAM
|
||||
|
||||
This exercise demonstrates usage of PSRAM (Pseudo SRAM) on an ESP32-S3 board, alongside regular RAM metrics.
|
||||
|
||||
Behavior:
|
||||
- Reports heap and PSRAM statistics every second over serial.
|
||||
- Shows live heap and PSRAM status on the OLED display (both on same line).
|
||||
- Allows you to write/append/read/clear data in a PSRAM-backed buffer (up to ~2MB).
|
||||
- Designed as an extension of Exercise 15_RAM to explore larger volatile storage.
|
||||
|
||||
Note: the exercise now targets a PSRAM-enabled ESP32-S3 board definition (`freenove_esp32_s3_wroom`). This board profile has 8MB flash + 8MB PSRAM, matching the T-Beam Supreme specifications. If your hardware differs, adjust accordingly.
|
||||
|
||||
Sources:
|
||||
- LilyGo T-Beam SUPREME datasheet/wiki: https://wiki.lilygo.cc/get_started/en/LoRa_GPS/T-Beam-SUPREME/T-Beam-SUPREME.html
|
||||
- PlatformIO board definition: https://docs.platformio.org/page/boards/espressif32/freenove_esp32_s3_wroom.html
|
||||
- Local PlatformIO board metadata: ~/.platformio/platforms/espressif32/boards/freenove_esp32_s3_wroom.json
|
||||
|
||||
Build and upload:
|
||||
|
||||
```bash
|
||||
cd /usr/local/src/microreticulum/microReticulumTbeam/exercises/16_PSRAM
|
||||
pio run -e amy -t upload
|
||||
pio device monitor -b 115200
|
||||
```
|
||||
|
||||
Commands:
|
||||
- `help` - show command menu
|
||||
- `stat` - show PSRAM buffer state
|
||||
- `read` - read PSRAM buffer contents
|
||||
- `clear` - clear PSRAM buffer
|
||||
- `write <text>` - write text to PSRAM buffer
|
||||
- `append <text>` - append text to PSRAM buffer
|
||||
54
exercises/16_PSRAM/platformio.ini
Normal file
54
exercises/16_PSRAM/platformio.ini
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
; 20260403 ChatGPT
|
||||
; Exercise 16_PSRAM
|
||||
|
||||
[platformio]
|
||||
default_envs = amy
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = freenove_esp32_s3_wroom
|
||||
monitor_speed = 115200
|
||||
extra_scripts = pre:scripts/set_build_epoch.py
|
||||
lib_deps =
|
||||
Wire
|
||||
olikraus/U8g2@^2.36.4
|
||||
|
||||
build_flags =
|
||||
-I ../../shared/boards
|
||||
-I ../../external/microReticulum_Firmware
|
||||
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||
-D OLED_SDA=17
|
||||
-D OLED_SCL=18
|
||||
-D OLED_ADDR=0x3C
|
||||
-D ARDUINO_USB_MODE=1
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
|
||||
board_build.flash_mode = qio
|
||||
board_build.psram = 1
|
||||
board_build.psram_type = spi
|
||||
board_build.arduino.memory_type = qio_qspi
|
||||
|
||||
[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\"
|
||||
12
exercises/16_PSRAM/scripts/set_build_epoch.py
Normal file
12
exercises/16_PSRAM/scripts/set_build_epoch.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import time
|
||||
Import("env")
|
||||
|
||||
epoch = int(time.time())
|
||||
utc_tag = time.strftime("%Y%m%d_%H%M%S_z", time.gmtime(epoch))
|
||||
|
||||
env.Append(
|
||||
CPPDEFINES=[
|
||||
("FW_BUILD_EPOCH", str(epoch)),
|
||||
("FW_BUILD_UTC", '\\"%s\\"' % utc_tag),
|
||||
]
|
||||
)
|
||||
342
exercises/16_PSRAM/src/main.cpp
Normal file
342
exercises/16_PSRAM/src/main.cpp
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
// 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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue