Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions components/m5stack-tab5/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,30 @@ the 1.8 MB rotation scratch buffer is not needed either). The frame buffers are
cache-line aligned, so they also satisfy a raised `LV_DRAW_BUF_ALIGN` (e.g. 128
for the PPA draw unit), which a plain `heap_caps_malloc()` draw buffer may not.

## Saving internal DMA-capable RAM

Applications that are short of internal DMA-capable RAM can skip the parts
they don't use. The SD card driver, for example, needs some of that RAM for
unaligned transfers.

- `initialize_audio({.playback_enabled = ..., .recording_enabled = ...})`
sets up only the directions it is asked for. Each I2S channel has a DMA
ring of about 11.5 KB and its own task, whose stack is in internal RAM.
Without recording there is no ES7210 setup and `start_audio_recording()`
fails. Without playback there is no ES8388 setup and `play_audio()` queues
nothing.
- `initialize_lcd({.dma2d_enabled = false})` keeps CPU copies for
`esp_lcd_panel_draw_bitmap()` instead of the DMA2D engine, which saves
about 3 KB. Rotated LVGL flushes don't use `draw_bitmap()` (the PPA rotates
them straight into the frame buffer), so an application that always
rotates loses nothing.

```cpp
auto &tab5 = espp::M5StackTab5::get();
tab5.initialize_lcd({.dma2d_enabled = false}); // always rotated
tab5.initialize_audio({.recording_enabled = false}); // playback only
```

## Example

The [example](./example) shows how to use the `espp::M5StackTab5` hardware abstraction component to initialize and use various subsystems of the Tab5.
Expand Down
67 changes: 65 additions & 2 deletions components/m5stack-tab5/include/m5stack-tab5.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,30 @@ class M5StackTab5 : public BaseComponent {
// Display & Touchpad
/////////////////////////////////////////////////////////////////////////////

/// How initialize_lcd() sets the panel up
struct LcdConfig {
/// Let the DPI panel copy esp_lcd_panel_draw_bitmap() areas into its
/// frame buffer with the DMA2D engine instead of the CPU (on ESP-IDF 6.0
/// and later only on the ST7123 / ST7121 panels: the ILI9881 panel keeps
/// CPU copies). That takes the copies' PSRAM traffic off the CPU, which
/// otherwise can starve the panel's scan-out DMA, but DMA2D costs about
/// 3 KB of internal DMA-capable RAM. Only unrotated LVGL flushes,
/// write_lcd_lines() and the software rotation fallback use
/// draw_bitmap(): rotated flushes go through the PPA straight into the
/// frame buffer, so an application that always rotates (e.g. landscape)
/// can turn this off and lose nothing.
bool dma2d_enabled{true};
};

/// Initialize the LCD (low level display driver, MIPI-DSI + ST7703)
/// \return true if the LCD was successfully initialized, false otherwise
bool initialize_lcd();

/// Initialize the LCD (low level display driver, MIPI-DSI + ST7703)
/// \param config How to set the panel up
/// \return true if the LCD was successfully initialized, false otherwise
bool initialize_lcd(const LcdConfig &config);

/// How initialize_display() sets LVGL up
struct DisplayConfig {
/// Size of each of LVGL's two draw buffers, in pixels. They are allocated
Expand Down Expand Up @@ -448,7 +468,31 @@ class M5StackTab5 : public BaseComponent {
// Audio System
/////////////////////////////////////////////////////////////////////////////

/// Initialize the dual audio system (ES8388 codec + ES7210 AEC)
/// How initialize_audio() sets the audio system up
struct AudioConfig {
/// The audio sample rate, in Hz
uint32_t sample_rate{48000};
/// Set up playback: the I2S TX channel, the ES8388 DAC, the speaker and
/// the playback task. Without it play_audio() queues nothing.
bool playback_enabled{true};
/// Set up recording: the I2S RX channel, the ES7210 microphones and the
/// microphone task. Without it start_audio_recording() fails.
/// \note Each I2S channel has a DMA ring of about 11.5 KB of internal
/// DMA-capable RAM, and each task's stack is in internal RAM too,
/// so an application that only plays (or only records) saves that
/// memory by turning the other direction off.
bool recording_enabled{true};
/// The playback task's configuration; the microphone task uses it too,
/// with " microphone" appended to the name
espp::Task::BaseConfig task_config{.name = "tab5_audio",
.stack_size_bytes =
CONFIG_M5STACK_TAB5_AUDIO_TASK_STACK_SIZE,
.priority = 20,
.core_id = 1};
};

/// Initialize the audio system (ES8388 codec + ES7210 AEC) for playback and
/// recording
/// \param sample_rate The audio sample rate (default 48kHz)
/// \param task_config The task configuration for the audio task
/// \return true if the audio system was successfully initialized, false otherwise
Expand All @@ -459,12 +503,21 @@ class M5StackTab5 : public BaseComponent {
.priority = 20,
.core_id = 1});

/// Initialize the audio system (ES8388 codec + ES7210 AEC)
/// \param config The sample rate, which directions to set up, and the task
/// configuration
/// \return true if the audio system was successfully initialized, false
/// otherwise (also when neither playback nor recording is enabled)
bool initialize_audio(const AudioConfig &config);

/// Enable or disable the audio system
/// \param enable True to enable, false to disable
void enable_audio(bool enable);

/// Set the audio volume
/// \param volume The volume as a percentage (0-100)
/// \note Without playback (AudioConfig::playback_enabled) the value is only
/// stored.
void volume(float volume);

/// Get the audio volume
Expand All @@ -473,6 +526,8 @@ class M5StackTab5 : public BaseComponent {

/// Mute or unmute the audio
/// \param mute True to mute, false to unmute
/// \note Without playback (AudioConfig::playback_enabled) the value is only
/// stored.
void mute(bool mute);

/// Check if audio is muted
Expand Down Expand Up @@ -516,7 +571,8 @@ class M5StackTab5 : public BaseComponent {
/// \param callback Function to call with recorded audio data: 16-bit
/// signed interleaved stereo (ES7210 microphone 1 on the left slot,
/// microphone 2 on the right) at audio_sample_rate()
/// \return True if recording started successfully
/// \return True if recording started successfully (false, among other
/// reasons, when initialize_audio() did not enable recording)
/// \note The callback runs in the dedicated microphone task's context (a
/// separate task from audio playback), so keep it quick and non-
/// blocking and size that task's stack accordingly
Expand All @@ -528,6 +584,8 @@ class M5StackTab5 : public BaseComponent {
/// Set the microphone volume
/// \param volume The volume as a percentage (0 - 100), mapped onto the
/// ES7210 analog microphone gain range (0 dB - +37.5 dB)
/// \note Without recording (AudioConfig::recording_enabled) the value is
/// only stored.
void microphone_volume(float volume);

/// Get the microphone volume
Expand Down Expand Up @@ -1046,6 +1104,11 @@ class M5StackTab5 : public BaseComponent {

// Audio system
std::atomic<bool> audio_initialized_{false};
// the directions initialize_audio() set up (written before
// audio_initialized_ is set; true until then, so that volume() and mute()
// keep reaching the ES8388 before initialization as they always have)
bool audio_playback_enabled_{true};
bool audio_recording_enabled_{true};
std::atomic<float> volume_{50.0f};
// microphone volume (percent), mapped onto the ES7210 analog gain range
std::atomic<float> mic_volume_{70.0f};
Expand Down
Loading
Loading