Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a540c644e |
@@ -0,0 +1,8 @@
|
|||||||
|
# Force CMake reload to detect new files module.cpp and Configuration.cpp
|
||||||
|
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||||
|
|
||||||
|
idf_component_register(
|
||||||
|
SRCS ${SOURCE_FILES}
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES Tactility ButtonControl ST7305 PwmBacklight driver
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#include "devices/Display.h"
|
||||||
|
|
||||||
|
#include <Tactility/hal/Configuration.h>
|
||||||
|
#include <ButtonControl.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
static constexpr auto* TAG = "WaveshareRLCD";
|
||||||
|
|
||||||
|
using namespace tt::hal;
|
||||||
|
|
||||||
|
static DeviceVector createDevices() {
|
||||||
|
return {
|
||||||
|
createDisplay(),
|
||||||
|
ButtonControl::createTwoButtonControl(GPIO_NUM_18, GPIO_NUM_0) // KEY=18 cycles, BOOT=0 selects
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool initBoot() {
|
||||||
|
// Amp enable GPIO46 active HIGH - keep LOW during boot to avoid pop,
|
||||||
|
// then HIGH after delay so speaker works for MCP / Mp3Player
|
||||||
|
gpio_config_t io_conf = {};
|
||||||
|
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||||
|
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||||
|
io_conf.pin_bit_mask = (1ULL << GPIO_NUM_46);
|
||||||
|
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||||
|
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||||
|
gpio_config(&io_conf);
|
||||||
|
gpio_set_level(GPIO_NUM_46, 0);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
gpio_set_level(GPIO_NUM_46, 1);
|
||||||
|
LOG_I(TAG, "Speaker amp GPIO46 enabled");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const Configuration hardwareConfiguration = {
|
||||||
|
.initBoot = initBoot,
|
||||||
|
.createDevices = createDevices
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "Display.h"
|
||||||
|
#include <St7305Display.h>
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
|
||||||
|
auto configuration = std::make_unique<St7305Display::Configuration>(
|
||||||
|
SPI2_HOST,
|
||||||
|
GPIO_NUM_40, // CS
|
||||||
|
GPIO_NUM_5, // DC
|
||||||
|
400, // Width
|
||||||
|
300, // Height
|
||||||
|
nullptr, // Touch device
|
||||||
|
false, // swapXY
|
||||||
|
false, // mirrorX
|
||||||
|
false, // mirrorY
|
||||||
|
true, // invertColor - initial true to match existing inverted look, user can toggle via Settings->Display
|
||||||
|
0 // bufferSize (0 = default, which is full screen = 120,000 pixels)
|
||||||
|
);
|
||||||
|
|
||||||
|
configuration->resetPin = GPIO_NUM_41;
|
||||||
|
|
||||||
|
auto display = std::make_shared<St7305Display>(std::move(configuration));
|
||||||
|
return std::reinterpret_pointer_cast<tt::hal::display::DisplayDevice>(display);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
static error_t start() {
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static error_t stop() {
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Module waveshare_esp32_s3_rlcd_module = {
|
||||||
|
.name = "waveshare-esp32-s3-rlcd",
|
||||||
|
.start = start,
|
||||||
|
.stop = stop,
|
||||||
|
.symbols = nullptr,
|
||||||
|
.internal = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
general.vendor=WaveShare
|
||||||
|
general.name=ESP32-S3-RLCD-4.2
|
||||||
|
general.incubating=true
|
||||||
|
|
||||||
|
apps.launcherAppId=Launcher
|
||||||
|
# apps.autoStartAppId=ApWebServer
|
||||||
|
|
||||||
|
hardware.target=ESP32S3
|
||||||
|
hardware.flashSize=16MB
|
||||||
|
hardware.spiRam=true
|
||||||
|
hardware.spiRamMode=OCT
|
||||||
|
hardware.spiRamSpeed=120M
|
||||||
|
hardware.tinyUsb=true
|
||||||
|
hardware.esptoolFlashFreq=120M
|
||||||
|
hardware.bluetooth=true
|
||||||
|
|
||||||
|
storage.userDataLocation=SD
|
||||||
|
|
||||||
|
display.size=4.2"
|
||||||
|
display.shape=rectangle
|
||||||
|
display.dpi=120
|
||||||
|
|
||||||
|
lvgl.colorDepth=16
|
||||||
|
lvgl.uiDensity=compact
|
||||||
|
lvgl.fontSize=20
|
||||||
|
lvgl.theme=DefaultDark
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
dependencies:
|
||||||
|
- Platforms/platform-esp32
|
||||||
|
dts: waveshare,esp32-s3-rlcd.dts
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/dts-v1/;
|
||||||
|
|
||||||
|
#include <tactility/bindings/root.h>
|
||||||
|
#include <tactility/bindings/esp32_ble.h>
|
||||||
|
#include <tactility/bindings/esp32_gpio.h>
|
||||||
|
#include <tactility/bindings/esp32_i2c_master.h>
|
||||||
|
#include <tactility/bindings/esp32_sdmmc.h>
|
||||||
|
#include <tactility/bindings/esp32_spi.h>
|
||||||
|
#include <tactility/bindings/esp32_i2s.h>
|
||||||
|
#include <tactility/bindings/esp32_uart.h>
|
||||||
|
#include <tactility/bindings/display_placeholder.h>
|
||||||
|
|
||||||
|
/ {
|
||||||
|
compatible = "root";
|
||||||
|
model = "Waveshare ESP32-S3-RLCD-4.2";
|
||||||
|
|
||||||
|
ble0 {
|
||||||
|
compatible = "espressif,esp32-ble";
|
||||||
|
status = "disabled";
|
||||||
|
};
|
||||||
|
|
||||||
|
gpio0 {
|
||||||
|
compatible = "espressif,esp32-gpio";
|
||||||
|
gpio-count = <49>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Audio: ES7210 mic + speaker via I2S0
|
||||||
|
// Pins verified from working MicroPython board_config.py for WAVESHARE_RLCD:
|
||||||
|
// mclk=16, bclk=9, ws=45, tx=8 (DAC out), rx=10 (ES7210 mic in)
|
||||||
|
// NOTE: Display DC is GPIO5, so I2S must NOT use GPIO5 to avoid bus corruption
|
||||||
|
i2s0 {
|
||||||
|
compatible = "espressif,esp32-i2s";
|
||||||
|
port = <I2S_NUM_0>;
|
||||||
|
pin-bclk = <&gpio0 9 GPIO_FLAG_NONE>;
|
||||||
|
pin-ws = <&gpio0 45 GPIO_FLAG_NONE>;
|
||||||
|
pin-data-out = <&gpio0 8 GPIO_FLAG_NONE>;
|
||||||
|
pin-data-in = <&gpio0 10 GPIO_FLAG_NONE>;
|
||||||
|
pin-mclk = <&gpio0 16 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
|
i2c0 {
|
||||||
|
compatible = "espressif,esp32-i2c-master";
|
||||||
|
port = <I2C_NUM_0>;
|
||||||
|
clock-frequency = <400000>;
|
||||||
|
pin-sda = <&gpio0 13 GPIO_FLAG_NONE>;
|
||||||
|
pin-scl = <&gpio0 14 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
|
||||||
|
spi0 {
|
||||||
|
compatible = "espressif,esp32-spi";
|
||||||
|
host = <SPI2_HOST>;
|
||||||
|
cs-gpios = <&gpio0 40 GPIO_FLAG_NONE>;
|
||||||
|
pin-mosi = <&gpio0 12 GPIO_FLAG_NONE>;
|
||||||
|
pin-sclk = <&gpio0 11 GPIO_FLAG_NONE>;
|
||||||
|
|
||||||
|
display {
|
||||||
|
compatible = "display-placeholder";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sdmmc0 {
|
||||||
|
compatible = "espressif,esp32-sdmmc";
|
||||||
|
pin-clk = <&gpio0 38 GPIO_FLAG_NONE>;
|
||||||
|
pin-cmd = <&gpio0 21 GPIO_FLAG_NONE>;
|
||||||
|
pin-d0 = <&gpio0 39 GPIO_FLAG_NONE>;
|
||||||
|
pin-d3 = <&gpio0 17 GPIO_FLAG_NONE>;
|
||||||
|
slot = <SDMMC_HOST_SLOT_1>;
|
||||||
|
bus-width = <1>;
|
||||||
|
pullups;
|
||||||
|
};
|
||||||
|
|
||||||
|
uart0 {
|
||||||
|
compatible = "espressif,esp32-uart";
|
||||||
|
port = <UART_NUM_0>;
|
||||||
|
pin-tx = <&gpio0 43 GPIO_FLAG_NONE>;
|
||||||
|
pin-rx = <&gpio0 44 GPIO_FLAG_NONE>;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRC_DIRS "Source"
|
||||||
|
INCLUDE_DIRS "Source"
|
||||||
|
REQUIRES Tactility driver EspLcdCompat
|
||||||
|
)
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#include "St7305Display.h"
|
||||||
|
#include "esp_lcd_st7305.h"
|
||||||
|
|
||||||
|
#include <tactility/log.h>
|
||||||
|
#include <esp_lcd_panel_ops.h>
|
||||||
|
#include <esp_lvgl_port.h>
|
||||||
|
|
||||||
|
static const char* TAG = "ST7305";
|
||||||
|
|
||||||
|
bool St7305Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
|
||||||
|
LOG_I(TAG, "Starting ST7305 SPI panel IO creation");
|
||||||
|
|
||||||
|
const esp_lcd_panel_io_spi_config_t panel_io_config = {
|
||||||
|
.cs_gpio_num = configuration->csPin,
|
||||||
|
.dc_gpio_num = configuration->dcPin,
|
||||||
|
.spi_mode = 0,
|
||||||
|
.pclk_hz = configuration->pixelClockFrequency,
|
||||||
|
.trans_queue_depth = configuration->transactionQueueDepth,
|
||||||
|
.on_color_trans_done = nullptr,
|
||||||
|
.user_ctx = nullptr,
|
||||||
|
.lcd_cmd_bits = 8,
|
||||||
|
.lcd_param_bits = 8,
|
||||||
|
.flags = {
|
||||||
|
.dc_high_on_cmd = 0,
|
||||||
|
.dc_low_on_data = 0,
|
||||||
|
.dc_low_on_param = 0,
|
||||||
|
.octal_mode = 0,
|
||||||
|
.quad_mode = 0,
|
||||||
|
.sio_mode = 0,
|
||||||
|
.lsb_first = 0,
|
||||||
|
.cs_high_active = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Failed to create panel SPI IO");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool St7305Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) {
|
||||||
|
const esp_lcd_panel_dev_config_t panel_config = {
|
||||||
|
.reset_gpio_num = configuration->resetPin,
|
||||||
|
.color_space = ESP_LCD_COLOR_SPACE_MONOCHROME,
|
||||||
|
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
||||||
|
.bits_per_pixel = 1,
|
||||||
|
.flags = {
|
||||||
|
.reset_active_high = false
|
||||||
|
},
|
||||||
|
.vendor_config = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
if (esp_lcd_new_panel_st7305(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Failed to create st7305 panel");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ST7305 needs extra delay after reset before init — prevents freeze on fast boot
|
||||||
|
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Failed to reset st7305 panel");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(150));
|
||||||
|
|
||||||
|
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Failed to init st7305 panel");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
|
||||||
|
if (configuration->invertColor) {
|
||||||
|
if (esp_lcd_panel_invert_color(panelHandle, true) != ESP_OK) {
|
||||||
|
LOG_W(TAG, "Failed to apply initial invertColor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
lvgl_port_display_cfg_t St7305Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
|
||||||
|
return lvgl_port_display_cfg_t {
|
||||||
|
.io_handle = ioHandle,
|
||||||
|
.panel_handle = panelHandle,
|
||||||
|
.control_handle = nullptr,
|
||||||
|
.buffer_size = configuration->bufferSize,
|
||||||
|
.double_buffer = false, // Monochrome displays usually use single buffering to save RAM
|
||||||
|
.trans_size = 0,
|
||||||
|
.hres = configuration->horizontalResolution,
|
||||||
|
.vres = configuration->verticalResolution,
|
||||||
|
.monochrome = true, // Enables esp_lvgl_port monochrome converter
|
||||||
|
.rotation = {
|
||||||
|
.swap_xy = false,
|
||||||
|
.mirror_x = true,
|
||||||
|
.mirror_y = false,
|
||||||
|
},
|
||||||
|
.color_format = LV_COLOR_FORMAT_RGB565, // Must be RGB565 for monochrome mode to trigger converter
|
||||||
|
.flags = {
|
||||||
|
.buff_dma = false,
|
||||||
|
.buff_spiram = false,
|
||||||
|
.sw_rotate = false,
|
||||||
|
.swap_bytes = false,
|
||||||
|
.full_refresh = true, // We want full refresh to rewrite the converted block format to ST7305
|
||||||
|
.direct_mode = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <EspLcdDisplay.h>
|
||||||
|
#include <Tactility/hal/display/DisplayDevice.h>
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/spi_common.h>
|
||||||
|
#include <esp_lcd_panel_io.h>
|
||||||
|
#include <esp_lcd_types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
class St7305Display final : public EspLcdDisplay {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
class Configuration {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Configuration(
|
||||||
|
spi_host_device_t spiHostDevice,
|
||||||
|
gpio_num_t csPin,
|
||||||
|
gpio_num_t dcPin,
|
||||||
|
unsigned int horizontalResolution,
|
||||||
|
unsigned int verticalResolution,
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
|
||||||
|
bool swapXY = false,
|
||||||
|
bool mirrorX = false,
|
||||||
|
bool mirrorY = false,
|
||||||
|
bool invertColor = false,
|
||||||
|
uint32_t bufferSize = 0
|
||||||
|
) : spiHostDevice(spiHostDevice),
|
||||||
|
csPin(csPin),
|
||||||
|
dcPin(dcPin),
|
||||||
|
horizontalResolution(horizontalResolution),
|
||||||
|
verticalResolution(verticalResolution),
|
||||||
|
swapXY(swapXY),
|
||||||
|
mirrorX(mirrorX),
|
||||||
|
mirrorY(mirrorY),
|
||||||
|
invertColor(invertColor),
|
||||||
|
bufferSize(bufferSize),
|
||||||
|
touch(std::move(touch))
|
||||||
|
{
|
||||||
|
if (this->bufferSize == 0) {
|
||||||
|
// For monochrome display, full pixel count is expected for buffer size
|
||||||
|
this->bufferSize = horizontalResolution * verticalResolution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_host_device_t spiHostDevice;
|
||||||
|
gpio_num_t csPin;
|
||||||
|
gpio_num_t dcPin;
|
||||||
|
gpio_num_t resetPin = GPIO_NUM_NC;
|
||||||
|
unsigned int pixelClockFrequency = 10'000'000; // 10MHz SPI clock for ST7305
|
||||||
|
size_t transactionQueueDepth = 10;
|
||||||
|
unsigned int horizontalResolution;
|
||||||
|
unsigned int verticalResolution;
|
||||||
|
bool swapXY = false;
|
||||||
|
bool mirrorX = false;
|
||||||
|
bool mirrorY = false;
|
||||||
|
bool invertColor = false;
|
||||||
|
uint32_t bufferSize = 0;
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
|
||||||
|
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::unique_ptr<Configuration> configuration;
|
||||||
|
|
||||||
|
bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override;
|
||||||
|
|
||||||
|
bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) override;
|
||||||
|
|
||||||
|
lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit St7305Display(std::unique_ptr<Configuration> inConfiguration) :
|
||||||
|
configuration(std::move(inConfiguration))
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::string getName() const override { return "ST7305"; }
|
||||||
|
|
||||||
|
std::string getDescription() const override { return "ST7305 monochrome reflective LCD display"; }
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override { return configuration->touch; }
|
||||||
|
|
||||||
|
void setBacklightDuty(uint8_t backlightDuty) override {
|
||||||
|
if (configuration->backlightDutyFunction != nullptr) {
|
||||||
|
configuration->backlightDutyFunction(backlightDuty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
#include "esp_lcd_st7305.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "esp_lcd_types.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/cdefs.h>
|
||||||
|
#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 <string.h>
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
static const char *TAG = "st7305";
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_del(esp_lcd_panel_t *panel);
|
||||||
|
static esp_err_t panel_st7305_reset(esp_lcd_panel_t *panel);
|
||||||
|
static esp_err_t panel_st7305_init(esp_lcd_panel_t *panel);
|
||||||
|
static esp_err_t panel_st7305_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_st7305_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
|
||||||
|
static esp_err_t panel_st7305_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
|
||||||
|
static esp_err_t panel_st7305_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
|
||||||
|
static esp_err_t panel_st7305_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
|
||||||
|
static esp_err_t panel_st7305_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;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
uint8_t rotation;
|
||||||
|
uint8_t madctl_val;
|
||||||
|
const st7305_lcd_init_cmd_t *init_cmds;
|
||||||
|
uint16_t init_cmds_size;
|
||||||
|
uint8_t *draw_buffer;
|
||||||
|
} st7305_panel_t;
|
||||||
|
|
||||||
|
static const st7305_lcd_init_cmd_t st7305_init_cmds[] = {
|
||||||
|
{0xD6, (uint8_t[]){0x17, 0x02}, 2, 0}, // NVM Load Control
|
||||||
|
{0xD1, (uint8_t[]){0x01}, 1, 0}, // Booster Enable
|
||||||
|
{0xC0, (uint8_t[]){0x11, 0x04}, 2, 0}, // Gate Voltage Setting
|
||||||
|
{0xC1, (uint8_t[]){0x41, 0x41, 0x41, 0x41}, 4, 0}, // VSHP Setting
|
||||||
|
{0xC2, (uint8_t[]){0x19, 0x19, 0x19, 0x19}, 4, 0}, // VSLP Setting
|
||||||
|
{0xC4, (uint8_t[]){0x41, 0x41, 0x41, 0x41}, 4, 0}, // VSHN Setting
|
||||||
|
{0xC5, (uint8_t[]){0x19, 0x13, 0x19, 0x19}, 4, 0}, // VSLN Setting
|
||||||
|
{0xD8, (uint8_t[]){0xA6, 0xE9}, 2, 0}, // OSC Setting
|
||||||
|
{0xB2, (uint8_t[]){0x05}, 1, 0}, // Frame Rate Control
|
||||||
|
{0xB3, (uint8_t[]){0xE5, 0xF6, 0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45}, 10, 0}, // Gate EQ HPM
|
||||||
|
{0xB4, (uint8_t[]){0x05, 0x46, 0x77, 0x77, 0x77, 0x77, 0x76, 0x45}, 8, 0}, // Gate EQ LPM
|
||||||
|
{0x62, (uint8_t[]){0x32, 0x03, 0x1F}, 3, 0}, // Gate Timing Control
|
||||||
|
{0xB7, (uint8_t[]){0x13}, 1, 0}, // Source EQ Enable
|
||||||
|
{0xB0, (uint8_t[]){0x64}, 1, 0}, // Gate Line Setting: 300 line (0x64 = 100 * 3)
|
||||||
|
{0x11, NULL, 0, 200}, // Sleep out
|
||||||
|
{0xC9, (uint8_t[]){0x00}, 1, 0}, // Source Voltage Select
|
||||||
|
{0x36, (uint8_t[]){0x48}, 1, 0}, // Memory Data Access Control (MX=1, DO=1)
|
||||||
|
{0x3A, (uint8_t[]){0x11}, 1, 0}, // Data Format Select: 1bpp
|
||||||
|
{0xB9, (uint8_t[]){0x20}, 1, 0}, // Gamma Mode Setting
|
||||||
|
{0xB8, (uint8_t[]){0x29}, 1, 0}, // Panel Setting
|
||||||
|
{0x21, NULL, 0, 0}, // Display Inversion On
|
||||||
|
{0x2A, (uint8_t[]){0x12, 0x2A}, 2, 0}, // Column Address Setting
|
||||||
|
{0x2B, (uint8_t[]){0x00, 0xC7}, 2, 0}, // Row Address Setting
|
||||||
|
{0x35, (uint8_t[]){0x00}, 1, 0}, // TE Line
|
||||||
|
{0xD0, (uint8_t[]){0xFF}, 1, 0}, // Auto power down ON
|
||||||
|
{0x38, NULL, 0, 0}, // High Power Mode ON
|
||||||
|
{0x29, NULL, 0, 100}, // Display ON
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t esp_lcd_new_panel_st7305(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;
|
||||||
|
st7305_panel_t *st7305 = 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");
|
||||||
|
st7305 = (st7305_panel_t *)calloc(1, sizeof(st7305_panel_t));
|
||||||
|
ESP_GOTO_ON_FALSE(st7305, ESP_ERR_NO_MEM, err, TAG, "no mem for st7305 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");
|
||||||
|
}
|
||||||
|
st7305->width = ST7305_WIDTH;
|
||||||
|
st7305->height = ST7305_HEIGHT;
|
||||||
|
st7305->madctl_val = 0x48; // MX=1, DO=1
|
||||||
|
st7305->rotation = 0;
|
||||||
|
|
||||||
|
st7305->io = io;
|
||||||
|
st7305->reset_gpio_num = panel_dev_config->reset_gpio_num;
|
||||||
|
st7305->reset_level = panel_dev_config->flags.reset_active_high;
|
||||||
|
if (panel_dev_config->vendor_config) {
|
||||||
|
st7305->init_cmds = ((st7305_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds;
|
||||||
|
st7305->init_cmds_size = ((st7305_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size;
|
||||||
|
} else {
|
||||||
|
st7305->init_cmds = st7305_init_cmds;
|
||||||
|
st7305->init_cmds_size = sizeof(st7305_init_cmds) / sizeof(st7305_lcd_init_cmd_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
st7305->draw_buffer = heap_caps_malloc(15000, MALLOC_CAP_DMA);
|
||||||
|
ESP_GOTO_ON_FALSE(st7305->draw_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for st7305 draw buffer");
|
||||||
|
memset(st7305->draw_buffer, 0, 15000);
|
||||||
|
st7305->base.del = panel_st7305_del;
|
||||||
|
st7305->base.reset = panel_st7305_reset;
|
||||||
|
st7305->base.init = panel_st7305_init;
|
||||||
|
st7305->base.draw_bitmap = panel_st7305_draw_bitmap;
|
||||||
|
st7305->base.invert_color = panel_st7305_invert_color;
|
||||||
|
st7305->base.set_gap = panel_st7305_set_gap;
|
||||||
|
st7305->base.mirror = panel_st7305_mirror;
|
||||||
|
st7305->base.swap_xy = panel_st7305_swap_xy;
|
||||||
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||||
|
st7305->base.disp_off = panel_st7305_disp_on_off;
|
||||||
|
#else
|
||||||
|
st7305->base.disp_on_off = panel_st7305_disp_on_off;
|
||||||
|
#endif
|
||||||
|
*ret_panel = &(st7305->base);
|
||||||
|
ESP_LOGD(TAG, "new st7305 panel @%p", st7305);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (st7305) {
|
||||||
|
if (panel_dev_config->reset_gpio_num >= 0) {
|
||||||
|
gpio_reset_pin(panel_dev_config->reset_gpio_num);
|
||||||
|
}
|
||||||
|
if (st7305->draw_buffer) {
|
||||||
|
free(st7305->draw_buffer);
|
||||||
|
}
|
||||||
|
free(st7305);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_del(esp_lcd_panel_t *panel)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
if (st7305->reset_gpio_num >= 0) {
|
||||||
|
gpio_reset_pin(st7305->reset_gpio_num);
|
||||||
|
}
|
||||||
|
if (st7305->draw_buffer) {
|
||||||
|
free(st7305->draw_buffer);
|
||||||
|
}
|
||||||
|
free(st7305);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_reset(esp_lcd_panel_t *panel)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
if (st7305->reset_gpio_num >= 0) {
|
||||||
|
gpio_set_level(st7305->reset_gpio_num, !st7305->reset_level);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
gpio_set_level(st7305->reset_gpio_num, st7305->reset_level);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(20));
|
||||||
|
gpio_set_level(st7305->reset_gpio_num, !st7305->reset_level);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_init(esp_lcd_panel_t *panel)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < st7305->init_cmds_size; i++) {
|
||||||
|
if (st7305->init_cmds[i].data_bytes > 0) {
|
||||||
|
esp_lcd_panel_io_tx_param(io, st7305->init_cmds[i].cmd,
|
||||||
|
st7305->init_cmds[i].data,
|
||||||
|
st7305->init_cmds[i].data_bytes);
|
||||||
|
} else {
|
||||||
|
esp_lcd_panel_io_tx_param(io, st7305->init_cmds[i].cmd, NULL, 0);
|
||||||
|
}
|
||||||
|
if (st7305->init_cmds[i].delay_ms > 0) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(st7305->init_cmds[i].delay_ms));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly clear display RAM to white
|
||||||
|
if (st7305->draw_buffer) {
|
||||||
|
memset(st7305->draw_buffer, 0xFF, 15000); // 0xFF is White
|
||||||
|
uint8_t caset[] = {0x12, 0x2A};
|
||||||
|
uint8_t raset[] = {0x00, 0xC7};
|
||||||
|
esp_lcd_panel_io_tx_param(io, ST7305_CMD_CASET, caset, sizeof(caset));
|
||||||
|
esp_lcd_panel_io_tx_param(io, ST7305_CMD_RASET, raset, sizeof(raset));
|
||||||
|
esp_lcd_panel_io_tx_color(io, ST7305_CMD_RAMWR, st7305->draw_buffer, 15000);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||||
|
|
||||||
|
const uint8_t *src = (const uint8_t *)color_data;
|
||||||
|
|
||||||
|
// Convert from vertical-page format (SSD1306-style) to ST7305 landscape 2x4 block format
|
||||||
|
for (int y = 0; y < 300; y++) {
|
||||||
|
int inv_y = 299 - y;
|
||||||
|
int block_y = inv_y >> 2;
|
||||||
|
int local_y = inv_y & 3;
|
||||||
|
|
||||||
|
int src_y_div_8 = y >> 3;
|
||||||
|
int src_y_mod_8 = y & 7;
|
||||||
|
uint8_t src_bit_mask = 1 << src_y_mod_8;
|
||||||
|
|
||||||
|
for (int x = 0; x < 400; x++) {
|
||||||
|
int byte_x = x >> 1;
|
||||||
|
int local_x = x & 1;
|
||||||
|
|
||||||
|
int dest_byte_idx = byte_x * 75 + block_y;
|
||||||
|
int dest_bit_pos = 7 - ((local_y << 1) | local_x);
|
||||||
|
|
||||||
|
int src_byte_idx = 400 * src_y_div_8 + x;
|
||||||
|
|
||||||
|
// Read standard vertical page byte bit
|
||||||
|
bool is_pixel_set = (src[src_byte_idx] & src_bit_mask) != 0;
|
||||||
|
|
||||||
|
// Invert the pixel logic if required, standard:
|
||||||
|
// esp_lvgl_port monochrome transform clears the bit (0) for light/chroma colors
|
||||||
|
// and sets the bit (1) for dark/black.
|
||||||
|
// In ST7305 display RAM, White/Light is 1, Black/Dark is 0.
|
||||||
|
// So we write: 1 (White) if is_pixel_set is false (light), and 0 (Black) if is_pixel_set is true (dark).
|
||||||
|
if (!is_pixel_set) {
|
||||||
|
st7305->draw_buffer[dest_byte_idx] |= (1 << dest_bit_pos);
|
||||||
|
} else {
|
||||||
|
st7305->draw_buffer[dest_byte_idx] &= ~(1 << dest_bit_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t caset[] = {0x12, 0x2A};
|
||||||
|
uint8_t raset[] = {0x00, 0xC7};
|
||||||
|
|
||||||
|
esp_lcd_panel_io_tx_param(io, ST7305_CMD_CASET, caset, sizeof(caset));
|
||||||
|
esp_lcd_panel_io_tx_param(io, ST7305_CMD_RASET, raset, sizeof(raset));
|
||||||
|
esp_lcd_panel_io_tx_color(io, ST7305_CMD_RAMWR, st7305->draw_buffer, 15000);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
return esp_lcd_panel_io_tx_param(st7305->io, invert_color_data ? ST7305_CMD_INVON : ST7305_CMD_INVOFF, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||||
|
if (mirror_x) {
|
||||||
|
st7305->madctl_val |= ST7305_MADCTL_MX;
|
||||||
|
} else {
|
||||||
|
st7305->madctl_val &= ~ST7305_MADCTL_MX;
|
||||||
|
}
|
||||||
|
if (mirror_y) {
|
||||||
|
st7305->madctl_val |= ST7305_MADCTL_MY;
|
||||||
|
} else {
|
||||||
|
st7305->madctl_val &= ~ST7305_MADCTL_MY;
|
||||||
|
}
|
||||||
|
return esp_lcd_panel_io_tx_param(io, ST7305_CMD_MADCTL, &st7305->madctl_val, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
esp_lcd_panel_io_handle_t io = st7305->io;
|
||||||
|
if (swap_axes) {
|
||||||
|
st7305->madctl_val |= ST7305_MADCTL_MV;
|
||||||
|
} else {
|
||||||
|
st7305->madctl_val &= ~ST7305_MADCTL_MV;
|
||||||
|
}
|
||||||
|
return esp_lcd_panel_io_tx_param(io, ST7305_CMD_MADCTL, (uint8_t[]) { st7305->madctl_val }, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
st7305->x_gap = x_gap;
|
||||||
|
st7305->y_gap = y_gap;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t panel_st7305_disp_on_off(esp_lcd_panel_t *panel, bool off)
|
||||||
|
{
|
||||||
|
st7305_panel_t *st7305 = __containerof(panel, st7305_panel_t, base);
|
||||||
|
return esp_lcd_panel_io_tx_param(st7305->io, off ? ST7305_CMD_DISPOFF : ST7305_CMD_DISPON, NULL, 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_lcd_types.h"
|
||||||
|
#include "esp_lcd_panel_vendor.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int cmd; /*<! The specific LCD command */
|
||||||
|
const void *data; /*<! Buffer that holds the command specific data */
|
||||||
|
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
|
||||||
|
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
|
||||||
|
} st7305_lcd_init_cmd_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const st7305_lcd_init_cmd_t *init_cmds;
|
||||||
|
uint16_t init_cmds_size;
|
||||||
|
} st7305_vendor_config_t;
|
||||||
|
|
||||||
|
#define ST7305_WIDTH 400
|
||||||
|
#define ST7305_HEIGHT 300
|
||||||
|
|
||||||
|
#define ST7305_CMD_NOP 0x00
|
||||||
|
#define ST7305_CMD_SWRESET 0x01
|
||||||
|
#define ST7305_CMD_SLPOUT 0x11
|
||||||
|
#define ST7305_CMD_NORON 0x13
|
||||||
|
#define ST7305_CMD_INVOFF 0x20
|
||||||
|
#define ST7305_CMD_INVON 0x21
|
||||||
|
#define ST7305_CMD_DISPOFF 0x28
|
||||||
|
#define ST7305_CMD_DISPON 0x29
|
||||||
|
#define ST7305_CMD_CASET 0x2A
|
||||||
|
#define ST7305_CMD_RASET 0x2B
|
||||||
|
#define ST7305_CMD_RAMWR 0x2C
|
||||||
|
#define ST7305_CMD_MADCTL 0x36
|
||||||
|
|
||||||
|
#define ST7305_MADCTL_MY 0x80
|
||||||
|
#define ST7305_MADCTL_MX 0x40
|
||||||
|
#define ST7305_MADCTL_MV 0x20
|
||||||
|
#define ST7305_MADCTL_ML 0x10
|
||||||
|
|
||||||
|
esp_err_t esp_lcd_new_panel_st7305(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);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -553,14 +553,9 @@ error_t close_stream(AudioStreamHandle handle_base) {
|
|||||||
Device* codec = is_input ? data->input_codec : data->output_codec;
|
Device* codec = is_input ? data->input_codec : data->output_codec;
|
||||||
AudioStreamHandleImpl** slot = is_input ? &data->open_input : &data->open_output;
|
AudioStreamHandleImpl** slot = is_input ? &data->open_input : &data->open_output;
|
||||||
|
|
||||||
// Determine if underlying codec is shared (BOTH codec used for both directions)
|
|
||||||
// In that case we must NOT close the codec if the other direction is still active.
|
|
||||||
Device* other_codec = is_input ? data->output_codec : data->input_codec;
|
|
||||||
AudioStreamHandleImpl** other_slot = is_input ? &data->open_output : &data->open_input;
|
|
||||||
bool codec_shared = (codec != nullptr && other_codec != nullptr && codec == other_codec);
|
|
||||||
|
|
||||||
xSemaphoreTake(data->mutex, portMAX_DELAY);
|
xSemaphoreTake(data->mutex, portMAX_DELAY);
|
||||||
if (handle->closing) {
|
if (handle->closing) {
|
||||||
|
// Already being closed by another caller (e.g. concurrent set_enabled + app close).
|
||||||
xSemaphoreGive(data->mutex);
|
xSemaphoreGive(data->mutex);
|
||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
@@ -568,16 +563,14 @@ error_t close_stream(AudioStreamHandle handle_base) {
|
|||||||
if (*slot == handle) {
|
if (*slot == handle) {
|
||||||
*slot = nullptr;
|
*slot = nullptr;
|
||||||
}
|
}
|
||||||
bool other_still_open = (other_slot != nullptr && *other_slot != nullptr && *other_slot != reinterpret_cast<AudioStreamHandleImpl*>(1));
|
|
||||||
bool must_drain = (handle->busy_count > 0);
|
bool must_drain = (handle->busy_count > 0);
|
||||||
bool should_close_codec = !codec_shared || !other_still_open;
|
|
||||||
xSemaphoreGive(data->mutex);
|
xSemaphoreGive(data->mutex);
|
||||||
|
|
||||||
if (must_drain && handle->drain_semaphore != nullptr) {
|
if (must_drain && handle->drain_semaphore != nullptr) {
|
||||||
xSemaphoreTake(handle->drain_semaphore, portMAX_DELAY);
|
xSemaphoreTake(handle->drain_semaphore, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (should_close_codec && codec != nullptr) {
|
if (codec != nullptr) {
|
||||||
audio_codec_close(codec);
|
audio_codec_close(codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,36 +53,16 @@ error_t open(Device* device, const struct AudioCodecStreamConfig* config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (data->is_open) {
|
if (data->is_open) {
|
||||||
// ES8311 is configured for WORK_MODE_BOTH, so an already-open device
|
// open_direction == BOTH already serves INPUT-only or OUTPUT-only requests on the
|
||||||
// can serve the opposite direction without reopening, provided sample
|
// same sample settings -- only an exact direction mismatch (e.g. requesting BOTH
|
||||||
// settings match. Promote open_direction to BOTH when we see a
|
// while opened for INPUT only) needs a reopen.
|
||||||
// complementary request.
|
bool direction_compatible = data->open_direction == config->direction
|
||||||
bool is_complementary = (data->open_direction == AUDIO_CODEC_DIR_OUTPUT && config->direction == AUDIO_CODEC_DIR_INPUT)
|
|| data->open_direction == AUDIO_CODEC_DIR_BOTH;
|
||||||
|| (data->open_direction == AUDIO_CODEC_DIR_INPUT && config->direction == AUDIO_CODEC_DIR_OUTPUT);
|
|
||||||
bool direction_compatible = (data->open_direction == config->direction)
|
|
||||||
|| (data->open_direction == AUDIO_CODEC_DIR_BOTH)
|
|
||||||
|| (config->direction == AUDIO_CODEC_DIR_BOTH)
|
|
||||||
|| is_complementary;
|
|
||||||
bool same_config = direction_compatible
|
bool same_config = direction_compatible
|
||||||
&& data->open_sample_info.bits_per_sample == sample_info.bits_per_sample
|
&& data->open_sample_info.bits_per_sample == sample_info.bits_per_sample
|
||||||
&& data->open_sample_info.channel == sample_info.channel
|
&& data->open_sample_info.channel == sample_info.channel
|
||||||
&& data->open_sample_info.sample_rate == sample_info.sample_rate;
|
&& data->open_sample_info.sample_rate == sample_info.sample_rate;
|
||||||
if (same_config) {
|
return same_config ? ERROR_NONE : ERROR_RESOURCE;
|
||||||
// If we opened OUTPUT then INPUT (or vice versa), mark as BOTH
|
|
||||||
if (is_complementary) {
|
|
||||||
data->open_direction = AUDIO_CODEC_DIR_BOTH;
|
|
||||||
}
|
|
||||||
return ERROR_NONE;
|
|
||||||
}
|
|
||||||
// Different sample config for opposite direction - ES8311 can only have one
|
|
||||||
// sample rate at a time (native 44100 resampled via audio-stream), so if
|
|
||||||
// codec rates differ we must fail. But if both sides use native 44100 (audio-stream
|
|
||||||
// always opens codec with native rate), we allow it.
|
|
||||||
if (direction_compatible) {
|
|
||||||
// Allow if both use same native rate path (audio-stream opens with codec's native)
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
|
||||||
return ERROR_RESOURCE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esp_codec_dev_open(data->codec_device, &sample_info) != ESP_CODEC_DEV_OK) {
|
if (esp_codec_dev_open(data->codec_device, &sample_info) != ESP_CODEC_DEV_OK) {
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ bool isValidAppVersionName(const std::string& version);
|
|||||||
bool isValidAppVersionCode(const std::string& version);
|
bool isValidAppVersionCode(const std::string& version);
|
||||||
bool isValidName(const std::string& name);
|
bool isValidName(const std::string& name);
|
||||||
|
|
||||||
/** Parses a comma-separated flags string (e.g. "HideStatusBar,Hidden") into appFlags bitmask. */
|
|
||||||
uint16_t parseAppFlagsString(const std::string& raw);
|
|
||||||
|
|
||||||
/** Parses a V1 (sectioned INI, e.g. "[app]versionName=...") manifest map. */
|
/** Parses a V1 (sectioned INI, e.g. "[app]versionName=...") manifest map. */
|
||||||
bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest& manifest);
|
bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest& manifest);
|
||||||
|
|
||||||
|
|||||||
@@ -56,34 +56,6 @@ bool isValidName(const std::string& name) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t parseAppFlagsString(const std::string& raw) {
|
|
||||||
uint16_t flags = AppManifest::Flags::None;
|
|
||||||
if (raw.empty()) {
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto parts = string::split(raw, ",");
|
|
||||||
for (auto& part : parts) {
|
|
||||||
std::string trimmed = string::trim(part, " \t\r\n");
|
|
||||||
if (trimmed.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string lower = string::lowercase(trimmed);
|
|
||||||
|
|
||||||
if (lower == "hidestatusbar" || lower == "hide_statusbar" || lower == "hide-statusbar" || lower == "hide_status_bar") {
|
|
||||||
flags |= AppManifest::Flags::HideStatusBar;
|
|
||||||
} else if (lower == "hidden") {
|
|
||||||
flags |= AppManifest::Flags::Hidden;
|
|
||||||
} else if (lower == "none" || lower == "0" || lower == "") {
|
|
||||||
// keep as none, no additional flag
|
|
||||||
} else {
|
|
||||||
LOG_W(TAG, "Unknown app flag \"%s\" - ignoring", trimmed.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The V1 format's first line is always the literal "[manifest]" section header; V2 files are flat from the first line onward. */
|
/** The V1 format's first line is always the literal "[manifest]" section header; V2 files are flat from the first line onward. */
|
||||||
static bool detectIsV1Format(const std::string& filePath) {
|
static bool detectIsV1Format(const std::string& filePath) {
|
||||||
std::string first_line;
|
std::string first_line;
|
||||||
|
|||||||
@@ -71,12 +71,6 @@ bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest&
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: [app]flags - e.g. "HideStatusBar" or "HideStatusBar,Hidden"
|
|
||||||
auto flags_it = map.find("[app]flags");
|
|
||||||
if (flags_it != map.end()) {
|
|
||||||
manifest.appFlags = parseAppFlagsString(flags_it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,12 +71,6 @@ bool parseManifestV2(const std::map<std::string, std::string>& map, AppManifest&
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: app.flags - e.g. "HideStatusBar" or "HideStatusBar,Hidden"
|
|
||||||
auto flags_it = map.find("app.flags");
|
|
||||||
if (flags_it != map.end()) {
|
|
||||||
manifest.appFlags = parseAppFlagsString(flags_it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,70 +81,3 @@ TEST_CASE("parseManifestV2() should fail when the app id is invalid") {
|
|||||||
AppManifest manifest;
|
AppManifest manifest;
|
||||||
CHECK_EQ(parseManifestV2(properties, manifest), false);
|
CHECK_EQ(parseManifestV2(properties, manifest), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("parseAppFlagsString() should parse various flag combinations") {
|
|
||||||
CHECK_EQ(parseAppFlagsString(""), 0);
|
|
||||||
CHECK_EQ(parseAppFlagsString("None"), 0);
|
|
||||||
CHECK_EQ(parseAppFlagsString("HideStatusBar"), AppManifest::Flags::HideStatusBar);
|
|
||||||
CHECK_EQ(parseAppFlagsString("hidden"), AppManifest::Flags::Hidden);
|
|
||||||
CHECK_EQ(parseAppFlagsString("HideStatusBar,Hidden"), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
|
||||||
CHECK_EQ(parseAppFlagsString(" hidestatusbar , hidden "), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
|
||||||
CHECK_EQ(parseAppFlagsString("HideStatusBar, Hidden"), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("parseManifest() should parse V1 flags for fullscreen") {
|
|
||||||
TestFile file("test-manifest-v1-flags.properties");
|
|
||||||
file.writeData(
|
|
||||||
"[manifest]\n"
|
|
||||||
"version=0.1\n"
|
|
||||||
"[target]\n"
|
|
||||||
"sdk=0.0.0\n"
|
|
||||||
"platforms=esp32\n"
|
|
||||||
"[app]\n"
|
|
||||||
"id=one.tactility.sdktest\n"
|
|
||||||
"versionName=0.1.0\n"
|
|
||||||
"versionCode=1\n"
|
|
||||||
"name=SDK Test\n"
|
|
||||||
"flags=HideStatusBar\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
AppManifest manifest;
|
|
||||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
|
||||||
CHECK_EQ(manifest.appFlags & AppManifest::Flags::HideStatusBar, AppManifest::Flags::HideStatusBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("parseManifest() should parse V2 flags for fullscreen") {
|
|
||||||
TestFile file("test-manifest-v2-flags.properties");
|
|
||||||
file.writeData(
|
|
||||||
"manifest.version=0.1\n"
|
|
||||||
"target.sdk=0.0.0\n"
|
|
||||||
"target.platforms=esp32\n"
|
|
||||||
"app.id=one.tactility.sdktest\n"
|
|
||||||
"app.version.name=0.1.0\n"
|
|
||||||
"app.version.code=1\n"
|
|
||||||
"app.name=SDK Test\n"
|
|
||||||
"app.flags=HideStatusBar\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
AppManifest manifest;
|
|
||||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
|
||||||
CHECK_EQ(manifest.appFlags & AppManifest::Flags::HideStatusBar, AppManifest::Flags::HideStatusBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("parseManifest() should parse combined flags") {
|
|
||||||
TestFile file("test-manifest-v2-flags2.properties");
|
|
||||||
file.writeData(
|
|
||||||
"manifest.version=0.1\n"
|
|
||||||
"target.sdk=0.0.0\n"
|
|
||||||
"target.platforms=esp32\n"
|
|
||||||
"app.id=one.tactility.sdktest\n"
|
|
||||||
"app.version.name=0.1.0\n"
|
|
||||||
"app.version.code=1\n"
|
|
||||||
"app.name=SDK Test\n"
|
|
||||||
"app.flags=HideStatusBar,Hidden\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
AppManifest manifest;
|
|
||||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
|
||||||
CHECK_EQ(manifest.appFlags, (AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden));
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user