From e1ff22e4d6ecc3f94cb5626c2ca78eb84d856cb5 Mon Sep 17 00:00:00 2001 From: LILYGO_L <135582120+Llgok@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:58:14 +0800 Subject: [PATCH] Adapt for LilyGO-T-Circle-S3 device (#89) * Adapt for LilyGO-T-Circle-S3 device * Adapt for LilyGO-T-Circle-S3 device * Remove comments and modify the size of the lilygo-t-circle-s3 image * Modify the code style and format to Google C++ * Modify the code style and format to Google C++ --------- Co-authored-by: Xiaoxia --- README.md | 4 + README_en.md | 4 + README_ja.md | 4 + docs/lilygo-t-circle-s3.jpg | Bin 0 -> 48137 bytes main/CMakeLists.txt | 4 + main/Kconfig.projbuild | 2 + main/audio_codecs/tcircles3_audio_codec.cc | 140 +++++++ main/audio_codecs/tcircles3_audio_codec.h | 38 ++ main/boards/lilygo-t-circle-s3/config.h | 48 +++ .../lilygo-t-circle-s3/esp_lcd_gc9d01n.c | 353 ++++++++++++++++++ .../lilygo-t-circle-s3/esp_lcd_gc9d01n.h | 99 +++++ .../lilygo-t-circle-s3/lilygo-t-circle-s3.cc | 233 ++++++++++++ main/boards/lilygo-t-circle-s3/pin_config.h | 47 +++ 13 files changed, 976 insertions(+) create mode 100644 docs/lilygo-t-circle-s3.jpg create mode 100644 main/audio_codecs/tcircles3_audio_codec.cc create mode 100644 main/audio_codecs/tcircles3_audio_codec.h create mode 100644 main/boards/lilygo-t-circle-s3/config.h create mode 100644 main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c create mode 100644 main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h create mode 100644 main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc create mode 100644 main/boards/lilygo-t-circle-s3/pin_config.h diff --git a/README.md b/README.md index 447f262..447ed72 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ - 神奇按钮 2.4 - 虾哥 Mini C3 - 微雪电子 ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3
@@ -73,6 +74,9 @@ + + +
## 固件部分 diff --git a/README_en.md b/README_en.md index aee7409..d396472 100644 --- a/README_en.md +++ b/README_en.md @@ -53,6 +53,7 @@ Breadboard setup shown below: - MagiClick 2.4 - Xmini C3 - Waveshare ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3
@@ -73,6 +74,9 @@ Breadboard setup shown below: + + +
## Firmware Section diff --git a/README_ja.md b/README_ja.md index 93a0a8b..a659df8 100644 --- a/README_ja.md +++ b/README_ja.md @@ -53,6 +53,7 @@ - MagiClick 2.4 - Xmini C3 - Waveshare ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3
@@ -73,6 +74,9 @@ + + +
## ファームウェアセクション diff --git a/docs/lilygo-t-circle-s3.jpg b/docs/lilygo-t-circle-s3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..45985d878adda2598cfcded37f1fa32306a6d56b GIT binary patch literal 48137 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 F698*=H(dY# literal 0 HcmV?d00001 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 9cc3963..e194ee7 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -3,12 +3,14 @@ set(SOURCES "audio_codecs/audio_codec.cc" "audio_codecs/box_audio_codec.cc" "audio_codecs/es8311_audio_codec.cc" "audio_codecs/cores3_audio_codec.cc" + "audio_codecs/tcircles3_audio_codec.cc" "led/single_led.cc" "led/circular_strip.cc" "display/display.cc" "display/no_display.cc" "display/lcd_display.cc" "display/ssd1306_display.cc" + "boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c" "protocols/protocol.cc" "protocols/mqtt_protocol.cc" "protocols/websocket_protocol.cc" @@ -68,6 +70,8 @@ elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8) set(BOARD_TYPE "esp32-s3-touch-amoled-1.8") elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_LCD) set(BOARD_TYPE "bread-compact-wifi-lcd") +elseif(CONFIG_BOARD_TYPE_LILYGO_T_CIRCLE_S3) + set(BOARD_TYPE "lilygo-t-circle-s3") endif() file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc) list(APPEND SOURCES ${BOARD_SOURCES}) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 3b5bdfc..71b8467 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -70,6 +70,8 @@ choice BOARD_TYPE bool "ESP-SparkBot开发板" config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8 bool "Waveshare ESP32-S3-Touch-AMOLED-1.8" + config BOARD_TYPE_LILYGO_T_CIRCLE_S3 + bool "LILYGO T-Circle-S3" endchoice choice DISPLAY_LCD_TYPE diff --git a/main/audio_codecs/tcircles3_audio_codec.cc b/main/audio_codecs/tcircles3_audio_codec.cc new file mode 100644 index 0000000..32ce43f --- /dev/null +++ b/main/audio_codecs/tcircles3_audio_codec.cc @@ -0,0 +1,140 @@ +#include "tcircles3_audio_codec.h" + +#include +#include +#include +#include +#include + +static const char TAG[] = "Tcircles3AudioCodec"; + +Tcircles3AudioCodec::Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference){ + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); + + gpio_config_t config; + config.pin_bit_mask = BIT64(GPIO_NUM_45); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + config.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config); + gpio_set_level(GPIO_NUM_45, 0); + ESP_LOGI(TAG, "Tcircles3AudioCodec initialized"); +} + +Tcircles3AudioCodec::~Tcircles3AudioCodec(){ + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Tcircles3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data){ + + i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER); + spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + + ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); + ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); + + i2s_std_config_t mic_config ={ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(static_cast(input_sample_rate_)), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = mic_bclk, + .ws = mic_ws, + .dout = I2S_GPIO_UNUSED, + .din = mic_data, + .invert_flags ={ + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + } + } + }; + + i2s_std_config_t spkr_config ={ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(static_cast(11025)), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = spkr_bclk, + .ws = spkr_lrclk, + .dout = spkr_data, + .din = I2S_GPIO_UNUSED, + .invert_flags ={ + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); + ESP_LOGI(TAG, "Voice hardware created"); +} + +void Tcircles3AudioCodec::SetOutputVolume(int volume){ + volume_ = volume; + AudioCodec::SetOutputVolume(volume); +} + +void Tcircles3AudioCodec::EnableInput(bool enable){ + if (enable){ + }else{ + } + AudioCodec::EnableInput(enable); +} + +void Tcircles3AudioCodec::EnableOutput(bool enable){ + if (enable){ + gpio_set_level(GPIO_NUM_45, 1); + }else{ + gpio_set_level(GPIO_NUM_45, 0); + } + AudioCodec::EnableOutput(enable); +} + +int Tcircles3AudioCodec::Read(int16_t *dest, int samples){ + if (input_enabled_){ + size_t bytes_read; + i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + } + return samples; +} + +void AdjustVolume(const int16_t *input_data, int16_t *output_data, size_t samples, float volume){ + for (size_t i = 0; i < samples; i++){ + output_data[i] = (float)input_data[i] * volume; + } +} + +int Tcircles3AudioCodec::Write(const int16_t *data, int samples){ + if (output_enabled_){ + size_t bytes_read; + auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); + AdjustVolume(data, output_data, samples, (float)(volume_ / 100.0)); + i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + free(output_data); + } + return samples; +} diff --git a/main/audio_codecs/tcircles3_audio_codec.h b/main/audio_codecs/tcircles3_audio_codec.h new file mode 100644 index 0000000..3db28e7 --- /dev/null +++ b/main/audio_codecs/tcircles3_audio_codec.h @@ -0,0 +1,38 @@ +#ifndef _TCIRCLES3_AUDIO_CODEC_H +#define _TCIRCLES3_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class Tcircles3AudioCodec : public AudioCodec +{ +private: + const audio_codec_data_if_t *data_if_ = nullptr; + const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; + const audio_codec_if_t *out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; + const audio_codec_if_t *in_codec_if_ = nullptr; + const audio_codec_gpio_if_t *gpio_if_ = nullptr; + + uint32_t volume_ = 70; + + void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); + + virtual int Read(int16_t *dest, int samples) override; + virtual int Write(const int16_t *data, int samples) override; + +public: + Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference); + virtual ~Tcircles3AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/lilygo-t-circle-s3/config.h b/main/boards/lilygo-t-circle-s3/config.h new file mode 100644 index 0000000..e115c77 --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/config.h @@ -0,0 +1,48 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// M5Stack CoreS3 Board configuration + +#include +#include "pin_config.h" + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_MIC_I2S_GPIO_BCLK static_cast(MSM261_BCLK) +#define AUDIO_MIC_I2S_GPIO_WS static_cast(MSM261_WS) +#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MSM261_DATA) + +#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) +#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) +#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) +#define AUDIO_SPKR_ENABLE static_cast(MAX98357A_SD_MODE) + +#define TOUCH_I2C_SDA_PIN static_cast(TP_SDA) +#define TOUCH_I2C_SCL_PIN static_cast(TP_SCL) + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH LCD_WIDTH +#define DISPLAY_HEIGHT LCD_HEIGHT +#define DISPLAY_MOSI LCD_MOSI +#define DISPLAY_SCLK LCD_SCLK +#define DISPLAY_DC LCD_DC +#define DISPLAY_RST LCD_RST +#define DISPLAY_CS LCD_CS +#define DISPLAY_BL static_cast(LCD_BL) +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c new file mode 100644 index 0000000..25a7867 --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c @@ -0,0 +1,353 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_check.h" + +#include "esp_lcd_gc9d01n.h" + +static const char *TAG = "gc9d01n"; + +static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct{ + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const gc9d01n_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} gc9d01n_panel_t; + +esp_err_t esp_lcd_new_panel_gc9d01n(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel){ + esp_err_t ret = ESP_OK; + gc9d01n_panel_t *gc9d01n = NULL; + gpio_config_t io_conf = {0}; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + gc9d01n = (gc9d01n_panel_t *)calloc(1, sizeof(gc9d01n_panel_t)); + ESP_GOTO_ON_FALSE(gc9d01n, ESP_ERR_NO_MEM, err, TAG, "no mem for gc9d01n panel"); + + if (panel_dev_config->reset_gpio_num >= 0){ + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space){ + case ESP_LCD_COLOR_SPACE_RGB: + gc9d01n->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#else + switch (panel_dev_config->rgb_endian){ + case LCD_RGB_ENDIAN_RGB: + gc9d01n->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel){ + case 16: // RGB565 + gc9d01n->colmod_val = 0x55; + gc9d01n->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + gc9d01n->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + gc9d01n->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + gc9d01n->io = io; + gc9d01n->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9d01n->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config){ + gc9d01n->init_cmds = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + gc9d01n->init_cmds_size = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + gc9d01n->base.del = panel_gc9d01n_del; + gc9d01n->base.reset = panel_gc9d01n_reset; + gc9d01n->base.init = panel_gc9d01n_init; + gc9d01n->base.draw_bitmap = panel_gc9d01n_draw_bitmap; + gc9d01n->base.invert_color = panel_gc9d01n_invert_color; + gc9d01n->base.set_gap = panel_gc9d01n_set_gap; + gc9d01n->base.mirror = panel_gc9d01n_mirror; + gc9d01n->base.swap_xy = panel_gc9d01n_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + gc9d01n->base.disp_off = panel_gc9d01n_disp_on_off; +#else + gc9d01n->base.disp_on_off = panel_gc9d01n_disp_on_off; +#endif + *ret_panel = &(gc9d01n->base); + ESP_LOGD(TAG, "new gc9d01n panel @%p", gc9d01n); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9D01N_VER_MAJOR, ESP_LCD_GC9D01N_VER_MINOR, + // ESP_LCD_GC9D01N_VER_PATCH); + + return ESP_OK; + +err: + if (gc9d01n){ + if (panel_dev_config->reset_gpio_num >= 0){ + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9d01n); + } + return ret; +} + +static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + + if (gc9d01n->reset_gpio_num >= 0){ + gpio_reset_pin(gc9d01n->reset_gpio_num); + } + ESP_LOGD(TAG, "del gc9d01n panel @%p", gc9d01n); + free(gc9d01n); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + // perform hardware reset + if (gc9d01n->reset_gpio_num >= 0){ + gpio_set_level(gc9d01n->reset_gpio_num, gc9d01n->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9d01n->reset_gpio_num, !gc9d01n->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } + else{ // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +static const gc9d01n_lcd_init_cmd_t vendor_specific_init_default[] = { + // {cmd, { data }, data_size, delay_ms} + // Enable Inter Register + {0xFE, (uint8_t[]){0x00}, 0, 0}, + {0xEF, (uint8_t[]){0x00}, 0, 0}, + {0x80, (uint8_t[]){0xFF}, 1, 0}, + {0x81, (uint8_t[]){0xFF}, 1, 0}, + {0x82, (uint8_t[]){0xFF}, 1, 0}, + {0x84, (uint8_t[]){0xFF}, 1, 0}, + {0x85, (uint8_t[]){0xFF}, 1, 0}, + {0x86, (uint8_t[]){0xFF}, 1, 0}, + {0x87, (uint8_t[]){0xFF}, 1, 0}, + {0x88, (uint8_t[]){0xFF}, 1, 0}, + {0x89, (uint8_t[]){0xFF}, 1, 0}, + {0x8A, (uint8_t[]){0xFF}, 1, 0}, + {0x8B, (uint8_t[]){0xFF}, 1, 0}, + {0x8C, (uint8_t[]){0xFF}, 1, 0}, + {0x8D, (uint8_t[]){0xFF}, 1, 0}, + {0x8E, (uint8_t[]){0xFF}, 1, 0}, + {0x8F, (uint8_t[]){0xFF}, 1, 0}, + {0x3A, (uint8_t[]){0x05}, 1, 0}, + {0xEC, (uint8_t[]){0x01}, 1, 0}, + {0x74, (uint8_t[]){0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x98, (uint8_t[]){0x3E}, 1, 0}, + {0x99, (uint8_t[]){0x3E}, 1, 0}, + {0xB5, (uint8_t[]){0x0D, 0x0D}, 2, 0}, + {0x60, (uint8_t[]){0x38, 0x0F, 0x79, 0x67}, 4, 0}, + {0x61, (uint8_t[]){0x38, 0x11, 0x79, 0x67}, 4, 0}, + {0x64, (uint8_t[]){0x38, 0x17, 0x71, 0x5F, 0x79, 0x67}, 6, 0}, + {0x65, (uint8_t[]){0x38, 0x13, 0x71, 0x5B, 0x79, 0x67}, 6, 0}, + {0x6A, (uint8_t[]){0x00, 0x00}, 2, 0}, + {0x6C, (uint8_t[]){0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50}, 7, 0}, + {0x6E, (uint8_t[]){0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, 0x10, 0x10, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04}, 32, 0}, + {0xBF, (uint8_t[]){0x01}, 1, 0}, + {0xF9, (uint8_t[]){0x40}, 1, 0}, + {0x9B, (uint8_t[]){0x3B, 0x93, 0x33, 0x7F, 0x00}, 5, 0}, + {0x7E, (uint8_t[]){0x30}, 1, 0}, + {0x70, (uint8_t[]){0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08}, 6, 0}, + {0x71, (uint8_t[]){0x0D, 0x02, 0x08}, 3, 0}, + {0x91, (uint8_t[]){0x0E, 0x09}, 2, 0}, + {0xC3, (uint8_t[]){0x19, 0xC4, 0x19, 0xC9, 0x3C}, 5, 0}, + {0xF0, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E}, 6, 0}, + {0xF1, (uint8_t[]){0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F}, 6, 0}, + {0xF2, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A}, 6, 0}, + {0xF3, (uint8_t[]){0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF}, 6, 0}, + + // {0x20, (uint8_t[]){0x00}, 0, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 0, 200}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + {0x2C, (uint8_t[]){0x00}, 0, 20}, +}; + +static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val,},1),TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){gc9d01n->colmod_val,},1),TAG, "send command failed"); + + const gc9d01n_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (gc9d01n->init_cmds){ + init_cmds = gc9d01n->init_cmds; + init_cmds_size = gc9d01n->init_cmds_size; + }else{ + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9d01n_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++){ + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd){ + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + gc9d01n->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + gc9d01n->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten){ + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + x_start += gc9d01n->x_gap; + x_end += gc9d01n->x_gap; + y_start += gc9d01n->y_gap; + y_end += gc9d01n->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){(x_start >> 8) & 0xFF,x_start & 0xFF,((x_end - 1) >> 8) & 0xFF,(x_end - 1) & 0xFF,},4),TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){(y_start >> 8) & 0xFF,y_start & 0xFF,((y_end - 1) >> 8) & 0xFF,(y_end - 1) & 0xFF,},4),TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * gc9d01n->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send color failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + int command = 0; + if (invert_color_data){ + command = LCD_CMD_INVON; + }else{ + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + if (mirror_x){ + gc9d01n->madctl_val |= LCD_CMD_MX_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y){ + gc9d01n->madctl_val |= LCD_CMD_MY_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + if (swap_axes){ + gc9d01n->madctl_val |= LCD_CMD_MV_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MV_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + gc9d01n->x_gap = x_gap; + gc9d01n->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool on_off){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off){ + command = LCD_CMD_DISPON; + }else{ + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h new file mode 100644 index 0000000..ec057cc --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h @@ -0,0 +1,99 @@ +#pragma once + +#include "esp_lcd_panel_vendor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* +#include +#include +#include +#include +#include "esp_lcd_gc9d01n.h" + +#define TAG "LilygoTCircleS3Board" + +class Cst816x : public I2cDevice{ +public: + struct TouchPoint_t{ + int num = 0; + int x = -1; + int y = -1; + }; + + Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr){ + uint8_t chip_id = ReadReg(0xA7); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst816x(){ + delete[] read_buffer_; + } + + void UpdateTouchPoint(){ + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t &GetTouchPoint(){ + return tp_; + } + +private: + uint8_t *read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class LilygoTCircleS3Board : public WifiBoard{ +private: + i2c_master_bus_handle_t i2c_bus_; + Cst816x *cst816d_; + LcdDisplay *display_; + Button boot_button_; + + void InitI2c(){ + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_config = { + .i2c_port = I2C_NUM_0, + .sda_io_num = TOUCH_I2C_SDA_PIN, + .scl_io_num = TOUCH_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + } + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); + } + + void I2cDetect(){ + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16){ + printf("%02x: ", i); + for (int j = 0; j < 16; j++){ + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK){ + printf("%02x ", address); + }else if (ret == ESP_ERR_TIMEOUT){ + printf("UU "); + }else{ + printf("-- "); + } + } + printf("\r\n"); + } + } + + static void touchpad_daemon(void *param){ + vTaskDelay(pdMS_TO_TICKS(2000)); + auto &board = (LilygoTCircleS3Board&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + bool was_touched = false; + while (1){ + touchpad->UpdateTouchPoint(); + if (touchpad->GetTouchPoint().num > 0){ + // On press + if (!was_touched){ + was_touched = true; + Application::GetInstance().ToggleChatState(); + } + } + // On release + else if (was_touched){ + was_touched = false; + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); + } + + void InitCst816d(){ + ESP_LOGI(TAG, "Init CST816x"); + cst816d_ = new Cst816x(i2c_bus_, 0x15); + xTaskCreate(touchpad_daemon, "tp", 2048, NULL, 5, NULL); + } + + void InitSpi(){ + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitGc9d01nDisplay(){ + ESP_LOGI(TAG, "Init GC9D01N"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9d01n(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + + gpio_config_t config; + config.pin_bit_mask = BIT64(DISPLAY_BL); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + config.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config); + gpio_set_level(DISPLAY_BL, 0); + } + + void InitializeButtons(){ + boot_button_.OnClick([this]() + { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeIot(){ + auto &thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("Speaker")); + } + +public: + LilygoTCircleS3Board() : boot_button_(BOOT_BUTTON_GPIO){ + InitI2c(); + InitCst816d(); + I2cDetect(); + InitSpi(); + InitGc9d01nDisplay(); + InitializeButtons(); + InitializeIot(); + } + + virtual AudioCodec *GetAudioCodec() override{ + static Tcircles3AudioCodec *audio_codec = nullptr; + if (audio_codec == nullptr){ + audio_codec = new Tcircles3AudioCodec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_MIC_I2S_GPIO_BCLK, AUDIO_MIC_I2S_GPIO_WS, AUDIO_MIC_I2S_GPIO_DATA, + AUDIO_SPKR_I2S_GPIO_BCLK, AUDIO_SPKR_I2S_GPIO_LRCLK, AUDIO_SPKR_I2S_GPIO_DATA, + AUDIO_INPUT_REFERENCE); + } + return audio_codec; + } + + virtual Display *GetDisplay() override{ + return display_; + } + + Cst816x *GetTouchpad(){ + return cst816d_; + } +}; + +DECLARE_BOARD(LilygoTCircleS3Board); diff --git a/main/boards/lilygo-t-circle-s3/pin_config.h b/main/boards/lilygo-t-circle-s3/pin_config.h new file mode 100644 index 0000000..db428ca --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/pin_config.h @@ -0,0 +1,47 @@ +/* + * @Description: None + * @Author: LILYGO_L + * @Date: 2023-08-16 14:24:03 + * @LastEditTime: 2025-01-20 10:11:16 + * @License: GPL 3.0 + */ +#pragma once + +// MAX98357A +#define MAX98357A_BCLK 5 +#define MAX98357A_LRCLK 4 +#define MAX98357A_DATA 6 +#define MAX98357A_SD_MODE 45 + +// MSM261 +#define MSM261_BCLK 7 +#define MSM261_WS 9 +#define MSM261_DATA 8 + +// APA102 +#define APA102_DATA 38 +#define APA102_CLOCK 39 + +// H0075Y002-V0 +#define LCD_WIDTH 160 +#define LCD_HEIGHT 160 +#define LCD_MOSI 17 +#define LCD_SCLK 15 +#define LCD_DC 16 +#define LCD_RST -1 +#define LCD_CS 13 +#define LCD_BL 18 + +// IIC +#define IIC_SDA 11 +#define IIC_SCL 14 + +// CST816D +#define TP_SDA 11 +#define TP_SCL 14 +#define TP_RST -1 +#define TP_INT 12 + +//Rotary Encoder +#define KNOB_DATA_A 47 +#define KNOB_DATA_B 48