rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
display.c File Reference

The EK79007 panel on the ESP32-P4's DSI host, written from the EK79007 datasheet and ESP-IDF's esp_lcd headers. More...

#include "display.h"
#include <inttypes.h>
#include <stdatomic.h>
#include <string.h>
#include "board.h"
#include "board_pins.h"
#include "driver/gpio.h"
#include "driver/ppa.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_ldo_regulator.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "gfx_turn.h"

Data Structures

struct  ek_reg_t
 One register write: a command and its one parameter byte. More...
struct  display_state_t
 The driver's state; all zero while the display is down. More...

Macros

#define DISPLAY_NUM_FBS   2
 Two frame buffers, always: the DPI panel scans one while the CPU draws the other, and display_flip() hands the finished one over.
#define FLIP_WAIT_MS   200
 The longest display_flip() waits for a frame-done event.
#define FB_BYTES   ((size_t)BOARD_LCD_H_RES * BOARD_LCD_V_RES * sizeof(gfx_color_t))
 One frame buffer's bytes: 1024 x 600 x 2, 1,228,800.
#define LFB_ALIGN   128
 The logical buffers' alignment: the PSRAM's L2 cache line, 128 bytes.
#define RESET_LOW_MS   10
 The panel's reset: low for 10ms (the EK79007 needs 1ms), then 20ms before the first command (the datasheet states no minimum).
#define RESET_WAIT_MS   20
 Wait after the reset, ms: no minimum is stated in the datasheet.
#define SLEEP_OUT_MS   120
 After sleep out (0x11): the EK79007 takes the next command after 5ms and shows a picture about 7 frames later. 120ms covers both.
#define EK_CMD_SLEEP_OUT   0x11
 DCS (Display Command Set) commands, EK79007 register list.
#define EK_CMD_ADDRESS_MODE   0x36
#define EK_CMD_PANEL_CTRL2   0xB2

Functions

static bool on_frame_done (esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
 Frame-done callback: a frame buffer's scan ended. ISR (interrupt service routine) context.
static bool on_vsync (esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
 VSYNC callback: count it and wake display_wait_vsync(). ISR context.
static void rebind_canvas (void)
 Point the canvas at the UI's area of the back buffer, or of its logical buffer while turned, and start the frame's damage record.
static bool on_ppa_done (ppa_client_handle_t client, ppa_event_data_t *edata, void *user_ctx)
 PPA transaction done: wake ppa_wait(). ISR (interrupt service routine) context, from the 2D-DMA's interrupt, which is not IRAM-safe in this build (CONFIG_DMA2D_ISR_IRAM_SAFE off) and so never runs while the flash cache is off. It touches the semaphore alone.
static bool ppa_submit (void *engine, const gfx_turn_job_t *job)
 Start one job on the PPA: SRM (scale, rotate, mirror) at 180°, RGB565, non-blocking, its user_data the job's.
static bool ppa_wait (void *engine, uint32_t budget_us)
 Wait for on_ppa_done(), at most budget_us, in whole milliseconds up and at least one tick (CONFIG_FREERTOS_HZ 1000).
static gfx_turn_copy_t ppa_copy (void *ctx, const gfx_color_t *src, gfx_color_t *dst, gfx_rect_t r, uint32_t budget_us)
 One block of the logical buffer into the frame buffer, turned by 180°, through the PPA (gfx_turn_engine_copy()).
static uint32_t turn_now_us (void *ctx)
 The time for gfx_turn_flush(), us.
static void turn_up (void)
 Screen rotation's buffers and engine: two logical buffers in PSRAM and a PPA client for 180° copies. A failure leaves the panel NORMAL (gfx_turn_fail()) and the display working.
static void turn_down (void)
 Free what turn_up() got. A client the driver will not let go, because a transaction is still pending, keeps its semaphore, so a late on_ppa_done() never gives a deleted one.
static esp_err_t panel_reset (void)
 Pulse the panel's reset line, RESET_LOW_MS low, then RESET_WAIT_MS.
static esp_err_t panel_commands (void)
 Send the register table, then sleep out.
static esp_err_t bus_up (void)
 Take the PHY supply, then create the DSI bus, the command interface and the DPI panel with its frame buffers.
static void bus_down (void)
 Free whatever bus_up() and start() got, in reverse, after a failure or at deinit, and zero the state.
static esp_err_t start (const display_config_t *c)
 Bring the display up: semaphores, bus, callbacks, frame buffers, panel reset and commands, video stream.
esp_err_t display_init (const display_config_t *cfg)
 Bring up the DSI PHY supply, the bus, the panel and the video stream, and the board's I2C bus and backlight if not already up.
esp_err_t display_deinit (void)
 Stop the video stream and release the panel, the bus and the PHY supply.
gfx_canvas_t * display_canvas (void)
 The canvas the application draws into. The returned pointer is stable for the lifetime of the driver; display_flip() re-points it at the new back buffer in place.
esp_err_t display_wait_vsync (uint32_t timeout_ms)
 Block until the next VSYNC (vertical sync) event.
static void account_frame (void)
 Count a flip and update the rolling rate every 500ms.
static esp_err_t wait_frame_done (void)
 Wait for the next frame-done event, at most FLIP_WAIT_MS.
esp_err_t display_flip (void)
 Present the back buffer and block until the buffer it replaces is no longer scanned out, at most 200ms.
int display_width (void)
 The canvas's width: the UI's area.
int display_height (void)
 The canvas's height: the UI's area.
int display_back_index (void)
 Which frame buffer display_canvas() points at.
uint32_t display_last_wait_us (void)
 How long the last display_flip() spent blocked.
uint32_t display_frame_count (void)
 Frames presented since display_init().
float display_fps (void)
 Rolling frames per second, measured over the last 500ms.
uint32_t display_vsync_count (void)
 VSYNC events since display_init(): the panel's own refresh, whatever the flips do.
void display_set_turned (bool turned)
 Screen rotation: 180°, or NORMAL, from the next display_flip() on. May be called from any task, also before display_init().
bool display_turned (void)
 Whether the picture on the glass is turned by 180°: the flag the touch map turns by (gfx_turn_shown()).
bool display_take_repaint (void)
 Whether the UI must repaint everything before it draws the next frame: the canvas changed between a logical buffer and a frame buffer. Clears it. Call it from the task that flips.
bool display_take_turn_alert (void)
 Whether the alert that Screen rotation 180° fell back to NORMAL is due. Clears it. Call it from the task that flips.
uint32_t display_last_turn_us (void)
 The time the last display_flip() spent copying turned blocks.
esp_err_t display_backlight (bool on)
 Backlight at 100% or 0%.

Variables

static const char * TAG = "display"
static const ek_reg_t k_init []
 The initialisation table.
static display_state_t s_disp
 The one display.
static atomic_bool s_want
 What Screen rotation asks for (display_set_turned()). Outside s_disp: it is set before display_init() and from any task; the flip reads it.
static const gfx_turn_engine_t k_ppa_engine
 The PPA as a gfx_turn engine; its done callback gets s_disp.
static const gfx_turn_ops_t k_turn_ops
 The PPA as gfx_turn_flush()'s copy engine.

Detailed Description

The EK79007 panel on the ESP32-P4's DSI host, written from the EK79007 datasheet and ESP-IDF's esp_lcd headers.

Bring-up order: the PHY supply, the bus, the command interface, the DPI panel (frame buffers allocated and black, video not started), the panel's reset, the initialisation commands, then the video stream.

Screen rotation 180° turns the picture in the flush (gfx_turn.h): the UI draws into a logical buffer, and display_flip() copies what it drew into the frame buffer, turned, through the PPA (pixel-processing accelerator) before it presents that buffer. The EK79007's scan direction (0x36) cannot do it on this board: its PINCTL strap holds the scan direction and the register is ignored, at start-up as while the video runs.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ EK_CMD_ADDRESS_MODE

#define EK_CMD_ADDRESS_MODE   0x36

EK79007 address mode command, DCS 0x36

◆ EK_CMD_PANEL_CTRL2

#define EK_CMD_PANEL_CTRL2   0xB2

bit 4: 2 lanes; bit 6: normally black

Function Documentation

◆ bus_up()

esp_err_t bus_up ( void )
static

Take the PHY supply, then create the DSI bus, the command interface and the DPI panel with its frame buffers.

Returns
ESP_OK, or the error of the step that failed; bus_down() frees what was taken.

◆ display_back_index()

int display_back_index ( void )

Which frame buffer display_canvas() points at.

The buffers alternate, so static content has to be drawn into both. A per-buffer validity flag keyed on this index keeps unchanging chrome out of the per-frame path, which on a panel fed from PSRAM decides whether the refresh rate is met.

Returns
0 or 1.

◆ display_backlight()

esp_err_t display_backlight ( bool on)

Backlight at 100% or 0%.

Parameters
ontrue for 100%, false for 0% (dark).
Returns
As board_backlight().

◆ display_canvas()

gfx_canvas_t * display_canvas ( void )

The canvas the application draws into. The returned pointer is stable for the lifetime of the driver; display_flip() re-points it at the new back buffer in place.

Returns
The canvas; before display_init() it has no pixels.

◆ display_deinit()

esp_err_t display_deinit ( void )

Stop the video stream and release the panel, the bus and the PHY supply.

Returns
ESP_OK, also when the display was not up.

◆ display_flip()

esp_err_t display_flip ( void )

Present the back buffer and block until the buffer it replaces is no longer scanned out, at most 200ms.

Returns
ESP_OK; ESP_ERR_TIMEOUT when no frame-done event came in 200ms; ESP_ERR_INVALID_STATE before display_init(); or the DPI panel's error for the hand-over.

◆ display_fps()

float display_fps ( void )

Rolling frames per second, measured over the last 500ms.

Returns
Frames per second; 0 before the first 500ms.

◆ display_frame_count()

uint32_t display_frame_count ( void )

Frames presented since display_init().

Returns
The count; it wraps at 2^32.

◆ display_height()

int display_height ( void )

The canvas's height: the UI's area.

Returns
BOARD_UI_V_RES, 600 pixels.

◆ display_init()

esp_err_t display_init ( const display_config_t * cfg)

Bring up the DSI PHY supply, the bus, the panel and the video stream, and the board's I2C bus and backlight if not already up.

Parameters
cfgThe set-up; NULL takes DISPLAY_CONFIG_DEFAULT().
Returns
ESP_OK, also when already up; otherwise the error of the step that failed, with everything before it released.

◆ display_last_turn_us()

uint32_t display_last_turn_us ( void )

The time the last display_flip() spent copying turned blocks.

Returns
Microseconds, 0 in NORMAL.

◆ display_last_wait_us()

uint32_t display_last_wait_us ( void )

How long the last display_flip() spent blocked.

Returns
Microseconds.

◆ display_set_turned()

void display_set_turned ( bool turned)

Screen rotation: 180°, or NORMAL, from the next display_flip() on. May be called from any task, also before display_init().

Parameters
turnedtrue for 180°

◆ display_take_repaint()

bool display_take_repaint ( void )

Whether the UI must repaint everything before it draws the next frame: the canvas changed between a logical buffer and a frame buffer. Clears it. Call it from the task that flips.

Returns
true once per switch

◆ display_take_turn_alert()

bool display_take_turn_alert ( void )

Whether the alert that Screen rotation 180° fell back to NORMAL is due. Clears it. Call it from the task that flips.

Returns
true once per start at most

◆ display_turned()

bool display_turned ( void )

Whether the picture on the glass is turned by 180°: the flag the touch map turns by (gfx_turn_shown()).

Returns
true while turned

◆ display_vsync_count()

uint32_t display_vsync_count ( void )

VSYNC events since display_init(): the panel's own refresh, whatever the flips do.

Returns
The count; it wraps at 2^32.

◆ display_wait_vsync()

esp_err_t display_wait_vsync ( uint32_t timeout_ms)

Block until the next VSYNC (vertical sync) event.

Parameters
timeout_msThe longest wait in milliseconds; 0 waits forever.
Returns
ESP_OK, ESP_ERR_TIMEOUT, or ESP_ERR_INVALID_STATE before display_init().

◆ display_width()

int display_width ( void )

The canvas's width: the UI's area.

Returns
BOARD_UI_H_RES, 1024 pixels.

◆ on_frame_done()

bool on_frame_done ( esp_lcd_panel_handle_t panel,
esp_lcd_dpi_panel_event_data_t * edata,
void * user_ctx )
static

Frame-done callback: a frame buffer's scan ended. ISR (interrupt service routine) context.

Parameters
panelUnused.
edataUnused.
user_ctxThe display_state_t.
Returns
true when a task woke and a context switch is due.

◆ on_ppa_done()

bool on_ppa_done ( ppa_client_handle_t client,
ppa_event_data_t * edata,
void * user_ctx )
static

PPA transaction done: wake ppa_wait(). ISR (interrupt service routine) context, from the 2D-DMA's interrupt, which is not IRAM-safe in this build (CONFIG_DMA2D_ISR_IRAM_SAFE off) and so never runs while the flash cache is off. It touches the semaphore alone.

The driver passes each transaction's user_data, which ppa_submit() sets from the job (gfx_turn_engine_copy() fills it with &s_disp). A NULL context, or a semaphore already deleted, is counted (gfx_turn_stray_dones()) or skipped; the wait then times out and the flush falls back to NORMAL.

Parameters
clientUnused.
edataUnused.
user_ctxThe display_state_t.
Returns
true when a task woke and a context switch is due.

◆ on_vsync()

bool on_vsync ( esp_lcd_panel_handle_t panel,
esp_lcd_dpi_panel_event_data_t * edata,
void * user_ctx )
static

VSYNC callback: count it and wake display_wait_vsync(). ISR context.

Parameters
panelUnused.
edataUnused.
user_ctxThe display_state_t.
Returns
true when a task woke and a context switch is due.

◆ panel_commands()

esp_err_t panel_commands ( void )
static

Send the register table, then sleep out.

All in LP (low-power) mode on virtual channel 0, before the video stream starts.

Returns
ESP_OK, or the error of the first command that failed.

◆ panel_reset()

esp_err_t panel_reset ( void )
static

Pulse the panel's reset line, RESET_LOW_MS low, then RESET_WAIT_MS.

Returns
ESP_OK, or the GPIO error.

◆ ppa_copy()

gfx_turn_copy_t ppa_copy ( void * ctx,
const gfx_color_t * src,
gfx_color_t * dst,
gfx_rect_t r,
uint32_t budget_us )
static

One block of the logical buffer into the frame buffer, turned by 180°, through the PPA (gfx_turn_engine_copy()).

Parameters
ctxUnused: k_ppa_engine holds the state.
srcThe logical buffer.
dstThe frame buffer of the same index.
rThe block, in logical coordinates.
budget_usThe longest wait, us.
Returns
How the copy ended.

◆ ppa_submit()

bool ppa_submit ( void * engine,
const gfx_turn_job_t * job )
static

Start one job on the PPA: SRM (scale, rotate, mirror) at 180°, RGB565, non-blocking, its user_data the job's.

Parameters
engineThe display_state_t.
jobThe block (gfx_turn_engine_copy()).
Returns
false when the driver refuses it.

◆ ppa_wait()

bool ppa_wait ( void * engine,
uint32_t budget_us )
static

Wait for on_ppa_done(), at most budget_us, in whole milliseconds up and at least one tick (CONFIG_FREERTOS_HZ 1000).

Parameters
engineThe display_state_t.
budget_usThe longest wait, us.
Returns
true when the transaction ended in time.

◆ start()

esp_err_t start ( const display_config_t * c)
static

Bring the display up: semaphores, bus, callbacks, frame buffers, panel reset and commands, video stream.

Parameters
cThe set-up.
Returns
ESP_OK, or the error of the step that failed; the caller runs bus_down().

◆ turn_now_us()

uint32_t turn_now_us ( void * ctx)
static

The time for gfx_turn_flush(), us.

Parameters
ctxUnused.
Returns
esp_timer's time, wrapping at 2^32.

◆ wait_frame_done()

esp_err_t wait_frame_done ( void )
static

Wait for the next frame-done event, at most FLIP_WAIT_MS.

Returns
ESP_OK, or ESP_ERR_TIMEOUT.

Variable Documentation

◆ k_init

const ek_reg_t k_init[]
static
Initial value:
= {
{ EK_CMD_PANEL_CTRL2, 0x10 },
{ 0x80, 0x8B },
{ 0x81, 0x78 },
{ 0x82, 0x84 },
{ 0x83, 0x88 },
{ 0x84, 0xA8 },
{ 0x85, 0xE3 },
{ 0x86, 0x88 },
}
#define BOARD_LCD_ADDRESS_MODE
The EK79007's address mode (DCS command 0x36): bit 1 the gate scan direction, bit 0 the source shift ...
Definition board_pins.h:99
#define EK_CMD_PANEL_CTRL2
Definition display.c:78
#define EK_CMD_ADDRESS_MODE
Definition display.c:77

The initialisation table.

Every register takes one parameter byte and takes effect at the next valid frame. The lane count goes first: the chip resets to 4 lanes. 0x80 to 0x86 are the 14 gamma fields, two per register; the values are the ones Espressif's EK79007 driver writes (Apache-2.0), against 0x88 in every register after reset.

◆ k_ppa_engine

const gfx_turn_engine_t k_ppa_engine
static
Initial value:
= {
.submit = ppa_submit,
.wait = ppa_wait,
.engine = &s_disp,
.done_ctx = &s_disp,
}
static bool ppa_submit(void *engine, const gfx_turn_job_t *job)
Start one job on the PPA: SRM (scale, rotate, mirror) at 180°, RGB565, non-blocking,...
Definition display.c:238
static display_state_t s_disp
The one display.
Definition display.c:136
static bool ppa_wait(void *engine, uint32_t budget_us)
Wait for on_ppa_done(), at most budget_us, in whole milliseconds up and at least one tick (CONFIG_FRE...
Definition display.c:281

The PPA as a gfx_turn engine; its done callback gets s_disp.

◆ k_turn_ops

const gfx_turn_ops_t k_turn_ops
static
Initial value:
= {
.copy = ppa_copy,
.now_us = turn_now_us,
.ctx = NULL,
}
static gfx_turn_copy_t ppa_copy(void *ctx, const gfx_color_t *src, gfx_color_t *dst, gfx_rect_t r, uint32_t budget_us)
One block of the logical buffer into the frame buffer, turned by 180°, through the PPA (gfx_turn_engi...
Definition display.c:309
static uint32_t turn_now_us(void *ctx)
The time for gfx_turn_flush(), us.
Definition display.c:323

The PPA as gfx_turn_flush()'s copy engine.