diff --git a/firmware/platformio.ini b/firmware/platformio.ini index 939d49b..1750449 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -23,7 +23,6 @@ build_unflags = lib_deps = SPI - bodmer/TFT_eSPI https://github.com/bitbank2/JPEGDEC bblanchon/ArduinoJson monitor_filters = @@ -45,27 +44,22 @@ board_build.arduino.memory_type = qio_opi build_flags = ${common.build_flags} -DBOARD_HAS_PSRAM - ; -DUSE_DMA + -DLED_GPIO=GPIO_NUM_45 ; optional TFT power pin - -DUSE_HSPI_PORT - -DTFT_POWER=GPIO_NUM_7 - -DTFT_POWER_ON=LOW - ; TFT_eSPI setup - -DUSER_SETUP_LOADED=1 - -DTFT_RGB_ORDER=1 + -DPOWER_PIN=GPIO_NUM_7 + -DPOWER_PIN_ON=LOW + ; TFT setup + -DTFT_ST7789 -DTFT_WIDTH=240 -DTFT_HEIGHT=280 - -DST7789_DRIVER=1 -DTFT_SCLK=GPIO_NUM_41 -DTFT_MISO=GPIO_NUM_NC -DTFT_MOSI=GPIO_NUM_40 -DTFT_RST=GPIO_NUM_39 -DTFT_DC=GPIO_NUM_44 -DTFT_CS=GPIO_NUM_42 - -DTFT_BL=GPIO_NUM_0 - ; -DLOAD_FONT2=1 - -DSMOOTH_FONT - -DSPI_FREQUENCY=40000000 + -DTFT_BL=GPIO_NUM_NC + -DTFT_SPI_FREQUENCY=SPI_MASTER_FREQ_40M ; audio settings -DSPK_MODE=GPIO_NUM_12 -DI2S_SPEAKER_SERIAL_CLOCK=GPIO_NUM_14 @@ -74,8 +68,6 @@ build_flags = ; SD card -DUSE_SDCARD -DUSE_SDIO=1 - -DSD_CARD_PWR=GPIO_NUM_7 - -DSD_CARD_PWR_ON=LOW -DSD_CARD_D0=GPIO_NUM_5 -DSD_CARD_D1=GPIO_NUM_4 -DSD_CARD_D2=GPIO_NUM_17 @@ -89,56 +81,6 @@ build_flags = -DARDUINO_USB_MODE -DARDUINO_USB_CDC_ON_BOOT -[env:atomic14-esp32-zxspectrum-v1] -extends = esp32_common -board = esp32-s3-devkitc-1 -board_build.arduino.memory_type = qio_opi -build_flags = - ${common.build_flags} - -DBOARD_HAS_PSRAM - ; -DUSE_DMA - ; We have a touch keyboard! - -DTOUCH_KEYBOARD - ; optional TFT power pin - -DUSE_HSPI_PORT - ; TFT_eSPI setup - -DTFT_ROTATION=1 - -DTFT_RGB_ORDER=1 - -DUSER_SETUP_LOADED=1 - -DTFT_WIDTH=240 - -DTFT_HEIGHT=280 - -DST7789_DRIVER=1 - -DTFT_SCLK=GPIO_NUM_17 - -DTFT_MISO=GPIO_NUM_NC - -DTFT_MOSI=GPIO_NUM_18 - -DTFT_RST=GPIO_NUM_14 - -DTFT_DC=GPIO_NUM_15 - -DTFT_CS=GPIO_NUM_16 - -DTFT_BL=GPIO_NUM_NC - -DTOUCH_CS=GPIO_NUM_NC - ; -DLOAD_FONT2=1 - -DSMOOTH_FONT - -DSPI_FREQUENCY=40000000 - ; audio settings - ; SD card - -DUSE_SDCARD - -DUSE_SDIO=1 - -DSD_CARD_D0=GPIO_NUM_40 - -DSD_CARD_D1=GPIO_NUM_39 - -DSD_CARD_D2=GPIO_NUM_43 - -DSD_CARD_D3=GPIO_NUM_44 - -DSD_CARD_CMD=GPIO_NUM_42 - -DSD_CARD_CLK=GPIO_NUM_41 - ; SPEAKER - -DBUZZER_GPIO_NUM=GPIO_NUM_46 - -DBUZZER_DEFAULT_VOLUME=100 - ; nunchuck - ; -DNUNCHUK_CLOCK=GPIO_NUM_47 - ; -DNUNCHUK_DATA=GPIO_NUM_38 - ; make sure serial output works - -DARDUINO_USB_MODE - -DARDUINO_USB_CDC_ON_BOOT - [env:atomic14-esp32-zxspectrum-v0_1] extends = esp32_common board = esp32-s3-devkitc-1 @@ -148,19 +90,15 @@ build_unflags = build_flags = ${common.build_flags} -DBOARD_HAS_PSRAM - ; DMA used to work - now it doesn't :( - ; -DUSE_DMA ; We have a touch keyboard! -DTOUCH_KEYBOARD_V2 - ; optional TFT power pin - -DUSE_HSPI_PORT - ; TFT_eSPI setup - -DTFT_ROTATION=1 + -DLED_GPIO=GPIO_NUM_1 + ; TFT setup + -DTFT_ST7789 + -DTFT_SPI_FREQUENCY=SPI_MASTER_FREQ_80M -DTFT_RGB_ORDER=TFT_BGR - -DUSER_SETUP_LOADED=1 -DTFT_WIDTH=240 -DTFT_HEIGHT=320 - -DST7789_DRIVER=1 -DTFT_SCLK=GPIO_NUM_18 -DTFT_MISO=GPIO_NUM_NC -DTFT_MOSI=GPIO_NUM_15 @@ -168,10 +106,6 @@ build_flags = -DTFT_DC=GPIO_NUM_17 -DTFT_CS=GPIO_NUM_16 -DTFT_BL=GPIO_NUM_NC - -DTOUCH_CS=GPIO_NUM_NC - ; -DLOAD_FONT2=1 - -DSMOOTH_FONT - -DSPI_FREQUENCY=40000000 ; audio settings ; SD card -DUSE_SDCARD @@ -192,103 +126,6 @@ build_flags = -DARDUINO_USB_MODE -DARDUINO_USB_CDC_ON_BOOT -[env:atomic14-esp32-zxspectrum-v1-3_5] -extends = esp32_common -board = esp32-s3-devkitc-1 -board_build.arduino.memory_type = qio_opi -build_flags = - ${common.build_flags} - -DBOARD_HAS_PSRAM - ;-DUSE_DMA - ; We have a touch keyboard! - -DTOUCH_KEYBOARD - ; optional TFT power pin - -DUSE_HSPI_PORT - ; TFT_eSPI setup - -DTFT_ROTATION=1 - ; -DTFT_RGB_ORDER=1 - -DUSER_SETUP_LOADED=1 - -DTFT_WIDTH=240 - -DTFT_HEIGHT=320 - ; -DST7789_DRIVER=1 - -DILI9341_DRIVER - -DTFT_SCLK=GPIO_NUM_17 - -DTFT_MISO=GPIO_NUM_NC - -DTFT_MOSI=GPIO_NUM_18 - -DTFT_RST=GPIO_NUM_14 - -DTFT_DC=GPIO_NUM_15 - -DTFT_CS=GPIO_NUM_16 - -DTFT_BL=GPIO_NUM_NC - -DTOUCH_CS=GPIO_NUM_NC - ; -DLOAD_FONT2=1 - -DSMOOTH_FONT - -DSPI_FREQUENCY=40000000 - ; audio settings - ; SD card - -DUSE_SDCARD - -DUSE_SDIO=1 - -DSD_CARD_D0=GPIO_NUM_40 - -DSD_CARD_D1=GPIO_NUM_39 - -DSD_CARD_D2=GPIO_NUM_43 - -DSD_CARD_D3=GPIO_NUM_44 - -DSD_CARD_CMD=GPIO_NUM_42 - -DSD_CARD_CLK=GPIO_NUM_41 - ; SPEAKER - -DBUZZER_GPIO_NUM=GPIO_NUM_46 - ; nunchuck - ; -DNUNCHUK_CLOCK=GPIO_NUM_47 - ; -DNUNCHUK_DATA=GPIO_NUM_38 - ; make sure serial output works - -DARDUINO_USB_MODE - -DARDUINO_USB_CDC_ON_BOOT - -[env:TinyS3] -extends = esp32_common -board = um_tinys3 -build_flags = - ${common.build_flags} - -DUSE_DMA - ; Remote Control Pins - -DHAS_IR_REMOTE - -DIR_RECV_PIN=GPIO_NUM_36 - -DIR_RECV_PWR=GPIO_NUM_35 - -DIR_RECV_GND=GPIO_NUM_37 - -DIR_RECV_IND=GPIO_NUM_NC - -DDECODE_NEC - ; TFT_eSPI setup - -DUSER_SETUP_LOADED - -DTFT_WIDTH=240 - -DTFT_HEIGHT=280 - -DST7789_DRIVER=1 - -DTFT_SCLK=GPIO_NUM_6 - -DTFT_MISO=GPIO_NUM_8 - -DTFT_MOSI=GPIO_NUM_4 - -DTFT_RST=GPIO_NUM_5 - -DTFT_DC=GPIO_NUM_21 - -DTFT_CS=GPIO_NUM_8 - -DTFT_BL=GPIO_NUM_7 - -DTOUCH_CS=GPIO_NUM_NC - -DTFT_BACKLIGHT_ON=HIGH - ; -DLOAD_FONT2 - -DSMOOTH_FONT - -DSPI_FREQUENCY=80000000 - ; audio settings - -DI2S_SPEAKER_SERIAL_CLOCK=GPIO_NUM_39 - -DI2S_SPEAKER_LEFT_RIGHT_CLOCK=GPIO_NUM_40 - -DI2S_SPEAKER_SERIAL_DATA=GPIO_NUM_38 - ; SD card - ; -DUSE_SDCARD - ; -DSD_CARD_MISO=GPIO_NUM_3 - ; -DSD_CARD_MOSI=GPIO_NUM_8 - ; -DSD_CARD_CLK=GPIO_NUM_46 - ; -DSD_CARD_CS=GPIO_NUM_18 - ; make sure serial output works - -DARDUINO_USB_MODE - -DARDUINO_USB_CDC_ON_BOOT -; decode exceptions -monitor_filters = esp32_exception_decoder -monitor_speed = 115200 - [env:cheap-yellow-display] extends = esp32_common board = esp-wrover-kit @@ -296,12 +133,11 @@ platform_packages = platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.13 build_flags = ${common.build_flags} - -DUSE_DMA - ; TFT_eSPI setup - -DUSER_SETUP_LOADED + ; TFT setup + -DTFT_ILI9341 + -DTFT_SPI_FREQUENCY=SPI_MASTER_FREQ_40M -DTFT_WIDTH=240 -DTFT_HEIGHT=320 - -DILI9341_2_DRIVER -DTFT_SCLK=GPIO_NUM_14 -DTFT_MISO=GPIO_NUM_12 -DTFT_MOSI=GPIO_NUM_13 @@ -309,126 +145,14 @@ build_flags = -DTFT_DC=GPIO_NUM_2 -DTFT_CS=GPIO_NUM_15 -DTFT_BL=GPIO_NUM_21 - -DTOUCH_CS=GPIO_NUM_NC -DTFT_BACKLIGHT_ON=HIGH - ; -DLOAD_FONT2=1 - -DSMOOTH_FONT - -DSPI_FREQUENCY=55000000 - -DSPI_READ_FREQUENCY=20000000 - -DSPI_TOUCH_FREQUENCY=2500000 - ; audio settings - cheap yellow display uses the DAC - -DUSE_DAC_AUDIO - ; SD card - ; -DUSE_SDCARD - ; -DSD_CARD_MISO=GPIO_NUM_19 - ; -DSD_CARD_MOSI=GPIO_NUM_23 - ; -DSD_CARD_CLK=GPIO_NUM_18 - ; -DSD_CARD_CS=GPIO_NUM_5 - -[env:touch-down] -extends = esp32_common -board = esp-wrover-kit -platform_packages = - platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.13 -build_flags = - ${common.build_flags} - ; -DUSE_DMA ; no DMA on the ILI9488 driver :( - ; TFT_eSPI setup - -DUSER_SETUP_LOADED - -DILI9488_DRIVER - -DTFT_BL=32 - -DTFT_MISO=GPIO_NUM_NC - -DTFT_MOSI=GPIO_NUM_23 - -DTFT_SCLK=GPIO_NUM_18 - -DTFT_CS=GPIO_NUM_15 - -DTFT_DC=GPIO_NUM_2 - -DTFT_RST=GPIO_NUM_4 - -DTFT_BL=GPIO_NUM_NC - -DTOUCH_CS=GPIO_NUM_NC - ; -DLOAD_FONT2 - -DSMOOTH_FONT - -DTFT_BACKLIGHT_ON=HIGH - -DSPI_FREQUENCY=27000000 - -DSPI_READ_FREQUENCY=20000000 - -DSPI_TOUCH_FREQUENCY=2500000 - ; audio settings - touch down uses the DAC - -DUSE_DAC_AUDIO - -[env:m5core2] -extends = esp32_common -platform = espressif32 @ 4.4.0 -board = esp32dev -build_flags = - ${common.build_flags} - -DM5CORE2 - -DHAS_BUTTONS - -DUSE_DMA - ; TFT_eSPI setup - -DUSER_SETUP_LOADED - -DILI9342_DRIVER=1 - -DTFT_INVERSION_ON=1 - -DTFT_MISO=GPIO_NUM_38 - -DTFT_MOSI=GPIO_NUM_23 - -DTFT_SCLK=GPIO_NUM_18 - -DTFT_CS=GPIO_NUM_5 - -DTFT_DC=GPIO_NUM_15 - -DTFT_RST=GPIO_NUM_NC - -DLOAD_GLCD=1 - -DTFT_BL=GPIO_NUM_NC - -DTOUCH_CS=GPIO_NUM_NC - ; -DLOAD_FONT2=1 - -DSMOOTH_FONT - -DSPI_FREQUENCY=40000000 - -DSPI_READ_FREQUENCY=8000000 - ; audio settings - cheap yellow display uses the DAC - -DI2S_SPEAKER_SERIAL_CLOCK=GPIO_NUM_12 - -DI2S_SPEAKER_LEFT_RIGHT_CLOCK=GPIO_NUM_0 - -DI2S_SPEAKER_SERIAL_DATA=GPIO_NUM_2 - ; SD card - ; TODO: it fails, detect it but it can't mount: https://github.com/m5stack/M5Core2/issues/136 - ; - ; -DUSE_SDCARD - ; -DSD_CARD_MISO=GPIO_NUM_38 - ; -DSD_CARD_MOSI=GPIO_NUM_23 - ; -DSD_CARD_CLK=GPIO_NUM_18 - ; -DSD_CARD_CS=GPIO_NUM_4 - - - -[env:TinyWatch] -extends = esp32_common -board = um_tinys3 -; board = um_feathers3 -; board = um_tinys2 -build_flags = - ${common.build_flags} - -DUSE_DMA - ; TFT_eSPI setup - -DUSER_SETUP_LOADED - -DTFT_WIDTH=240 - -DTFT_HEIGHT=280 - -DST7789_DRIVER - -DCGRAM_OFFSET - -DTFT_SCLK=GPIO_NUM_36 - -DTFT_MISO=GPIO_NUM_37 - -DTFT_MOSI=GPIO_NUM_35 - -DTFT_RST=GPIO_NUM_17 - -DTFT_DC=GPIO_NUM_15 - -DTFT_CS=GPIO_NUM_16 - -DTFT_RGB_ORDER=TFT_RGB - -DTFT_BL=GPIO_NUM_13 - -DTFT_BACKLIGHT_ON=HIGH - -DTOUCH_CS=GPIO_NUM_NC - ; -DLOAD_FONT2 - -DSMOOTH_FONT - -DSPI_FREQUENCY=80000000 - ; audio settings - -DBUZZER_GPIO_NUM=GPIO_NUM_18 - ; SD card - ; make sure serial output works - ; -DARDUINO_USB_MODE=1 - ; -DARDUINO_USB_CDC_ON_BOOT=1 -; decode exceptions -monitor_filters = esp32_exception_decoder -monitor_speed = 115200 - + ; SPEAKER - don't use DAC it's noisy on the CYD - bodge it with PWM + -DBUZZER_GPIO_NUM=GPIO_NUM_26 + -DBUZZER_DEFAULT_VOLUME=255 ; SD card + -DUSE_SDCARD + ; avoid conflicts with the TFT + -DSD_CARD_SPI_HOST=VSPI_HOST + -DSD_CARD_MISO=GPIO_NUM_19 + -DSD_CARD_MOSI=GPIO_NUM_23 + -DSD_CARD_CLK=GPIO_NUM_18 + -DSD_CARD_CS=GPIO_NUM_5 diff --git a/firmware/src/Files/Files.h b/firmware/src/Files/Files.h index 1e768a4..dd444b1 100644 --- a/firmware/src/Files/Files.h +++ b/firmware/src/Files/Files.h @@ -42,14 +42,16 @@ class BusyLight public: BusyLight() { - pinMode(GPIO_NUM_1, OUTPUT); - digitalWrite(GPIO_NUM_1, HIGH); - Serial.printf("Busy light on: %ld\n", millis()); + #ifdef LED_GPIO + pinMode(LED_GPIO, OUTPUT); + digitalWrite(LED_GPIO, HIGH); + #endif } ~BusyLight() { - digitalWrite(GPIO_NUM_1, LOW); - Serial.printf("Busy light off: %ld\n", millis()); + #ifdef LED_GPIO + digitalWrite(LED_GPIO, LOW); + #endif } }; diff --git a/firmware/src/Files/SDCard.cpp b/firmware/src/Files/SDCard.cpp index 23f7e95..7756ca2 100644 --- a/firmware/src/Files/SDCard.cpp +++ b/firmware/src/Files/SDCard.cpp @@ -65,6 +65,10 @@ SDCard::SDCard(const char *mountPoint, gpio_num_t miso, gpio_num_t mosi, gpio_nu { m_mountPoint = mountPoint; m_host.max_freq_khz = SDMMC_FREQ_52M; + // only enable on ESP32 + #ifdef SD_CARD_SPI_HOST + m_host.slot = SD_CARD_SPI_HOST; + #endif esp_err_t ret; // Options for mounting the filesystem. // If format_if_mount_failed is set to true, SD card will be partitioned and diff --git a/firmware/src/Screens/PickerScreen.h b/firmware/src/Screens/PickerScreen.h index ee69bfe..4c2b90b 100644 --- a/firmware/src/Screens/PickerScreen.h +++ b/firmware/src/Screens/PickerScreen.h @@ -131,6 +131,7 @@ class PickerScreen : public Screen m_lastPageDrawn = page; } m_tft.loadFont(GillSans_15_vlw); + m_tft.setTextColor(TFT_WHITE, TFT_BLACK); m_tft.drawString((title + " - 5: Back, 6: Down, 7: Up, ENTER: Pick").c_str(), 0, 0); m_tft.drawFastHLine(0, 15, m_tft.width() - 1, TFT_WHITE); m_tft.loadFont(GillSans_30_vlw); diff --git a/firmware/src/TFT/ILI9341.cpp b/firmware/src/TFT/ILI9341.cpp new file mode 100644 index 0000000..2c6a662 --- /dev/null +++ b/firmware/src/TFT/ILI9341.cpp @@ -0,0 +1,155 @@ +#include "Arduino.h" +#include "ILI9341.h" +#include "esp_log.h" + +#define ILI9341_NOP 0x00 +#define ILI9341_SWRESET 0x01 +#define ILI9341_RDDID 0x04 +#define ILI9341_RDDST 0x09 + +#define ILI9341_SLPIN 0x10 +#define ILI9341_SLPOUT 0x11 +#define ILI9341_PTLON 0x12 +#define ILI9341_NORON 0x13 + +#define ILI9341_RDMODE 0x0A +#define ILI9341_RDMADCTL 0x0B +#define ILI9341_RDPIXFMT 0x0C +#define ILI9341_RDIMGFMT 0x0A +#define ILI9341_RDSELFDIAG 0x0F + +#define ILI9341_INVOFF 0x20 +#define ILI9341_INVON 0x21 +#define ILI9341_GAMMASET 0x26 +#define ILI9341_DISPOFF 0x28 +#define ILI9341_DISPON 0x29 + +#define ILI9341_CASET 0x2A +#define ILI9341_PASET 0x2B +#define ILI9341_RAMWR 0x2C +#define ILI9341_RAMRD 0x2E + +#define ILI9341_PTLAR 0x30 +#define ILI9341_VSCRDEF 0x33 +#define ILI9341_MADCTL 0x36 +#define ILI9341_VSCRSADD 0x37 +#define ILI9341_PIXFMT 0x3A + +#define ILI9341_WRDISBV 0x51 +#define ILI9341_RDDISBV 0x52 +#define ILI9341_WRCTRLD 0x53 + +#define ILI9341_FRMCTR1 0xB1 +#define ILI9341_FRMCTR2 0xB2 +#define ILI9341_FRMCTR3 0xB3 +#define ILI9341_INVCTR 0xB4 +#define ILI9341_DFUNCTR 0xB6 + +#define ILI9341_PWCTR1 0xC0 +#define ILI9341_PWCTR2 0xC1 +#define ILI9341_PWCTR3 0xC2 +#define ILI9341_PWCTR4 0xC3 +#define ILI9341_PWCTR5 0xC4 +#define ILI9341_VMCTR1 0xC5 +#define ILI9341_VMCTR2 0xC7 + +#define ILI9341_RDID4 0xD3 +#define ILI9341_RDINDEX 0xD9 +#define ILI9341_RDID1 0xDA +#define ILI9341_RDID2 0xDB +#define ILI9341_RDID3 0xDC +#define ILI9341_RDIDX 0xDD // TBC + +#define ILI9341_GMCTRP1 0xE0 +#define ILI9341_GMCTRN1 0xE1 + +#define ILI9341_MADCTL_MY 0x80 +#define ILI9341_MADCTL_MX 0x40 +#define ILI9341_MADCTL_MV 0x20 +#define ILI9341_MADCTL_ML 0x10 +#define ILI9341_MADCTL_RGB 0x00 +#define ILI9341_MADCTL_BGR 0x08 +#define ILI9341_MADCTL_MH 0x04 + +ILI9341::ILI9341(gpio_num_t mosi, gpio_num_t clk, gpio_num_t cs, gpio_num_t dc, gpio_num_t rst, gpio_num_t bl, int width, int height) + : TFTDisplay(mosi, clk, cs, dc, rst, bl, width, height) +{ + // Initialize SPI + spi_bus_config_t buscfg = { + .mosi_io_num = mosi, + .miso_io_num = -1, + .sclk_io_num = clk, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .max_transfer_sz = 65535, + .flags = SPICOMMON_BUSFLAG_MASTER, + //.isr_cpu_id = ESP_INTR_CPU_AFFINITY_1, + .intr_flags = 0, + }; + + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .mode = 0, // SPI mode 0 + //.clock_source = SPI_CLK_SRC_DEFAULT, // Use the same frequency as the APB bus + .duty_cycle_pos = 128, + .cs_ena_pretrans = 0, + .cs_ena_posttrans = 0, + .clock_speed_hz = SPI_MASTER_FREQ_40M, // Clock out at 80 MHz + .input_delay_ns = 0, + .spics_io_num = cs, // CS pin + .flags = SPI_DEVICE_NO_DUMMY, + .queue_size = 1, + .pre_cb = nullptr, + .post_cb = nullptr + + }; + + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi)); + + spi_device_acquire_bus(spi, portMAX_DELAY); + + // Reset the display + gpio_set_level(rst, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + gpio_set_level(rst, 1); + vTaskDelay(pdMS_TO_TICKS(100)); + + SEND_CMD_DATA(0xEF, 0x03, 0x80, 0x02); + SEND_CMD_DATA(0xCF, 0x00, 0xC1, 0x30); + SEND_CMD_DATA(0xED, 0x64, 0x03, 0x12, 0x81); + SEND_CMD_DATA(0xE8, 0x85, 0x00, 0x78); + SEND_CMD_DATA(0xCB, 0x39, 0x2C, 0x00, 0x34, 0x02); + SEND_CMD_DATA(0xF7, 0x20); + SEND_CMD_DATA(0xEA, 0x00, 0x00); + SEND_CMD_DATA(ILI9341_PWCTR1, 0x23); // Power control, VRH[5:0] + SEND_CMD_DATA(ILI9341_PWCTR2, 0x10); // Power control, SAP[2:0], BT[3:0] + SEND_CMD_DATA(ILI9341_VMCTR1, 0x3E, 0x28); // VCM control + SEND_CMD_DATA(ILI9341_VMCTR2, 0x86); // VCM control2 + SEND_CMD_DATA(ILI9341_MADCTL, ILI9341_MADCTL_MX | ILI9341_MADCTL_RGB); // Rotation 0 (portrait mode) + SEND_CMD_DATA(ILI9341_PIXFMT, 0x55); // Pixel Format Set + SEND_CMD_DATA(ILI9341_FRMCTR1, 0x00, 0x13); // Frame Rate Control (100Hz) + SEND_CMD_DATA(ILI9341_DFUNCTR, 0x08, 0x82, 0x27); // Display Function Control + SEND_CMD_DATA(0xF2, 0x00); // 3Gamma Function Disable + SEND_CMD_DATA(ILI9341_GAMMASET, 0x01); // Gamma curve selected + SEND_CMD_DATA(ILI9341_GMCTRP1, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00); // Set Gamma + SEND_CMD_DATA(ILI9341_GMCTRN1, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F); // Set Gamma + SEND_CMD_DATA(CMD_MADCTL, MADCTL_ML | MADCTL_MV | MADCTL_BGR); + SEND_CMD_DATA(0x11); // Exit Sleep + delay(120); + SEND_CMD_DATA(0x29); // Display on + delay(120); + fillScreen(TFT_GREEN); + + Serial.println("ILI9341 initialized"); +} + +ILI9341::~ILI9341() +{ +} diff --git a/firmware/src/TFT/ILI9341.h b/firmware/src/TFT/ILI9341.h new file mode 100644 index 0000000..346d54e --- /dev/null +++ b/firmware/src/TFT/ILI9341.h @@ -0,0 +1,10 @@ +#pragma once + +#include "driver/gpio.h" +#include "TFTDisplay.h" + +class ILI9341: public TFTDisplay { +public: + ILI9341(gpio_num_t mosi, gpio_num_t clk, gpio_num_t cs, gpio_num_t dc, gpio_num_t rst, gpio_num_t bl, int width = 240, int height = 320); + ~ILI9341(); +}; diff --git a/firmware/src/TFT/ST7789.cpp b/firmware/src/TFT/ST7789.cpp index f0365fb..6945a83 100644 --- a/firmware/src/TFT/ST7789.cpp +++ b/firmware/src/TFT/ST7789.cpp @@ -1,144 +1,10 @@ +#include "Arduino.h" #include "ST7789.h" -#include -#include "freertos/task.h" -#include #include "esp_log.h" -#include "../Emulator/48k_rom.h" - -#define ST7789_CMD_MADCTL 0x36 -#define MADCTL_MY 0x80 -#define MADCTL_MX 0x40 -#define MADCTL_MV 0x20 -#define MADCTL_ML 0x10 -#define MADCTL_RGB 0x00 -#define MADCTL_BGR 0x08 - -#define ST7789_CMD_SWRESET 0x01 -#define ST7789_CMD_SLPOUT 0x11 -#define ST7789_CMD_DISPON 0x29 -#define ST7789_CMD_CASET 0x2A -#define ST7789_CMD_RASET 0x2B -#define ST7789_CMD_RAMWR 0x2C - -// max buffer size that we support - larger transfers will be split accross multiple transactions -const size_t DMA_BUFFER_SIZE = 32768; - -// helper functions -inline uint16_t swapBytes(uint16_t val) -{ - return (val >> 8) | (val << 8); -} - -uint32_t swapEndian32(uint32_t val) -{ - return ((val >> 24) & 0x000000FF) | - ((val >> 8) & 0x0000FF00) | - ((val << 8) & 0x00FF0000) | - ((val << 24) & 0xFF000000); -} - -int32_t readInt32(const uint8_t *data) -{ - int32_t val = *((int32_t *)data); - val = (int32_t)swapEndian32((uint32_t)val); - return val; -} - -uint32_t readUInt32(const uint8_t *data) -{ - uint32_t val = *((uint32_t *)data); - val = (uint32_t)swapEndian32((uint32_t)val); - return val; -} - -class SPITransactionInfo -{ -public: - spi_transaction_t transaction; - bool isCommand = false; // false = data, true = command - void *buffer = nullptr; // the DMA buffer - ST7789 *display = nullptr; // the display that the transaction was for - - SPITransactionInfo(size_t bufferSize) - { - memset(&transaction, 0, sizeof(transaction)); - transaction.user = this; - if (bufferSize > 0) { - buffer = heap_caps_malloc(bufferSize, MALLOC_CAP_DMA); - } - } - - void setCommand(uint8_t cmd) - { - memset(&transaction, 0, sizeof(transaction)); - isCommand = true; - transaction.length = 8; // Command is 8 bits - transaction.tx_data[0] = cmd; - transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; - transaction.user = this; - } - - bool setData(const uint8_t *data, int len) - { - memset(&transaction, 0, sizeof(transaction)); - isCommand = false; - transaction.length = len * 8; // Data length in bits - transaction.user = this; - if (len <= 4) { - for(int i = 0; isetCommand(cmd); - sendTransaction(_transaction); -} - -void ST7789::sendTransaction(SPITransactionInfo *trans) { - isBusy = true; - if (trans->isCommand) - { - gpio_set_level(dc, 0); // Command mode - } - else - { - gpio_set_level(dc, 1); // Data mode - } - spi_device_polling_start(spi, &trans->transaction, portMAX_DELAY); -} - -void ST7789::sendPixels(const uint16_t *data, int numPixels) -{ - int bytes = numPixels * 2; - for(uint32_t i = 0; i < bytes; i += DMA_BUFFER_SIZE) { - uint32_t len = std::min(DMA_BUFFER_SIZE, bytes - i); - dmaWait(); - _transaction->setPixels(data + i / 2, len / 2); - sendTransaction(_transaction); - } -} - -void ST7789::sendData(const uint8_t *data, int length) -{ - for(uint32_t i = 0; i < length; i += DMA_BUFFER_SIZE) { - uint32_t len = std::min(DMA_BUFFER_SIZE, length - i); - dmaWait(); - _transaction->setData(data + i, len); - sendTransaction(_transaction); - } -} - -void ST7789::sendColor(uint16_t color, int numPixels) -{ - for(int i = 0; i> 1) { - int len = std::min(numPixels - i, int(DMA_BUFFER_SIZE >> 1)); - dmaWait(); - _transaction->setColor(color, len); - sendTransaction(_transaction); - } -} - -void ST7789::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) -{ - uint8_t data[4]; - - sendCmd(ST7789_CMD_CASET); // Column address set - data[0] = (x0 >> 8) & 0xFF; - data[1] = x0 & 0xFF; - data[2] = (x1 >> 8) & 0xFF; - data[3] = x1 & 0xFF; - sendData(data, 4); - - sendCmd(ST7789_CMD_RASET); // Row address set - data[0] = (y0 >> 8) & 0xFF; - data[1] = y0 & 0xFF; - data[2] = (y1 >> 8) & 0xFF; - data[3] = y1 & 0xFF; - sendData(data, 4); - - sendCmd(ST7789_CMD_RAMWR); // Write to RAM -} - -void ST7789::sendPixel(uint16_t color) -{ - uint16_t data[1]; - data[0] = color; - sendPixels(data, 1); -} - -void ST7789::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) -{ - setWindow(x, y, x + w - 1, y + h - 1); - sendColor(SWAPBYTES(color), w * h); -} - -void ST7789::drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { - color = SWAPBYTES(color); - drawFastHLine(x, y, w, color); - drawFastHLine(x, y + h - 1, w, color); - drawFastVLine(x, y, h, color); - drawFastVLine(x + w - 1, y, h, color); -} - - -void ST7789::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) -{ - fillRect(x, y, w, 1, swapBytes(color)); -} - -void ST7789::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) -{ - fillRect(x, y, 1, h, swapBytes(color)); -} - -void ST7789::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) { - // make sure that the line is always drawn from left to right - if (x0 > x1) { - std::swap(x0, x1); - std::swap(y0, y1); - } - int16_t dx = abs(x1 - x0); - int16_t dy = abs(y1 - y0); - int16_t sx = (x0 < x1) ? 1 : -1; - int16_t sy = (y0 < y1) ? 1 : -1; - int16_t err = dx - dy; - int16_t e2; - - while (true) { - // Draw the current pixel - drawPixel(color, x0, y0); - - // If we've reached the end of the line, break out of the loop - if (x0 == x1 && y0 == y1) { - break; - } - - e2 = err * 2; - - if (e2 > -dy) { // Horizontal step - err -= dy; - x0 += sx; - } - - if (e2 < dx) { // Vertical step - err += dx; - y0 += sy; - } - } -} - -void ST7789::drawPolygon(const std::vector& vertices, uint16_t color) { - if (vertices.size() < 3) { - return; // Not a polygon - } - for (size_t i = 0; i < vertices.size(); i++) { - const Point& v1 = vertices[i]; - const Point& v2 = vertices[(i + 1) % vertices.size()]; - drawLine(v1.x, v1.y, v2.x, v2.y, color); - } -} - -void ST7789::drawFilledPolygon(const std::vector& vertices, uint16_t color) { - if (vertices.size() < 3) { - return; // Not a polygon - } - - // Find the bounding box of the polygon - int16_t minY = vertices[0].y; - int16_t maxY = vertices[0].y; - for (const auto& vertex : vertices) { - minY = std::min(minY, vertex.y); - maxY = std::max(maxY, vertex.y); - } - - // Scanline fill algorithm - for (int16_t y = minY; y <= maxY; y++) { - std::vector nodeX; - - // Find intersections of the scanline with polygon edges - for (size_t i = 0; i < vertices.size(); i++) { - Point v1 = vertices[i]; - Point v2 = vertices[(i + 1) % vertices.size()]; - - if ((v1.y < y && v2.y >= y) || (v2.y < y && v1.y >= y)) { - int16_t x = v1.x + (y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y); - nodeX.push_back(x); - } - } - - // Sort the intersection points - std::sort(nodeX.begin(), nodeX.end()); - - // Draw horizontal lines between pairs of intersections - for (size_t i = 0; i < nodeX.size(); i += 2) { - if (i + 1 < nodeX.size()) { - drawFastHLine(nodeX[i], y, nodeX[i + 1] - nodeX[i] + 1, color); - } - } - } -} - - -void ST7789::fillScreen(uint16_t color) -{ - fillRect(0, 0, width, height, color); -} - -void ST7789::loadFont(const uint8_t *fontData) -{ - if (currentFont.fontData == fontData) - { - return; - } - currentFont.fontData = fontData; - // Read font metadata with endianness correction - currentFont.gCount = readUInt32(fontData); - uint32_t version = readUInt32(fontData + 4); - uint32_t fontSize = readUInt32(fontData + 8); - uint32_t mboxY = readUInt32(fontData + 12); - currentFont.ascent = readUInt32(fontData + 16); - currentFont.descent = readUInt32(fontData + 20); -} - -void ST7789::setTextColor(uint16_t color, uint16_t bgColor) -{ - textcolor = color; - textbgcolor = bgColor; -} - -void ST7789::dmaWait() -{ - if (isBusy) - { - spi_device_polling_end(spi, portMAX_DELAY); - isBusy = false; - } -} - -void ST7789::setRotation(uint8_t m) -{ - rotation = m % 4; - uint8_t madctl = 0; - switch (rotation) - { - case 0: - madctl = MADCTL_MX | MADCTL_MY | MADCTL_RGB; - width = 240; - height = 320; - break; - case 1: - madctl = MADCTL_MV | MADCTL_MY | MADCTL_RGB; - width = 320; - height = 240; - break; - case 2: - madctl = MADCTL_RGB; - width = 240; - height = 320; - break; - case 3: - madctl = MADCTL_MX | MADCTL_MV | MADCTL_RGB; - width = 320; - height = 240; - break; - } - sendCmd(ST7789_CMD_MADCTL); - sendData(&madctl, 1); -} - -Glyph ST7789::getGlyphData(uint32_t unicode) -{ - const uint8_t *fontPtr = currentFont.fontData + 24; - const uint8_t *bitmapPtr = currentFont.fontData + 24 + currentFont.gCount * 28; - - for (uint32_t i = 0; i < currentFont.gCount; i++) - { - uint32_t glyphUnicode = readUInt32(fontPtr); - uint32_t height = readUInt32(fontPtr + 4); - uint32_t width = readUInt32(fontPtr + 8); - int32_t gxAdvance = readInt32(fontPtr + 12); - int32_t dY = readInt32(fontPtr + 16); - int32_t dX = readInt32(fontPtr + 20); - - if (glyphUnicode == unicode) - { - Glyph glyph; - glyph.unicode = glyphUnicode; - glyph.width = width; - glyph.height = height; - glyph.gxAdvance = gxAdvance; - glyph.dX = dX; - glyph.dY = dY; - glyph.bitmap = bitmapPtr; - return glyph; - } - - // Move to next glyph (28 bytes for metadata + bitmap size) - fontPtr += 28; - bitmapPtr += width * height; - } - - // Return default glyph if not found - Serial.printf("Glyph not found: %c\n", unicode); - return getGlyphData(' '); -} - -void ST7789::drawPixel(uint16_t color, int x, int y) -{ - if (x < 0 || x >= width || y < 0 || y >= height) - { - return; - } - setWindow(x, y, x, y); - sendPixel(swapBytes(color)); -} - -void ST7789::drawGlyph(const Glyph &glyph, int x, int y) -{ - // Iterate over each pixel in the glyph's bitmap - const uint8_t *bitmap = glyph.bitmap; - uint16_t pixelBuffer[glyph.width * glyph.height] = {textbgcolor}; - for (int j = 0; j < glyph.height; j++) - { - for (int i = 0; i < glyph.width; i++) - { - // Get the alpha value for the current pixel (1 byte per pixel) - uint8_t alpha = bitmap[j * glyph.width + i]; - // blend between the text color and the background color - uint16_t fg = textcolor; - uint16_t bg = textbgcolor; - // extract the red, green and blue - uint8_t fgRed = (fg >> 11) & 0x1F; - uint8_t fgGreen = (fg >> 5) & 0x3F; - uint8_t fgBlue = fg & 0x1F; - - uint8_t bgRed = (bg >> 11) & 0x1F; - uint8_t bgGreen = (bg >> 5) & 0x3F; - uint8_t bgBlue = bg & 0x1F; - - uint8_t red = ((fgRed * alpha) + (bgRed * (255 - alpha))) / 255; - uint8_t green = ((fgGreen * alpha) + (bgGreen * (255 - alpha))) / 255; - uint8_t blue = ((fgBlue * alpha) + (bgBlue * (255 - alpha))) / 255; - - uint16_t color = (red << 11) | (green << 5) | blue; - pixelBuffer[i + j * glyph.width] = swapBytes(color); - } - } - setWindow(x + glyph.dX, y - glyph.dY, x + glyph.dX + glyph.width - 1, y + glyph.dY + glyph.height - 1); - sendPixels(pixelBuffer, glyph.width * glyph.height); -} - -void ST7789::drawString(const char *text, int16_t x, int16_t y) -{ - int cursorX = x; - int cursorY = y; - - while (*text) - { - char c = *text++; - - // Get the glyph data for the character - Glyph glyph = getGlyphData((uint32_t)c); - - // Draw the glyph bitmap at the current cursor position - drawGlyph(glyph, cursorX, cursorY + currentFont.ascent); - - // Move the cursor to the right by the glyph's gxAdvance value - cursorX += glyph.gxAdvance; - } -} - -Point ST7789::measureString(const char *string) { - Point result = {0, 0}; - int cursorX = 0; - int cursorY = 0; - while (*string) - { - char c = *string++; - - // Get the glyph data for the character - Glyph glyph = getGlyphData((uint32_t)c); - - // Move the cursor to the right by the glyph's gxAdvance value - cursorX += glyph.gxAdvance; - result.y = std::max(result.y, glyph.height); - } - result.x = cursorX; - return result; -} - diff --git a/firmware/src/TFT/ST7789.h b/firmware/src/TFT/ST7789.h index 93df8b7..10ce60b 100644 --- a/firmware/src/TFT/ST7789.h +++ b/firmware/src/TFT/ST7789.h @@ -1,100 +1,10 @@ #pragma once -#include "driver/spi_master.h" #include "driver/gpio.h" -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" #include "TFTDisplay.h" -struct Glyph { - uint32_t unicode; // Unicode value of the glyph - int16_t width; // Width of the glyph bitmap bounding box - int16_t height; // Height of the glyph bitmap bounding box - int16_t gxAdvance; // Cursor advance after drawing this glyph - int16_t dX; // Distance from cursor to the left side of the glyph bitmap - int16_t dY; // Distance from the baseline to the top of the glyph bitmap - const uint8_t* bitmap; // Pointer to the glyph bitmap data -}; - -// Font data -struct Font { - uint32_t gCount; // Number of glyphs - uint32_t ascent; // Ascent in pixels from baseline to top of "d" - uint32_t descent; // Descent in pixels from baseline to bottom of "p" - const uint8_t* fontData; // Pointer to the raw VLW font data -}; - -class SPITransactionInfo; - class ST7789: public TFTDisplay { public: - // queue of transaction with a DMA buffer allocated - QueueHandle_t bigTransactionQueue; - // queue of transactions with no DMA buffer (they use the tx_data field) - QueueHandle_t smallTransactionQueue; - - int width; - int height; - uint8_t rotation; - ST7789(gpio_num_t mosi, gpio_num_t clk, gpio_num_t cs, gpio_num_t dc, gpio_num_t rst, gpio_num_t bl, int width = 240, int height = 320); ~ST7789(); - - void startWrite(){ - // nothing to do - } - void endWrite(){ - // nothing to do - } - void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); - void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); - void setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1); - void pushPixels(uint16_t *data, uint32_t len){ - sendPixels(data, len); - } - void pushPixelsDMA(uint16_t *data, uint32_t len) { - sendPixels(data, len); - } - void dmaWait(); - void drawString(const char *string, int16_t x, int16_t y); - Point measureString(const char *string); - void fillScreen(uint16_t color); - void loadFont(const uint8_t *font); - void setTextColor(uint16_t color, uint16_t bgColor); - void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); - void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); - void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color); - void drawPolygon(const std::vector& vertices, uint16_t color); - void drawFilledPolygon(const std::vector& vertices, uint16_t color); -private: - void init(); - void sendCmd(uint8_t cmd); - void sendData(const uint8_t *data, int len); - void sendPixel(uint16_t color); - void sendPixels(const uint16_t *data, int numPixels); - void sendColor(uint16_t color, int numPixels); - void setRotation(uint8_t m); - - volatile bool isBusy = false; - SPITransactionInfo *_transaction; - void sendTransaction(SPITransactionInfo *trans); - - // Text rendering - Glyph getGlyphData(uint32_t unicode); - void drawPixel(uint16_t color, int x, int y); - void drawGlyph(const Glyph& glyph, int x, int y); - - gpio_num_t mosi; - gpio_num_t clk; - gpio_num_t cs; - gpio_num_t dc; - gpio_num_t rst; - gpio_num_t bl; - spi_device_handle_t spi; - - uint16_t textcolor; - uint16_t textbgcolor; - - // The current font - Font currentFont; }; diff --git a/firmware/src/TFT/TFTDisplay.cpp b/firmware/src/TFT/TFTDisplay.cpp new file mode 100644 index 0000000..b89ef05 --- /dev/null +++ b/firmware/src/TFT/TFTDisplay.cpp @@ -0,0 +1,494 @@ +#include +#include "TFTDisplay.h" +#include +#include +#include + +#define ST7789_CMD_CASET 0x2A +#define ST7789_CMD_RASET 0x2B +#define ST7789_CMD_RAMWR 0x2C + +// max buffer size that we support - larger transfers will be split accross multiple transactions +const size_t DMA_BUFFER_SIZE = 32768; + +// helper functions +inline uint16_t swapBytes(uint16_t val) +{ + return (val >> 8) | (val << 8); +} + +uint32_t swapEndian32(uint32_t val) +{ + return ((val >> 24) & 0x000000FF) | + ((val >> 8) & 0x0000FF00) | + ((val << 8) & 0x00FF0000) | + ((val << 24) & 0xFF000000); +} + +int32_t readInt32(const uint8_t *data) +{ + int32_t val = *((int32_t *)data); + val = (int32_t)swapEndian32((uint32_t)val); + return val; +} + +uint32_t readUInt32(const uint8_t *data) +{ + uint32_t val = *((uint32_t *)data); + val = (uint32_t)swapEndian32((uint32_t)val); + return val; +} + +class SPITransactionInfo +{ +public: + spi_transaction_t transaction; + bool isCommand = false; // false = data, true = command + void *buffer = nullptr; // the DMA buffer + TFTDisplay *display = nullptr; // the display that the transaction was for + + SPITransactionInfo(size_t bufferSize) + { + memset(&transaction, 0, sizeof(transaction)); + transaction.user = this; + if (bufferSize > 0) + { + buffer = heap_caps_malloc(bufferSize, MALLOC_CAP_DMA); + } + } + + void setCommand(uint8_t cmd) + { + memset(&transaction, 0, sizeof(transaction)); + isCommand = true; + transaction.length = 8; // Command is 8 bits + transaction.tx_data[0] = cmd; + transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; + transaction.user = this; + } + + bool setData(const uint8_t *data, int len) + { + memset(&transaction, 0, sizeof(transaction)); + isCommand = false; + transaction.length = len * 8; // Data length in bits + transaction.user = this; + if (len <= 4) + { + for (int i = 0; i < len; i++) + { + transaction.tx_data[i] = data[i]; + } + transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; + } + else + { + memcpy(buffer, data, len); + transaction.tx_buffer = buffer; + } + return true; + } + + bool setPixels(const uint16_t *data, int numPixels) + { + return setData((const uint8_t *)data, numPixels * 2); + } + + bool setColor(uint16_t color, int numPixels) + { + uint16_t *pixels = (uint16_t *)buffer; + for (int i = 0; i < numPixels; i++) + { + pixels[i] = color; + } + memset(&transaction, 0, sizeof(transaction)); + isCommand = false; + transaction.length = numPixels * 16; // Data length in bits + transaction.tx_buffer = buffer; + transaction.user = this; + return true; + } +}; + +TFTDisplay::TFTDisplay(gpio_num_t mosi, gpio_num_t clk, gpio_num_t cs, gpio_num_t dc, gpio_num_t rst, gpio_num_t bl, int width, int height) + : _width(width), _height(height), mosi(mosi), clk(clk), cs(cs), dc(dc), rst(rst), bl(bl), spi(nullptr) +{ + gpio_set_direction(rst, GPIO_MODE_OUTPUT); + gpio_set_direction(dc, GPIO_MODE_OUTPUT); + if (bl != GPIO_NUM_NC) + { + gpio_set_direction(bl, GPIO_MODE_OUTPUT); + gpio_set_level(bl, 1); // Turn on backlight + } + _transaction = new SPITransactionInfo(DMA_BUFFER_SIZE); +} + +void TFTDisplay::sendCmd(uint8_t cmd) +{ + dmaWait(); + _transaction->setCommand(cmd); + sendTransaction(_transaction); +} + +void TFTDisplay::sendTransaction(SPITransactionInfo *trans) +{ + isBusy = true; + if (trans->isCommand) + { + gpio_set_level(dc, 0); // Command mode + } + else + { + gpio_set_level(dc, 1); // Data mode + } + spi_device_polling_start(spi, &trans->transaction, portMAX_DELAY); +} + +void TFTDisplay::sendPixels(const uint16_t *data, int numPixels) +{ + int bytes = numPixels * 2; + for (uint32_t i = 0; i < bytes; i += DMA_BUFFER_SIZE) + { + uint32_t len = std::min(DMA_BUFFER_SIZE, bytes - i); + dmaWait(); + _transaction->setPixels(data + i / 2, len / 2); + sendTransaction(_transaction); + } +} + +void TFTDisplay::sendData(const uint8_t *data, int length) +{ + for (uint32_t i = 0; i < length; i += DMA_BUFFER_SIZE) + { + uint32_t len = std::min(DMA_BUFFER_SIZE, length - i); + dmaWait(); + _transaction->setData(data + i, len); + sendTransaction(_transaction); + } +} + +void TFTDisplay::sendColor(uint16_t color, int numPixels) +{ + for (int i = 0; i < numPixels; i += DMA_BUFFER_SIZE >> 1) + { + int len = std::min(numPixels - i, int(DMA_BUFFER_SIZE >> 1)); + dmaWait(); + _transaction->setColor(color, len); + sendTransaction(_transaction); + } +} + +void TFTDisplay::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) +{ + uint8_t data[4]; + + sendCmd(ST7789_CMD_CASET); // Column address set + data[0] = (x0 >> 8) & 0xFF; + data[1] = x0 & 0xFF; + data[2] = (x1 >> 8) & 0xFF; + data[3] = x1 & 0xFF; + sendData(data, 4); + + sendCmd(ST7789_CMD_RASET); // Row address set + data[0] = (y0 >> 8) & 0xFF; + data[1] = y0 & 0xFF; + data[2] = (y1 >> 8) & 0xFF; + data[3] = y1 & 0xFF; + sendData(data, 4); + + sendCmd(ST7789_CMD_RAMWR); // Write to RAM +} + +void TFTDisplay::sendPixel(uint16_t color) +{ + uint16_t data[1]; + data[0] = color; + sendPixels(data, 1); +} + +void TFTDisplay::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) +{ + setWindow(x, y, x + w - 1, y + h - 1); + sendColor(SWAPBYTES(color), w * h); +} + +void TFTDisplay::drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) +{ + color = SWAPBYTES(color); + drawFastHLine(x, y, w, color); + drawFastHLine(x, y + h - 1, w, color); + drawFastVLine(x, y, h, color); + drawFastVLine(x + w - 1, y, h, color); +} + +void TFTDisplay::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) +{ + fillRect(x, y, w, 1, swapBytes(color)); +} + +void TFTDisplay::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) +{ + fillRect(x, y, 1, h, swapBytes(color)); +} + +void TFTDisplay::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) +{ + // make sure that the line is always drawn from left to right + if (x0 > x1) + { + std::swap(x0, x1); + std::swap(y0, y1); + } + int16_t dx = abs(x1 - x0); + int16_t dy = abs(y1 - y0); + int16_t sx = (x0 < x1) ? 1 : -1; + int16_t sy = (y0 < y1) ? 1 : -1; + int16_t err = dx - dy; + int16_t e2; + + while (true) + { + // Draw the current pixel + drawPixel(color, x0, y0); + + // If we've reached the end of the line, break out of the loop + if (x0 == x1 && y0 == y1) + { + break; + } + + e2 = err * 2; + + if (e2 > -dy) + { // Horizontal step + err -= dy; + x0 += sx; + } + + if (e2 < dx) + { // Vertical step + err += dx; + y0 += sy; + } + } +} + +void TFTDisplay::drawPolygon(const std::vector &vertices, uint16_t color) +{ + if (vertices.size() < 3) + { + return; // Not a polygon + } + for (size_t i = 0; i < vertices.size(); i++) + { + const Point &v1 = vertices[i]; + const Point &v2 = vertices[(i + 1) % vertices.size()]; + drawLine(v1.x, v1.y, v2.x, v2.y, color); + } +} + +void TFTDisplay::drawFilledPolygon(const std::vector &vertices, uint16_t color) +{ + if (vertices.size() < 3) + { + return; // Not a polygon + } + + // Find the bounding box of the polygon + int16_t minY = vertices[0].y; + int16_t maxY = vertices[0].y; + for (const auto &vertex : vertices) + { + minY = std::min(minY, vertex.y); + maxY = std::max(maxY, vertex.y); + } + + // Scanline fill algorithm + for (int16_t y = minY; y <= maxY; y++) + { + std::vector nodeX; + + // Find intersections of the scanline with polygon edges + for (size_t i = 0; i < vertices.size(); i++) + { + Point v1 = vertices[i]; + Point v2 = vertices[(i + 1) % vertices.size()]; + + if ((v1.y < y && v2.y >= y) || (v2.y < y && v1.y >= y)) + { + int16_t x = v1.x + (y - v1.y) * (v2.x - v1.x) / (v2.y - v1.y); + nodeX.push_back(x); + } + } + + // Sort the intersection points + std::sort(nodeX.begin(), nodeX.end()); + + // Draw horizontal lines between pairs of intersections + for (size_t i = 0; i < nodeX.size(); i += 2) + { + if (i + 1 < nodeX.size()) + { + drawFastHLine(nodeX[i], y, nodeX[i + 1] - nodeX[i] + 1, color); + } + } + } +} + +void TFTDisplay::fillScreen(uint16_t color) +{ + fillRect(0, 0, _width, _height, color); +} + +void TFTDisplay::loadFont(const uint8_t *fontData) +{ + if (currentFont.fontData == fontData) + { + return; + } + currentFont.fontData = fontData; + // Read font metadata with endianness correction + currentFont.gCount = readUInt32(fontData); + uint32_t version = readUInt32(fontData + 4); + uint32_t fontSize = readUInt32(fontData + 8); + uint32_t mboxY = readUInt32(fontData + 12); + currentFont.ascent = readUInt32(fontData + 16); + currentFont.descent = readUInt32(fontData + 20); +} + +void TFTDisplay::setTextColor(uint16_t color, uint16_t bgColor) +{ + textcolor = color; + textbgcolor = bgColor; +} + +void TFTDisplay::dmaWait() +{ + if (isBusy) + { + spi_device_polling_end(spi, portMAX_DELAY); + isBusy = false; + } +} + +Glyph TFTDisplay::getGlyphData(uint32_t unicode) +{ + const uint8_t *fontPtr = currentFont.fontData + 24; + const uint8_t *bitmapPtr = currentFont.fontData + 24 + currentFont.gCount * 28; + + for (uint32_t i = 0; i < currentFont.gCount; i++) + { + uint32_t glyphUnicode = readUInt32(fontPtr); + uint32_t height = readUInt32(fontPtr + 4); + uint32_t width = readUInt32(fontPtr + 8); + int32_t gxAdvance = readInt32(fontPtr + 12); + int32_t dY = readInt32(fontPtr + 16); + int32_t dX = readInt32(fontPtr + 20); + + if (glyphUnicode == unicode) + { + Glyph glyph; + glyph.unicode = glyphUnicode; + glyph.width = width; + glyph.height = height; + glyph.gxAdvance = gxAdvance; + glyph.dX = dX; + glyph.dY = dY; + glyph.bitmap = bitmapPtr; + return glyph; + } + + // Move to next glyph (28 bytes for metadata + bitmap size) + fontPtr += 28; + bitmapPtr += width * height; + } + + // Return default glyph if not found + Serial.printf("Glyph not found: %c\n", unicode); + return getGlyphData(' '); +} + +void TFTDisplay::drawPixel(uint16_t color, int x, int y) +{ + if (x < 0 || x >= _width || y < 0 || y >= _height) + { + return; + } + setWindow(x, y, x, y); + sendPixel(swapBytes(color)); +} + +void TFTDisplay::drawGlyph(const Glyph &glyph, int x, int y) +{ + // Iterate over each pixel in the glyph's bitmap + const uint8_t *bitmap = glyph.bitmap; + uint16_t pixelBuffer[glyph.width * glyph.height] = {textbgcolor}; + for (int j = 0; j < glyph.height; j++) + { + for (int i = 0; i < glyph.width; i++) + { + // Get the alpha value for the current pixel (1 byte per pixel) + uint8_t alpha = bitmap[j * glyph.width + i]; + // blend between the text color and the background color + uint16_t fg = textcolor; + uint16_t bg = textbgcolor; + // extract the red, green and blue + uint8_t fgRed = (fg >> 11) & 0x1F; + uint8_t fgGreen = (fg >> 5) & 0x3F; + uint8_t fgBlue = fg & 0x1F; + + uint8_t bgRed = (bg >> 11) & 0x1F; + uint8_t bgGreen = (bg >> 5) & 0x3F; + uint8_t bgBlue = bg & 0x1F; + + uint8_t red = ((fgRed * alpha) + (bgRed * (255 - alpha))) / 255; + uint8_t green = ((fgGreen * alpha) + (bgGreen * (255 - alpha))) / 255; + uint8_t blue = ((fgBlue * alpha) + (bgBlue * (255 - alpha))) / 255; + + uint16_t color = (red << 11) | (green << 5) | blue; + pixelBuffer[i + j * glyph.width] = swapBytes(color); + } + } + setWindow(x + glyph.dX, y - glyph.dY, x + glyph.dX + glyph.width - 1, y + glyph.dY + glyph.height - 1); + sendPixels(pixelBuffer, glyph.width * glyph.height); +} + +void TFTDisplay::drawString(const char *text, int16_t x, int16_t y) +{ + int cursorX = x; + int cursorY = y; + + while (*text) + { + char c = *text++; + + // Get the glyph data for the character + Glyph glyph = getGlyphData((uint32_t)c); + + // Draw the glyph bitmap at the current cursor position + drawGlyph(glyph, cursorX, cursorY + currentFont.ascent); + + // Move the cursor to the right by the glyph's gxAdvance value + cursorX += glyph.gxAdvance; + } +} + +Point TFTDisplay::measureString(const char *string) +{ + Point result = {0, 0}; + int cursorX = 0; + int cursorY = 0; + while (*string) + { + char c = *string++; + + // Get the glyph data for the character + Glyph glyph = getGlyphData((uint32_t)c); + + // Move the cursor to the right by the glyph's gxAdvance value + cursorX += glyph.gxAdvance; + result.y = std::max(result.y, glyph.height); + } + result.x = cursorX; + return result; +} diff --git a/firmware/src/TFT/TFTDisplay.h b/firmware/src/TFT/TFTDisplay.h index 88f0995..8fe5e48 100644 --- a/firmware/src/TFT/TFTDisplay.h +++ b/firmware/src/TFT/TFTDisplay.h @@ -1,42 +1,135 @@ #pragma once +#include "driver/spi_master.h" +#include "driver/gpio.h" #include -#include + +#define TFT_WHITE 0xFFFF +#define TFT_BLACK 0x0000 +#define TFT_RED 0xF800 +#define TFT_GREEN 0x07E0 + +#define CMD_MADCTL 0x36 +#define MADCTL_MY 0x80 +#define MADCTL_MX 0x40 +#define MADCTL_MV 0x20 +#define MADCTL_ML 0x10 +#define MADCTL_RGB 0x00 +#define MADCTL_BGR 0x08 + +#define SEND_CMD_DATA(cmd, data...) \ +{ \ + const uint8_t c = cmd, x[] = {data}; \ + sendCmd(c); \ + if (sizeof(x)) \ + sendData(x, sizeof(x)); \ +} #define SWAPBYTES(i) ((i >> 8) | (i << 8)) -struct Point { - int16_t x; - int16_t y; +struct Point +{ + int16_t x; + int16_t y; +}; + +struct Glyph +{ + uint32_t unicode; // Unicode value of the glyph + int16_t width; // Width of the glyph bitmap bounding box + int16_t height; // Height of the glyph bitmap bounding box + int16_t gxAdvance; // Cursor advance after drawing this glyph + int16_t dX; // Distance from cursor to the left side of the glyph bitmap + int16_t dY; // Distance from the baseline to the top of the glyph bitmap + const uint8_t *bitmap; // Pointer to the glyph bitmap data }; -class TFTDisplay { +// Font data +struct Font +{ + uint32_t gCount; // Number of glyphs + uint32_t ascent; // Ascent in pixels from baseline to top of "d" + uint32_t descent; // Descent in pixels from baseline to bottom of "p" + const uint8_t *fontData; // Pointer to the raw VLW font data +}; + +class SPITransactionInfo; + +class TFTDisplay +{ public: - TFTDisplay() {}; - virtual void startWrite() = 0; - virtual void endWrite() = 0; - virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) = 0; - virtual void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) = 0; - virtual void setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) = 0; - virtual void pushPixels(uint16_t *data, uint32_t len) = 0; - virtual void pushPixelsDMA(uint16_t *data, uint32_t len) = 0; - virtual void dmaWait() = 0; - virtual void drawString(const char *string, int16_t x, int16_t y) = 0; - virtual Point measureString(const char *string) = 0; - virtual void fillScreen(uint16_t color) = 0; - virtual void loadFont(const uint8_t *font) = 0; - virtual void setTextColor(uint16_t color, uint16_t bgColor) = 0; - virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) = 0; - virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) = 0; - virtual void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) = 0; - virtual void drawPolygon(const std::vector& vertices, uint16_t color) = 0; - virtual void drawFilledPolygon(const std::vector& vertices, uint16_t color) = 0; - virtual uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { + TFTDisplay(gpio_num_t mosi, gpio_num_t clk, gpio_num_t cs, gpio_num_t dc, gpio_num_t rst, gpio_num_t bl, int width, int height); + void startWrite() + { + // nothing to do + } + void endWrite() + { + // nothing to do + } + void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); + void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); + void setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1); + void pushPixels(uint16_t *data, uint32_t len) + { + sendPixels(data, len); + } + void pushPixelsDMA(uint16_t *data, uint32_t len) + { + sendPixels(data, len); + } + void dmaWait(); + void drawString(const char *string, int16_t x, int16_t y); + Point measureString(const char *string); + void fillScreen(uint16_t color); + void loadFont(const uint8_t *font); + void setTextColor(uint16_t color, uint16_t bgColor); + void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); + void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color); + void drawPolygon(const std::vector &vertices, uint16_t color); + void drawFilledPolygon(const std::vector &vertices, uint16_t color); + virtual uint16_t color565(uint8_t r, uint8_t g, uint8_t b) + { // Convert 8-8-8 RGB to 5-6-5 RGB uint16_t r2 = r >> 3; uint16_t g2 = g >> 2; uint16_t b2 = b >> 3; return (r2 << 11) | (g2 << 5) | b2; } - virtual int width() { return TFT_HEIGHT;} - virtual int height() { return TFT_WIDTH; } + virtual int width() { return _width; } + virtual int height() { return _height; } + +protected: + int _width; + int _height; + + void init(); + void sendCmd(uint8_t cmd); + void sendData(const uint8_t *data, int len); + void sendPixel(uint16_t color); + void sendPixels(const uint16_t *data, int numPixels); + void sendColor(uint16_t color, int numPixels); + + volatile bool isBusy = false; + SPITransactionInfo *_transaction; + void sendTransaction(SPITransactionInfo *trans); + + // Text rendering + Glyph getGlyphData(uint32_t unicode); + void drawPixel(uint16_t color, int x, int y); + void drawGlyph(const Glyph &glyph, int x, int y); + + gpio_num_t mosi; + gpio_num_t clk; + gpio_num_t cs; + gpio_num_t dc; + gpio_num_t rst; + gpio_num_t bl; + spi_device_handle_t spi; + + uint16_t textcolor; + uint16_t textbgcolor; + + // The current font + Font currentFont; }; \ No newline at end of file diff --git a/firmware/src/TFT/TFTeSPIWrapper.h b/firmware/src/TFT/TFTeSPIWrapper.h deleted file mode 100644 index 45f3b0b..0000000 --- a/firmware/src/TFT/TFTeSPIWrapper.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "TFTDisplay.h" - -class TFT_eSPI; -class TFTeSPIWrapper : public TFTDisplay -{ -private: - TFT_eSPI *tft; - -public: - TFTeSPIWrapper() - { -#ifdef TFT_POWER - // turn on the TFT - pinMode(TFT_POWER, OUTPUT); - digitalWrite(TFT_POWER, LOW); -#endif - tft = new TFT_eSPI(); - tft->begin(); -#ifdef USE_DMA - tft->initDMA(); // Initialise the DMA engine -#endif -#ifdef TFT_ROTATION - tft->setRotation(TFT_ROTATION); -#else - tft->setRotation(3); -#endif - tft->fillScreen(TFT_BLACK); - } - void startWrite() { - tft->startWrite(); - } - void endWrite() { - tft->endWrite(); - } - void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { - tft->fillRect(x, y, w, h, color); - } - void setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { - tft->setWindow(x0, y0, x1, y1); - } - void pushPixels(uint16_t *data, uint32_t len) { - tft->pushPixels(data, len); - } - void pushPixelsDMA(uint16_t *data, uint32_t len) { - tft->pushPixelsDMA(data, len); - } - void dmaWait() { - tft->dmaWait(); - } - void drawString(const char *string, int16_t x, int16_t y) { - tft->drawString(string, x, y); - } - void fillScreen(uint16_t color) { - tft->fillScreen(color); - } - void loadFont(const uint8_t *font) { - tft->loadFont(font); - - } - void setTextColor(uint16_t color, uint16_t bgColor) { - tft->setTextColor(color, bgColor); - } - void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { - tft->drawFastHLine(x, y, w, color); - } - inline uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { - return tft->color565(r, g, b); - } - inline int width() { - return tft->width(); - } - inline int height() { - return tft->height(); - } -}; \ No newline at end of file diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index b0c5f0e..03dba00 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -29,8 +29,8 @@ #include "Input/SerialKeyboard.h" #include "Input/Nunchuck.h" #include "TFT/TFTDisplay.h" -#include "TFT/TFTeSPIWrapper.h" #include "TFT/ST7789.h" +#include "TFT/ILI9341.h" #ifdef TOUCH_KEYBOARD #include "Input/TouchKeyboard.h" #endif @@ -104,17 +104,20 @@ void setup(void) touchKeyboard->start(); #endif audioOutput->start(15625); + #ifdef POWER_PIN + pinMode(POWER_PIN, OUTPUT); + digitalWrite(POWER_PIN, POWER_PIN_ON); + vTaskDelay(100); + #endif + #ifdef TFT_ST7789 TFTDisplay *tft = new ST7789(TFT_MOSI, TFT_SCLK, TFT_CS, TFT_DC, TFT_RST, TFT_BL, 320, 240); - // tft = new TFTeSPIWrapper(); + // TFTDisplay *tft = new ST7789(TFT_MOSI, TFT_SCLK, TFT_CS, TFT_DC, TFT_RST, TFT_BL, 280, 240); + #endif + #ifdef TFT_ILI9341 + TFTDisplay *tft = new ILI9341(TFT_MOSI, TFT_SCLK, TFT_CS, TFT_DC, TFT_RST, TFT_BL, 320, 240); + #endif // Files #ifdef USE_SDCARD -#ifdef SD_CARD_PWR - if (SD_CARD_PWR != GPIO_NUM_NC) - { - pinMode(SD_CARD_PWR, OUTPUT); - digitalWrite(SD_CARD_PWR, SD_CARD_PWR_ON); - } -#endif #ifdef USE_SDIO SDCard *fileSystem = new SDCard(MOUNT_POINT, SD_CARD_CLK, SD_CARD_CMD, SD_CARD_D0, SD_CARD_D1, SD_CARD_D2, SD_CARD_D3); Files *files = new Files(fileSystem); @@ -132,7 +135,7 @@ void setup(void) navigationStack->push(&menuPicker); // start off the keyboard and feed keys into the active scene SerialKeyboard *keyboard = new SerialKeyboard([&](SpecKeys key, bool down) - { navigationStack->updatekey(key, down); }); + { navigationStack->updatekey(key, down); if (down) { navigationStack->pressKey(key); } }); // start up the nunchuk controller and feed events into the active screen #ifdef NUNCHUK_CLOCK