Skip to content

Instantly share code, notes, and snippets.

@riraosan
Last active June 9, 2022 09:55
Show Gist options
  • Save riraosan/ff3f144c7c403214df7c0cb84f837971 to your computer and use it in GitHub Desktop.
Save riraosan/ff3f144c7c403214df7c0cb84f837971 to your computer and use it in GitHub Desktop.
M5Unified_avatar_WebRadio_LED by using ESP32_8BIT_CVBS and M5Stack ATOM Lite
// LEDレベルメータを使用する;
//#define USE_FASTLED
// アバターをサブディスプレイに表示する;
#define USE_AVATAR
// 2画面モードで表示内容を入れ替える;
//#define SWAP_DISPLAY
#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASS "YOUR_PASSOWRD"
#include <HTTPClient.h>
#include <math.h>
/// need ESP8266Audio library. ( URL : https://github.com/earlephilhower/ESP8266Audio/ )
#include <AudioOutput.h>
#include <AudioFileSourceICYStream.h>
#include <AudioFileSource.h>
#include <AudioFileSourceBuffer.h>
#include <AudioGeneratorMP3.h>
#include <AudioOutputI2S.h>
//#include <M5UnitLCD.h>
//#include <M5UnitOLED.h>
#include <M5Unified.h>
#include <ESP32_8BIT_CVBS.h>
static ESP32_8BIT_CVBS display;
#ifdef USE_AVATAR
#include "Avatar.h"
#include "faces/DogFace.h"
#endif
#ifdef USE_FASTLED
#include <FastLED.h>
// How many leds in your strip?
#define NUM_LEDS 10
#if defined(ARDUINO_M5STACK_Core2)
#define DATA_PIN 25
#else
#define DATA_PIN 15
#endif
// Define the array of leds
CRGB leds[NUM_LEDS];
CRGB led_table[NUM_LEDS / 2] = {CRGB::Blue, CRGB::Green, CRGB::Yellow, CRGB::Orange, CRGB::Red};
void turn_off_led() {
// Now turn the LED off, then pause
for (int i = 0; i < NUM_LEDS; i++) leds[i] = CRGB::Black;
FastLED.show();
}
void fill_led_buff(CRGB color) {
// Now turn the LED off, then pause
for (int i = 0; i < NUM_LEDS; i++) leds[i] = color;
}
void clear_led_buff() {
// Now turn the LED off, then pause
for (int i = 0; i < NUM_LEDS; i++) leds[i] = CRGB::Black;
}
void level_led(int level1, int level2) {
if (level1 > NUM_LEDS / 2) level1 = NUM_LEDS / 2;
if (level2 > NUM_LEDS / 2) level2 = NUM_LEDS / 2;
clear_led_buff();
for (int i = 0; i < level1; i++) {
leds[NUM_LEDS / 2 - 1 - i] = led_table[i];
}
for (int i = 0; i < level2; i++) {
leds[i + NUM_LEDS / 2] = led_table[i];
}
FastLED.show();
}
#else
void turn_off_led() {}
// void fill_led_buff(CRGB color) {}
void clear_led_buff() {}
void level_led(int level1, int level2) {}
#endif
LGFX_Device* gfx1 = nullptr;
LGFX_Device* gfx2 = nullptr;
/// set M5Speaker virtual channel (0-7)
static constexpr uint8_t m5spk_virtual_channel = 0;
/// set web radio station url
static constexpr const char* station_list[][2] =
{
{"MAXXED Out", "http://149.56.195.94:8015/steam"},
{"Asia Dream", "http://igor.torontocast.com:1025/;.-mp3"},
{"thejazzstream", "http://wbgo.streamguys.net/thejazzstream"},
{"181-beatles_128k", "http://listen.181fm.com/181-beatles_128k.mp3"},
{"illstreet-128-mp3", "http://ice1.somafm.com/illstreet-128-mp3"},
{"bootliquor-128-mp3", "http://ice1.somafm.com/bootliquor-128-mp3"},
{"dronezone-128-mp3", "http://ice1.somafm.com/dronezone-128-mp3"},
{"Lite Favorites", "http://naxos.cdnstream.com:80/1255_128"},
{"Classic FM", "http://media-ice.musicradio.com:80/ClassicFMMP3"},
};
static constexpr const size_t stations = sizeof(station_list) / sizeof(station_list[0]);
class AudioOutputM5Speaker : public AudioOutput {
public:
AudioOutputM5Speaker(m5::Speaker_Class* m5sound, uint8_t virtual_sound_channel = 0) {
_m5sound = m5sound;
_virtual_ch = virtual_sound_channel;
}
virtual ~AudioOutputM5Speaker(void){};
virtual bool begin(void) override { return true; }
virtual bool ConsumeSample(int16_t sample[2]) override {
if (_tri_buffer_index < tri_buf_size) {
_tri_buffer[_tri_index][_tri_buffer_index] = sample[0];
_tri_buffer[_tri_index][_tri_buffer_index + 1] = sample[1];
_tri_buffer_index += 2;
return true;
}
flush();
return false;
}
virtual void flush(void) override {
if (_tri_buffer_index) {
_m5sound->playRaw(_tri_buffer[_tri_index], _tri_buffer_index, hertz, true, 1, _virtual_ch);
_tri_index = _tri_index < 2 ? _tri_index + 1 : 0;
_tri_buffer_index = 0;
++_update_count;
}
}
virtual bool stop(void) override {
flush();
_m5sound->stop(_virtual_ch);
for (size_t i = 0; i < 3; ++i) {
memset(_tri_buffer[i], 0, tri_buf_size * sizeof(int16_t));
}
++_update_count;
return true;
}
const int16_t* getBuffer(void) const { return _tri_buffer[(_tri_index + 2) % 3]; }
const uint32_t getUpdateCount(void) const { return _update_count; }
protected:
m5::Speaker_Class* _m5sound;
uint8_t _virtual_ch;
static constexpr size_t tri_buf_size = 640;
int16_t _tri_buffer[3][tri_buf_size];
size_t _tri_buffer_index = 0;
size_t _tri_index = 0;
size_t _update_count = 0;
};
#define FFT_SIZE 256
class fft_t {
float _wr[FFT_SIZE + 1];
float _wi[FFT_SIZE + 1];
float _fr[FFT_SIZE + 1];
float _fi[FFT_SIZE + 1];
uint16_t _br[FFT_SIZE + 1];
size_t _ie;
public:
fft_t(void) {
#ifndef M_PI
#define M_PI 3.141592653
#endif
_ie = logf((float)FFT_SIZE) / log(2.0) + 0.5;
static constexpr float omega = 2.0f * M_PI / FFT_SIZE;
static constexpr int s4 = FFT_SIZE / 4;
static constexpr int s2 = FFT_SIZE / 2;
for (int i = 1; i < s4; ++i) {
float f = cosf(omega * i);
_wi[s4 + i] = f;
_wi[s4 - i] = f;
_wr[i] = f;
_wr[s2 - i] = -f;
}
_wi[s4] = _wr[0] = 1;
size_t je = 1;
_br[0] = 0;
_br[1] = FFT_SIZE / 2;
for (size_t i = 0; i < _ie - 1; ++i) {
_br[je << 1] = _br[je] >> 1;
je = je << 1;
for (size_t j = 1; j < je; ++j) {
_br[je + j] = _br[je] + _br[j];
}
}
}
void exec(const int16_t* in) {
memset(_fi, 0, sizeof(_fi));
for (size_t j = 0; j < FFT_SIZE / 2; ++j) {
float basej = 0.25 * (1.0 - _wr[j]);
size_t r = FFT_SIZE - j - 1;
/// perform han window and stereo to mono convert.
_fr[_br[j]] = basej * (in[j * 2] + in[j * 2 + 1]);
_fr[_br[r]] = basej * (in[r * 2] + in[r * 2 + 1]);
}
size_t s = 1;
size_t i = 0;
do {
size_t ke = s;
s <<= 1;
size_t je = FFT_SIZE / s;
size_t j = 0;
do {
size_t k = 0;
do {
size_t l = s * j + k;
size_t m = ke * (2 * j + 1) + k;
size_t p = je * k;
float Wxmr = _fr[m] * _wr[p] + _fi[m] * _wi[p];
float Wxmi = _fi[m] * _wr[p] - _fr[m] * _wi[p];
_fr[m] = _fr[l] - Wxmr;
_fi[m] = _fi[l] - Wxmi;
_fr[l] += Wxmr;
_fi[l] += Wxmi;
} while (++k < ke);
} while (++j < je);
} while (++i < _ie);
}
uint32_t get(size_t index) {
return (index < FFT_SIZE / 2) ? (uint32_t)sqrtf(_fr[index] * _fr[index] + _fi[index] * _fi[index]) : 0u;
}
};
static constexpr const int preallocateBufferSize = 5 * 1024;
static constexpr const int preallocateCodecSize = 29192; // MP3 codec max mem needed
static void* preallocateBuffer = nullptr;
static void* preallocateCodec = nullptr;
static constexpr size_t WAVE_SIZE = 320;
static AudioOutputM5Speaker out(&M5.Speaker, m5spk_virtual_channel);
static AudioGenerator* decoder = nullptr;
static AudioFileSourceICYStream* file = nullptr;
static AudioFileSourceBuffer* buff = nullptr;
static fft_t fft;
static bool fft_enabled = false;
static bool wave_enabled = false;
static uint16_t prev_y[(FFT_SIZE / 2) + 1];
static uint16_t peak_y[(FFT_SIZE / 2) + 1];
static int16_t wave_y[WAVE_SIZE];
static int16_t wave_h[WAVE_SIZE];
static int16_t raw_data[WAVE_SIZE * 2];
static int header_height = 0;
static size_t station_index = 0;
static char stream_title[128] = {0};
static const char* meta_text[2] = {nullptr, stream_title};
static const size_t meta_text_num = sizeof(meta_text) / sizeof(meta_text[0]);
static uint8_t meta_mod_bits = 0;
static volatile size_t playindex = ~0u;
static void MDCallback(void* cbData, const char* type, bool isUnicode, const char* string) {
(void)cbData;
if ((strcmp(type, "StreamTitle") == 0) && (strcmp(stream_title, string) != 0)) {
strncpy(stream_title, string, sizeof(stream_title));
meta_mod_bits |= 2;
}
}
static void stop(void) {
if (decoder) {
decoder->stop();
delete decoder;
decoder = nullptr;
}
if (buff) {
buff->close();
delete buff;
buff = nullptr;
}
if (file) {
file->close();
delete file;
file = nullptr;
}
out.stop();
}
static void play(size_t index) {
playindex = index;
}
static void decodeTask(void*) {
for (;;) {
delay(1);
if (playindex != ~0u) {
auto index = playindex;
playindex = ~0u;
stop();
meta_text[0] = station_list[index][0];
stream_title[0] = 0;
meta_mod_bits = 3;
file = new AudioFileSourceICYStream(station_list[index][1]);
file->RegisterMetadataCB(MDCallback, (void*)"ICY");
buff = new AudioFileSourceBuffer(file, preallocateBuffer, preallocateBufferSize);
decoder = new AudioGeneratorMP3(preallocateCodec, preallocateCodecSize);
decoder->begin(buff, &out);
}
if (decoder && decoder->isRunning()) {
if (!decoder->loop()) {
decoder->stop();
}
}
}
}
static uint32_t bgcolor(LGFX_Device* gfx, int y) {
auto h = gfx->height();
auto dh = h - header_height;
int v = ((h - y) << 5) / dh;
if (dh > 44) {
int v2 = ((h - y - 1) << 5) / dh;
if ((v >> 2) != (v2 >> 2)) {
return 0x666666u;
}
}
return gfx->color888(v + 2, v, v + 6);
}
static void gfxSetup(LGFX_Device* gfx) {
if (gfx == nullptr) {
return;
}
if (gfx->width() < gfx->height()) {
gfx->setRotation(gfx->getRotation() ^ 1);
}
gfx->setFont(&fonts::lgfxJapanGothic_12);
gfx->setEpdMode(epd_mode_t::epd_fastest);
gfx->setTextWrap(false);
gfx->setCursor(0, 8);
gfx->println(" WebRadio player");
gfx->fillRect(0, 6, gfx->width(), 2, TFT_BLACK);
header_height = (gfx->height() > 80) ? 33 : 21;
fft_enabled = !gfx->isEPD();
if (fft_enabled) {
// wave_enabled = (gfx->getBoard() != m5gfx::board_M5UnitLCD);
wave_enabled = false;
for (int y = header_height; y < gfx->height(); ++y) {
gfx->drawFastHLine(0, y, gfx->width(), bgcolor(gfx, y));
}
}
for (int x = 0; x < (FFT_SIZE / 2) + 1; ++x) {
prev_y[x] = INT16_MAX;
peak_y[x] = INT16_MAX;
}
for (int x = 0; x < WAVE_SIZE; ++x) {
wave_y[x] = gfx->height();
wave_h[x] = 0;
}
}
void gfxLoop(LGFX_Device* gfx) {
if (gfx == nullptr) {
return;
}
if (header_height > 32) {
if (meta_mod_bits) {
// gfx->startWrite();
for (int id = 0; id < meta_text_num; ++id) {
if (0 == (meta_mod_bits & (1 << id))) {
continue;
}
meta_mod_bits &= ~(1 << id);
size_t y = id * 12;
if (y + 12 >= header_height) {
continue;
}
gfx->setCursor(4, 8 + y);
gfx->fillRect(0, 8 + y, gfx->width(), 12, gfx->getBaseColor());
gfx->print(meta_text[id]);
gfx->print(" "); // Garbage data removal when UTF8 characters are broken in the middle.
}
// gfx->display();
// gfx->endWrite();
}
} else {
static int title_x;
static int title_id;
static int wait = INT16_MAX;
if (meta_mod_bits) {
if (meta_mod_bits & 1) {
title_x = 4;
title_id = 0;
gfx->fillRect(0, 8, gfx->width(), 12, gfx->getBaseColor());
}
meta_mod_bits = 0;
wait = 0;
}
if (--wait < 0) {
int tx = title_x;
int tid = title_id;
wait = 3;
// gfx->startWrite();
uint_fast8_t no_data_bits = 0;
do {
if (tx == 4) {
wait = 255;
}
gfx->setCursor(tx, 8);
const char* meta = meta_text[tid];
if (meta[0] != 0) {
gfx->print(meta);
gfx->print(" / ");
tx = gfx->getCursorX();
if (++tid == meta_text_num) {
tid = 0;
}
if (tx <= 4) {
title_x = tx;
title_id = tid;
}
} else {
if ((no_data_bits |= 1 << tid) == ((1 << meta_text_num) - 1)) {
break;
}
if (++tid == meta_text_num) {
tid = 0;
}
}
} while (tx < gfx->width());
--title_x;
// gfx->display();
// gfx->endWrite();
}
}
if (fft_enabled) {
static int prev_x[2];
static int peak_x[2];
auto buf = out.getBuffer();
if (buf) {
memcpy(raw_data, buf, WAVE_SIZE * 2 * sizeof(int16_t)); // stereo data copy
// gfx->startWrite();
int32_t levels[2];
// draw stereo level meter
for (size_t i = 0; i < 2; ++i) {
int32_t level = 0;
for (size_t j = i; j < 640; j += 32) {
uint32_t lv = abs(raw_data[j]);
if (level < lv) {
level = lv;
}
}
levels[i] = level;
int32_t x = (level * gfx->width()) / INT16_MAX;
int32_t px = prev_x[i];
if (px != x) {
gfx->fillRect(x, i * 3, px - x, 2, px < x ? 0xFF9900u : 0x330000u);
prev_x[i] = x;
}
px = peak_x[i];
if (px > x) {
gfx->writeFastVLine(px, i * 3, 2, TFT_BLACK);
px--;
} else {
px = x;
}
if (peak_x[i] != px) {
peak_x[i] = px;
gfx->writeFastVLine(px, i * 3, 2, TFT_WHITE);
}
}
// gfx->display();
level_led(levels[0] * 8 / INT16_MAX, levels[1] * 8 / INT16_MAX);
// draw FFT level meter
fft.exec(raw_data);
size_t bw = gfx->width() / 60;
if (bw < 3) {
bw = 3;
}
int32_t dsp_height = gfx->height();
int32_t fft_height = dsp_height - header_height - 1;
size_t xe = gfx->width() / bw;
if (xe > (FFT_SIZE / 2)) {
xe = (FFT_SIZE / 2);
}
int32_t wave_next = ((header_height + dsp_height) >> 1) + (((256 - (raw_data[0] + raw_data[1])) * fft_height) >> 17);
uint32_t bar_color[2] = {0x000033u, 0x99AAFFu};
for (size_t bx = 0; bx <= xe; ++bx) {
size_t x = bx * bw;
if ((x & 7) == 0) {
// gfx->display();
taskYIELD();
}
int32_t f = fft.get(bx);
int32_t y = (f * fft_height) >> 18;
if (y > fft_height) {
y = fft_height;
}
y = dsp_height - y;
int32_t py = prev_y[bx];
if (y != py) {
gfx->fillRect(x, y, bw - 1, py - y, bar_color[(y < py)]);
prev_y[bx] = y;
}
py = peak_y[bx] + 1;
if (py < y) {
gfx->writeFastHLine(x, py - 1, bw - 1, bgcolor(gfx, py - 1));
} else {
py = y - 1;
}
if (peak_y[bx] != py) {
peak_y[bx] = py;
gfx->writeFastHLine(x, py, bw - 1, TFT_WHITE);
}
if (wave_enabled) {
for (size_t bi = 0; bi < bw; ++bi) {
size_t i = x + bi;
if (i >= gfx->width() || i >= WAVE_SIZE) {
break;
}
y = wave_y[i];
int32_t h = wave_h[i];
bool use_bg = (bi + 1 == bw);
if (h > 0) { /// erase previous wave.
gfx->setAddrWindow(i, y, 1, h);
h += y;
do {
uint32_t bg = (use_bg || y < peak_y[bx]) ? bgcolor(gfx, y)
: (y == peak_y[bx]) ? 0xFFFFFFu
: bar_color[(y >= prev_y[bx])];
gfx->writeColor(bg, 1);
} while (++y < h);
}
size_t i2 = i << 1;
int32_t y1 = wave_next;
wave_next = ((header_height + dsp_height) >> 1) + (((256 - (raw_data[i2] + raw_data[i2 + 1])) * fft_height) >> 17);
int32_t y2 = wave_next;
if (y1 > y2) {
int32_t tmp = y1;
y1 = y2;
y2 = tmp;
}
y = y1;
h = y2 + 1 - y;
wave_y[i] = y;
wave_h[i] = h;
if (h > 0) { /// draw new wave.
gfx->setAddrWindow(i, y, 1, h);
h += y;
do {
uint32_t bg = (y < prev_y[bx]) ? 0xFFCC33u : 0xFFFFFFu;
gfx->writeColor(bg, 1);
} while (++y < h);
}
}
}
}
gfx->display();
// gfx->endWrite();
}
}
// if (!gfx->displayBusy())
// { // draw volume bar
// static int px;
// uint8_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel);
// int x = v * (gfx->width()) >> 8;
// if (px != x)
// {
// gfx->fillRect(x, 6, px - x, 2, px < x ? 0xAAFFAAu : 0u);
// // gfx->display();
// px = x;
// }
// }
}
#ifdef USE_AVATAR
using namespace m5avatar;
Avatar* avatar;
void lipSync(void* args) {
float gazeX, gazeY;
int level = 0;
DriveContext* ctx = (DriveContext*)args;
Avatar* avatar = ctx->getAvatar();
for (;;) {
level = abs(*out.getBuffer());
if (level < 1500) level = 0;
if (level > 15000) {
level = 15000;
}
float open = (float)level / 15000.0;
avatar->setMouthOpenRatio(open);
avatar->getGaze(&gazeY, &gazeX);
avatar->setRotation(gazeX * 5);
delay(50);
}
}
#endif
void setup(void) {
display.begin();
display.startWrite();
auto cfg = M5.config();
cfg.external_spk = true; /// use external speaker (SPK HAT / ATOMIC SPK)
// cfg.external_spk_detail.omit_atomic_spk = true; // exclude ATOMIC SPK
// cfg.external_spk_detail.omit_spk_hat = true; // exclude SPK HAT
M5.begin(cfg);
preallocateBuffer = malloc(preallocateBufferSize);
preallocateCodec = malloc(preallocateCodecSize);
if (!preallocateBuffer || !preallocateCodec) {
// M5.Display.printf("FATAL ERROR: Unable to preallocate %d bytes for app\n", preallocateBufferSize + preallocateCodecSize);
for (;;) {
delay(1000);
}
}
// { /// custom setting
// auto spk_cfg = M5.Speaker.config();
// /// Increasing the sample_rate will improve the sound quality instead of increasing the CPU load.
// spk_cfg.sample_rate = 96000; // default:64000 (64kHz) e.g. 48000 , 50000 , 80000 , 96000 , 100000 , 128000 , 144000 , 192000 , 200000
// spk_cfg.task_pinned_core = APP_CPU_NUM;
// M5.Speaker.config(spk_cfg);
// }
{ /// custom setting
auto spk_cfg = M5.Speaker.config();
/// Increasing the sample_rate will improve the sound quality instead of increasing the CPU load.
spk_cfg.sample_rate = 192000; // default:64000 (64kHz) e.g. 48000 , 50000 , 80000 , 96000 , 100000 , 128000 , 144000 , 192000 , 200000
spk_cfg.task_pinned_core = APP_CPU_NUM;
spk_cfg.i2s_port = i2s_port_t::I2S_NUM_1; // IS2_NUM_0はCVBSが使用する。AudioはI2S_NUM_1を使用する。
M5.Speaker.config(spk_cfg);
}
M5.Speaker.begin();
// gfx1 = &M5.Display;
gfx1 = &display;
#ifdef USE_AVATAR
// gfx2 = new M5UnitLCD(M5.Ex_I2C.getSDA(), M5.Ex_I2C.getSCL());
// if(!gfx2->init()) {
// delete gfx2;
// gfx2 = new M5UnitOLED(M5.Ex_I2C.getSDA(), M5.Ex_I2C.getSCL());
// if (!gfx2->init())
// {
// delete gfx2;
// gfx2 = nullptr;
// }
// }
if (gfx1) {
// gfx2->setRotation(3);
// gfx2->fillScreen((uint16_t)TFT_BLACK);
#ifdef SWAP_DISPLAY
std::swap(gfx1, gfx2);
#endif
avatar = new Avatar(gfx1);
switch (gfx1->getBoard()) {
case m5::board_t::board_M5UnitLCD:
avatar->setScale(0.5);
avatar->setOffset(-40, -50);
break;
case m5::board_t::board_M5UnitOLED:
avatar->setScale(0.4);
avatar->setOffset(-95, -90);
break;
default:
avatar->setScale(1.0);
avatar->setOffset(-35, 0);
break;
}
ColorPalette cp;
cp.set(COLOR_PRIMARY, TFT_WHITE);
cp.set(COLOR_BACKGROUND, TFT_BLACK);
avatar->setColorPalette(cp);
avatar->init(); // start drawing
avatar->addTask(lipSync, "lipSync");
}
#endif
gfx1->println("Connecting to WiFi");
WiFi.disconnect();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
#if defined(WIFI_SSID) && defined(WIFI_PASS)
WiFi.begin(WIFI_SSID, WIFI_PASS);
#else
WiFi.begin();
#endif
// Try forever
while (WiFi.status() != WL_CONNECTED) {
gfx1->print(".");
gfx1->display();
delay(100);
}
gfx1->clear();
// gfxSetup(gfx1);
play(station_index);
M5.Speaker.setChannelVolume(m5spk_virtual_channel, 255 / 3); // 0-255
xTaskCreatePinnedToCore(decodeTask, "decodeTask", 4096, nullptr, 5, nullptr, PRO_CPU_NUM);
#ifdef USE_FASTLED
if (M5.getBoard() == m5::board_t::board_M5Stack) {
FastLED.addLeds<SK6812, GPIO_NUM_15, GRB>(leds, NUM_LEDS); // GRB ordering is typical
} else if (M5.getBoard() == m5::board_t::board_M5StackCore2) {
FastLED.addLeds<SK6812, GPIO_NUM_25, GRB>(leds, NUM_LEDS); // GRB ordering is typical
}
FastLED.setBrightness(32);
level_led(5, 5);
FastLED.show();
#endif
}
void loop(void) {
// gfxLoop(gfx1);
{
static int prev_frame;
int frame;
do {
delay(1);
} while (prev_frame == (frame = millis() >> 3)); /// 8 msec cycle wait
prev_frame = frame;
}
M5.update();
if (M5.BtnA.wasPressed()) {
M5.Speaker.tone(440, 50);
}
if (M5.BtnA.wasDeciedClickCount()) {
switch (M5.BtnA.getClickCount()) {
case 1:
M5.Speaker.tone(1000, 100);
if (++station_index >= stations) {
station_index = 0;
}
play(station_index);
break;
case 2:
M5.Speaker.tone(800, 100);
if (station_index == 0) {
station_index = stations;
}
play(--station_index);
break;
}
}
// if (M5.BtnA.isHolding() || M5.BtnB.isPressed() || M5.BtnC.isPressed())
// {
// size_t v = M5.Speaker.getChannelVolume(m5spk_virtual_channel);
// int add = (M5.BtnB.isPressed()) ? -1 : 1;
// if (M5.BtnA.isHolding())
// {
// add = M5.BtnA.getClickCount() ? -1 : 1;
// }
// v += add;
// if (v <= 255)
// {
// M5.Speaker.setChannelVolume(m5spk_virtual_channel, v);
// }
// }
}
@riraosan
Copy link
Author

riraosan commented Jun 8, 2022

使用したライブラリは以下の通りです。ご参考まで。PlatformIOのlib_depsを転載しておきます。

[arduino-esp32]
platform           = platformio/espressif32@^3.5.0
framework          = arduino
;platform_packages = platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git
board_build.arduino.upstream_packages = no

board_build.mcu         = esp32
board_build.f_cpu       = 240000000L
board_build.f_flash     = 80000000L
board_build.flash_mode  = dout
board_build.partitions  = default.csv

lib_deps =
        WiFi
        SPI
        SPIFFS
        SD(esp32)
        HTTPClient
        WiFiClientSecure
        Ticker
        https://github.com/tanakamasayuki/efont.git
        https://github.com/m5stack/M5Unified.git#0.0.7
        https://github.com/riraosan/ESP_8_BIT_composite.git ;for ESP32_8BIT_CVBS
        https://github.com/earlephilhower/ESP8266Audio.git#1.9.5
        https://github.com/riraosan/ESP32_8BIT_CVBS.git

@riraosan
Copy link
Author

riraosan commented Jun 9, 2022

build_flags =
        -std=gnu++14
        -D ARDUINO_ARCH_ESP32
        -D ESP32
        -D CORE_DEBUG_LEVEL=4
        -D CONFIG_ARDUHAL_LOG_COLORS
        -D ENABLE_GPIO26
        -I sample

ビルドフラグを転載しておきます。ご参考まで。

@riraosan
Copy link
Author

riraosan commented Jun 9, 2022

忘れていた。。
ESP32_8BIT_CVBSクラスの中で
_panel.enableDoubleBuffer(false);
を設定してください。SRAM不足を回避できると思います。
ダブルバッファではなく、シングルバッファとなります。(CVBSクラスの話)

#pragma once

#include <ESP_8_BIT_GFX.h>
#include "Panel_CVBS.hpp"

class ESP32_8BIT_CVBS : public lgfx::LGFX_Device {
public:
  ESP32_8BIT_CVBS(void) {
    auto cfg = _panel.config();

    cfg.memory_width = cfg.panel_width = 256;
    cfg.memory_height = cfg.panel_height = 240;

    _panel.config(cfg);
    _panel.setCopyAfterSwap(true);
    _panel.enableDoubleBuffer(false);
    _panel.setColorDepth(lgfx::v1::color_depth_t::rgb332_1Byte);
    _panel.setRotation(0);

    setPanel(&_panel);
  }

  void waitForFrame(void) {
    _panel.waitForFrame();
  }

  void setCopyAfterSwap(bool isSwap) {
    _panel.setCopyAfterSwap(isSwap);
  }

private:
  lgfx::Panel_CVBS _panel;
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment