-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathesp-box.hpp
More file actions
548 lines (468 loc) · 20.8 KB
/
esp-box.hpp
File metadata and controls
548 lines (468 loc) · 20.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <driver/gpio.h>
#include <driver/i2s_std.h>
#include <driver/spi_master.h>
#include <hal/spi_types.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/stream_buffer.h>
#include <freertos/task.h>
#include "base_component.hpp"
#include "es7210.hpp"
#include "es8311.hpp"
#include "gt911.hpp"
#include "i2c.hpp"
#include "icm42607.hpp"
#include "interrupt.hpp"
#include "st7789.hpp"
#include "touchpad_input.hpp"
#include "tt21100.hpp"
namespace espp {
/// The EspBox class provides an interface to the ESP32-S3-BOX and
/// ESP32-S3-BOX-3 development boards.
///
/// The class provides access to the following features:
/// - Touchpad
/// - Display
/// - Audio
/// - Interrupts
/// - Buttons (boot and mute)
/// - I2C
/// - IMU (Inertial Measurement Unit), 6-axis ICM42607
///
/// The class is a singleton and can be accessed using the get() method.
///
/// \section esp_box_example Example
/// \snippet esp_box_example.cpp esp box example
class EspBox : public BaseComponent {
public:
/// Alias for the button callback function
using button_callback_t = espp::Interrupt::event_callback_fn;
/// Alias for the pixel type used by the ESP-Box display
using Pixel = lv_color16_t;
/// Alias for the display driver used by the ESP-Box display
using DisplayDriver = espp::St7789;
/// Alias for the touchpad data used by the ESP-Box touchpad
using TouchpadData = espp::TouchpadData;
/// Alias the IMU used by the ESP-Box
using Imu = espp::Icm42607<icm42607::Interface::I2C>;
/// Alias for the touch callback when touch events are received
using touch_callback_t = std::function<void(const TouchpadData &)>;
/// The type of the box
enum class BoxType {
UNKNOWN, ///< unknown box
BOX, ///< ESP32-S3-BOX
BOX3, ///< ESP32-S3-BOX-3
};
/// @brief Access the singleton instance of the EspBox class
/// @return Reference to the singleton instance of the EspBox class
static EspBox &get() {
static EspBox instance;
return instance;
}
EspBox(const EspBox &) = delete;
EspBox &operator=(const EspBox &) = delete;
EspBox(EspBox &&) = delete;
EspBox &operator=(EspBox &&) = delete;
/// Get the type of the box
/// \return The type of the box that was detected
/// \see BoxType
BoxType box_type() const { return box_type_; }
/// Get a reference to the internal I2C bus
/// \return A reference to the internal I2C bus
/// \note The internal I2C bus is used for the touchscreen and audio codec
I2c &internal_i2c() { return internal_i2c_; }
/// Get a reference to the interrupts
/// \return A reference to the interrupts
espp::Interrupt &interrupts() { return interrupts_; }
/////////////////////////////////////////////////////////////////////////////
// Touchpad
/////////////////////////////////////////////////////////////////////////////
/// Initialize the touchpad
/// \param callback The touchpad callback
/// \return true if the touchpad was successfully initialized, false otherwise
/// \warning This method should be called after the display has been
/// initialized if you want the touchpad to be recognized and used
/// with LVGL and its objects.
/// \note This will configure the touchpad interrupt pin which will
/// automatically call the touch callback function when the touchpad is
/// touched
bool initialize_touch(const touch_callback_t &callback = nullptr);
/// Get the touchpad input
/// \return A shared pointer to the touchpad input
std::shared_ptr<TouchpadInput> touchpad_input() const { return touchpad_input_; }
/// Get the most recent touchpad data
/// \return The touchpad data
TouchpadData touchpad_data() const { return touchpad_data_; }
/// Get the most recent touchpad data
/// \param num_touch_points The number of touch points
/// \param x The x coordinate
/// \param y The y coordinate
/// \param btn_state The button state (0 = button released, 1 = button pressed)
/// \note This method is a convenience method for integrating with LVGL, the
/// data it returns is identical to the data returned by the
/// touchpad_data() method
/// \see touchpad_data()
void touchpad_read(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, uint8_t *btn_state);
/// Convert touchpad data from raw reading to display coordinates
/// \param data The touchpad data to convert
/// \return The converted touchpad data
/// \note Uses the touch_invert_x and touch_invert_y settings to determine
/// if the x and y coordinates should be inverted
TouchpadData touchpad_convert(const TouchpadData &data) const;
/////////////////////////////////////////////////////////////////////////////
// Display
/////////////////////////////////////////////////////////////////////////////
/// Initialize the LCD (low level display driver)
/// \return true if the LCD was successfully initialized, false otherwise
bool initialize_lcd();
/// Initialize the display (lvgl display driver)
/// \param pixel_buffer_size The size of the pixel buffer
/// \return true if the display was successfully initialized, false otherwise
/// \note This will also allocate two full frame buffers in the SPIRAM
bool initialize_display(size_t pixel_buffer_size);
/// Get the width of the LCD in pixels
/// \return The width of the LCD in pixels
static constexpr size_t lcd_width() { return lcd_width_; }
/// Get the height of the LCD in pixels
/// \return The height of the LCD in pixels
static constexpr size_t lcd_height() { return lcd_height_; }
/// Get the GPIO pin for the LCD data/command signal
/// \return The GPIO pin for the LCD data/command signal
static constexpr auto get_lcd_dc_gpio() { return lcd_dc_io; }
/// Get a shared pointer to the display
/// \return A shared pointer to the display
std::shared_ptr<Display<Pixel>> display() const { return display_; }
/// Set the brightness of the backlight
/// \param brightness The brightness of the backlight as a percentage (0 - 100)
void brightness(float brightness);
/// Get the brightness of the backlight
/// \return The brightness of the backlight as a percentage (0 - 100)
float brightness() const;
/// Get the VRAM 0 pointer (DMA memory used by LVGL)
/// \return The VRAM 0 pointer
/// \note This is the memory used by LVGL for rendering
/// \note This is null unless initialize_display() has been called
Pixel *vram0() const;
/// Get the VRAM 1 pointer (DMA memory used by LVGL)
/// \return The VRAM 1 pointer
/// \note This is the memory used by LVGL for rendering
/// \note This is null unless initialize_display() has been called
Pixel *vram1() const;
/// Get the frame buffer 0 pointer
/// \return The frame buffer 0 pointer
/// \note This memory is designed to be used by the application developer and
/// is provided as a convenience. It is not used by the display driver.
/// \note This is null unless initialize_display() has been called
uint8_t *frame_buffer0() const;
/// Get the frame buffer 1 pointer
/// \return The frame buffer 1 pointer
/// \note This memory is designed to be used by the application developer and
/// is provided as a convenience. It is not used by the display driver.
/// \note This is null unless initialize_display() has been called
uint8_t *frame_buffer1() const;
/// Write command and optional parameters to the LCD
/// \param command The command to write
/// \param parameters The command parameters to write
/// \param user_data User data to pass to the spi transaction callback
/// \note This method is designed to be used by the display driver
/// \note This method queues the data to be written to the LCD, only blocking
/// if there is an ongoing SPI transaction
void write_command(uint8_t command, std::span<const uint8_t> parameters, uint32_t user_data);
/// Write a frame to the LCD
/// \param x The x coordinate
/// \param y The y coordinate
/// \param width The width of the frame, in pixels
/// \param height The height of the frame, in pixels
/// \param data The data to write
/// \note This method queues the data to be written to the LCD, only blocking
/// if there is an ongoing SPI transaction
void write_lcd_frame(const uint16_t x, const uint16_t y, const uint16_t width,
const uint16_t height, uint8_t *data);
/// Write lines to the LCD
/// \param xs The x start coordinate
/// \param ys The y start coordinate
/// \param xe The x end coordinate
/// \param ye The y end coordinate
/// \param data The data to write
/// \param user_data User data to pass to the spi transaction callback
/// \note This method queues the data to be written to the LCD, only blocking
/// if there is an ongoing SPI transaction
void write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, uint32_t user_data);
/////////////////////////////////////////////////////////////////////////////
// Button
/////////////////////////////////////////////////////////////////////////////
/// Initialize the boot button (side of the box)
/// \param callback The callback function to call when the button is pressed
/// \return true if the button was successfully initialized, false otherwise
bool initialize_boot_button(const button_callback_t &callback = nullptr);
/// Get the boot button state
/// \return The button state (true = button pressed, false = button released)
bool boot_button_state() const;
/// Initialize the mute button (top of the box)
/// \param callback The callback function to call when the button is pressed
/// \return true if the button was successfully initialized, false otherwise
bool initialize_mute_button(const button_callback_t &callback = nullptr);
/// Get the mute button state
/// \return The button state (true = button pressed, false = button released)
bool mute_button_state() const;
/////////////////////////////////////////////////////////////////////////////
// Audio
/////////////////////////////////////////////////////////////////////////////
/// Get the GPIO pin for the mute button (top of the box)
/// \return The GPIO pin for the mute button
static constexpr auto get_mute_pin() { return mute_pin; }
/// Initialize the sound subsystem
/// \param default_audio_rate The default audio rate
/// \param task_config The task configuration for the audio task
/// \return true if the sound subsystem was successfully initialized, false
/// otherwise
bool initialize_sound(uint32_t default_audio_rate = 48000,
const espp::Task::BaseConfig &task_config = {
.name = "audio",
.stack_size_bytes = 4096,
.priority = 19,
.core_id = 1,
});
/// Enable or disable sound
/// \note This method sets the power pin to the appropriate value
void enable_sound(bool enable);
/// Get the audio sample rate
/// \return The audio sample rate, in Hz
uint32_t audio_sample_rate() const;
/// Set the audio sample rate
/// \param sample_rate The audio sample rate, in Hz
void audio_sample_rate(uint32_t sample_rate);
/// Get the audio buffer size
/// \return The audio buffer size, in bytes
size_t audio_buffer_size() const;
/// Mute or unmute the audio
/// \param mute true to mute the audio, false to unmute the audio
void mute(bool mute);
/// Check if the audio is muted
/// \return true if the audio is muted, false otherwise
bool is_muted() const;
/// Set the volume
/// \param volume The volume in percent (0 - 100)
void volume(float volume);
/// Get the volume
/// \return The volume in percent (0 - 100)
float volume() const;
/// Play audio
/// \param data The audio data to play
void play_audio(const std::vector<uint8_t> &data);
/// Play audio
/// \param data The audio data to play
/// \param num_bytes The number of bytes to play
void play_audio(const uint8_t *data, uint32_t num_bytes);
/////////////////////////////////////////////////////////////////////////////
// IMU
/////////////////////////////////////////////////////////////////////////////
/// Initialize the IMU
/// \param orientation_filter The orientation filter, if provided
/// \param imu_config The IMU configuration
/// \return true if the IMU was successfully initialized, false otherwise
bool initialize_imu(const Imu::filter_fn &orientation_filter = nullptr,
const Imu::ImuConfig &imu_config = {
.accelerometer_range = Imu::AccelerometerRange::RANGE_2G,
.accelerometer_odr = Imu::AccelerometerODR::ODR_400_HZ,
.gyroscope_range = Imu::GyroscopeRange::RANGE_2000DPS,
.gyroscope_odr = Imu::GyroscopeODR::ODR_400_HZ,
});
/// Get the IMU
/// \return A shared pointer to the IMU
std::shared_ptr<Imu> imu() const;
protected:
EspBox();
void detect();
bool initialize_codec();
bool initialize_i2s(uint32_t default_audio_rate);
bool update_touch();
bool update_gt911();
bool update_tt21100();
void update_volume_output();
bool audio_task_callback(std::mutex &m, std::condition_variable &cv, bool &task_notified);
void lcd_wait_lines();
// box 3:
struct box3 {
static constexpr gpio_num_t backlight_io = GPIO_NUM_47; // was 45 on ESP32-S3-BOX
static constexpr bool reset_value = true; // was false on ESP32-S3-BOX
static constexpr gpio_num_t i2s_ws_io = GPIO_NUM_45; // was 47 on ESP32-S3-BOX
static constexpr bool touch_invert_x = false; // was true on ESP32-S3-BOX
static constexpr auto touch_interrupt_level = espp::Interrupt::ActiveLevel::HIGH;
static constexpr auto touch_interrupt_type = espp::Interrupt::Type::RISING_EDGE;
static constexpr auto touch_interrupt_pullup_enabled = false;
}; // struct box3
// box:
struct box {
static constexpr gpio_num_t backlight_io = GPIO_NUM_45;
static constexpr bool reset_value = false;
static constexpr gpio_num_t i2s_ws_io = GPIO_NUM_47;
static constexpr bool touch_invert_x = true;
static constexpr auto touch_interrupt_level = espp::Interrupt::ActiveLevel::LOW;
static constexpr auto touch_interrupt_type = espp::Interrupt::Type::FALLING_EDGE;
static constexpr auto touch_interrupt_pullup_enabled = true;
}; // struct box
// set by the detect() method using the box3 and box namespaces
gpio_num_t backlight_io;
bool reset_value;
gpio_num_t i2s_ws_io;
bool touch_invert_x;
espp::Interrupt::ActiveLevel touch_interrupt_level = box::touch_interrupt_level;
espp::Interrupt::Type touch_interrupt_type;
bool touch_interrupt_pullup_enabled;
// common:
// button (boot button)
static constexpr gpio_num_t boot_button_io = GPIO_NUM_0; // active low
// internal i2c (touchscreen, audio codec)
static constexpr auto internal_i2c_port = I2C_NUM_0;
static constexpr auto internal_i2c_clock_speed = 400 * 1000;
static constexpr gpio_num_t internal_i2c_sda = GPIO_NUM_8;
static constexpr gpio_num_t internal_i2c_scl = GPIO_NUM_18;
// LCD
static constexpr size_t lcd_width_ = 320;
static constexpr size_t lcd_height_ = 240;
static constexpr size_t lcd_bytes_per_pixel = 2;
static constexpr size_t frame_buffer_size = (((lcd_width_)*lcd_bytes_per_pixel) * lcd_height_);
static constexpr int lcd_clock_speed = 60 * 1000 * 1000;
static constexpr auto lcd_spi_num = SPI2_HOST;
static constexpr gpio_num_t lcd_cs_io = GPIO_NUM_5;
static constexpr gpio_num_t lcd_mosi_io = GPIO_NUM_6;
static constexpr gpio_num_t lcd_sclk_io = GPIO_NUM_7;
static constexpr gpio_num_t lcd_reset_io = GPIO_NUM_48;
static constexpr gpio_num_t lcd_dc_io = GPIO_NUM_4;
static constexpr bool backlight_value = true;
static constexpr bool invert_colors = true;
static constexpr auto rotation = espp::DisplayRotation::LANDSCAPE;
static constexpr bool mirror_x = true;
static constexpr bool mirror_y = true;
static constexpr bool swap_xy = false;
static constexpr bool swap_color_order = true;
// touch
static constexpr bool touch_swap_xy = false;
static constexpr bool touch_invert_y = false;
static constexpr gpio_num_t touch_interrupt = GPIO_NUM_3;
// sound
static constexpr gpio_num_t sound_power_pin = GPIO_NUM_46;
static constexpr auto i2s_port = I2S_NUM_0;
static constexpr gpio_num_t i2s_mck_io = GPIO_NUM_2;
static constexpr gpio_num_t i2s_bck_io = GPIO_NUM_17;
static constexpr gpio_num_t i2s_do_io = GPIO_NUM_15;
static constexpr gpio_num_t i2s_di_io = GPIO_NUM_16;
static constexpr gpio_num_t mute_pin = GPIO_NUM_1;
static constexpr int NUM_CHANNELS = 2;
static constexpr int NUM_BYTES_PER_CHANNEL = 2;
static constexpr int UPDATE_FREQUENCY = 60;
static constexpr int calc_audio_buffer_size(int sample_rate) {
return sample_rate * NUM_CHANNELS * NUM_BYTES_PER_CHANNEL / UPDATE_FREQUENCY;
}
BoxType box_type_{BoxType::UNKNOWN};
// TODO: allow core id configuration
I2c internal_i2c_{{.port = internal_i2c_port,
.sda_io_num = internal_i2c_sda,
.scl_io_num = internal_i2c_scl,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE}};
espp::Interrupt::PinConfig boot_button_interrupt_pin_{
.gpio_num = boot_button_io,
.callback =
[this](const auto &event) {
if (boot_button_callback_) {
boot_button_callback_(event);
}
},
.active_level = espp::Interrupt::ActiveLevel::LOW,
.interrupt_type = espp::Interrupt::Type::ANY_EDGE,
.pullup_enabled = true};
espp::Interrupt::PinConfig mute_button_interrupt_pin_{
.gpio_num = mute_pin,
.callback =
[this](const auto &event) {
mute(event.active);
if (mute_button_callback_) {
mute_button_callback_(event);
}
},
.active_level = espp::Interrupt::ActiveLevel::LOW,
.interrupt_type = espp::Interrupt::Type::ANY_EDGE,
.pullup_enabled = true};
// NOTE: the active level, interrupt type, and pullup configuration is set by
// detect(), since it depends on the box type
espp::Interrupt::PinConfig touch_interrupt_pin_{
.gpio_num = touch_interrupt,
.callback =
[this](const auto &event) {
if (update_touch()) {
if (touch_callback_) {
touch_callback_(touchpad_data());
}
}
},
.active_level = touch_interrupt_level,
.filter_type = espp::Interrupt::FilterType::PIN_GLITCH_FILTER,
};
// we'll only add each interrupt pin if the initialize method is called
espp::Interrupt interrupts_{
{.interrupts = {},
.task_config = {.name = "esp-box interrupts",
.stack_size_bytes = CONFIG_ESP_BOX_INTERRUPT_STACK_SIZE}}};
// button
std::atomic<bool> boot_button_initialized_{false};
button_callback_t boot_button_callback_{nullptr};
std::atomic<bool> mute_button_initialized_{false};
button_callback_t mute_button_callback_{nullptr};
// touch
std::shared_ptr<Gt911> gt911_; // only used on ESP32-S3-BOX-3
std::shared_ptr<Tt21100> tt21100_; // only used on ESP32-S3-BOX
std::shared_ptr<TouchpadInput> touchpad_input_;
std::recursive_mutex touchpad_data_mutex_;
TouchpadData touchpad_data_;
touch_callback_t touch_callback_{nullptr};
// display
std::shared_ptr<Display<Pixel>> display_;
/// SPI bus for communication with the LCD
spi_bus_config_t lcd_spi_bus_config_;
spi_device_interface_config_t lcd_config_;
spi_device_handle_t lcd_handle_{nullptr};
static constexpr int spi_queue_size = 6;
spi_transaction_t trans[spi_queue_size];
std::atomic<int> num_queued_trans = 0;
uint8_t *frame_buffer0_{nullptr};
uint8_t *frame_buffer1_{nullptr};
// sound
std::atomic<bool> sound_initialized_{false};
std::atomic<float> volume_{50.0f};
std::atomic<bool> mute_{false};
std::unique_ptr<espp::Task> audio_task_{nullptr};
// i2s / low-level audio
i2s_chan_handle_t audio_tx_handle{nullptr};
std::vector<uint8_t> audio_tx_buffer;
StreamBufferHandle_t audio_tx_stream;
i2s_std_config_t audio_std_cfg;
i2s_event_callbacks_t audio_tx_callbacks_;
std::atomic<bool> has_sound{false};
// IMU
std::shared_ptr<Imu> imu_;
}; // class EspBox
} // namespace espp
// for easy printing of BoxType using libfmt
template <> struct fmt::formatter<espp::EspBox::BoxType> : fmt::formatter<std::string> {
template <typename FormatContext> auto format(espp::EspBox::BoxType c, FormatContext &ctx) const {
std::string name;
switch (c) {
case espp::EspBox::BoxType::UNKNOWN:
name = "UNKNOWN";
break;
case espp::EspBox::BoxType::BOX:
name = "BOX";
break;
case espp::EspBox::BoxType::BOX3:
name = "BOX3";
break;
}
return formatter<std::string>::format(name, ctx);
}
};