diff --git a/drivers/spi/spi_esp32_spim.c b/drivers/spi/spi_esp32_spim.c index 6a61868acf163..643c8e71baba1 100644 --- a/drivers/spi/spi_esp32_spim.c +++ b/drivers/spi/spi_esp32_spim.c @@ -401,14 +401,20 @@ static int IRAM_ATTR spi_esp32_configure(const struct device *dev, spi_hal_dev_config_t *hal_dev = &data->dev_config; int freq; - if (spi_context_configured(ctx, spi_cfg)) { - return 0; - } - - ctx->config = spi_cfg; - - if (spi_cfg->operation & SPI_HALF_DUPLEX) { - LOG_ERR("Half-duplex not supported"); + /* + * Always reapply the hardware configuration so register state remains + * consistent across transactions even when the same spi_config pointer + * is reused. The ESP32 HAL updates a number of user and clock + * registers as part of each transfer and these need to be reprogrammed + * before the next transaction, otherwise subsequent transfers inherit + * the modified state. + */ + ctx->config = spi_cfg; + + bool request_half_duplex = (spi_cfg->operation & SPI_HALF_DUPLEX) != 0U; + + if (request_half_duplex && !cfg->half_duplex) { + LOG_ERR("Half-duplex requested but not enabled in devicetree"); return -ENOTSUP; } @@ -430,6 +436,9 @@ static int IRAM_ATTR spi_esp32_configure(const struct device *dev, return ret; } + hal_dev->half_duplex = cfg->half_duplex || request_half_duplex; + hal_dev->sio = cfg->sio && hal_dev->half_duplex; + /* input parameters to calculate timing configuration */ spi_hal_timing_param_t timing_param = { .half_duplex = hal_dev->half_duplex, @@ -658,6 +667,8 @@ static DEVICE_API(spi, spi_api) = { .dma_enabled = DT_INST_PROP(idx, dma_enabled), \ .dma_host = DT_INST_PROP(idx, dma_host), \ SPI_DMA_CFG(idx), \ + .half_duplex = DT_INST_PROP(idx, half_duplex), \ + .sio = DT_INST_PROP(idx, sio), \ .cs_setup = DT_INST_PROP_OR(idx, cs_setup_time, 0), \ .cs_hold = DT_INST_PROP_OR(idx, cs_hold_time, 0), \ .line_idle_low = DT_INST_PROP(idx, line_idle_low), \ diff --git a/drivers/spi/spi_esp32_spim.h b/drivers/spi/spi_esp32_spim.h index 4b1e1aa24c30b..ed3bb6502d582 100644 --- a/drivers/spi/spi_esp32_spim.h +++ b/drivers/spi/spi_esp32_spim.h @@ -44,6 +44,8 @@ struct spi_esp32_config { #else int dma_clk_src; #endif + bool half_duplex; + bool sio; int cs_setup; int cs_hold; bool line_idle_low; diff --git a/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/CMakeLists.txt b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/CMakeLists.txt new file mode 100644 index 0000000000000..81c0cd23182e7 --- /dev/null +++ b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20.0) + +set(DTC_OVERLAY_FILE boards/m5stack_cores3.overlay) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(m5stack_cores3_spi_lcd_raw) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/README.rst b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/README.rst new file mode 100644 index 0000000000000..67fef04cf3c59 --- /dev/null +++ b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/README.rst @@ -0,0 +1,40 @@ +.. _m5stack_cores3_spi_lcd_raw: + +M5Stack CoreS3 raw SPI LCD sample +================================= + +Overview +-------- + +This sample shows how to talk to the ILI9342C based display that is fitted to +M5Stack CoreS3 without using Zephyr's display drivers. It enables the +Espressif SPI controller 3-wire mode so that 9-bit transfers (command bit plus +8 data bits) can be issued directly. After a minimal initialization sequence a +simple RGB gradient is written to the screen to demonstrate how to pack the +command/data bit into the transmit stream. + +Requirements +------------ + +* `M5Stack CoreS3 `_ + +Building and running +-------------------- + +Use the following command to build and flash the sample:: + + west build -b m5stack_cores3/esp32s3/procpu \ + samples/boards/espressif/m5stack_cores3_spi_lcd_raw --pristine + west flash + +After programming the board and resetting it the display should shortly show a +full-screen colour gradient and the console will print status messages similar +to:: + + *** Booting Zephyr OS build v3.x.x *** + [00:00:00.025,000] spi_lcd_raw: Resetting LCD + [00:00:00.320,000] spi_lcd_raw: Drawing gradient + [00:00:00.750,000] spi_lcd_raw: Gradient complete + +The SD card slot is disabled by the sample overlay while the display is driven +in half-duplex 3-wire mode. diff --git a/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/boards/m5stack_cores3.overlay b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/boards/m5stack_cores3.overlay new file mode 100644 index 0000000000000..2d75c948825af --- /dev/null +++ b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/boards/m5stack_cores3.overlay @@ -0,0 +1,17 @@ +&spi2 { + status = "okay"; + half-duplex; + sio; + + lcd_spi: display@0 { + compatible = "zephyr,spi-device"; + reg = <0>; + spi-max-frequency = <20000000>; + spi-interframe-delay-ns = <0>; + status = "okay"; + }; +}; + +&sd0 { + status = "disabled"; +}; diff --git a/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/prj.conf b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/prj.conf new file mode 100644 index 0000000000000..90e95cac7812b --- /dev/null +++ b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/prj.conf @@ -0,0 +1,4 @@ +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_SPI=y diff --git a/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/sample.yaml b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/sample.yaml new file mode 100644 index 0000000000000..bc781bf5ea8ed --- /dev/null +++ b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/sample.yaml @@ -0,0 +1,14 @@ +sample: + description: Raw SPI access to the CoreS3 LCD using 3-wire mode + name: m5stack_cores3_spi_lcd_raw + +common: + tags: + - display + - spi + +tests: + sample.boards.espressif.m5stack_cores3_spi_lcd_raw: + build_only: true + platform_allow: + - m5stack_cores3/esp32s3/procpu diff --git a/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/src/main.c b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/src/main.c new file mode 100644 index 0000000000000..6db66ba203e43 --- /dev/null +++ b/samples/boards/espressif/m5stack_cores3_spi_lcd_raw/src/main.c @@ -0,0 +1,292 @@ +/* +* SPDX-License-Identifier: Apache-2.0 +*/ + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(spi_lcd_raw, CONFIG_LOG_DEFAULT_LEVEL); + +#define LCD_NODE DT_NODELABEL(lcd_spi) +#define LCD_BUS_NODE DT_NODELABEL(spi2) + +BUILD_ASSERT(DT_NODE_HAS_STATUS(LCD_NODE, okay), +"LCD SPI device not defined in the device tree"); + +static struct spi_cs_control lcd_cs_control = { + .gpio = GPIO_DT_SPEC_GET_BY_IDX(LCD_BUS_NODE, cs_gpios, 0), + .delay = 0U, + .cs_is_gpio = true, +}; + +static const struct spi_dt_spec lcd_spi = { + .bus = DEVICE_DT_GET(LCD_BUS_NODE), + .config = { + .frequency = 20000000U, + .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(9) | + SPI_TRANSFER_MSB | SPI_HALF_DUPLEX | + SPI_HOLD_ON_CS, + .slave = DT_REG_ADDR(LCD_NODE), + .cs = &lcd_cs_control, + .word_delay = 0U, + }, +}; + +#define LCD_WIDTH 320U +#define LCD_HEIGHT 240U + +#define ILI9XXX_SWRESET 0x01 +#define ILI9XXX_SLPOUT 0x11 +#define ILI9XXX_DISPON 0x29 +#define ILI9XXX_CASET 0x2A +#define ILI9XXX_PASET 0x2B +#define ILI9XXX_RAMWR 0x2C +#define ILI9XXX_MADCTL 0x36 +#define ILI9XXX_PIXSET 0x3A +#define ILI9XXX_INVON 0x21 + +#define ILI9XXX_MADCTL_BGR BIT(3) +#define ILI9XXX_PIXSET_16BIT 0x55 + +static int lcd_write_words(uint8_t dc, const uint8_t *data, size_t len) +{ + uint8_t buffer[64]; + size_t buffered = 0U; + uint8_t current_byte = 0U; + uint8_t bits_filled = 0U; + + while (len > 0U) { + uint16_t word = (uint16_t)((dc & 0x1U) << 8) | *data++; + + for (int bit = 8; bit >= 0; --bit) { + current_byte = (uint8_t)((current_byte << 1) | + ((word >> bit) & 0x1U)); + bits_filled++; + + if (bits_filled == 8U) { + buffer[buffered++] = current_byte; + current_byte = 0U; + bits_filled = 0U; + + if (buffered == sizeof(buffer)) { + struct spi_buf tx_buf = { + .buf = buffer, + .len = buffered, + }; + struct spi_buf_set tx_set = { + .buffers = &tx_buf, + .count = 1, + }; + + int ret = spi_write_dt(&lcd_spi, &tx_set); + if (ret < 0) { + return ret; + } + + buffered = 0U; + } + } + } + + len--; + } + + if (bits_filled > 0U) { + current_byte <<= (8U - bits_filled); + buffer[buffered++] = current_byte; + } + + if (buffered > 0U) { + struct spi_buf tx_buf = { + .buf = buffer, + .len = buffered, + }; + struct spi_buf_set tx_set = { + .buffers = &tx_buf, + .count = 1, + }; + + int ret = spi_write_dt(&lcd_spi, &tx_set); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +static int lcd_send_command(uint8_t cmd) +{ + int ret = lcd_write_words(0U, &cmd, 1U); + int release_ret = spi_release_dt(&lcd_spi); + + if (ret == 0 && release_ret < 0) { + ret = release_ret; + } + + return ret; +} + +static int lcd_send_command_with_data(uint8_t cmd, const uint8_t *data, size_t len) +{ + int ret = lcd_write_words(0U, &cmd, 1U); + + if (ret < 0) { + (void)spi_release_dt(&lcd_spi); + return ret; + } + + if (len > 0U) { + ret = lcd_write_words(1U, data, len); + } + + int release_ret = spi_release_dt(&lcd_spi); + + if (ret == 0 && release_ret < 0) { + ret = release_ret; + } + + return ret; +} + +static int lcd_set_address_window(uint16_t x0, uint16_t y0, +uint16_t x1, uint16_t y1) +{ + uint8_t column_range[] = { + (uint8_t)(x0 >> 8), + (uint8_t)(x0 & 0xFF), + (uint8_t)(x1 >> 8), + (uint8_t)(x1 & 0xFF), + }; + uint8_t row_range[] = { + (uint8_t)(y0 >> 8), + (uint8_t)(y0 & 0xFF), + (uint8_t)(y1 >> 8), + (uint8_t)(y1 & 0xFF), + }; + int ret; + + ret = lcd_send_command_with_data(ILI9XXX_CASET, column_range, + sizeof(column_range)); + if (ret < 0) { + return ret; + } + + ret = lcd_send_command_with_data(ILI9XXX_PASET, row_range, + sizeof(row_range)); + + return ret; +} + +static uint16_t gradient_color(uint16_t x, uint16_t y) +{ + uint16_t r = (x * 31U) / (LCD_WIDTH - 1U); + uint16_t g = (y * 63U) / (LCD_HEIGHT - 1U); + uint16_t b = ((x + y) * 31U) / ((LCD_WIDTH - 1U) + (LCD_HEIGHT - 1U)); + + return (uint16_t)((r << 11) | (g << 5) | b); +} + +static int lcd_fill_gradient(void) +{ + uint8_t command = ILI9XXX_RAMWR; + uint8_t line_buffer[64]; + const size_t pixels_per_chunk = sizeof(line_buffer) / 2U; + int ret = lcd_write_words(0U, &command, 1U); + + if (ret < 0) { + (void)spi_release_dt(&lcd_spi); + return ret; + } + + for (uint16_t y = 0U; y < LCD_HEIGHT && ret == 0; ++y) { + for (uint16_t x = 0U; x < LCD_WIDTH; x += pixels_per_chunk) { + size_t pixels = MIN((size_t)(LCD_WIDTH - x), pixels_per_chunk); + + for (size_t i = 0U; i < pixels; ++i) { + uint16_t color = gradient_color(x + i, y); + + line_buffer[2U * i] = (uint8_t)(color >> 8); + line_buffer[(2U * i) + 1U] = (uint8_t)(color & 0xFF); + } + + ret = lcd_write_words(1U, line_buffer, pixels * 2U); + if (ret < 0) { + break; + } + } + } + + int release_ret = spi_release_dt(&lcd_spi); + + if (ret == 0 && release_ret < 0) { + ret = release_ret; + } + + return ret; +} + +void main(void) +{ + if (!spi_is_ready_dt(&lcd_spi)) { + LOG_ERR("SPI device not ready"); + return; + } + + LOG_INF("Resetting LCD"); + if (lcd_send_command(ILI9XXX_SWRESET) < 0) { + LOG_ERR("Failed to reset LCD"); + return; + } + + k_msleep(120); + + if (lcd_send_command(ILI9XXX_SLPOUT) < 0) { + LOG_ERR("Failed to exit sleep mode"); + return; + } + + k_msleep(120); + + uint8_t pixel_format = ILI9XXX_PIXSET_16BIT; + if (lcd_send_command_with_data(ILI9XXX_PIXSET, &pixel_format, 1U) < 0) { + LOG_ERR("Failed to set pixel format"); + return; + } + + uint8_t madctl = ILI9XXX_MADCTL_BGR; + if (lcd_send_command_with_data(ILI9XXX_MADCTL, &madctl, 1U) < 0) { + LOG_ERR("Failed to set MADCTL"); + return; + } + + if (lcd_send_command(ILI9XXX_INVON) < 0) { + LOG_WRN("Display inversion command failed"); + } + + if (lcd_set_address_window(0U, 0U, LCD_WIDTH - 1U, LCD_HEIGHT - 1U) < 0) { + LOG_ERR("Failed to set display area"); + return; + } + + if (lcd_send_command(ILI9XXX_DISPON) < 0) { + LOG_ERR("Failed to turn display on"); + return; + } + + k_msleep(20); + + LOG_INF("Drawing gradient"); + + if (lcd_fill_gradient() < 0) { + LOG_ERR("Failed to draw gradient"); + return; + } + + LOG_INF("Gradient complete"); +}