279 lines
8.1 KiB
C
279 lines
8.1 KiB
C
#include "ili9341.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "driver/gpio.h"
|
|
#include "driver/spi_master.h"
|
|
#include "esp_check.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
#include "font5x7.h"
|
|
|
|
#define TFT_PIN_MISO GPIO_NUM_12
|
|
#define TFT_PIN_MOSI GPIO_NUM_13
|
|
#define TFT_PIN_SCLK GPIO_NUM_14
|
|
#define TFT_PIN_CS GPIO_NUM_15
|
|
#define TFT_PIN_DC GPIO_NUM_2
|
|
#define TFT_PIN_BL GPIO_NUM_21
|
|
|
|
// Rotated additional 90° CCW from previous baseline; logical drawing space is 320x240.
|
|
#define TFT_WIDTH 320
|
|
#define TFT_HEIGHT 240
|
|
|
|
#define ILI9341_SWRESET 0x01
|
|
#define ILI9341_SLPOUT 0x11
|
|
#define ILI9341_GAMMASET 0x26
|
|
#define ILI9341_DISPON 0x29
|
|
#define ILI9341_CASET 0x2A
|
|
#define ILI9341_PASET 0x2B
|
|
#define ILI9341_RAMWR 0x2C
|
|
#define ILI9341_MADCTL 0x36
|
|
#define ILI9341_COLMOD 0x3A
|
|
#define ILI9341_INVOFF 0x20
|
|
|
|
static const char *TAG = "ili9341";
|
|
|
|
static spi_device_handle_t s_spi;
|
|
static bool s_initialized;
|
|
|
|
static esp_err_t ili9341_write_cmd(uint8_t cmd)
|
|
{
|
|
gpio_set_level(TFT_PIN_DC, 0);
|
|
|
|
spi_transaction_t t = {
|
|
.length = 8,
|
|
.tx_buffer = &cmd,
|
|
};
|
|
|
|
return spi_device_polling_transmit(s_spi, &t);
|
|
}
|
|
|
|
static esp_err_t ili9341_write_data(const void *data, size_t len)
|
|
{
|
|
if (len == 0) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
gpio_set_level(TFT_PIN_DC, 1);
|
|
|
|
spi_transaction_t t = {
|
|
.length = len * 8,
|
|
.tx_buffer = data,
|
|
};
|
|
|
|
return spi_device_polling_transmit(s_spi, &t);
|
|
}
|
|
|
|
static esp_err_t ili9341_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
|
|
{
|
|
uint8_t data[4];
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_CASET), TAG, "CASET failed");
|
|
data[0] = (uint8_t)(x0 >> 8);
|
|
data[1] = (uint8_t)(x0 & 0xFF);
|
|
data[2] = (uint8_t)(x1 >> 8);
|
|
data[3] = (uint8_t)(x1 & 0xFF);
|
|
ESP_RETURN_ON_ERROR(ili9341_write_data(data, sizeof(data)), TAG, "CASET data failed");
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_PASET), TAG, "PASET failed");
|
|
data[0] = (uint8_t)(y0 >> 8);
|
|
data[1] = (uint8_t)(y0 & 0xFF);
|
|
data[2] = (uint8_t)(y1 >> 8);
|
|
data[3] = (uint8_t)(y1 & 0xFF);
|
|
ESP_RETURN_ON_ERROR(ili9341_write_data(data, sizeof(data)), TAG, "PASET data failed");
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_RAMWR), TAG, "RAMWR failed");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t ili9341_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
|
|
{
|
|
if (x >= TFT_WIDTH || y >= TFT_HEIGHT) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
uint8_t pix[2] = {
|
|
(uint8_t)(color >> 8),
|
|
(uint8_t)(color & 0xFF),
|
|
};
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_set_window(x, y, x, y), TAG, "set_window failed");
|
|
ESP_RETURN_ON_ERROR(ili9341_write_data(pix, sizeof(pix)), TAG, "pixel write failed");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t ili9341_blit_rgb565(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *rgb565_be)
|
|
{
|
|
if (w == 0 || h == 0 || rgb565_be == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
uint16_t x1 = (uint16_t)(x + w - 1);
|
|
uint16_t y1 = (uint16_t)(y + h - 1);
|
|
|
|
if (x >= TFT_WIDTH || y >= TFT_HEIGHT || x1 >= TFT_WIDTH || y1 >= TFT_HEIGHT) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_set_window(x, y, x1, y1), TAG, "set_window failed");
|
|
return ili9341_write_data(rgb565_be, (size_t)w * h * 2);
|
|
}
|
|
|
|
esp_err_t ili9341_init(void)
|
|
{
|
|
if (s_initialized) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
gpio_config_t dc_cfg = {
|
|
.pin_bit_mask = 1ULL << TFT_PIN_DC,
|
|
.mode = GPIO_MODE_OUTPUT,
|
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
.intr_type = GPIO_INTR_DISABLE,
|
|
};
|
|
ESP_RETURN_ON_ERROR(gpio_config(&dc_cfg), TAG, "DC gpio config failed");
|
|
|
|
gpio_config_t bl_cfg = {
|
|
.pin_bit_mask = 1ULL << TFT_PIN_BL,
|
|
.mode = GPIO_MODE_OUTPUT,
|
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
.intr_type = GPIO_INTR_DISABLE,
|
|
};
|
|
ESP_RETURN_ON_ERROR(gpio_config(&bl_cfg), TAG, "BL gpio config failed");
|
|
gpio_set_level(TFT_PIN_BL, 0);
|
|
|
|
spi_bus_config_t buscfg = {
|
|
.mosi_io_num = TFT_PIN_MOSI,
|
|
.miso_io_num = TFT_PIN_MISO,
|
|
.sclk_io_num = TFT_PIN_SCLK,
|
|
.quadwp_io_num = -1,
|
|
.quadhd_io_num = -1,
|
|
.max_transfer_sz = TFT_WIDTH * 20 * 2,
|
|
};
|
|
ESP_RETURN_ON_ERROR(spi_bus_initialize(HSPI_HOST, &buscfg, SPI_DMA_CH_AUTO), TAG, "spi_bus_initialize failed");
|
|
|
|
spi_device_interface_config_t devcfg = {
|
|
.clock_speed_hz = 40 * 1000 * 1000,
|
|
.mode = 0,
|
|
.spics_io_num = TFT_PIN_CS,
|
|
.queue_size = 4,
|
|
};
|
|
ESP_RETURN_ON_ERROR(spi_bus_add_device(HSPI_HOST, &devcfg, &s_spi), TAG, "spi_bus_add_device failed");
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_SWRESET), TAG, "SWRESET failed");
|
|
vTaskDelay(pdMS_TO_TICKS(120));
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_SLPOUT), TAG, "SLPOUT failed");
|
|
vTaskDelay(pdMS_TO_TICKS(120));
|
|
|
|
const uint8_t colmod = 0x55;
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_COLMOD), TAG, "COLMOD cmd failed");
|
|
ESP_RETURN_ON_ERROR(ili9341_write_data(&colmod, 1), TAG, "COLMOD data failed");
|
|
|
|
const uint8_t madctl = 0x28; // MV | BGR (180° from previous 320x240 orientation)
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_MADCTL), TAG, "MADCTL cmd failed");
|
|
ESP_RETURN_ON_ERROR(ili9341_write_data(&madctl, 1), TAG, "MADCTL data failed");
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_INVOFF), TAG, "INVOFF failed");
|
|
|
|
const uint8_t gamma_2 = 0x02;
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_GAMMASET), TAG, "GAMMASET #1 cmd failed");
|
|
ESP_RETURN_ON_ERROR(ili9341_write_data(&gamma_2, 1), TAG, "GAMMASET #1 data failed");
|
|
vTaskDelay(pdMS_TO_TICKS(120));
|
|
|
|
const uint8_t gamma_1 = 0x01;
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_GAMMASET), TAG, "GAMMASET #2 cmd failed");
|
|
ESP_RETURN_ON_ERROR(ili9341_write_data(&gamma_1, 1), TAG, "GAMMASET #2 data failed");
|
|
|
|
ESP_RETURN_ON_ERROR(ili9341_write_cmd(ILI9341_DISPON), TAG, "DISPON failed");
|
|
vTaskDelay(pdMS_TO_TICKS(20));
|
|
|
|
gpio_set_level(TFT_PIN_BL, 1);
|
|
|
|
s_initialized = true;
|
|
ESP_LOGI(TAG, "ILI9341 initialized (320x240, HSPI @ 40MHz)");
|
|
return ESP_OK;
|
|
}
|
|
|
|
void ili9341_fill_screen(uint16_t color)
|
|
{
|
|
if (!s_initialized) {
|
|
return;
|
|
}
|
|
|
|
const int chunk_lines = 20;
|
|
const size_t chunk_bytes = TFT_WIDTH * chunk_lines * 2;
|
|
uint8_t *buf = (uint8_t *)malloc(chunk_bytes);
|
|
if (!buf) {
|
|
ESP_LOGE(TAG, "malloc failed in fill_screen");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < TFT_WIDTH * chunk_lines; ++i) {
|
|
buf[i * 2 + 0] = (uint8_t)(color >> 8);
|
|
buf[i * 2 + 1] = (uint8_t)(color & 0xFF);
|
|
}
|
|
|
|
for (int y = 0; y < TFT_HEIGHT; y += chunk_lines) {
|
|
int y_end = y + chunk_lines - 1;
|
|
if (y_end >= TFT_HEIGHT) {
|
|
y_end = TFT_HEIGHT - 1;
|
|
}
|
|
|
|
int lines = y_end - y + 1;
|
|
size_t bytes = (size_t)TFT_WIDTH * lines * 2;
|
|
|
|
if (ili9341_set_window(0, (uint16_t)y, TFT_WIDTH - 1, (uint16_t)y_end) == ESP_OK) {
|
|
(void)ili9341_write_data(buf, bytes);
|
|
}
|
|
}
|
|
|
|
free(buf);
|
|
}
|
|
|
|
static void ili9341_draw_char(int x, int y, char c, uint16_t fg, uint16_t bg)
|
|
{
|
|
uint8_t cols[5];
|
|
if (!font5x7_get(c, cols)) {
|
|
c = ' ';
|
|
(void)font5x7_get(c, cols);
|
|
}
|
|
|
|
for (int cx = 0; cx < 5; ++cx) {
|
|
uint8_t col = cols[cx];
|
|
for (int cy = 0; cy < 8; ++cy) {
|
|
bool on = (col >> cy) & 0x01;
|
|
uint16_t color = on ? fg : bg;
|
|
(void)ili9341_draw_pixel((uint16_t)(x + cx), (uint16_t)(y + cy), color);
|
|
}
|
|
}
|
|
|
|
for (int cy = 0; cy < 8; ++cy) {
|
|
(void)ili9341_draw_pixel((uint16_t)(x + 5), (uint16_t)(y + cy), bg);
|
|
}
|
|
}
|
|
|
|
void ili9341_draw_text(int x, int y, const char *text, uint16_t fg, uint16_t bg)
|
|
{
|
|
if (!s_initialized || text == NULL) {
|
|
return;
|
|
}
|
|
|
|
int cursor_x = x;
|
|
while (*text) {
|
|
if (cursor_x + 6 >= TFT_WIDTH) {
|
|
break;
|
|
}
|
|
ili9341_draw_char(cursor_x, y, *text, fg, bg);
|
|
cursor_x += 6;
|
|
++text;
|
|
}
|
|
}
|