first commit
This commit is contained in:
commit
5893b00dd2
1669 changed files with 1982740 additions and 0 deletions
|
|
@ -0,0 +1,677 @@
|
|||
|
||||
#include <M5UnitLCD.h>
|
||||
#include <M5UnitOLED.h>
|
||||
#include <M5Unified.h>
|
||||
|
||||
/// need ESP32-A2DP library. ( URL : https://github.com/pschatzmann/ESP32-A2DP/ )
|
||||
#include <BluetoothA2DPSink.h>
|
||||
|
||||
/// set M5Speaker virtual channel (0-7)
|
||||
static constexpr uint8_t m5spk_virtual_channel = 0;
|
||||
|
||||
/// set ESP32-A2DP device name
|
||||
static constexpr char bt_device_name[] = "ESP32";
|
||||
|
||||
|
||||
class BluetoothA2DPSink_M5Speaker : public BluetoothA2DPSink
|
||||
{
|
||||
public:
|
||||
BluetoothA2DPSink_M5Speaker(m5::Speaker_Class* m5sound, uint8_t virtual_channel = 0)
|
||||
: BluetoothA2DPSink()
|
||||
{
|
||||
is_i2s_output = false; // I2S control by BluetoothA2DPSink is not required.
|
||||
}
|
||||
|
||||
// get rawdata buffer for FFT.
|
||||
const int16_t* getBuffer(void) const { return _tri_buf[_export_index]; }
|
||||
|
||||
const char* getMetaData(size_t id, bool clear_flg = true) { if (clear_flg) { _meta_bits &= ~(1<<id); } return (id < metatext_num) ? _meta_text[id] : nullptr; }
|
||||
|
||||
uint8_t getMetaUpdateInfo(void) const { return _meta_bits; }
|
||||
void clearMetaUpdateInfo(void) { _meta_bits = 0; }
|
||||
|
||||
void clear(void)
|
||||
{
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
if (_tri_buf[i]) { memset(_tri_buf[i], 0, _tri_buf_size[i]); }
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr size_t metatext_size = 128;
|
||||
static constexpr size_t metatext_num = 3;
|
||||
|
||||
protected:
|
||||
int16_t* _tri_buf[3] = { nullptr, nullptr, nullptr };
|
||||
size_t _tri_buf_size[3] = { 0, 0, 0 };
|
||||
size_t _tri_index = 0;
|
||||
size_t _export_index = 0;
|
||||
char _meta_text[metatext_num][metatext_size];
|
||||
uint8_t _meta_bits = 0;
|
||||
size_t _sample_rate = 48000;
|
||||
|
||||
void clearMetaData(void)
|
||||
{
|
||||
for (int i = 0; i < metatext_num; ++i)
|
||||
{
|
||||
_meta_text[i][0] = 0;
|
||||
}
|
||||
_meta_bits = (1<<metatext_num)-1;
|
||||
}
|
||||
|
||||
void av_hdl_a2d_evt(uint16_t event, void *p_param) override
|
||||
{
|
||||
esp_a2d_cb_param_t* a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
|
||||
switch (event) {
|
||||
case ESP_A2D_CONNECTION_STATE_EVT:
|
||||
if (ESP_A2D_CONNECTION_STATE_CONNECTED == a2d->conn_stat.state)
|
||||
{ // 接続
|
||||
|
||||
}
|
||||
else
|
||||
if (ESP_A2D_CONNECTION_STATE_DISCONNECTED == a2d->conn_stat.state)
|
||||
{ // 切断
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_A2D_AUDIO_STATE_EVT:
|
||||
if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state)
|
||||
{ // 再生
|
||||
|
||||
} else
|
||||
if ( ESP_A2D_AUDIO_STATE_REMOTE_SUSPEND == a2d->audio_stat.state
|
||||
|| ESP_A2D_AUDIO_STATE_STOPPED == a2d->audio_stat.state )
|
||||
{ // 停止
|
||||
clearMetaData();
|
||||
clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_A2D_AUDIO_CFG_EVT:
|
||||
{
|
||||
esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)(p_param);
|
||||
size_t tmp = a2d->audio_cfg.mcc.cie.sbc[0];
|
||||
size_t rate = 16000;
|
||||
if ( tmp & (1 << 6)) { rate = 32000; }
|
||||
else if (tmp & (1 << 5)) { rate = 44100; }
|
||||
else if (tmp & (1 << 4)) { rate = 48000; }
|
||||
_sample_rate = rate;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BluetoothA2DPSink::av_hdl_a2d_evt(event, p_param);
|
||||
}
|
||||
|
||||
void av_hdl_avrc_evt(uint16_t event, void *p_param) override
|
||||
{
|
||||
esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param);
|
||||
|
||||
switch (event)
|
||||
{
|
||||
case ESP_AVRC_CT_METADATA_RSP_EVT:
|
||||
for (size_t i = 0; i < metatext_num; ++i)
|
||||
{
|
||||
if (0 == (rc->meta_rsp.attr_id & (1 << i))) { continue; }
|
||||
strncpy(_meta_text[i], (char*)(rc->meta_rsp.attr_text), metatext_size);
|
||||
_meta_bits |= rc->meta_rsp.attr_id;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_AVRC_CT_CONNECTION_STATE_EVT:
|
||||
break;
|
||||
|
||||
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
BluetoothA2DPSink::av_hdl_avrc_evt(event, p_param);
|
||||
}
|
||||
|
||||
int16_t* get_next_buf(const uint8_t* src_data, uint32_t len)
|
||||
{
|
||||
size_t tri = _tri_index < 2 ? _tri_index + 1 : 0;
|
||||
if (_tri_buf_size[tri] < len)
|
||||
{
|
||||
_tri_buf_size[tri] = len;
|
||||
if (_tri_buf[tri] != nullptr) { heap_caps_free(_tri_buf[tri]); }
|
||||
auto tmp = (int16_t*)heap_caps_malloc(len, MALLOC_CAP_8BIT);
|
||||
_tri_buf[tri] = tmp;
|
||||
if (tmp == nullptr)
|
||||
{
|
||||
_tri_buf_size[tri] = 0;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
memcpy(_tri_buf[tri], src_data, len);
|
||||
_tri_index = tri;
|
||||
return _tri_buf[tri];
|
||||
}
|
||||
|
||||
void audio_data_callback(const uint8_t *data, uint32_t length) override
|
||||
{
|
||||
// Reduce memory requirements by dividing the received data into the first and second halves.
|
||||
length >>= 1;
|
||||
M5.Speaker.playRaw(get_next_buf( data , length), length >> 1, _sample_rate, true, 1, m5spk_virtual_channel);
|
||||
M5.Speaker.playRaw(get_next_buf(&data[length], length), length >> 1, _sample_rate, true, 1, m5spk_virtual_channel);
|
||||
_export_index = _tri_index;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#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 size_t WAVE_SIZE = 320;
|
||||
static BluetoothA2DPSink_M5Speaker a2dp_sink = { &M5.Speaker, m5spk_virtual_channel };
|
||||
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;
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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->setCursor(0, 8);
|
||||
gfx->print("BT A2DP : ");
|
||||
gfx->println(bt_device_name);
|
||||
gfx->setTextWrap(false);
|
||||
gfx->fillRect(0, 6, gfx->width(), 2, TFT_BLACK);
|
||||
|
||||
header_height = (gfx->height() > 80) ? 45 : 21;
|
||||
fft_enabled = !gfx->isEPD();
|
||||
if (fft_enabled)
|
||||
{
|
||||
wave_enabled = (gfx->getBoard() != m5gfx::board_M5UnitLCD);
|
||||
|
||||
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)
|
||||
{
|
||||
auto bits = a2dp_sink.getMetaUpdateInfo();
|
||||
if (bits)
|
||||
{
|
||||
gfx->startWrite();
|
||||
for (int id = 0; id < a2dp_sink.metatext_num; ++id)
|
||||
{
|
||||
if (0 == (bits & (1<<id))) { continue; }
|
||||
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(a2dp_sink.getMetaData(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 (a2dp_sink.getMetaUpdateInfo())
|
||||
{
|
||||
gfx->fillRect(0, 8, gfx->width(), 12, TFT_BLACK);
|
||||
a2dp_sink.clearMetaUpdateInfo();
|
||||
title_x = 4;
|
||||
title_id = 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 = a2dp_sink.getMetaData(tid, false);
|
||||
if (meta[0] != 0)
|
||||
{
|
||||
gfx->print(meta);
|
||||
gfx->print(" / ");
|
||||
tx = gfx->getCursorX();
|
||||
if (++tid == a2dp_sink.metatext_num) { tid = 0; }
|
||||
if (tx <= 4)
|
||||
{
|
||||
title_x = tx;
|
||||
title_id = tid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((no_data_bits |= 1 << tid) == ((1 << a2dp_sink.metatext_num) - 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (++tid == a2dp_sink.metatext_num) { tid = 0; }
|
||||
}
|
||||
} while (tx < gfx->width());
|
||||
--title_x;
|
||||
gfx->display();
|
||||
gfx->endWrite();
|
||||
}
|
||||
}
|
||||
if (!gfx->displayBusy())
|
||||
{ // draw volume bar
|
||||
static int px;
|
||||
uint8_t v = M5.Speaker.getVolume();
|
||||
int x = v * (gfx->width()) >> 8;
|
||||
if (px != x)
|
||||
{
|
||||
gfx->fillRect(x, 6, px - x, 2, px < x ? 0xAAFFAAu : 0u);
|
||||
gfx->display();
|
||||
px = x;
|
||||
}
|
||||
}
|
||||
|
||||
if (fft_enabled && !gfx->displayBusy())
|
||||
{
|
||||
static int prev_x[2];
|
||||
static int peak_x[2];
|
||||
static bool prev_conn;
|
||||
bool connected = a2dp_sink.is_connected();
|
||||
if (prev_conn != connected)
|
||||
{
|
||||
prev_conn = connected;
|
||||
if (!connected)
|
||||
{
|
||||
a2dp_sink.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto buf = a2dp_sink.getBuffer();
|
||||
if (buf)
|
||||
{
|
||||
memcpy(raw_data, buf, WAVE_SIZE * 2 * sizeof(int16_t)); // stereo data copy
|
||||
gfx->startWrite();
|
||||
|
||||
// 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; }
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
// If you want to play sound from ModuleDisplay, write this
|
||||
// cfg.external_speaker.module_display = true;
|
||||
|
||||
// If you want to play sound from ModuleRCA, write this
|
||||
// cfg.external_speaker.module_rca = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker, write this
|
||||
cfg.external_speaker.hat_spk = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker2, write this
|
||||
// cfg.external_speaker.hat_spk2 = true;
|
||||
|
||||
// If you want to play sound from ATOMIC Speaker, write this
|
||||
cfg.external_speaker.atomic_spk = true;
|
||||
|
||||
M5.begin(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 = 96000; // default:64000 (64kHz) e.g. 48000 , 50000 , 80000 , 96000 , 100000 , 128000 , 144000 , 192000 , 200000
|
||||
spk_cfg.task_pinned_core = APP_CPU_NUM;
|
||||
// spk_cfg.task_priority = configMAX_PRIORITIES - 2;
|
||||
spk_cfg.dma_buf_count = 20;
|
||||
// spk_cfg.dma_buf_len = 512;
|
||||
M5.Speaker.config(spk_cfg);
|
||||
}
|
||||
|
||||
|
||||
M5.Speaker.begin();
|
||||
|
||||
a2dp_sink.start(bt_device_name, false);
|
||||
|
||||
gfxSetup(&M5.Display);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
gfxLoop(&M5.Display);
|
||||
|
||||
{
|
||||
static int prev_frame;
|
||||
int frame;
|
||||
do
|
||||
{
|
||||
vTaskDelay(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.wasDecideClickCount())
|
||||
{
|
||||
switch (M5.BtnA.getClickCount())
|
||||
{
|
||||
case 1:
|
||||
M5.Speaker.tone(1000, 100);
|
||||
a2dp_sink.next();
|
||||
break;
|
||||
|
||||
case 2:
|
||||
M5.Speaker.tone(800, 100);
|
||||
a2dp_sink.previous();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (M5.BtnA.isHolding() || M5.BtnB.isPressed() || M5.BtnC.isPressed())
|
||||
{
|
||||
size_t v = M5.Speaker.getVolume();
|
||||
int add = (M5.BtnB.isPressed()) ? -1 : 1;
|
||||
if (M5.BtnA.isHolding())
|
||||
{
|
||||
add = M5.BtnA.getClickCount() ? -1 : 1;
|
||||
}
|
||||
v += add;
|
||||
if (v <= 255)
|
||||
{
|
||||
M5.Speaker.setVolume(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,511 @@
|
|||
#include <SD.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <math.h>
|
||||
|
||||
/// need ESP8266Audio library. ( URL : https://github.com/earlephilhower/ESP8266Audio/ )
|
||||
#include <AudioOutput.h>
|
||||
#include <AudioFileSourceSD.h>
|
||||
#include <AudioFileSourceID3.h>
|
||||
#include <AudioGeneratorMP3.h>
|
||||
|
||||
#include <M5UnitLCD.h>
|
||||
#include <M5UnitOLED.h>
|
||||
#include <M5Unified.h>
|
||||
|
||||
/// set M5Speaker virtual channel (0-7)
|
||||
static constexpr uint8_t m5spk_virtual_channel = 0;
|
||||
|
||||
/// set your mp3 filename
|
||||
static constexpr const char* filename[] =
|
||||
{
|
||||
"/mp3/file01.mp3",
|
||||
"/mp3/file02.mp3",
|
||||
"/mp3/file03.mp3",
|
||||
"/mp3/file04.mp3",
|
||||
};
|
||||
static constexpr const size_t filecount = sizeof(filename) / sizeof(filename[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;
|
||||
}
|
||||
}
|
||||
virtual bool stop(void) override
|
||||
{
|
||||
flush();
|
||||
_m5sound->stop(_virtual_ch);
|
||||
return true;
|
||||
}
|
||||
|
||||
const int16_t* getBuffer(void) const { return _tri_buffer[(_tri_index + 2) % 3]; }
|
||||
|
||||
protected:
|
||||
m5::Speaker_Class* _m5sound;
|
||||
uint8_t _virtual_ch;
|
||||
static constexpr size_t tri_buf_size = 1536;
|
||||
int16_t _tri_buffer[3][tri_buf_size];
|
||||
size_t _tri_buffer_index = 0;
|
||||
size_t _tri_index = 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 size_t WAVE_SIZE = 320;
|
||||
static AudioFileSourceSD file;
|
||||
static AudioOutputM5Speaker out(&M5.Speaker, m5spk_virtual_channel);
|
||||
static AudioGeneratorMP3 mp3;
|
||||
static AudioFileSourceID3* id3 = 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 fileindex = 0;
|
||||
|
||||
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
|
||||
{
|
||||
(void)cbData;
|
||||
if (string[0] == 0) { return; }
|
||||
if (strcmp(type, "eof") == 0)
|
||||
{
|
||||
M5.Display.display();
|
||||
return;
|
||||
}
|
||||
int y = M5.Display.getCursorY();
|
||||
if (y+1 >= header_height) { return; }
|
||||
M5.Display.fillRect(0, y, M5.Display.width(), 12, M5.Display.getBaseColor());
|
||||
M5.Display.printf("%s: %s", type, string);
|
||||
M5.Display.setCursor(0, y+12);
|
||||
}
|
||||
|
||||
void stop(void)
|
||||
{
|
||||
if (id3 == nullptr) return;
|
||||
out.stop();
|
||||
mp3.stop();
|
||||
id3->RegisterMetadataCB(nullptr, nullptr);
|
||||
id3->close();
|
||||
file.close();
|
||||
delete id3;
|
||||
id3 = nullptr;
|
||||
}
|
||||
|
||||
void play(const char* fname)
|
||||
{
|
||||
if (id3 != nullptr) { stop(); }
|
||||
M5.Display.setCursor(0, 8);
|
||||
file.open(fname);
|
||||
id3 = new AudioFileSourceID3(&file);
|
||||
id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG");
|
||||
id3->open(fname);
|
||||
mp3.begin(id3, &out);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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->setCursor(0, 8);
|
||||
gfx->println("MP3 player");
|
||||
gfx->setTextWrap(false);
|
||||
gfx->fillRect(0, 6, gfx->width(), 2, TFT_BLACK);
|
||||
|
||||
header_height = 45;
|
||||
fft_enabled = !gfx->isEPD();
|
||||
if (fft_enabled)
|
||||
{
|
||||
wave_enabled = (gfx->getBoard() != m5gfx::board_M5UnitLCD);
|
||||
|
||||
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 (!gfx->displayBusy())
|
||||
{ // draw volume bar
|
||||
static int px;
|
||||
uint8_t v = M5.Speaker.getVolume();
|
||||
int x = v * (gfx->width()) >> 8;
|
||||
if (px != x)
|
||||
{
|
||||
gfx->fillRect(x, 6, px - x, 2, px < x ? 0xAAFFAAu : 0u);
|
||||
gfx->display();
|
||||
px = x;
|
||||
}
|
||||
}
|
||||
|
||||
if (fft_enabled && !gfx->displayBusy() && M5.Speaker.isPlaying(m5spk_virtual_channel) > 1)
|
||||
{
|
||||
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();
|
||||
|
||||
// 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; }
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
// If you want to play sound from ModuleDisplay, write this
|
||||
// cfg.external_speaker.module_display = true;
|
||||
|
||||
// If you want to play sound from ModuleRCA, write this
|
||||
// cfg.external_speaker.module_rca = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker, write this
|
||||
cfg.external_speaker.hat_spk = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker2, write this
|
||||
// cfg.external_speaker.hat_spk2 = true;
|
||||
|
||||
// If you want to play sound from ATOMIC Speaker, write this
|
||||
cfg.external_speaker.atomic_spk = true;
|
||||
|
||||
M5.begin(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 = 96000; // default:64000 (64kHz) e.g. 48000 , 50000 , 80000 , 96000 , 100000 , 128000 , 144000 , 192000 , 200000
|
||||
M5.Speaker.config(spk_cfg);
|
||||
}
|
||||
|
||||
|
||||
M5.Speaker.begin();
|
||||
|
||||
while (false == SD.begin(GPIO_NUM_4, SPI, 25000000))
|
||||
{
|
||||
M5.delay(500);
|
||||
}
|
||||
|
||||
gfxSetup(&M5.Display);
|
||||
|
||||
play(filename[fileindex]);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
gfxLoop(&M5.Display);
|
||||
|
||||
if (mp3.isRunning())
|
||||
{
|
||||
if (!mp3.loop()) { mp3.stop(); }
|
||||
}
|
||||
else
|
||||
{
|
||||
M5.delay(1);
|
||||
}
|
||||
|
||||
M5.update();
|
||||
if (M5.BtnA.wasClicked())
|
||||
{
|
||||
M5.Speaker.tone(1000, 100);
|
||||
stop();
|
||||
if (++fileindex >= filecount) { fileindex = 0; }
|
||||
play(filename[fileindex]);
|
||||
}
|
||||
else
|
||||
if (M5.BtnA.isHolding() || M5.BtnB.isPressed() || M5.BtnC.isPressed())
|
||||
{
|
||||
size_t v = M5.Speaker.getVolume();
|
||||
if (M5.BtnB.isPressed()) { --v; } else { ++v; }
|
||||
if (v <= 255 || M5.BtnA.isHolding())
|
||||
{
|
||||
M5.Speaker.setVolume(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
777
libraries/M5Unified/examples/Advanced/Mic_FFT/Mic_FFT.ino
Normal file
777
libraries/M5Unified/examples/Advanced/Mic_FFT/Mic_FFT.ino
Normal file
|
|
@ -0,0 +1,777 @@
|
|||
// External displays can be enabled if necessary
|
||||
// #include <M5ModuleDisplay.h>
|
||||
// #include <M5AtomDisplay.h>
|
||||
// #include <M5UnitGLASS2.h>
|
||||
// #include <M5UnitOLED.h>
|
||||
// #include <M5UnitLCD.h>
|
||||
|
||||
#include <M5Unified.h>
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
static void* memory_alloc(size_t size)
|
||||
{
|
||||
return heap_caps_malloc(size, MALLOC_CAP_8BIT);
|
||||
}
|
||||
|
||||
static void memory_free(void* ptr)
|
||||
{
|
||||
heap_caps_free(ptr);
|
||||
}
|
||||
|
||||
struct wav_data_t {
|
||||
int16_t* wav = nullptr;
|
||||
size_t length = 0;
|
||||
size_t latest_index = 0;
|
||||
|
||||
size_t searchEdge(size_t offset, size_t search_length) const {
|
||||
int mem_position = latest_index + offset;
|
||||
if (mem_position >= length) { mem_position -= length; }
|
||||
int mem_difference = 0;
|
||||
int position = - 1;
|
||||
uint32_t counter[2] = { 0,0 };
|
||||
bool prev_sign = false;
|
||||
|
||||
for (size_t i = 0; i < search_length; ++i) {
|
||||
size_t idx = latest_index + i;
|
||||
if (idx >= length) { idx -= length; }
|
||||
int value = wav[idx];
|
||||
bool sign = value < 0;
|
||||
if (prev_sign != sign) {
|
||||
prev_sign = sign;
|
||||
if (sign) { // When changing from positive to negative
|
||||
if (position >= 0) {
|
||||
int diff = counter[0] + counter[1];
|
||||
if (mem_difference < diff) {
|
||||
mem_difference = diff;
|
||||
mem_position = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
counter[sign] = 0;
|
||||
if (i >= offset) {
|
||||
int pidx = (idx ? idx : length) - 1;
|
||||
int cv = abs(wav[idx]);
|
||||
int pv = abs(wav[pidx]);
|
||||
position = (cv < pv) ? idx : pidx;
|
||||
}
|
||||
}
|
||||
uint32_t v = value * value;
|
||||
counter[sign] += v >> 7;
|
||||
}
|
||||
mem_position -= offset;
|
||||
if (mem_position < 0) { mem_position += length; }
|
||||
return mem_position;
|
||||
}
|
||||
};
|
||||
|
||||
struct fft_data_t {
|
||||
float *fdata = nullptr;
|
||||
size_t length = 0;
|
||||
size_t sample_rate;
|
||||
|
||||
wav_data_t *wav_data = nullptr;
|
||||
uint8_t fft_size_bits = 0;
|
||||
|
||||
float getDataByPixel(uint16_t x, uint16_t width) const {
|
||||
if (length <= width) {
|
||||
int index = x * length / width;
|
||||
if (index >= length) { index = length - 1; }
|
||||
return fdata[index];
|
||||
}
|
||||
int index0 = x * length / width;
|
||||
int index1 = (x + 1) * length / width;
|
||||
if (index0 >= length) { index0 = length - 1; }
|
||||
if (index1 >= length) { index1 = length - 1; }
|
||||
float value = 0;
|
||||
for (int i = index0; i < index1; ++i) {
|
||||
if (value < fdata[i]) {
|
||||
value = fdata[i];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
class fft_function_t {
|
||||
public:
|
||||
bool setup(uint8_t max_fft_size_bits)
|
||||
{
|
||||
close();
|
||||
int FFT_SIZE = 1 << max_fft_size_bits;
|
||||
int need_size = 1+(FFT_SIZE * 3 / 4) * sizeof(float) + (FFT_SIZE * sizeof(uint16_t));
|
||||
_work_area = memory_alloc(need_size);
|
||||
if (_work_area == nullptr) {
|
||||
return false;
|
||||
}
|
||||
_max_fft_size_bits = max_fft_size_bits;
|
||||
_initialized_fft_size_bits = 0;
|
||||
|
||||
fi = (float*)memory_alloc(sizeof(float) * FFT_SIZE + 1);
|
||||
fr = (float*)memory_alloc(sizeof(float) * FFT_SIZE + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void close(void)
|
||||
{
|
||||
if (_work_area) {
|
||||
memory_free(_work_area);
|
||||
_work_area = nullptr;
|
||||
}
|
||||
_max_fft_size_bits = 0;
|
||||
_initialized_fft_size_bits = 0;
|
||||
}
|
||||
|
||||
__attribute((optimize("-O3")))
|
||||
bool update(fft_data_t* fft_data)
|
||||
{
|
||||
if (_work_area == nullptr || fft_data == nullptr || fft_data->fdata == nullptr || fft_data->fft_size_bits == 0) {
|
||||
return false;
|
||||
}
|
||||
if (_initialized_fft_size_bits != fft_data->fft_size_bits) {
|
||||
if (!_init(fft_data->fft_size_bits)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int FFT_SIZE = 1 << fft_data->fft_size_bits;
|
||||
|
||||
uint16_t *br = (uint16_t*)_work_area;
|
||||
{
|
||||
auto src = fft_data->wav_data->wav;
|
||||
for (int i = 0; i < FFT_SIZE; ++i)
|
||||
{
|
||||
float lv = src[i];
|
||||
fr[br[i]] = lv;
|
||||
}
|
||||
memset(fi, 0, sizeof(float) * FFT_SIZE);
|
||||
}
|
||||
int s4 = FFT_SIZE / 4;
|
||||
|
||||
float *wi = (float*) (&br[FFT_SIZE]);
|
||||
size_t s = 1;
|
||||
size_t i = 0;
|
||||
size_t je = FFT_SIZE;
|
||||
do
|
||||
{
|
||||
size_t ke = s;
|
||||
s <<= 1;
|
||||
je >>= 1;
|
||||
size_t j = 0;
|
||||
do
|
||||
{
|
||||
size_t k = 0;
|
||||
size_t m = ke * ((j << 1) + 1);
|
||||
size_t l = s * j;
|
||||
auto frm_p = &fr[m];
|
||||
auto fim_p = &fi[m];
|
||||
auto frl_p = &fr[l];
|
||||
auto fil_p = &fi[l];
|
||||
auto wi_p = &wi[0];
|
||||
auto wr_p = &wi[s4];
|
||||
do
|
||||
{
|
||||
// size_t p = je * k;
|
||||
float wi = *wi_p;
|
||||
float wr = *wr_p;
|
||||
float frm = *frm_p;
|
||||
float fim = *fim_p;
|
||||
float Wxmr = frm * wr + fim * wi;
|
||||
float Wxmi = fim * wr - frm * wi;
|
||||
float frl = *frl_p;
|
||||
float fil = *fil_p;
|
||||
*frm_p++ = frl - Wxmr;
|
||||
*frl_p++ = frl + Wxmr;
|
||||
*fim_p++ = fil - Wxmi;
|
||||
*fil_p++ = fil + Wxmi;
|
||||
wi_p += je;
|
||||
wr_p += je;
|
||||
} while ( ++k < ke);
|
||||
} while ( ++j < je );
|
||||
} while ( ++i < _initialized_fft_size_bits );
|
||||
|
||||
|
||||
int loop_end = fft_data->length;
|
||||
if (loop_end > FFT_SIZE) {
|
||||
loop_end = FFT_SIZE;
|
||||
}
|
||||
|
||||
float max_value = 0.0f;
|
||||
for (uint32_t i = 0; i < loop_end; ++i) {
|
||||
float vr = fr[i];
|
||||
float vi = fi[i];
|
||||
float v = sqrtf(vr * vr + vi * vi);
|
||||
fft_data->fdata[i] = v;
|
||||
if (max_value < v) { max_value = v; }
|
||||
}
|
||||
|
||||
// peak level adjust
|
||||
float k = (65536.0f) / max_value;
|
||||
if (k > 0.03125f) { k = 0.03125f; } else
|
||||
{
|
||||
for (uint32_t i = 0; i < loop_end; ++i) {
|
||||
float tmp = fft_data->fdata[i];
|
||||
tmp *= k;
|
||||
fft_data->fdata[i] = tmp;
|
||||
}
|
||||
}
|
||||
//*/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
bool _init(uint8_t size_bits)
|
||||
{
|
||||
if (_max_fft_size_bits < size_bits) { return false; }
|
||||
_initialized_fft_size_bits = size_bits;
|
||||
|
||||
uint16_t *br = (uint16_t*)_work_area;
|
||||
|
||||
int FFT_SIZE = 1 << size_bits;
|
||||
float *wi = (float*) (&br[FFT_SIZE]);
|
||||
|
||||
size_t je = 1;
|
||||
br[0] = 0;
|
||||
br[1] = FFT_SIZE >> 1;
|
||||
for ( size_t i = 0 ; i < size_bits - 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];
|
||||
}
|
||||
}
|
||||
|
||||
float omega = 2.0f * M_PI / FFT_SIZE;
|
||||
int s2 = FFT_SIZE >> 1;
|
||||
int s4 = FFT_SIZE >> 2;
|
||||
wi[0] = 0;
|
||||
wi[s2] = 0;
|
||||
wi[s4] = 1;
|
||||
for ( int i = 1 ; i < s4 ; ++i)
|
||||
{
|
||||
float f = cosf(omega * i);
|
||||
wi[s4 + i] = f;
|
||||
wi[s4 - i] = f;
|
||||
wi[s4+s2 - i] = -f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
float* fi;
|
||||
float* fr;
|
||||
void* _work_area = nullptr;
|
||||
uint8_t _max_fft_size_bits = 0;
|
||||
uint8_t _initialized_fft_size_bits;
|
||||
};
|
||||
|
||||
|
||||
struct rect_t
|
||||
{
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t w;
|
||||
int16_t h;
|
||||
};
|
||||
|
||||
|
||||
class wav_drawer_t
|
||||
{
|
||||
LGFX_Device* _gfx = nullptr;
|
||||
int16_t* prev_y = nullptr;
|
||||
int16_t* prev_h = nullptr;
|
||||
rect_t draw_rect = {0, 0, 0, 0};
|
||||
uint32_t bg_color = 0x000000u;
|
||||
uint32_t fg_color = 0xFFFFFFu;
|
||||
uint32_t line_color = 0x303030u;
|
||||
|
||||
public:
|
||||
bool setup(LGFX_Device* gfx, const rect_t& rect)
|
||||
{
|
||||
if (gfx == nullptr) { return false; }
|
||||
_gfx = gfx;
|
||||
draw_rect = rect;
|
||||
gfx->fillRect(rect.x, rect.y, rect.w, rect.h, bg_color);
|
||||
gfx->drawFastVLine(rect.x + (rect.w >> 1), rect.y, rect.h, line_color);
|
||||
int width = rect.w;
|
||||
prev_y = (int16_t*)memory_alloc(width * sizeof(int16_t));
|
||||
prev_h = (int16_t*)memory_alloc(width * sizeof(int16_t));
|
||||
memset(prev_y, 0, width * sizeof(int16_t));
|
||||
memset(prev_h, 0, width * sizeof(int16_t));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update(const wav_data_t& wav_data)
|
||||
{
|
||||
auto gfx = _gfx;
|
||||
|
||||
int32_t width = draw_rect.w;
|
||||
int32_t height = draw_rect.h;
|
||||
|
||||
int wav_count = wav_data.length;
|
||||
int wav_index = wav_data.searchEdge(draw_rect.w >> 1, wav_count / 3);
|
||||
auto wav = wav_data.wav;
|
||||
|
||||
int32_t max_value = 1;
|
||||
auto wi = wav_index;
|
||||
for (int i = 0; i < width; ++i)
|
||||
{
|
||||
int32_t tmp = abs(wav[wi]);
|
||||
if (max_value < tmp) { max_value = tmp; }
|
||||
if (++wi >= wav_count) { wi = 0; }
|
||||
}
|
||||
int new_k = (draw_rect.h << 15) / max_value;
|
||||
if (new_k > 65536)
|
||||
{ new_k = 65536; }
|
||||
static int k;
|
||||
if (k > new_k) { k = new_k; }
|
||||
else {
|
||||
k = (k * 127 + new_k) >> 7;
|
||||
}
|
||||
int32_t value1 = (32768-wav[wav_index] * k) >> 16;
|
||||
int32_t value2 = value1;
|
||||
int32_t base_y = draw_rect.y + (height >> 1);
|
||||
|
||||
for (int i = 0; i < width; ++i)
|
||||
{
|
||||
if (++wav_index >= wav_count) { wav_index = 0; }
|
||||
int32_t x = i + draw_rect.x;
|
||||
int32_t y = prev_y[i];
|
||||
int32_t h = prev_h[i];
|
||||
|
||||
gfx->setColor(i == (width >> 1) ? line_color : bg_color);
|
||||
gfx->drawFastVLine(x, base_y + y, h);
|
||||
|
||||
int32_t value0 = value1;
|
||||
value1 = value2;
|
||||
value2 = (32768-wav[wav_index] * k) >> 16;
|
||||
int32_t value_01 = (value0 + value1) >> 1;
|
||||
int32_t value_12 = (value1 + value2) >> 1;
|
||||
|
||||
int32_t y_min = (value_01 < value_12) ? value_01 : value_12;
|
||||
if (y_min > value1) { y_min = value1; }
|
||||
|
||||
int32_t y_max = (value_01 > value_12) ? value_01 : value_12;
|
||||
if (y_max < value1) { y_max = value1; }
|
||||
|
||||
y = y_min;
|
||||
h = y_max + 1 - y;
|
||||
prev_y[i] = y;
|
||||
prev_h[i] = h;
|
||||
gfx->drawPixel(x, base_y, line_color);
|
||||
/// draw new wave.
|
||||
gfx->drawFastVLine(x, base_y + y, h, fg_color);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class fft_drawer_t
|
||||
{
|
||||
LGFX_Device* _gfx = nullptr;
|
||||
|
||||
uint16_t* prev_y = nullptr;
|
||||
uint16_t* prev_h = nullptr;
|
||||
rect_t draw_rect = {0, 0, 0, 0};
|
||||
uint32_t bg_color = 0x000066u;
|
||||
uint32_t fg_color = 0x00FF00u;
|
||||
|
||||
public:
|
||||
bool setup(LGFX_Device* gfx, const rect_t& rect)
|
||||
{
|
||||
if (gfx == nullptr) { return false; }
|
||||
_gfx = gfx;
|
||||
draw_rect = rect;
|
||||
gfx->fillRect(rect.x, rect.y, rect.w, rect.h, bg_color);
|
||||
int width = rect.w;
|
||||
|
||||
prev_y = (uint16_t*)memory_alloc(width * sizeof(int16_t));
|
||||
prev_h = (uint16_t*)memory_alloc(width * sizeof(int16_t));
|
||||
memset(prev_y, 0, width * sizeof(int16_t));
|
||||
memset(prev_h, 0, width * sizeof(int16_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update(const fft_data_t& fft_data)
|
||||
{
|
||||
auto gfx = _gfx;
|
||||
|
||||
int32_t width = draw_rect.w;
|
||||
int32_t height = draw_rect.h - 1;
|
||||
|
||||
int32_t value1 = height - (((int32_t)(fft_data.getDataByPixel(0, width) * height)) >> 16);
|
||||
if (value1 < 0) { value1 = 0; }
|
||||
int32_t value2 = value1;
|
||||
|
||||
for (int i = 0; i < width; ++i)
|
||||
{
|
||||
int32_t x = i + draw_rect.x;
|
||||
int32_t y = prev_y[i];
|
||||
int32_t h = prev_h[i];
|
||||
|
||||
gfx->drawFastVLine(x, draw_rect.y + y, h, bg_color);
|
||||
|
||||
int32_t value0 = value1;
|
||||
value1 = value2;
|
||||
value2 = height - (((int32_t)(fft_data.getDataByPixel(i+1, width) * height)) >> 16);
|
||||
if (value2 < 0) { value2 = 0; }
|
||||
|
||||
int32_t value_01 = (value0 + value1) >> 1;
|
||||
int32_t value_12 = (value1 + value2) >> 1;
|
||||
|
||||
int32_t y_min = (value_01 < value_12) ? value_01 : value_12;
|
||||
if (y_min > value1) { y_min = value1; }
|
||||
|
||||
int32_t y_max = (value_01 > value_12) ? value_01 : value_12;
|
||||
if (y_max < value1) { y_max = value1; }
|
||||
|
||||
y = y_min;
|
||||
h = y_max + 1 - y;
|
||||
prev_y[i] = y;
|
||||
prev_h[i] = h;
|
||||
|
||||
gfx->drawFastVLine(x, draw_rect.y + y, h, fg_color);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class fft_peak_t
|
||||
{
|
||||
LGFX_Device* _gfx = nullptr;
|
||||
|
||||
rect_t draw_rect = {0, 0, 0, 0};
|
||||
uint32_t bg_color = 0x000000u;
|
||||
uint32_t fg_color = 0x00FFFFu;
|
||||
std::set<uint16_t> peak_index_set;
|
||||
uint16_t prev_peak_index = UINT16_MAX;
|
||||
char text_buf[10] = {0,};
|
||||
char prev_text[10] = {0,};
|
||||
uint8_t step = 0;
|
||||
public:
|
||||
bool setup(LGFX_Device* gfx, const rect_t& rect)
|
||||
{
|
||||
if (gfx == nullptr) { return false; }
|
||||
_gfx = gfx;
|
||||
draw_rect = rect;
|
||||
gfx->fillRect(rect.x, rect.y, rect.w, rect.h, bg_color);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update(const fft_data_t& fft_data)
|
||||
{
|
||||
auto gfx = _gfx;
|
||||
|
||||
auto fdata = fft_data.fdata;
|
||||
|
||||
int32_t loop_end = fft_data.length;
|
||||
|
||||
auto boarder_value = (uint16_t*)memory_alloc(sizeof(uint16_t) * loop_end);
|
||||
memset(boarder_value, 0, sizeof(uint16_t) * loop_end);
|
||||
{
|
||||
float thresh = 0;
|
||||
for (int i = 0; i < loop_end; ++i)
|
||||
{
|
||||
float value = fdata[i];
|
||||
if (thresh < value) {
|
||||
thresh = value;
|
||||
}
|
||||
boarder_value[i] = thresh;
|
||||
thresh = thresh * 0.9f;
|
||||
}
|
||||
for (int i = loop_end - 1; i >= 0; --i)
|
||||
{
|
||||
int value = fdata[i];
|
||||
if (thresh < value) {
|
||||
thresh = value;
|
||||
}
|
||||
if (boarder_value[i] < thresh) {
|
||||
boarder_value[i] = thresh;
|
||||
}
|
||||
thresh = thresh * 0.9f;
|
||||
}
|
||||
}
|
||||
static constexpr const size_t peak_index_set_size = 8;
|
||||
std::multimap<float, uint16_t> peak_map;
|
||||
for (int i = 0; i < loop_end; ++i)
|
||||
{
|
||||
float value = fdata[i];
|
||||
if (value >= boarder_value[i]) {
|
||||
peak_map.insert(std::make_pair(value, i));
|
||||
if (peak_map.size() > peak_index_set_size) {
|
||||
peak_map.erase(peak_map.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
memory_free(boarder_value);
|
||||
if (peak_map.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
auto c = text_buf[step];
|
||||
if (prev_text[step] != c) {
|
||||
prev_text[step] = c;
|
||||
gfx->drawChar(c, draw_rect.x + (step * draw_rect.w >> 3), draw_rect.y);
|
||||
break;
|
||||
}
|
||||
if (++step >= 8)
|
||||
{
|
||||
step = 0;
|
||||
uint16_t peak_index = peak_map.rbegin()->second;
|
||||
if (prev_peak_index != peak_index) {
|
||||
prev_peak_index = peak_index;
|
||||
gfx->setTextDatum(m5gfx::datum_t::top_right);
|
||||
float freq = (float)(fft_data.sample_rate * peak_index) / (float)(1<<fft_data.fft_size_bits);
|
||||
snprintf(text_buf, sizeof(text_buf), "%8.1f", freq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int width = draw_rect.w;
|
||||
int icon_size = peak_index_set_size;
|
||||
for (auto peak_index : peak_index_set)
|
||||
{
|
||||
int x = draw_rect.x + (peak_index * width / fft_data.length);
|
||||
int y = draw_rect.y + draw_rect.h - icon_size - 1;
|
||||
gfx->fillRect(x - icon_size, y, icon_size * 2 + 1, icon_size+1, bg_color);
|
||||
}
|
||||
peak_index_set.clear();
|
||||
|
||||
for (auto it = peak_map.rbegin(); it != peak_map.rend(); ++it)
|
||||
{
|
||||
int peak_index = it->second;
|
||||
int x = draw_rect.x + (peak_index * width / fft_data.length);
|
||||
int y = draw_rect.y + draw_rect.h - 1;
|
||||
gfx->drawTriangle(x, y, x - icon_size, y - icon_size, x + icon_size, y - icon_size, fg_color);
|
||||
peak_index_set.insert(peak_index);
|
||||
icon_size -= 2;
|
||||
if (icon_size <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class fft_history_t
|
||||
{
|
||||
LGFX_Device* _gfx = nullptr;
|
||||
|
||||
uint8_t* color_map = nullptr;
|
||||
rect_t draw_rect = {0, 0, 0, 0};
|
||||
uint32_t bg_color = 0x000033u;
|
||||
uint32_t fg_color = 0xFFFF00u;
|
||||
int step = 0;
|
||||
public:
|
||||
bool setup(LGFX_Device* gfx, const rect_t& rect)
|
||||
{
|
||||
if (gfx == nullptr) { return false; }
|
||||
_gfx = gfx;
|
||||
draw_rect = rect;
|
||||
int width = rect.w;
|
||||
int height = rect.h;
|
||||
|
||||
color_map = (uint8_t*)memory_alloc(width * height * sizeof(uint8_t));
|
||||
memset(color_map, 0, width * height * sizeof(uint8_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update(const fft_data_t& fft_data)
|
||||
{
|
||||
int32_t width = draw_rect.w;
|
||||
int32_t height = draw_rect.h;
|
||||
|
||||
if (--step < 0) {
|
||||
step = 7;
|
||||
memmove(&color_map[width], color_map, width * (height - 1));
|
||||
memset(color_map, 0, width);
|
||||
}
|
||||
for (int i = 0; i < width; ++i)
|
||||
{
|
||||
int v = fft_data.getDataByPixel(i, width) / 256.0f;
|
||||
if (v > 255) { v = 255; }
|
||||
color_map[i] = color_map[i] > v ? color_map[i] : v;
|
||||
}
|
||||
int y0 = ( step * draw_rect.h) >> 3;
|
||||
int y1 = ((step + 1) * draw_rect.h) >> 3;
|
||||
_gfx->pushGrayscaleImage(draw_rect.x, draw_rect.y + y0, draw_rect.w, y1 - y0, &color_map[y0*width], m5gfx::color_depth_t::grayscale_8bit, fg_color, bg_color);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// The higher the sample rate, the higher the frequency results obtained by FFT.
|
||||
// If limited to the audible range, 24kHz to 48kHz is sufficient.
|
||||
static constexpr const size_t SAMPLE_RATE = 24000;
|
||||
// static constexpr const size_t SAMPLE_RATE = 96000;
|
||||
|
||||
// The larger the FFT_BITS, the higher the accuracy of the FFT, but the processing load also increases
|
||||
static constexpr const size_t FFT_BITS = 10;
|
||||
|
||||
// WAVE_BLOCK_SIZE is the size of one processing when capturing data from I2S.
|
||||
// If it is too large, the loop cycle will be slow and the frequency of drawing updates will decrease.
|
||||
// If it is too small, recoding interruptions will occur.
|
||||
// For example, if the sample rate is 96kHz and the block size is 384, data will be captured every 4ms.
|
||||
// Therefore, the drawing process, FFT process, and other loop iterations must be completed within 4ms.
|
||||
static constexpr const size_t WAVE_BLOCK_SIZE = 256;
|
||||
|
||||
static constexpr const size_t FFT_SIZE = 1u << FFT_BITS;
|
||||
static constexpr const size_t WAVE_BLOCK_COUNT = 3 + FFT_SIZE / WAVE_BLOCK_SIZE;
|
||||
static constexpr const size_t WAVE_TOTAL_SIZE = WAVE_BLOCK_SIZE * WAVE_BLOCK_COUNT;
|
||||
static fft_function_t fft_function;
|
||||
static fft_data_t fft_data;
|
||||
static wav_data_t wav_data;
|
||||
static wav_drawer_t wav_drawer;
|
||||
static fft_drawer_t fft_drawer;
|
||||
static fft_peak_t fft_peak;
|
||||
static fft_history_t fft_history;
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
#if defined ( __M5GFX_M5MODULEDISPLAY__ )
|
||||
cfg.module_display.logical_width = 320;
|
||||
cfg.module_display.logical_height = 180;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5ATOMDISPLAY__ )
|
||||
cfg.atom_display.logical_width = 320;
|
||||
cfg.atom_display.logical_height = 180;
|
||||
#endif
|
||||
|
||||
// use for ATOMIC ECHO BASE
|
||||
cfg.external_speaker.atomic_echo = true;
|
||||
|
||||
M5.begin(cfg);
|
||||
M5.setPrimaryDisplayType({m5gfx::board_M5ModuleDisplay, m5gfx::board_M5AtomDisplay});
|
||||
M5.Speaker.tone(440, 100);
|
||||
M5.delay(100);
|
||||
{
|
||||
auto cfg = M5.Mic.config();
|
||||
cfg.dma_buf_count = 3;
|
||||
cfg.dma_buf_len = WAVE_BLOCK_SIZE;
|
||||
cfg.over_sampling = 1;
|
||||
cfg.noise_filter_level = 0;
|
||||
cfg.sample_rate = SAMPLE_RATE;
|
||||
cfg.magnification = cfg.use_adc ? 16 : 1;
|
||||
|
||||
/*
|
||||
{ // use for Unit PDM ( Port A )
|
||||
cfg.pin_data_in = M5.getPin(m5::pin_name_t::port_a_pin2);
|
||||
cfg.pin_ws = M5.getPin(m5::pin_name_t::port_a_pin1);
|
||||
cfg.pin_bck = -1;
|
||||
cfg.pin_mck = -1;
|
||||
cfg.use_adc = false;
|
||||
cfg.stereo = false;
|
||||
cfg.i2s_port = i2s_port_t::I2S_NUM_0;
|
||||
}
|
||||
//*/
|
||||
M5.Mic.config(cfg);
|
||||
}
|
||||
|
||||
if (!M5.Mic.isEnabled()) {
|
||||
M5.Display.printf("microphone is not available.");
|
||||
M5_LOGE("microphone is not available.");
|
||||
for (;;) {
|
||||
M5.delay(256);
|
||||
}
|
||||
}
|
||||
M5.Speaker.end();
|
||||
M5.Mic.begin();
|
||||
|
||||
M5.Display.startWrite();
|
||||
|
||||
int16_t w = M5.Display.width();
|
||||
int16_t h = M5.Display.height() >> 2;
|
||||
int16_t y = 0;
|
||||
|
||||
rect_t rect_fft_peak = {0, y, w, h};
|
||||
y += h;
|
||||
rect_t rect_fft_drawer = {0, y, w, h};
|
||||
y += h;
|
||||
rect_t rect_fft_history = {0, y, w, h};
|
||||
y += h;
|
||||
rect_t rect_wav_drawer = {0, y, w, h};
|
||||
|
||||
fft_function.setup(FFT_BITS);
|
||||
fft_peak.setup(&M5.Display, rect_fft_peak);
|
||||
fft_drawer.setup(&M5.Display, rect_fft_drawer);
|
||||
fft_history.setup(&M5.Display, rect_fft_history);
|
||||
wav_drawer.setup(&M5.Display, rect_wav_drawer);
|
||||
|
||||
M5.Display.setTextSize(w / 64.0f, h / 16.0f);
|
||||
M5.Display.setFont(&fonts::AsciiFont8x16);
|
||||
M5.Display.setEpdMode(epd_mode_t::epd_fastest);
|
||||
|
||||
fft_data.fft_size_bits = FFT_BITS;
|
||||
fft_data.sample_rate = SAMPLE_RATE;
|
||||
fft_data.wav_data = &wav_data;
|
||||
fft_data.length = (1 << (fft_data.fft_size_bits - 1)) + 1;
|
||||
fft_data.fdata = (typeof(fft_data.fdata))memory_alloc(fft_data.length * sizeof(fft_data.fdata[0]));
|
||||
|
||||
wav_data.length = WAVE_TOTAL_SIZE;
|
||||
wav_data.wav = (typeof(wav_data.wav))memory_alloc(WAVE_TOTAL_SIZE * sizeof(wav_data.wav[0]));
|
||||
memset(wav_data.wav, 0 , WAVE_TOTAL_SIZE * sizeof(int16_t));
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static int step = -1;
|
||||
|
||||
if (M5.Mic.isEnabled())
|
||||
{
|
||||
int wav_idx = wav_data.latest_index;
|
||||
while (M5.Mic.isRecording() < 2) {
|
||||
M5.Mic.record(&(wav_data.wav[wav_idx]), WAVE_BLOCK_SIZE, SAMPLE_RATE, false);
|
||||
wav_idx += WAVE_BLOCK_SIZE;
|
||||
if (wav_idx >= WAVE_TOTAL_SIZE) { wav_idx = 0; }
|
||||
wav_data.latest_index = wav_idx;
|
||||
};
|
||||
|
||||
{
|
||||
switch (++step) {
|
||||
default:
|
||||
step = 0;
|
||||
M5.Display.display();
|
||||
fft_function.update(&fft_data);
|
||||
break;
|
||||
case 1:
|
||||
wav_drawer.update(wav_data);
|
||||
break;
|
||||
case 2:
|
||||
fft_drawer.update(fft_data);
|
||||
break;
|
||||
case 3:
|
||||
fft_history.update(fft_data);
|
||||
break;
|
||||
case 4:
|
||||
fft_peak.update(fft_data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for ESP-IDF
|
||||
extern "C" {
|
||||
__attribute__((weak))
|
||||
void app_main()
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
#include <M5UnitLCD.h>
|
||||
#include <M5UnitOLED.h>
|
||||
#include <M5Unified.h>
|
||||
|
||||
/// need AquesTalk library. ( URL : https://www.a-quest.com/ )
|
||||
#include <aquestalk.h>
|
||||
|
||||
/// set M5Speaker virtual channel (0-7)
|
||||
static constexpr uint8_t m5spk_virtual_channel = 0;
|
||||
|
||||
static constexpr uint8_t LEN_FRAME = 32;
|
||||
|
||||
static uint32_t workbuf[AQ_SIZE_WORKBUF];
|
||||
static TaskHandle_t task_handle = nullptr;
|
||||
volatile bool is_talking = false;
|
||||
|
||||
static void talk_task(void*)
|
||||
{
|
||||
int16_t wav[3][LEN_FRAME];
|
||||
int tri_index = 0;
|
||||
for (;;)
|
||||
{
|
||||
ulTaskNotifyTake( pdTRUE, portMAX_DELAY ); // wait notify
|
||||
while (is_talking)
|
||||
{
|
||||
uint16_t len;
|
||||
if (CAqTkPicoF_SyntheFrame(wav[tri_index], &len)) { is_talking = false; break; }
|
||||
M5.Speaker.playRaw(wav[tri_index], len, 8000, false, 1, m5spk_virtual_channel, false);
|
||||
tri_index = tri_index < 2 ? tri_index + 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 音声再生の終了を待機する;
|
||||
static void waitAquesTalk(void)
|
||||
{
|
||||
while (is_talking) { vTaskDelay(1); }
|
||||
}
|
||||
|
||||
/// 音声再生を停止する;
|
||||
static void stopAquesTalk(void)
|
||||
{
|
||||
if (is_talking) { is_talking = false; vTaskDelay(1); }
|
||||
}
|
||||
|
||||
/// 音声再生を開始する。(再生中の場合は中断して新たな音声再生を開始する) ;
|
||||
static void playAquesTalk(const char *koe)
|
||||
{
|
||||
stopAquesTalk();
|
||||
|
||||
M5.Display.printf("Play:%s\n", koe);
|
||||
|
||||
int iret = CAqTkPicoF_SetKoe((const uint8_t*)koe, 100, 0xFFu);
|
||||
if (iret) { M5.Display.println("ERR:CAqTkPicoF_SetKoe"); }
|
||||
|
||||
is_talking = true;
|
||||
xTaskNotifyGive(task_handle);
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
// If you want to play sound from ModuleDisplay, write this
|
||||
// cfg.external_speaker.module_display = true;
|
||||
|
||||
// If you want to play sound from ModuleRCA, write this
|
||||
// cfg.external_speaker.module_rca = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker, write this
|
||||
cfg.external_speaker.hat_spk = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker2, write this
|
||||
// cfg.external_speaker.hat_spk2 = true;
|
||||
|
||||
// If you want to play sound from ATOMIC Speaker, write this
|
||||
cfg.external_speaker.atomic_spk = true;
|
||||
|
||||
M5.begin(cfg);
|
||||
|
||||
xTaskCreateUniversal(talk_task, "talk_task", 4096, nullptr, 1, &task_handle, APP_CPU_NUM);
|
||||
/*
|
||||
/// Increasing the sample_rate will improve the sound quality instead of increasing the CPU load.
|
||||
auto spk_cfg = M5.Speaker.config();
|
||||
spk_cfg.sample_rate = 96000; // default:64000 (64kHz) e.g. 48000 , 50000 , 80000 , 96000 , 100000 , 128000 , 144000 , 192000 , 200000
|
||||
M5.Speaker.config(spk_cfg);
|
||||
//*/
|
||||
M5.Speaker.setVolume(128);
|
||||
|
||||
M5.Display.setEpdMode(epd_mode_t::epd_fastest);
|
||||
M5.Display.setTextWrap(true);
|
||||
|
||||
int iret = CAqTkPicoF_Init(workbuf, LEN_FRAME, "XXX-XXX-XXX");
|
||||
if (iret) {
|
||||
M5.Display.println("ERR:CAqTkPicoF_Init");
|
||||
}
|
||||
|
||||
playAquesTalk("akue_suto'-_ku/kido-shima'_shita.");
|
||||
waitAquesTalk();
|
||||
playAquesTalk("botanno/o_shitekudasa'i.");
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.update();
|
||||
|
||||
if ( M5.BtnA.wasClicked()) { playAquesTalk("kuri'kku"); }
|
||||
else if (M5.BtnA.wasHold()) { playAquesTalk("ho'-rudo"); }
|
||||
else if (M5.BtnA.wasReleased()) { playAquesTalk("riri'-su"); }
|
||||
else if (M5.BtnB.wasReleased()) { playAquesTalk("korewa;te'_sutode_su."); }
|
||||
else if (M5.BtnC.wasReleased()) { playAquesTalk("yukkuri_siteittene?"); }
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreateUniversal(loopTask, "loopTask", 8192, NULL, 1, NULL, APP_CPU_NUM);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
#include <SD.h>
|
||||
#include <M5Unified.h>
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
static constexpr const gpio_num_t SDCARD_CSPIN = GPIO_NUM_4;
|
||||
|
||||
static constexpr const char* files[] = {
|
||||
"/file1.wav",
|
||||
"/file2.wav",
|
||||
"/file3.wav",
|
||||
};
|
||||
|
||||
static constexpr const size_t buf_num = 3;
|
||||
static constexpr const size_t buf_size = 1024;
|
||||
static uint8_t wav_data[buf_num][buf_size];
|
||||
|
||||
struct __attribute__((packed)) wav_header_t
|
||||
{
|
||||
char RIFF[4];
|
||||
uint32_t chunk_size;
|
||||
char WAVEfmt[8];
|
||||
uint32_t fmt_chunk_size;
|
||||
uint16_t audiofmt;
|
||||
uint16_t channel;
|
||||
uint32_t sample_rate;
|
||||
uint32_t byte_per_sec;
|
||||
uint16_t block_size;
|
||||
uint16_t bit_per_sample;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) sub_chunk_t
|
||||
{
|
||||
char identifier[4];
|
||||
uint32_t chunk_size;
|
||||
uint8_t data[1];
|
||||
};
|
||||
|
||||
static bool playSdWav(const char* filename)
|
||||
{
|
||||
auto file = SD.open(filename);
|
||||
|
||||
if (!file) { return false; }
|
||||
|
||||
wav_header_t wav_header;
|
||||
file.read((uint8_t*)&wav_header, sizeof(wav_header_t));
|
||||
|
||||
ESP_LOGD("wav", "RIFF : %.4s" , wav_header.RIFF );
|
||||
ESP_LOGD("wav", "chunk_size : %d" , wav_header.chunk_size );
|
||||
ESP_LOGD("wav", "WAVEfmt : %.8s" , wav_header.WAVEfmt );
|
||||
ESP_LOGD("wav", "fmt_chunk_size : %d" , wav_header.fmt_chunk_size);
|
||||
ESP_LOGD("wav", "audiofmt : %d" , wav_header.audiofmt );
|
||||
ESP_LOGD("wav", "channel : %d" , wav_header.channel );
|
||||
ESP_LOGD("wav", "sample_rate : %d" , wav_header.sample_rate );
|
||||
ESP_LOGD("wav", "byte_per_sec : %d" , wav_header.byte_per_sec );
|
||||
ESP_LOGD("wav", "block_size : %d" , wav_header.block_size );
|
||||
ESP_LOGD("wav", "bit_per_sample : %d" , wav_header.bit_per_sample);
|
||||
|
||||
if ( memcmp(wav_header.RIFF, "RIFF", 4)
|
||||
|| memcmp(wav_header.WAVEfmt, "WAVEfmt ", 8)
|
||||
|| wav_header.audiofmt != 1
|
||||
|| wav_header.bit_per_sample < 8
|
||||
|| wav_header.bit_per_sample > 16
|
||||
|| wav_header.channel == 0
|
||||
|| wav_header.channel > 2
|
||||
)
|
||||
{
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
file.seek(offsetof(wav_header_t, audiofmt) + wav_header.fmt_chunk_size);
|
||||
sub_chunk_t sub_chunk;
|
||||
|
||||
file.read((uint8_t*)&sub_chunk, 8);
|
||||
|
||||
ESP_LOGD("wav", "sub id : %.4s" , sub_chunk.identifier);
|
||||
ESP_LOGD("wav", "sub chunk_size : %d" , sub_chunk.chunk_size);
|
||||
|
||||
while(memcmp(sub_chunk.identifier, "data", 4))
|
||||
{
|
||||
if (!file.seek(sub_chunk.chunk_size, SeekMode::SeekCur)) { break; }
|
||||
file.read((uint8_t*)&sub_chunk, 8);
|
||||
|
||||
ESP_LOGD("wav", "sub id : %.4s" , sub_chunk.identifier);
|
||||
ESP_LOGD("wav", "sub chunk_size : %d" , sub_chunk.chunk_size);
|
||||
}
|
||||
|
||||
if (memcmp(sub_chunk.identifier, "data", 4))
|
||||
{
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t data_len = sub_chunk.chunk_size;
|
||||
bool flg_16bit = (wav_header.bit_per_sample >> 4);
|
||||
|
||||
size_t idx = 0;
|
||||
while (data_len > 0) {
|
||||
size_t len = data_len < buf_size ? data_len : buf_size;
|
||||
len = file.read(wav_data[idx], len);
|
||||
data_len -= len;
|
||||
|
||||
if (flg_16bit) {
|
||||
M5.Speaker.playRaw((const int16_t*)wav_data[idx], len >> 1, wav_header.sample_rate, wav_header.channel > 1, 1, 0);
|
||||
} else {
|
||||
M5.Speaker.playRaw((const uint8_t*)wav_data[idx], len, wav_header.sample_rate, wav_header.channel > 1, 1, 0);
|
||||
}
|
||||
idx = idx < (buf_num - 1) ? idx + 1 : 0;
|
||||
}
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
SD.begin(SDCARD_CSPIN, SPI, 25000000);
|
||||
|
||||
// M5.Speaker.setVolume(32);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
for (auto filename : files) {
|
||||
playSdWav(filename);
|
||||
M5.delay(500);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,681 @@
|
|||
#define WIFI_SSID "SET YOUR WIFI SSID"
|
||||
#define WIFI_PASS "SET YOUR WIFI PASS"
|
||||
|
||||
|
||||
#include <WiFi.h>
|
||||
#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 <M5UnitLCD.h>
|
||||
#include <M5UnitOLED.h>
|
||||
#include <M5Unified.h>
|
||||
|
||||
/// 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] =
|
||||
{
|
||||
{"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"},
|
||||
{"MAXXED Out" , "http://149.56.195.94:8015/steam"},
|
||||
{"Asia Dream" , "http://igor.torontocast.com:1025/;.-mp3"},
|
||||
};
|
||||
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 (;;)
|
||||
{
|
||||
M5.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);
|
||||
|
||||
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();
|
||||
|
||||
// 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; }
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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.getVolume();
|
||||
int x = v * (gfx->width()) >> 8;
|
||||
if (px != x)
|
||||
{
|
||||
gfx->fillRect(x, 6, px - x, 2, px < x ? 0xAAFFAAu : 0u);
|
||||
gfx->display();
|
||||
px = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
// If you want to play sound from ModuleDisplay, write this
|
||||
// cfg.external_speaker.module_display = true;
|
||||
|
||||
// If you want to play sound from ModuleRCA, write this
|
||||
// cfg.external_speaker.module_rca = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker, write this
|
||||
cfg.external_speaker.hat_spk = true;
|
||||
|
||||
// If you want to play sound from HAT Speaker2, write this
|
||||
// cfg.external_speaker.hat_spk2 = true;
|
||||
|
||||
// If you want to play sound from ATOMIC Speaker, write this
|
||||
cfg.external_speaker.atomic_spk = true;
|
||||
|
||||
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 (;;) { M5.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 = 48000; // 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);
|
||||
}
|
||||
|
||||
|
||||
M5.Speaker.begin();
|
||||
|
||||
M5.Display.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) {
|
||||
M5.Display.print(".");
|
||||
M5.delay(100);
|
||||
}
|
||||
M5.Display.clear();
|
||||
|
||||
gfxSetup(&M5.Display);
|
||||
|
||||
play(station_index);
|
||||
|
||||
xTaskCreatePinnedToCore(decodeTask, "decodeTask", 4096, nullptr, 1, nullptr, PRO_CPU_NUM);
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
gfxLoop(&M5.Display);
|
||||
|
||||
{
|
||||
static int prev_frame;
|
||||
int frame;
|
||||
do
|
||||
{
|
||||
M5.delay(1);
|
||||
} while (prev_frame == (frame = millis() >> 3)); /// 8 msec cycle wait
|
||||
prev_frame = frame;
|
||||
}
|
||||
|
||||
M5.update();
|
||||
auto td = M5.Touch.getDetail();
|
||||
if (M5.BtnA.wasPressed() || td.wasPressed())
|
||||
{
|
||||
M5.Speaker.tone(440, 50);
|
||||
}
|
||||
int cc = M5.BtnA.getClickCount();
|
||||
if (cc == 0) cc = td.getClickCount();
|
||||
if (M5.BtnA.wasDecideClickCount() || td.wasClicked())
|
||||
{
|
||||
switch (cc)
|
||||
{
|
||||
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() || td.isHolding())
|
||||
{
|
||||
size_t v = M5.Speaker.getVolume();
|
||||
int add = (M5.BtnB.isPressed()) ? -1 : 1;
|
||||
if (M5.BtnA.isHolding() || td.isHolding())
|
||||
{
|
||||
add = (M5.BtnA.getClickCount() || td.getClickCount()) ? -1 : 1;
|
||||
}
|
||||
v += add;
|
||||
if (v <= 255)
|
||||
{
|
||||
M5.Speaker.setVolume(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
#include <M5Unified.h>
|
||||
|
||||
static bool got_notif_flag = false;
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
M5.begin();
|
||||
M5.Power.Axp2101.disableIRQ(AXP2101_IRQ_ALL);
|
||||
M5.Power.Axp2101.clearIRQStatuses();
|
||||
M5.Power.Axp2101.enableIRQ(
|
||||
AXP2101_IRQ_BAT_CHG_UNDER_TEMP | AXP2101_IRQ_BAT_CHG_OVER_TEMP | // Battery temp in charging
|
||||
AXP2101_IRQ_VBUS_INSERT | AXP2101_IRQ_VBUS_REMOVE // Usb insert/remove
|
||||
);
|
||||
|
||||
M5.Display.setTextSize(3);
|
||||
}
|
||||
|
||||
void check_irq_statuses()
|
||||
{
|
||||
M5.Power.Axp2101.getIRQStatuses();
|
||||
|
||||
if(M5.Power.Axp2101.isBatChargerUnderTemperatureIrq())
|
||||
{
|
||||
M5.Display.drawString("BatUnderTempCharge", 50, 120);
|
||||
got_notif_flag = true;
|
||||
}
|
||||
if(M5.Power.Axp2101.isBatChargerOverTemperatureIrq())
|
||||
{
|
||||
M5.Display.drawString("BatOverTempCharge", 50, 120);
|
||||
got_notif_flag = true;
|
||||
}
|
||||
if(M5.Power.Axp2101.isVbusInsertIrq())
|
||||
{
|
||||
M5.Display.drawString("Usb inserted", 50, 120);
|
||||
got_notif_flag = true;
|
||||
}
|
||||
if(M5.Power.Axp2101.isVbusRemoveIrq())
|
||||
{
|
||||
M5.Display.drawString("Usb removed", 50, 120);
|
||||
got_notif_flag = true;
|
||||
}
|
||||
|
||||
M5.Power.Axp2101.clearIRQStatuses();
|
||||
}
|
||||
|
||||
void refresh_display()
|
||||
{
|
||||
static unsigned long started_time = 0;
|
||||
if(got_notif_flag == false) { return; }
|
||||
|
||||
unsigned long now_time = millis();
|
||||
if(started_time == 0)
|
||||
{
|
||||
started_time = now_time;
|
||||
}
|
||||
else if(now_time - started_time > 500)
|
||||
{
|
||||
started_time = 0;
|
||||
M5.Display.fillScreen(BLACK);
|
||||
got_notif_flag = false;
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.update();
|
||||
check_irq_statuses();
|
||||
refresh_display();
|
||||
vTaskDelay(50);
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
124
libraries/M5Unified/examples/Basic/Button/Button.ino
Normal file
124
libraries/M5Unified/examples/Basic/Button/Button.ino
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#include <M5Unified.h>
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
/// For models with EPD : refresh control
|
||||
M5.Display.setEpdMode(epd_mode_t::epd_fastest); // fastest but very-low quality.
|
||||
|
||||
if (M5.Display.width() < M5.Display.height())
|
||||
{ /// Landscape mode.
|
||||
M5.Display.setRotation(M5.Display.getRotation() ^ 1);
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.delay(1);
|
||||
|
||||
M5.update();
|
||||
//------------------- Button test
|
||||
/*
|
||||
/// List of available buttons:
|
||||
M5Stack BASIC/GRAY/GO/FIRE: BtnA,BtnB,BtnC
|
||||
M5Stack Core2: BtnA,BtnB,BtnC,BtnPWR
|
||||
M5Stick C/CPlus: BtnA,BtnB, BtnPWR
|
||||
M5Stick CoreInk: BtnA,BtnB,BtnC,BtnPWR,BtnEXT
|
||||
M5Paper: BtnA,BtnB,BtnC
|
||||
M5Station: BtnA,BtnB,BtnC,BtnPWR
|
||||
M5Tough: BtnPWR
|
||||
M5Atom M5AtomU: BtnA
|
||||
M5Stamp Pico/C3/C3U: BtnA
|
||||
*/
|
||||
|
||||
static constexpr const int colors[] = { TFT_WHITE, TFT_CYAN, TFT_RED, TFT_YELLOW, TFT_BLUE, TFT_GREEN };
|
||||
static constexpr const char* const names[] = { "none", "wasHold", "wasClicked", "wasPressed", "wasReleased", "wasDeciedCount" };
|
||||
|
||||
int w = M5.Display.width() / 5;
|
||||
int h = M5.Display.height();
|
||||
M5.Display.startWrite();
|
||||
|
||||
/// BtnPWR: "wasClicked"/"wasHold" can be use.
|
||||
/// BtnPWR of CoreInk: "isPressed"/"wasPressed"/"isReleased"/"wasReleased"/"wasClicked"/"wasHold"/"isHolding" can be use.
|
||||
int state = M5.BtnPWR.wasHold() ? 1
|
||||
: M5.BtnPWR.wasClicked() ? 2
|
||||
: M5.BtnPWR.wasPressed() ? 3
|
||||
: M5.BtnPWR.wasReleased() ? 4
|
||||
: M5.BtnPWR.wasDecideClickCount() ? 5
|
||||
: 0;
|
||||
|
||||
if (state)
|
||||
{
|
||||
M5_LOGI("BtnPWR:%s count:%d", names[state], M5.BtnPWR.getClickCount());
|
||||
M5.Display.fillRect(w*0, 0, w-1, h, colors[state]);
|
||||
}
|
||||
|
||||
/// BtnA,BtnB,BtnC,BtnEXT: "isPressed"/"wasPressed"/"isReleased"/"wasReleased"/"wasClicked"/"wasHold"/"isHolding" can be use.
|
||||
state = M5.BtnA.wasHold() ? 1
|
||||
: M5.BtnA.wasClicked() ? 2
|
||||
: M5.BtnA.wasPressed() ? 3
|
||||
: M5.BtnA.wasReleased() ? 4
|
||||
: M5.BtnA.wasDecideClickCount() ? 5
|
||||
: 0;
|
||||
if (state)
|
||||
{
|
||||
M5_LOGI("BtnA:%s count:%d", names[state], M5.BtnA.getClickCount());
|
||||
M5.Display.fillRect(w*1, 0, w-1, h, colors[state]);
|
||||
}
|
||||
|
||||
state = M5.BtnB.wasHold() ? 1
|
||||
: M5.BtnB.wasClicked() ? 2
|
||||
: M5.BtnB.wasPressed() ? 3
|
||||
: M5.BtnB.wasReleased() ? 4
|
||||
: M5.BtnB.wasDecideClickCount() ? 5
|
||||
: 0;
|
||||
if (state)
|
||||
{
|
||||
M5_LOGI("BtnB:%s count:%d", names[state], M5.BtnB.getClickCount());
|
||||
M5.Display.fillRect(w*2, 0, w-1, h, colors[state]);
|
||||
}
|
||||
|
||||
state = M5.BtnC.wasHold() ? 1
|
||||
: M5.BtnC.wasClicked() ? 2
|
||||
: M5.BtnC.wasPressed() ? 3
|
||||
: M5.BtnC.wasReleased() ? 4
|
||||
: M5.BtnC.wasDecideClickCount() ? 5
|
||||
: 0;
|
||||
if (state)
|
||||
{
|
||||
M5_LOGI("BtnC:%s count:%d", names[state], M5.BtnC.getClickCount());
|
||||
M5.Display.fillRect(w*3, 0, w-1, h, colors[state]);
|
||||
}
|
||||
|
||||
state = M5.BtnEXT.wasHold() ? 1
|
||||
: M5.BtnEXT.wasClicked() ? 2
|
||||
: M5.BtnEXT.wasPressed() ? 3
|
||||
: M5.BtnEXT.wasReleased() ? 4
|
||||
: M5.BtnEXT.wasDecideClickCount() ? 5
|
||||
: 0;
|
||||
if (state)
|
||||
{
|
||||
M5_LOGI("BtnEXT:%s count:%d", names[state], M5.BtnEXT.getClickCount());
|
||||
M5.Display.fillRect(w*4, 0, w-1, h, colors[state]);
|
||||
}
|
||||
M5.Display.endWrite();
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO ) && defined ( ESP_PLATFORM )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
291
libraries/M5Unified/examples/Basic/Displays/Displays.ino
Normal file
291
libraries/M5Unified/examples/Basic/Displays/Displays.ino
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
|
||||
#if defined ( ARDUINO )
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// If you use SD card, write this.
|
||||
#include <SD.h>
|
||||
|
||||
// If you use SPIFFS, write this.
|
||||
#include <SPIFFS.h>
|
||||
|
||||
#endif
|
||||
|
||||
// * The filesystem header must be included before the display library.
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
// If you use ATOM Display, write this.
|
||||
#include <M5AtomDisplay.h>
|
||||
|
||||
// If you use Module Display, write this.
|
||||
#include <M5ModuleDisplay.h>
|
||||
|
||||
// If you use Module RCA, write this.
|
||||
#include <M5ModuleRCA.h>
|
||||
|
||||
// If you use Unit GLASS, write this.
|
||||
#include <M5UnitGLASS.h>
|
||||
|
||||
// If you use Unit GLASS2, write this.
|
||||
#include <M5UnitGLASS2.h>
|
||||
|
||||
// If you use Unit OLED, write this.
|
||||
#include <M5UnitOLED.h>
|
||||
|
||||
// If you use Unit Mini OLED, write this.
|
||||
#include <M5UnitMiniOLED.h>
|
||||
|
||||
// If you use Unit LCD, write this.
|
||||
#include <M5UnitLCD.h>
|
||||
|
||||
// If you use UnitRCA (for Video output), write this.
|
||||
#include <M5UnitRCA.h>
|
||||
|
||||
// * The display header must be included before the M5Unified library.
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
// Include this to enable the M5 global instance.
|
||||
#include <M5Unified.h>
|
||||
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
// external display setting. (Pre-include required)
|
||||
cfg.external_display.module_display = true; // default=true. use ModuleDisplay
|
||||
cfg.external_display.atom_display = true; // default=true. use AtomDisplay
|
||||
cfg.external_display.unit_glass = false; // default=true. use UnitGLASS
|
||||
cfg.external_display.unit_glass2 = false; // default=true. use UnitGLASS2
|
||||
cfg.external_display.unit_oled = false; // default=true. use UnitOLED
|
||||
cfg.external_display.unit_mini_oled = false; // default=true. use UnitMiniOLED
|
||||
cfg.external_display.unit_lcd = false; // default=true. use UnitLCD
|
||||
cfg.external_display.unit_rca = false; // default=true. use UnitRCA VideoOutput
|
||||
cfg.external_display.module_rca = false; // default=true. use ModuleRCA VideoOutput
|
||||
|
||||
/*
|
||||
※ Unit OLED, Unit Mini OLED, Unit GLASS2 cannot be distinguished at runtime and may be misidentified as each other.
|
||||
|
||||
※ Display with auto-detection
|
||||
- module_display
|
||||
- atom_display
|
||||
- unit_glass
|
||||
- unit_glass2
|
||||
- unit_oled
|
||||
- unit_mini_oled
|
||||
- unit_lcd
|
||||
|
||||
※ Displays that cannot be auto-detected
|
||||
- module_rca
|
||||
- unit_rca
|
||||
|
||||
※ Note that if you enable a display that cannot be auto-detected,
|
||||
it will operate as if it were connected, even if it is not actually connected.
|
||||
When RCA is enabled, it consumes a lot of memory to allocate the frame buffer.
|
||||
//*/
|
||||
|
||||
|
||||
// Set individual parameters for external displays.
|
||||
// (※ Use only the items you wish to change. Basically, it can be omitted.)
|
||||
#if defined ( __M5GFX_M5ATOMDISPLAY__ ) // setting for ATOM Display.
|
||||
// cfg.atom_display.logical_width = 1280;
|
||||
// cfg.atom_display.logical_height = 720;
|
||||
// cfg.atom_display.output_width = 1280;
|
||||
// cfg.atom_display.output_height = 720;
|
||||
// cfg.atom_display.refresh_rate = 60;
|
||||
// cfg.atom_display.scale_w = 1;
|
||||
// cfg.atom_display.scale_h = 1;
|
||||
// cfg.atom_display.pixel_clock = 74250000;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5MODULEDISPLAY__ ) // setting for Module Display.
|
||||
// cfg.module_display.logical_width = 1280;
|
||||
// cfg.module_display.logical_height = 720;
|
||||
// cfg.module_display.output_width = 1280;
|
||||
// cfg.module_display.output_height = 720;
|
||||
// cfg.module_display.refresh_rate = 60;
|
||||
// cfg.module_display.scale_w = 1;
|
||||
// cfg.module_display.scale_h = 1;
|
||||
// cfg.module_display.pixel_clock = 74250000;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5MODULERCA__ ) // setting for Module RCA.
|
||||
// cfg.module_rca.logical_width = 216;
|
||||
// cfg.module_rca.logical_height = 144;
|
||||
// cfg.module_rca.output_width = 216;
|
||||
// cfg.module_rca.output_height = 144;
|
||||
// cfg.module_rca.signal_type = M5ModuleRCA::signal_type_t::PAL; // NTSC / NTSC_J / PAL_M / PAL_N
|
||||
// cfg.module_rca.use_psram = M5ModuleRCA::use_psram_t::psram_use; // psram_no_use / psram_half_use
|
||||
// cfg.module_rca.pin_dac = GPIO_NUM_26;
|
||||
// cfg.module_rca.output_level = 128;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5UNITRCA__ ) // setting for Unit RCA.
|
||||
// cfg.unit_rca.logical_width = 216;
|
||||
// cfg.unit_rca.logical_height = 144;
|
||||
// cfg.unit_rca.output_width = 216;
|
||||
// cfg.unit_rca.output_height = 144;
|
||||
// cfg.unit_rca.signal_type = M5UnitRCA::signal_type_t::PAL; // NTSC / NTSC_J / PAL_M / PAL_N
|
||||
// cfg.unit_rca.use_psram = M5UnitRCA::use_psram_t::psram_use; // psram_no_use / psram_half_use
|
||||
// cfg.unit_rca.pin_dac = GPIO_NUM_26;
|
||||
// cfg.unit_rca.output_level = 128;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5UNITGLASS__ ) // setting for Unit GLASS.
|
||||
// cfg.unit_glass.pin_sda = GPIO_NUM_21;
|
||||
// cfg.unit_glass.pin_scl = GPIO_NUM_22;
|
||||
// cfg.unit_glass.i2c_addr = 0x3D;
|
||||
// cfg.unit_glass.i2c_freq = 400000;
|
||||
// cfg.unit_glass.i2c_port = I2C_NUM_0;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5UNITGLASS2__ ) // setting for Unit GLASS2.
|
||||
// cfg.unit_glass2.pin_sda = GPIO_NUM_21;
|
||||
// cfg.unit_glass2.pin_scl = GPIO_NUM_22;
|
||||
// cfg.unit_glass2.i2c_addr = 0x3C;
|
||||
// cfg.unit_glass2.i2c_freq = 400000;
|
||||
// cfg.unit_glass2.i2c_port = I2C_NUM_0;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5UNITOLED__ ) // setting for Unit OLED.
|
||||
// cfg.unit_oled.pin_sda = GPIO_NUM_21;
|
||||
// cfg.unit_oled.pin_scl = GPIO_NUM_22;
|
||||
// cfg.unit_oled.i2c_addr = 0x3C;
|
||||
// cfg.unit_oled.i2c_freq = 400000;
|
||||
// cfg.unit_oled.i2c_port = I2C_NUM_0;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5UNITMINIOLED__ ) // setting for Unit Mini OLED.
|
||||
// cfg.unit_mini_oled.pin_sda = GPIO_NUM_21;
|
||||
// cfg.unit_mini_oled.pin_scl = GPIO_NUM_22;
|
||||
// cfg.unit_mini_oled.i2c_addr = 0x3C;
|
||||
// cfg.unit_mini_oled.i2c_freq = 400000;
|
||||
// cfg.unit_mini_oled.i2c_port = I2C_NUM_0;
|
||||
#endif
|
||||
#if defined ( __M5GFX_M5UNITLCD__ ) // setting for Unit LCD.
|
||||
// cfg.unit_lcd.pin_sda = GPIO_NUM_21;
|
||||
// cfg.unit_lcd.pin_scl = GPIO_NUM_22;
|
||||
// cfg.unit_lcd.i2c_addr = 0x3E;
|
||||
// cfg.unit_lcd.i2c_freq = 400000;
|
||||
// cfg.unit_lcd.i2c_port = I2C_NUM_0;
|
||||
#endif
|
||||
|
||||
|
||||
// begin M5Unified.
|
||||
M5.begin(cfg);
|
||||
|
||||
// Get the number of available displays
|
||||
int display_count = M5.getDisplayCount();
|
||||
|
||||
for (int i = 0; i < display_count; ++i) {
|
||||
// All displays are available in M5.Displays.
|
||||
// ※ Note that the order of which displays are numbered is the order in which they are detected, so the order may change.
|
||||
|
||||
int textsize = M5.Displays(i).height() / 60;
|
||||
if (textsize == 0) { textsize = 1; }
|
||||
M5.Displays(i).setTextSize(textsize);
|
||||
M5.Displays(i).printf("No.%d\n", i);
|
||||
}
|
||||
|
||||
|
||||
// If an external display is to be used as the main display, it can be listed in order of priority.
|
||||
M5.setPrimaryDisplayType( {
|
||||
m5::board_t::board_M5ModuleDisplay,
|
||||
m5::board_t::board_M5AtomDisplay,
|
||||
// m5::board_t::board_M5ModuleRCA,
|
||||
// m5::board_t::board_M5UnitGLASS,
|
||||
// m5::board_t::board_M5UnitGLASS2,
|
||||
// m5::board_t::board_M5UnitMiniOLED,
|
||||
// m5::board_t::board_M5UnitOLED,
|
||||
// m5::board_t::board_M5UnitLCD,
|
||||
// m5::board_t::board_M5UnitRCA,
|
||||
} );
|
||||
|
||||
|
||||
// The primary display can be used with M5.Display.
|
||||
M5.Display.print("primary display\n");
|
||||
|
||||
|
||||
// Examine the indexes of a given type of display
|
||||
int index_module_display = M5.getDisplayIndex(m5::board_t::board_M5ModuleDisplay);
|
||||
int index_atom_display = M5.getDisplayIndex(m5::board_t::board_M5AtomDisplay);
|
||||
int index_module_rca = M5.getDisplayIndex(m5::board_t::board_M5ModuleRCA);
|
||||
int index_unit_glass = M5.getDisplayIndex(m5::board_t::board_M5UnitGLASS);
|
||||
int index_unit_glass2 = M5.getDisplayIndex(m5::board_t::board_M5UnitGLASS2);
|
||||
int index_unit_oled = M5.getDisplayIndex(m5::board_t::board_M5UnitOLED);
|
||||
int index_unit_mini_oled = M5.getDisplayIndex(m5::board_t::board_M5UnitMiniOLED);
|
||||
int index_unit_lcd = M5.getDisplayIndex(m5::board_t::board_M5UnitLCD);
|
||||
int index_unit_rca = M5.getDisplayIndex(m5::board_t::board_M5UnitRCA);
|
||||
|
||||
if (index_module_display >= 0) {
|
||||
M5.Displays(index_module_display).print("This is Module Display\n");
|
||||
}
|
||||
if (index_atom_display >= 0) {
|
||||
M5.Displays(index_atom_display).print("This is Atom Display\n");
|
||||
}
|
||||
if (index_module_rca >= 0) {
|
||||
M5.Displays(index_module_rca).print("This is Module RCA\n");
|
||||
}
|
||||
if (index_unit_glass >= 0) {
|
||||
M5.Displays(index_unit_glass).print("This is Unit GLASS\n");
|
||||
}
|
||||
if (index_unit_glass2 >= 0) {
|
||||
M5.Displays(index_unit_glass2).print("This is Unit GLASS2\n");
|
||||
}
|
||||
if (index_unit_oled >= 0) {
|
||||
M5.Displays(index_unit_oled).print("This is Unit OLED\n");
|
||||
}
|
||||
if (index_unit_mini_oled >= 0) {
|
||||
M5.Displays(index_unit_mini_oled ).print("This is Unit Mini OLED\n");
|
||||
}
|
||||
if (index_unit_lcd >= 0) {
|
||||
M5.Displays(index_unit_lcd).print("This is Unit LCD\n");
|
||||
}
|
||||
if (index_unit_rca >= 0) {
|
||||
M5.Displays(index_unit_rca).print("This is Unit RCA\n");
|
||||
}
|
||||
M5.delay(5000);
|
||||
}
|
||||
|
||||
|
||||
// When creating a function for drawing, it can be used universally by accepting a LovyanGFX type as an argument.
|
||||
void draw_function(LovyanGFX* gfx)
|
||||
{
|
||||
int x = rand() % gfx->width();
|
||||
int y = rand() % gfx->height();
|
||||
int r = (gfx->width() >> 4) + 2;
|
||||
uint16_t c = rand();
|
||||
gfx->fillRect(x-r, y-r, r*2, r*2, c);
|
||||
}
|
||||
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.delay(1);
|
||||
|
||||
for (int i = 0; i < M5.getDisplayCount(); ++i) {
|
||||
int x = rand() % M5.Displays(i).width();
|
||||
int y = rand() % M5.Displays(i).height();
|
||||
int r = (M5.Displays(i).width() >> 4) + 2;
|
||||
uint16_t c = rand();
|
||||
M5.Displays(i).fillCircle(x, y, r, c);
|
||||
}
|
||||
|
||||
for (int i = 0; i < M5.getDisplayCount(); ++i) {
|
||||
draw_function(&M5.Displays(i));
|
||||
}
|
||||
}
|
||||
|
||||
// for ESP-IDF compat
|
||||
#if !defined ( ARDUINO ) && defined ( ESP_PLATFORM )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
3574
libraries/M5Unified/examples/Basic/HowToUse/HowToUse.ino
Normal file
3574
libraries/M5Unified/examples/Basic/HowToUse/HowToUse.ino
Normal file
File diff suppressed because it is too large
Load diff
299
libraries/M5Unified/examples/Basic/Imu/Imu.ino
Normal file
299
libraries/M5Unified/examples/Basic/Imu/Imu.ino
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
|
||||
// If you use Unit OLED, write this.
|
||||
// #include <M5UnitOLED.h>
|
||||
|
||||
// If you use Unit LCD, write this.
|
||||
// #include <M5UnitLCD.h>
|
||||
|
||||
|
||||
// Include this to enable the M5 global instance.
|
||||
#include <M5Unified.h>
|
||||
|
||||
// Strength of the calibration operation;
|
||||
// 0: disables calibration.
|
||||
// 1 is weakest and 255 is strongest.
|
||||
static constexpr const uint8_t calib_value = 64;
|
||||
|
||||
|
||||
// This sample code performs calibration by clicking on a button or screen.
|
||||
// After 10 seconds of calibration, the results are stored in NVS.
|
||||
// The saved calibration values are loaded at the next startup.
|
||||
//
|
||||
// === How to calibration ===
|
||||
// ※ Calibration method for Accelerometer
|
||||
// Change the direction of the main unit by 90 degrees
|
||||
// and hold it still for 2 seconds. Repeat multiple times.
|
||||
// It is recommended that as many surfaces as possible be on the bottom.
|
||||
//
|
||||
// ※ Calibration method for Gyro
|
||||
// Simply place the unit on a quiet desk and hold it still.
|
||||
// It is recommended that this be done after the accelerometer calibration.
|
||||
//
|
||||
// ※ Calibration method for geomagnetic sensors
|
||||
// Rotate the main unit slowly in multiple directions.
|
||||
// It is recommended that as many surfaces as possible be oriented to the north.
|
||||
//
|
||||
// Values for extremely large attitude changes are ignored.
|
||||
// During calibration, it is desirable to move the device as gently as possible.
|
||||
|
||||
struct rect_t
|
||||
{
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t w;
|
||||
int32_t h;
|
||||
};
|
||||
|
||||
static constexpr const uint32_t color_tbl[18] =
|
||||
{
|
||||
0xFF0000u, 0xCCCC00u, 0xCC00FFu,
|
||||
0xFFCC00u, 0x00FF00u, 0x0088FFu,
|
||||
0xFF00CCu, 0x00FFCCu, 0x0000FFu,
|
||||
0xFF0000u, 0xCCCC00u, 0xCC00FFu,
|
||||
0xFFCC00u, 0x00FF00u, 0x0088FFu,
|
||||
0xFF00CCu, 0x00FFCCu, 0x0000FFu,
|
||||
};
|
||||
static constexpr const float coefficient_tbl[3] = { 0.5f, (1.0f / 256.0f), (1.0f / 1024.0f) };
|
||||
|
||||
static auto &dsp = (M5.Display);
|
||||
static rect_t rect_graph_area;
|
||||
static rect_t rect_text_area;
|
||||
|
||||
static uint8_t calib_countdown = 0;
|
||||
|
||||
static int prev_xpos[18];
|
||||
|
||||
void drawBar(int32_t ox, int32_t oy, int32_t nx, int32_t px, int32_t h, uint32_t color)
|
||||
{
|
||||
uint32_t bgcolor = (color >> 3) & 0x1F1F1Fu;
|
||||
if (px && ((nx < 0) != (px < 0)))
|
||||
{
|
||||
dsp.fillRect(ox, oy, px, h, bgcolor);
|
||||
px = 0;
|
||||
}
|
||||
if (px != nx)
|
||||
{
|
||||
if ((nx > px) != (nx < 0))
|
||||
{
|
||||
bgcolor = color;
|
||||
}
|
||||
dsp.setColor(bgcolor);
|
||||
dsp.fillRect(nx + ox, oy, px - nx, h);
|
||||
}
|
||||
}
|
||||
|
||||
void drawGraph(const rect_t& r, const m5::imu_data_t& data)
|
||||
{
|
||||
float aw = (128 * r.w) >> 1;
|
||||
float gw = (128 * r.w) / 256.0f;
|
||||
float mw = (128 * r.w) / 1024.0f;
|
||||
int ox = (r.x + r.w)>>1;
|
||||
int oy = r.y;
|
||||
int h = (r.h / 18) * (calib_countdown ? 1 : 2);
|
||||
int bar_count = 9 * (calib_countdown ? 2 : 1);
|
||||
|
||||
dsp.startWrite();
|
||||
for (int index = 0; index < bar_count; ++index)
|
||||
{
|
||||
float xval;
|
||||
if (index < 9)
|
||||
{
|
||||
auto coe = coefficient_tbl[index / 3] * r.w;
|
||||
xval = data.value[index] * coe;
|
||||
}
|
||||
else
|
||||
{
|
||||
xval = M5.Imu.getOffsetData(index - 9) * (1.0f / (1 << 19));
|
||||
}
|
||||
|
||||
// for Linear scale graph.
|
||||
float tmp = xval;
|
||||
|
||||
// The smaller the value, the larger the amount of change in the graph.
|
||||
// float tmp = sqrtf(fabsf(xval * 128)) * (signbit(xval) ? -1 : 1);
|
||||
|
||||
int nx = tmp;
|
||||
int px = prev_xpos[index];
|
||||
if (nx != px)
|
||||
prev_xpos[index] = nx;
|
||||
drawBar(ox, oy + h * index, nx, px, h - 1, color_tbl[index]);
|
||||
}
|
||||
dsp.endWrite();
|
||||
}
|
||||
|
||||
void updateCalibration(uint32_t c, bool clear = false)
|
||||
{
|
||||
calib_countdown = c;
|
||||
|
||||
if (c == 0) {
|
||||
clear = true;
|
||||
}
|
||||
|
||||
if (clear)
|
||||
{
|
||||
memset(prev_xpos, 0, sizeof(prev_xpos));
|
||||
dsp.fillScreen(TFT_BLACK);
|
||||
|
||||
if (c)
|
||||
{ // Start calibration.
|
||||
M5.Imu.setCalibration(calib_value, calib_value, calib_value);
|
||||
// ※ The actual calibration operation is performed each time during M5.Imu.update.
|
||||
//
|
||||
// There are three arguments, which can be specified in the order of Accelerometer, gyro, and geomagnetic.
|
||||
// If you want to calibrate only the Accelerometer, do the following.
|
||||
// M5.Imu.setCalibration(100, 0, 0);
|
||||
//
|
||||
// If you want to calibrate only the gyro, do the following.
|
||||
// M5.Imu.setCalibration(0, 100, 0);
|
||||
//
|
||||
// If you want to calibrate only the geomagnetism, do the following.
|
||||
// M5.Imu.setCalibration(0, 0, 100);
|
||||
}
|
||||
else
|
||||
{ // Stop calibration. (Continue calibration only for the geomagnetic sensor)
|
||||
M5.Imu.setCalibration(0, 0, calib_value);
|
||||
|
||||
// If you want to stop all calibration, write this.
|
||||
// M5.Imu.setCalibration(0, 0, 0);
|
||||
|
||||
// save calibration values.
|
||||
M5.Imu.saveOffsetToNVS();
|
||||
}
|
||||
}
|
||||
|
||||
auto backcolor = (c == 0) ? TFT_BLACK : TFT_BLUE;
|
||||
dsp.fillRect(rect_text_area.x, rect_text_area.y, rect_text_area.w, rect_text_area.h, backcolor);
|
||||
|
||||
if (c)
|
||||
{
|
||||
dsp.setCursor(rect_text_area.x + 2, rect_text_area.y + 1);
|
||||
dsp.setTextColor(TFT_WHITE, TFT_BLUE);
|
||||
dsp.printf("Countdown:%d ", c);
|
||||
}
|
||||
}
|
||||
|
||||
void startCalibration(void)
|
||||
{
|
||||
updateCalibration(10, true);
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
// If you want to use external IMU, write this
|
||||
//cfg.external_imu = true;
|
||||
|
||||
M5.begin(cfg);
|
||||
|
||||
const char* name;
|
||||
auto imu_type = M5.Imu.getType();
|
||||
switch (imu_type)
|
||||
{
|
||||
case m5::imu_none: name = "not found"; break;
|
||||
case m5::imu_sh200q: name = "sh200q"; break;
|
||||
case m5::imu_mpu6050: name = "mpu6050"; break;
|
||||
case m5::imu_mpu6886: name = "mpu6886"; break;
|
||||
case m5::imu_mpu9250: name = "mpu9250"; break;
|
||||
case m5::imu_bmi270: name = "bmi270"; break;
|
||||
default: name = "unknown"; break;
|
||||
};
|
||||
M5_LOGI("imu:%s", name);
|
||||
M5.Display.printf("imu:%s", name);
|
||||
|
||||
if (imu_type == m5::imu_none)
|
||||
{
|
||||
for (;;) { delay(1); }
|
||||
}
|
||||
|
||||
int32_t w = dsp.width();
|
||||
int32_t h = dsp.height();
|
||||
if (w < h)
|
||||
{
|
||||
dsp.setRotation(dsp.getRotation() ^ 1);
|
||||
w = dsp.width();
|
||||
h = dsp.height();
|
||||
}
|
||||
int32_t graph_area_h = ((h - 8) / 18) * 18;
|
||||
int32_t text_area_h = h - graph_area_h;
|
||||
float fontsize = text_area_h / 8;
|
||||
dsp.setTextSize(fontsize);
|
||||
|
||||
rect_graph_area = { 0, 0, w, graph_area_h };
|
||||
rect_text_area = {0, graph_area_h, w, text_area_h };
|
||||
|
||||
|
||||
// Read calibration values from NVS.
|
||||
if (!M5.Imu.loadOffsetFromNVS())
|
||||
{
|
||||
startCalibration();
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static uint32_t frame_count = 0;
|
||||
static uint32_t prev_sec = 0;
|
||||
|
||||
// To update the IMU value, use M5.Imu.update.
|
||||
// If a new value is obtained, the return value is non-zero.
|
||||
auto imu_update = M5.Imu.update();
|
||||
if (imu_update)
|
||||
{
|
||||
// Obtain data on the current value of the IMU.
|
||||
auto data = M5.Imu.getImuData();
|
||||
drawGraph(rect_graph_area, data);
|
||||
/*
|
||||
// The data obtained by getImuData can be used as follows.
|
||||
data.accel.x; // accel x-axis value.
|
||||
data.accel.y; // accel y-axis value.
|
||||
data.accel.z; // accel z-axis value.
|
||||
data.accel.value; // accel 3values array [0]=x / [1]=y / [2]=z.
|
||||
|
||||
data.gyro.x; // gyro x-axis value.
|
||||
data.gyro.y; // gyro y-axis value.
|
||||
data.gyro.z; // gyro z-axis value.
|
||||
data.gyro.value; // gyro 3values array [0]=x / [1]=y / [2]=z.
|
||||
|
||||
data.mag.x; // mag x-axis value.
|
||||
data.mag.y; // mag y-axis value.
|
||||
data.mag.z; // mag z-axis value.
|
||||
data.mag.value; // mag 3values array [0]=x / [1]=y / [2]=z.
|
||||
|
||||
data.value; // all sensor 9values array [0~2]=accel / [3~5]=gyro / [6~8]=mag
|
||||
|
||||
M5_LOGV("ax:%f ay:%f az:%f", data.accel.x, data.accel.y, data.accel.z);
|
||||
M5_LOGV("gx:%f gy:%f gz:%f", data.gyro.x , data.gyro.y , data.gyro.z );
|
||||
M5_LOGV("mx:%f my:%f mz:%f", data.mag.x , data.mag.y , data.mag.z );
|
||||
//*/
|
||||
++frame_count;
|
||||
}
|
||||
else
|
||||
{
|
||||
M5.update();
|
||||
|
||||
// Calibration is initiated when a button or screen is clicked.
|
||||
if (M5.BtnA.wasClicked() || M5.BtnPWR.wasClicked() || M5.Touch.getDetail().wasClicked())
|
||||
{
|
||||
startCalibration();
|
||||
}
|
||||
}
|
||||
|
||||
int32_t sec = millis() / 1000;
|
||||
if (prev_sec != sec)
|
||||
{
|
||||
prev_sec = sec;
|
||||
M5_LOGI("sec:%d frame:%d", sec, frame_count);
|
||||
frame_count = 0;
|
||||
|
||||
if (calib_countdown)
|
||||
{
|
||||
updateCalibration(calib_countdown - 1);
|
||||
}
|
||||
|
||||
if ((sec & 7) == 0)
|
||||
{ // prevent WDT.
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
libraries/M5Unified/examples/Basic/LogOutput/LogOutput.ino
Normal file
133
libraries/M5Unified/examples/Basic/LogOutput/LogOutput.ino
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include <M5Unified.h>
|
||||
|
||||
|
||||
void user_made_log_callback(esp_log_level_t, bool, const char*);
|
||||
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
/// Example of ESP32 standard log output macro usage.
|
||||
/// ESP_LOGx series outputs to USB-connected PC terminal.
|
||||
/// You can set the output level with `Core Debug Level` in ArduinoIDE.
|
||||
/// ※ If the `Core Debug Level` is set to `None`, nothing is output.
|
||||
#if defined ( ESP_PLATFORM )
|
||||
ESP_LOGE("TAG", "using ESP_LOGE error log."); // Critical errors, software module can not recover on its own
|
||||
ESP_LOGW("TAG", "using ESP_LOGW warn log."); // Error conditions from which recovery measures have been taken
|
||||
ESP_LOGI("TAG", "using ESP_LOGI info log."); // Information messages which describe normal flow of events
|
||||
ESP_LOGD("TAG", "using ESP_LOGD debug log."); // Extra information which is not necessary for normal use (values, pointers, sizes, etc).
|
||||
ESP_LOGV("TAG", "using ESP_LOGV verbose log."); // Bigger chunks of debugging information, or frequent messages which can potentially flood the output.
|
||||
#endif
|
||||
|
||||
M5.begin();
|
||||
|
||||
/// If you want to output logs to the display, write this.
|
||||
M5.setLogDisplayIndex(0);
|
||||
|
||||
/// use wrapping from bottom edge to top edge.
|
||||
M5.Display.setTextWrap(true, true);
|
||||
/// use scrolling.
|
||||
// M5.Display.setTextScroll(true);
|
||||
|
||||
/// Example of M5Unified log output class usage.
|
||||
/// Unlike ESP_LOGx, the M5.Log series can output to serial, display, and user callback function in a single line of code.
|
||||
|
||||
|
||||
/// You can set Log levels for each output destination.
|
||||
/// ESP_LOG_ERROR / ESP_LOG_WARN / ESP_LOG_INFO / ESP_LOG_DEBUG / ESP_LOG_VERBOSE
|
||||
M5.Log.setLogLevel(m5::log_target_serial, ESP_LOG_VERBOSE);
|
||||
M5.Log.setLogLevel(m5::log_target_display, ESP_LOG_DEBUG);
|
||||
M5.Log.setLogLevel(m5::log_target_callback, ESP_LOG_INFO);
|
||||
|
||||
/// Set up user-specific callback functions.
|
||||
M5.Log.setCallback(user_made_log_callback);
|
||||
|
||||
/// You can color the log or not.
|
||||
M5.Log.setEnableColor(m5::log_target_serial, true);
|
||||
M5.Log.setEnableColor(m5::log_target_display, true);
|
||||
M5.Log.setEnableColor(m5::log_target_callback, true);
|
||||
|
||||
/// You can set the text to be added to the end of the log for each output destination.
|
||||
/// ( default value : "\n" )
|
||||
M5.Log.setSuffix(m5::log_target_serial, "\n");
|
||||
M5.Log.setSuffix(m5::log_target_display, "\n");
|
||||
M5.Log.setSuffix(m5::log_target_callback, "");
|
||||
|
||||
/// `M5.Log()` can be used to output a simple log
|
||||
M5.Log(ESP_LOG_ERROR , "M5.Log error log"); /// ERROR level output
|
||||
M5.Log(ESP_LOG_WARN , "M5.Log warn log"); /// WARN level output
|
||||
M5.Log(ESP_LOG_INFO , "M5.Log info log"); /// INFO level output
|
||||
M5.Log(ESP_LOG_DEBUG , "M5.Log debug log"); /// DEBUG level output
|
||||
M5.Log(ESP_LOG_VERBOSE , "M5.Log verbose log"); /// VERBOSE level output
|
||||
|
||||
/// `M5_LOGx` macro can be used to output a log containing the source file name, line number, and function name.
|
||||
M5_LOGE("M5_LOGE error log"); /// ERROR level output with source info
|
||||
M5_LOGW("M5_LOGW warn log"); /// WARN level output with source info
|
||||
M5_LOGI("M5_LOGI info log"); /// INFO level output with source info
|
||||
M5_LOGD("M5_LOGD debug log"); /// DEBUG level output with source info
|
||||
M5_LOGV("M5_LOGV verbose log"); /// VERBOSE level output with source info
|
||||
|
||||
/// `M5.Log.printf()` is output without log level and without suffix and is output to all serial, display, and callback.
|
||||
M5.Log.printf("M5.Log.printf non level output\n");
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.delay(1);
|
||||
M5.update();
|
||||
|
||||
if (M5.BtnPWR.wasClicked()) { M5_LOGE("BtnP %d click", M5.BtnPWR.getClickCount()); }
|
||||
if (M5.BtnA .wasClicked()) { M5_LOGW("BtnA %d click", M5.BtnA .getClickCount()); }
|
||||
if (M5.BtnB .wasClicked()) { M5_LOGI("BtnB %d click", M5.BtnB .getClickCount()); }
|
||||
if (M5.BtnC .wasClicked()) { M5_LOGD("BtnC %d click", M5.BtnC .getClickCount()); }
|
||||
|
||||
static uint32_t counter = 0;
|
||||
if ((++counter & 0x3FF) == 0)
|
||||
{
|
||||
static int prev_y;
|
||||
int cursor_y = M5.Display.getCursorY();
|
||||
if (prev_y > cursor_y)
|
||||
{
|
||||
M5.Display.clear();
|
||||
}
|
||||
prev_y = cursor_y;
|
||||
M5_LOGV("count:%d", counter >> 10);
|
||||
}
|
||||
}
|
||||
|
||||
void user_made_log_callback(esp_log_level_t log_level, bool use_color, const char* log_text)
|
||||
{
|
||||
// You can also create your own callback function to output log contents to a file,WiFi,and more other destination
|
||||
|
||||
#if defined ( ARDUINO )
|
||||
/*
|
||||
if (SD.begin(GPIO_NUM_4, SPI, 25000000))
|
||||
{
|
||||
auto file = SD.open("/logfile.txt", FILE_APPEND);
|
||||
file.print(log_text);
|
||||
file.close();
|
||||
SD.end();
|
||||
}
|
||||
//*/
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/// for ESP-IDF
|
||||
#if !defined ( ARDUINO ) && defined ( ESP_PLATFORM )
|
||||
extern "C"
|
||||
{
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
145
libraries/M5Unified/examples/Basic/Microphone/Microphone.ino
Normal file
145
libraries/M5Unified/examples/Basic/Microphone/Microphone.ino
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#include <M5UnitLCD.h>
|
||||
#include <M5UnitOLED.h>
|
||||
#include <M5Unified.h>
|
||||
|
||||
static constexpr const size_t record_number = 256;
|
||||
static constexpr const size_t record_length = 200;
|
||||
static constexpr const size_t record_size = record_number * record_length;
|
||||
static constexpr const size_t record_samplerate = 16000;
|
||||
static int16_t prev_y[record_length];
|
||||
static int16_t prev_h[record_length];
|
||||
static size_t rec_record_idx = 2;
|
||||
static size_t draw_record_idx = 0;
|
||||
static int16_t *rec_data;
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
// cfg.external_speaker.hat_spk = true; /// use external speaker (HAT SPK)
|
||||
// cfg.external_speaker.hat_spk2 = true; /// use external speaker (HAT SPK2)
|
||||
// cfg.external_speaker.atomic_spk = true; /// use external speaker (ATOMIC SPK)
|
||||
|
||||
M5.begin(cfg);
|
||||
|
||||
M5.Display.startWrite();
|
||||
|
||||
if (M5.Display.width() > M5.Display.height())
|
||||
{
|
||||
M5.Display.setRotation(M5.Display.getRotation()^1);
|
||||
}
|
||||
|
||||
M5.Display.setCursor(0, 0);
|
||||
M5.Display.print("REC");
|
||||
rec_data = (typeof(rec_data))heap_caps_malloc(record_size * sizeof(int16_t), MALLOC_CAP_8BIT);
|
||||
memset(rec_data, 0 , record_size * sizeof(int16_t));
|
||||
M5.Speaker.setVolume(255);
|
||||
|
||||
/// Since the microphone and speaker cannot be used at the same time, turn off the speaker here.
|
||||
M5.Speaker.end();
|
||||
M5.Mic.begin();
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.update();
|
||||
|
||||
if (M5.Mic.isEnabled())
|
||||
{
|
||||
static constexpr int shift = 6;
|
||||
auto data = &rec_data[rec_record_idx * record_length];
|
||||
if (M5.Mic.record(data, record_length, record_samplerate))
|
||||
{
|
||||
data = &rec_data[draw_record_idx * record_length];
|
||||
|
||||
int32_t w = M5.Display.width();
|
||||
if (w > record_length - 1) { w = record_length - 1; }
|
||||
for (int32_t x = 0; x < w; ++x)
|
||||
{
|
||||
M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], TFT_BLACK);
|
||||
int32_t y1 = (data[x ] >> shift);
|
||||
int32_t y2 = (data[x + 1] >> shift);
|
||||
if (y1 > y2)
|
||||
{
|
||||
int32_t tmp = y1;
|
||||
y1 = y2;
|
||||
y2 = tmp;
|
||||
}
|
||||
int32_t y = (M5.Display.height() >> 1) + y1;
|
||||
int32_t h = (M5.Display.height() >> 1) + y2 + 1 - y;
|
||||
prev_y[x] = y;
|
||||
prev_h[x] = h;
|
||||
M5.Display.writeFastVLine(x, y, h, TFT_WHITE);
|
||||
}
|
||||
M5.Display.display();
|
||||
|
||||
if (++draw_record_idx >= record_number) { draw_record_idx = 0; }
|
||||
if (++rec_record_idx >= record_number) { rec_record_idx = 0; }
|
||||
}
|
||||
}
|
||||
|
||||
if (M5.BtnA.wasHold() || M5.BtnB.wasClicked())
|
||||
{
|
||||
auto cfg = M5.Mic.config();
|
||||
cfg.noise_filter_level = (cfg.noise_filter_level + 8) & 255;
|
||||
M5.Mic.config(cfg);
|
||||
M5.Display.setCursor(32,0);
|
||||
M5.Display.printf("nf:%03d", cfg.noise_filter_level);
|
||||
}
|
||||
else
|
||||
if (M5.BtnA.wasClicked() || (M5.Touch.getCount() && M5.Touch.getDetail(0).wasClicked()))
|
||||
{
|
||||
if (M5.Speaker.isEnabled())
|
||||
{
|
||||
M5.Display.clear();
|
||||
while (M5.Mic.isRecording()) { M5.delay(1); }
|
||||
|
||||
/// Since the microphone and speaker cannot be used at the same time, turn off the microphone here.
|
||||
M5.Mic.end();
|
||||
M5.Speaker.begin();
|
||||
|
||||
M5.Display.setCursor(0,0);
|
||||
M5.Display.print("PLAY");
|
||||
int start_pos = rec_record_idx * record_length;
|
||||
if (start_pos < record_size)
|
||||
{
|
||||
M5.Speaker.playRaw(&rec_data[start_pos], record_size - start_pos, record_samplerate, false, 1, 0);
|
||||
}
|
||||
if (start_pos > 0)
|
||||
{
|
||||
M5.Speaker.playRaw(rec_data, start_pos, record_samplerate, false, 1, 0);
|
||||
}
|
||||
do
|
||||
{
|
||||
M5.delay(1);
|
||||
M5.update();
|
||||
} while (M5.Speaker.isPlaying());
|
||||
|
||||
/// Since the microphone and speaker cannot be used at the same time, turn off the speaker here.
|
||||
M5.Speaker.end();
|
||||
M5.Mic.begin();
|
||||
|
||||
M5.Display.clear();
|
||||
M5.Display.setCursor(0,0);
|
||||
M5.Display.print("REC");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
155
libraries/M5Unified/examples/Basic/Rtc/Rtc.ino
Normal file
155
libraries/M5Unified/examples/Basic/Rtc/Rtc.ino
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#if defined ( ARDUINO )
|
||||
|
||||
#define WIFI_SSID "YOUR WIFI SSID NAME"
|
||||
#define WIFI_PASSWORD "YOUR WIFI PASSWORD"
|
||||
#define NTP_TIMEZONE "JST-9"
|
||||
#define NTP_SERVER1 "0.pool.ntp.org"
|
||||
#define NTP_SERVER2 "1.pool.ntp.org"
|
||||
#define NTP_SERVER3 "2.pool.ntp.org"
|
||||
|
||||
#include <WiFi.h>
|
||||
|
||||
// Different versions of the framework have different SNTP header file names and availability.
|
||||
#if __has_include (<esp_sntp.h>)
|
||||
#include <esp_sntp.h>
|
||||
#define SNTP_ENABLED 1
|
||||
#elif __has_include (<sntp.h>)
|
||||
#include <sntp.h>
|
||||
#define SNTP_ENABLED 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef SNTP_ENABLED
|
||||
#define SNTP_ENABLED 0
|
||||
#endif
|
||||
|
||||
#include <M5Unified.h>
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
auto cfg = M5.config();
|
||||
|
||||
cfg.external_rtc = true; // default=false. use Unit RTC.
|
||||
|
||||
M5.begin(cfg);
|
||||
|
||||
M5.Display.setEpdMode(m5gfx::epd_fastest);
|
||||
M5.setLogDisplayIndex(0);
|
||||
|
||||
if (!M5.Rtc.isEnabled())
|
||||
{
|
||||
M5.Log.println("RTC not found.");
|
||||
for (;;) { M5.delay(500); }
|
||||
}
|
||||
|
||||
M5.Log.println("RTC found.");
|
||||
|
||||
// It is recommended to set UTC for the RTC and ESP32 internal clocks.
|
||||
|
||||
|
||||
/* /// setup RTC ( direct setting )
|
||||
// YYYY MM DD hh mm ss
|
||||
M5.Rtc.setDateTime( { { 2021, 12, 31 }, { 12, 34, 56 } } );
|
||||
//*/
|
||||
|
||||
|
||||
/// setup RTC ( NTP auto setting )
|
||||
configTzTime(NTP_TIMEZONE, NTP_SERVER1, NTP_SERVER2, NTP_SERVER3);
|
||||
#ifdef WiFi_h
|
||||
M5.Log.print("WiFi:");
|
||||
WiFi.begin( WIFI_SSID, WIFI_PASSWORD );
|
||||
|
||||
for (int i = 20; i && WiFi.status() != WL_CONNECTED; --i)
|
||||
{
|
||||
M5.Log.print(".");
|
||||
M5.delay(500);
|
||||
}
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
M5.Log.println("\r\nWiFi Connected.");
|
||||
M5.Log.print("NTP:");
|
||||
#if SNTP_ENABLED
|
||||
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED)
|
||||
{
|
||||
M5.Log.print(".");
|
||||
M5.delay(1000);
|
||||
}
|
||||
#else
|
||||
M5.delay(1600);
|
||||
struct tm timeInfo;
|
||||
while (!getLocalTime(&timeInfo, 1000))
|
||||
{
|
||||
M5.Log.print('.');
|
||||
};
|
||||
#endif
|
||||
M5.Log.println("\r\nNTP Connected.");
|
||||
|
||||
time_t t = time(nullptr)+1; // Advance one second.
|
||||
while (t > time(nullptr)); /// Synchronization in seconds
|
||||
M5.Rtc.setDateTime( gmtime( &t ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
M5.Log.println("\r\nWiFi none...");
|
||||
}
|
||||
#endif
|
||||
|
||||
M5.Display.clear();
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
static constexpr const char* const wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"};
|
||||
|
||||
M5.delay(500);
|
||||
|
||||
auto dt = M5.Rtc.getDateTime();
|
||||
M5.Display.setCursor(0,0);
|
||||
M5.Log.printf("RTC UTC :%04d/%02d/%02d (%s) %02d:%02d:%02d\r\n"
|
||||
, dt.date.year
|
||||
, dt.date.month
|
||||
, dt.date.date
|
||||
, wd[dt.date.weekDay]
|
||||
, dt.time.hours
|
||||
, dt.time.minutes
|
||||
, dt.time.seconds
|
||||
);
|
||||
|
||||
/// ESP32 internal timer
|
||||
auto t = time(nullptr);
|
||||
{
|
||||
auto tm = gmtime(&t); // for UTC.
|
||||
M5.Display.setCursor(0,20);
|
||||
M5.Log.printf("ESP32 UTC :%04d/%02d/%02d (%s) %02d:%02d:%02d\r\n",
|
||||
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
|
||||
wd[tm->tm_wday],
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
}
|
||||
|
||||
{
|
||||
auto tm = localtime(&t); // for local timezone.
|
||||
M5.Display.setCursor(0,40);
|
||||
M5.Log.printf("ESP32 %s:%04d/%02d/%02d (%s) %02d:%02d:%02d\r\n", NTP_TIMEZONE,
|
||||
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
|
||||
wd[tm->tm_wday],
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
17724
libraries/M5Unified/examples/Basic/Speaker/Speaker.ino
Normal file
17724
libraries/M5Unified/examples/Basic/Speaker/Speaker.ino
Normal file
File diff suppressed because it is too large
Load diff
164
libraries/M5Unified/examples/Basic/Touch/DragDrop/DragDrop.ino
Normal file
164
libraries/M5Unified/examples/Basic/Touch/DragDrop/DragDrop.ino
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include <M5Unified.h>
|
||||
|
||||
struct box_t
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
std::uint16_t color;
|
||||
int touch_id = -1;
|
||||
|
||||
void clear(void)
|
||||
{
|
||||
M5.Display.setColor(M5.Display.getBaseColor());
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
M5.Display.drawRect(x+i, y+i, w-i*2, h-i*2);
|
||||
}
|
||||
}
|
||||
void draw(void)
|
||||
{
|
||||
M5.Display.setColor(color);
|
||||
int ie = touch_id < 0 ? 4 : 8;
|
||||
for (int i = 0; i < ie; ++i)
|
||||
{
|
||||
M5.Display.drawRect(x+i, y+i, w-i*2, h-i*2);
|
||||
}
|
||||
}
|
||||
bool contain(int x, int y)
|
||||
{
|
||||
return this->x <= x && x < (this->x + this->w)
|
||||
&& this->y <= y && y < (this->y + this->h);
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr std::size_t box_count = 4;
|
||||
static box_t box_list[box_count];
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
for (std::size_t i = 0; i < box_count; ++i)
|
||||
{
|
||||
box_list[i].w = 100;
|
||||
box_list[i].h = 100;
|
||||
}
|
||||
box_list[0].x = 0;
|
||||
box_list[0].y = 0;
|
||||
box_list[1].x = M5.Display.width() - box_list[1].w;
|
||||
box_list[1].y = 0;
|
||||
box_list[2].x = 0;
|
||||
box_list[2].y = M5.Display.height() - box_list[2].h;
|
||||
box_list[3].x = M5.Display.width() - box_list[3].w;
|
||||
box_list[3].y = M5.Display.height() - box_list[3].h;
|
||||
box_list[0].color = TFT_RED;
|
||||
box_list[1].color = TFT_BLUE;
|
||||
box_list[2].color = TFT_GREEN;
|
||||
box_list[3].color = TFT_YELLOW;
|
||||
|
||||
M5.Display.setEpdMode(epd_mode_t::epd_fastest);
|
||||
M5.Display.startWrite();
|
||||
M5.Display.setTextSize(2);
|
||||
for (std::size_t i = 0; i < box_count; ++i)
|
||||
{
|
||||
box_list[i].draw();
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.delay(1);
|
||||
|
||||
M5.update();
|
||||
|
||||
auto count = M5.Touch.getCount();
|
||||
if (!count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static m5::touch_state_t prev_state;
|
||||
auto t = M5.Touch.getDetail();
|
||||
if (prev_state != t.state)
|
||||
{
|
||||
prev_state = t.state;
|
||||
static constexpr const char* state_name[16] =
|
||||
{ "none"
|
||||
, "touch"
|
||||
, "touch_end"
|
||||
, "touch_begin"
|
||||
, "___"
|
||||
, "hold"
|
||||
, "hold_end"
|
||||
, "hold_begin"
|
||||
, "___"
|
||||
, "flick"
|
||||
, "flick_end"
|
||||
, "flick_begin"
|
||||
, "___"
|
||||
, "drag"
|
||||
, "drag_end"
|
||||
, "drag_begin"
|
||||
};
|
||||
M5_LOGI("%s", state_name[t.state]);
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
auto t = M5.Touch.getDetail(i);
|
||||
|
||||
for (std::size_t j = 0; j < box_count; ++j)
|
||||
{
|
||||
if (t.wasHold())
|
||||
{
|
||||
if (box_list[j].contain(t.x, t.y))
|
||||
{
|
||||
box_list[j].touch_id = t.id;
|
||||
}
|
||||
}
|
||||
|
||||
M5.Display.waitDisplay();
|
||||
if (box_list[j].touch_id == t.id)
|
||||
{
|
||||
if (t.wasReleased())
|
||||
{
|
||||
box_list[j].touch_id = -1;
|
||||
box_list[j].clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dx = t.deltaX();
|
||||
auto dy = t.deltaY();
|
||||
if (dx || dy)
|
||||
{
|
||||
box_list[j].clear();
|
||||
box_list[j].x += dx;
|
||||
box_list[j].y += dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
box_list[j].draw();
|
||||
}
|
||||
}
|
||||
M5.Display.display();
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO ) && defined ( ESP_PLATFORM )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
228
libraries/M5Unified/examples/Basic/Touch/SliderUI/SliderUI.ino
Normal file
228
libraries/M5Unified/examples/Basic/Touch/SliderUI/SliderUI.ino
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
#include <M5Unified.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct rect_t
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int w;
|
||||
int h;
|
||||
|
||||
bool contain(int x, int y)
|
||||
{
|
||||
return this->x <= x && x < (this->x + this->w)
|
||||
&& this->y <= y && y < (this->y + this->h);
|
||||
}
|
||||
bool contain(m5::Touch_Class::point_t &p) {
|
||||
return contain(p.x, p.y);
|
||||
}
|
||||
};
|
||||
|
||||
class slider_t
|
||||
{
|
||||
public:
|
||||
void setup(const rect_t &r, int min_value, int max_value, int value, uint16_t frame_color = 0xFFFF, uint16_t back_color = 0, uint16_t thumb_color = 0xFFE0) {
|
||||
setupPosition(r);
|
||||
setupValue(min_value, max_value, value);
|
||||
setupColor(frame_color, back_color, thumb_color);
|
||||
}
|
||||
|
||||
void setupPosition(const rect_t &r) {
|
||||
_rect = r;
|
||||
_vertical = r.h > r.w;
|
||||
}
|
||||
|
||||
void setupValue(int min_value, int max_value, int value)
|
||||
{
|
||||
_min_value = min_value;
|
||||
_max_value = max_value;
|
||||
_current_value = value;
|
||||
_control_value = value;
|
||||
}
|
||||
|
||||
void setupColor(uint16_t frame_color, uint16_t back_color, uint16_t thumb_color)
|
||||
{
|
||||
_frame_color = frame_color;
|
||||
_back_color = back_color;
|
||||
_thumb_color = thumb_color;
|
||||
}
|
||||
|
||||
void draw(LovyanGFX* gfx = &M5.Display)
|
||||
{
|
||||
gfx->startWrite();
|
||||
draw_frame(gfx);
|
||||
int pos = calc_pos(_current_value);
|
||||
draw_thumb(pos, _thumb_color, gfx);
|
||||
gfx->endWrite();
|
||||
}
|
||||
|
||||
bool update(m5::touch_detail_t &td, LovyanGFX* gfx = &M5.Display)
|
||||
{
|
||||
_was_changed = false;
|
||||
if (!_rect.contain(td.base)) return false;
|
||||
|
||||
if (td.wasReleased()) {
|
||||
_current_value = _control_value;
|
||||
}
|
||||
|
||||
if (td.isPressed()) {
|
||||
int rw = _vertical ? _rect.h : _rect.w;
|
||||
int tmp = (_current_value - _min_value) * rw / (_max_value - _min_value);
|
||||
tmp += _vertical ? td.distanceY() : td.distanceX();
|
||||
int v = _min_value + (_max_value - _min_value) * tmp / rw;
|
||||
_was_changed = value_update(v, gfx);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int getValue(void) const { return _control_value; }
|
||||
bool wasChanged(void) const { return _was_changed; }
|
||||
|
||||
protected:
|
||||
rect_t _rect;
|
||||
|
||||
int _min_value;
|
||||
int _max_value;
|
||||
int _current_value;
|
||||
int _control_value;
|
||||
|
||||
uint16_t _frame_color = 0xFFFF;
|
||||
uint16_t _back_color = 0;
|
||||
uint16_t _thumb_color = 0xFFE0;
|
||||
bool _vertical = false;
|
||||
bool _was_changed = false;
|
||||
|
||||
bool value_update(int new_value, LovyanGFX* gfx = &M5.Display)
|
||||
{
|
||||
int min_v = _min_value;
|
||||
int max_v = _max_value;
|
||||
if (min_v > max_v) {
|
||||
min_v = _max_value;
|
||||
max_v = _min_value;
|
||||
}
|
||||
new_value = new_value < min_v ? min_v : new_value > max_v ? max_v : new_value;
|
||||
|
||||
if (_control_value == new_value) {
|
||||
return false;
|
||||
}
|
||||
int prev_pos = calc_pos(_control_value);
|
||||
int new_pos = calc_pos(new_value);
|
||||
_control_value = new_value;
|
||||
|
||||
if (prev_pos != new_pos) {
|
||||
gfx->startWrite();
|
||||
draw_thumb(prev_pos, _back_color, gfx);
|
||||
draw_thumb(new_pos, _thumb_color, gfx);
|
||||
gfx->endWrite();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int calc_pos(int value)
|
||||
{
|
||||
int diff = _max_value - _min_value;
|
||||
if (diff == 0) return 0;
|
||||
value -= _min_value;
|
||||
int w = _vertical ? (_rect.h - _rect.w) : (_rect.w - _rect.h);
|
||||
return (w * value) / diff;
|
||||
}
|
||||
|
||||
void draw_frame(LovyanGFX* gfx = &M5.Display)
|
||||
{
|
||||
gfx->fillRoundRect(_rect.x+1, _rect.y+1, _rect.w-2, _rect.h-2, 2, _back_color);
|
||||
gfx->drawRoundRect(_rect.x, _rect.y, _rect.w, _rect.h, 3, _frame_color);
|
||||
}
|
||||
|
||||
void draw_thumb(int pos, uint16_t color, LovyanGFX* gfx = &M5.Display)
|
||||
{
|
||||
int rx = _rect.x + 1;
|
||||
int ry = _rect.y + 1;
|
||||
int rw;
|
||||
if (_vertical) {
|
||||
ry += pos;
|
||||
rw = _rect.w;
|
||||
} else {
|
||||
rx += pos;
|
||||
rw = _rect.h;
|
||||
}
|
||||
rw -= 2;
|
||||
gfx->fillRoundRect(rx, ry, rw, rw, 3, color);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static constexpr std::size_t slider_count = 4;
|
||||
static slider_t slider_list[slider_count];
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
int dw = M5.Display.width();
|
||||
int dh = M5.Display.height();
|
||||
|
||||
slider_list[0].setup({ 20, dh/2 -20, dw-40, 40 }, 5, 25, 15, TFT_WHITE, TFT_BLACK, TFT_LIGHTGRAY);
|
||||
slider_list[1].setup({ dw*1/4-20, 0, 40, dh/2 -40 }, 255, 0, 127, TFT_WHITE, TFT_BLACK, TFT_RED);
|
||||
slider_list[2].setup({ dw*2/4-20, 0, 40, dh/2 -40 }, 255, 0, 127, TFT_WHITE, TFT_BLACK, TFT_GREEN);
|
||||
slider_list[3].setup({ dw*3/4-20, 0, 40, dh/2 -40 }, 255, 0, 127, TFT_WHITE, TFT_BLACK, TFT_BLUE);
|
||||
|
||||
M5.Display.setEpdMode(epd_mode_t::epd_fastest);
|
||||
M5.Display.setTextSize(2);
|
||||
for (std::size_t i = 0; i < slider_count; ++i)
|
||||
{
|
||||
slider_list[i].draw();
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.delay(16);
|
||||
|
||||
M5.update();
|
||||
|
||||
if (0 == M5.Touch.getCount())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
auto t = M5.Touch.getDetail();
|
||||
for (std::size_t i = 0; i < slider_count; ++i)
|
||||
{
|
||||
if (slider_list[i].update(t)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
int w = slider_list[0].getValue();
|
||||
int r = slider_list[1].getValue();
|
||||
int g = slider_list[2].getValue();
|
||||
int b = slider_list[3].getValue();
|
||||
uint32_t rgb = r << 16 | g << 8 | b;
|
||||
if (slider_list[0].wasChanged())
|
||||
{
|
||||
M5.Display.fillRect(M5.Display.width()/2-25, M5.Display.height()*3/4-25, 51,51, TFT_BLACK);
|
||||
}
|
||||
M5.Display.fillCircle(M5.Display.width()/2, M5.Display.height()*3/4, w, rgb);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined ( ARDUINO ) && defined ( ESP_PLATFORM )
|
||||
extern "C" {
|
||||
void loopTask(void*)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main()
|
||||
{
|
||||
xTaskCreatePinnedToCore(loopTask, "loopTask", 8192, NULL, 1, NULL, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
3
libraries/M5Unified/examples/PlatformIO_SDL/README.md
Normal file
3
libraries/M5Unified/examples/PlatformIO_SDL/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
# Check here for setup instructions.
|
||||
## [Steps to run M5GFX on a PC.](https://github.com/m5stack/M5GFX/blob/master/examples/PlatformIO_SDL/README.md)
|
||||
66
libraries/M5Unified/examples/PlatformIO_SDL/platformio.ini
Normal file
66
libraries/M5Unified/examples/PlatformIO_SDL/platformio.ini
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = native
|
||||
|
||||
[env]
|
||||
lib_extra_dirs=../../../
|
||||
|
||||
[env:native]
|
||||
platform = native
|
||||
build_type = debug
|
||||
build_flags = -O0 -xc++ -std=c++14 -lSDL2
|
||||
-I"/usr/local/include/SDL2" ; for intel mac homebrew SDL2
|
||||
-L"/usr/local/lib" ; for intel mac homebrew SDL2
|
||||
-I"${sysenv.HOMEBREW_PREFIX}/include/SDL2" ; for arm mac homebrew SDL2
|
||||
-L"${sysenv.HOMEBREW_PREFIX}/lib" ; for arm mac homebrew SDL2
|
||||
|
||||
[env:native_StickCPlus]
|
||||
extends = native
|
||||
platform = native
|
||||
build_flags = ${env:native.build_flags}
|
||||
-DM5GFX_SCALE=2
|
||||
-DM5GFX_ROTATION=0
|
||||
-DM5GFX_BOARD=board_M5StickCPlus
|
||||
|
||||
[env:native_Paper]
|
||||
extends = native
|
||||
platform = native
|
||||
build_flags = ${env:native.build_flags}
|
||||
-DM5GFX_ROTATION=0
|
||||
-DM5GFX_BOARD=board_M5Paper
|
||||
|
||||
[esp32_base]
|
||||
build_type = debug
|
||||
platform = espressif32
|
||||
board = esp32dev
|
||||
upload_speed = 1500000
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:esp32_arduino]
|
||||
extends = esp32_base
|
||||
framework = arduino
|
||||
|
||||
[env:esp32c3_arduino]
|
||||
extends = esp32_base
|
||||
framework = arduino
|
||||
board = esp32-c3-devkitm-1
|
||||
|
||||
[env:esp32s3_arduino]
|
||||
extends = esp32_base
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
|
||||
[env:esp32_idf]
|
||||
extends = esp32_base
|
||||
framework = espidf
|
||||
|
||||
25
libraries/M5Unified/examples/PlatformIO_SDL/src/sdl_main.cpp
Normal file
25
libraries/M5Unified/examples/PlatformIO_SDL/src/sdl_main.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#include <M5GFX.h>
|
||||
#if defined ( SDL_h_ )
|
||||
|
||||
void setup(void);
|
||||
void loop(void);
|
||||
|
||||
__attribute__((weak))
|
||||
int user_func(bool* running)
|
||||
{
|
||||
setup();
|
||||
do
|
||||
{
|
||||
loop();
|
||||
} while (*running);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
// The second argument is effective for step execution with breakpoints.
|
||||
// You can specify the time in milliseconds to perform slow execution that ensures screen updates.
|
||||
return lgfx::Panel_sdl::main(user_func, 128);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#include <M5Unified.h>
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
/// You may output logs to standard output.
|
||||
M5_LOGE("this is error LOG");
|
||||
M5_LOGW("this is warning LOG");
|
||||
M5_LOGI("this is info LOG");
|
||||
M5_LOGD("this is debug LOG");
|
||||
M5_LOGV("this is verbose LOG");
|
||||
|
||||
M5.begin();
|
||||
M5.Speaker.tone(2000, 100, 0, false);
|
||||
M5.Speaker.tone(1000, 100, 0, false);
|
||||
|
||||
M5.Display.printf("Please push cursor keys.\nButtonA == Left key\nButtonB == Down key\nButtonC == Right key\nButtonPWR == Up key\n");
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
M5.delay(8);
|
||||
M5.update();
|
||||
auto td = M5.Touch.getDetail();
|
||||
if (td.isPressed()) {
|
||||
M5.Display.fillCircle(td.x, td.y, 64, rand());
|
||||
int32_t tone = 880 + td.x + td.y;
|
||||
if (tone > 0) {
|
||||
M5.Speaker.tone(tone, 50, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (M5.BtnPWR.wasClicked()) {
|
||||
M5.Speaker.tone(4000, 200);
|
||||
M5_LOGD("BtnPWR Clicked");
|
||||
}
|
||||
if (M5.BtnA.wasClicked()) {
|
||||
M5.Speaker.tone(2000, 200);
|
||||
M5_LOGI("BtnA Clicked");
|
||||
}
|
||||
if (M5.BtnB.wasClicked()) {
|
||||
M5.Speaker.tone(1000, 200);
|
||||
M5_LOGW("BtnB Clicked");
|
||||
}
|
||||
if (M5.BtnC.wasClicked()) {
|
||||
M5.Speaker.tone(500, 200);
|
||||
M5_LOGE("BtnC Clicked");
|
||||
}
|
||||
}
|
||||
|
||||
#if defined ( ESP_PLATFORM ) && !defined ( ARDUINO )
|
||||
extern "C" {
|
||||
int app_main(int, char**)
|
||||
{
|
||||
setup();
|
||||
for (;;) {
|
||||
loop();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue