diff options
Diffstat (limited to 'main/display.c')
| -rw-r--r-- | main/display.c | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/main/display.c b/main/display.c new file mode 100644 index 0000000..a1a64fd --- /dev/null +++ b/main/display.c @@ -0,0 +1,275 @@ +#include "display.h" + +#include <inttypes.h> +#include <math.h> +#include <string.h> + +#include "driver/i2c_master.h" +#include "esp_check.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_ops.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "viewport_state.h" + +static const char *TAG = "display"; + +// ============================================================================ +// Pin / bus assignments +// +// TODO confirm against the Waveshare ESP32-P4-ETH schematic and the +// 15-pin Pi FPC → 22-pin Waveshare DSI adapter. The two I2C lines below +// must reach the Hosyond panel's 15-pin FPC at the standard Pi positions. +// Waveshare's bundled-panel BSP uses these GPIOs for touch I2C; whether +// they also carry through their DSI connector to the FPC is unconfirmed. +// ============================================================================ +#define PIN_I2C_SDA 7 +#define PIN_I2C_SCL 8 +#define I2C_PORT I2C_NUM_0 +#define I2C_FREQ_HZ 100000 // panel MCU is slow; 100kHz is safe + +// Panel I2C addresses (Pi 7" touchscreen architecture, replicated by +// Hosyond/Elecrow/etc. clones for dtoverlay=vc4-kms-dsi-7inch). +#define PANEL_MCU_ADDR 0x45 +#define TOUCH_FT5426_ADDR 0x38 + +// Pi panel MCU register map. See Linux: +// drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c +enum { + REG_ID = 0x80, // reads back 0xC3 on a healthy panel + REG_PORTA = 0x81, + REG_PORTB = 0x82, + REG_PORTC = 0x83, + REG_PORTD = 0x84, + REG_POWERON = 0x85, // write 1 to power up; 0 to power down + REG_PWM = 0x86, // 0..255 backlight duty + REG_ADDR_H = 0x87, + REG_ADDR_L = 0x88, + REG_WRITE_H = 0x89, + REG_WRITE_L = 0x8a, + REG_READ_H = 0x8b, + REG_READ_L = 0x8c, +}; + +#define PANEL_ID_EXPECTED 0xC3 +#define PANEL_POWERON_DELAY_MS 120 // empirically ~80ms; pad to be safe + +// ============================================================================ +// DSI / DPI panel timing for 800x480 @ 60Hz +// (Raspberry Pi 7" canonical timings — same panel architecture as Hosyond 5") +// ============================================================================ +#define PANEL_H_ACTIVE 800 +#define PANEL_V_ACTIVE 480 +#define PANEL_HSYNC_PULSE 18 +#define PANEL_HSYNC_BP 20 +#define PANEL_HSYNC_FP 62 +#define PANEL_VSYNC_PULSE 4 +#define PANEL_VSYNC_BP 27 +#define PANEL_VSYNC_FP 18 +#define PANEL_PIXEL_CLOCK_MHZ 30 +#define DSI_LANE_RATE_MBPS 480 +#define DSI_NUM_DATA_LANES 2 + +// ============================================================================ +// Module state +// ============================================================================ +static i2c_master_bus_handle_t s_i2c_bus; +static i2c_master_dev_handle_t s_panel_mcu; +static esp_lcd_dsi_bus_handle_t s_dsi_bus; +static esp_lcd_panel_handle_t s_panel; +static bool s_up; +static uint8_t s_last_pwm; + +// ============================================================================ +// Panel-MCU I2C helpers +// ============================================================================ +static esp_err_t mcu_write_u8(uint8_t reg, uint8_t val) +{ + uint8_t buf[2] = { reg, val }; + return i2c_master_transmit(s_panel_mcu, buf, sizeof(buf), 50); +} + +static esp_err_t mcu_read_u8(uint8_t reg, uint8_t *out) +{ + return i2c_master_transmit_receive(s_panel_mcu, ®, 1, out, 1, 50); +} + +static esp_err_t panel_mcu_attach(void) +{ + i2c_master_bus_config_t bus_cfg = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = I2C_PORT, + .scl_io_num = PIN_I2C_SCL, + .sda_io_num = PIN_I2C_SDA, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_RETURN_ON_ERROR(i2c_new_master_bus(&bus_cfg, &s_i2c_bus), + TAG, "i2c_new_master_bus"); + + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = PANEL_MCU_ADDR, + .scl_speed_hz = I2C_FREQ_HZ, + }; + ESP_RETURN_ON_ERROR( + i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &s_panel_mcu), + TAG, "i2c add device 0x45"); + + uint8_t id = 0; + esp_err_t err = mcu_read_u8(REG_ID, &id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "panel MCU @0x45 unreachable (%s) — check FPC + adapter", + esp_err_to_name(err)); + return err; + } + if (id != PANEL_ID_EXPECTED) { + ESP_LOGW(TAG, "panel MCU id 0x%02x (expected 0x%02x) — wiring " + "looks ok but panel might be a different rev", + id, PANEL_ID_EXPECTED); + // Continue anyway; some clones report different IDs. + } else { + ESP_LOGI(TAG, "panel MCU id 0x%02x — Pi 7\" architecture ack'd", id); + } + return ESP_OK; +} + +static esp_err_t panel_power_on(void) +{ + ESP_RETURN_ON_ERROR(mcu_write_u8(REG_POWERON, 1), TAG, "POWERON=1"); + vTaskDelay(pdMS_TO_TICKS(PANEL_POWERON_DELAY_MS)); + ESP_LOGI(TAG, "panel powered on"); + return ESP_OK; +} + +// ============================================================================ +// DSI bring-up (DPI video mode — Pi-style panels need no DSI command init) +// ============================================================================ +static esp_err_t dsi_bring_up(void) +{ + esp_lcd_dsi_bus_config_t bus_cfg = { + .bus_id = 0, + .num_data_lanes = DSI_NUM_DATA_LANES, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = DSI_LANE_RATE_MBPS, + }; + ESP_RETURN_ON_ERROR(esp_lcd_new_dsi_bus(&bus_cfg, &s_dsi_bus), + TAG, "esp_lcd_new_dsi_bus"); + + esp_lcd_dpi_panel_config_t dpi_cfg = { + .virtual_channel = 0, + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = PANEL_PIXEL_CLOCK_MHZ, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 1, + .video_timing = { + .h_size = PANEL_H_ACTIVE, + .v_size = PANEL_V_ACTIVE, + .hsync_pulse_width = PANEL_HSYNC_PULSE, + .hsync_back_porch = PANEL_HSYNC_BP, + .hsync_front_porch = PANEL_HSYNC_FP, + .vsync_pulse_width = PANEL_VSYNC_PULSE, + .vsync_back_porch = PANEL_VSYNC_BP, + .vsync_front_porch = PANEL_VSYNC_FP, + }, + .flags.use_dma2d = true, + }; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_dpi(s_dsi_bus, &dpi_cfg, &s_panel), + TAG, "esp_lcd_new_panel_dpi"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_init(s_panel), TAG, "panel_init"); + + ESP_LOGI(TAG, "DSI up: %dx%d %d MHz, %d-lane %d Mbps", + PANEL_H_ACTIVE, PANEL_V_ACTIVE, PANEL_PIXEL_CLOCK_MHZ, + DSI_NUM_DATA_LANES, DSI_LANE_RATE_MBPS); + return ESP_OK; +} + +// ============================================================================ +// Public API +// ============================================================================ +esp_err_t display_init(void) +{ + if (s_up) return ESP_OK; + + ESP_RETURN_ON_ERROR(panel_mcu_attach(), TAG, "panel MCU attach"); + ESP_RETURN_ON_ERROR(panel_power_on(), TAG, "panel power-on"); + ESP_RETURN_ON_ERROR(dsi_bring_up(), TAG, "DSI bring-up"); + + s_up = true; + + // Default brightness from shared state (set by /config in M4; 80 on + // first boot). + viewport_state_lock(); + uint8_t b = viewport_state_get()->brightness; + viewport_state_unlock(); + display_set_brightness(b); + + return ESP_OK; +} + +bool display_is_up(void) { return s_up; } + +esp_err_t display_set_brightness(uint8_t pct) +{ + if (pct > 100) pct = 100; + // Perceptual: duty = (pct/100)^2.2 * 255. + float frac = (float)pct / 100.0f; + float gamma = powf(frac, 2.2f); + uint8_t duty = (uint8_t)(gamma * 255.0f + 0.5f); + + s_last_pwm = duty; + if (!s_up) return ESP_ERR_INVALID_STATE; + return mcu_write_u8(REG_PWM, duty); +} + +esp_err_t display_sleep(void) +{ + if (!s_up) return ESP_ERR_INVALID_STATE; + return mcu_write_u8(REG_PWM, 0); +} + +esp_err_t display_wake(void) +{ + if (!s_up) return ESP_ERR_INVALID_STATE; + return mcu_write_u8(REG_PWM, s_last_pwm); +} + +esp_err_t display_fill(uint16_t rgb565) +{ + if (!s_up) return ESP_ERR_INVALID_STATE; + void *fb = NULL; + ESP_RETURN_ON_ERROR(esp_lcd_dpi_panel_get_frame_buffer(s_panel, 1, &fb), + TAG, "get frame buffer"); + uint16_t *px = (uint16_t *)fb; + size_t n = PANEL_H_ACTIVE * PANEL_V_ACTIVE; + for (size_t i = 0; i < n; ++i) px[i] = rgb565; + return esp_lcd_panel_draw_bitmap(s_panel, 0, 0, + PANEL_H_ACTIVE, PANEL_V_ACTIVE, fb); +} + +esp_err_t display_test_pattern(void) +{ + if (!s_up) return ESP_ERR_INVALID_STATE; + void *fb = NULL; + ESP_RETURN_ON_ERROR(esp_lcd_dpi_panel_get_frame_buffer(s_panel, 1, &fb), + TAG, "get frame buffer"); + uint16_t *px = (uint16_t *)fb; + + // 8 vertical color bars: white, yellow, cyan, green, magenta, red, blue, black. + static const uint16_t bars[8] = { + 0xFFFF, 0xFFE0, 0x07FF, 0x07E0, + 0xF81F, 0xF800, 0x001F, 0x0000, + }; + const int bar_w = PANEL_H_ACTIVE / 8; + for (int y = 0; y < PANEL_V_ACTIVE; ++y) { + for (int x = 0; x < PANEL_H_ACTIVE; ++x) { + int b = x / bar_w; + if (b > 7) b = 7; + px[y * PANEL_H_ACTIVE + x] = bars[b]; + } + } + return esp_lcd_panel_draw_bitmap(s_panel, 0, 0, + PANEL_H_ACTIVE, PANEL_V_ACTIVE, fb); +} |
