UDisplay Modular Refactoring, adding DSI for P4 (#24107)
This commit is contained in:
parent
e7bc18c148
commit
c0f1be5994
File diff suppressed because it is too large
Load Diff
@ -7,14 +7,54 @@
|
||||
#include <SPI.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#if __has_include("soc/soc_caps.h")
|
||||
# include "soc/soc_caps.h"
|
||||
#else
|
||||
# error "No ESP capability header found"
|
||||
#endif
|
||||
#if (SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS)
|
||||
#define UDISPLAY_I80
|
||||
#include "uDisplay_I80_panel.h"
|
||||
#endif
|
||||
|
||||
#if defined(SOC_LCD_RGB_SUPPORTED)
|
||||
#include "uDisplay_RGB_panel.h"
|
||||
#endif
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
#include "uDisplay_DSI_panel.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
#define USE_ESP32_S3
|
||||
#endif
|
||||
#include "soc/gpio_periph.h"
|
||||
#include <rom/gpio.h>
|
||||
#include "driver/spi_master.h"
|
||||
// #include "driver/spi_master.h"
|
||||
#endif
|
||||
|
||||
#include "uDisplay_SPI_controller.h"
|
||||
#include "uDisplay_I2C_panel.h"
|
||||
#include "uDisplay_EPD_panel.h"
|
||||
#include "uDisplay_SPI_panel.h"
|
||||
|
||||
// ===== Panel Config Union =====
|
||||
// Union to hold any panel configuration type
|
||||
// Only one config is active at a time based on interface type
|
||||
union PanelConfigUnion {
|
||||
SPIPanelConfig spi;
|
||||
I2CPanelConfig i2c;
|
||||
EPDPanelConfig epd;
|
||||
#ifdef UDISPLAY_I80
|
||||
I80PanelConfig i80;
|
||||
#endif
|
||||
#if SOC_LCD_RGB_SUPPORTED
|
||||
esp_lcd_rgb_panel_config_t rgb; // ESP-IDF native config
|
||||
#endif
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
DSIPanelConfig dsi;
|
||||
#endif
|
||||
};
|
||||
|
||||
enum {
|
||||
UT_RD,UT_RDM,UT_CP,UT_RTF,UT_MV,UT_MVB,UT_RT,UT_RTT,UT_RDW,UT_RDWM,UT_WR,UT_WRW,UT_CPR,UT_AND,UT_SCALE,UT_LIM,UT_DBG,UT_GSRT,UT_XPT,UT_CPM,UT_END
|
||||
};
|
||||
@ -24,37 +64,20 @@ enum {
|
||||
#define UDSP_READ_STATUS 0xf2
|
||||
|
||||
|
||||
#define SIMPLERS_XP par_dbl[1]
|
||||
#define SIMPLERS_XM par_cs
|
||||
#define SIMPLERS_YP par_rs
|
||||
#define SIMPLERS_YM par_dbl[0]
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include "esp_private/gdma.h"
|
||||
#include <hal/gpio_ll.h>
|
||||
#include <hal/lcd_hal.h>
|
||||
#include <soc/lcd_cam_reg.h>
|
||||
#include <soc/lcd_cam_struct.h>
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
#include "esp_pm.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include <hal/dma_types.h>
|
||||
#include <rom/cache.h>
|
||||
#include "esp_rom_lldesc.h"
|
||||
#endif // USE_ESP32_S3
|
||||
// Simple resistive touch pin mapping (I80 only):
|
||||
// XP = data_pins_low[1], XM = cs_pin, YP = dc_pin, YM = data_pins_low[0]
|
||||
|
||||
#define _UDSP_I2C 1
|
||||
#define _UDSP_SPI 2
|
||||
#define _UDSP_PAR8 3
|
||||
#define _UDSP_PAR16 4
|
||||
#define _UDSP_RGB 5
|
||||
#define _UDSP_DSI 6
|
||||
|
||||
#define UDISP1_WHITE 1
|
||||
#define UDISP1_BLACK 0
|
||||
|
||||
#define MAX_LUTS 5
|
||||
// #define MAX_LUTS 5
|
||||
|
||||
#define DISPLAY_INIT_MODE 0
|
||||
#define DISPLAY_INIT_PARTIAL 1
|
||||
@ -95,14 +118,25 @@ class uDisplay : public Renderer {
|
||||
|
||||
private:
|
||||
uint8_t *frame_buffer;
|
||||
uint8_t *lut_full;
|
||||
uint8_t *lut_partial;
|
||||
uint8_t *lut_array[MAX_LUTS];
|
||||
// uint8_t *lut_full; // MOVED to EPDPanelConfig.lut_full_data
|
||||
// uint8_t *lut_partial; // MOVED to EPDPanelConfig.lut_partial_data
|
||||
// uint8_t *lut_array[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_array_data
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
uint8_t dsp_cmds[1024]; // for DSI, does not hurt for ESP32
|
||||
#else
|
||||
uint8_t dsp_cmds[256];
|
||||
#endif
|
||||
char dname[16];
|
||||
SPIClass *uspi;
|
||||
|
||||
SPIController *spiController;
|
||||
TwoWire *wire;
|
||||
SPISettings spiSettings;
|
||||
UniversalPanel* universal_panel = nullptr;
|
||||
|
||||
// ===== Panel Configuration Union =====
|
||||
// Heap-allocated union holding the active panel config
|
||||
// Allocated after parsing :H line, populated during INI parsing
|
||||
PanelConfigUnion* panel_config = nullptr;
|
||||
|
||||
|
||||
uint16_t x_addr_offs[4];
|
||||
uint16_t y_addr_offs[4];
|
||||
@ -117,8 +151,8 @@ private:
|
||||
uint16_t seta_xp2;
|
||||
uint16_t seta_yp1;
|
||||
uint16_t seta_yp2;
|
||||
uint16_t lutptime;
|
||||
uint16_t lut3time;
|
||||
// uint16_t lutptime; // MOVED to EPDPanelConfig.lut_partial_time
|
||||
// uint16_t lut3time; // MOVED to EPDPanelConfig.update_time
|
||||
uint16_t lut_num;
|
||||
|
||||
uint8_t bpp;
|
||||
@ -129,14 +163,14 @@ private:
|
||||
uint8_t i2c_col_end;
|
||||
uint8_t i2c_page_start;
|
||||
uint8_t i2c_page_end;
|
||||
uint8_t dsp_ncmds;
|
||||
uint16_t dsp_ncmds;
|
||||
uint8_t dsp_on;
|
||||
uint8_t dsp_off;
|
||||
uint8_t allcmd_mode;
|
||||
uint8_t splash_size;
|
||||
uint8_t dimmer8;
|
||||
uint8_t spi_speed;
|
||||
uint8_t spi_nr;
|
||||
// uint8_t spi_nr;
|
||||
uint8_t rot[4];
|
||||
uint8_t rot_t[4];
|
||||
uint8_t madctrl;
|
||||
@ -150,19 +184,19 @@ private:
|
||||
uint8_t inv_off;
|
||||
uint8_t sa_mode;
|
||||
uint8_t dim_op;
|
||||
uint8_t lutfsize;
|
||||
uint8_t lutpsize;
|
||||
uint8_t lut_siz_full;
|
||||
uint8_t lut_siz_partial;
|
||||
uint8_t epcoffs_full;
|
||||
uint8_t epc_full_cnt;
|
||||
uint8_t epcoffs_part;
|
||||
uint8_t epc_part_cnt;
|
||||
uint8_t lut_cnt[MAX_LUTS];
|
||||
uint8_t lut_cmd[MAX_LUTS];
|
||||
uint8_t lut_siz[MAX_LUTS];
|
||||
// uint8_t lutfsize; // MOVED to EPDPanelConfig.lutfsize
|
||||
// uint8_t lutpsize; // MOVED to EPDPanelConfig.lutpsize
|
||||
// uint8_t lut_siz_full; // Local variable only
|
||||
// uint8_t lut_siz_partial; // Local variable only
|
||||
// uint8_t epcoffs_full; // MOVED to EPDPanelConfig.epcoffs_full
|
||||
// uint8_t epc_full_cnt; // MOVED to EPDPanelConfig.epc_full_cnt
|
||||
// uint8_t epcoffs_part; // MOVED to EPDPanelConfig.epcoffs_part
|
||||
// uint8_t epc_part_cnt; // MOVED to EPDPanelConfig.epc_part_cnt
|
||||
// uint8_t lut_cnt[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_cnt_data
|
||||
// uint8_t lut_cmd[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_cmd
|
||||
// uint8_t lut_siz[MAX_LUTS]; // MOVED to EPDPanelConfig.lut_siz
|
||||
uint8_t ep_mode;
|
||||
uint8_t ep_update_mode;
|
||||
// uint8_t ep_update_mode; // MOVED to EPDPanel.update_mode
|
||||
uint8_t sspi;
|
||||
|
||||
int8_t spec_init;
|
||||
@ -172,64 +206,29 @@ private:
|
||||
int8_t reset;
|
||||
int8_t splash_font;
|
||||
int8_t bpmode;
|
||||
int8_t spi_cs;
|
||||
int8_t spi_clk;
|
||||
int8_t spi_mosi;
|
||||
int8_t spi_dc;
|
||||
// int8_t spi_cs;
|
||||
// int8_t spi_clk;
|
||||
// int8_t spi_mosi;
|
||||
// int8_t spi_dc;
|
||||
int8_t bpanel;
|
||||
int8_t spi_miso;
|
||||
int8_t busy_pin;
|
||||
// int8_t spi_miso;
|
||||
// int8_t busy_pin; // MOVED to EPDPanelConfig.busy_pin (EPD-only)
|
||||
|
||||
int16_t lutftime;
|
||||
// int16_t lutftime; // MOVED to EPDPanelConfig.lut_full_time
|
||||
int16_t rotmap_xmin;
|
||||
int16_t rotmap_xmax;
|
||||
int16_t rotmap_ymin;
|
||||
int16_t rotmap_ymax;
|
||||
|
||||
void beginTransaction(SPISettings s);
|
||||
void endTransaction(void);
|
||||
void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
||||
void drawPixel(int16_t x, int16_t y, uint16_t color);
|
||||
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
|
||||
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
|
||||
uint32_t str2c(char **sp, char *vp, uint32_t len);
|
||||
|
||||
void i2c_command(uint8_t val);
|
||||
void ulcd_command_one(uint8_t val);
|
||||
void ulcd_command(uint8_t val);
|
||||
void ulcd_data8(uint8_t val);
|
||||
void ulcd_data16(uint16_t val);
|
||||
void ulcd_data32(uint32_t val);
|
||||
void write8(uint8_t val);
|
||||
void write8_slow(uint8_t val);
|
||||
void write9(uint8_t val, uint8_t dc);
|
||||
void write9_slow(uint8_t val, uint8_t dc);
|
||||
void hw_write9(uint8_t val, uint8_t dc);
|
||||
void write16(uint16_t val);
|
||||
void write32(uint32_t val);
|
||||
void spi_data9(uint8_t d, uint8_t dc);
|
||||
uint8_t readData(void);
|
||||
uint8_t readStatus(void);
|
||||
uint8_t writeReg16(uint8_t reg, uint16_t wval);
|
||||
void WriteColor(uint16_t color);
|
||||
void SetLut(const unsigned char* lut);
|
||||
void SetLuts(void);
|
||||
void DisplayFrame_29(void);
|
||||
void Updateframe_EPD();
|
||||
void SetFrameMemory(const unsigned char* image_buffer);
|
||||
void SetFrameMemory(const unsigned char* image_buffer, uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height);
|
||||
void SetMemoryArea(int x_start, int y_start, int x_end, int y_end);
|
||||
void SetMemoryPointer(int x, int y);
|
||||
void DrawAbsolutePixel(int x, int y, int16_t color);
|
||||
void drawPixel_EPD(int16_t x, int16_t y, uint16_t color);
|
||||
void fillRect_EPD(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
|
||||
void drawFastVLine_EPD(int16_t x, int16_t y, int16_t h, uint16_t color);
|
||||
void drawFastHLine_EPD(int16_t x, int16_t y, int16_t w, uint16_t color);
|
||||
void Init_EPD(int8_t p);
|
||||
void spi_command_EPD(uint8_t val);
|
||||
void spi_data8_EPD(uint8_t val);
|
||||
void ClearFrameMemory(unsigned char color);
|
||||
void ClearFrame_42(void);
|
||||
void DisplayFrame_42(void);
|
||||
|
||||
|
||||
uint8_t strlen_ln(char *str);
|
||||
int32_t next_val(char **sp);
|
||||
uint32_t next_hex(char **sp);
|
||||
@ -238,82 +237,11 @@ private:
|
||||
void delay_sync(int32_t time);
|
||||
void reset_pin(int32_t delayl, int32_t delayh);
|
||||
void delay_arg(uint32_t arg);
|
||||
void Send_EP_Data(void);
|
||||
|
||||
void send_spi_cmds(uint16_t cmd_offset, uint16_t cmd_size);
|
||||
void send_spi_icmds(uint16_t cmd_size);
|
||||
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
int8_t par_cs;
|
||||
int8_t par_rs;
|
||||
int8_t par_wr;
|
||||
int8_t par_rd;
|
||||
|
||||
int8_t par_dbl[8];
|
||||
int8_t par_dbh[8];
|
||||
|
||||
int8_t de;
|
||||
int8_t vsync;
|
||||
int8_t hsync;
|
||||
int8_t pclk;
|
||||
|
||||
uint16_t hsync_polarity;
|
||||
uint16_t hsync_front_porch;
|
||||
uint16_t hsync_pulse_width;
|
||||
uint16_t hsync_back_porch;
|
||||
uint16_t vsync_polarity;
|
||||
uint16_t vsync_front_porch;
|
||||
uint16_t vsync_pulse_width;
|
||||
uint16_t vsync_back_porch;
|
||||
uint16_t pclk_active_neg;
|
||||
|
||||
esp_lcd_panel_handle_t _panel_handle = NULL;
|
||||
|
||||
esp_lcd_i80_bus_handle_t _i80_bus = nullptr;
|
||||
gdma_channel_handle_t _dma_chan;
|
||||
lldesc_t *_dmadesc = nullptr;
|
||||
uint32_t _dmadesc_size = 0;
|
||||
uint32_t _clock_reg_value;
|
||||
void calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq);
|
||||
void _alloc_dmadesc(size_t len);
|
||||
void _setup_dma_desc_links(const uint8_t *data, int32_t len);
|
||||
void pb_beginTransaction(void);
|
||||
void pb_endTransaction(void);
|
||||
void pb_wait(void);
|
||||
bool pb_busy(void);
|
||||
void _pb_init_pin(bool);
|
||||
bool pb_writeCommand(uint32_t data, uint_fast8_t bit_length);
|
||||
void pb_writeData(uint32_t data, uint_fast8_t bit_length);
|
||||
void pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma);
|
||||
void pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma);
|
||||
void _send_align_data(void);
|
||||
volatile lcd_cam_dev_t* _dev;
|
||||
uint32_t* _cache_flip;
|
||||
static constexpr size_t CACHE_SIZE = 256;
|
||||
uint32_t _cache[2][CACHE_SIZE / sizeof(uint32_t)];
|
||||
bool _has_align_data;
|
||||
uint8_t _align_data;
|
||||
void cs_control(bool level);
|
||||
uint32_t get_sr_touch(uint32_t xp, uint32_t xm, uint32_t yp, uint32_t ym);
|
||||
void drawPixel_RGB(int16_t x, int16_t y, uint16_t color);
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
// dma section
|
||||
bool DMA_Enabled = false;
|
||||
uint8_t spiBusyCheck = 0;
|
||||
spi_transaction_t trans;
|
||||
spi_device_handle_t dmaHAL;
|
||||
spi_host_device_t spi_host = VSPI_HOST;
|
||||
// spi_host_device_t spi_host = VSPI_HOST;
|
||||
bool initDMA(int32_t ctrl_cs);
|
||||
void deInitDMA(void);
|
||||
bool dmaBusy(void);
|
||||
void dmaWait(void);
|
||||
void pushPixelsDMA(uint16_t* image, uint32_t len);
|
||||
void pushPixels3DMA(uint8_t* image, uint32_t len);
|
||||
#endif // ESP32
|
||||
|
||||
#ifdef USE_UNIVERSAL_TOUCH
|
||||
// universal touch driver
|
||||
void ut_trans(char **sp, uint8_t **ut_code);
|
||||
|
||||
289
lib/lib_display/UDisplay/uDisplay_DSI_panel.cpp
Normal file
289
lib/lib_display/UDisplay/uDisplay_DSI_panel.cpp
Normal file
@ -0,0 +1,289 @@
|
||||
// ======================================================
|
||||
// uDisplay_DSI_panel.cpp - Hardcoded JD9165 MIPI-DSI Implementation
|
||||
// Based on esp_lcd_jd9165.c from Espressif
|
||||
// ======================================================
|
||||
|
||||
#include "uDisplay_DSI_panel.h"
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_ldo_regulator.h"
|
||||
#include "driver/gpio.h"
|
||||
#include <rom/cache.h>
|
||||
|
||||
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
|
||||
|
||||
DSIPanel::DSIPanel(const DSIPanelConfig& config)
|
||||
: cfg(config), rotation(0)
|
||||
{
|
||||
|
||||
framebuffer_size = cfg.width * cfg.height * 2;
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
// Step 1: Initialize LDO for display power (from config)
|
||||
if (cfg.ldo_channel >= 0 && cfg.ldo_voltage_mv > 0) {
|
||||
esp_ldo_channel_config_t ldo_config = {
|
||||
.chan_id = cfg.ldo_channel,
|
||||
.voltage_mv = cfg.ldo_voltage_mv,
|
||||
};
|
||||
ret = esp_ldo_acquire_channel(&ldo_config, &ldo_handle);
|
||||
if (ret != ESP_OK) {
|
||||
AddLog(3, "DSI: Failed to acquire LDO: %d", ret);
|
||||
return;
|
||||
}
|
||||
AddLog(3, "DSI: LDO enabled (ch %d @ %dmV)", cfg.ldo_channel, cfg.ldo_voltage_mv);
|
||||
delay(10);
|
||||
} else {
|
||||
AddLog(3, "DSI: No LDO configuration");
|
||||
}
|
||||
|
||||
// Step 2: Create DSI bus (from config)
|
||||
esp_lcd_dsi_bus_handle_t dsi_bus = nullptr;
|
||||
esp_lcd_dsi_bus_config_t bus_config = {
|
||||
.bus_id = 0,
|
||||
.num_data_lanes = cfg.dsi_lanes,
|
||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
|
||||
.lane_bit_rate_mbps = cfg.lane_speed_mbps
|
||||
};
|
||||
ret = esp_lcd_new_dsi_bus(&bus_config, &dsi_bus);
|
||||
if (ret != ESP_OK) {
|
||||
AddLog(3, "DSI: Failed to create DSI bus: %d", ret);
|
||||
return;
|
||||
}
|
||||
AddLog(3, "DSI: DSI bus created");
|
||||
|
||||
// Step 3: Create DBI IO for commands
|
||||
esp_lcd_dbi_io_config_t io_config = {
|
||||
.virtual_channel = 0,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
};
|
||||
ret = esp_lcd_new_panel_io_dbi(dsi_bus, &io_config, &io_handle);
|
||||
if (ret != ESP_OK) {
|
||||
AddLog(3, "DSI: Failed to create DBI IO: %d", ret);
|
||||
return;
|
||||
}
|
||||
AddLog(3, "DSI: DBI IO created");
|
||||
|
||||
// Step 4: Configure DPI panel (from config)
|
||||
esp_lcd_dpi_panel_config_t dpi_config = {};
|
||||
dpi_config.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT;
|
||||
dpi_config.dpi_clock_freq_mhz = cfg.pixel_clock_hz / 1000000;
|
||||
dpi_config.virtual_channel = 0;
|
||||
dpi_config.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565;
|
||||
dpi_config.num_fbs = 1;
|
||||
dpi_config.video_timing.h_size = cfg.width;
|
||||
dpi_config.video_timing.v_size = cfg.height;
|
||||
dpi_config.video_timing.hsync_back_porch = cfg.timing.h_back_porch;
|
||||
dpi_config.video_timing.hsync_pulse_width = cfg.timing.h_sync_pulse;
|
||||
dpi_config.video_timing.hsync_front_porch = cfg.timing.h_front_porch;
|
||||
dpi_config.video_timing.vsync_back_porch = cfg.timing.v_back_porch;
|
||||
dpi_config.video_timing.vsync_pulse_width = cfg.timing.v_sync_pulse;
|
||||
dpi_config.video_timing.vsync_front_porch = cfg.timing.v_front_porch;
|
||||
dpi_config.flags.use_dma2d = 1;
|
||||
|
||||
AddLog(3, "DSI: DPI config: clk=%dMHz res=%dx%d", dpi_config.dpi_clock_freq_mhz, cfg.width, cfg.height);
|
||||
AddLog(3, "DSI: H timing: BP=%d PW=%d FP=%d", cfg.timing.h_back_porch, cfg.timing.h_sync_pulse, cfg.timing.h_front_porch);
|
||||
AddLog(3, "DSI: V timing: BP=%d PW=%d FP=%d", cfg.timing.v_back_porch, cfg.timing.v_sync_pulse, cfg.timing.v_front_porch);
|
||||
AddLog(3, "DSI: Expected: clk=54MHz res=1024x600 H:160/40/160 V:23/10/12");
|
||||
|
||||
// Step 5: Create DPI panel
|
||||
ret = esp_lcd_new_panel_dpi(dsi_bus, &dpi_config, &panel_handle);
|
||||
if (ret != ESP_OK) {
|
||||
AddLog(3, "DSI: Failed to create DPI panel: %d", ret);
|
||||
return;
|
||||
}
|
||||
AddLog(3, "DSI: DPI panel created");
|
||||
|
||||
// Step 6: Reset via GPIO (from config)
|
||||
if (cfg.reset_pin >= 0) {
|
||||
gpio_config_t gpio_conf = {
|
||||
.pin_bit_mask = 1ULL << cfg.reset_pin,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
};
|
||||
gpio_config(&gpio_conf);
|
||||
gpio_set_level((gpio_num_t)cfg.reset_pin, 1);
|
||||
delay(5);
|
||||
gpio_set_level((gpio_num_t)cfg.reset_pin, 0);
|
||||
delay(10);
|
||||
gpio_set_level((gpio_num_t)cfg.reset_pin, 1);
|
||||
delay(120);
|
||||
AddLog(3, "DSI: GPIO reset completed (pin %d)", cfg.reset_pin);
|
||||
} else {
|
||||
AddLog(3, "DSI: No reset pin configured");
|
||||
}
|
||||
|
||||
// Step 7: Initialize DPI panel
|
||||
ret = esp_lcd_panel_init(panel_handle);
|
||||
if (ret != ESP_OK) {
|
||||
AddLog(3, "DSI: Panel init failed: %d", ret);
|
||||
return;
|
||||
}
|
||||
AddLog(3, "DSI: DPI panel initialized");
|
||||
|
||||
// Step 8: Get framebuffer
|
||||
void* fb_ptr = nullptr;
|
||||
ret = esp_lcd_dpi_panel_get_frame_buffer(panel_handle, 1, &fb_ptr);
|
||||
if (ret == ESP_OK && fb_ptr != nullptr) {
|
||||
framebuffer = (uint16_t*)fb_ptr;
|
||||
AddLog(3, "DSI: Framebuffer at %p", framebuffer);
|
||||
} else {
|
||||
framebuffer = nullptr;
|
||||
AddLog(3, "DSI: No framebuffer, using draw_bitmap");
|
||||
}
|
||||
|
||||
// Step 9: Send init commands from INI file
|
||||
if (cfg.init_commands && cfg.init_commands_count > 0) {
|
||||
AddLog(3, "DSI: Sending init commands from INI file");
|
||||
uint16_t index = 0;
|
||||
uint16_t cmd_num = 0;
|
||||
|
||||
while (index < cfg.init_commands_count) {
|
||||
uint8_t cmd = cfg.init_commands[index++];
|
||||
uint8_t data_size = cfg.init_commands[index++];
|
||||
|
||||
if (data_size > 0) {
|
||||
ret = esp_lcd_panel_io_tx_param(io_handle, cmd, &cfg.init_commands[index], data_size);
|
||||
index += data_size;
|
||||
} else {
|
||||
ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
|
||||
}
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
AddLog(3, "DSI: Cmd 0x%02x failed: %d", cmd, ret);
|
||||
}
|
||||
|
||||
uint8_t delay_ms = cfg.init_commands[index++];
|
||||
if (delay_ms > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
||||
}
|
||||
cmd_num++;
|
||||
}
|
||||
AddLog(3, "DSI: Sent %d commands from INI", cmd_num);
|
||||
} else {
|
||||
AddLog(3, "DSI: No init commands in config");
|
||||
}
|
||||
}
|
||||
|
||||
DSIPanel::~DSIPanel() {
|
||||
if (panel_handle) {
|
||||
esp_lcd_panel_del(panel_handle);
|
||||
}
|
||||
if (io_handle) {
|
||||
esp_lcd_panel_io_del(io_handle);
|
||||
}
|
||||
if (ldo_handle) {
|
||||
esp_ldo_release_channel(ldo_handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool DSIPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
if (!framebuffer || x < 0 || y < 0 || x >= cfg.width || y >= cfg.height) return true;
|
||||
|
||||
int16_t w = cfg.width, h = cfg.height;
|
||||
switch (rotation) {
|
||||
case 1: std::swap(w, h); std::swap(x, y); x = w - x - 1; break;
|
||||
case 2: x = w - x - 1; y = h - y - 1; break;
|
||||
case 3: std::swap(w, h); std::swap(x, y); y = h - y - 1; break;
|
||||
}
|
||||
|
||||
uint16_t* p = &framebuffer[y * cfg.width + x];
|
||||
*p = color;
|
||||
framebuffer_dirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DSIPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
for (int16_t yp = y; yp < y + h; yp++) {
|
||||
uint16_t* line_start = &framebuffer[yp * cfg.width + x];
|
||||
for (int16_t i = 0; i < w; i++) {
|
||||
line_start[i] = color;
|
||||
}
|
||||
// CACHE_WRITEBACK_ADDR((uint32_t)line_start, w * 2);
|
||||
}
|
||||
framebuffer_dirty = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DSIPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
return fillRect(x, y, w, 1, color);
|
||||
}
|
||||
|
||||
bool DSIPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
return fillRect(x, y, 1, h, color);
|
||||
}
|
||||
|
||||
bool DSIPanel::pushColors(uint16_t *data, uint16_t len, bool not_swapped) {
|
||||
esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_handle, window_x0, window_y0, window_x1, window_y1, data);
|
||||
return (ret == ESP_OK);
|
||||
}
|
||||
|
||||
bool DSIPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
||||
window_x0 = x0;
|
||||
window_y0 = y0;
|
||||
window_x1 = x1;
|
||||
window_y1 = y1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DSIPanel::displayOnff(int8_t on) {
|
||||
if (!io_handle) return false;
|
||||
|
||||
// Use commands from descriptor
|
||||
uint8_t cmd = on ? cfg.cmd_display_on : cfg.cmd_display_off;
|
||||
|
||||
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
|
||||
return (ret == ESP_OK);
|
||||
}
|
||||
|
||||
bool DSIPanel::invertDisplay(bool invert) {
|
||||
if (!io_handle) return false;
|
||||
|
||||
// Standard MIPI DCS commands for invert
|
||||
uint8_t cmd = invert ? 0x21 : 0x20; // 0x21 = INVON, 0x20 = INVOFF
|
||||
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, cmd, NULL, 0);
|
||||
return (ret == ESP_OK);
|
||||
}
|
||||
|
||||
bool DSIPanel::setRotation(uint8_t rot) {
|
||||
if (!io_handle) return false;
|
||||
|
||||
rotation = rot & 3;
|
||||
|
||||
// Standard MIPI DCS MADCTL (0x36) values for rotation
|
||||
// These are common values but may need adjustment for specific displays
|
||||
uint8_t madctl_val = 0;
|
||||
|
||||
switch (rotation) {
|
||||
case 0: // Portrait
|
||||
madctl_val = 0x00; // Normal
|
||||
break;
|
||||
case 1: // Landscape (90° clockwise)
|
||||
madctl_val = 0x60; // MX + MV
|
||||
break;
|
||||
case 2: // Portrait inverted (180°)
|
||||
madctl_val = 0xC0; // MX + MY
|
||||
break;
|
||||
case 3: // Landscape inverted (270° clockwise)
|
||||
madctl_val = 0xA0; // MY + MV
|
||||
break;
|
||||
}
|
||||
|
||||
// Send MADCTL command (0x36) with rotation value
|
||||
esp_err_t ret = esp_lcd_panel_io_tx_param(io_handle, 0x36, &madctl_val, 1);
|
||||
return false; // pass job to Renderer
|
||||
}
|
||||
|
||||
bool DSIPanel::updateFrame() {
|
||||
if (!framebuffer_dirty) {
|
||||
return true;
|
||||
}
|
||||
CACHE_WRITEBACK_ADDR((uint32_t)framebuffer, framebuffer_size); //KISS and fast enough!
|
||||
framebuffer_dirty = false; // ← RESET
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // SOC_MIPI_DSI_SUPPORTED
|
||||
103
lib/lib_display/UDisplay/uDisplay_DSI_panel.h
Normal file
103
lib/lib_display/UDisplay/uDisplay_DSI_panel.h
Normal file
@ -0,0 +1,103 @@
|
||||
// WIP - NOT REALLY IMPLEMENTED!!!
|
||||
// ======================================================
|
||||
// uDisplay_DSI_panel.h - MIPI-DSI Display Panel Implementation
|
||||
// ======================================================
|
||||
|
||||
#pragma once
|
||||
#ifdef ESP32
|
||||
#if __has_include("soc/soc_caps.h")
|
||||
# include "soc/soc_caps.h"
|
||||
#else
|
||||
# error "No ESP capability header found"
|
||||
#endif
|
||||
|
||||
#if SOC_MIPI_DSI_SUPPORTED
|
||||
|
||||
#include "uDisplay_panel.h"
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_ldo_regulator.h"
|
||||
|
||||
struct DSIPanelConfig {
|
||||
// Basic display info
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t bpp; // 24
|
||||
|
||||
// DSI parameters (from :H line)
|
||||
uint8_t dsi_lanes; // 2
|
||||
int8_t te_pin; // -1 (no TE)
|
||||
int8_t reset_pin; // -1 (no reset control)
|
||||
int ldo_channel; // 3
|
||||
int ldo_voltage_mv; // 2500
|
||||
uint32_t pixel_clock_hz; // 54000000
|
||||
uint32_t lane_speed_mbps; // 750
|
||||
uint8_t rgb_order; // 0=RGB, 1=BGR
|
||||
uint8_t data_endian; // 0=Big, 1=Little
|
||||
|
||||
// Video timing (from :V line)
|
||||
struct {
|
||||
uint16_t h_front_porch; // 160
|
||||
uint16_t v_front_porch; // 40
|
||||
uint16_t h_back_porch; // 160
|
||||
uint16_t h_sync_pulse; // 12
|
||||
uint16_t v_sync_pulse; // 10
|
||||
uint16_t v_back_porch; // 23
|
||||
} timing;
|
||||
|
||||
// Init commands (from :I section)
|
||||
uint8_t* init_commands;
|
||||
uint16_t init_commands_count;
|
||||
|
||||
// Display on/off commands (from :O and :o lines)
|
||||
uint8_t cmd_display_on; // 0x29
|
||||
uint8_t cmd_display_off; // 0x28
|
||||
};
|
||||
|
||||
class DSIPanel : public UniversalPanel {
|
||||
public:
|
||||
// Constructor - takes ESP-IDF panel handle (already initialized)
|
||||
DSIPanel(const DSIPanelConfig& config);
|
||||
~DSIPanel();
|
||||
|
||||
// Core graphics API (must return bool)
|
||||
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
||||
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
|
||||
bool pushColors(uint16_t *data, uint16_t len, bool not_swapped) override;
|
||||
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
|
||||
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
|
||||
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
|
||||
|
||||
// Control API
|
||||
bool displayOnff(int8_t on) override;
|
||||
bool invertDisplay(bool invert) override;
|
||||
bool setRotation(uint8_t rotation) override;
|
||||
bool updateFrame() override;
|
||||
|
||||
// Get direct framebuffer access (for DPI mode)
|
||||
uint16_t* framebuffer = nullptr;
|
||||
|
||||
private:
|
||||
// ESP-IDF panel handle
|
||||
esp_lcd_panel_handle_t panel_handle = nullptr;
|
||||
esp_lcd_panel_io_handle_t io_handle = nullptr;
|
||||
esp_ldo_channel_handle_t ldo_handle = nullptr;
|
||||
DSIPanelConfig cfg;
|
||||
void sendInitCommandsDBI();
|
||||
|
||||
// Display parameters
|
||||
uint8_t rotation = 0;
|
||||
|
||||
// Address window tracking
|
||||
int16_t window_x0 = 0;
|
||||
int16_t window_y0 = 0;
|
||||
int16_t window_x1 = 0;
|
||||
int16_t window_y1 = 0;
|
||||
size_t framebuffer_size = 0;
|
||||
uint32_t framebuffer_dirty = false;
|
||||
};
|
||||
|
||||
#endif // SOC_MIPI_DSI_SUPPORTED
|
||||
#endif // ESP32
|
||||
514
lib/lib_display/UDisplay/uDisplay_EPD_panel.cpp
Normal file
514
lib/lib_display/UDisplay/uDisplay_EPD_panel.cpp
Normal file
@ -0,0 +1,514 @@
|
||||
// ======================================================
|
||||
// uDisplay_epd_panel.cpp - E-Paper Display Panel Implementation
|
||||
// ======================================================
|
||||
|
||||
#include "uDisplay_EPD_panel.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
// EPD Command Definitions
|
||||
static constexpr uint8_t DRIVER_OUTPUT_CONTROL = 0x01;
|
||||
static constexpr uint8_t BOOSTER_SOFT_START_CONTROL = 0x0C;
|
||||
static constexpr uint8_t GATE_SCAN_START_POSITION = 0x0F;
|
||||
static constexpr uint8_t DEEP_SLEEP_MODE = 0x10;
|
||||
static constexpr uint8_t DATA_ENTRY_MODE_SETTING = 0x11;
|
||||
static constexpr uint8_t SW_RESET = 0x12;
|
||||
static constexpr uint8_t TEMPERATURE_SENSOR_CONTROL = 0x1A;
|
||||
static constexpr uint8_t MASTER_ACTIVATION = 0x20;
|
||||
static constexpr uint8_t DISPLAY_UPDATE_CONTROL_1 = 0x21;
|
||||
static constexpr uint8_t DISPLAY_UPDATE_CONTROL_2 = 0x22;
|
||||
static constexpr uint8_t WRITE_RAM = 0x24;
|
||||
static constexpr uint8_t WRITE_VCOM_REGISTER = 0x2C;
|
||||
static constexpr uint8_t WRITE_LUT_REGISTER = 0x32;
|
||||
static constexpr uint8_t SET_DUMMY_LINE_PERIOD = 0x3A;
|
||||
static constexpr uint8_t SET_GATE_TIME = 0x3B;
|
||||
static constexpr uint8_t BORDER_WAVEFORM_CONTROL = 0x3C;
|
||||
static constexpr uint8_t SET_RAM_X_ADDRESS_START_END_POSITION = 0x44;
|
||||
static constexpr uint8_t SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45;
|
||||
static constexpr uint8_t SET_RAM_X_ADDRESS_COUNTER = 0x4E;
|
||||
static constexpr uint8_t SET_RAM_Y_ADDRESS_COUNTER = 0x4F;
|
||||
static constexpr uint8_t TERMINATE_FRAME_READ_WRITE = 0xFF;
|
||||
|
||||
EPDPanel::EPDPanel(const EPDPanelConfig& config,
|
||||
SPIController* spi_ctrl,
|
||||
uint8_t* framebuffer)
|
||||
: spi(spi_ctrl), cfg(config), fb_buffer(framebuffer), update_mode(0), rotation(0)
|
||||
{
|
||||
// Don't do automatic initialization here - let the descriptor init commands handle it
|
||||
// The uDisplay framework will call send_spi_cmds() after panel creation
|
||||
// which will handle reset, LUT setup, and initial display state
|
||||
}
|
||||
|
||||
EPDPanel::~EPDPanel() {
|
||||
// Panel doesn't own framebuffer or SPI controller
|
||||
|
||||
// Free owned LUT data
|
||||
if (cfg.lut_full_data) {
|
||||
free(cfg.lut_full_data);
|
||||
}
|
||||
|
||||
if (cfg.lut_partial_data) {
|
||||
free(cfg.lut_partial_data);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < 5; i++) {
|
||||
if (cfg.lut_array_data[i]) {
|
||||
free(cfg.lut_array_data[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EPDPanel::delay_sync(int32_t ms) {
|
||||
uint8_t busy_level = cfg.busy_invert ? LOW : HIGH;
|
||||
uint32_t time = millis();
|
||||
if (cfg.busy_pin >= 0) {
|
||||
while (digitalRead(cfg.busy_pin) == busy_level) {
|
||||
delay(1);
|
||||
if ((millis() - time) > cfg.busy_timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delay(ms);
|
||||
}
|
||||
}
|
||||
|
||||
void EPDPanel::resetDisplay() {
|
||||
if (cfg.reset_pin < 0) return;
|
||||
|
||||
pinMode(cfg.reset_pin, OUTPUT);
|
||||
digitalWrite(cfg.reset_pin, HIGH);
|
||||
delay(10);
|
||||
digitalWrite(cfg.reset_pin, LOW);
|
||||
delay(10);
|
||||
digitalWrite(cfg.reset_pin, HIGH);
|
||||
delay(10);
|
||||
delay_sync(100); // Use delay_sync instead of waitBusy
|
||||
}
|
||||
|
||||
void EPDPanel::waitBusy() {
|
||||
// Deprecated - use delay_sync instead
|
||||
delay_sync(cfg.update_time);
|
||||
}
|
||||
|
||||
void EPDPanel::setLut(const uint8_t* lut, uint16_t len) {
|
||||
if (!lut || len == 0) return;
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(WRITE_LUT_REGISTER);
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
spi->writeData8(lut[i]);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
}
|
||||
|
||||
void EPDPanel::setMemoryArea(int x_start, int y_start, int x_end, int y_end) {
|
||||
int x_start1 = (x_start >> 3) & 0xFF;
|
||||
int x_end1 = (x_end >> 3) & 0xFF;
|
||||
int y_start1 = y_start & 0xFF;
|
||||
int y_start2 = (y_start >> 8) & 0xFF;
|
||||
int y_end1 = y_end & 0xFF;
|
||||
int y_end2 = (y_end >> 8) & 0xFF;
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(SET_RAM_X_ADDRESS_START_END_POSITION);
|
||||
spi->writeData8(x_start1);
|
||||
spi->writeData8(x_end1);
|
||||
|
||||
spi->writeCommand(SET_RAM_Y_ADDRESS_START_END_POSITION);
|
||||
if (cfg.ep_mode == 3) {
|
||||
// ep_mode 3: reversed Y order
|
||||
spi->writeData8(y_end1);
|
||||
spi->writeData8(y_end2);
|
||||
spi->writeData8(y_start1);
|
||||
spi->writeData8(y_start2);
|
||||
} else {
|
||||
spi->writeData8(y_start1);
|
||||
spi->writeData8(y_start2);
|
||||
spi->writeData8(y_end1);
|
||||
spi->writeData8(y_end2);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
}
|
||||
|
||||
void EPDPanel::setMemoryPointer(int x, int y) {
|
||||
int x1, y1, y2;
|
||||
|
||||
if (cfg.ep_mode == 3) {
|
||||
x1 = (x >> 3) & 0xFF;
|
||||
y--;
|
||||
y1 = y & 0xFF;
|
||||
y2 = (y >> 8) & 0xFF;
|
||||
} else {
|
||||
x1 = (x >> 3) & 0xFF;
|
||||
y1 = y & 0xFF;
|
||||
y2 = (y >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(SET_RAM_X_ADDRESS_COUNTER);
|
||||
spi->writeData8(x1);
|
||||
spi->writeCommand(SET_RAM_Y_ADDRESS_COUNTER);
|
||||
spi->writeData8(y1);
|
||||
spi->writeData8(y2);
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
}
|
||||
|
||||
void EPDPanel::clearFrameMemory(uint8_t color) {
|
||||
setMemoryArea(0, 0, cfg.width - 1, cfg.height - 1);
|
||||
setMemoryPointer(0, 0);
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(WRITE_RAM);
|
||||
|
||||
uint32_t pixel_count = (cfg.width * cfg.height) / 8;
|
||||
for (uint32_t i = 0; i < pixel_count; i++) {
|
||||
spi->writeData8(color);
|
||||
}
|
||||
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
}
|
||||
|
||||
void EPDPanel::displayFrame() {
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(DISPLAY_UPDATE_CONTROL_2);
|
||||
spi->writeData8(0xC4);
|
||||
spi->writeCommand(MASTER_ACTIVATION);
|
||||
spi->writeData8(TERMINATE_FRAME_READ_WRITE);
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
|
||||
delay_sync(cfg.update_time); // Use delay_sync with proper timing
|
||||
}
|
||||
|
||||
void EPDPanel::drawAbsolutePixel(int x, int y, uint16_t color) {
|
||||
// Bounds check using physical dimensions
|
||||
if (x < 0 || x >= cfg.width || y < 0 || y >= cfg.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CRITICAL: Must match Renderer::drawPixel() layout!
|
||||
//
|
||||
// Two rendering systems write to the SAME framebuffer:
|
||||
// 1. Renderer::drawPixel() - used by DrawStringAt() for text (Splash Screen)
|
||||
// 2. EPDPanel::drawPixel() - used by Adafruit_GFX for graphics (circles, lines)
|
||||
//
|
||||
// Both MUST use the same framebuffer layout: Y-column-wise
|
||||
// Layout: fb[x + (y/8)*width] with bit position (y&7)
|
||||
// This means 8 vertical pixels are stored in one byte.
|
||||
//
|
||||
// setFrameMemory() will convert Y-column to X-row format when sending to hardware.
|
||||
|
||||
if (color) {
|
||||
fb_buffer[x + (y / 8) * cfg.width] |= (1 << (y & 7));
|
||||
} else {
|
||||
fb_buffer[x + (y / 8) * cfg.width] &= ~(1 << (y & 7));
|
||||
}
|
||||
}
|
||||
|
||||
// ===== UniversalPanel Interface Implementation =====
|
||||
|
||||
bool EPDPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
if (!fb_buffer) return false;
|
||||
|
||||
// Get rotated dimensions for bounds check
|
||||
int16_t w = cfg.width, h = cfg.height;
|
||||
if (rotation == 1 || rotation == 3) {
|
||||
std::swap(w, h);
|
||||
}
|
||||
|
||||
if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) {
|
||||
return false; // Out of bounds
|
||||
}
|
||||
|
||||
// Apply rotation transformation using PHYSICAL dimensions (gxs/gys)
|
||||
switch (rotation) {
|
||||
case 1:
|
||||
std::swap(x, y);
|
||||
x = cfg.width - x - 1; // gxs
|
||||
break;
|
||||
case 2:
|
||||
x = cfg.width - x - 1; // gxs
|
||||
y = cfg.height - y - 1; // gys
|
||||
break;
|
||||
case 3:
|
||||
std::swap(x, y);
|
||||
y = cfg.height - y - 1; // gys
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert color to monochrome and draw
|
||||
drawAbsolutePixel(x, y, (color != 0) ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
// Use drawPixel to handle rotation properly
|
||||
for (int16_t yp = y; yp < y + h; yp++) {
|
||||
for (int16_t xp = x; xp < x + w; xp++) {
|
||||
drawPixel(xp, yp, color);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::pushColors(uint16_t *data, uint16_t len, bool first) {
|
||||
// Convert RGB565 to monochrome and write to framebuffer
|
||||
// Pixel is white if at least one of the 3 RGB components is above 50%
|
||||
static constexpr uint16_t RGB16_TO_MONO = 0x8410;
|
||||
|
||||
if (!fb_buffer) return false;
|
||||
|
||||
// Write pixels to framebuffer based on window coordinates
|
||||
// IMPORTANT: window coordinates are in LOGICAL (rotated) space,
|
||||
// so we must use drawPixel (not drawAbsolutePixel) to apply rotation!
|
||||
for (int16_t y = window_y1; y < window_y2 && len > 0; y++) {
|
||||
for (int16_t x = window_x1; x < window_x2 && len > 0; x++, len--) {
|
||||
uint16_t color = *data++;
|
||||
// Convert to mono: white if any component > 50%
|
||||
bool pixel = (color & RGB16_TO_MONO) ? true : false;
|
||||
if (cfg.invert_colors) pixel = !pixel;
|
||||
drawPixel(x, y, pixel ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
return true; // Handled by EPD panel
|
||||
}
|
||||
|
||||
bool EPDPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
||||
// Save window coordinates for pushColors
|
||||
window_x1 = x0;
|
||||
window_y1 = y0;
|
||||
window_x2 = x1;
|
||||
window_y2 = y1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
while (w--) {
|
||||
drawPixel(x, y, color);
|
||||
x++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
while (h--) {
|
||||
drawPixel(x, y, color);
|
||||
y++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::displayOnff(int8_t on) {
|
||||
// EPD doesn't have on/off in traditional sense
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::invertDisplay(bool invert) {
|
||||
// Toggle color inversion logic
|
||||
cfg.invert_colors = invert;
|
||||
|
||||
// For EPD, we need to redraw the entire display when inversion changes
|
||||
if (fb_buffer) {
|
||||
// Invert the entire framebuffer
|
||||
uint32_t byte_count = (cfg.width * cfg.height) / 8;
|
||||
for (uint32_t i = 0; i < byte_count; i++) {
|
||||
fb_buffer[i] = ~fb_buffer[i];
|
||||
}
|
||||
updateFrame();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::setRotation(uint8_t rot) {
|
||||
rotation = rot & 3; // Store rotation (0-3)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EPDPanel::updateFrame() {
|
||||
if (!fb_buffer) return false;
|
||||
|
||||
// Handle different EPD modes
|
||||
if (cfg.ep_mode == 1 || cfg.ep_mode == 3) {
|
||||
// Mode 1 (2-LUT) or Mode 3 (command-based): Use descriptor command sequences
|
||||
switch (update_mode) {
|
||||
case 1: // DISPLAY_INIT_PARTIAL
|
||||
if (cfg.epc_part_cnt && cfg.send_cmds_callback) {
|
||||
cfg.send_cmds_callback(cfg.epcoffs_part, cfg.epc_part_cnt);
|
||||
}
|
||||
break;
|
||||
case 2: // DISPLAY_INIT_FULL
|
||||
if (cfg.epc_full_cnt && cfg.send_cmds_callback) {
|
||||
cfg.send_cmds_callback(cfg.epcoffs_full, cfg.epc_full_cnt);
|
||||
}
|
||||
break;
|
||||
default: // DISPLAY_INIT_MODE (0)
|
||||
// Default: write framebuffer and display
|
||||
setFrameMemory(fb_buffer, 0, 0, cfg.width, cfg.height);
|
||||
displayFrame();
|
||||
}
|
||||
} else if (cfg.ep_mode == 2) {
|
||||
// Mode 2 (5-LUT / 4.2" displays): Use internal displayFrame_42
|
||||
displayFrame_42();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== ep_mode 2 Support (5-LUT mode) =====
|
||||
|
||||
void EPDPanel::setLuts() {
|
||||
if (!cfg.lut_array || !cfg.lut_cnt) return;
|
||||
|
||||
for (uint8_t index = 0; index < 5; index++) {
|
||||
if (cfg.lut_cmd[index] == 0 || !cfg.lut_array[index]) continue;
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(cfg.lut_cmd[index]);
|
||||
for (uint8_t count = 0; count < cfg.lut_cnt[index]; count++) {
|
||||
spi->writeData8(cfg.lut_array[index][count]);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
void EPDPanel::clearFrame_42() {
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
|
||||
spi->writeCommand(cfg.saw_1);
|
||||
for (uint16_t j = 0; j < cfg.height; j++) {
|
||||
for (uint16_t i = 0; i < cfg.width; i++) {
|
||||
spi->writeData8(0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
spi->writeCommand(cfg.saw_2);
|
||||
for (uint16_t j = 0; j < cfg.height; j++) {
|
||||
for (uint16_t i = 0; i < cfg.width; i++) {
|
||||
spi->writeData8(0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
spi->writeCommand(cfg.saw_3);
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
|
||||
delay_sync(100);
|
||||
}
|
||||
|
||||
void EPDPanel::displayFrame_42() {
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
|
||||
spi->writeCommand(cfg.saw_1);
|
||||
for(int i = 0; i < cfg.width / 8 * cfg.height; i++) {
|
||||
spi->writeData8(0xFF);
|
||||
}
|
||||
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
delay(2);
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(cfg.saw_2);
|
||||
for(int i = 0; i < cfg.width / 8 * cfg.height; i++) {
|
||||
spi->writeData8(fb_buffer[i] ^ 0xff);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
delay(2);
|
||||
|
||||
setLuts();
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(cfg.saw_3);
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
|
||||
delay_sync(100);
|
||||
}
|
||||
|
||||
// ===== Frame Memory Management =====
|
||||
|
||||
// Helper: Convert Y-column framebuffer to X-row format and send via SPI
|
||||
// Y-column: fb[x + (y/8)*width] with bit (y&7) - 8 vertical pixels per byte
|
||||
// X-row: 8 horizontal pixels per byte, MSB = leftmost pixel
|
||||
void EPDPanel::sendYColumnAsXRow(const uint8_t* y_column_buffer, uint16_t buffer_width,
|
||||
uint16_t rows, uint16_t cols_bytes) {
|
||||
for (uint16_t row = 0; row < rows; row++) {
|
||||
for (uint16_t x_byte = 0; x_byte < cols_bytes; x_byte++) {
|
||||
uint8_t byte_out = 0;
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
uint16_t x = x_byte * 8 + bit;
|
||||
uint8_t pixel = (y_column_buffer[x + (row / 8) * buffer_width] >> (row & 7)) & 1;
|
||||
if (pixel) byte_out |= (0x80 >> bit);
|
||||
}
|
||||
spi->writeData8(byte_out ^ 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EPDPanel::setFrameMemory(const uint8_t* image_buffer) {
|
||||
setMemoryArea(0, 0, cfg.width - 1, cfg.height - 1);
|
||||
setMemoryPointer(0, 0);
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(WRITE_RAM);
|
||||
sendYColumnAsXRow(image_buffer, cfg.width, cfg.height, cfg.width / 8);
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
}
|
||||
|
||||
void EPDPanel::setFrameMemory(const uint8_t* image_buffer, uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height) {
|
||||
if (!image_buffer) return;
|
||||
|
||||
// Align to 8-pixel boundary
|
||||
x &= 0xFFF8;
|
||||
image_width &= 0xFFF8;
|
||||
|
||||
uint16_t x_end = (x + image_width >= cfg.width) ? cfg.width - 1 : x + image_width - 1;
|
||||
uint16_t y_end = (y + image_height >= cfg.height) ? cfg.height - 1 : y + image_height - 1;
|
||||
|
||||
// Full screen optimization
|
||||
if (!x && !y && image_width == cfg.width && image_height == cfg.height) {
|
||||
setFrameMemory(image_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
setMemoryArea(x, y, x_end, y_end);
|
||||
setMemoryPointer(x, y);
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
spi->writeCommand(WRITE_RAM);
|
||||
sendYColumnAsXRow(image_buffer, image_width, y_end - y + 1, (x_end - x + 1) / 8);
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
}
|
||||
|
||||
void EPDPanel::sendEPData() {
|
||||
// EP_SEND_DATA (0x66) - used by some display.ini files (e.g. v2)
|
||||
// Must also convert Y-column to X-row format like setFrameMemory()
|
||||
sendYColumnAsXRow(fb_buffer, cfg.width, cfg.height, cfg.width / 8);
|
||||
}
|
||||
|
||||
// ===== Update Mode Control =====
|
||||
|
||||
void EPDPanel::setUpdateMode(uint8_t mode) {
|
||||
update_mode = mode;
|
||||
}
|
||||
134
lib/lib_display/UDisplay/uDisplay_EPD_panel.h
Normal file
134
lib/lib_display/UDisplay/uDisplay_EPD_panel.h
Normal file
@ -0,0 +1,134 @@
|
||||
// ======================================================
|
||||
// uDisplay_epd_panel.h - E-Paper Display Panel Implementation
|
||||
// ======================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include "uDisplay_panel.h"
|
||||
#include "uDisplay_SPI_controller.h"
|
||||
|
||||
/**
|
||||
* Configuration for E-Paper displays
|
||||
*/
|
||||
struct EPDPanelConfig {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t bpp; // Always 1 for EPD
|
||||
uint8_t ep_mode; // 1=2-LUT, 2=5-LUT, 3=command-based
|
||||
|
||||
// Timing
|
||||
int16_t lut_full_time;
|
||||
uint16_t lut_partial_time;
|
||||
uint16_t update_time;
|
||||
|
||||
// Pins
|
||||
int8_t reset_pin;
|
||||
int8_t busy_pin;
|
||||
|
||||
// EPD-specific flags
|
||||
bool invert_colors; // If true, invert color logic
|
||||
bool invert_framebuffer; // If true, invert when sending to display
|
||||
bool busy_invert; // If true, busy pin is active low
|
||||
|
||||
// Busy timeout
|
||||
uint16_t busy_timeout = 3000; // UDSP_BUSY_TIMEOUT
|
||||
|
||||
// Command bytes for ep_mode 2 (4.2" displays)
|
||||
uint8_t saw_1 = 0; // First command for frame update
|
||||
uint8_t saw_2 = 0; // Second command for frame update
|
||||
uint8_t saw_3 = 0; // Third command for frame update
|
||||
|
||||
// LUT data (for ep_mode 1 - 2-LUT mode)
|
||||
const uint8_t* lut_full = nullptr;
|
||||
uint16_t lut_full_len = 0;
|
||||
const uint8_t* lut_partial = nullptr;
|
||||
uint16_t lut_partial_len = 0;
|
||||
|
||||
// LUT data (for ep_mode 2 - 5-LUT mode)
|
||||
const uint8_t** lut_array = nullptr; // Array of 5 LUTs
|
||||
const uint8_t* lut_cnt = nullptr; // Size of each LUT
|
||||
uint8_t lut_cmd[5] = {0}; // Commands for each LUT
|
||||
|
||||
// Additional LUT management (owned by EPD panel)
|
||||
uint8_t* lut_full_data = nullptr; // Owned pointer to full LUT data
|
||||
uint8_t* lut_partial_data = nullptr; // Owned pointer to partial LUT data
|
||||
uint8_t* lut_array_data[5] = {nullptr, nullptr, nullptr, nullptr, nullptr}; // Owned pointers to LUT array data
|
||||
uint16_t lutfsize = 0; // Filled size of lut_full
|
||||
uint16_t lutpsize = 0; // Filled size of lut_partial
|
||||
uint8_t lut_cnt_data[5] = {0}; // Filled sizes of each LUT in array
|
||||
uint8_t lut_siz[5] = {0}; // Allocated sizes of each LUT in array
|
||||
|
||||
// Command offsets for ep_mode 1 and 3
|
||||
uint16_t epcoffs_full = 0; // Offset to full update commands
|
||||
uint16_t epcoffs_part = 0; // Offset to partial update commands
|
||||
uint8_t epc_full_cnt = 0; // Count of full update commands
|
||||
uint8_t epc_part_cnt = 0; // Count of partial update commands
|
||||
|
||||
// Callback to send command sequences from descriptor
|
||||
std::function<void(uint16_t offset, uint16_t count)> send_cmds_callback;
|
||||
};
|
||||
|
||||
class EPDPanel : public UniversalPanel {
|
||||
public:
|
||||
EPDPanel(const EPDPanelConfig& config,
|
||||
SPIController* spi_ctrl,
|
||||
uint8_t* framebuffer); // REQUIRED for EPD
|
||||
|
||||
~EPDPanel();
|
||||
|
||||
// UniversalPanel interface
|
||||
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
||||
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
|
||||
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override;
|
||||
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
|
||||
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
|
||||
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
|
||||
|
||||
bool displayOnff(int8_t on) override;
|
||||
bool invertDisplay(bool invert) override;
|
||||
bool setRotation(uint8_t rotation) override;
|
||||
bool updateFrame() override;
|
||||
|
||||
// EPD-specific public methods (for uDisplay wrapper compatibility)
|
||||
void resetDisplay();
|
||||
void setLut(const uint8_t* lut, uint16_t len);
|
||||
void setLuts(); // For ep_mode 2 (5-LUT mode)
|
||||
void setMemoryArea(int x_start, int y_start, int x_end, int y_end);
|
||||
void setMemoryPointer(int x, int y);
|
||||
void clearFrameMemory(uint8_t color);
|
||||
void displayFrame();
|
||||
void delay_sync(int32_t ms);
|
||||
|
||||
// ep_mode 2 specific (4.2" displays)
|
||||
void clearFrame_42();
|
||||
void displayFrame_42();
|
||||
|
||||
// Frame memory management
|
||||
void setFrameMemory(const uint8_t* image_buffer);
|
||||
void setFrameMemory(const uint8_t* image_buffer, uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
||||
void sendEPData();
|
||||
|
||||
// Update mode control (for ep_mode 1 and 3)
|
||||
void setUpdateMode(uint8_t mode); // 0=DISPLAY_INIT_MODE, 1=DISPLAY_INIT_PARTIAL, 2=DISPLAY_INIT_FULL
|
||||
|
||||
EPDPanelConfig cfg;
|
||||
|
||||
private:
|
||||
SPIController* spi;
|
||||
uint8_t* fb_buffer; // Framebuffer (always used)
|
||||
uint8_t update_mode; // 0=DISPLAY_INIT_MODE, 1=DISPLAY_INIT_PARTIAL, 2=DISPLAY_INIT_FULL
|
||||
uint8_t rotation; // Current rotation (0-3)
|
||||
|
||||
// Address window for pushColors
|
||||
int16_t window_x1 = 0;
|
||||
int16_t window_y1 = 0;
|
||||
int16_t window_x2 = 0;
|
||||
int16_t window_y2 = 0;
|
||||
|
||||
// Private helpers
|
||||
void waitBusy();
|
||||
void drawAbsolutePixel(int x, int y, uint16_t color);
|
||||
void sendYColumnAsXRow(const uint8_t* y_column_buffer, uint16_t buffer_width,
|
||||
uint16_t rows, uint16_t cols_bytes);
|
||||
};
|
||||
61
lib/lib_display/UDisplay/uDisplay_I2C_panel.cpp
Normal file
61
lib/lib_display/UDisplay/uDisplay_I2C_panel.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "uDisplay_I2C_panel.h"
|
||||
|
||||
i2c_panel::i2c_panel(const I2CPanelConfig& config, uint8_t* framebuffer)
|
||||
: cfg(config), framebuffer(framebuffer) {
|
||||
|
||||
// Execute initialization commands
|
||||
if (cfg.init_commands && cfg.init_commands_count > 0) {
|
||||
for (uint16_t i = 0; i < cfg.init_commands_count; i++) {
|
||||
i2c_command(cfg.init_commands[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool i2c_panel::updateFrame() {
|
||||
if (!framebuffer) return false;
|
||||
|
||||
i2c_command(cfg.cmd_set_addr_x | 0x0);
|
||||
i2c_command(cfg.page_start | 0x0);
|
||||
i2c_command(cfg.page_end | 0x0);
|
||||
|
||||
uint8_t ys = cfg.height >> 3;
|
||||
uint8_t xs = cfg.width >> 3;
|
||||
uint8_t m_row = cfg.cmd_set_addr_y;
|
||||
uint8_t m_col = cfg.col_start;
|
||||
|
||||
uint16_t p = 0;
|
||||
uint8_t i, j, k = 0;
|
||||
|
||||
for (i = 0; i < ys; i++) {
|
||||
i2c_command(0xB0 + i + m_row);
|
||||
i2c_command(m_col & 0xf);
|
||||
i2c_command(0x10 | (m_col >> 4));
|
||||
|
||||
for (j = 0; j < 8; j++) {
|
||||
cfg.wire->beginTransmission(cfg.i2c_address);
|
||||
cfg.wire->write(0x40);
|
||||
for (k = 0; k < xs; k++, p++) {
|
||||
cfg.wire->write(framebuffer[p]);
|
||||
}
|
||||
cfg.wire->endTransmission();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool i2c_panel::displayOnff(int8_t on) {
|
||||
i2c_command(on ? cfg.cmd_display_on : cfg.cmd_display_off);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool i2c_panel::invertDisplay(bool invert) {
|
||||
i2c_command(invert ? cfg.cmd_invert_on : cfg.cmd_invert_off);
|
||||
return true;
|
||||
}
|
||||
|
||||
void i2c_panel::i2c_command(uint8_t val) {
|
||||
cfg.wire->beginTransmission(cfg.i2c_address);
|
||||
cfg.wire->write(0);
|
||||
cfg.wire->write(val);
|
||||
cfg.wire->endTransmission();
|
||||
}
|
||||
73
lib/lib_display/UDisplay/uDisplay_I2C_panel.h
Normal file
73
lib/lib_display/UDisplay/uDisplay_I2C_panel.h
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef _UDISPLAY_I2C_PANEL_H_
|
||||
#define _UDISPLAY_I2C_PANEL_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include "uDisplay_panel.h"
|
||||
|
||||
/**
|
||||
* Configuration for I2C displays (typically OLED like SSD1306)
|
||||
*/
|
||||
struct I2CPanelConfig {
|
||||
// ===== Display Dimensions =====
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t bpp; // bits per pixel (typically 1 for OLED)
|
||||
|
||||
// ===== I2C Configuration =====
|
||||
uint8_t i2c_address; // I2C device address
|
||||
TwoWire* wire; // I2C bus instance
|
||||
|
||||
// ===== Display Commands =====
|
||||
uint8_t cmd_set_addr_x; // Set column address command
|
||||
uint8_t cmd_set_addr_y; // Set page address command
|
||||
uint8_t cmd_write_ram; // Write data command
|
||||
|
||||
// ===== Display Control Commands =====
|
||||
uint8_t cmd_display_on;
|
||||
uint8_t cmd_display_off;
|
||||
uint8_t cmd_invert_on;
|
||||
uint8_t cmd_invert_off;
|
||||
|
||||
// ===== Address Range =====
|
||||
uint8_t page_start; // Starting page
|
||||
uint8_t page_end; // Ending page
|
||||
uint8_t col_start; // Starting column
|
||||
uint8_t col_end; // Ending column
|
||||
|
||||
// ===== Initialization =====
|
||||
uint8_t* init_commands;
|
||||
uint16_t init_commands_count;
|
||||
};
|
||||
|
||||
class i2c_panel : public UniversalPanel {
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor - receives configuration struct and framebuffer
|
||||
*/
|
||||
i2c_panel(const I2CPanelConfig& config, uint8_t* framebuffer);
|
||||
|
||||
bool updateFrame() override;
|
||||
bool displayOnff(int8_t on) override;
|
||||
bool invertDisplay(bool invert) override;
|
||||
bool setRotation(uint8_t rotation) override { return true; }
|
||||
|
||||
bool drawPixel(int16_t x, int16_t y, uint16_t color) override { return false; }
|
||||
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override { return false; }
|
||||
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override { return false; }
|
||||
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override { return false; }
|
||||
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override { return false; }
|
||||
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override { return false; }
|
||||
|
||||
uint8_t* framebuffer = nullptr;
|
||||
|
||||
private:
|
||||
// ===== Hardware & Configuration =====
|
||||
I2CPanelConfig cfg; // Copy of config
|
||||
|
||||
// ===== Internal Helpers =====
|
||||
void i2c_command(uint8_t val);
|
||||
};
|
||||
|
||||
#endif // _UDISPLAY_I2C_PANEL_H_
|
||||
733
lib/lib_display/UDisplay/uDisplay_I80_panel.cpp
Normal file
733
lib/lib_display/UDisplay/uDisplay_I80_panel.cpp
Normal file
@ -0,0 +1,733 @@
|
||||
#include "uDisplay_I80_panel.h"
|
||||
|
||||
#if (SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS )
|
||||
|
||||
#ifdef UDSP_DEBUG
|
||||
extern void AddLog(uint32_t loglevel, const char *formatP, ...);
|
||||
#define LOG_LEVEL_DEBUG 3
|
||||
#endif
|
||||
|
||||
// Pin control helpers
|
||||
static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; }
|
||||
static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; }
|
||||
static inline void gpio_hi(int_fast8_t pin) { if (pin >= 0) *get_gpio_hi_reg(pin) = 1 << (pin & 31); }
|
||||
static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); }
|
||||
|
||||
I80Panel::I80Panel(const I80PanelConfig& config)
|
||||
: cfg(config),
|
||||
_width(config.width), _height(config.height), _rotation(0),
|
||||
_i80_bus(nullptr), _dev(nullptr), _dmadesc(nullptr),
|
||||
_DMA_Enabled(false), _dma_chan(nullptr), _dmadesc_size(0),
|
||||
_addr_x0(0), _addr_y0(0), _addr_x1(0), _addr_y1(0) {
|
||||
|
||||
framebuffer = nullptr;
|
||||
|
||||
// Initialize pins manually FIRST (matching old code order)
|
||||
if (cfg.cs_pin >= 0) {
|
||||
pinMode(cfg.cs_pin, OUTPUT);
|
||||
digitalWrite(cfg.cs_pin, HIGH);
|
||||
}
|
||||
pinMode(cfg.dc_pin, OUTPUT);
|
||||
digitalWrite(cfg.dc_pin, HIGH);
|
||||
pinMode(cfg.wr_pin, OUTPUT);
|
||||
digitalWrite(cfg.wr_pin, HIGH);
|
||||
if (cfg.rd_pin >= 0) {
|
||||
pinMode(cfg.rd_pin, OUTPUT);
|
||||
digitalWrite(cfg.rd_pin, HIGH);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
pinMode(cfg.data_pins_low[i], OUTPUT);
|
||||
}
|
||||
if (cfg.bus_width == 16) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
pinMode(cfg.data_pins_high[i], OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
// Now create I80 bus config
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.dc_gpio_num = cfg.dc_pin,
|
||||
.wr_gpio_num = cfg.wr_pin,
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.bus_width = cfg.bus_width,
|
||||
.max_transfer_bytes = (size_t)cfg.width * cfg.height * 2
|
||||
};
|
||||
|
||||
// Set data pins
|
||||
if (cfg.bus_width == 8) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bus_config.data_gpio_nums[i] = cfg.data_pins_low[i];
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bus_config.data_gpio_nums[i] = cfg.data_pins_low[i];
|
||||
bus_config.data_gpio_nums[i + 8] = cfg.data_pins_high[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Create I80 bus (this will take over GPIO matrix for DC, WR, and data pins)
|
||||
esp_lcd_new_i80_bus(&bus_config, &_i80_bus);
|
||||
|
||||
// Calculate clock using original algorithm
|
||||
uint32_t div_a, div_b, div_n, clkcnt;
|
||||
calcClockDiv(&div_a, &div_b, &div_n, &clkcnt, 240*1000*1000, cfg.clock_speed_hz);
|
||||
|
||||
lcd_cam_lcd_clock_reg_t lcd_clock;
|
||||
lcd_clock.lcd_clkcnt_n = std::max((uint32_t)1u, clkcnt - 1);
|
||||
lcd_clock.lcd_clk_equ_sysclk = (clkcnt == 1);
|
||||
lcd_clock.lcd_ck_idle_edge = true;
|
||||
lcd_clock.lcd_ck_out_edge = false;
|
||||
lcd_clock.lcd_clkm_div_num = div_n;
|
||||
lcd_clock.lcd_clkm_div_b = div_b;
|
||||
lcd_clock.lcd_clkm_div_a = div_a;
|
||||
lcd_clock.lcd_clk_sel = 2; // 240MHz
|
||||
lcd_clock.clk_en = true;
|
||||
_clock_reg_value = lcd_clock.val;
|
||||
|
||||
_alloc_dmadesc(1);
|
||||
_dev = &LCD_CAM;
|
||||
|
||||
// EXECUTE INITIALIZATION COMMANDS (from original uDisplay code)
|
||||
if (cfg.init_commands && cfg.init_commands_count > 0) {
|
||||
uint16_t index = 0;
|
||||
pb_beginTransaction();
|
||||
|
||||
while (index < cfg.init_commands_count) {
|
||||
cs_control(false);
|
||||
|
||||
uint8_t cmd = cfg.init_commands[index++];
|
||||
pb_writeCommand(cmd, 8);
|
||||
|
||||
if (index < cfg.init_commands_count) {
|
||||
uint8_t args = cfg.init_commands[index++];
|
||||
uint8_t arg_count = args & 0x1f;
|
||||
|
||||
#ifdef UDSP_DEBUG
|
||||
AddLog(LOG_LEVEL_DEBUG, "UDisplay: cmd, args %02x, %d", cmd, arg_count);
|
||||
#endif
|
||||
|
||||
for (uint32_t cnt = 0; cnt < arg_count && index < cfg.init_commands_count; cnt++) {
|
||||
uint8_t arg_data = cfg.init_commands[index++];
|
||||
#ifdef UDSP_DEBUG
|
||||
AddLog(LOG_LEVEL_DEBUG, "%02x ", arg_data);
|
||||
#endif
|
||||
pb_writeData(arg_data, 8);
|
||||
}
|
||||
|
||||
cs_control(true);
|
||||
|
||||
// Handle delay after command
|
||||
if (args & 0x80) {
|
||||
uint32_t delay_ms = 0;
|
||||
switch (args & 0xE0) {
|
||||
case 0x80: delay_ms = 150; break;
|
||||
case 0xA0: delay_ms = 10; break;
|
||||
case 0xE0: delay_ms = 500; break;
|
||||
}
|
||||
if (delay_ms > 0) {
|
||||
#ifdef UDSP_DEBUG
|
||||
AddLog(LOG_LEVEL_DEBUG, "UDisplay: delay %d ms", delay_ms);
|
||||
#endif
|
||||
delay(delay_ms);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cs_control(true);
|
||||
}
|
||||
}
|
||||
|
||||
pb_endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
I80Panel::~I80Panel() {
|
||||
deInitDMA();
|
||||
if (_dmadesc) {
|
||||
heap_caps_free(_dmadesc);
|
||||
_dmadesc = nullptr;
|
||||
}
|
||||
if (_i80_bus) {
|
||||
esp_lcd_del_i80_bus(_i80_bus);
|
||||
}
|
||||
}
|
||||
|
||||
// DMA Implementation
|
||||
bool I80Panel::initDMA() {
|
||||
if (_DMA_Enabled) return true;
|
||||
|
||||
gdma_channel_alloc_config_t dma_chan_config = {
|
||||
.direction = GDMA_CHANNEL_DIRECTION_TX
|
||||
};
|
||||
|
||||
if (gdma_new_channel(&dma_chan_config, &_dma_chan) == ESP_OK) {
|
||||
gdma_connect(_dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
|
||||
_alloc_dmadesc(16);
|
||||
_DMA_Enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void I80Panel::deInitDMA() {
|
||||
if (_dma_chan) {
|
||||
gdma_disconnect(_dma_chan);
|
||||
gdma_del_channel(_dma_chan);
|
||||
_dma_chan = nullptr;
|
||||
}
|
||||
_DMA_Enabled = false;
|
||||
}
|
||||
|
||||
bool I80Panel::dmaBusy() {
|
||||
if (!_DMA_Enabled) return false;
|
||||
return (_dev->lcd_user.val & LCD_CAM_LCD_START);
|
||||
}
|
||||
|
||||
void I80Panel::dmaWait() {
|
||||
if (!_DMA_Enabled) return;
|
||||
while (dmaBusy()) {
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Graphics implementation
|
||||
bool I80Panel::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return true;
|
||||
|
||||
pb_beginTransaction();
|
||||
cs_control(false);
|
||||
setAddrWindow_int(x, y, 1, 1);
|
||||
writeColor(color);
|
||||
cs_control(true);
|
||||
pb_endTransaction();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I80Panel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
if((x >= _width) || (y >= _height)) return true;
|
||||
if((x + w - 1) >= _width) w = _width - x;
|
||||
if((y + h - 1) >= _height) h = _height - y;
|
||||
|
||||
pb_beginTransaction();
|
||||
cs_control(false);
|
||||
setAddrWindow_int(x, y, w, h);
|
||||
|
||||
for (int16_t yp = h; yp > 0; yp--) {
|
||||
for (int16_t xp = w; xp > 0; xp--) {
|
||||
writeColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
cs_control(true);
|
||||
pb_endTransaction();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I80Panel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
if((x >= _width) || (y >= _height)) return true;
|
||||
if((x + w - 1) >= _width) w = _width - x;
|
||||
|
||||
pb_beginTransaction();
|
||||
cs_control(false);
|
||||
setAddrWindow_int(x, y, w, 1);
|
||||
|
||||
while (w--) {
|
||||
writeColor(color);
|
||||
}
|
||||
|
||||
cs_control(true);
|
||||
pb_endTransaction();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I80Panel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
if ((x >= _width) || (y >= _height)) return true;
|
||||
if ((y + h - 1) >= _height) h = _height - y;
|
||||
|
||||
pb_beginTransaction();
|
||||
cs_control(false);
|
||||
setAddrWindow_int(x, y, 1, h);
|
||||
|
||||
while (h--) {
|
||||
writeColor(color);
|
||||
}
|
||||
|
||||
cs_control(true);
|
||||
pb_endTransaction();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I80Panel::pushColors(uint16_t *data, uint16_t len, bool first) {
|
||||
// Match old code: just push pixels, no transaction management
|
||||
// Transaction is managed by setAddrWindow()
|
||||
pb_pushPixels(data, len, true, false); // swap_bytes=true to match old driver
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I80Panel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
||||
// Match old code behavior exactly
|
||||
if (!x0 && !y0 && !x1 && !y1) {
|
||||
// End transaction signal
|
||||
cs_control(true);
|
||||
pb_endTransaction();
|
||||
} else {
|
||||
// Begin transaction and send address window commands
|
||||
pb_beginTransaction();
|
||||
cs_control(false);
|
||||
setAddrWindow_int(x0, y0, x1 - x0, y1 - y0);
|
||||
// Leave transaction open for pushColors
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I80Panel::displayOnff(int8_t on) {
|
||||
return false; // Let uDisplay handle display commands
|
||||
}
|
||||
|
||||
bool I80Panel::invertDisplay(bool invert) {
|
||||
return false; // Let uDisplay handle inversion commands
|
||||
}
|
||||
|
||||
bool I80Panel::setRotation(uint8_t rotation) {
|
||||
_rotation = rotation & 3;
|
||||
|
||||
// Calculate new dimensions based on rotation FIRST
|
||||
uint16_t new_width, new_height;
|
||||
switch (_rotation) {
|
||||
case 0:
|
||||
case 2:
|
||||
new_width = cfg.width;
|
||||
new_height = cfg.height;
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
new_width = cfg.height;
|
||||
new_height = cfg.width;
|
||||
break;
|
||||
}
|
||||
|
||||
// Send MADCTL rotation command to display
|
||||
pb_beginTransaction();
|
||||
cs_control(false);
|
||||
|
||||
// Send rotation command (matches old code behavior)
|
||||
pb_writeCommand(cfg.cmd_madctl, 8);
|
||||
if (!cfg.allcmd_mode) {
|
||||
pb_writeData(cfg.rot_cmd[_rotation], 8);
|
||||
} else {
|
||||
pb_writeCommand(cfg.rot_cmd[_rotation], 8);
|
||||
}
|
||||
|
||||
// For sa_mode == 8, also send startline command
|
||||
if (cfg.sa_mode == 8 && !cfg.allcmd_mode) {
|
||||
pb_writeCommand(cfg.cmd_startline, 8);
|
||||
pb_writeData((_rotation < 2) ? new_height : 0, 8);
|
||||
}
|
||||
|
||||
cs_control(true);
|
||||
pb_endTransaction();
|
||||
|
||||
// Update dimensions
|
||||
_width = new_width;
|
||||
_height = new_height;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I80Panel::updateFrame() {
|
||||
return true; // I80 updates are immediate
|
||||
}
|
||||
|
||||
uint32_t I80Panel::getSimpleResistiveTouch(uint32_t threshold) {
|
||||
uint32_t aval = 0;
|
||||
uint16_t xp, yp;
|
||||
if (pb_busy()) return 0;
|
||||
|
||||
// Disable GPIO matrix routing to use pins as GPIOs
|
||||
_pb_init_pin(true);
|
||||
|
||||
// Temporarily reconfigure I80 pins as GPIOs for analog touch
|
||||
gpio_matrix_out(cfg.dc_pin, 0x100, 0, 0);
|
||||
|
||||
pinMode(cfg.data_pins_low[0], INPUT_PULLUP);
|
||||
pinMode(cfg.dc_pin, INPUT_PULLUP);
|
||||
|
||||
pinMode(cfg.cs_pin, OUTPUT);
|
||||
pinMode(cfg.data_pins_low[1], OUTPUT);
|
||||
digitalWrite(cfg.cs_pin, HIGH);
|
||||
digitalWrite(cfg.data_pins_low[1], LOW);
|
||||
|
||||
xp = 4096 - analogRead(cfg.data_pins_low[0]);
|
||||
|
||||
pinMode(cfg.cs_pin, INPUT_PULLUP);
|
||||
pinMode(cfg.data_pins_low[1], INPUT_PULLUP);
|
||||
|
||||
pinMode(cfg.data_pins_low[0], OUTPUT);
|
||||
pinMode(cfg.dc_pin, OUTPUT);
|
||||
digitalWrite(cfg.data_pins_low[0], HIGH);
|
||||
digitalWrite(cfg.dc_pin, LOW);
|
||||
|
||||
yp = 4096 - analogRead(cfg.data_pins_low[1]);
|
||||
|
||||
aval = (xp << 16) | yp;
|
||||
|
||||
// Restore pins to I80 function
|
||||
pinMode(cfg.dc_pin, OUTPUT);
|
||||
pinMode(cfg.cs_pin, OUTPUT);
|
||||
pinMode(cfg.data_pins_low[0], OUTPUT);
|
||||
pinMode(cfg.data_pins_low[1], OUTPUT);
|
||||
digitalWrite(cfg.dc_pin, HIGH);
|
||||
digitalWrite(cfg.cs_pin, HIGH);
|
||||
|
||||
// Re-enable GPIO matrix routing for I80
|
||||
_pb_init_pin(false);
|
||||
gpio_matrix_out(cfg.dc_pin, LCD_DC_IDX, 0, 0);
|
||||
|
||||
return aval;
|
||||
}
|
||||
|
||||
// Color mode helper
|
||||
void I80Panel::writeColor(uint16_t color) {
|
||||
if (cfg.color_mode == 18) {
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
|
||||
pb_writeData(r, 8);
|
||||
pb_writeData(g, 8);
|
||||
pb_writeData(b, 8);
|
||||
} else {
|
||||
pb_writeData(color, 16);
|
||||
}
|
||||
}
|
||||
|
||||
void I80Panel::setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
// Apply rotation-specific offsets (matches old code logic)
|
||||
x += cfg.x_addr_offset[_rotation];
|
||||
y += cfg.y_addr_offset[_rotation];
|
||||
|
||||
uint16_t x2 = x + w - 1;
|
||||
uint16_t y2 = y + h - 1;
|
||||
|
||||
if (cfg.sa_mode != 8) {
|
||||
// Normal mode: send 32-bit packed coordinates
|
||||
uint32_t xa = ((uint32_t)x << 16) | x2;
|
||||
uint32_t ya = ((uint32_t)y << 16) | y2;
|
||||
|
||||
pb_writeCommand(cfg.cmd_set_addr_x, 8);
|
||||
pb_writeData(xa, 32);
|
||||
pb_writeCommand(cfg.cmd_set_addr_y, 8);
|
||||
pb_writeData(ya, 32);
|
||||
|
||||
if (cfg.cmd_write_ram != 0xff) {
|
||||
pb_writeCommand(cfg.cmd_write_ram, 8);
|
||||
}
|
||||
} else {
|
||||
// Special mode 8: swap coordinates if rotation is odd
|
||||
if (_rotation & 1) {
|
||||
uint16_t tmp;
|
||||
tmp = x; x = y; y = tmp;
|
||||
tmp = x2; x2 = y2; y2 = tmp;
|
||||
}
|
||||
|
||||
pb_writeCommand(cfg.cmd_set_addr_x, 8);
|
||||
if (cfg.allcmd_mode) {
|
||||
pb_writeCommand(x, 8);
|
||||
pb_writeCommand(x2, 8);
|
||||
} else {
|
||||
pb_writeData(x, 8);
|
||||
pb_writeData(x2, 8);
|
||||
}
|
||||
|
||||
pb_writeCommand(cfg.cmd_set_addr_y, 8);
|
||||
if (cfg.allcmd_mode) {
|
||||
pb_writeCommand(y, 8);
|
||||
pb_writeCommand(y2, 8);
|
||||
} else {
|
||||
pb_writeData(y, 8);
|
||||
pb_writeData(y2, 8);
|
||||
}
|
||||
|
||||
if (cfg.cmd_write_ram != 0xff) {
|
||||
pb_writeCommand(cfg.cmd_write_ram, 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Store for push operations
|
||||
_addr_x0 = x;
|
||||
_addr_y0 = y;
|
||||
_addr_x1 = x2;
|
||||
_addr_y1 = y2;
|
||||
}
|
||||
|
||||
// Low-level I80 implementation
|
||||
void I80Panel::calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq) {
|
||||
uint32_t diff = INT32_MAX;
|
||||
*div_n = 256;
|
||||
*div_a = 63;
|
||||
*div_b = 62;
|
||||
*clkcnt = 64;
|
||||
|
||||
uint32_t start_cnt = std::min<uint32_t>(64u, (baseClock / (targetFreq * 2) + 1));
|
||||
uint32_t end_cnt = std::max<uint32_t>(2u, baseClock / 256u / targetFreq);
|
||||
if (start_cnt <= 2) { end_cnt = 1; }
|
||||
|
||||
for (uint32_t cnt = start_cnt; diff && cnt >= end_cnt; --cnt) {
|
||||
float fdiv = (float)baseClock / cnt / targetFreq;
|
||||
uint32_t n = std::max<uint32_t>(2u, (uint32_t)fdiv);
|
||||
fdiv -= n;
|
||||
|
||||
for (uint32_t a = 63; diff && a > 0; --a) {
|
||||
uint32_t b = roundf(fdiv * a);
|
||||
if (a == b && n == 256) break;
|
||||
|
||||
uint32_t freq = baseClock / ((n * cnt) + (float)(b * cnt) / (float)a);
|
||||
uint32_t d = abs((int)targetFreq - (int)freq);
|
||||
if (diff <= d) continue;
|
||||
|
||||
diff = d;
|
||||
*clkcnt = cnt;
|
||||
*div_n = n;
|
||||
*div_b = b;
|
||||
*div_a = a;
|
||||
if (b == 0 || a == b) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (*div_a == *div_b) {
|
||||
*div_b = 0;
|
||||
*div_n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void I80Panel::_alloc_dmadesc(size_t len) {
|
||||
if (_dmadesc) heap_caps_free(_dmadesc);
|
||||
_dmadesc_size = len;
|
||||
_dmadesc = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * len, MALLOC_CAP_DMA);
|
||||
}
|
||||
|
||||
void I80Panel::_setup_dma_desc_links(const uint8_t *data, int32_t len) {
|
||||
// ORIGINAL CODE: This function was empty in the original implementation
|
||||
// DMA descriptor setup is incomplete - transfers larger than pre-allocated
|
||||
// descriptor count will be silently truncated, causing corrupted data
|
||||
// This matches the original uDisplay behavior but should be fixed eventually
|
||||
|
||||
static constexpr size_t MAX_DMA_LEN = (4096-4);
|
||||
// TODO: Implement proper DMA descriptor chain setup
|
||||
// Currently, if len > MAX_DMA_LEN * _dmadesc_size, data will be truncated
|
||||
// without any error detection or recovery
|
||||
}
|
||||
|
||||
void I80Panel::pb_beginTransaction(void) {
|
||||
auto dev = _dev;
|
||||
dev->lcd_clock.val = _clock_reg_value;
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
|
||||
dev->lcd_user.val = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M;
|
||||
}
|
||||
|
||||
void I80Panel::pb_endTransaction(void) {
|
||||
auto dev = _dev;
|
||||
while (dev->lcd_user.val & LCD_CAM_LCD_START) {}
|
||||
}
|
||||
|
||||
void I80Panel::pb_wait(void) {
|
||||
auto dev = _dev;
|
||||
while (dev->lcd_user.val & LCD_CAM_LCD_START) {}
|
||||
}
|
||||
|
||||
bool I80Panel::pb_busy(void) {
|
||||
auto dev = _dev;
|
||||
return (dev->lcd_user.val & LCD_CAM_LCD_START);
|
||||
}
|
||||
|
||||
void I80Panel::_pb_init_pin(bool read) {
|
||||
if (read) {
|
||||
if (cfg.bus_width == 8) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_low[i]);
|
||||
}
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_ll_output_disable(&GPIO, (gpio_num_t)cfg.data_pins_high[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto idx_base = LCD_DATA_OUT0_IDX;
|
||||
if (cfg.bus_width == 8) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_matrix_out(cfg.data_pins_low[i], idx_base + i, 0, 0);
|
||||
}
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_matrix_out(cfg.data_pins_high[i], idx_base + 8 + i, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool I80Panel::pb_writeCommand(uint32_t data, uint_fast8_t bit_length) {
|
||||
auto dev = _dev;
|
||||
auto reg_lcd_user = &(dev->lcd_user.val);
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE | LCD_CAM_LCD_CD_CMD_SET;
|
||||
|
||||
if (cfg.bus_width == 8) {
|
||||
auto bytes = bit_length >> 3;
|
||||
do {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = data;
|
||||
data >>= 8;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
} while (--bytes);
|
||||
return true;
|
||||
} else {
|
||||
dev->lcd_cmd_val.val = data;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void I80Panel::pb_writeData(uint32_t data, uint_fast8_t bit_length) {
|
||||
auto dev = _dev;
|
||||
auto reg_lcd_user = &(dev->lcd_user.val);
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
|
||||
auto bytes = bit_length >> 3;
|
||||
|
||||
if (cfg.bus_width == 8) {
|
||||
uint8_t shift = (bytes - 1) * 8;
|
||||
for (uint32_t cnt = 0; cnt < bytes; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff;
|
||||
shift -= 8;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
if (bytes == 1 || bytes == 4) {
|
||||
uint8_t shift = (bytes - 1) * 8;
|
||||
for (uint32_t cnt = 0; cnt < bytes; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff;
|
||||
shift -= 8;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dev->lcd_cmd_val.val = data;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void I80Panel::pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma) {
|
||||
// original code commented out
|
||||
/*
|
||||
uint32_t freq = spi_speed * 1000000;
|
||||
uint32_t slow = (freq< 4000000) ? 2 : (freq < 8000000) ? 1 : 0;
|
||||
|
||||
auto dev = _dev;
|
||||
do {
|
||||
auto reg_lcd_user = &(dev->lcd_user.val);
|
||||
dev->lcd_misc.lcd_cd_cmd_set = 0;
|
||||
dev->lcd_cmd_val.lcd_cmd_value = data[0] | data[1] << 16;
|
||||
uint32_t cmd_val = data[2] | data[3] << 16;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
|
||||
if (use_dma) {
|
||||
if (slow) { ets_delay_us(slow); }
|
||||
_setup_dma_desc_links(&data[4], length - 4);
|
||||
gdma_start(_dma_chan, (intptr_t)(_dmadesc));
|
||||
length = 0;
|
||||
} else {
|
||||
size_t len = length;
|
||||
if (len > CACHE_SIZE) {
|
||||
len = (((len - 1) % CACHE_SIZE) + 4) & ~3u;
|
||||
}
|
||||
memcpy(_cache_flip, &data[4], (len-4+3)&~3);
|
||||
_setup_dma_desc_links((const uint8_t*)_cache_flip, len-4);
|
||||
gdma_start(_dma_chan, (intptr_t)(_dmadesc));
|
||||
length -= len;
|
||||
data += len;
|
||||
_cache_flip = _cache[(_cache_flip == _cache[0])];
|
||||
}
|
||||
dev->lcd_cmd_val.lcd_cmd_value = cmd_val;
|
||||
dev->lcd_misc.lcd_cd_data_set = 0;
|
||||
*reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_UPDATE_REG;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_ALWAYS_OUT_EN | LCD_CAM_LCD_DOUT | LCD_CAM_LCD_CMD | LCD_CAM_LCD_CMD_2_CYCLE_EN | LCD_CAM_LCD_START;
|
||||
} while (length);
|
||||
*/
|
||||
}
|
||||
|
||||
// FIXED: Byte swap logic was backwards in 8-bit mode
|
||||
void I80Panel::pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma) {
|
||||
auto dev = _dev;
|
||||
auto reg_lcd_user = &(dev->lcd_user.val);
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
|
||||
|
||||
if (cfg.bus_width == 8) {
|
||||
if (swap_bytes) {
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte first
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte second
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
data++;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data; // Low byte first
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data >> 8; // High byte second
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
data++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (swap_bytes) {
|
||||
uint16_t iob;
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
iob = *data++;
|
||||
iob = (iob << 8) | (iob >> 8);
|
||||
dev->lcd_cmd_val.lcd_cmd_value = iob;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data++;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_M | LCD_CAM_LCD_START;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I80Panel::cs_control(bool level) {
|
||||
auto pin = cfg.cs_pin;
|
||||
if (pin < 0) return;
|
||||
if (level) {
|
||||
gpio_hi(pin);
|
||||
} else {
|
||||
gpio_lo(pin);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES
|
||||
144
lib/lib_display/UDisplay/uDisplay_I80_panel.h
Normal file
144
lib/lib_display/UDisplay/uDisplay_I80_panel.h
Normal file
@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP32
|
||||
#if __has_include("soc/soc_caps.h")
|
||||
# include "soc/soc_caps.h"
|
||||
#else
|
||||
# error "No ESP capability header found"
|
||||
#endif
|
||||
|
||||
#if (SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES && !SOC_PARLIO_GROUPS)
|
||||
|
||||
#include "uDisplay_panel.h"
|
||||
|
||||
#include "esp_private/gdma.h"
|
||||
#include <hal/gpio_ll.h>
|
||||
#include <hal/lcd_hal.h>
|
||||
#include <soc/lcd_cam_reg.h>
|
||||
#include <soc/lcd_cam_struct.h>
|
||||
#include "esp_rom_lldesc.h"
|
||||
#include "esp_lcd_io_i80.h"
|
||||
#include "esp_private/gdma.h"
|
||||
#include <hal/gpio_ll.h>
|
||||
#include <hal/lcd_hal.h>
|
||||
#include <soc/lcd_cam_reg.h>
|
||||
#include <soc/lcd_cam_struct.h>
|
||||
#include "esp_pm.h"
|
||||
#include <hal/dma_types.h>
|
||||
#include <rom/cache.h>
|
||||
#include "esp_rom_lldesc.h"
|
||||
#include <rom/gpio.h>
|
||||
|
||||
/**
|
||||
* Configuration for I80 (8080/6800) parallel displays
|
||||
*/
|
||||
struct I80PanelConfig {
|
||||
// ===== Display Dimensions =====
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t bpp; // bits per pixel (typically 16)
|
||||
uint8_t color_mode; // color mode (16, 18, etc.)
|
||||
|
||||
// ===== Bus Configuration =====
|
||||
int8_t cs_pin; // Chip select
|
||||
int8_t dc_pin; // Data/Command
|
||||
int8_t wr_pin; // Write strobe
|
||||
int8_t rd_pin; // Read strobe (-1 if not used)
|
||||
int8_t data_pins_low[8]; // D0-D7 pins
|
||||
int8_t data_pins_high[8]; // D8-D15 pins (for 16-bit bus)
|
||||
uint8_t bus_width; // 8 or 16
|
||||
uint32_t clock_speed_hz; // Bus clock speed
|
||||
|
||||
// ===== Display Commands =====
|
||||
uint8_t cmd_set_addr_x; // Column address command
|
||||
uint8_t cmd_set_addr_y; // Row/page address command
|
||||
uint8_t cmd_write_ram; // Write to RAM command
|
||||
uint8_t cmd_madctl; // Memory access control command (typically 0x36)
|
||||
uint8_t cmd_startline; // Start line command (for sa_mode == 8)
|
||||
|
||||
// ===== Display Modes =====
|
||||
uint8_t sa_mode; // Set address mode (8 = special, 16 = normal)
|
||||
uint8_t allcmd_mode; // If true, send data as commands
|
||||
|
||||
// ===== Per-Rotation Configuration =====
|
||||
uint8_t rot_cmd[4]; // MADCTL rotation command value per rotation
|
||||
uint16_t x_addr_offset[4]; // Address offset per rotation
|
||||
uint16_t y_addr_offset[4];
|
||||
|
||||
// ===== Initialization =====
|
||||
uint8_t* init_commands;
|
||||
uint16_t init_commands_count;
|
||||
};
|
||||
|
||||
class I80Panel : public UniversalPanel {
|
||||
public:
|
||||
/**
|
||||
* Constructor - receives configuration struct
|
||||
*/
|
||||
I80Panel(const I80PanelConfig& config);
|
||||
virtual ~I80Panel();
|
||||
|
||||
// UniversalPanel interface
|
||||
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
||||
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
|
||||
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override;
|
||||
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
|
||||
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
|
||||
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
|
||||
bool displayOnff(int8_t on) override;
|
||||
bool invertDisplay(bool invert) override;
|
||||
bool setRotation(uint8_t rotation) override;
|
||||
bool updateFrame() override;
|
||||
|
||||
// Simple resistive touch hack
|
||||
uint32_t getSimpleResistiveTouch(uint32_t threshold);
|
||||
|
||||
// DMA functionality
|
||||
bool initDMA();
|
||||
void deInitDMA();
|
||||
bool dmaBusy();
|
||||
void dmaWait();
|
||||
|
||||
private:
|
||||
// ===== Hardware & Configuration =====
|
||||
I80PanelConfig cfg; // Copy of config
|
||||
|
||||
// ===== Display State =====
|
||||
int16_t _width, _height;
|
||||
uint8_t _rotation;
|
||||
|
||||
// I80 hardware handles
|
||||
esp_lcd_i80_bus_handle_t _i80_bus;
|
||||
volatile lcd_cam_dev_t* _dev;
|
||||
uint32_t _clock_reg_value;
|
||||
|
||||
// DMA resources
|
||||
bool _DMA_Enabled;
|
||||
gdma_channel_handle_t _dma_chan;
|
||||
lldesc_t *_dmadesc;
|
||||
uint32_t _dmadesc_size;
|
||||
|
||||
// Current address window
|
||||
int16_t _addr_x0, _addr_y0, _addr_x1, _addr_y1;
|
||||
|
||||
// Low-level I80 functions
|
||||
void calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq);
|
||||
void _alloc_dmadesc(size_t len);
|
||||
void _setup_dma_desc_links(const uint8_t *data, int32_t len);
|
||||
void pb_beginTransaction(void);
|
||||
void pb_endTransaction(void);
|
||||
void pb_wait(void);
|
||||
bool pb_busy(void);
|
||||
void _pb_init_pin(bool read);
|
||||
bool pb_writeCommand(uint32_t data, uint_fast8_t bit_length);
|
||||
void pb_writeData(uint32_t data, uint_fast8_t bit_length);
|
||||
void pb_writeBytes(const uint8_t* data, uint32_t length, bool use_dma);
|
||||
void pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma);
|
||||
void cs_control(bool level);
|
||||
|
||||
// Color mode helpers
|
||||
void writeColor(uint16_t color);
|
||||
void setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
|
||||
};
|
||||
#endif // SOC_LCD_I80_SUPPORTED && SOC_LCDCAM_I80_NUM_BUSES
|
||||
#endif // ESP32
|
||||
118
lib/lib_display/UDisplay/uDisplay_RGB_panel.cpp
Normal file
118
lib/lib_display/UDisplay/uDisplay_RGB_panel.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
// ======================================================
|
||||
// panel/uDisplay_rgb_panel.cpp - RGB Panel Implementation
|
||||
// ======================================================
|
||||
#include "uDisplay_RGB_panel.h"
|
||||
|
||||
#if SOC_LCD_RGB_SUPPORTED
|
||||
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <rom/cache.h>
|
||||
|
||||
extern int CACHE_WRITEBACK_ADDR(uint32_t addr, uint32_t size);
|
||||
|
||||
RGBPanel::RGBPanel(const esp_lcd_rgb_panel_config_t *config) {
|
||||
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(config, &panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||||
width = config->timings.h_res;
|
||||
height = config->timings.v_res;
|
||||
framebuffer_size = width * height * 2; // 16 bpp
|
||||
void* buf = NULL;
|
||||
esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 1, &buf);
|
||||
framebuffer = (uint16_t*)buf;
|
||||
uint16_t color = random(0xffff);
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 1, 1, &color));
|
||||
}
|
||||
|
||||
RGBPanel::~RGBPanel() {
|
||||
// TODO: Cleanup panel_handle if needed
|
||||
}
|
||||
|
||||
bool RGBPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
int16_t w = width, h = height;
|
||||
|
||||
// Apply rotation
|
||||
switch (rotation) {
|
||||
case 1: std::swap(w, h); std::swap(x, y); x = w - x - 1; break;
|
||||
case 2: x = w - x - 1; y = h - y - 1; break;
|
||||
case 3: std::swap(w, h); std::swap(x, y); y = h - y - 1; break;
|
||||
}
|
||||
|
||||
if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) return true; // Handled (out of bounds)
|
||||
|
||||
framebuffer[y * w + x] = color;
|
||||
framebuffer_dirty = true;
|
||||
return true; // Handled by RGB panel
|
||||
}
|
||||
|
||||
bool RGBPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
for (int16_t yp = y; yp < y + h; yp++) {
|
||||
uint16_t* line_start = &framebuffer[yp * width + x];
|
||||
for (int16_t i = 0; i < w; i++) {
|
||||
line_start[i] = color;
|
||||
}
|
||||
}
|
||||
framebuffer_dirty = true;
|
||||
return true; // Handled by RGB panel
|
||||
}
|
||||
|
||||
bool RGBPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
||||
window_x1 = x0;
|
||||
window_y1 = y0;
|
||||
window_x2 = x1;
|
||||
window_y2 = y1;
|
||||
return true; // Handled by RGB panel
|
||||
}
|
||||
|
||||
bool RGBPanel::pushColors(uint16_t *data, uint16_t len, bool first) {
|
||||
esp_lcd_panel_draw_bitmap(panel_handle, window_x1, window_y1, window_x2, window_y2, data);
|
||||
return true; // Handled by RGB panel
|
||||
}
|
||||
|
||||
bool RGBPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
uint16_t* line_start = &framebuffer[y * width + x];
|
||||
for (int16_t i = 0; i < w; i++) {
|
||||
line_start[i] = color;
|
||||
}
|
||||
CACHE_WRITEBACK_ADDR((uint32_t)line_start, w * 2);
|
||||
return true; // Handled by RGB panel
|
||||
}
|
||||
|
||||
bool RGBPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
for (int16_t j = 0; j < h; j++) {
|
||||
framebuffer[(y + j) * width + x] = color;
|
||||
}
|
||||
CACHE_WRITEBACK_ADDR((uint32_t)&framebuffer[y * width + x], h * 2);
|
||||
return true; // Handled by RGB panel
|
||||
}
|
||||
|
||||
bool RGBPanel::displayOnff(int8_t on) {
|
||||
esp_lcd_panel_disp_on_off(panel_handle, on != 0);
|
||||
return false; // bpanel is controlled from display class
|
||||
}
|
||||
|
||||
bool RGBPanel::invertDisplay(bool invert) {
|
||||
// TODO: Not supported by RGB panels in ESP-IDF API
|
||||
return false; // Not handled - let uDisplay handle if possible
|
||||
}
|
||||
|
||||
bool RGBPanel::setRotation(uint8_t rotation) {
|
||||
this->rotation = rotation & 3;
|
||||
esp_lcd_panel_mirror(panel_handle, rotation == 1 || rotation == 2, rotation & 2);
|
||||
esp_lcd_panel_swap_xy(panel_handle, rotation & 1);
|
||||
return true; // Handled by RGB panel
|
||||
}
|
||||
|
||||
bool RGBPanel::updateFrame() {
|
||||
if (!framebuffer_dirty) {
|
||||
return true;
|
||||
}
|
||||
CACHE_WRITEBACK_ADDR((uint32_t)framebuffer, framebuffer_size); //KISS and fast enough!
|
||||
framebuffer_dirty = false;
|
||||
|
||||
return true; // Handled (no-op is still handled)
|
||||
}
|
||||
|
||||
|
||||
#endif // #if SOC_LCD_RGB_SUPPORTED
|
||||
55
lib/lib_display/UDisplay/uDisplay_RGB_panel.h
Normal file
55
lib/lib_display/UDisplay/uDisplay_RGB_panel.h
Normal file
@ -0,0 +1,55 @@
|
||||
// ======================================================
|
||||
// uDisplay_rgb_panel.h - RGB Panel Implementation
|
||||
// ======================================================
|
||||
|
||||
#pragma once
|
||||
#ifdef ESP32
|
||||
#if __has_include("soc/soc_caps.h")
|
||||
# include "soc/soc_caps.h"
|
||||
#else
|
||||
# error "No ESP capability header found"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if SOC_LCD_RGB_SUPPORTED
|
||||
|
||||
#include "uDisplay_panel.h"
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_panel_rgb.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
|
||||
class RGBPanel : public UniversalPanel {
|
||||
public:
|
||||
// Takes only the ESP-IDF config
|
||||
RGBPanel(const esp_lcd_rgb_panel_config_t *config);
|
||||
~RGBPanel();
|
||||
|
||||
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
||||
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
|
||||
bool pushColors(uint16_t *data, uint16_t len, bool first = false) override;
|
||||
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
|
||||
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
|
||||
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
|
||||
|
||||
bool displayOnff(int8_t on) override;
|
||||
bool invertDisplay(bool invert) override;
|
||||
bool setRotation(uint8_t rotation) override;
|
||||
bool updateFrame() override;
|
||||
uint16_t* framebuffer = nullptr;
|
||||
|
||||
private:
|
||||
esp_lcd_panel_handle_t panel_handle = nullptr;
|
||||
uint8_t rotation = 0;
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
int16_t window_x1 = 0;
|
||||
int16_t window_y1 = 0;
|
||||
int16_t window_x2 = 1;
|
||||
int16_t window_y2 = 1;
|
||||
size_t framebuffer_size = 0;
|
||||
uint32_t framebuffer_dirty = false;
|
||||
|
||||
};
|
||||
|
||||
#endif //SOC_LCD_RGB_SUPPORTED
|
||||
486
lib/lib_display/UDisplay/uDisplay_SPI_controller.cpp
Normal file
486
lib/lib_display/UDisplay/uDisplay_SPI_controller.cpp
Normal file
@ -0,0 +1,486 @@
|
||||
#include "uDisplay_SPI_controller.h"
|
||||
|
||||
// ===== GPIO Macros =====
|
||||
#ifdef ESP8266
|
||||
#define PIN_OUT_SET 0x60000304
|
||||
#define PIN_OUT_CLEAR 0x60000308
|
||||
#define GPIO_SET(A) WRITE_PERI_REG(PIN_OUT_SET, 1 << A)
|
||||
#define GPIO_CLR(A) WRITE_PERI_REG(PIN_OUT_CLEAR, 1 << A)
|
||||
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
|
||||
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
|
||||
#else // ESP32
|
||||
#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4
|
||||
#define GPIO_CLR(A) GPIO.out_w1tc.val = (1 << A)
|
||||
#define GPIO_SET(A) GPIO.out_w1ts.val = (1 << A)
|
||||
#else // plain ESP32 or S3
|
||||
#define GPIO_CLR(A) GPIO.out_w1tc = (1 << A)
|
||||
#define GPIO_SET(A) GPIO.out_w1ts = (1 << A)
|
||||
#endif
|
||||
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
|
||||
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
|
||||
#endif
|
||||
|
||||
// ===== RA8876 Constants =====
|
||||
static constexpr uint8_t RA8876_DATA_WRITE = 0x80;
|
||||
static constexpr uint8_t RA8876_DATA_READ = 0xC0;
|
||||
static constexpr uint8_t RA8876_CMD_WRITE = 0x00;
|
||||
static constexpr uint8_t RA8876_STATUS_READ = 0x40;
|
||||
|
||||
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
|
||||
|
||||
SPIController::SPIController(const SPIControllerConfig& config)
|
||||
: spi_config(config)
|
||||
{
|
||||
if (spi_config.dc >= 0) {
|
||||
pinMode(spi_config.dc, OUTPUT);
|
||||
digitalWrite(spi_config.dc, HIGH);
|
||||
}
|
||||
if (spi_config.cs >= 0) {
|
||||
pinMode(spi_config.cs, OUTPUT);
|
||||
digitalWrite(spi_config.cs, HIGH);
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
if (spi_config.bus_nr <= 1) {
|
||||
SPI.begin();
|
||||
spi = &SPI;
|
||||
} else {
|
||||
pinMode(spi_config.clk, OUTPUT);
|
||||
digitalWrite(spi_config.clk, LOW);
|
||||
pinMode(spi_config.mosi, OUTPUT);
|
||||
digitalWrite(spi_config.mosi, LOW);
|
||||
if (spi_config.miso >= 0) {
|
||||
pinMode(spi_config.miso, INPUT_PULLUP);
|
||||
}
|
||||
}
|
||||
#endif // ESP8266
|
||||
|
||||
#ifdef ESP32
|
||||
if (spi_config.bus_nr == 1) {
|
||||
spi = &SPI;
|
||||
spi->begin(spi_config.clk, spi_config.miso, spi_config.mosi, -1);
|
||||
} else if (spi_config.bus_nr == 2) {
|
||||
spi = new SPIClass(HSPI);
|
||||
spi->begin(spi_config.clk, spi_config.miso, spi_config.mosi, -1);
|
||||
} else {
|
||||
pinMode(spi_config.clk, OUTPUT);
|
||||
digitalWrite(spi_config.clk, LOW);
|
||||
pinMode(spi_config.mosi, OUTPUT);
|
||||
digitalWrite(spi_config.mosi, LOW);
|
||||
if (spi_config.miso >= 0) {
|
||||
pinMode(spi_config.miso, INPUT_PULLUP);
|
||||
}
|
||||
}
|
||||
#endif // ESP32
|
||||
spi_settings = SPISettings((uint32_t)spi_config.speed*1000000, MSBFIRST, SPI_MODE3);
|
||||
}
|
||||
|
||||
// ===== Pin Control =====
|
||||
|
||||
void SPIController::csLow() {
|
||||
if (spi_config.cs >= 0) GPIO_CLR_SLOW(spi_config.cs);
|
||||
}
|
||||
|
||||
void SPIController::csHigh() {
|
||||
if (spi_config.cs >= 0) GPIO_SET_SLOW(spi_config.cs);
|
||||
}
|
||||
|
||||
void SPIController::dcLow() {
|
||||
if (spi_config.dc >= 0) GPIO_CLR_SLOW(spi_config.dc);
|
||||
}
|
||||
|
||||
void SPIController::dcHigh() {
|
||||
if (spi_config.dc >= 0) GPIO_SET_SLOW(spi_config.dc);
|
||||
}
|
||||
|
||||
// ===== Transaction Control =====
|
||||
|
||||
void SPIController::beginTransaction() {
|
||||
if (spi_config.bus_nr <= 2) spi->beginTransaction(spi_settings);
|
||||
}
|
||||
|
||||
void SPIController::endTransaction() {
|
||||
if (spi_config.bus_nr <= 2) spi->endTransaction();
|
||||
}
|
||||
|
||||
// ===== Low-Level Write Functions =====
|
||||
void SPIController::writeCommand(uint8_t cmd) {
|
||||
if (spi_config.dc < 0) {
|
||||
// 9-bit mode
|
||||
if (spi_config.bus_nr > 2) {
|
||||
if (spi_config.bus_nr == 3) write9(cmd, 0);
|
||||
else write9_slow(cmd, 0);
|
||||
} else {
|
||||
hw_write9(cmd, 0);
|
||||
}
|
||||
} else {
|
||||
// 8-bit mode
|
||||
dcLow();
|
||||
writeData8(cmd);
|
||||
dcHigh();
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::writeData8(uint8_t data) {
|
||||
if (spi_config.dc < 0) {
|
||||
// 9-bit mode
|
||||
if (spi_config.bus_nr > 2) {
|
||||
if (spi_config.bus_nr == 3) write9(data, 1);
|
||||
else write9_slow(data, 1);
|
||||
} else {
|
||||
hw_write9(data, 1);
|
||||
}
|
||||
} else {
|
||||
// 8-bit mode
|
||||
if (spi_config.bus_nr > 2) {
|
||||
if (spi_config.bus_nr == 3) write8(data);
|
||||
else write8_slow(data);
|
||||
} else {
|
||||
spi->write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::writeData16(uint16_t data) {
|
||||
if (spi_config.dc < 0) {
|
||||
// 9-bit: break into bytes
|
||||
writeData8(data >> 8);
|
||||
writeData8(data);
|
||||
} else {
|
||||
// 8-bit mode
|
||||
if (spi_config.bus_nr > 2) {
|
||||
if (spi_config.bus_nr == 3) write16(data);
|
||||
else {
|
||||
// Slow mode: break into bytes
|
||||
writeData8(data >> 8);
|
||||
writeData8(data);
|
||||
}
|
||||
} else {
|
||||
spi->write16(data); // Assume SPI has write16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::writeData32(uint32_t data) {
|
||||
if (spi_config.dc < 0) {
|
||||
// 9-bit mode: break into bytes
|
||||
writeData8(data >> 24);
|
||||
writeData8(data >> 16);
|
||||
writeData8(data >> 8);
|
||||
writeData8(data);
|
||||
} else {
|
||||
// 8-bit mode
|
||||
if (spi_config.bus_nr > 2) {
|
||||
if (spi_config.bus_nr == 3) {
|
||||
write32(data); // Fast bit-banging
|
||||
} else {
|
||||
// Slow mode: break into bytes
|
||||
writeData8(data >> 24);
|
||||
writeData8(data >> 16);
|
||||
writeData8(data >> 8);
|
||||
writeData8(data);
|
||||
}
|
||||
} else {
|
||||
// Hardware SPI
|
||||
spi->write32(data); // Assume SPI has write32 on ESP32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Low-Level Write Functions =====
|
||||
|
||||
void SPIController::write8(uint8_t val) {
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_config.clk);
|
||||
if (val & bit) GPIO_SET(spi_config.mosi);
|
||||
else GPIO_CLR(spi_config.mosi);
|
||||
GPIO_SET(spi_config.clk);
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::write8_slow(uint8_t val) {
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR_SLOW(spi_config.clk);
|
||||
if (val & bit) GPIO_SET_SLOW(spi_config.mosi);
|
||||
else GPIO_CLR_SLOW(spi_config.mosi);
|
||||
GPIO_SET_SLOW(spi_config.clk);
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::write9(uint8_t val, uint8_t dc) {
|
||||
GPIO_CLR(spi_config.clk);
|
||||
if (dc) GPIO_SET(spi_config.mosi);
|
||||
else GPIO_CLR(spi_config.mosi);
|
||||
GPIO_SET(spi_config.clk);
|
||||
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_config.clk);
|
||||
if (val & bit) GPIO_SET(spi_config.mosi);
|
||||
else GPIO_CLR(spi_config.mosi);
|
||||
GPIO_SET(spi_config.clk);
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::write9_slow(uint8_t val, uint8_t dc) {
|
||||
GPIO_CLR_SLOW(spi_config.clk);
|
||||
if (dc) GPIO_SET_SLOW(spi_config.mosi);
|
||||
else GPIO_CLR_SLOW(spi_config.mosi);
|
||||
GPIO_SET_SLOW(spi_config.clk);
|
||||
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR_SLOW(spi_config.clk);
|
||||
if (val & bit) GPIO_SET_SLOW(spi_config.mosi);
|
||||
else GPIO_CLR_SLOW(spi_config.mosi);
|
||||
GPIO_SET_SLOW(spi_config.clk);
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::write16(uint16_t val) {
|
||||
for (uint16_t bit = 0x8000; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_config.clk);
|
||||
if (val & bit) GPIO_SET(spi_config.mosi);
|
||||
else GPIO_CLR(spi_config.mosi);
|
||||
GPIO_SET(spi_config.clk);
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::write32(uint32_t val) {
|
||||
for (uint32_t bit = 0x80000000; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_config.clk);
|
||||
if (val & bit) GPIO_SET(spi_config.mosi);
|
||||
else GPIO_CLR(spi_config.mosi);
|
||||
GPIO_SET(spi_config.clk);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Hardware 9-bit Mode =====
|
||||
|
||||
#ifdef ESP32
|
||||
void SPIController::hw_write9(uint8_t val, uint8_t dc) {
|
||||
if (spi_config.dc < -1) {
|
||||
// RA8876 mode
|
||||
if (!dc) {
|
||||
spi->write(RA8876_CMD_WRITE);
|
||||
spi->write(val);
|
||||
} else {
|
||||
spi->write(RA8876_DATA_WRITE);
|
||||
spi->write(val);
|
||||
}
|
||||
} else {
|
||||
uint32_t regvalue = val >> 1;
|
||||
if (dc) regvalue |= 0x80;
|
||||
else regvalue &= 0x7f;
|
||||
if (val & 1) regvalue |= 0x8000;
|
||||
|
||||
REG_SET_BIT(SPI_USER_REG(3), SPI_USR_MOSI);
|
||||
REG_WRITE(SPI_MOSI_DLEN_REG(3), 9 - 1);
|
||||
uint32_t *dp = (uint32_t*)SPI_W0_REG(3);
|
||||
*dp = regvalue;
|
||||
REG_SET_BIT(SPI_CMD_REG(3), SPI_USR);
|
||||
while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR));
|
||||
}
|
||||
}
|
||||
#else
|
||||
void SPIController::hw_write9(uint8_t val, uint8_t dc) {
|
||||
if (spi_config.dc < -1) {
|
||||
// RA8876 mode
|
||||
if (!dc) {
|
||||
spi->write(RA8876_CMD_WRITE);
|
||||
spi->write(val);
|
||||
} else {
|
||||
spi->write(RA8876_DATA_WRITE);
|
||||
spi->write(val);
|
||||
}
|
||||
} else {
|
||||
uint32_t regvalue;
|
||||
uint8_t bytetemp;
|
||||
if (!dc) {
|
||||
bytetemp = (val >> 1) & 0x7f;
|
||||
} else {
|
||||
bytetemp = (val >> 1) | 0x80;
|
||||
}
|
||||
regvalue = ((8 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | ((uint32)bytetemp);
|
||||
if (val & 0x01) regvalue |= BIT15;
|
||||
while (READ_PERI_REG(SPI_CMD(1)) & SPI_USR);
|
||||
WRITE_PERI_REG(SPI_USER2(1), regvalue);
|
||||
SET_PERI_REG_MASK(SPI_CMD(1), SPI_USR);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// DMA
|
||||
#ifdef ESP32
|
||||
bool SPIController::initDMA(uint16_t width, uint16_t flushlines, uint8_t data) {
|
||||
AddLog(3,"init dma %u %u %d",flushlines,data, spi_config.cs);
|
||||
if (!spi && spi_config.cs == -1) return false;
|
||||
if((data&1) == 0){
|
||||
AddLog(3,"no dma selected");
|
||||
return false;
|
||||
}
|
||||
if (spi_config.bus_nr == 1){
|
||||
AddLog(3,"dma spi 1");
|
||||
} else if (spi_config.bus_nr == 2){
|
||||
AddLog(3,"dma spi 2");
|
||||
spi_host = HSPI_HOST;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret;
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = spi_config.mosi,
|
||||
.miso_io_num = spi_config.miso,
|
||||
.sclk_io_num = spi_config.clk,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = width * flushlines * 2 + 8,
|
||||
.flags = 0,
|
||||
.intr_flags = 0
|
||||
};
|
||||
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 0,
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
.mode = SPI_MODE3,
|
||||
.duty_cycle_pos = 0,
|
||||
.cs_ena_pretrans = 0,
|
||||
.cs_ena_posttrans = 0,
|
||||
.clock_speed_hz = (int)spi_config.speed,
|
||||
.input_delay_ns = 0,
|
||||
.spics_io_num = spi_config.cs,
|
||||
.flags = SPI_DEVICE_NO_DUMMY,
|
||||
.queue_size = 1,
|
||||
.pre_cb = 0,
|
||||
.post_cb = 0
|
||||
};
|
||||
|
||||
// spi_host_device_t spi_host = (spi_config.bus_nr == 1) ? VSPI_HOST : HSPI_HOST;
|
||||
|
||||
// Try to initialize the bus, but if it's already initialized (by Arduino SPI), that's OK
|
||||
|
||||
ret = spi_bus_initialize(spi_host, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
AddLog(3,"init dma bus init failed: %d", ret);
|
||||
return false;
|
||||
}
|
||||
if (ret == ESP_ERR_INVALID_STATE) {
|
||||
AddLog(3,"init dma bus already initialized (OK)");
|
||||
}
|
||||
|
||||
ret = spi_bus_add_device(spi_host, &devcfg, &dmaHAL);
|
||||
if (ret == ESP_OK) {
|
||||
DMA_Enabled = true;
|
||||
async_dma_enabled = ((data&4) != 0);
|
||||
dma_enabled = true;
|
||||
spiBusyCheck = 0;
|
||||
AddLog(3,"init dma succes");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// just a placeholder
|
||||
// void SPIController::deInitDMA(void) {
|
||||
// if (!DMA_Enabled) return;
|
||||
// spi_bus_remove_device(dmaHAL);
|
||||
// spi_bus_free(spi_host);
|
||||
// DMA_Enabled = false;
|
||||
// }
|
||||
|
||||
bool SPIController::dmaBusy(void) {
|
||||
if (!DMA_Enabled || !spiBusyCheck) return false;
|
||||
|
||||
spi_transaction_t *rtrans;
|
||||
esp_err_t ret;
|
||||
uint8_t checks = spiBusyCheck;
|
||||
for (int i = 0; i < checks; ++i) {
|
||||
ret = spi_device_get_trans_result(dmaHAL, &rtrans, 0);
|
||||
if (ret == ESP_OK) spiBusyCheck--;
|
||||
}
|
||||
if (spiBusyCheck == 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SPIController::dmaWait(void) {
|
||||
if (!DMA_Enabled || !spiBusyCheck) return;
|
||||
spi_transaction_t *rtrans;
|
||||
esp_err_t ret;
|
||||
for (int i = 0; i < spiBusyCheck; ++i) {
|
||||
ret = spi_device_get_trans_result(dmaHAL, &rtrans, portMAX_DELAY);
|
||||
assert(ret == ESP_OK);
|
||||
}
|
||||
spiBusyCheck = 0;
|
||||
}
|
||||
|
||||
void SPIController::pushPixelsDMA(uint16_t* image, uint32_t len) {
|
||||
if(!DMA_Enabled){
|
||||
getSPI()->writePixels(image, len * 2);
|
||||
return;
|
||||
}
|
||||
if (len == 0) return;
|
||||
|
||||
dmaWait();
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
memset(&trans, 0, sizeof(spi_transaction_t));
|
||||
|
||||
trans.user = (void *)1;
|
||||
trans.tx_buffer = image; //finally send the line data
|
||||
trans.length = len * 16; //Data length, in bits
|
||||
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
|
||||
|
||||
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
spiBusyCheck++;
|
||||
if (!async_dma_enabled) {
|
||||
dmaWait();
|
||||
}
|
||||
}
|
||||
|
||||
void SPIController::pushPixels3DMA(uint8_t* image, uint32_t len) {
|
||||
if ((len == 0) || (!DMA_Enabled)) return;
|
||||
|
||||
dmaWait();
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
memset(&trans, 0, sizeof(spi_transaction_t));
|
||||
|
||||
trans.user = (void *)1;
|
||||
trans.tx_buffer = image; //finally send the line data
|
||||
trans.length = len * 24; //Data length, in bits
|
||||
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
|
||||
|
||||
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
spiBusyCheck++;
|
||||
if (!async_dma_enabled) {
|
||||
dmaWait();
|
||||
}
|
||||
}
|
||||
#endif // ESP32
|
||||
// ===== RA8876 Specific =====
|
||||
|
||||
uint8_t SPIController::writeReg16(uint8_t reg, uint16_t wval) {
|
||||
hw_write9(reg, 0);
|
||||
hw_write9(wval, 1);
|
||||
hw_write9(reg + 1, 0);
|
||||
hw_write9(wval >> 8, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t SPIController::readData(void) {
|
||||
if (!spi) return 0;
|
||||
spi->write(RA8876_DATA_READ);
|
||||
return spi->transfer(0);
|
||||
}
|
||||
|
||||
uint8_t SPIController::readStatus(void) {
|
||||
if (!spi) return 0;
|
||||
spi->write(RA8876_STATUS_READ);
|
||||
return spi->transfer(0);
|
||||
}
|
||||
98
lib/lib_display/UDisplay/uDisplay_SPI_controller.h
Normal file
98
lib/lib_display/UDisplay/uDisplay_SPI_controller.h
Normal file
@ -0,0 +1,98 @@
|
||||
#ifndef _UDISPLAY_SPI_CONTROLLER_H_
|
||||
#define _UDISPLAY_SPI_CONTROLLER_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include "soc/spi_reg.h"
|
||||
#include "soc/spi_struct.h"
|
||||
#include "esp32-hal-spi.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#endif
|
||||
|
||||
#ifndef ESP32
|
||||
#include "spi_register.h"
|
||||
#endif
|
||||
|
||||
struct SPIControllerConfig {
|
||||
uint8_t bus_nr;
|
||||
int8_t cs;
|
||||
int8_t clk;
|
||||
int8_t mosi;
|
||||
int8_t dc;
|
||||
int8_t miso;
|
||||
uint32_t speed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Minimal SPIController - wraps low-level SPI functions
|
||||
* Extracted from uDisplay_spi.cpp
|
||||
*/
|
||||
class SPIController {
|
||||
public:
|
||||
SPIController(const SPIControllerConfig& config);
|
||||
~SPIController() = default;
|
||||
|
||||
// ===== Pin Control =====
|
||||
void csLow();
|
||||
void csHigh();
|
||||
void dcLow();
|
||||
void dcHigh();
|
||||
|
||||
// ===== Transaction Control =====
|
||||
void beginTransaction();
|
||||
void endTransaction();
|
||||
|
||||
// ===== High-Level Write Functions =====
|
||||
void writeCommand(uint8_t cmd);
|
||||
void writeData8(uint8_t data);
|
||||
void writeData16(uint16_t data);
|
||||
void writeData32(uint32_t data);
|
||||
|
||||
// ===== RA8876 Specific =====
|
||||
uint8_t writeReg16(uint8_t reg, uint16_t wval);
|
||||
uint8_t readData(void);
|
||||
uint8_t readStatus(void);
|
||||
|
||||
// ===== Direct Access =====
|
||||
SPIClass* getSPI() { return spi; }
|
||||
// SPISettings getSPISettings() { return spi_settings; }
|
||||
|
||||
// ===== DMA =====
|
||||
#ifdef ESP32
|
||||
bool initDMA(uint16_t width, uint16_t height, uint8_t data);
|
||||
void dmaWait(void);
|
||||
bool dmaBusy(void);
|
||||
void pushPixelsDMA(uint16_t* image, uint32_t len);
|
||||
void pushPixels3DMA(uint8_t* image, uint32_t len);
|
||||
#endif
|
||||
SPIControllerConfig spi_config; // make this private in the future again!
|
||||
|
||||
private:
|
||||
SPIClass* spi;
|
||||
SPISettings spi_settings;
|
||||
|
||||
// ===== Low-Level Write Functions =====
|
||||
void write8(uint8_t val);
|
||||
void write8_slow(uint8_t val);
|
||||
void write9(uint8_t val, uint8_t dc);
|
||||
void write9_slow(uint8_t val, uint8_t dc);
|
||||
void write16(uint16_t val);
|
||||
void write32(uint32_t val);
|
||||
void hw_write9(uint8_t val, uint8_t dc);
|
||||
|
||||
#ifdef ESP32
|
||||
bool dma_enabled = false;
|
||||
bool async_dma_enabled = false;
|
||||
|
||||
spi_host_device_t spi_host = VSPI_HOST;
|
||||
bool DMA_Enabled = false;
|
||||
uint8_t spiBusyCheck;
|
||||
spi_device_handle_t dmaHAL = nullptr; // For DMA
|
||||
spi_transaction_t trans;
|
||||
#endif //ESP32
|
||||
};
|
||||
|
||||
#endif // _UDISPLAY_SPI_CONTROLLER_H_
|
||||
367
lib/lib_display/UDisplay/uDisplay_SPI_panel.cpp
Normal file
367
lib/lib_display/UDisplay/uDisplay_SPI_panel.cpp
Normal file
@ -0,0 +1,367 @@
|
||||
// WIP
|
||||
// ======================================================
|
||||
// uDisplay_spi_panel.cpp - SPI LCD Panel Implementation
|
||||
// ======================================================
|
||||
|
||||
#include "uDisplay_SPI_panel.h"
|
||||
#include <Arduino.h>
|
||||
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
|
||||
|
||||
|
||||
SPIPanel::SPIPanel(const SPIPanelConfig& config,
|
||||
SPIController* spi_ctrl,
|
||||
uint8_t* framebuffer)
|
||||
: spi(spi_ctrl), cfg(config), fb_buffer(framebuffer),
|
||||
rotation(0), display_on(true), inverted(false)
|
||||
{
|
||||
// Initialize address window state
|
||||
window_x0 = 0;
|
||||
window_y0 = 0;
|
||||
window_x1 = 0;
|
||||
window_y1 = 0;
|
||||
use_hw_spi = (spi->spi_config.dc >= 0) && (spi->spi_config.bus_nr <= 2);
|
||||
}
|
||||
|
||||
SPIPanel::~SPIPanel() {
|
||||
// Panel doesn't own framebuffer or SPI controller
|
||||
}
|
||||
|
||||
|
||||
// ===== UniversalPanel Interface Implementation =====
|
||||
|
||||
bool SPIPanel::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
// From original uDisplay::drawPixel - only handle direct SPI drawing for color TFTs
|
||||
if ((x < 0) || (x >= cfg.width) || (y < 0) || (y >= cfg.height)) return true;
|
||||
|
||||
// Only handle direct SPI drawing for color displays without framebuffer
|
||||
if (!fb_buffer && cfg.bpp >= 16) {
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
setAddrWindow_internal(x, y, 1, 1);
|
||||
spi->writeCommand(cfg.cmd_write_ram);
|
||||
|
||||
if (cfg.col_mode == 18) {
|
||||
// From original WriteColor function
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
spi->writeData8(r);
|
||||
spi->writeData8(g);
|
||||
spi->writeData8(b);
|
||||
} else {
|
||||
spi->writeData16(color);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // Let uDisplay handle framebuffer cases (monochrome OLEDs)
|
||||
}
|
||||
|
||||
bool SPIPanel::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
// From original uDisplay::fillRect
|
||||
if((x >= cfg.width) || (y >= cfg.height)) return true;
|
||||
if((x + w - 1) >= cfg.width) w = cfg.width - x;
|
||||
if((y + h - 1) >= cfg.height) h = cfg.height - y;
|
||||
|
||||
// Only handle direct SPI drawing for color displays without framebuffer
|
||||
if (!fb_buffer && cfg.bpp >= 16) {
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
setAddrWindow_internal(x, y, w, h);
|
||||
spi->writeCommand(cfg.cmd_write_ram);
|
||||
|
||||
if (cfg.col_mode == 18) {
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
|
||||
for (int16_t yp = h; yp > 0; yp--) {
|
||||
for (int16_t xp = w; xp > 0; xp--) {
|
||||
spi->writeData8(r);
|
||||
spi->writeData8(g);
|
||||
spi->writeData8(b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int16_t yp = h; yp > 0; yp--) {
|
||||
for (int16_t xp = w; xp > 0; xp--) {
|
||||
spi->writeData16(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // Let uDisplay handle framebuffer cases (monochrome OLEDs)
|
||||
}
|
||||
|
||||
bool SPIPanel::pushColors(uint16_t *data, uint16_t len, bool not_swapped) {
|
||||
// Only handle direct rendering for color displays
|
||||
if (cfg.bpp < 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle byte swapping for LVGL (when not_swapped == false)
|
||||
if (!not_swapped && cfg.col_mode != 18) {
|
||||
// LVGL data - bytes are already swapped
|
||||
if (use_hw_spi) {
|
||||
#ifdef ESP32
|
||||
spi->pushPixelsDMA(data, len);
|
||||
#else
|
||||
spi->getSPI()->writeBytes((uint8_t*)data, len * 2);
|
||||
#endif
|
||||
} else {
|
||||
// Software SPI - write pixel by pixel
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
spi->writeData16(data[i]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle 18-bit color mode
|
||||
if (cfg.col_mode == 18) {
|
||||
#ifdef ESP32
|
||||
if (use_hw_spi) {
|
||||
uint8_t *line = (uint8_t*)malloc(len * 3);
|
||||
if (line) {
|
||||
uint8_t *lp = line;
|
||||
for (uint32_t cnt = 0; cnt < len; cnt++) {
|
||||
uint16_t color = data[cnt];
|
||||
if (!not_swapped) {
|
||||
color = (color << 8) | (color >> 8);
|
||||
}
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
*lp++ = r;
|
||||
*lp++ = g;
|
||||
*lp++ = b;
|
||||
}
|
||||
spi->pushPixels3DMA(line, len);
|
||||
free(line);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// Software SPI or ESP8266
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
uint16_t color = data[i];
|
||||
if (!not_swapped) {
|
||||
color = (color << 8) | (color >> 8);
|
||||
}
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
spi->writeData8(r);
|
||||
spi->writeData8(g);
|
||||
spi->writeData8(b);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle 16-bit color mode with no byte swapping (not_swapped == true)
|
||||
if (not_swapped) {
|
||||
if (use_hw_spi) {
|
||||
#ifdef ESP32
|
||||
spi->getSPI()->writePixels(data, len * 2);
|
||||
#else
|
||||
// ESP8266: writePixels() doesn't exist, use per-pixel write
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
spi->writeData16(data[i]);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
// Software SPI - write per-pixel
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
spi->writeData16(data[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SPIPanel::setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
||||
// From original uDisplay::setAddrWindow
|
||||
window_x0 = x0;
|
||||
window_y0 = y0;
|
||||
window_x1 = x1;
|
||||
window_y1 = y1;
|
||||
if (!x0 && !y0 && !x1 && !y1) {
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
} else {
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
setAddrWindow_internal(x0, y0, x1 - x0, y1 - y0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SPIPanel::setAddrWindow_internal(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
// From original uDisplay::setAddrWindow_int
|
||||
x += cfg.x_addr_offset[rotation];
|
||||
y += cfg.y_addr_offset[rotation];
|
||||
uint16_t x2 = x + w - 1;
|
||||
uint16_t y2 = y + h - 1;
|
||||
|
||||
if (cfg.address_mode != 8) {
|
||||
// 16/32-bit addressing (most TFT displays)
|
||||
uint32_t xa = ((uint32_t)x << 16) | x2;
|
||||
uint32_t ya = ((uint32_t)y << 16) | y2;
|
||||
|
||||
spi->writeCommand(cfg.cmd_set_addr_x);
|
||||
spi->writeData32(xa);
|
||||
|
||||
spi->writeCommand(cfg.cmd_set_addr_y);
|
||||
spi->writeData32(ya);
|
||||
|
||||
if (cfg.cmd_write_ram != 0xFF) {
|
||||
spi->writeCommand(cfg.cmd_write_ram);
|
||||
}
|
||||
} else {
|
||||
// 8-bit addressing mode (OLED displays)
|
||||
if (rotation & 1) {
|
||||
// Vertical address increment mode
|
||||
uint16_t temp = x; x = y; y = temp;
|
||||
temp = x2; x2 = y2; y2 = temp;
|
||||
}
|
||||
|
||||
spi->writeCommand(cfg.cmd_set_addr_x);
|
||||
if (cfg.all_commands_mode) {
|
||||
spi->writeData8(x);
|
||||
spi->writeData8(x2);
|
||||
} else {
|
||||
spi->writeCommand(x);
|
||||
spi->writeCommand(x2);
|
||||
}
|
||||
|
||||
spi->writeCommand(cfg.cmd_set_addr_y);
|
||||
if (cfg.all_commands_mode) {
|
||||
spi->writeData8(y);
|
||||
spi->writeData8(y2);
|
||||
} else {
|
||||
spi->writeCommand(y);
|
||||
spi->writeCommand(y2);
|
||||
}
|
||||
|
||||
if (cfg.cmd_write_ram != 0xFF) {
|
||||
spi->writeCommand(cfg.cmd_write_ram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SPIPanel::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
// From original uDisplay::drawFastHLine
|
||||
return fillRect(x, y, w, 1, color);
|
||||
}
|
||||
|
||||
bool SPIPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
// From original uDisplay::drawFastVLine
|
||||
return fillRect(x, y, 1, h, color);
|
||||
}
|
||||
|
||||
bool SPIPanel::displayOnff(int8_t on) {
|
||||
display_on = (on != 0);
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
if (display_on && cfg.cmd_display_on != 0xFF) {
|
||||
spi->writeCommand(cfg.cmd_display_on);
|
||||
} else if (!display_on && cfg.cmd_display_off != 0xFF) {
|
||||
spi->writeCommand(cfg.cmd_display_off);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPIPanel::invertDisplay(bool invert) {
|
||||
inverted = invert;
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
if (invert && cfg.cmd_invert_on != 0xFF) {
|
||||
spi->writeCommand(cfg.cmd_invert_on);
|
||||
} else if (!invert && cfg.cmd_invert_off != 0xFF) {
|
||||
spi->writeCommand(cfg.cmd_invert_off);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPIPanel::setRotation(uint8_t rot) {
|
||||
// From original uDisplay::setRotation
|
||||
rotation = rot & 3;
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
|
||||
if (cfg.cmd_memory_access != 0xFF && cfg.rot_cmd[rotation] != 0xFF) {
|
||||
spi->writeCommand(cfg.cmd_memory_access);
|
||||
if (!cfg.all_commands_mode) {
|
||||
spi->writeData8(cfg.rot_cmd[rotation]);
|
||||
} else {
|
||||
spi->writeCommand(cfg.rot_cmd[rotation]);
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
return true;
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SPIPanel::updateFrame() {
|
||||
// From original uDisplay::Updateframe - only for monochrome SPI OLEDs
|
||||
// Only handle framebuffer updates for monochrome displays
|
||||
if (!fb_buffer || cfg.bpp != 1) return false;
|
||||
|
||||
// OLED page-based framebuffer update (from original code)
|
||||
uint8_t ys = cfg.height >> 3;
|
||||
uint8_t xs = cfg.width >> 3;
|
||||
uint8_t m_row = cfg.cmd_set_addr_y; // saw_2 in original
|
||||
uint8_t m_col = 0; // i2c_col_start in original
|
||||
|
||||
uint16_t p = 0;
|
||||
uint8_t i, j, k = 0;
|
||||
|
||||
spi->beginTransaction();
|
||||
spi->csLow();
|
||||
for (i = 0; i < ys; i++) {
|
||||
spi->writeCommand(0xB0 + i + m_row); // set page address
|
||||
spi->writeCommand(m_col & 0xf); // set lower column address
|
||||
spi->writeCommand(0x10 | (m_col >> 4)); // set higher column address
|
||||
|
||||
for (j = 0; j < 8; j++) {
|
||||
for (k = 0; k < xs; k++, p++) {
|
||||
spi->writeData8(fb_buffer[p]);
|
||||
}
|
||||
}
|
||||
}
|
||||
spi->csHigh();
|
||||
spi->endTransaction();
|
||||
return true;
|
||||
}
|
||||
116
lib/lib_display/UDisplay/uDisplay_SPI_panel.h
Normal file
116
lib/lib_display/UDisplay/uDisplay_SPI_panel.h
Normal file
@ -0,0 +1,116 @@
|
||||
// WIP
|
||||
// ======================================================
|
||||
// uDisplay_spi_panel.h - SPI LCD Panel Implementation
|
||||
// ======================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "uDisplay_panel.h"
|
||||
#include "uDisplay_SPI_controller.h"
|
||||
|
||||
typedef struct LVGL_PARAMS_t {
|
||||
uint16_t flushlines;
|
||||
union {
|
||||
uint8_t data;
|
||||
struct {
|
||||
uint8_t use_dma : 1;
|
||||
uint8_t swap_color : 1;
|
||||
uint8_t async_dma : 1; // force DMA completion before returning, avoid conflict with other devices on same bus. If set you should make sure the display is the only device on the bus
|
||||
uint8_t busy_invert : 1;
|
||||
uint8_t invert_bw : 1;
|
||||
uint8_t resvd_3 : 1;
|
||||
uint8_t resvd_4 : 1;
|
||||
uint8_t resvd_5 : 1;
|
||||
};
|
||||
};
|
||||
}LVGL_PARAMS_t;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration for SPI-based displays
|
||||
*/
|
||||
struct SPIPanelConfig {
|
||||
// ===== Display Dimensions =====
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t bpp; // bits per pixel (1, 8, 16, etc.)
|
||||
uint8_t col_mode; // color mode (16, 18, etc.)
|
||||
|
||||
// ===== Address Window Protocol =====
|
||||
uint8_t cmd_set_addr_x; // Command to set X address range
|
||||
uint8_t cmd_set_addr_y; // Command to set Y address range
|
||||
uint8_t cmd_write_ram; // Command to write pixel data
|
||||
|
||||
// ===== Display Control Commands =====
|
||||
uint8_t cmd_display_on;
|
||||
uint8_t cmd_display_off;
|
||||
uint8_t cmd_invert_on;
|
||||
uint8_t cmd_invert_off;
|
||||
uint8_t cmd_memory_access; // For rotation settings
|
||||
uint8_t cmd_startline; // For vertical scroll offset
|
||||
|
||||
// ===== Per-Rotation Configuration =====
|
||||
uint8_t rot_cmd[4]; // Memory access command variant for each rotation
|
||||
uint16_t x_addr_offset[4]; // Address offset per rotation
|
||||
uint16_t y_addr_offset[4];
|
||||
uint8_t address_mode; // Addressing scheme (8, 16, 32-bit)
|
||||
|
||||
// ===== Flags =====
|
||||
bool all_commands_mode; // If true: send data bytes as commands
|
||||
|
||||
// ===== Reset & Power Control =====
|
||||
int8_t reset_pin; // GPIO for display reset (-1 if none)
|
||||
// int8_t busy_pin; // REMOVED - busy_pin is EPD-only, moved to EPDPanelConfig
|
||||
int8_t bpanel; // Backlight GPIO (-1 if none)
|
||||
};
|
||||
|
||||
class SPIPanel : public UniversalPanel {
|
||||
public:
|
||||
/**
|
||||
* Constructor - receives framebuffer from uDisplay if needed
|
||||
*/
|
||||
SPIPanel(const SPIPanelConfig& config,
|
||||
SPIController* spi_ctrl,
|
||||
uint8_t* framebuffer);
|
||||
|
||||
~SPIPanel();
|
||||
|
||||
// ===== UniversalPanel Interface =====
|
||||
bool drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
||||
bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override;
|
||||
bool pushColors(uint16_t *data, uint16_t len, bool not_swapped = false) override;
|
||||
bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) override;
|
||||
bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override;
|
||||
bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override;
|
||||
|
||||
bool displayOnff(int8_t on) override;
|
||||
bool invertDisplay(bool invert) override;
|
||||
bool setRotation(uint8_t rotation) override;
|
||||
bool updateFrame() override;
|
||||
|
||||
private:
|
||||
// ===== Hardware & Configuration =====
|
||||
SPIController* spi; // Not owned by panel
|
||||
SPIPanelConfig cfg; // Copy of config
|
||||
|
||||
// ===== Framebuffer =====
|
||||
uint8_t* fb_buffer; // Framebuffer (if provided by uDisplay)
|
||||
|
||||
// ===== Display State =====
|
||||
uint8_t rotation; // Current rotation (0-3)
|
||||
int16_t window_x0, window_y0, window_x1, window_y1;
|
||||
bool display_on;
|
||||
bool inverted;
|
||||
|
||||
bool use_hw_spi = false;
|
||||
|
||||
// ===== Internal Helpers =====
|
||||
void setAddrWindow_internal(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
||||
void sendAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
||||
void sendCommand(uint8_t cmd);
|
||||
void sendData8(uint8_t data);
|
||||
void sendData16(uint16_t data);
|
||||
void writeColor(uint16_t color);
|
||||
void resetDisplay();
|
||||
void waitBusy();
|
||||
};
|
||||
@ -14,7 +14,7 @@ extern void AddLog(uint32_t loglevel, const char* formatP, ...);
|
||||
extern int32_t ESP_ResetInfoReason();
|
||||
extern float CharToFloat(const char *str);
|
||||
extern SPIClass *SpiBegin(uint32 bus);
|
||||
extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size);
|
||||
// extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size);
|
||||
extern bool UsePSRAM(void);
|
||||
|
||||
enum uColorType { uCOLOR_BW, uCOLOR_COLOR };
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
#include "uDisplay_spi.h"
|
||||
|
||||
void udisp_bpwr(uint8_t on);
|
||||
|
||||
@ -21,50 +20,43 @@ void uDisplay::DisplayOnff(int8_t on) {
|
||||
if (pwr_cbp) {
|
||||
pwr_cbp(on);
|
||||
}
|
||||
if (universal_panel->displayOnff(on)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#define AW_PWMRES 1024
|
||||
|
||||
if (interface == _UDSP_I2C) {
|
||||
if (on) {
|
||||
i2c_command(dsp_on);
|
||||
} else {
|
||||
i2c_command(dsp_off);
|
||||
if (on) {
|
||||
if (bpanel >= 0) {
|
||||
#ifdef ESP32
|
||||
if (!bpmode) {
|
||||
analogWrite(bpanel, dimmer10_gamma);
|
||||
} else {
|
||||
analogWrite(bpanel, AW_PWMRES - dimmer10_gamma);
|
||||
}
|
||||
#else
|
||||
if (!bpmode) {
|
||||
digitalWrite(bpanel, HIGH);
|
||||
} else {
|
||||
digitalWrite(bpanel, LOW);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
if (on) {
|
||||
if (dsp_on != 0xff) ulcd_command_one(dsp_on);
|
||||
if (bpanel >= 0) {
|
||||
if (bpanel >= 0) {
|
||||
#ifdef ESP32
|
||||
if (!bpmode) {
|
||||
analogWrite(bpanel, dimmer10_gamma);
|
||||
} else {
|
||||
analogWrite(bpanel, AW_PWMRES - dimmer10_gamma);
|
||||
}
|
||||
#else
|
||||
if (!bpmode) {
|
||||
digitalWrite(bpanel, HIGH);
|
||||
} else {
|
||||
digitalWrite(bpanel, LOW);
|
||||
}
|
||||
#endif
|
||||
if (!bpmode) {
|
||||
analogWrite(bpanel, 0);
|
||||
} else {
|
||||
analogWrite(bpanel, AW_PWMRES - 1);
|
||||
}
|
||||
} else {
|
||||
if (dsp_off != 0xff) ulcd_command_one(dsp_off);
|
||||
if (bpanel >= 0) {
|
||||
#ifdef ESP32
|
||||
if (!bpmode) {
|
||||
analogWrite(bpanel, 0);
|
||||
} else {
|
||||
analogWrite(bpanel, AW_PWMRES - 1);
|
||||
}
|
||||
#else
|
||||
if (!bpmode) {
|
||||
digitalWrite(bpanel, LOW);
|
||||
} else {
|
||||
digitalWrite(bpanel, HIGH);
|
||||
}
|
||||
#endif
|
||||
if (!bpmode) {
|
||||
digitalWrite(bpanel, LOW);
|
||||
} else {
|
||||
digitalWrite(bpanel, HIGH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,12 +85,12 @@ void uDisplay::dim10(uint8_t dim, uint16_t dim_gamma) {
|
||||
|
||||
if (interface == _UDSP_SPI) {
|
||||
if (dim_op != 0xff) {
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
ulcd_command(dim_op);
|
||||
ulcd_data8(dimmer8);
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
spiController->beginTransaction();
|
||||
spiController->csLow();
|
||||
spiController->writeCommand(dim_op);
|
||||
spiController->writeData8(dimmer8);
|
||||
spiController->csHigh();
|
||||
spiController->endTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,24 +98,8 @@ void uDisplay::dim10(uint8_t dim, uint16_t dim_gamma) {
|
||||
// ===== Display Inversion =====
|
||||
|
||||
void uDisplay::invertDisplay(boolean i) {
|
||||
if (ep_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interface == _UDSP_SPI || interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
if (i) {
|
||||
ulcd_command_one(inv_on);
|
||||
} else {
|
||||
ulcd_command_one(inv_off);
|
||||
}
|
||||
}
|
||||
|
||||
if (interface == _UDSP_I2C) {
|
||||
if (i) {
|
||||
i2c_command(inv_on);
|
||||
} else {
|
||||
i2c_command(inv_off);
|
||||
}
|
||||
if (universal_panel) {
|
||||
universal_panel->invertDisplay(i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +110,10 @@ void uDisplay::Splash(void) {
|
||||
|
||||
if (ep_mode) {
|
||||
Updateframe();
|
||||
delay_sync(lut3time * 10);
|
||||
if (universal_panel) {
|
||||
EPDPanel* epd = static_cast<EPDPanel*>(universal_panel);
|
||||
epd->delay_sync(panel_config->epd.update_time * 10);
|
||||
}
|
||||
}
|
||||
|
||||
setTextFont(splash_font);
|
||||
|
||||
@ -1,133 +0,0 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
// ===== DMA Initialization and Control =====
|
||||
// ESP 32 DMA section , derived from TFT_eSPI
|
||||
|
||||
bool uDisplay::initDMA(int32_t ctrl_cs) {
|
||||
if (DMA_Enabled) return false;
|
||||
|
||||
esp_err_t ret;
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = spi_mosi,
|
||||
.miso_io_num = -1,
|
||||
.sclk_io_num = spi_clk,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = width() * height() * 2 + 8, // TFT screen size
|
||||
.flags = 0,
|
||||
.intr_flags = 0
|
||||
};
|
||||
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 0,
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
.mode = SPI_MODE3,
|
||||
.duty_cycle_pos = 0,
|
||||
.cs_ena_pretrans = 0,
|
||||
.cs_ena_posttrans = 0,
|
||||
.clock_speed_hz = spi_speed*1000000,
|
||||
.input_delay_ns = 0,
|
||||
.spics_io_num = ctrl_cs,
|
||||
.flags = SPI_DEVICE_NO_DUMMY, //0,
|
||||
.queue_size = 1,
|
||||
.pre_cb = 0, //dc_callback, //Callback to handle D/C line
|
||||
.post_cb = 0
|
||||
};
|
||||
|
||||
ret = spi_bus_initialize(spi_host, &buscfg, 1);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ret = spi_bus_add_device(spi_host, &devcfg, &dmaHAL);
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
DMA_Enabled = true;
|
||||
spiBusyCheck = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void uDisplay::deInitDMA(void) {
|
||||
if (!DMA_Enabled) return;
|
||||
spi_bus_remove_device(dmaHAL);
|
||||
spi_bus_free(spi_host);
|
||||
DMA_Enabled = false;
|
||||
}
|
||||
|
||||
bool uDisplay::dmaBusy(void) {
|
||||
if (!DMA_Enabled || !spiBusyCheck) return false;
|
||||
|
||||
spi_transaction_t *rtrans;
|
||||
esp_err_t ret;
|
||||
uint8_t checks = spiBusyCheck;
|
||||
for (int i = 0; i < checks; ++i) {
|
||||
ret = spi_device_get_trans_result(dmaHAL, &rtrans, 0);
|
||||
if (ret == ESP_OK) spiBusyCheck--;
|
||||
}
|
||||
|
||||
//Serial.print("spiBusyCheck=");Serial.println(spiBusyCheck);
|
||||
if (spiBusyCheck == 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void uDisplay::dmaWait(void) {
|
||||
if (!DMA_Enabled || !spiBusyCheck) return;
|
||||
spi_transaction_t *rtrans;
|
||||
esp_err_t ret;
|
||||
for (int i = 0; i < spiBusyCheck; ++i) {
|
||||
ret = spi_device_get_trans_result(dmaHAL, &rtrans, portMAX_DELAY);
|
||||
assert(ret == ESP_OK);
|
||||
}
|
||||
spiBusyCheck = 0;
|
||||
}
|
||||
|
||||
// ===== DMA Data Transfer Functions =====
|
||||
|
||||
void uDisplay::pushPixelsDMA(uint16_t* image, uint32_t len) {
|
||||
if ((len == 0) || (!DMA_Enabled)) return;
|
||||
|
||||
dmaWait();
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
memset(&trans, 0, sizeof(spi_transaction_t));
|
||||
|
||||
trans.user = (void *)1;
|
||||
trans.tx_buffer = image; //finally send the line data
|
||||
trans.length = len * 16; //Data length, in bits
|
||||
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
|
||||
|
||||
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
spiBusyCheck++;
|
||||
if (!lvgl_param.async_dma) {
|
||||
dmaWait();
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::pushPixels3DMA(uint8_t* image, uint32_t len) {
|
||||
if ((len == 0) || (!DMA_Enabled)) return;
|
||||
|
||||
dmaWait();
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
memset(&trans, 0, sizeof(spi_transaction_t));
|
||||
|
||||
trans.user = (void *)1;
|
||||
trans.tx_buffer = image; //finally send the line data
|
||||
trans.length = len * 24; //Data length, in bits
|
||||
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
|
||||
|
||||
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
|
||||
assert(ret == ESP_OK);
|
||||
|
||||
spiBusyCheck++;
|
||||
if (!lvgl_param.async_dma) {
|
||||
dmaWait();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ESP32
|
||||
@ -1,362 +0,0 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
#include "uDisplay_spi.h"
|
||||
|
||||
|
||||
// ===== EPD Command Definitions =====
|
||||
static constexpr uint8_t DRIVER_OUTPUT_CONTROL = 0x01;
|
||||
static constexpr uint8_t BOOSTER_SOFT_START_CONTROL = 0x0C;
|
||||
static constexpr uint8_t GATE_SCAN_START_POSITION = 0x0F;
|
||||
static constexpr uint8_t DEEP_SLEEP_MODE = 0x10;
|
||||
static constexpr uint8_t DATA_ENTRY_MODE_SETTING = 0x11;
|
||||
static constexpr uint8_t SW_RESET = 0x12;
|
||||
static constexpr uint8_t TEMPERATURE_SENSOR_CONTROL = 0x1A;
|
||||
static constexpr uint8_t MASTER_ACTIVATION = 0x20;
|
||||
static constexpr uint8_t DISPLAY_UPDATE_CONTROL_1 = 0x21;
|
||||
static constexpr uint8_t DISPLAY_UPDATE_CONTROL_2 = 0x22;
|
||||
static constexpr uint8_t WRITE_RAM = 0x24;
|
||||
static constexpr uint8_t WRITE_VCOM_REGISTER = 0x2C;
|
||||
static constexpr uint8_t WRITE_LUT_REGISTER = 0x32;
|
||||
static constexpr uint8_t SET_DUMMY_LINE_PERIOD = 0x3A;
|
||||
static constexpr uint8_t SET_GATE_TIME = 0x3B;
|
||||
static constexpr uint8_t BORDER_WAVEFORM_CONTROL = 0x3C;
|
||||
static constexpr uint8_t SET_RAM_X_ADDRESS_START_END_POSITION = 0x44;
|
||||
static constexpr uint8_t SET_RAM_Y_ADDRESS_START_END_POSITION = 0x45;
|
||||
static constexpr uint8_t SET_RAM_X_ADDRESS_COUNTER = 0x4E;
|
||||
static constexpr uint8_t SET_RAM_Y_ADDRESS_COUNTER = 0x4F;
|
||||
static constexpr uint8_t TERMINATE_FRAME_READ_WRITE = 0xFF;
|
||||
|
||||
// ===== EPD Initialization and Control =====
|
||||
|
||||
void uDisplay::Init_EPD(int8_t p) {
|
||||
if (p == DISPLAY_INIT_PARTIAL) {
|
||||
if (lutpsize) {
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: init partial epaper mode"));
|
||||
SetLut(lut_partial);
|
||||
Updateframe_EPD();
|
||||
delay_sync(lutptime * 10);
|
||||
}
|
||||
return;
|
||||
} else if (p == DISPLAY_INIT_FULL) {
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: init full epaper mode"));
|
||||
if (lutfsize) {
|
||||
SetLut(lut_full);
|
||||
Updateframe_EPD();
|
||||
}
|
||||
if (ep_mode == 2) {
|
||||
ClearFrame_42();
|
||||
DisplayFrame_42();
|
||||
}
|
||||
delay_sync(lutftime * 10);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ep_mode == 2) Init_EPD(DISPLAY_INIT_FULL);
|
||||
}
|
||||
|
||||
void uDisplay::SetLut(const unsigned char* lut) {
|
||||
spi_command_EPD(lut_cmd[0]);
|
||||
for (int i = 0; i < lutfsize; i++) {
|
||||
spi_data8_EPD(lut[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::SetLuts(void) {
|
||||
uint8_t index, count;
|
||||
for (index = 0; index < MAX_LUTS; index++) {
|
||||
spi_command_EPD(lut_cmd[index]);
|
||||
for (count = 0; count < lut_cnt[index]; count++) {
|
||||
spi_data8_EPD(lut_array[index][count]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== EPD Frame Memory Management =====
|
||||
|
||||
void uDisplay::ClearFrameMemory(unsigned char color) {
|
||||
SetMemoryArea(0, 0, gxs - 1, gys - 1);
|
||||
SetMemoryPointer(0, 0);
|
||||
spi_command_EPD(WRITE_RAM);
|
||||
for (int i = 0; i < gxs / 8 * gys; i++) {
|
||||
spi_data8_EPD(color);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::ClearFrame_42(void) {
|
||||
spi_command_EPD(saw_1);
|
||||
for (uint16_t j = 0; j < gys; j++) {
|
||||
for (uint16_t i = 0; i < gxs; i++) {
|
||||
spi_data8_EPD(0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
spi_command_EPD(saw_2);
|
||||
for (uint16_t j = 0; j < gys; j++) {
|
||||
for (uint16_t i = 0; i < gxs; i++) {
|
||||
spi_data8_EPD(0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
spi_command_EPD(saw_3);
|
||||
delay_sync(100);
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: EPD Clearframe"));
|
||||
}
|
||||
|
||||
void uDisplay::DisplayFrame_42(void) {
|
||||
spi_command_EPD(saw_1);
|
||||
for(int i = 0; i < gxs / 8 * gys; i++) {
|
||||
spi_data8_EPD(0xFF);
|
||||
}
|
||||
delay(2);
|
||||
|
||||
spi_command_EPD(saw_2);
|
||||
for(int i = 0; i < gxs / 8 * gys; i++) {
|
||||
spi_data8_EPD(framebuffer[i]^0xff);
|
||||
}
|
||||
delay(2);
|
||||
|
||||
SetLuts();
|
||||
|
||||
spi_command_EPD(saw_3);
|
||||
delay_sync(100);
|
||||
|
||||
AddLog(LOG_LEVEL_DEBUG, PSTR("DSP: EPD Displayframe"));
|
||||
}
|
||||
|
||||
void uDisplay::DisplayFrame_29(void) {
|
||||
spi_command_EPD(DISPLAY_UPDATE_CONTROL_2);
|
||||
spi_data8_EPD(0xC4);
|
||||
spi_command_EPD(MASTER_ACTIVATION);
|
||||
spi_data8_EPD(TERMINATE_FRAME_READ_WRITE);
|
||||
}
|
||||
|
||||
// ===== EPD Memory Addressing =====
|
||||
|
||||
void uDisplay::SetMemoryArea(int x_start, int y_start, int x_end, int y_end) {
|
||||
int x_start1 = (x_start >> 3) & 0xFF;
|
||||
int x_end1 = (x_end >> 3) & 0xFF;
|
||||
int y_start1 = y_start & 0xFF;
|
||||
int y_start2 = (y_start >> 8) & 0xFF;
|
||||
int y_end1 = y_end & 0xFF;
|
||||
int y_end2 = (y_end >> 8) & 0xFF;
|
||||
|
||||
if (ep_mode == 3) {
|
||||
spi_command_EPD(SET_RAM_X_ADDRESS_START_END_POSITION);
|
||||
spi_data8_EPD(x_start1);
|
||||
spi_data8_EPD(x_end1);
|
||||
spi_command_EPD(SET_RAM_Y_ADDRESS_START_END_POSITION);
|
||||
spi_data8_EPD(y_end1);
|
||||
spi_data8_EPD(y_end2);
|
||||
spi_data8_EPD(y_start1);
|
||||
spi_data8_EPD(y_start2);
|
||||
} else {
|
||||
spi_command_EPD(SET_RAM_X_ADDRESS_START_END_POSITION);
|
||||
spi_data8_EPD(x_start1);
|
||||
spi_data8_EPD(x_end1);
|
||||
spi_command_EPD(SET_RAM_Y_ADDRESS_START_END_POSITION);
|
||||
spi_data8_EPD(y_start1);
|
||||
spi_data8_EPD(y_start2);
|
||||
spi_data8_EPD(y_end1);
|
||||
spi_data8_EPD(y_end2);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::SetMemoryPointer(int x, int y) {
|
||||
int x1;
|
||||
int y1;
|
||||
int y2;
|
||||
|
||||
if (ep_mode == 3) {
|
||||
x1 = (x >> 3) & 0xFF;
|
||||
y--;
|
||||
y1 = y & 0xFF;
|
||||
y2 = (y >> 8) & 0xFF;
|
||||
} else {
|
||||
x1 = (x >> 3) & 0xFF;
|
||||
y1 = y & 0xFF;
|
||||
y2 = (y >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
spi_command_EPD(SET_RAM_X_ADDRESS_COUNTER);
|
||||
spi_data8_EPD(x1);
|
||||
spi_command_EPD(SET_RAM_Y_ADDRESS_COUNTER);
|
||||
spi_data8_EPD(y1);
|
||||
spi_data8_EPD(y2);
|
||||
}
|
||||
|
||||
// ===== EPD Frame Updates =====
|
||||
|
||||
void uDisplay::Updateframe_EPD(void) {
|
||||
if (ep_mode == 1 || ep_mode == 3) {
|
||||
switch (ep_update_mode) {
|
||||
case DISPLAY_INIT_PARTIAL:
|
||||
if (epc_part_cnt) {
|
||||
send_spi_cmds(epcoffs_part, epc_part_cnt);
|
||||
}
|
||||
break;
|
||||
case DISPLAY_INIT_FULL:
|
||||
if (epc_full_cnt) {
|
||||
send_spi_cmds(epcoffs_full, epc_full_cnt);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SetFrameMemory(framebuffer, 0, 0, gxs, gys);
|
||||
DisplayFrame_29();
|
||||
}
|
||||
} else {
|
||||
DisplayFrame_42();
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::SetFrameMemory(const unsigned char* image_buffer) {
|
||||
SetMemoryArea(0, 0, gxs - 1, gys - 1);
|
||||
SetMemoryPointer(0, 0);
|
||||
spi_command_EPD(WRITE_RAM);
|
||||
for (int i = 0; i < gxs / 8 * gys; i++) {
|
||||
spi_data8_EPD(image_buffer[i] ^ 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::SetFrameMemory(const unsigned char* image_buffer,
|
||||
uint16_t x, uint16_t y, uint16_t image_width, uint16_t image_height) {
|
||||
|
||||
uint16_t x_end;
|
||||
uint16_t y_end;
|
||||
|
||||
if (image_buffer == NULL || x < 0 || image_width < 0 || y < 0 || image_height < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
x &= 0xFFF8;
|
||||
image_width &= 0xFFF8;
|
||||
if (x + image_width >= gxs) {
|
||||
x_end = gxs - 1;
|
||||
} else {
|
||||
x_end = x + image_width - 1;
|
||||
}
|
||||
if (y + image_height >= gys) {
|
||||
y_end = gys - 1;
|
||||
} else {
|
||||
y_end = y + image_height - 1;
|
||||
}
|
||||
|
||||
if (!x && !y && image_width == gxs && image_height == gys) {
|
||||
SetFrameMemory(image_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
SetMemoryArea(x, y, x_end, y_end);
|
||||
SetMemoryPointer(x, y);
|
||||
spi_command_EPD(WRITE_RAM);
|
||||
for (uint16_t j = 0; j < y_end - y + 1; j++) {
|
||||
for (uint16_t i = 0; i < (x_end - x + 1) / 8; i++) {
|
||||
spi_data8_EPD(image_buffer[i + j * (image_width / 8)] ^ 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::Send_EP_Data() {
|
||||
uint16_t image_width = gxs & 0xFFF8;
|
||||
uint16_t x = 0;
|
||||
uint16_t y = 0;
|
||||
uint16_t x_end = gxs - 1;
|
||||
uint16_t y_end = gys - 1;
|
||||
|
||||
for (uint16_t j = 0; j < y_end - y + 1; j++) {
|
||||
for (uint16_t i = 0; i < (x_end - x + 1) / 8; i++) {
|
||||
spi_data8_EPD(framebuffer[i + j * (image_width / 8)] ^ 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== EPD Drawing Primitives =====
|
||||
#define IF_INVERT_COLOR 1
|
||||
#define renderer_swap(a, b) { int16_t t = a; a = b; b = t; }
|
||||
|
||||
void uDisplay::DrawAbsolutePixel(int x, int y, int16_t color) {
|
||||
int16_t w = width(), h = height();
|
||||
if (cur_rot == 1 || cur_rot == 3) {
|
||||
renderer_swap(w, h);
|
||||
}
|
||||
|
||||
if (x < 0 || x >= w || y < 0 || y >= h) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IF_INVERT_COLOR) {
|
||||
if (color) {
|
||||
framebuffer[(x + y * w) / 8] |= 0x80 >> (x % 8);
|
||||
} else {
|
||||
framebuffer[(x + y * w) / 8] &= ~(0x80 >> (x % 8));
|
||||
}
|
||||
} else {
|
||||
if (color) {
|
||||
framebuffer[(x + y * w) / 8] &= ~(0x80 >> (x % 8));
|
||||
} else {
|
||||
framebuffer[(x + y * w) / 8] |= 0x80 >> (x % 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::drawPixel_EPD(int16_t x, int16_t y, uint16_t color) {
|
||||
if (!framebuffer) return;
|
||||
if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
|
||||
return;
|
||||
|
||||
switch (cur_rot) {
|
||||
case 1:
|
||||
renderer_swap(x, y);
|
||||
x = gxs - x - 1;
|
||||
break;
|
||||
case 2:
|
||||
x = gxs - x - 1;
|
||||
y = gys - y - 1;
|
||||
break;
|
||||
case 3:
|
||||
renderer_swap(x, y);
|
||||
y = gys - y - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
DrawAbsolutePixel(x, y, color);
|
||||
}
|
||||
|
||||
void uDisplay::fillRect_EPD(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
for (uint32_t yp = y; yp < y + h; yp++) {
|
||||
for (uint32_t xp = x; xp < x + w; xp++) {
|
||||
drawPixel_EPD(xp, yp, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::drawFastVLine_EPD(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
while (h--) {
|
||||
drawPixel_EPD(x, y, color);
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::drawFastHLine_EPD(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
while (w--) {
|
||||
drawPixel_EPD(x, y, color);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== EPD SPI Helpers =====
|
||||
|
||||
void uDisplay::spi_command_EPD(uint8_t val) {
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
ulcd_command(val);
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
|
||||
void uDisplay::spi_data8_EPD(uint8_t val) {
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
ulcd_data8(val);
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
@ -1,158 +0,0 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1ts.val : &GPIO.out_w1ts; }
|
||||
//static inline volatile uint32_t* get_gpio_hi_reg(int_fast8_t pin) { return (volatile uint32_t*)((pin & 32) ? 0x60004014 : 0x60004008) ; } // workaround Eratta
|
||||
static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (pin & 32) ? &GPIO.out1_w1tc.val : &GPIO.out_w1tc; }
|
||||
//static inline volatile uint32_t* get_gpio_lo_reg(int_fast8_t pin) { return (volatile uint32_t*)((pin & 32) ? 0x60004018 : 0x6000400C) ; }
|
||||
static inline bool gpio_in(int_fast8_t pin) { return ((pin & 32) ? GPIO.in1.data : GPIO.in) & (1 << (pin & 31)); }
|
||||
static inline void gpio_hi(int_fast8_t pin) { if (pin >= 0) *get_gpio_hi_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_hi: %d", pin); }
|
||||
static inline void gpio_lo(int_fast8_t pin) { if (pin >= 0) *get_gpio_lo_reg(pin) = 1 << (pin & 31); } // ESP_LOGI("LGFX", "gpio_lo: %d", pin); }
|
||||
|
||||
// ===== ESP32-S3 Clock Calculation =====
|
||||
|
||||
void uDisplay::calcClockDiv(uint32_t* div_a, uint32_t* div_b, uint32_t* div_n, uint32_t* clkcnt, uint32_t baseClock, uint32_t targetFreq) {
|
||||
uint32_t diff = INT32_MAX;
|
||||
*div_n = 256;
|
||||
*div_a = 63;
|
||||
*div_b = 62;
|
||||
*clkcnt = 64;
|
||||
uint32_t start_cnt = std::min<uint32_t>(64u, (baseClock / (targetFreq * 2) + 1));
|
||||
uint32_t end_cnt = std::max<uint32_t>(2u, baseClock / 256u / targetFreq);
|
||||
if (start_cnt <= 2) { end_cnt = 1; }
|
||||
for (uint32_t cnt = start_cnt; diff && cnt >= end_cnt; --cnt)
|
||||
{
|
||||
float fdiv = (float)baseClock / cnt / targetFreq;
|
||||
uint32_t n = std::max<uint32_t>(2u, (uint32_t)fdiv);
|
||||
fdiv -= n;
|
||||
|
||||
for (uint32_t a = 63; diff && a > 0; --a)
|
||||
{
|
||||
uint32_t b = roundf(fdiv * a);
|
||||
if (a == b && n == 256) {
|
||||
break;
|
||||
}
|
||||
uint32_t freq = baseClock / ((n * cnt) + (float)(b * cnt) / (float)a);
|
||||
uint32_t d = abs((int)targetFreq - (int)freq);
|
||||
if (diff <= d) { continue; }
|
||||
diff = d;
|
||||
*clkcnt = cnt;
|
||||
*div_n = n;
|
||||
*div_b = b;
|
||||
*div_a = a;
|
||||
if (b == 0 || a == b) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (*div_a == *div_b)
|
||||
{
|
||||
*div_b = 0;
|
||||
*div_n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== ESP32-S3 DMA Descriptor Management =====
|
||||
|
||||
void uDisplay::_alloc_dmadesc(size_t len) {
|
||||
if (_dmadesc) heap_caps_free(_dmadesc);
|
||||
_dmadesc_size = len;
|
||||
_dmadesc = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t) * len, MALLOC_CAP_DMA);
|
||||
}
|
||||
|
||||
void uDisplay::_setup_dma_desc_links(const uint8_t *data, int32_t len) {
|
||||
static constexpr size_t MAX_DMA_LEN = (4096-4);
|
||||
}
|
||||
|
||||
// ===== ESP32-S3 Pin Control =====
|
||||
|
||||
void uDisplay::cs_control(bool level) {
|
||||
auto pin = par_cs;
|
||||
if (pin < 0) return;
|
||||
if (level) {
|
||||
gpio_hi(pin);
|
||||
}
|
||||
else {
|
||||
gpio_lo(pin);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::_pb_init_pin(bool read) {
|
||||
if (read) {
|
||||
if (interface == _UDSP_PAR8) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_ll_output_disable(&GPIO, (gpio_num_t)par_dbl[i]);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_ll_output_disable(&GPIO, (gpio_num_t)par_dbl[i]);
|
||||
}
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_ll_output_disable(&GPIO, (gpio_num_t)par_dbh[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto idx_base = LCD_DATA_OUT0_IDX;
|
||||
if (interface == _UDSP_PAR8) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_matrix_out(par_dbl[i], idx_base + i, 0, 0);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_matrix_out(par_dbl[i], idx_base + i, 0, 0);
|
||||
}
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
gpio_matrix_out(par_dbh[i], idx_base + 8 + i, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== ESP32-S3 Simple Resistive Touch =====
|
||||
|
||||
uint32_t uDisplay::get_sr_touch(uint32_t _xp, uint32_t _xm, uint32_t _yp, uint32_t _ym) {
|
||||
uint32_t aval = 0;
|
||||
uint16_t xp,yp;
|
||||
if (pb_busy()) return 0;
|
||||
|
||||
_pb_init_pin(true);
|
||||
gpio_matrix_out(par_rs, 0x100, 0, 0);
|
||||
|
||||
pinMode(_ym, INPUT_PULLUP); // d0
|
||||
pinMode(_yp, INPUT_PULLUP); // rs
|
||||
|
||||
pinMode(_xm, OUTPUT); // cs
|
||||
pinMode(_xp, OUTPUT); // d1
|
||||
digitalWrite(_xm, HIGH); // cs
|
||||
digitalWrite(_xp, LOW); // d1
|
||||
|
||||
xp = 4096 - analogRead(_ym); // d0
|
||||
|
||||
pinMode(_xm, INPUT_PULLUP); // cs
|
||||
pinMode(_xp, INPUT_PULLUP); // d1
|
||||
|
||||
pinMode(_ym, OUTPUT); // d0
|
||||
pinMode(_yp, OUTPUT); // rs
|
||||
digitalWrite(_ym, HIGH); // d0
|
||||
digitalWrite(_yp, LOW); // rs
|
||||
|
||||
yp = 4096 - analogRead(_xp); // d1
|
||||
|
||||
aval = (xp << 16) | yp;
|
||||
|
||||
pinMode(_yp, OUTPUT); // rs
|
||||
pinMode(_xm, OUTPUT); // cs
|
||||
pinMode(_ym, OUTPUT); // d0
|
||||
pinMode(_xp, OUTPUT); // d1
|
||||
digitalWrite(_yp, HIGH); // rs
|
||||
digitalWrite(_xm, HIGH); // cs
|
||||
|
||||
_pb_init_pin(false);
|
||||
gpio_matrix_out(par_rs, LCD_DC_IDX, 0, 0);
|
||||
|
||||
return aval;
|
||||
}
|
||||
|
||||
#endif // USE_ESP32_S3
|
||||
@ -1,44 +1,28 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
#include "uDisplay_spi.h"
|
||||
|
||||
// ===== Basic Drawing Primitives =====
|
||||
|
||||
static constexpr uint16_t RGB16_TO_MONO = 0x8410;
|
||||
static constexpr uint16_t RGB16_SWAP_TO_MONO = 0x1084;
|
||||
#define renderer_swap(a, b) { int16_t t = a; a = b; b = t; }
|
||||
|
||||
void uDisplay::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_RGB) {
|
||||
drawPixel_RGB(x, y, color);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ep_mode) {
|
||||
drawPixel_EPD(x, y, color);
|
||||
return;
|
||||
if (universal_panel->drawPixel(x, y, color)) {
|
||||
return; // Handled by universal panel
|
||||
}
|
||||
|
||||
if (framebuffer) {
|
||||
Renderer::drawPixel(x, y, color);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return;
|
||||
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
setAddrWindow_int(x, y, 1, 1);
|
||||
WriteColor(color);
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
|
||||
void uDisplay::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
if (ep_mode) {
|
||||
drawFastHLine_EPD(x, y, w, color);
|
||||
// Rudimentary clipping
|
||||
if((x >= _width) || (y >= _height)) return;
|
||||
if((x + w - 1) >= _width) w = _width - x;
|
||||
|
||||
if (universal_panel->drawFastHLine(x, y, w, color)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -47,65 +31,9 @@ void uDisplay::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Rudimentary clipping
|
||||
if((x >= _width) || (y >= _height)) return;
|
||||
if((x + w - 1) >= _width) w = _width - x;
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_RGB) {
|
||||
if (cur_rot > 0) {
|
||||
while (w--) {
|
||||
drawPixel_RGB(x , y , color);
|
||||
x++;
|
||||
}
|
||||
} else {
|
||||
uint16_t *fb = rgb_fb;
|
||||
fb += (int32_t)y * _width;
|
||||
fb += x;
|
||||
while (w--) {
|
||||
*fb = color;
|
||||
Cache_WriteBack_Addr((uint32_t)fb, 2);
|
||||
fb++;
|
||||
x++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
setAddrWindow_int(x, y, w, 1);
|
||||
|
||||
if (col_mode == 18) {
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
|
||||
while (w--) {
|
||||
ulcd_data8(r);
|
||||
ulcd_data8(g);
|
||||
ulcd_data8(b);
|
||||
}
|
||||
} else {
|
||||
while (w--) {
|
||||
WriteColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
|
||||
void uDisplay::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
if (ep_mode) {
|
||||
drawFastVLine_EPD(x, y, h, color);
|
||||
return;
|
||||
}
|
||||
|
||||
if (framebuffer) {
|
||||
Renderer::drawFastVLine(x, y, h, color);
|
||||
return;
|
||||
@ -115,67 +43,15 @@ void uDisplay::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
|
||||
if ((x >= _width) || (y >= _height)) return;
|
||||
if ((y + h - 1) >= _height) h = _height - y;
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_RGB) {
|
||||
if (cur_rot > 0) {
|
||||
while (h--) {
|
||||
drawPixel_RGB(x , y , color);
|
||||
y++;
|
||||
}
|
||||
} else {
|
||||
uint16_t *fb = rgb_fb;
|
||||
fb += (int32_t)y * _width;
|
||||
fb += x;
|
||||
while (h--) {
|
||||
*fb = color;
|
||||
Cache_WriteBack_Addr((uint32_t)fb, 2);
|
||||
fb+=_width;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
if (universal_panel->drawFastVLine(x, y, h, color)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
setAddrWindow_int(x, y, 1, h);
|
||||
|
||||
if (col_mode == 18) {
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
|
||||
while (h--) {
|
||||
ulcd_data8(r);
|
||||
ulcd_data8(g);
|
||||
ulcd_data8(b);
|
||||
}
|
||||
} else {
|
||||
while (h--) {
|
||||
WriteColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
|
||||
void uDisplay::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_RGB) {
|
||||
for (uint32_t yp = y; yp < y + h; yp++) {
|
||||
drawFastHLine(x, yp, w, color);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ep_mode) {
|
||||
fillRect_EPD(x, y, w, h, color);
|
||||
if (universal_panel->fillRect(x, y, w, h, color)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -183,40 +59,6 @@ void uDisplay::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t col
|
||||
Renderer::fillRect(x, y, w, h, color);
|
||||
return;
|
||||
}
|
||||
|
||||
if((x >= _width) || (y >= _height)) return;
|
||||
if((x + w - 1) >= _width) w = _width - x;
|
||||
if((y + h - 1) >= _height) h = _height - y;
|
||||
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
setAddrWindow_int(x, y, w, h);
|
||||
|
||||
if (col_mode == 18) {
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
|
||||
for (y = h; y > 0; y--) {
|
||||
for (x = w; x > 0; x--) {
|
||||
ulcd_data8(r);
|
||||
ulcd_data8(g);
|
||||
ulcd_data8(b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (y = h; y > 0; y--) {
|
||||
for (x = w; x > 0; x--) {
|
||||
WriteColor(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
|
||||
void uDisplay::fillScreen(uint16_t color) {
|
||||
@ -225,140 +67,12 @@ void uDisplay::fillScreen(uint16_t color) {
|
||||
|
||||
static inline void lvgl_color_swap(uint16_t *data, uint16_t len) { for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8); }
|
||||
|
||||
void uDisplay::pushColors(uint16_t *data, uint16_t len, boolean not_swapped) {
|
||||
void uDisplay::pushColors(uint16_t *data, uint16_t len, boolean not_swapped) { //not_swapped is always true in call form LVGL driver!!!!
|
||||
|
||||
if (lvgl_param.swap_color) {
|
||||
not_swapped = !not_swapped;
|
||||
}
|
||||
|
||||
//Serial.printf("push %x - %d - %d - %d\n", (uint32_t)data, len, not_swapped, lvgl_param.data);
|
||||
|
||||
// Isolating _UDSP_RGB to increase code sharing
|
||||
//
|
||||
// Use ESP-IDF LCD driver to push colors and rely on the following assumptions:
|
||||
// * bytes swapping is already handled in the driver configuration (see uDisplay::Init()),
|
||||
// * pushColors() is only called with not_swapped equals true,
|
||||
// * cache flushing is done by the LCD driver.
|
||||
if (interface == _UDSP_RGB) {
|
||||
#ifdef USE_ESP32_S3
|
||||
if (!not_swapped) {
|
||||
// internal error -> write error message but continue (with possibly wrong colors)
|
||||
AddLog(LOG_LEVEL_ERROR, PSTR("DSP: Unexpected byte-swapping requested in pushColors()"));
|
||||
if (lvgl_param.swap_color) {
|
||||
not_swapped = !not_swapped;
|
||||
}
|
||||
|
||||
// check that bytes count matches the size of area, and remove from inner loop
|
||||
if ((seta_yp2 - seta_yp1) * (seta_xp2 - seta_xp2) > len) { return; }
|
||||
|
||||
esp_lcd_panel_draw_bitmap(_panel_handle, seta_xp1, seta_yp1, seta_xp2, seta_yp2, (void *)data);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (not_swapped == false) {
|
||||
// called from LVGL bytes are swapped
|
||||
if (bpp != 16) {
|
||||
// lvgl_color_swap(data, len); -- no need to swap anymore, we have inverted the mask
|
||||
pushColorsMono(data, len, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( (col_mode != 18) && (spi_dc >= 0) && (spi_nr <= 2) ) {
|
||||
// special version 8 bit spi I or II
|
||||
#ifdef ESP8266
|
||||
lvgl_color_swap(data, len);
|
||||
while (len--) {
|
||||
uspi->write(*data++);
|
||||
}
|
||||
#else
|
||||
if (lvgl_param.use_dma) {
|
||||
pushPixelsDMA(data, len );
|
||||
} else {
|
||||
uspi->writeBytes((uint8_t*)data, len * 2);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
|
||||
#ifdef ESP32
|
||||
if ( (col_mode == 18) && (spi_dc >= 0) && (spi_nr <= 2) ) {
|
||||
uint8_t *line = (uint8_t*)malloc(len * 3);
|
||||
uint8_t *lp = line;
|
||||
if (line) {
|
||||
uint16_t color;
|
||||
for (uint32_t cnt = 0; cnt < len; cnt++) {
|
||||
color = *data++;
|
||||
color = (color << 8) | (color >> 8);
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
*lp++ = r;
|
||||
*lp++ = g;
|
||||
*lp++ = b;
|
||||
}
|
||||
|
||||
if (lvgl_param.use_dma) {
|
||||
pushPixels3DMA(line, len );
|
||||
} else {
|
||||
uspi->writeBytes(line, len * 3);
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 9 bit and others
|
||||
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
#ifdef USE_ESP32_S3
|
||||
pb_pushPixels(data, len, true, false);
|
||||
#endif // USE_ESP32_S3
|
||||
} else {
|
||||
lvgl_color_swap(data, len);
|
||||
while (len--) {
|
||||
WriteColor(*data++);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ESP32
|
||||
|
||||
#ifdef ESP8266
|
||||
lvgl_color_swap(data, len);
|
||||
while (len--) {
|
||||
WriteColor(*data++);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
// called from displaytext, no byte swap, currently no dma here
|
||||
|
||||
if (bpp != 16) {
|
||||
pushColorsMono(data, len);
|
||||
return;
|
||||
}
|
||||
if ( (col_mode != 18) && (spi_dc >= 0) && (spi_nr <= 2) ) {
|
||||
// special version 8 bit spi I or II
|
||||
#ifdef ESP8266
|
||||
while (len--) {
|
||||
//uspi->write(*data++);
|
||||
WriteColor(*data++);
|
||||
}
|
||||
#else
|
||||
uspi->writePixels(data, len * 2);
|
||||
#endif
|
||||
} else {
|
||||
// 9 bit and others
|
||||
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
#ifdef USE_ESP32_S3
|
||||
pb_pushPixels(data, len, false, false);
|
||||
#endif // USE_ESP32_S3
|
||||
} else {
|
||||
while (len--) {
|
||||
WriteColor(*data++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
universal_panel->pushColors(data, len, not_swapped);
|
||||
}
|
||||
|
||||
// convert to mono, these are framebuffer based
|
||||
@ -390,176 +104,34 @@ void uDisplay::pushColorsMono(uint16_t *data, uint16_t len, bool rgb16_swap) {
|
||||
}
|
||||
|
||||
void uDisplay::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
|
||||
if (bpp != 16 || interface == _UDSP_RGB) {
|
||||
// Just save params or update frame
|
||||
if (!x0 && !y0 && !x1 && !y1) {
|
||||
if (!ep_mode) {
|
||||
Updateframe();
|
||||
}
|
||||
} else {
|
||||
seta_xp1 = x0;
|
||||
seta_xp2 = x1;
|
||||
seta_yp1 = y0;
|
||||
seta_yp2 = y1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (interface == _UDSP_RGB) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!x0 && !y0 && !x1 && !y1) {
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
} else {
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
setAddrWindow_int(x0, y0, x1 - x0, y1 - y0);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
if (interface == _UDSP_RGB) {
|
||||
return;
|
||||
}
|
||||
|
||||
x += x_addr_offs[cur_rot];
|
||||
y += y_addr_offs[cur_rot];
|
||||
|
||||
if (sa_mode != 8) {
|
||||
uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
|
||||
uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);
|
||||
|
||||
ulcd_command(saw_1);
|
||||
ulcd_data32(xa);
|
||||
|
||||
ulcd_command(saw_2);
|
||||
ulcd_data32(ya);
|
||||
|
||||
if (saw_3 != 0xff) {
|
||||
ulcd_command(saw_3); // write to RAM
|
||||
}
|
||||
} else {
|
||||
uint16_t x2 = x + w - 1,
|
||||
y2 = y + h - 1;
|
||||
|
||||
if (cur_rot & 1) { // Vertical address increment mode
|
||||
renderer_swap(x,y);
|
||||
renderer_swap(x2,y2);
|
||||
}
|
||||
ulcd_command(saw_1);
|
||||
if (allcmd_mode) {
|
||||
ulcd_data8(x);
|
||||
ulcd_data8(x2);
|
||||
} else {
|
||||
ulcd_command(x);
|
||||
ulcd_command(x2);
|
||||
}
|
||||
ulcd_command(saw_2);
|
||||
if (allcmd_mode) {
|
||||
ulcd_data8(y);
|
||||
ulcd_data8(y2);
|
||||
} else {
|
||||
ulcd_command(y);
|
||||
ulcd_command(y2);
|
||||
}
|
||||
if (saw_3 != 0xff) {
|
||||
ulcd_command(saw_3); // write to RAM
|
||||
}
|
||||
}
|
||||
universal_panel->setAddrWindow(x0, y0, x1, y1);
|
||||
}
|
||||
|
||||
void uDisplay::setRotation(uint8_t rotation) {
|
||||
cur_rot = rotation;
|
||||
if (universal_panel->setRotation(rotation)) {
|
||||
// Update Renderer dimensions based on rotation
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
case 2:
|
||||
_width = gxs;
|
||||
_height = gys;
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
_width = gys;
|
||||
_height = gxs;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (framebuffer) {
|
||||
Renderer::setRotation(cur_rot);
|
||||
return;
|
||||
}
|
||||
|
||||
if (interface == _UDSP_SPI || interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
if (ep_mode) {
|
||||
Renderer::setRotation(cur_rot);
|
||||
return;
|
||||
}
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
ulcd_command(madctrl);
|
||||
|
||||
if (!allcmd_mode) {
|
||||
ulcd_data8(rot[cur_rot]);
|
||||
} else {
|
||||
ulcd_command(rot[cur_rot]);
|
||||
}
|
||||
|
||||
if ((sa_mode == 8) && !allcmd_mode) {
|
||||
ulcd_command(startline);
|
||||
ulcd_data8((cur_rot < 2) ? height() : 0);
|
||||
}
|
||||
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
_width = gxs;
|
||||
_height = gys;
|
||||
break;
|
||||
case 1:
|
||||
_width = gys;
|
||||
_height = gxs;
|
||||
break;
|
||||
case 2:
|
||||
_width = gxs;
|
||||
_height = gys;
|
||||
break;
|
||||
case 3:
|
||||
_width = gys;
|
||||
_height = gxs;
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_RGB) {
|
||||
// Utilize the ESP-IDF LCD driver's support for display rotation
|
||||
esp_lcd_panel_mirror(_panel_handle, rotation == 1 || rotation == 2, rotation & 2);
|
||||
esp_lcd_panel_swap_xy(_panel_handle, rotation & 1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
void uDisplay::drawPixel_RGB(int16_t x, int16_t y, uint16_t color) {
|
||||
int16_t w = _width, h = _height;
|
||||
|
||||
if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check rotation, move pixel around if necessary
|
||||
switch (cur_rot) {
|
||||
case 1:
|
||||
renderer_swap(w, h);
|
||||
renderer_swap(x, y);
|
||||
x = w - x - 1;
|
||||
break;
|
||||
case 2:
|
||||
x = w - x - 1;
|
||||
y = h - y - 1;
|
||||
break;
|
||||
case 3:
|
||||
renderer_swap(w, h);
|
||||
renderer_swap(x, y);
|
||||
y = h - y - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t *fb = rgb_fb;
|
||||
fb += (int32_t)y * w;
|
||||
fb += x;
|
||||
*fb = color;
|
||||
Cache_WriteBack_Addr((uint32_t)fb, 2);
|
||||
void uDisplay::Updateframe(void) {
|
||||
universal_panel->updateFrame();
|
||||
}
|
||||
#endif
|
||||
37
lib/lib_display/UDisplay/uDisplay_panel.h
Normal file
37
lib/lib_display/UDisplay/uDisplay_panel.h
Normal file
@ -0,0 +1,37 @@
|
||||
// ======================================================
|
||||
// uDisplay_panel.h - Base Panel Interface
|
||||
// ======================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
#include "esp_cache.h"
|
||||
#define CACHE_WRITEBACK_ADDR(addr, size) esp_cache_msync((void*)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_C2M)
|
||||
#else
|
||||
#define CACHE_WRITEBACK_ADDR(addr, size) Cache_WriteBack_Addr(addr, size)
|
||||
#endif
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class UniversalPanel {
|
||||
public:
|
||||
virtual ~UniversalPanel() {}
|
||||
|
||||
// Core graphics API - return true if handled, false for uDisplay fallback
|
||||
virtual bool drawPixel(int16_t x, int16_t y, uint16_t color) = 0;
|
||||
virtual bool fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) = 0;
|
||||
virtual bool pushColors(uint16_t *data, uint16_t len, bool first = false) = 0;
|
||||
virtual bool setAddrWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1) = 0;
|
||||
virtual bool drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) = 0;
|
||||
virtual bool drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) = 0;
|
||||
|
||||
// Control API - return true if handled, false for uDisplay fallback
|
||||
virtual bool displayOnff(int8_t on) = 0;
|
||||
virtual bool invertDisplay(bool invert) = 0;
|
||||
virtual bool setRotation(uint8_t rotation) = 0;
|
||||
|
||||
// Frame update method for displays that need explicit updates
|
||||
virtual bool updateFrame() = 0;
|
||||
// Framebuffer - own or external
|
||||
uint16_t* framebuffer = nullptr;
|
||||
};
|
||||
@ -1,142 +0,0 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
|
||||
#ifdef ESP32
|
||||
#include "esp8266toEsp32.h"
|
||||
#endif
|
||||
|
||||
#define WAIT_LCD_NOT_BUSY while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
|
||||
// ===== Parallel Bus Control Functions =====
|
||||
|
||||
void uDisplay::pb_beginTransaction(void) {
|
||||
auto dev = _dev;
|
||||
dev->lcd_clock.val = _clock_reg_value;
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
|
||||
dev->lcd_user.val = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG;
|
||||
_cache_flip = _cache[0];
|
||||
}
|
||||
|
||||
void uDisplay::pb_endTransaction(void) {
|
||||
auto dev = _dev;
|
||||
while (dev->lcd_user.val & LCD_CAM_LCD_START) {}
|
||||
}
|
||||
|
||||
void uDisplay::pb_wait(void) {
|
||||
auto dev = _dev;
|
||||
while (dev->lcd_user.val & LCD_CAM_LCD_START) {}
|
||||
}
|
||||
|
||||
bool uDisplay::pb_busy(void) {
|
||||
auto dev = _dev;
|
||||
return (dev->lcd_user.val & LCD_CAM_LCD_START);
|
||||
}
|
||||
|
||||
// ===== Parallel Bus Write Functions =====
|
||||
|
||||
bool uDisplay::pb_writeCommand(uint32_t data, uint_fast8_t bit_length) {
|
||||
auto dev = _dev;
|
||||
auto reg_lcd_user = &(dev->lcd_user.val);
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE | LCD_CAM_LCD_CD_CMD_SET;
|
||||
|
||||
if (interface == _UDSP_PAR8) {
|
||||
auto bytes = bit_length >> 3;
|
||||
do {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = data;
|
||||
data >>= 8;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
} while (--bytes);
|
||||
return true;
|
||||
} else {
|
||||
dev->lcd_cmd_val.val = data;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::pb_writeData(uint32_t data, uint_fast8_t bit_length) {
|
||||
auto dev = _dev;
|
||||
auto reg_lcd_user = &(dev->lcd_user.val);
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
|
||||
auto bytes = bit_length >> 3;
|
||||
|
||||
if (interface == _UDSP_PAR8) {
|
||||
uint8_t shift = (bytes - 1) * 8;
|
||||
for (uint32_t cnt = 0; cnt < bytes; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff;
|
||||
shift -= 8;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
if (bytes == 1 || bytes == 4) {
|
||||
uint8_t shift = (bytes - 1) * 8;
|
||||
for (uint32_t cnt = 0; cnt < bytes; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = (data >> shift) & 0xff;
|
||||
shift -= 8;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dev->lcd_cmd_val.val = data;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::pb_pushPixels(uint16_t* data, uint32_t length, bool swap_bytes, bool use_dma) {
|
||||
auto dev = _dev;
|
||||
auto reg_lcd_user = &(dev->lcd_user.val);
|
||||
dev->lcd_misc.val = LCD_CAM_LCD_CD_IDLE_EDGE;
|
||||
|
||||
if (interface == _UDSP_PAR8) {
|
||||
if (swap_bytes) {
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data >> 8;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
data++;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data >> 8;
|
||||
while (*reg_lcd_user & LCD_CAM_LCD_START) {}
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
data++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (swap_bytes) {
|
||||
uint16_t iob;
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
iob = *data++;
|
||||
iob = (iob << 8) | (iob >> 8);
|
||||
dev->lcd_cmd_val.lcd_cmd_value = iob;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t cnt = 0; cnt < length; cnt++) {
|
||||
dev->lcd_cmd_val.lcd_cmd_value = *data++;
|
||||
WAIT_LCD_NOT_BUSY
|
||||
*reg_lcd_user = LCD_CAM_LCD_2BYTE_EN | LCD_CAM_LCD_CMD | LCD_CAM_LCD_UPDATE_REG | LCD_CAM_LCD_START;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -1,154 +0,0 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
#include "uDisplay_spi.h"
|
||||
#ifndef ESP32
|
||||
#include "spi_register.h"
|
||||
#endif
|
||||
|
||||
// ===== Low-Level SPI Write Functions =====
|
||||
// ===== RA8876 Controller Commands =====
|
||||
static constexpr uint8_t RA8876_DATA_WRITE = 0x80;
|
||||
static constexpr uint8_t RA8876_DATA_READ = 0xC0;
|
||||
static constexpr uint8_t RA8876_CMD_WRITE = 0x00;
|
||||
static constexpr uint8_t RA8876_STATUS_READ = 0x40;
|
||||
|
||||
#ifdef ESP32
|
||||
void uDisplay::hw_write9(uint8_t val, uint8_t dc) {
|
||||
if (spi_dc < -1) {
|
||||
// RA8876 mode
|
||||
if (!dc) {
|
||||
uspi->write(RA8876_CMD_WRITE);
|
||||
uspi->write(val);
|
||||
} else {
|
||||
uspi->write(RA8876_DATA_WRITE);
|
||||
uspi->write(val);
|
||||
}
|
||||
} else {
|
||||
uint32_t regvalue = val >> 1;
|
||||
if (dc) regvalue |= 0x80;
|
||||
else regvalue &= 0x7f;
|
||||
if (val & 1) regvalue |= 0x8000;
|
||||
|
||||
REG_SET_BIT(SPI_USER_REG(3), SPI_USR_MOSI);
|
||||
REG_WRITE(SPI_MOSI_DLEN_REG(3), 9 - 1);
|
||||
uint32_t *dp = (uint32_t*)SPI_W0_REG(3);
|
||||
*dp = regvalue;
|
||||
REG_SET_BIT(SPI_CMD_REG(3), SPI_USR);
|
||||
while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR));
|
||||
}
|
||||
}
|
||||
#else
|
||||
void uDisplay::hw_write9(uint8_t val, uint8_t dc) {
|
||||
if (spi_dc < -1) {
|
||||
// RA8876 mode
|
||||
if (!dc) {
|
||||
uspi->write(RA8876_CMD_WRITE);
|
||||
uspi->write(val);
|
||||
} else {
|
||||
uspi->write(RA8876_DATA_WRITE);
|
||||
uspi->write(val);
|
||||
}
|
||||
} else {
|
||||
uint32_t regvalue;
|
||||
uint8_t bytetemp;
|
||||
if (!dc) {
|
||||
bytetemp = (val>> 1) & 0x7f;
|
||||
} else {
|
||||
bytetemp = (val >> 1) | 0x80;
|
||||
}
|
||||
regvalue = ((8 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | ((uint32)bytetemp);
|
||||
if (val & 0x01) regvalue |= BIT15;
|
||||
while (READ_PERI_REG(SPI_CMD(1)) & SPI_USR);
|
||||
WRITE_PERI_REG(SPI_USER2(1), regvalue);
|
||||
SET_PERI_REG_MASK(SPI_CMD(1), SPI_USR);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// ===== Software SPI (Slow) Functions =====
|
||||
|
||||
void uDisplay::write8(uint8_t val) {
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_clk);
|
||||
if (val & bit) GPIO_SET(spi_mosi);
|
||||
else GPIO_CLR(spi_mosi);
|
||||
GPIO_SET(spi_clk);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::write8_slow(uint8_t val) {
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR_SLOW(spi_clk);
|
||||
if (val & bit) GPIO_SET_SLOW(spi_mosi);
|
||||
else GPIO_CLR_SLOW(spi_mosi);
|
||||
GPIO_SET_SLOW(spi_clk);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::write9(uint8_t val, uint8_t dc) {
|
||||
GPIO_CLR(spi_clk);
|
||||
if (dc) GPIO_SET(spi_mosi);
|
||||
else GPIO_CLR(spi_mosi);
|
||||
GPIO_SET(spi_clk);
|
||||
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_clk);
|
||||
if (val & bit) GPIO_SET(spi_mosi);
|
||||
else GPIO_CLR(spi_mosi);
|
||||
GPIO_SET(spi_clk);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::write9_slow(uint8_t val, uint8_t dc) {
|
||||
GPIO_CLR_SLOW(spi_clk);
|
||||
if (dc) GPIO_SET_SLOW(spi_mosi);
|
||||
else GPIO_CLR_SLOW(spi_mosi);
|
||||
GPIO_SET_SLOW(spi_clk);
|
||||
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
GPIO_CLR_SLOW(spi_clk);
|
||||
if (val & bit) GPIO_SET_SLOW(spi_mosi);
|
||||
else GPIO_CLR_SLOW(spi_mosi);
|
||||
GPIO_SET_SLOW(spi_clk);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::write16(uint16_t val) {
|
||||
for (uint16_t bit = 0x8000; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_clk);
|
||||
if (val & bit) GPIO_SET(spi_mosi);
|
||||
else GPIO_CLR(spi_mosi);
|
||||
GPIO_SET(spi_clk);
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::write32(uint32_t val) {
|
||||
for (uint32_t bit = 0x80000000; bit; bit >>= 1) {
|
||||
GPIO_CLR(spi_clk);
|
||||
if (val & bit) GPIO_SET(spi_mosi);
|
||||
else GPIO_CLR(spi_mosi);
|
||||
GPIO_SET(spi_clk);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== RA8876 Specific Functions =====
|
||||
|
||||
uint8_t uDisplay::writeReg16(uint8_t reg, uint16_t wval) {
|
||||
hw_write9(reg, 0);
|
||||
hw_write9(wval, 1);
|
||||
hw_write9(reg + 1, 0);
|
||||
hw_write9(wval >> 8, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t uDisplay::readData(void) {
|
||||
uspi->write(RA8876_DATA_READ);
|
||||
uint8_t val = uspi->transfer(0);
|
||||
return val;
|
||||
}
|
||||
|
||||
uint8_t uDisplay::readStatus(void) {
|
||||
uspi->write(RA8876_STATUS_READ);
|
||||
uint8_t val = uspi->transfer(0);
|
||||
return val;
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
#ifndef UDISPLAY_SPI_LOWLEVEL_H
|
||||
#define UDISPLAY_SPI_LOWLEVEL_H
|
||||
|
||||
#include "uDisplay_config.h"
|
||||
|
||||
// ===== SPI Platform-Specific Includes =====
|
||||
#ifdef ESP32
|
||||
#include "soc/spi_reg.h"
|
||||
#include "soc/spi_struct.h"
|
||||
#include "esp32-hal-spi.h"
|
||||
#include "esp32-hal.h"
|
||||
#include "soc/spi_struct.h"
|
||||
#endif
|
||||
|
||||
// ===== GPIO Control Macros =====
|
||||
|
||||
#ifdef ESP8266
|
||||
#define PIN_OUT_SET 0x60000304
|
||||
#define PIN_OUT_CLEAR 0x60000308
|
||||
#define GPIO_SET(A) WRITE_PERI_REG( PIN_OUT_SET, 1 << A)
|
||||
#define GPIO_CLR(A) WRITE_PERI_REG( PIN_OUT_CLEAR, 1 << A)
|
||||
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
|
||||
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
|
||||
#else // ESP32
|
||||
#undef GPIO_SET
|
||||
#undef GPIO_CLR
|
||||
#undef GPIO_SET_SLOW
|
||||
#undef GPIO_CLR_SLOW
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32P4
|
||||
#define GPIO_CLR(A) GPIO.out_w1tc.val = (1 << A)
|
||||
#define GPIO_SET(A) GPIO.out_w1ts.val = (1 << A)
|
||||
#else // plain ESP32
|
||||
#define GPIO_CLR(A) GPIO.out_w1tc = (1 << A)
|
||||
#define GPIO_SET(A) GPIO.out_w1ts = (1 << A)
|
||||
#endif
|
||||
|
||||
#define GPIO_CLR_SLOW(A) digitalWrite(A, LOW)
|
||||
#define GPIO_SET_SLOW(A) digitalWrite(A, HIGH)
|
||||
#endif
|
||||
|
||||
// ===== SPI Transaction Control Macros =====
|
||||
#define SPI_BEGIN_TRANSACTION if (spi_nr <= 2) beginTransaction(spiSettings);
|
||||
#define SPI_END_TRANSACTION if (spi_nr <= 2) endTransaction();
|
||||
|
||||
#define SPI_CS_LOW if (spi_cs >= 0) GPIO_CLR_SLOW(spi_cs);
|
||||
#define SPI_CS_HIGH if (spi_cs >= 0) GPIO_SET_SLOW(spi_cs);
|
||||
#define SPI_DC_LOW if (spi_dc >= 0) GPIO_CLR_SLOW(spi_dc);
|
||||
#define SPI_DC_HIGH if (spi_dc >= 0) GPIO_SET_SLOW(spi_dc);
|
||||
|
||||
// ===== Function Declarations =====
|
||||
// These would typically be in the class declaration in uDisplay.h
|
||||
// but we list them here for reference:
|
||||
|
||||
/*
|
||||
// Low-Level SPI Write Functions
|
||||
void hw_write9(uint8_t val, uint8_t dc);
|
||||
void write8(uint8_t val);
|
||||
void write8_slow(uint8_t val);
|
||||
void write9(uint8_t val, uint8_t dc);
|
||||
void write9_slow(uint8_t val, uint8_t dc);
|
||||
void write16(uint16_t val);
|
||||
void write32(uint32_t val);
|
||||
|
||||
// RA8876 Specific Functions
|
||||
uint8_t writeReg16(uint8_t reg, uint16_t wval);
|
||||
uint8_t readData(void);
|
||||
uint8_t readStatus(void);
|
||||
*/
|
||||
|
||||
#endif // UDISPLAY_SPI_LOWLEVEL_H
|
||||
@ -1,158 +0,0 @@
|
||||
#include "uDisplay.h"
|
||||
#include "uDisplay_config.h"
|
||||
#include "uDisplay_spi.h" // Your SPI header
|
||||
|
||||
// ===== High-Level SPI Communication Functions =====
|
||||
|
||||
void uDisplay::ulcd_command(uint8_t val) {
|
||||
if (interface == _UDSP_SPI) {
|
||||
if (spi_dc < 0) {
|
||||
if (spi_nr > 2) {
|
||||
if (spi_nr == 3) {
|
||||
write9(val, 0);
|
||||
} else {
|
||||
write9_slow(val, 0);
|
||||
}
|
||||
} else {
|
||||
hw_write9(val, 0);
|
||||
}
|
||||
} else {
|
||||
SPI_DC_LOW
|
||||
if (spi_nr > 2) {
|
||||
if (spi_nr == 3) {
|
||||
write8(val);
|
||||
} else {
|
||||
write8_slow(val);
|
||||
}
|
||||
} else {
|
||||
uspi->write(val);
|
||||
}
|
||||
SPI_DC_HIGH
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
pb_writeCommand(val, 8);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void uDisplay::ulcd_data8(uint8_t val) {
|
||||
if (interface == _UDSP_SPI) {
|
||||
if (spi_dc < 0) {
|
||||
if (spi_nr > 2) {
|
||||
if (spi_nr == 3) {
|
||||
write9(val, 1);
|
||||
} else {
|
||||
write9_slow(val, 1);
|
||||
}
|
||||
} else {
|
||||
hw_write9(val, 1);
|
||||
}
|
||||
} else {
|
||||
if (spi_nr > 2) {
|
||||
if (spi_nr == 3) {
|
||||
write8(val);
|
||||
} else {
|
||||
write8_slow(val);
|
||||
}
|
||||
} else {
|
||||
uspi->write(val);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
pb_writeData(val, 8);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void uDisplay::ulcd_data16(uint16_t val) {
|
||||
if (interface == _UDSP_SPI) {
|
||||
if (spi_dc < 0) {
|
||||
if (spi_nr > 2) {
|
||||
write9(val >> 8, 1);
|
||||
write9(val, 1);
|
||||
} else {
|
||||
hw_write9(val >> 8, 1);
|
||||
hw_write9(val, 1);
|
||||
}
|
||||
} else {
|
||||
if (spi_nr > 2) {
|
||||
write16(val);
|
||||
} else {
|
||||
uspi->write16(val);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
pb_writeData(val, 16);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void uDisplay::ulcd_data32(uint32_t val) {
|
||||
if (interface == _UDSP_SPI) {
|
||||
if (spi_dc < 0) {
|
||||
if (spi_nr > 2) {
|
||||
write9(val >> 24, 1);
|
||||
write9(val >> 16, 1);
|
||||
write9(val >> 8, 1);
|
||||
write9(val, 1);
|
||||
} else {
|
||||
hw_write9(val >> 24, 1);
|
||||
hw_write9(val >> 16, 1);
|
||||
hw_write9(val >> 8, 1);
|
||||
hw_write9(val, 1);
|
||||
}
|
||||
} else {
|
||||
if (spi_nr > 2) {
|
||||
write32(val);
|
||||
} else {
|
||||
uspi->write32(val);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_S3
|
||||
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
|
||||
pb_writeData(val, 32);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void uDisplay::ulcd_command_one(uint8_t val) {
|
||||
if (interface == _UDSP_SPI) {
|
||||
SPI_BEGIN_TRANSACTION
|
||||
SPI_CS_LOW
|
||||
ulcd_command(val);
|
||||
SPI_CS_HIGH
|
||||
SPI_END_TRANSACTION
|
||||
}
|
||||
}
|
||||
|
||||
void uDisplay::WriteColor(uint16_t color) {
|
||||
if (col_mode == 18) {
|
||||
uint8_t r = (color & 0xF800) >> 11;
|
||||
uint8_t g = (color & 0x07E0) >> 5;
|
||||
uint8_t b = color & 0x001F;
|
||||
r = (r * 255) / 31;
|
||||
g = (g * 255) / 63;
|
||||
b = (b * 255) / 31;
|
||||
|
||||
ulcd_data8(r);
|
||||
ulcd_data8(g);
|
||||
ulcd_data8(b);
|
||||
} else {
|
||||
ulcd_data16(color);
|
||||
}
|
||||
}
|
||||
@ -24,37 +24,3 @@ void uDisplay::reset_pin(int32_t msl, int32_t msh) {
|
||||
delay(msh);
|
||||
}
|
||||
}
|
||||
#define UDSP_BUSY_TIMEOUT 3000
|
||||
|
||||
void uDisplay::delay_sync(int32_t ms) {
|
||||
uint8_t busy_level = HIGH;
|
||||
if (lvgl_param.busy_invert) {
|
||||
busy_level = LOW;
|
||||
}
|
||||
uint32_t time = millis();
|
||||
if (busy_pin > 0) {
|
||||
while (digitalRead(busy_pin) == busy_level) {
|
||||
delay(1);
|
||||
if ((millis() - time) > UDSP_BUSY_TIMEOUT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delay(ms);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== SPI Transaction Control =====
|
||||
|
||||
void uDisplay::beginTransaction(SPISettings s) {
|
||||
#ifdef ESP32
|
||||
if (lvgl_param.use_dma) {
|
||||
dmaWait();
|
||||
}
|
||||
#endif
|
||||
uspi->beginTransaction(s);
|
||||
}
|
||||
|
||||
void uDisplay::endTransaction(void) {
|
||||
uspi->endTransaction();
|
||||
}
|
||||
@ -32,10 +32,14 @@ bool uDisplay::utouch_Init(char **name) {
|
||||
attachInterrupt(ut_irq, ut_touch_irq, FALLING);
|
||||
}
|
||||
|
||||
if (ut_spi_nr == spi_nr) {
|
||||
// same as display
|
||||
ut_spi = uspi;
|
||||
if (ut_wire) {
|
||||
// I2C touch - no SPI needed
|
||||
ut_spi = nullptr;
|
||||
} else if (spiController && ut_spi_nr == spiController->spi_config.bus_nr) {
|
||||
// SPI touch using same bus as display
|
||||
ut_spi = spiController->getSPI();
|
||||
} else {
|
||||
// SPI touch using different bus or display doesn't use SPI
|
||||
#ifdef ESP32
|
||||
ut_spi = SpiBegin(ut_spi_nr);
|
||||
#endif
|
||||
@ -406,9 +410,13 @@ int16_t uDisplay::ut_execute(uint8_t *ut_code) {
|
||||
break;
|
||||
|
||||
case UT_GSRT:
|
||||
#ifdef USE_ESP32_S3
|
||||
#ifdef UDISPLAY_I80
|
||||
{
|
||||
uint32_t val = get_sr_touch(SIMPLERS_XP, SIMPLERS_XM, SIMPLERS_YP, SIMPLERS_YM);
|
||||
// Simple resistive touch using I80 data pins
|
||||
uint32_t val = get_sr_touch(panel_config->i80.data_pins_low[1], // XP
|
||||
panel_config->i80.cs_pin, // XM
|
||||
panel_config->i80.dc_pin, // YP
|
||||
panel_config->i80.data_pins_low[0]); // YM
|
||||
if (val == 0) {
|
||||
return false;
|
||||
}
|
||||
@ -426,7 +434,7 @@ int16_t uDisplay::ut_execute(uint8_t *ut_code) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // USE_ESP32_S3
|
||||
#endif // UDISPLAY_I80
|
||||
break;
|
||||
|
||||
case UT_XPT:
|
||||
|
||||
@ -106,6 +106,6 @@ void uDisplay::TS_RotConvert(int16_t *x, int16_t *y) {
|
||||
|
||||
// ===== Color Conversion Helper =====
|
||||
|
||||
static inline void lvgl_color_swap(uint16_t *data, uint16_t len) {
|
||||
for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8);
|
||||
}
|
||||
// static inline void lvgl_color_swap(uint16_t *data, uint16_t len) {
|
||||
// for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8);
|
||||
// }
|
||||
48
tasmota/displaydesc/EK79007_P4ev_DSI.ini
Normal file
48
tasmota/displaydesc/EK79007_P4ev_DSI.ini
Normal file
@ -0,0 +1,48 @@
|
||||
:H,EK79007,1024,600,16,DSI,2,-1,26,27,3,2500,52000000,900,0,0 ; DisplayName,Width,Height,BPP,Interface,DSI_Lanes,TE_Pin,Backlight_Pin,Reset_Pin,LDO_Chan,LDO_mV,PixelClock_Hz,LaneSpeed_Mbps,RGB_Order,Endian
|
||||
:V,160,12,160,10,1,23 ; ^H_FP ^V_FP ^H_BP ^H_SW ^V_SW ^V_BP
|
||||
:B,60,01
|
||||
:I
|
||||
B2,01,10,00
|
||||
80,01,8B,00
|
||||
81,01,78,00
|
||||
82,01,84,00
|
||||
83,01,88,00
|
||||
84,01,A8,00
|
||||
85,01,E3,00
|
||||
86,01,88,00
|
||||
11,01,00,78
|
||||
29,01,00,32
|
||||
:o,28
|
||||
:O,29
|
||||
:i,20,21
|
||||
:D,51
|
||||
:UTI,GT911,I1,5d,-1,-1
|
||||
RDWM 8140 4
|
||||
MV 0 1
|
||||
CPR 39
|
||||
RTF
|
||||
MV 1 1
|
||||
CPR 31
|
||||
RTF
|
||||
MV 2 1
|
||||
CPR 31
|
||||
RTF
|
||||
RT
|
||||
:UTT
|
||||
RDW 814E
|
||||
MV 0 1
|
||||
AND 80
|
||||
CPR 80
|
||||
RTF
|
||||
RDWM 8150 8
|
||||
WRW 814E 00
|
||||
RT
|
||||
:UTX
|
||||
MV 0 3
|
||||
SCL 1024 -1
|
||||
RT
|
||||
:UTY
|
||||
MV 2 3
|
||||
SCL 600 -1
|
||||
RT
|
||||
#
|
||||
88
tasmota/displaydesc/JD9165_DSI.ini
Normal file
88
tasmota/displaydesc/JD9165_DSI.ini
Normal file
@ -0,0 +1,88 @@
|
||||
:H,JD9165,1024,600,16,DSI,2,-1,23,27,3,2500,56000000,750,0,0 ; DisplayName,Width,Height,BPP,Interface,DSI_Lanes,TE_Pin,Backlight_Pin,Reset_Pin,LDO_Chan,LDO_mV,PixelClock_Hz,LaneSpeed_Mbps,RGB_Order,Endian
|
||||
:V,160,12,160,40,10,23 ; ^H_FP ^V_FP ^H_BP ^H_SW ^V_SW ^V_BP
|
||||
:B,60,01
|
||||
:I
|
||||
30,01,00,00
|
||||
F7,04,49,61,02,00,00
|
||||
30,01,01,00
|
||||
04,01,0C,00
|
||||
05,01,00,00
|
||||
06,01,00,00
|
||||
0B,01,11,00
|
||||
17,01,00,00
|
||||
20,01,04,00
|
||||
1F,01,05,00
|
||||
23,01,00,00
|
||||
25,01,19,00
|
||||
28,01,18,00
|
||||
29,01,04,00
|
||||
2A,01,01,00
|
||||
2B,01,04,00
|
||||
2C,01,01,00
|
||||
30,01,02,00
|
||||
01,01,22,00
|
||||
03,01,12,00
|
||||
04,01,00,00
|
||||
05,01,64,00
|
||||
0A,01,08,00
|
||||
0B,0B,0A,1A,0B,0D,0D,11,10,06,08,1F,1D,00
|
||||
0C,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00
|
||||
0D,0B,16,1B,0B,0D,0D,11,10,07,09,1E,1C,00
|
||||
0E,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00
|
||||
0F,0B,16,1B,0D,0B,0D,11,10,1C,1E,09,07,00
|
||||
10,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00
|
||||
11,0B,0A,1A,0D,0B,0D,11,10,1D,1F,08,06,00
|
||||
12,0B,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,0D,00
|
||||
14,04,00,00,11,11,00
|
||||
18,01,99,00
|
||||
30,01,06,00
|
||||
12,0E,36,2C,2E,3C,38,35,35,32,2E,1D,2B,21,16,29,00
|
||||
13,0E,36,2C,2E,3C,38,35,35,32,2E,1D,2B,21,16,29,00
|
||||
30,01,0A,00
|
||||
02,01,4F,00
|
||||
0B,01,40,00
|
||||
12,01,3E,00
|
||||
13,01,78,00
|
||||
30,01,0D,00
|
||||
0D,01,04,00
|
||||
10,01,0C,00
|
||||
11,01,0C,00
|
||||
12,01,0C,00
|
||||
13,01,0C,00
|
||||
30,01,00,00
|
||||
11,01,00,78
|
||||
29,01,00,32
|
||||
:o,28
|
||||
:O,29
|
||||
:i,20,21
|
||||
:D,51
|
||||
:UTI,GT911,I1,5d,22,21
|
||||
RDWM 8140 4
|
||||
MV 0 1
|
||||
CPR 39
|
||||
RTF
|
||||
MV 1 1
|
||||
CPR 31
|
||||
RTF
|
||||
MV 2 1
|
||||
CPR 31
|
||||
RTF
|
||||
RT
|
||||
:UTT
|
||||
RDW 814E
|
||||
MV 0 1
|
||||
AND 80
|
||||
CPR 80
|
||||
RTF
|
||||
RDWM 8150 8
|
||||
WRW 814E 00
|
||||
RT
|
||||
:UTX
|
||||
MV 0 3
|
||||
SCL 0 1.281
|
||||
RT
|
||||
:UTY
|
||||
MV 2 3
|
||||
SCL 0 1.2525
|
||||
RT
|
||||
#
|
||||
Loading…
Reference in New Issue
Block a user