ESP32-S3 do-over

This commit is contained in:
Phillip Burgess 2022-06-11 16:07:40 -07:00
parent b7c7c5d4f3
commit b3d8339719

View file

@ -51,6 +51,7 @@
#include <esp_rom_gpio.h>
#include <hal/dma_types.h>
#include <hal/gpio_hal.h>
#include <hal/lcd_ll.h>
#include <soc/lcd_cam_reg.h>
#include <soc/lcd_cam_struct.h>
@ -76,36 +77,31 @@ static uint32_t _PM_directBitMask(Protomatter_core *core, int pin) {
static dma_descriptor_t desc;
static gdma_channel_handle_t dma_chan;
static volatile bool xfer_done = true; // DMA completion flag
// DMA callback for end-of-transfer interrupt
static IRAM_ATTR bool dma_callback(gdma_channel_handle_t dma_chan,
gdma_event_data_t *event_data,
void *user_data) {
xfer_done = true;
return true;
}
// LCD_CAM requires a complete replacement of the "blast" functions in order
// to use the DMA-based peripheral.
#define _PM_CUSTOM_BLAST // Disable blast_*() functions in core.c
IRAM_ATTR static void blast_byte(Protomatter_core *core, uint8_t *data) {
// while (!xfer_done); // Wait for completion of prior xfer
gdma_reset(dma_chan); // Stop any current DMA
LCD_CAM.lcd_user.lcd_start = 0; // Stop any current LCD transfer
// Reset LCD DOUT parameters each time (required).
// IN PRINCIPLE, cyclelen should be chainBits-1 (resulting in chainBits
// cycles). But due to the required dummy phases at start of transfer,
// extend by 1; set to chainBits, issue chainBits+1 cycles.
LCD_CAM.lcd_user.lcd_dout_cyclelen = core->chainBits;
LCD_CAM.lcd_user.lcd_dout = 1;
LCD_CAM.lcd_user.lcd_update = 1;
LCD_CAM.lcd_misc.lcd_afifo_reset = 1; // Reset TX FIFO (required)
// Reset LCD TX FIFO each time, else we see old data. When doing this,
// it's REQUIRED in the setup code to enable at least one dummy pulse,
// else the PCLK & data are randomly misaligned by 1-2 clocks!
LCD_CAM.lcd_misc.lcd_afifo_reset = 1;
// Re-init parts of descriptor each time (required)
// Partially re-init descriptor each time (required)
desc.dw0.size = desc.dw0.length = core->chainBits;
desc.buffer = data;
xfer_done = false;
gdma_start(dma_chan, (intptr_t)&desc);
esp_rom_delay_us(1); // Necessary? Another ESP32 example suggests so
esp_rom_delay_us(1); // Necessary before starting xfer
// This begins the DMA xfer out the LCD periph...
LCD_CAM.lcd_user.lcd_start = 1;
LCD_CAM.lcd_user.lcd_start = 1; // Begin LCD DMA xfer
}
// If using custom "blast" function(s), all three must be declared.
@ -128,20 +124,45 @@ void _PM_timerInit(Protomatter_core *core) {
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
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
LCD_CAM.lcd_clock.lcd_clkm_div_a = 0; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus '7' below yields...
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N)
// Configure signal pins
// IN THEORY this could be expanded to support 2 parallel chains,
// but the rest of the LCD & DMA setup isn't currently written for
// that, so it's limited to a single chain.
// Configure frame format. Some of these could probably be skipped and
// use defaults, but being verbose for posterity...
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 = 0; // Only when requested
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
// MUST enable at least one dummy phase at start of output, else clock and
// data are randomly misaligned by 1-2 cycles following required TX FIFO
// reset in blast_byte(). One phase MOSTLY works but sparkles a tiny bit
// (as in still very occasionally misaligned by 1 cycle). Two seems ideal;
// no sparkle. Since HUB75 is just a shift register, the extra clock ticks
// are harmless and the zero-data shifts off end of the chain.
LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase(s) @ LCD start
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 2 dummy phases
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0;
LCD_CAM.lcd_user.lcd_update = 1;
// Configure signal pins. IN THEORY this could be expanded to support
// 2 parallel chains, but the rest of the LCD & DMA setup is not currently
// written for that, so it's limited to a single chain for now.
const uint8_t signal[] = {LCD_DATA_OUT0_IDX, LCD_DATA_OUT1_IDX,
LCD_DATA_OUT2_IDX, LCD_DATA_OUT3_IDX,
LCD_DATA_OUT4_IDX, LCD_DATA_OUT5_IDX};
@ -149,44 +170,31 @@ void _PM_timerInit(Protomatter_core *core) {
pinmux(core->rgbPins[i], signal[i]);
pinmux(core->clockPin, LCD_PCLK_IDX);
// Configure frame format
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_dout_cyclelen = core->chainBits;
LCD_CAM.lcd_user.lcd_always_out_en = 0;
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_dout = 1; // Enable data out
// MUST enable at least one dummy phase at start of output, else clock
// and data are intermittently misaligned. Since HUB75 is just a shift
// register, the extra clock tick is harmless and the zero-data shifts
// off the end of the chain. Unsure if this is a lack of understanding
// on my part, a documentation issue, or silicon errata. Regardless,
// harmless now that it's known.
LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase at LCD start
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 0;
LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0;
LCD_CAM.lcd_user.lcd_update = 1; // Update registers before 1st xfer
// Disable LCD_CAM interrupts, clear any pending interrupt
LCD_CAM.lc_dma_int_ena.val &= ~LCD_LL_EVENT_TRANS_DONE;
LCD_CAM.lc_dma_int_clr.val = 0x03;
// Set up DMA TX descriptor
desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc.dw0.suc_eof = 1;
// Size, length and buffer are set on each DMA setup
desc.dw0.size = desc.dw0.length = core->chainBits;
desc.buffer = core->screenData;
desc.next = NULL;
// Alloc DMA channel & connect it to LCD periph
gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {
.reserve_sibling = 0
}
};
esp_err_t ret = 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_strategy_config_t strategy_config = {
.owner_check = false,
.auto_update_desc = false
};
gdma_apply_strategy(dma_chan, &strategy_config);
gdma_transfer_ability_t ability = {
.sram_trans_align = 0,
@ -195,9 +203,10 @@ void _PM_timerInit(Protomatter_core *core) {
gdma_set_transfer_ability(dma_chan, &ability);
gdma_start(dma_chan, (intptr_t)&desc);
// 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);
// Enable TRANS_DONE interrupt. Note that we do NOT require nor install
// an interrupt service routine, but DO need to enable the TRANS_DONE
// flag to make the LCD DMA transfer work.
LCD_CAM.lc_dma_int_ena.val |= LCD_LL_EVENT_TRANS_DONE & 0x03;
_PM_esp32commonTimerInit(core); // In esp32-common.h
}
@ -210,7 +219,7 @@ void _PM_timerInit(Protomatter_core *core) {
// If none are required, this function can be deleted and the version
// above can be moved before the ARDUIO/CIRCUITPY checks. If minimal
// changes, consider a single _PM_timerInit() implementation with
// ARDUINO/CIRCUITPY checks inside.
// ARDUINO/CIRCUITPY checks inside. It's all good.
// On S3, initialize the LCD_CAM peripheral and DMA.
@ -218,20 +227,45 @@ void _PM_timerInit(Protomatter_core *core) {
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
LCD_CAM.lcd_clock.clk_en = 1; // Enable clock
LCD_CAM.lcd_clock.lcd_clk_sel = 3; // PLL160M source
LCD_CAM.lcd_clock.lcd_clkm_div_a = 0; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_a = 1; // 1/1 fractional divide,
LCD_CAM.lcd_clock.lcd_clkm_div_b = 1; // plus '7' below yields...
LCD_CAM.lcd_clock.lcd_clkm_div_num = 7; // 1:8 prescale (20 MHz CLK)
LCD_CAM.lcd_clock.lcd_ck_out_edge = 0; // PCLK low in first half of cycle
LCD_CAM.lcd_clock.lcd_ck_idle_edge = 0; // PCLK low idle
LCD_CAM.lcd_clock.lcd_clk_equ_sysclk = 1; // PCLK = CLK (ignore CLKCNT_N)
// Configure signal pins
// IN THEORY this could be expanded to support 2 parallel chains,
// but the rest of the LCD & DMA setup isn't currently written for
// that, so it's limited to a single chain.
// Configure frame format. Some of these could probably be skipped and
// use defaults, but being verbose for posterity...
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 = 0; // Only when requested
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
// MUST enable at least one dummy phase at start of output, else clock and
// data are randomly misaligned by 1-2 cycles following required TX FIFO
// reset in blast_byte(). One phase MOSTLY works but sparkles a tiny bit
// (as in still very occasionally misaligned by 1 cycle). Two seems ideal;
// no sparkle. Since HUB75 is just a shift register, the extra clock ticks
// are harmless and the zero-data shifts off end of the chain.
LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase(s) @ LCD start
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 1; // 2 dummy phases
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0;
LCD_CAM.lcd_user.lcd_update = 1;
// Configure signal pins. IN THEORY this could be expanded to support
// 2 parallel chains, but the rest of the LCD & DMA setup is not currently
// written for that, so it's limited to a single chain for now.
const uint8_t signal[] = {LCD_DATA_OUT0_IDX, LCD_DATA_OUT1_IDX,
LCD_DATA_OUT2_IDX, LCD_DATA_OUT3_IDX,
LCD_DATA_OUT4_IDX, LCD_DATA_OUT5_IDX};
@ -239,44 +273,31 @@ void _PM_timerInit(Protomatter_core *core) {
pinmux(core->rgbPins[i], signal[i]);
pinmux(core->clockPin, LCD_PCLK_IDX);
// Configure frame format
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_dout_cyclelen = core->chainBits;
LCD_CAM.lcd_user.lcd_always_out_en = 0;
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_dout = 1; // Enable data out
// MUST enable at least one dummy phase at start of output, else clock
// and data are intermittently misaligned. Since HUB75 is just a shift
// register, the extra clock tick is harmless and the zero-data shifts
// off the end of the chain. Unsure if this is a lack of understanding
// on my part, a documentation issue, or silicon errata. Regardless,
// harmless now that it's known.
LCD_CAM.lcd_user.lcd_dummy = 1; // Enable dummy phase at LCD start
LCD_CAM.lcd_user.lcd_cmd = 0; // No command at LCD start
LCD_CAM.lcd_user.lcd_dummy_cyclelen = 0;
LCD_CAM.lcd_user.lcd_cmd_2_cycle_en = 0;
LCD_CAM.lcd_user.lcd_update = 1; // Update registers before 1st xfer
// Disable LCD_CAM interrupts, clear any pending interrupt
LCD_CAM.lc_dma_int_ena.val &= ~LCD_LL_EVENT_TRANS_DONE;
LCD_CAM.lc_dma_int_clr.val = 0x03;
// Set up DMA TX descriptor
desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc.dw0.suc_eof = 1;
// Size, length and buffer are set on each DMA setup
desc.dw0.size = desc.dw0.length = core->chainBits;
desc.buffer = core->screenData;
desc.next = NULL;
// Alloc DMA channel & connect it to LCD periph
gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
.sibling_chan = NULL,
.direction = GDMA_CHANNEL_DIRECTION_TX,
.flags = {
.reserve_sibling = 0
}
};
esp_err_t ret = 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_strategy_config_t strategy_config = {
.owner_check = false,
.auto_update_desc = false
};
gdma_apply_strategy(dma_chan, &strategy_config);
gdma_transfer_ability_t ability = {
.sram_trans_align = 0,
@ -285,9 +306,10 @@ void _PM_timerInit(Protomatter_core *core) {
gdma_set_transfer_ability(dma_chan, &ability);
gdma_start(dma_chan, (intptr_t)&desc);
// 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);
// Enable TRANS_DONE interrupt. Note that we do NOT require nor install
// an interrupt service routine, but DO need to enable the TRANS_DONE
// flag to make the LCD DMA transfer work.
LCD_CAM.lc_dma_int_ena.val |= LCD_LL_EVENT_TRANS_DONE & 0x03;
_PM_esp32commonTimerInit(core); // In esp32-common.h
}