src.nth.io/

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
l---------.#README.md1
-rw-r--r--main/CMakeLists.txt3
-rw-r--r--main/app_main.c11
-rw-r--r--main/display.c275
-rw-r--r--main/display.h30
5 files changed, 318 insertions, 2 deletions
diff --git a/.#README.md b/.#README.md
new file mode 120000
index 0000000..4ecc514
--- /dev/null
+++ b/.#README.md
@@ -0,0 +1 @@
[email protected]:1776223810 \ No newline at end of file
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index ad43a53..90a4c83 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -1,5 +1,6 @@
idf_component_register(
SRC_DIRS "."
INCLUDE_DIRS "."
- REQUIRES esp_eth esp_event esp_netif esp_http_server esp_timer json mdns nvs_flash
+ REQUIRES driver esp_driver_i2c esp_eth esp_event esp_http_server
+ esp_lcd esp_netif esp_timer json mdns nvs_flash
)
diff --git a/main/app_main.c b/main/app_main.c
index 9389642..c3c12c0 100644
--- a/main/app_main.c
+++ b/main/app_main.c
@@ -1,3 +1,4 @@
+#include "display.h"
#include "http_api.h"
#include "mdns_service.h"
#include "net_eth.h"
@@ -29,7 +30,15 @@ void app_main(void)
ESP_ERROR_CHECK(mdns_service_start());
ESP_ERROR_CHECK(http_api_start());
- // TODO M3: MIPI-DSI panel init (800x480 IPS, default portrait 480x800)
+ // Display is best-effort during bring-up: a missing/miswired panel must
+ // not kill networking + /state. M3 acceptance: show a test pattern.
+ if (display_init() == ESP_OK) {
+ display_test_pattern();
+ ESP_LOGI(TAG, "display up — test pattern on screen");
+ } else {
+ ESP_LOGW(TAG, "display init failed — continuing without panel");
+ }
+
// TODO M4: /config persistence (NVS) + GET /config + POST /config
// TODO M5: /frame JPEG decode -> framebuffer
// TODO M6: POST /state + idle timer
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, &reg, 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);
+}
diff --git a/main/display.h b/main/display.h
new file mode 100644
index 0000000..58e71b7
--- /dev/null
+++ b/main/display.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "esp_err.h"
+
+// Initialize the panel-side MCU over I2C, then bring up the DSI link in
+// video (DPI) mode. Safe to call when the panel is not attached — returns
+// an error and lets the rest of the firmware keep running.
+esp_err_t display_init(void);
+
+bool display_is_up(void);
+
+// 0–100, gamma-corrected to 0–255 on the panel-MCU PWM register.
+// brightness=0 is fully off (backlight enable line de-asserted).
+esp_err_t display_set_brightness(uint8_t brightness_0_100);
+
+// Sleep / wake hooks for the wake/sleep state machine (M6).
+// Sleep cuts backlight; wake restores last brightness.
+esp_err_t display_sleep(void);
+esp_err_t display_wake(void);
+
+// Paint a solid RGB565 color to the entire framebuffer + flush.
+// Used by M3 for the test pattern and by local_screens for the IP and
+// loading screens (M8).
+esp_err_t display_fill(uint16_t rgb565);
+
+// Show a deterministic test pattern (vertical color bars). M3 acceptance.
+esp_err_t display_test_pattern(void);