mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-09 11:16:39 +00:00
feat(l2): Arduino-ESP32 3.3.12 / IDF 5.5 env, NimBLE and new I2S driver ports, large UI buffers in PSRAM
- New env Wio_Tracker_L2_companion_solo_lvgl_v3 on pioarduino 55.03.312-1; the 2.0.17 env stays until the new one passes the hardware checklist - SerialBLEInterface: NimBLE variant (PIN pairing through encrypted, authenticated characteristic properties; setAccessPermissions is a no-op) - buzzer: i2s_std driver on IDF 5 (legacy driver kept for 2.0.17) - ESPNOWRadio: IDF 5 callback signatures - ui-lvgl: psramBuf() for trail drawing, tile cache, map marks, list rows, keyboard maps and message metadata; internal heap back to 2.0.17 level Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include <esp_now.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_mac.h>
|
||||
|
||||
static uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
static esp_now_peer_info_t peerInfo;
|
||||
@@ -10,13 +11,21 @@ static esp_err_t last_send_result;
|
||||
static uint8_t rx_buf[256];
|
||||
static uint8_t last_rx_len = 0;
|
||||
|
||||
// callback when data is sent
|
||||
// callback when data is sent (IDF 5.5 passes tx info / rx info instead of MACs)
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
static void OnDataSent(const wifi_tx_info_t *info, esp_now_send_status_t status) {
|
||||
#else
|
||||
static void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
|
||||
#endif
|
||||
is_send_complete = true;
|
||||
ESPNOW_DEBUG_PRINTLN("Send Status: %d", (int)status);
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
static void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
|
||||
#else
|
||||
static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
|
||||
#endif
|
||||
ESPNOW_DEBUG_PRINTLN("Recv: len = %d", len);
|
||||
memcpy(rx_buf, data, len);
|
||||
last_rx_len = len;
|
||||
|
||||
@@ -28,9 +28,15 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
|
||||
BLEDevice::setSecurityCallbacks(this);
|
||||
BLEDevice::setMTU(MAX_FRAME_SIZE);
|
||||
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
BLESecurity::setPassKey(true, pin_code); // static PIN, shown by this device
|
||||
BLESecurity::setCapability(ESP_IO_CAP_OUT);
|
||||
BLESecurity::setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
|
||||
#else
|
||||
BLESecurity sec;
|
||||
sec.setStaticPIN(pin_code);
|
||||
sec.setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND);
|
||||
#endif
|
||||
|
||||
//BLEDevice::setPower(ESP_PWR_LVL_N8);
|
||||
|
||||
@@ -42,12 +48,22 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
|
||||
pService = pServer->createService(SERVICE_UUID);
|
||||
|
||||
// Create a BLE Characteristic
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
// NimBLE takes the encryption / MITM requirement as properties (its
|
||||
// setAccessPermissions() is a no-op) and adds the 2902 descriptor itself.
|
||||
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX,
|
||||
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY |
|
||||
BLECharacteristic::PROPERTY_READ_ENC | BLECharacteristic::PROPERTY_READ_AUTHEN);
|
||||
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX,
|
||||
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_ENC | BLECharacteristic::PROPERTY_WRITE_AUTHEN);
|
||||
#else
|
||||
pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM);
|
||||
pTxCharacteristic->addDescriptor(new BLE2902());
|
||||
|
||||
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
|
||||
pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM);
|
||||
#endif
|
||||
pRxCharacteristic->setCallbacks(this);
|
||||
|
||||
pServer->getAdvertising()->addServiceUUID(SERVICE_UUID);
|
||||
@@ -74,8 +90,19 @@ bool SerialBLEInterface::onSecurityRequest() {
|
||||
return true; // allow
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
void SerialBLEInterface::onAuthenticationComplete(ble_gap_conn_desc* desc, int status) {
|
||||
(void)desc;
|
||||
authDone(status == 0);
|
||||
}
|
||||
#else
|
||||
void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
|
||||
if (cmpl.success) {
|
||||
authDone(cmpl.success);
|
||||
}
|
||||
#endif
|
||||
|
||||
void SerialBLEInterface::authDone(bool ok) {
|
||||
if (ok) {
|
||||
BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success");
|
||||
deviceConnected = true;
|
||||
} else {
|
||||
@@ -92,6 +119,16 @@ void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) {
|
||||
void SerialBLEInterface::onConnect(BLEServer* pServer) {
|
||||
}
|
||||
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
void SerialBLEInterface::onConnect(BLEServer* pServer, ble_gap_conn_desc* desc) {
|
||||
BLE_DEBUG_PRINTLN("onConnect(), conn_handle=%d", desc->conn_handle);
|
||||
last_conn_id = desc->conn_handle;
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, ble_gap_conn_desc* desc, uint16_t mtu) {
|
||||
BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", mtu);
|
||||
}
|
||||
#else
|
||||
void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) {
|
||||
BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id));
|
||||
last_conn_id = param->connect.conn_id;
|
||||
@@ -100,6 +137,7 @@ void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t
|
||||
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
|
||||
BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", pServer->getPeerMTU(param->mtu.conn_id));
|
||||
}
|
||||
#endif
|
||||
|
||||
void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
|
||||
BLE_DEBUG_PRINTLN("onDisconnect()");
|
||||
@@ -111,7 +149,11 @@ void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
|
||||
|
||||
// -------- BLECharacteristicCallbacks methods
|
||||
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) {
|
||||
#else
|
||||
void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, esp_ble_gatts_cb_param_t* param) {
|
||||
#endif
|
||||
uint8_t* rxValue = pCharacteristic->getData();
|
||||
int len = pCharacteristic->getLength();
|
||||
|
||||
|
||||
@@ -40,16 +40,30 @@ protected:
|
||||
void onPassKeyNotify(uint32_t pass_key) override;
|
||||
bool onConfirmPIN(uint32_t pass_key) override;
|
||||
bool onSecurityRequest() override;
|
||||
#if defined(CONFIG_NIMBLE_ENABLED) // Arduino-ESP32 3.x on NimBLE (ESP32-S3 default)
|
||||
void onAuthenticationComplete(ble_gap_conn_desc* desc, int status) override;
|
||||
#else
|
||||
void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) override;
|
||||
#endif
|
||||
|
||||
// BLEServerCallbacks methods
|
||||
void onConnect(BLEServer* pServer) override;
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
void onConnect(BLEServer* pServer, ble_gap_conn_desc* desc) override;
|
||||
void onMtuChanged(BLEServer* pServer, ble_gap_conn_desc* desc, uint16_t mtu) override;
|
||||
#else
|
||||
void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) override;
|
||||
void onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) override;
|
||||
#endif
|
||||
void onDisconnect(BLEServer* pServer) override;
|
||||
|
||||
// BLECharacteristicCallbacks methods
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
void onWrite(BLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc) override;
|
||||
#else
|
||||
void onWrite(BLECharacteristic* pCharacteristic, esp_ble_gatts_cb_param_t* param) override;
|
||||
#endif
|
||||
void authDone(bool ok);
|
||||
|
||||
public:
|
||||
SerialBLEInterface() {
|
||||
|
||||
+70
-21
@@ -356,9 +356,13 @@ void genericBuzzer::setVolume(uint8_t level) {
|
||||
// too) are driven from the caller's thread, which owns the bus.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#include <driver/i2s.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include "ES8311.h"
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
#include <driver/i2s_std.h> // Arduino-ESP32 3.x (the legacy driver goes in IDF 6)
|
||||
#else
|
||||
#include <driver/i2s.h>
|
||||
#endif
|
||||
|
||||
#ifndef AUDIO_AMP_SETTLE_MS
|
||||
// Silence after amp power-up, so the first note isn't clipped. Seeed's
|
||||
@@ -367,7 +371,6 @@ void genericBuzzer::setVolume(uint8_t level) {
|
||||
#endif
|
||||
|
||||
static const int SAMPLE_RATE = 16000;
|
||||
static const i2s_port_t I2S_PORT = I2S_NUM_0;
|
||||
static const uint32_t AMP_LINGER_MS = 3000; // amp stays on between close sounds (no settle wait each time)
|
||||
static const uint32_t CLK_SETTLE_MS = 30; // codec clocked this long before the amp comes on
|
||||
// Note edges follow a raised cosine (a linear 3 ms ramp still ticked): the
|
||||
@@ -395,22 +398,51 @@ static int16_t peakFor(uint8_t level) {
|
||||
return PEAK[level < 5 ? level : 4];
|
||||
}
|
||||
|
||||
bool genericBuzzer::_i2sBegin() {
|
||||
// i2s_driver_install() crashes in IDF's cleanup when its DMA allocation
|
||||
// fails (PR #3381 saw a boot loop): don't try without clear headroom.
|
||||
if (heap_caps_get_free_size(MALLOC_CAP_DMA) < 32000) return false;
|
||||
for (int i = 0; i < 256; i++) s_sine[i] = (int16_t)(32767.0f * sinf(i * 2.0f * (float)M_PI / 256.0f));
|
||||
for (int i = 0; i <= 64; i++) s_ease[i] = (int16_t)(32767.0f * 0.5f * (1.0f - cosf(i * (float)M_PI / 64.0f)));
|
||||
|
||||
// ── The I2S channel: 16 kHz, 16-bit stereo, 6 DMA buffers of CHUNK frames,
|
||||
// MCLK = 256 fs; an underrun plays silence, not the last buffer again. ──────
|
||||
static const uint32_t WAIT_FOREVER = 0xFFFFFFFF;
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
static i2s_chan_handle_t s_tx = nullptr;
|
||||
static bool i2sInstall() {
|
||||
i2s_chan_config_t cc = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
cc.dma_desc_num = 6; // 48 ms queued: short, since the task keeps it topped up
|
||||
cc.dma_frame_num = CHUNK;
|
||||
cc.auto_clear_after_cb = true;
|
||||
if (i2s_new_channel(&cc, &s_tx, nullptr) != ESP_OK) return false;
|
||||
i2s_std_config_t sc = {};
|
||||
sc.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE);
|
||||
sc.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256;
|
||||
sc.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);
|
||||
sc.gpio_cfg.mclk = (gpio_num_t)PIN_I2S_MCLK;
|
||||
sc.gpio_cfg.bclk = (gpio_num_t)PIN_I2S_BCK;
|
||||
sc.gpio_cfg.ws = (gpio_num_t)PIN_I2S_WS;
|
||||
sc.gpio_cfg.dout = (gpio_num_t)PIN_I2S_DOUT;
|
||||
sc.gpio_cfg.din = I2S_GPIO_UNUSED;
|
||||
if (i2s_channel_init_std_mode(s_tx, &sc) != ESP_OK || i2s_channel_enable(s_tx) != ESP_OK) {
|
||||
i2s_del_channel(s_tx); s_tx = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static void i2sUninstall() { i2s_channel_disable(s_tx); i2s_del_channel(s_tx); s_tx = nullptr; }
|
||||
static void i2sWrite(const void* src, size_t n, uint32_t ms) {
|
||||
size_t w;
|
||||
i2s_channel_write(s_tx, src, n, &w, ms);
|
||||
}
|
||||
static void i2sStop() { i2s_channel_disable(s_tx); } // clocks off
|
||||
static void i2sRestart() { i2s_channel_enable(s_tx); } // auto-clear left the buffers silent
|
||||
#else
|
||||
static const i2s_port_t I2S_PORT = I2S_NUM_0;
|
||||
static bool i2sInstall() {
|
||||
i2s_config_t cfg = {};
|
||||
cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
|
||||
cfg.sample_rate = SAMPLE_RATE;
|
||||
cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
|
||||
cfg.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
cfg.communication_format = I2S_COMM_FORMAT_STAND_I2S;
|
||||
cfg.dma_buf_count = 6; // 48 ms queued: short, since the task keeps it topped up
|
||||
cfg.dma_buf_count = 6;
|
||||
cfg.dma_buf_len = CHUNK;
|
||||
cfg.tx_desc_auto_clear = true; // an underrun plays silence, not the last buffer again
|
||||
cfg.tx_desc_auto_clear = true;
|
||||
cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256;
|
||||
i2s_pin_config_t pins = {};
|
||||
pins.mck_io_num = PIN_I2S_MCLK;
|
||||
@@ -421,10 +453,29 @@ bool genericBuzzer::_i2sBegin() {
|
||||
if (i2s_driver_install(I2S_PORT, &cfg, 0, nullptr) != ESP_OK) return false;
|
||||
if (i2s_set_pin(I2S_PORT, &pins) != ESP_OK) { i2s_driver_uninstall(I2S_PORT); return false; }
|
||||
i2s_zero_dma_buffer(I2S_PORT);
|
||||
return true;
|
||||
}
|
||||
static void i2sUninstall() { i2s_driver_uninstall(I2S_PORT); }
|
||||
static void i2sWrite(const void* src, size_t n, uint32_t ms) {
|
||||
size_t w;
|
||||
i2s_write(I2S_PORT, src, n, &w, ms == WAIT_FOREVER ? portMAX_DELAY : pdMS_TO_TICKS(ms));
|
||||
}
|
||||
static void i2sStop() { i2s_stop(I2S_PORT); }
|
||||
static void i2sRestart() { i2s_zero_dma_buffer(I2S_PORT); i2s_start(I2S_PORT); }
|
||||
#endif
|
||||
|
||||
bool genericBuzzer::_i2sBegin() {
|
||||
// i2s_driver_install() crashes in IDF's cleanup when its DMA allocation
|
||||
// fails (PR #3381 saw a boot loop): don't try without clear headroom.
|
||||
if (heap_caps_get_free_size(MALLOC_CAP_DMA) < 32000) return false;
|
||||
for (int i = 0; i < 256; i++) s_sine[i] = (int16_t)(32767.0f * sinf(i * 2.0f * (float)M_PI / 256.0f));
|
||||
for (int i = 0; i <= 64; i++) s_ease[i] = (int16_t)(32767.0f * 0.5f * (1.0f - cosf(i * (float)M_PI / 64.0f)));
|
||||
|
||||
if (!i2sInstall()) return false;
|
||||
|
||||
// MCLK is running now, so the codec's clock tree comes up with it.
|
||||
if (!es8311::begin(Wire, BUZZER_CODEC_ES8311)) {
|
||||
i2s_driver_uninstall(I2S_PORT);
|
||||
i2sUninstall();
|
||||
return false;
|
||||
}
|
||||
_clk_on_ms = millis();
|
||||
@@ -445,7 +496,6 @@ void genericBuzzer::_taskLoop() {
|
||||
static const int16_t zeros[CHUNK * 2] = {0};
|
||||
static char mel[MEL_MAX];
|
||||
uint32_t done_req = 0;
|
||||
size_t w;
|
||||
for (;;) {
|
||||
// Between sounds, while the codec is clocked, keep the DMA queue full of
|
||||
// silence: a sound starting into a queue that had run dry could be played
|
||||
@@ -455,13 +505,13 @@ void genericBuzzer::_taskLoop() {
|
||||
uint32_t idle_since = millis();
|
||||
while (_req == done_req) {
|
||||
if (!_clk_running) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); idle_since = millis(); continue; }
|
||||
i2s_write(I2S_PORT, zeros, sizeof(zeros), &w, pdMS_TO_TICKS(50));
|
||||
i2sWrite(zeros, sizeof(zeros), 50);
|
||||
if (millis() - idle_since < AMP_LINGER_MS + 500) continue;
|
||||
bool stop = false;
|
||||
portENTER_CRITICAL(&s_mux);
|
||||
if (!_amp_on && !_amp_pending && _req == done_req) { _clk_running = false; stop = true; }
|
||||
portEXIT_CRITICAL(&s_mux);
|
||||
if (stop) i2s_stop(I2S_PORT); else idle_since = millis();
|
||||
if (stop) i2sStop(); else idle_since = millis();
|
||||
}
|
||||
ulTaskNotifyTake(pdTRUE, 0); // its request is being taken now
|
||||
for (;;) {
|
||||
@@ -475,8 +525,7 @@ void genericBuzzer::_taskLoop() {
|
||||
_note_idx = -1;
|
||||
if (stop || !mel[0]) { _task_playing = false; break; }
|
||||
if (!_clk_running) {
|
||||
i2s_zero_dma_buffer(I2S_PORT);
|
||||
i2s_start(I2S_PORT);
|
||||
i2sRestart();
|
||||
portENTER_CRITICAL(&s_mux);
|
||||
_clk_on_ms = millis();
|
||||
_clk_running = true; // loop() powers the amp once the codec settles
|
||||
@@ -487,7 +536,7 @@ void genericBuzzer::_taskLoop() {
|
||||
memset(buf, 0, sizeof(buf));
|
||||
for (uint32_t n = (uint32_t)settle * SAMPLE_RATE / 1000; n > 0 && !cut; ) {
|
||||
uint32_t k = n < CHUNK ? n : CHUNK;
|
||||
i2s_write(I2S_PORT, buf, k * 4, &w, portMAX_DELAY);
|
||||
i2sWrite(buf, k * 4, WAIT_FOREVER);
|
||||
n -= k;
|
||||
cut = _req != req;
|
||||
}
|
||||
@@ -517,7 +566,7 @@ void genericBuzzer::_taskLoop() {
|
||||
}
|
||||
buf[j * 2] = buf[j * 2 + 1] = s;
|
||||
}
|
||||
i2s_write(I2S_PORT, buf, k * 4, &w, portMAX_DELAY);
|
||||
i2sWrite(buf, k * 4, WAIT_FOREVER);
|
||||
cut = _req != req;
|
||||
}
|
||||
if (cut && freq && g) { // fade out from where the note was cut
|
||||
@@ -528,14 +577,14 @@ void genericBuzzer::_taskLoop() {
|
||||
buf[m * 2] = buf[m * 2 + 1] = (int16_t)((int32_t)s_sine[phase >> 24] * gj / 32767);
|
||||
phase += step;
|
||||
}
|
||||
i2s_write(I2S_PORT, buf, k * 4, &w, portMAX_DELAY);
|
||||
i2sWrite(buf, k * 4, WAIT_FOREVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
_note_idx = -1;
|
||||
if (cut) continue; // a newer request: take it at once
|
||||
// Let the DMA queue play out before reporting the melody done.
|
||||
for (int i = 0; i < 6; i++) i2s_write(I2S_PORT, zeros, sizeof(zeros), &w, portMAX_DELAY);
|
||||
for (int i = 0; i < 6; i++) i2sWrite(zeros, sizeof(zeros), WAIT_FOREVER);
|
||||
if (_req == req) _task_playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user