espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.59k stars 7.27k forks source link

What are the conditions that trigger the LCD_CAM_LCD_TRANS_DONE_INT interrupt? (IDFGH-12103) #13161

Closed PickaxeHit closed 8 months ago

PickaxeHit commented 8 months ago

Answers checklist.

General issue report

Hello! I'm manually implementing a screen driver bus similar to the i8080, but I'm stuck in a few places. For the interrupt LCD_CAM_LCD_TRANS_DONE_INT, what are the conditions for the LCD peripheral to trigger it? How do I trigger this interrupt in fixed-length output mode(LCD_CAM.lcd_user.lcd_always_out_en=0, LCD_CAM.lcd_user.lcd_dout_cyclelen=N-1)? I tried the following:

  1. Configure the GDMA linked list, and the total data length is greater than N;
  2. Configure interrupt
    • int flcd_lcd_periph_bus_id = 0;
    • int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED;
    • esp_intr_alloc_intrstatus(lcd_periph_signals.buses[flcd_lcd_periph_bus_id].irq_id, isr_flags,(uint32_t)lcd_ll_get_interrupt_status_reg(hal.dev),LCD_LL_EVENT_TRANS_DONE,flcd_ctrl_gpio_callback, &flcd_state, NULL); The interrupt function contains an operation to flip the gpio;
  3. Turn on LCD transmission by setting lcd_start;
  4. Observe the gpio output through the logic analyzer.

I found that the interrupt was never triggered, even though the number of flips of the LCD's PCLK pin had reached the set value. How can I modify it? Or what alternative should I use to achieve a fixed number of cycles of output and trigger an interrupt at the end? Thanks!

PickaxeHit commented 8 months ago

New question. There is a strange behavior between PCLK and the correct data length:

I want to send 8000 Bytes data using 2 dma descriptors, but if the frequency is too low, the PCLK count would be less than suggested.

about 1.85MHz: image image

about 2.4MHz: image image

above 2.55MHz: image image

Corrected.

Here's the code (copy from https://blog.adafruit.com/2022/06/21/esp32uesday-more-s3-lcd-peripheral-hacking-with-code/ and with some modification)

/*
  Simple example of using the ESP32-S3's LCD peripheral for general-purpose
  (non-LCD) parallel data output with DMA. Connect 8 LEDs (or logic analyzer),
  cycles through a pattern among them at about 1 Hz.
  This code is ONLY for the ESP32-S3, NOT the S2, C3 or original ESP32.
  None of this is authoritative canon, just a lot of trial error w/datasheet
  and register poking. Probably more robust ways of doing this still TBD.
*/

#include <driver/periph_ctrl.h>
#include <esp_private/gdma.h>
#include <hal/dma_types.h>
#include <hal/gpio_hal.h>
#include <soc/lcd_cam_struct.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "hal/gpio_ll.h"
#include "esp_rom_gpio.h"
#include "driver/gpio.h"

gdma_channel_handle_t dma_chan; // DMA channel
dma_descriptor_t desc;          // DMA descriptor
dma_descriptor_t desc2;         // DMA descriptor
uint8_t data[8][500];           // Transmit buffer (2496 bytes total)
uint8_t data2[8][500];          // Transmit buffer (2496 bytes total)

// End-of-DMA-transfer callback
static IRAM_ATTR bool dma_callback(gdma_channel_handle_t dma_chan,
                                   gdma_event_data_t *event_data, void *user_data)
{
    // This DMA callback seems to trigger a moment before the last data has
    // issued (buffering between DMA & LCD peripheral?), so pause a moment
    // before stopping LCD data out. The ideal delay may depend on the LCD
    // clock rate...this one was determined empirically by monitoring on a
    // logic analyzer. YMMV.
    vTaskDelay(100);
    // The LCD peripheral stops transmitting at the end of the DMA xfer, but
    // clear the lcd_start flag anyway -- we poll it in loop() to decide when
    // the transfer has finished, and the same flag is set later to trigger
    // the next transfer.
    LCD_CAM.lcd_user.lcd_start = 0;
    return true;
}

void setup()
{
    // LCD_CAM peripheral isn't enabled by default -- MUST begin with this:
    periph_module_enable(PERIPH_LCD_CAM_MODULE);
    periph_module_reset(PERIPH_LCD_CAM_MODULE);

    // Reset LCD bus
    LCD_CAM.lcd_user.lcd_reset = 1;
    esp_rom_delay_us(100);

    // Configure LCD clock. Since this program generates human-perceptible
    // output and not data for LED matrices or NeoPixels, use almost the
    // slowest LCD clock rate possible. The S3-mini module used on Feather
    // ESP32-S3 has a 40 MHz crystal. A 2-stage clock division of 1:16000
    // is applied (250*64), yielding 2,500 Hz. Still much too fast for
    // human eyes, so later we set up the data to repeat each output byte
    // many times over.
    LCD_CAM.lcd_clock.clk_en = 1;             // Enable peripheral clock
    LCD_CAM.lcd_clock.lcd_clk_sel = 3;        // XTAL_CLK source
    LCD_CAM.lcd_clock.lcd_ck_out_edge = 0;    // PCLK low in 1st half cycle
    LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0;   // PCLK low idle
    LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 0; // PCLK = CLK / (CLKCNT_N+1)
    LCD_CAM.lcd_clock.lcd_clkm_div_num = 2;   // 1st stage 1:250 divide
    LCD_CAM.lcd_clock.lcd_clkm_div_a = 0;     // 0/1 fractional divide
    LCD_CAM.lcd_clock.lcd_clkm_div_b = 1;
    LCD_CAM.lcd_clock.lcd_clkcnt_n = 29; // 2nd stage 1:64 divide
    // See section 26.3.3.1 of the ESP32­S3 Technical Reference Manual
    // for information on other clock sources and dividers.

    // Configure LCD frame format. This is where we fiddle the peripheral
    // to provide generic 8-bit output rather than actually driving an LCD.
    // There's also a 16-bit mode but that's not shown here.
    LCD_CAM.lcd_ctrl.lcd_rgb_mode_en = 0;    // i8080 mode (not RGB)
    LCD_CAM.lcd_rgb_yuv.lcd_conv_bypass = 0; // Disable RGB/YUV converter
    LCD_CAM.lcd_misc.lcd_next_frame_en = 0;  // Do NOT auto-frame
    LCD_CAM.lcd_data_dout_mode.val = 0;      // No data delays
    LCD_CAM.lcd_user.lcd_always_out_en = 1;  // Enable 'always out' mode
    LCD_CAM.lcd_user.lcd_8bits_order = 0;    // Do not swap bytes
    LCD_CAM.lcd_user.lcd_bit_order = 0;      // Do not reverse bit order
    LCD_CAM.lcd_user.lcd_2byte_en = 0;       // 8-bit data mode
    LCD_CAM.lcd_user.lcd_dummy = 0;          // Dummy phase(s) @ LCD start
    LCD_CAM.lcd_user.lcd_dummy_cyclelen = 0; // 1 dummy phase
    LCD_CAM.lcd_user.lcd_cmd = 0;            // No command at LCD start
    LCD_CAM.lcd_misc.lcd_bk_en = 1;
    // "Dummy phases" are initial LCD peripheral clock cycles before data
    // begins transmitting when requested. After much testing, determined
    // that at least one dummy phase MUST be enabled for DMA to trigger
    // reliably. A problem with dummy phase(s) is if we're also using the
    // LCD_PCLK_IDX signal (not used in this code, but Adafruit_Protomatter
    // does)...the clock signal will start a couple of pulses before data,
    // which may or may not be problematic in some situations. You can
    // disable the dummy phase but need to keep the LCD TX FIFO primed
    // in that case, which gets complex.
    // always_out_en is set above to allow aribtrary-length transfers,
    // else lcd_dout_cyclelen is used...but is limited to 8K. Long (>4K)
    // transfers need DMA linked lists, not used here but mentioned later.

    // Route 8 LCD data signals to GPIO pins
    const struct
    {
        int8_t pin;
        uint8_t signal;
    } mux[] = {
        {3, LCD_DATA_OUT0_IDX},  // These are 8 consecutive pins down one
        {46, LCD_DATA_OUT1_IDX}, // side of the ESP32-S3 Feather. The ESP32
        {9, LCD_DATA_OUT2_IDX},  // has super flexible pin MUX capabilities,
        {10, LCD_DATA_OUT3_IDX}, // so any signal can go to any pin!
        {11, LCD_DATA_OUT4_IDX},
        {12, LCD_DATA_OUT5_IDX},
        {13, LCD_DATA_OUT6_IDX},
        {14, LCD_DATA_OUT7_IDX},
        {40, LCD_PCLK_IDX},
    };
    for (int i = 0; i < 9; i++)
    {
        esp_rom_gpio_connect_out_signal(mux[i].pin, mux[i].signal, false, false);
        gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[mux[i].pin], PIN_FUNC_GPIO);
        gpio_set_drive_capability((gpio_num_t)mux[i].pin, (gpio_drive_cap_t)3);
    }

    // This program has a known fixed-size data buffer (2496 bytes) that fits
    // in a single DMA descriptor (max 4095 bytes). Large transfers would
    // require a linked list of descriptors, but here it's just one...
    desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
    desc.dw0.suc_eof = 1; // Last descriptor
    desc.next = NULL;     // No linked list    // require a linked list of descriptors, but here it's just one...
    desc2.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
    desc2.dw0.suc_eof = 1; // Last descriptor
    desc2.next = NULL;     // No linked list
    // Remaining descriptor elements are initialized before each DMA transfer.

    // Allocate DMA channel and connect it to the LCD peripheral
    gdma_channel_alloc_config_t dma_chan_config = {
        .sibling_chan = NULL,
        .direction = GDMA_CHANNEL_DIRECTION_TX,
        .flags = {
            .reserve_sibling = 0}};
    gdma_new_channel(&dma_chan_config, &dma_chan);
    gdma_connect(dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
    gdma_strategy_config_t strategy_config = {
        .owner_check = false,
        .auto_update_desc = false};
    gdma_apply_strategy(dma_chan, &strategy_config);

    // Enable DMA transfer callback
    gdma_tx_event_callbacks_t tx_cbs = {
        .on_trans_eof = dma_callback};
    gdma_register_tx_event_callbacks(dma_chan, &tx_cbs, NULL);

    // As mentioned earlier, the slowest clock we can get to the LCD
    // peripheral is 40 MHz / 250 / 64 = 2500 Hz. To make an even slower
    // bit pattern that's perceptible, we just repeat each value many
    // times over. The pattern here just counts through each of 8 bits
    // (each LED lights in sequence)...so to get this to repeat at about
    // 1 Hz, each LED is lit for 2500/8 or 312 cycles, hence the
    // data[8][312] declaration at the start of this code (it's not
    // precisely 1 Hz because reality is messy, but sufficient for demo).
    // In actual use, say controlling an LED matrix or NeoPixels, such
    // shenanigans aren't necessary, as these operate at multiple MHz
    // with much smaller clock dividers and can use 1 byte per datum.
    for (int i = 0; i < (sizeof(data) / sizeof(data[0])); i++)
    { // 0 to 7
        for (int j = 0; j < sizeof(data[0]); j++)
        { // 0 to 311
            data[i][j] = 1 << i;
        }
    }
}

void loop()
{
    // This uses a busy loop to wait for each DMA transfer to complete...
    // but the whole point of DMA is that one's code can do other work in
    // the interim. The CPU is totally free while the transfer runs!
    while (LCD_CAM.lcd_user.lcd_start)
        ; // Wait for DMA completion callback

    // After much experimentation, each of these steps is required to get
    // a clean start on the next LCD transfer:
    gdma_reset(dma_chan);                 // Reset DMA to known state
    LCD_CAM.lcd_user.lcd_dout = 1;        // Enable data out
    LCD_CAM.lcd_user.lcd_update = 1;      // Update registers
    LCD_CAM.lcd_misc.lcd_afifo_reset = 1; // Reset LCD TX FIFO

    // This program happens to send the same data over and over...but,
    // if desired, one could fill the data buffer with a new bit pattern
    // here, or point to a completely different buffer each time through.
    // With two buffers, one can make best use of time by filling each
    // with new data before the busy loop above, alternating between them.

    // Reset elements of DMA descriptor. Just one in this code, long
    // transfers would loop through a linked list.
    desc.dw0.size = desc.dw0.length = sizeof(data);
    desc.buffer = data;
    desc.next = &desc2;
    desc.dw0.suc_eof = 0;
    desc2.dw0.size = desc2.dw0.length = sizeof(data2);
    desc2.buffer = data2;

    gdma_start(dma_chan, (intptr_t)&desc); // Start DMA w/updated descriptor(s)
    vTaskDelay(5);                         // Must 'bake' a moment before...
    LCD_CAM.lcd_user.lcd_start = 1;        // Trigger LCD DMA transfer
}

void app_main(void)
{
    setup();
    printf("%d\n", LCD_CAM.lcd_misc.lcd_vfk_cyclelen);
    for (;;)
    {
        LCD_CAM.lcd_clock.lcd_clkcnt_n--;
        loop();
    }
}