初めて、「FREENOVE ESP32-S3 WROOM」内蔵「PSRAM」の意識的な活用が出来ました。ArduinoIDEでの「コンパイル後の出力」です。
最大3145728バイトのフラッシュメモリのうち、スケッチが390435バイト(12%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が24008バイト(7%)を使っていて、ローカル変数で303672バイト使うことができます。
esptool v5.0.0
Serial port COM6:
Connecting....
Connected to ESP32-S3 on COM6:
Chip type: ESP32-S3 (QFN56) (revision v0.2)
Features: Wi-Fi, BT 5 (LE), Dual Core + LP Core, 240MHz, Embedded PSRAM 8MB (AP_3v3)
Crystal frequency: 40MHz
MAC: xx:xx:xx:xx:xx:xx
Uploading stub flasher...
Running stub flasher...
Stub flasher running.
Changing baud rate to 921600...
Changed.
次は、起動直後にPSRAMの状況をシリアル表示させたもの。広大なラム領域が使えそうです。
PSRAM is enabled and initialized!
Total PSRAM: 8388608 bytes
Free PSRAM: 8383968 bytes
After ps_malloc
Free PSRAM: 6843868 bytes
使用物品/環境です。
0.Windows 11 Pro 25H2
1.Arduino IDE 2.3.10
2.esp32 by Espressif Systems 3.3.0
(コンパイル時のエラーが良く起こるのでこのバージョンを使いました)
3.MCUは
FREENOVE ESP32‐S3 WROOM Camera Model(カメラは不使用)
Flash 8MB, PSRAM 8MB
4.TFT LCDは
4inch 800X480 Display MAR4018, driver chip NT35510
5.LovyanGFX ライブラリ使用
これまでも、「ESP32」と「TFT480x320ディスプレイ」を用いてマンデルブロー集合の描画をしてきました。
800x480ディスプレイでの表示は当然遅くなります。高速化のため、既に発散済み座標の計算はスキップしたいのですが、800x480サイズとなると発散を記録する配列が大きくなって取れません。これまでは諦めていましたが、チップ内蔵のPSRAMが利用出来ないかと考えました。esptoolでみるとEmbeddedですが、よくわからず、積極的には取組んできませんでした。本気になって調べ、試したら使えました。次のサイトなどが参考になりました。
https://qiita.com/Dreamwalker/items/01cd216d48b4528c5959
「Arduino IDE」内で必要なコーディングを行い、
ツール -> PSRAM:"OPI PSRAM"
と設定してコンパイルすると簡単に出来ました。
高速化のために次の2点も行いました。
1.platform.txtを編集
/* C:\Users\xxxxxx\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.3.0\
platform.txt -----------------------------------------------------------
# EXPERIMENTAL feature: optimization flags
# - this is alpha and may be subject to change without notice
compiler.optimization_flags=-O2
compiler.optimization_flags.release=-O2
compiler.optimization_flags.debug=-Og -g3
------------------------------------------------------------------------------- */
2.static修飾子を一部の変数に付ける
static uint16_t C[800]; // 色配列
static bool* cels = (bool *)ps_malloc(sizeof(bool *) * 800 * 480);
この結果です。
| 20ページ描画に要する秒数 | |
|---|---|
| 全部の点を常に計算 | 190 |
| 発散済の点は計算せず | 142 |
| static修飾子の付加 | 137(何故かな?) |
| "-O2"から"-Ofast" にすると | 129 |
ArduinoIDEにおけるツールオプションです。
スケッチ本体です。LovyanGFXライブラリの個別設定ファイルも一緒にしてます。(ご利用は計画的に)
// mylovyanGFX.hpp begin --------------------------------------------------------
#pragma once
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
// https://github.com/lovyan03/LovyanGFX/blob/master/examples/HowToUse/2_user_setting/2_user_setting.ino
class LGFX_NT35510_800x480 : public lgfx::LGFX_Device {
lgfx::Panel_NT35510 _panel_instance;
lgfx::Bus_Parallel16 _bus_instance; //16bit Parallelのインスタンス(ESP32s3)
public:
LGFX_NT35510_800x480(void) {
{ // バス制御の設定を行います。
auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。
// 16ビットパラレルバスの設定
cfg.freq_write = 120000000; // 送信クロック(最大20MHz,80MHzを整数割の値に丸める)
cfg.pin_wr = 8; // WR を接続しているピン番号
cfg.pin_rd = 10; // RD を接続しているピン番号
cfg.pin_rs = 3; //8;//11; // RS(D/C)を接続しているピン番号
cfg.pin_d0 = 18; // D0 を接続しているピン番号
cfg.pin_d1 = 21; //37;//36; // D1 を接続しているピン番号
cfg.pin_d2 = 17; // D2 を接続しているピン番号
cfg.pin_d3 = 47; //38;//37; // D3 を接続しているピン番号
cfg.pin_d4 = 16; // D4 を接続しているピン番号
cfg.pin_d5 = 45; //39;//38; // D5 を接続しているピン番号
cfg.pin_d6 = 15; // D6 を接続しているピン番号s
cfg.pin_d7 = 0; //40;//39; // D7 を接続しているピン番号
cfg.pin_d8 = 1; //2; // D8 を接続しているピン番号
cfg.pin_d9 = 4; // D9 を接続しているピン番号
cfg.pin_d10 = 2; //42; // D10を接続しているピン番号
cfg.pin_d11 = 5; // D11を接続しているピン番号
cfg.pin_d12 = 42; //41; // D12を接続しているピン番号
cfg.pin_d13 = 6; // D13を接続しているピン番号
cfg.pin_d14 = 41; //40; // D14を接続しているピン番号
cfg.pin_d15 = 7; // D15を接続しているピン番号
_bus_instance.config(cfg); // 設定値をバスに反映します。
_panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
}
{ // 表示パネル制御の設定
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
cfg.pin_cs = 46; //11;//10; // CSが接続されているピン番号 (-1 = disable)
cfg.pin_rst = 9; // RSTが接続されているピン番号 (-1 = disable)
cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable)
cfg.memory_width = 480; // ドライバICがサポートしている最大の幅
cfg.memory_height = 800; // ドライバICがサポートしている最大の高さ
cfg.panel_width = 480; // 実際に表示可能な幅
cfg.panel_height = 800; // 実際に表示可能な高さ
cfg.offset_x = 0; // パネルのX方向オフセット量
cfg.offset_y = 0; // パネルのY方向オフセット量
cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数
cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数
cfg.readable = false; // データ読出しが可能な場合 trueに設定
cfg.invert = false; // パネルの明暗が反転してしまう場合 trueに設定
cfg.rgb_order = true; // パネルの赤と青が入れ替わってしまう場合 trueに設定
cfg.dlen_16bit = true; // データ長を16bit単位で送信するパネルの場合trueに設定
cfg.bus_shared = false; // SDカードとバス共有はtrueに設定
_panel_instance.config(cfg);
}
setPanel(&_panel_instance); // 使用するパネルをセットします。
}
};
// myLovyanGFX.hpp end -----------------------------------------------------------
static LGFX_NT35510_800x480 tft; // LGFXのインスタンスを作成。
/* C:\Users\xxxxxx\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.3.0\
platform.txt option -----------------------------------------------------------
# EXPERIMENTAL feature: optimization flags
# - this is alpha and may be subject to change without notice
compiler.optimization_flags=-O2
compiler.optimization_flags.release=-O2
compiler.optimization_flags.debug=-Og -g3
------------------------------------------------------------------------------- */
const uint16_t col[]{
0x0000, //0
0x2228, //1
0x867E, //2LightSkyBlue
0xFF1B, //3MistyRose
0x480F, //4Indigo
0xAEBC, //5LightBlue
0xF81F, //6Magenta
0xFFFB, //7LightYellow
0x07FF, //8Cyan
0xF800, //9Red
0x07E0, //10Green
0x001F, //11Blue
0xFCEE, //12LightSalmon
0xEF31, //13Khaki
0xDFFF, //14LightCyan
0xFFE0 //15Yellow
};
// Display 固有定数
#define W 800 // tft.width()
#define H 480 // tft.heght()
#define X0 400 // tft.width()/2
#define Y0 240 // tft.height()/2
const float toRad = PI / 180.0; //度をラジアンに変換
const float absZ = 4.0; // 発散判定
// default Mandelbrot Set -----------------------------------------
// viewpW=2.4; CtoX = 0.0; CtoY = 0.0;
// repeat=200; page=1; dp=0;
// ----------------------------------------------------------------
// viewpW = 0.00024; // 10000倍に拡大
// Viewport width; Viewport Center CtoX;CtoY;
// 風景の良い場所例
// example 1
// viewpW = 0.00024; CtoX = -1.24997; CtoY = -0.00877;
// repeat=190; page=28; dp=2;
// example 2
// viewpW = 0.00024; CtoX=-1.401;CtoY=-0.000178;
// repeat=300; page=19; dp=2;
const float viewpW = 0.00122; // viewport width(window width)
const float di = viewpW / W; // real/imaginary calculation step a pixel
const float CtoX = -1.19998; // 現在の表示(viewpW)の中心
const float CtoY = -0.15507;
const uint8_t dp = 2;
uint16_t repeat = 120; // 反復数.page進行に応じ増加させる
uint16_t k; // 反復回カウンター
uint16_t pi;
int16_t i, j;
uint16_t Xi, Yj;
float x, y, zr, zi, R, I; //複素計算変数
uint32_t now, starttime;
//const uint8_t page = 20;
#define page 20
static uint16_t C[800]; // 色配列
////////////////////////////////////////////////////
// Tool Option > PSRAM: "OPI PSRAM"
///////////////////////////////////////////////////
void setup() {
Serial.begin(115200);
Serial.println("begin");
tft.begin();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
// PSRAM Check
if (psramInit()) {
Serial.println("");
Serial.println("PSRAM is enabled and initialized!");
Serial.printf("Total PSRAM: %d bytes\n", ESP.getPsramSize());//8388608
Serial.printf("Free PSRAM: %d bytes\n", ESP.getFreePsram());//8383968
} else {
Serial.println("PSRAM not available or disabled in settings.");
}
for (k = 0; k < 800; k++) C[k] = ColorSel(k);
static bool* cels = (bool *)ps_malloc(sizeof(bool *) * 800 * 480);// ここで定義すると129s !
for (Xi = 0; Xi < W; Xi++) {
for (Yj = 0; Yj < H; Yj++) cels[Xi + Yj * 800] = false;
}
Serial.println("After ps_malloc");
Serial.printf("Free PSRAM: %d bytes\n", ESP.getFreePsram());//6843868
starttime = millis();
for (pi = 0; pi < page; pi++) { // 反復数上限を増やしていく
for (j = -Y0; j < Y0; j++) {
Yj = Y0 - j - 1;
y = j * di + CtoY;
for (i = -X0; i < X0; i++) {
Xi = X0 + i; // 190s at No checking cels
if (cels[Xi + Yj * 800] == false) { // 142s on Checking cels
// 137s with adding "static"
x = i * di + CtoX;
zr = 0.0;
zi = 0.0;
k = 0; //-1;
do {
k++;
R = zr * zr - zi * zi + x;
I = 2 * zr * zi + y;
zr = R;
zi = I;
if (k == repeat) break;
} while ((zr * zr + zi * zi) < absZ);
if (k < repeat) {
tft.drawPixel(Xi, Yj, C[k]);
cels[Xi + Yj * 800] = true;
}
}
}
}
now = millis();
tft.setCursor(0, 0);
tft.printf("%2dpage", pi + 1);
tft.setCursor(0, 8);
tft.printf("%.0fs", (now - starttime) * 0.001);
Serial.println((now - starttime) * 0.001);
repeat += dp * pi;
}
grid();
}
void grid(void) {
tft.drawLine(X0 + 100, 0, X0 + 100, tft.height(), 0xFFFF);
tft.drawLine(X0 - 100, 0, X0 - 100, tft.height(), 0xFFFF);
tft.drawLine(X0 + 200, 0, X0 + 200, tft.height(), 0xFFFF);
tft.drawLine(X0 - 200, 0, X0 - 200, tft.height(), 0xFFFF);
tft.drawLine(0, Y0 + 100, tft.width(), Y0 + 100, 0xFFFF);
tft.drawLine(0, Y0 - 100, tft.width(), Y0 - 100, 0xFFFF);
tft.drawLine(X0, 0, X0, tft.height(), 0xFFFF); // 中心線
tft.drawLine(0, Y0, tft.width(), Y0, 0xFFFF);
tft.drawString("1.0", X0 + 192, H - 8);
tft.drawString("0", 0, Y0 - 3);
tft.drawString("0", X0 - 2, H - 8);
tft.drawString("-1.0", X0 - 213, H - 8);
tft.drawString("End", 0, H - 8);
}
uint16_t ColorSel(uint16_t c) {
uint16_t re, gr, bl; // color 0-255
if (c < 16) return col[c];
else {
c -= 16;
re = 128 + 126 * cos(c * toRad);
gr = 128 + 126 * cos((c + 120) * toRad);
bl = 128 + 126 * cos((c + 240) * toRad);
return tft.color565(re, gr, bl);
}
}
void loop(void) {
}
最後まで見ていただきありがとうございました。
