diff --git a/ports/espressif/boards/freenove_esp32_display_3_5/board.c b/ports/espressif/boards/freenove_esp32_display_3_5/board.c new file mode 100644 index 00000000000..e5b3e4b5b92 --- /dev/null +++ b/ports/espressif/boards/freenove_esp32_display_3_5/board.c @@ -0,0 +1,96 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Brian Nelson +// +// SPDX-License-Identifier: MIT + +#include "supervisor/board.h" +#include "mpconfigboard.h" + +#include "shared-bindings/busio/SPI.h" +#include "shared-bindings/fourwire/FourWire.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-module/displayio/__init__.h" +#include "shared-module/displayio/mipi_constants.h" +#include "supervisor/shared/settings.h" + +// ST7796 init sequence from Freenove's TFT_eSPI setup, landscape (MADCTL MV | BGR) +static uint8_t display_init_sequence[] = { + 0x01, 0x80, 0x78, // Software reset, delay 120 ms + 0x11, 0x80, 0x78, // Sleep out, delay 120 ms + 0xF0, 0x01, 0xC3, // Enable extension command 2 part I + 0xF0, 0x01, 0x96, // Enable extension command 2 part II + 0x36, 0x01, 0x28, // Memory data access control: row/column exchange, BGR + 0x3A, 0x01, 0x55, // 16 bits per pixel + 0xB4, 0x01, 0x01, // 1-dot inversion + 0xB6, 0x03, 0x80, 0x02, 0x3B, // Display function control + 0xE8, 0x08, 0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33, // Display output control adjust + 0xC1, 0x01, 0x06, // Power control 2 + 0xC2, 0x01, 0xA7, // Power control 3 + 0xC5, 0x81, 0x18, 0x78, // VCOM control, delay 120 ms + 0xE0, 0x0E, 0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14, 0x18, 0x1B, // Positive gamma + 0xE1, 0x8E, 0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14, 0x17, 0x1B, 0x78, // Negative gamma, delay 120 ms + 0xF0, 0x01, 0x3C, // Disable extension command 2 part I + 0xF0, 0x81, 0x69, 0x78, // Disable extension command 2 part II, delay 120 ms + 0x29, 0x00, // Display on +}; + +static void display_init(void) { + fourwire_fourwire_obj_t *bus = &allocate_display_bus()->fourwire_bus; + busio_spi_obj_t *spi = &bus->inline_bus; + mp_int_t rotation; + + common_hal_busio_spi_construct(spi, &pin_GPIO14, &pin_GPIO13, &pin_GPIO12, false); + common_hal_busio_spi_never_reset(spi); + + bus->base.type = &fourwire_fourwire_type; + common_hal_fourwire_fourwire_construct(bus, + spi, + MP_OBJ_FROM_PTR(&pin_GPIO2), // LCD_DC + MP_OBJ_FROM_PTR(&pin_GPIO15), // LCD_CS + mp_const_none, // Reset is tied to the ESP32 reset + 26666666, // Baudrate: ESP32 SPI through the GPIO matrix tops out at 26.67 MHz + 0, // Polarity + 0); // Phase + + busdisplay_busdisplay_obj_t *display = &allocate_display()->display; + display->base.type = &busdisplay_busdisplay_type; + if (settings_get_int("CIRCUITPY_DISPLAY_ROTATION", &rotation) != SETTINGS_OK) { + rotation = 0; + } + + common_hal_busdisplay_busdisplay_construct(display, + bus, + 480, // Width + 320, // Height + 0, // column start + 0, // row start + rotation, // rotation + 16, // Color depth + false, // Grayscale + false, // pixels in a byte share a row. Only valid for depths < 8 + 1, // bytes per cell. Only valid for depths < 8 + false, // reverse_pixels_in_byte. Only valid for depths < 8 + true, // reverse_pixels_in_word + MIPI_COMMAND_SET_COLUMN_ADDRESS, // Set column command + MIPI_COMMAND_SET_PAGE_ADDRESS, // Set row command + MIPI_COMMAND_WRITE_MEMORY_START, // Write memory command + display_init_sequence, + sizeof(display_init_sequence), + &pin_GPIO27, // backlight pin + NO_BRIGHTNESS_COMMAND, + 1.0f, // brightness + false, // single_byte_bounds + false, // data_as_commands + true, // auto_refresh + 60, // native_frames_per_second + true, // backlight_on_high + false, // SH1107_addressing + 50000); // backlight pwm frequency +} + +void board_init(void) { + display_init(); +} + +// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/espressif/boards/freenove_esp32_display_3_5/mpconfigboard.h b/ports/espressif/boards/freenove_esp32_display_3_5/mpconfigboard.h new file mode 100644 index 00000000000..412bf2b0e15 --- /dev/null +++ b/ports/espressif/boards/freenove_esp32_display_3_5/mpconfigboard.h @@ -0,0 +1,29 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Brian Nelson +// +// SPDX-License-Identifier: MIT + +#pragma once + +#define MICROPY_HW_BOARD_NAME "Freenove ESP32 Display 3.5 inch" +#define MICROPY_HW_MCU_NAME "ESP32" + +// Blue element of the common-anode RGB LED +#define MICROPY_HW_LED_STATUS (&pin_GPIO17) +#define MICROPY_HW_LED_STATUS_INVERTED (1) + +#define CIRCUITPY_BOOT_BUTTON (&pin_GPIO0) + +#define DEFAULT_I2C_BUS_SDA (&pin_GPIO32) +#define DEFAULT_I2C_BUS_SCL (&pin_GPIO25) + +#define CIRCUITPY_BOARD_SPI (2) +#define CIRCUITPY_BOARD_SPI_PIN { \ + {.clock = &pin_GPIO18, .mosi = &pin_GPIO23, .miso = &pin_GPIO19}, /* SD card and SPI header */ \ + {.clock = &pin_GPIO14, .mosi = &pin_GPIO13, .miso = &pin_GPIO12}, /* LCD and touch */ \ +} + +// UART pins attached to the CH340C USB-to-serial converter +#define CIRCUITPY_CONSOLE_UART_TX (&pin_GPIO1) +#define CIRCUITPY_CONSOLE_UART_RX (&pin_GPIO3) diff --git a/ports/espressif/boards/freenove_esp32_display_3_5/mpconfigboard.mk b/ports/espressif/boards/freenove_esp32_display_3_5/mpconfigboard.mk new file mode 100644 index 00000000000..d7ab69e1e51 --- /dev/null +++ b/ports/espressif/boards/freenove_esp32_display_3_5/mpconfigboard.mk @@ -0,0 +1,8 @@ +CIRCUITPY_CREATOR_ID = 0x1C330000 +CIRCUITPY_CREATION_ID = 0x01030035 + +IDF_TARGET = esp32 + +CIRCUITPY_ESP_FLASH_MODE = qio +CIRCUITPY_ESP_FLASH_FREQ = 80m +CIRCUITPY_ESP_FLASH_SIZE = 4MB diff --git a/ports/espressif/boards/freenove_esp32_display_3_5/pins.c b/ports/espressif/boards/freenove_esp32_display_3_5/pins.c new file mode 100644 index 00000000000..47e36ce49be --- /dev/null +++ b/ports/espressif/boards/freenove_esp32_display_3_5/pins.c @@ -0,0 +1,72 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2026 Brian Nelson +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/board/__init__.h" +#include "shared-module/displayio/__init__.h" + +CIRCUITPY_BOARD_BUS_SINGLETON(lcd_spi, spi, 1) + +static const mp_rom_map_elem_t board_module_globals_table[] = { + CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS + + // BOOT button + { MP_ROM_QSTR(MP_QSTR_BUTTON), MP_ROM_PTR(&pin_GPIO0) }, + { MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_PTR(&pin_GPIO0) }, + + // RGB LED, common anode (drive low to light) + { MP_ROM_QSTR(MP_QSTR_LED_RED), MP_ROM_PTR(&pin_GPIO22) }, + { MP_ROM_QSTR(MP_QSTR_LED_GREEN), MP_ROM_PTR(&pin_GPIO16) }, + { MP_ROM_QSTR(MP_QSTR_LED_BLUE), MP_ROM_PTR(&pin_GPIO17) }, + + // ST7796 display, HSPI + { MP_ROM_QSTR(MP_QSTR_LCD_SCK), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_LCD_MOSI), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_LCD_MISO), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_LCD_CS), MP_ROM_PTR(&pin_GPIO15) }, + { MP_ROM_QSTR(MP_QSTR_LCD_DC), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_LCD_BACKLIGHT), MP_ROM_PTR(&pin_GPIO27) }, + + // XPT2046 resistive touch controller, shares the LCD SPI bus + { MP_ROM_QSTR(MP_QSTR_TOUCH_CS), MP_ROM_PTR(&pin_GPIO33) }, + { MP_ROM_QSTR(MP_QSTR_TOUCH_IRQ), MP_ROM_PTR(&pin_GPIO36) }, + + // microSD card slot, VSPI + { MP_ROM_QSTR(MP_QSTR_SD_CLK), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_SD_MOSI), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_SD_MISO), MP_ROM_PTR(&pin_GPIO19) }, + { MP_ROM_QSTR(MP_QSTR_SD_CS), MP_ROM_PTR(&pin_GPIO5) }, + + // SPI header, shares the SD card bus + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO19) }, + { MP_ROM_QSTR(MP_QSTR_CS), MP_ROM_PTR(&pin_GPIO21) }, + + // I2C header + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO32) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO25) }, + + // UART header, shared with the USB serial console + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO3) }, + + // Input header (input-only pins) + { MP_ROM_QSTR(MP_QSTR_IO35), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_IO39), MP_ROM_PTR(&pin_GPIO39) }, + + // Speaker: DAC output into the amplifier, and the amplifier shutdown (low = on) + { MP_ROM_QSTR(MP_QSTR_SPEAKER), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_SPEAKER_ENABLE_INVERTED), MP_ROM_PTR(&pin_GPIO4) }, + + // Battery voltage through a 1:2 divider + { MP_ROM_QSTR(MP_QSTR_BATTERY), MP_ROM_PTR(&pin_GPIO34) }, + + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_LCD_SPI), MP_ROM_PTR(&board_lcd_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_DISPLAY), MP_ROM_PTR(&displays[0].display) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/espressif/boards/freenove_esp32_display_3_5/sdkconfig b/ports/espressif/boards/freenove_esp32_display_3_5/sdkconfig new file mode 100644 index 00000000000..e69de29bb2d