components/tft/tftspi.c

Sat, 06 Jun 2020 13:28:46 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Sat, 06 Jun 2020 13:28:46 +0200
changeset 77
66c77497d86d
parent 34
5c92103c5e72
permissions
-rw-r--r--

Changed the recipe database so that it is expandable, version 2. More mash fields and allow 16 steps. Allow 20 Additions. Removed separate mash steps from the state machine, the steps are moved to the runtime data. There is no fixed step number for mashout anymore. There is no fixed step for mash-in anymore, just use the first step and heat to the infusion temperature. After malt add, switch to the normal step temperature. Implemented decoction steps.

/*
 *  Author: LoBo (loboris@gmail.com, loboris.github)
 *
 *  Module supporting SPI TFT displays based on ILI9341 & ILI9488 controllers
 * 
 * HIGH SPEED LOW LEVEL DISPLAY FUNCTIONS
 * USING DIRECT or DMA SPI TRANSFER MODEs
 *
*/

#include <string.h>
#include "tftspi.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include "freertos/task.h"
#include "esp_heap_caps.h"
#include "soc/spi_reg.h"

// ====================================================
// ==== Global variables, default values ==============

// Converts colors to grayscale if set to 1
uint8_t gray_scale = 0;
// Spi clock for reading data from display memory in Hz
uint32_t max_rdclock = 8000000;

// Default display dimensions
int _width = DEFAULT_TFT_DISPLAY_WIDTH;
int _height = DEFAULT_TFT_DISPLAY_HEIGHT;

// Display type, DISP_TYPE_ILI9488 or DISP_TYPE_ILI9341
uint8_t tft_disp_type = DEFAULT_DISP_TYPE;

// Spi device handles for display and touch screen
spi_lobo_device_handle_t disp_spi = NULL;
spi_lobo_device_handle_t ts_spi = NULL;

// ====================================================


static color_t *trans_cline = NULL;
static uint8_t _dma_sending = 0;

// RGB to GRAYSCALE constants
// 0.2989  0.5870  0.1140
#define GS_FACT_R 0.2989
#define GS_FACT_G 0.4870
#define GS_FACT_B 0.2140



// ==== Functions =====================

//------------------------------------------------------
esp_err_t IRAM_ATTR wait_trans_finish(uint8_t free_line)
{
	// Wait for SPI bus ready
	while (disp_spi->host->hw->cmd.usr);
	if ((free_line) && (trans_cline)) {
		free(trans_cline);
		trans_cline = NULL;
	}
	if (_dma_sending) {
	    //Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset.
	    if (disp_spi->host->dma_chan) spi_lobo_dmaworkaround_idle(disp_spi->host->dma_chan);

	    // Reset DMA
		disp_spi->host->hw->dma_conf.val |= SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST;
		disp_spi->host->hw->dma_out_link.start=0;
		disp_spi->host->hw->dma_in_link.start=0;
		disp_spi->host->hw->dma_conf.val &= ~(SPI_OUT_RST|SPI_IN_RST|SPI_AHBM_RST|SPI_AHBM_FIFO_RST);
		disp_spi->host->hw->dma_conf.out_data_burst_en=1;
		_dma_sending = 0;
	}
    return ESP_OK;
}

//-------------------------------
esp_err_t IRAM_ATTR disp_select()
{
	wait_trans_finish(1);
	return spi_lobo_device_select(disp_spi, 0);
}

//---------------------------------
esp_err_t IRAM_ATTR disp_deselect()
{
	wait_trans_finish(1);
	return spi_lobo_device_deselect(disp_spi);
}

//---------------------------------------------------------------------------------------------------
static void IRAM_ATTR _spi_transfer_start(spi_lobo_device_handle_t spi_dev, int wrbits, int rdbits) {
	// Load send buffer
	spi_dev->host->hw->user.usr_mosi_highpart = 0;
	spi_dev->host->hw->mosi_dlen.usr_mosi_dbitlen = wrbits-1;
	spi_dev->host->hw->user.usr_mosi = 1;
    if (rdbits) {
        spi_dev->host->hw->miso_dlen.usr_miso_dbitlen = rdbits;
        spi_dev->host->hw->user.usr_miso = 1;
    }
    else {
        spi_dev->host->hw->miso_dlen.usr_miso_dbitlen = 0;
        spi_dev->host->hw->user.usr_miso = 0;
    }
	// Start transfer
	spi_dev->host->hw->cmd.usr = 1;
    // Wait for SPI bus ready
	while (spi_dev->host->hw->cmd.usr);
}

// Send 1 byte display command, display must be selected
//------------------------------------------------
void IRAM_ATTR disp_spi_transfer_cmd(int8_t cmd) {
	// Wait for SPI bus ready
	while (disp_spi->host->hw->cmd.usr);

	// Set DC to 0 (command mode);
    gpio_set_level(PIN_NUM_DC, 0);

    disp_spi->host->hw->data_buf[0] = (uint32_t)cmd;
    _spi_transfer_start(disp_spi, 8, 0);
}

// Send command with data to display, display must be selected
//----------------------------------------------------------------------------------
void IRAM_ATTR disp_spi_transfer_cmd_data(int8_t cmd, uint8_t *data, uint32_t len) {
	// Wait for SPI bus ready
	while (disp_spi->host->hw->cmd.usr);

    // Set DC to 0 (command mode);
    gpio_set_level(PIN_NUM_DC, 0);

    disp_spi->host->hw->data_buf[0] = (uint32_t)cmd;
    _spi_transfer_start(disp_spi, 8, 0);

	if ((len == 0) || (data == NULL)) return;

    // Set DC to 1 (data mode);
	gpio_set_level(PIN_NUM_DC, 1);

	uint8_t idx=0, bidx=0;
	uint32_t bits=0;
	uint32_t count=0;
	uint32_t wd = 0;
	while (count < len) {
		// get data byte from buffer
		wd |= (uint32_t)data[count] << bidx;
    	count++;
    	bits += 8;
		bidx += 8;
    	if (count == len) {
    		disp_spi->host->hw->data_buf[idx] = wd;
    		break;
    	}
		if (bidx == 32) {
			disp_spi->host->hw->data_buf[idx] = wd;
			idx++;
			bidx = 0;
			wd = 0;
		}
    	if (idx == 16) {
    		// SPI buffer full, send data
			_spi_transfer_start(disp_spi, bits, 0);
    		
			bits = 0;
    		idx = 0;
			bidx = 0;
    	}
    }
    if (bits > 0) _spi_transfer_start(disp_spi, bits, 0);
}

// Set the address window for display write & read commands, display must be selected
//---------------------------------------------------------------------------------------------------
static void IRAM_ATTR disp_spi_transfer_addrwin(uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2) {
	uint32_t wd;

    taskDISABLE_INTERRUPTS();
	// Wait for SPI bus ready
	while (disp_spi->host->hw->cmd.usr);
    gpio_set_level(PIN_NUM_DC, 0);

	disp_spi->host->hw->data_buf[0] = (uint32_t)TFT_CASET;
	disp_spi->host->hw->user.usr_mosi_highpart = 0;
	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 7;
	disp_spi->host->hw->user.usr_mosi = 1;
	disp_spi->host->hw->miso_dlen.usr_miso_dbitlen = 0;
	disp_spi->host->hw->user.usr_miso = 0;

	disp_spi->host->hw->cmd.usr = 1; // Start transfer

	wd = (uint32_t)(x1>>8);
	wd |= (uint32_t)(x1&0xff) << 8;
	wd |= (uint32_t)(x2>>8) << 16;
	wd |= (uint32_t)(x2&0xff) << 24;

	while (disp_spi->host->hw->cmd.usr); // wait transfer end
	gpio_set_level(PIN_NUM_DC, 1);
	disp_spi->host->hw->data_buf[0] = wd;
	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 31;
	disp_spi->host->hw->cmd.usr = 1; // Start transfer

    while (disp_spi->host->hw->cmd.usr);
    gpio_set_level(PIN_NUM_DC, 0);
    disp_spi->host->hw->data_buf[0] = (uint32_t)TFT_PASET;
	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 7;
	disp_spi->host->hw->cmd.usr = 1; // Start transfer

	wd = (uint32_t)(y1>>8);
	wd |= (uint32_t)(y1&0xff) << 8;
	wd |= (uint32_t)(y2>>8) << 16;
	wd |= (uint32_t)(y2&0xff) << 24;

	while (disp_spi->host->hw->cmd.usr);
	gpio_set_level(PIN_NUM_DC, 1);

	disp_spi->host->hw->data_buf[0] = wd;
	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 31;
	disp_spi->host->hw->cmd.usr = 1; // Start transfer
	while (disp_spi->host->hw->cmd.usr);
    taskENABLE_INTERRUPTS();
}

// Convert color to gray scale
//----------------------------------------------
static color_t IRAM_ATTR color2gs(color_t color)
{
	color_t _color;
    float gs_clr = GS_FACT_R * color.r + GS_FACT_G * color.g + GS_FACT_B * color.b;
    if (gs_clr > 255) gs_clr = 255;

    _color.r = (uint8_t)gs_clr;
    _color.g = (uint8_t)gs_clr;
    _color.b = (uint8_t)gs_clr;

    return _color;
}

// Set display pixel at given coordinates to given color
//------------------------------------------------------------------------
void IRAM_ATTR drawPixel(int16_t x, int16_t y, color_t color, uint8_t sel)
{
	if (!(disp_spi->cfg.flags & LB_SPI_DEVICE_HALFDUPLEX)) return;

	if (sel) {
		if (disp_select()) return;
	}
	else wait_trans_finish(1);

	uint32_t wd = 0;
    color_t _color = color;
	if (gray_scale) _color = color2gs(color);

    taskDISABLE_INTERRUPTS();
	disp_spi_transfer_addrwin(x, x+1, y, y+1);

	// Send RAM WRITE command
    gpio_set_level(PIN_NUM_DC, 0);
    disp_spi->host->hw->data_buf[0] = (uint32_t)TFT_RAMWR;
	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 7;
	disp_spi->host->hw->cmd.usr = 1;		// Start transfer
	while (disp_spi->host->hw->cmd.usr);	// Wait for SPI bus ready

	wd = (uint32_t)_color.r;
	wd |= (uint32_t)_color.g << 8;
	wd |= (uint32_t)_color.b << 16;

    // Set DC to 1 (data mode);
	gpio_set_level(PIN_NUM_DC, 1);

	disp_spi->host->hw->data_buf[0] = wd;
	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 23;
	disp_spi->host->hw->cmd.usr = 1;		// Start transfer
	while (disp_spi->host->hw->cmd.usr);	// Wait for SPI bus ready

    taskENABLE_INTERRUPTS();
   if (sel) disp_deselect();
}

//-----------------------------------------------------------
static void IRAM_ATTR _dma_send(uint8_t *data, uint32_t size)
{
    //Fill DMA descriptors
    spi_lobo_dmaworkaround_transfer_active(disp_spi->host->dma_chan); //mark channel as active
    spi_lobo_setup_dma_desc_links(disp_spi->host->dmadesc_tx, size, data, false);
    disp_spi->host->hw->user.usr_mosi_highpart=0;
    disp_spi->host->hw->dma_out_link.addr=(int)(&disp_spi->host->dmadesc_tx[0]) & 0xFFFFF;
    disp_spi->host->hw->dma_out_link.start=1;
    disp_spi->host->hw->user.usr_mosi_highpart=0;

	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = (size * 8) - 1;

	_dma_sending = 1;
	// Start transfer
	disp_spi->host->hw->cmd.usr = 1;
}

//---------------------------------------------------------------------------
static void IRAM_ATTR _direct_send(color_t *color, uint32_t len, uint8_t rep)
{
	uint32_t cidx = 0;	// color buffer index
	uint32_t wd = 0;
	int idx = 0;
	int bits = 0;
	int wbits = 0;

    taskDISABLE_INTERRUPTS();
	color_t _color = color[0];
	if ((rep) && (gray_scale)) _color = color2gs(color[0]);

	while (len) {
		// ** Get color data from color buffer **
		if (rep == 0) {
			if (gray_scale) _color = color2gs(color[cidx]);
			else _color = color[cidx];
		}

		wd |= (uint32_t)_color.r << wbits;
		wbits += 8;
		if (wbits == 32) {
			bits += wbits;
			wbits = 0;
			disp_spi->host->hw->data_buf[idx++] = wd;
			wd = 0;
		}
		wd |= (uint32_t)_color.g << wbits;
		wbits += 8;
		if (wbits == 32) {
			bits += wbits;
			wbits = 0;
			disp_spi->host->hw->data_buf[idx++] = wd;
			wd = 0;
		}
		wd |= (uint32_t)_color.b << wbits;
		wbits += 8;
		if (wbits == 32) {
			bits += wbits;
			wbits = 0;
			disp_spi->host->hw->data_buf[idx++] = wd;
			wd = 0;
		}
    	len--;					// Decrement colors counter
        if (rep == 0) cidx++;	// if not repeating color, increment color buffer index
    }
	if (bits) {
		while (disp_spi->host->hw->cmd.usr);						// Wait for SPI bus ready
		disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = bits-1;	// set number of bits to be sent
        disp_spi->host->hw->cmd.usr = 1;							// Start transfer
	}
    taskENABLE_INTERRUPTS();
}

// ================================================================
// === Main function to send data to display ======================
// If  rep==true:  repeat sending color data to display 'len' times
// If rep==false:  send 'len' color data from color buffer to display
// ** Device must already be selected and address window set **
// ================================================================
//----------------------------------------------------------------------------------------------
static void IRAM_ATTR _TFT_pushColorRep(color_t *color, uint32_t len, uint8_t rep, uint8_t wait)
{
	if (len == 0) return;
	if (!(disp_spi->cfg.flags & LB_SPI_DEVICE_HALFDUPLEX)) return;

	// Send RAM WRITE command
    gpio_set_level(PIN_NUM_DC, 0);
    disp_spi->host->hw->data_buf[0] = (uint32_t)TFT_RAMWR;
	disp_spi->host->hw->mosi_dlen.usr_mosi_dbitlen = 7;
	disp_spi->host->hw->cmd.usr = 1;		// Start transfer
	while (disp_spi->host->hw->cmd.usr);	// Wait for SPI bus ready

	gpio_set_level(PIN_NUM_DC, 1);								// Set DC to 1 (data mode);

	if ((len*24) <= 512) {

		_direct_send(color, len, rep);

	}
	else if (rep == 0)  {
		// ==== use DMA transfer ====
		// ** Prepare data
		if (gray_scale) {
			for (int n=0; n<len; n++) {
				color[n] = color2gs(color[n]);
			}
	    }

	    _dma_send((uint8_t *)color, len*3);
	}
	else {
		// ==== Repeat color, more than 512 bits total ====

		color_t _color;
		uint32_t buf_colors;
		int buf_bytes, to_send;

		/*
		to_send = len;
		while (to_send > 0) {
			wait_trans_finish(0);
			_direct_send(color, ((to_send > 21) ? 21 : to_send), rep);
			to_send -= 21;
		}
		*/

		buf_colors = ((len > (_width*2)) ? (_width*2) : len);
		buf_bytes = buf_colors * 3;

		// Prepare color buffer of maximum 2 color lines
		trans_cline = heap_caps_malloc(buf_bytes, MALLOC_CAP_DMA);
		if (trans_cline == NULL) return;

		// Prepare fill color
		if (gray_scale) _color = color2gs(color[0]);
		else _color = color[0];

		// Fill color buffer with fill color
		for (uint32_t i=0; i<buf_colors; i++) {
			trans_cline[i] = _color;
		}

		// Send 'len' colors
		to_send = len;
		while (to_send > 0) {
			wait_trans_finish(0);
			_dma_send((uint8_t *)trans_cline, ((to_send > buf_colors) ? buf_bytes : (to_send*3)));
			to_send -= buf_colors;
		}
	}

	if (wait) wait_trans_finish(1);
}

// Write 'len' color data to TFT 'window' (x1,y2),(x2,y2)
//-------------------------------------------------------------------------------------------
void IRAM_ATTR TFT_pushColorRep(int x1, int y1, int x2, int y2, color_t color, uint32_t len)
{
	if (disp_select() != ESP_OK) return;

	// ** Send address window **
	disp_spi_transfer_addrwin(x1, x2, y1, y2);

	_TFT_pushColorRep(&color, len, 1, 1);

	disp_deselect();
}

// Write 'len' color data to TFT 'window' (x1,y2),(x2,y2) from given buffer
// ** Device must already be selected **
//-----------------------------------------------------------------------------------
void IRAM_ATTR send_data(int x1, int y1, int x2, int y2, uint32_t len, color_t *buf)
{
	// ** Send address window **
	disp_spi_transfer_addrwin(x1, x2, y1, y2);
	_TFT_pushColorRep(buf, len, 0, 0);
}

// Reads 'len' pixels/colors from the TFT's GRAM 'window'
// 'buf' is an array of bytes with 1st byte reserved for reading 1 dummy byte
// and the rest is actually an array of color_t values
//--------------------------------------------------------------------------------------------
int IRAM_ATTR read_data(int x1, int y1, int x2, int y2, int len, uint8_t *buf, uint8_t set_sp)
{
	spi_lobo_transaction_t t;
	uint32_t current_clock = 0;

    memset(&t, 0, sizeof(t));  //Zero out the transaction
	memset(buf, 0, len*sizeof(color_t));

	if (set_sp) {
		if (disp_deselect() != ESP_OK) return -1;
		// Change spi clock if needed
		current_clock = spi_lobo_get_speed(disp_spi);
		if (max_rdclock < current_clock) spi_lobo_set_speed(disp_spi, max_rdclock);
	}

	if (disp_select() != ESP_OK) return -2;

	// ** Send address window **
	disp_spi_transfer_addrwin(x1, x2, y1, y2);

    // ** GET pixels/colors **
	disp_spi_transfer_cmd(TFT_RAMRD);

    t.length=0;                //Send nothing
    t.tx_buffer=NULL;
    t.rxlength=8*((len*3)+1);  //Receive size in bits
    t.rx_buffer=buf;
    //t.user = (void*)1;

	esp_err_t res = spi_lobo_transfer_data(disp_spi, &t); // Receive using direct mode

	disp_deselect();

	if (set_sp) {
		// Restore spi clock if needed
		if (max_rdclock < current_clock) spi_lobo_set_speed(disp_spi, current_clock);
	}

    return res;
}

// Reads one pixel/color from the TFT's GRAM at position (x,y)
//-----------------------------------------------
color_t IRAM_ATTR readPixel(int16_t x, int16_t y)
{
    uint8_t color_buf[sizeof(color_t)+1] = {0};

    read_data(x, y, x+1, y+1, 1, color_buf, 1);

    color_t color;
	color.r = color_buf[1];
	color.g = color_buf[2];
	color.b = color_buf[3];
	return color;
}

// get 16-bit data from touch controller for specified type
// ** Touch device must already be selected **
//----------------------------------------
int IRAM_ATTR touch_get_data(uint8_t type)
{
    /*
    esp_err_t ret;
    spi_lobo_transaction_t t;
    memset(&t, 0, sizeof(t));            //Zero out the transaction
    uint8_t rxdata[2] = {0};

    // send command byte & receive 2 byte response
    t.rxlength=8*2;
    t.rx_buffer=&rxdata;
    t.command = type;

    ret = spi_lobo_transfer_data(ts_spi, &t);    // Transmit using direct mode

    if (ret != ESP_OK) res = -1;
    res = (((int)(rxdata[0] << 8) | (int)(rxdata[1])) >> 4);
*/
//    spi_lobo_device_select(ts_spi, 0);

    ts_spi->host->hw->data_buf[0] = type;
    _spi_transfer_start(ts_spi, 24, 24);

//    printf("touch_get_data(%02X) %06x %02x %02x %02x ", type, ts_spi->host->hw->data_buf[0],
//		    (ts_spi->host->hw->data_buf[0] & 0x0ff), 
//		    (ts_spi->host->hw->data_buf[0] >> 11 & 0x0ff), 
//		    (ts_spi->host->hw->data_buf[0] >> 19 & 0x0ff) );
    uint16_t res = (uint16_t)(((ts_spi->host->hw->data_buf[0] >> 11 & 0x0ff) << 8) | (ts_spi->host->hw->data_buf[0] >> 19 & 0x0ff));
//    printf("res=%d %04x\n", res, res);
//    spi_lobo_device_deselect(ts_spi);
    return res;
}

// ==== STMPE610 ===============================================================


// ----- STMPE610 --------------------------------------------------------------------------

// Send 1 byte display command, display must be selected
//---------------------------------------------------------
static void IRAM_ATTR stmpe610_write_reg(uint8_t reg, uint8_t val) {

    spi_lobo_device_select(ts_spi, 0);

    ts_spi->host->hw->data_buf[0] = (val << 8) | reg;
    _spi_transfer_start(ts_spi, 16, 0);

    spi_lobo_device_deselect(ts_spi);
}

//-----------------------------------------------
static uint8_t IRAM_ATTR stmpe610_read_byte(uint8_t reg) {
    spi_lobo_device_select(ts_spi, 0);

    ts_spi->host->hw->data_buf[0] = (reg << 8) | (reg | 0x80);
    _spi_transfer_start(ts_spi, 16, 16);
    uint8_t res = ts_spi->host->hw->data_buf[0] >> 8;

    spi_lobo_device_deselect(ts_spi);
    return res;
}

//-----------------------------------------
static uint16_t IRAM_ATTR stmpe610_read_word(uint8_t reg) {
    spi_lobo_device_select(ts_spi, 0);

    ts_spi->host->hw->data_buf[0] = ((((reg+1) << 8) | ((reg+1) | 0x80)) << 16) | (reg << 8) | (reg | 0x80);
    _spi_transfer_start(ts_spi, 32, 32);
    uint16_t res = (uint16_t)(ts_spi->host->hw->data_buf[0] & 0xFF00);
    res |= (uint16_t)(ts_spi->host->hw->data_buf[0] >> 24);

    spi_lobo_device_deselect(ts_spi);
    return res;
}

//-----------------------
uint32_t stmpe610_getID()
{
    uint16_t tid = stmpe610_read_word(0);
    uint8_t tver = stmpe610_read_byte(2);
    return (tid << 8) | tver;
}

//==================
void stmpe610_Init()
{
    stmpe610_write_reg(STMPE610_REG_SYS_CTRL1, 0x02);        // Software chip reset
    vTaskDelay(10 / portTICK_RATE_MS);

    stmpe610_write_reg(STMPE610_REG_SYS_CTRL2, 0x04);        // Temperature sensor clock off, GPIO clock off, touch clock on, ADC clock on

    stmpe610_write_reg(STMPE610_REG_INT_EN, 0x00);           // Don't Interrupt on INT pin

    stmpe610_write_reg(STMPE610_REG_ADC_CTRL1, 0x48);        // ADC conversion time = 80 clock ticks, 12-bit ADC, internal voltage refernce
    vTaskDelay(2 / portTICK_RATE_MS);
    stmpe610_write_reg(STMPE610_REG_ADC_CTRL2, 0x01);        // ADC speed 3.25MHz
    stmpe610_write_reg(STMPE610_REG_GPIO_AF, 0x00);          // GPIO alternate function - OFF
    stmpe610_write_reg(STMPE610_REG_TSC_CFG, 0xE3);          // Averaging 8, touch detect delay 1ms, panel driver settling time 1ms
    stmpe610_write_reg(STMPE610_REG_FIFO_TH, 0x01);          // FIFO threshold = 1
    stmpe610_write_reg(STMPE610_REG_FIFO_STA, 0x01);         // FIFO reset enable
    stmpe610_write_reg(STMPE610_REG_FIFO_STA, 0x00);         // FIFO reset disable
    stmpe610_write_reg(STMPE610_REG_TSC_FRACT_XYZ, 0x07);    // Z axis data format
    stmpe610_write_reg(STMPE610_REG_TSC_I_DRIVE, 0x01);      // max 50mA touchscreen line current
    stmpe610_write_reg(STMPE610_REG_TSC_CTRL, 0x30);         // X&Y&Z, 16 reading window
    stmpe610_write_reg(STMPE610_REG_TSC_CTRL, 0x31);         // X&Y&Z, 16 reading window, TSC enable
    stmpe610_write_reg(STMPE610_REG_INT_STA, 0xFF);          // Clear all interrupts
    stmpe610_write_reg(STMPE610_REG_INT_CTRL, 0x00);         // Level interrupt, disable interrupts
}

//===========================================================
int stmpe610_get_touch(uint16_t *x, uint16_t *y, uint16_t *z)
{
	if (!(stmpe610_read_byte(STMPE610_REG_TSC_CTRL) & 0x80)) return 0;

    // Get touch data
    uint8_t fifo_size = stmpe610_read_byte(STMPE610_REG_FIFO_SIZE);
    while (fifo_size < 2) {
    	if (!(stmpe610_read_byte(STMPE610_REG_TSC_CTRL) & 0x80)) return 0;
        fifo_size = stmpe610_read_byte(STMPE610_REG_FIFO_SIZE);
    }
    while (fifo_size > 120) {
    	if (!(stmpe610_read_byte(STMPE610_REG_TSC_CTRL) & 0x80)) return 0;
        *x = stmpe610_read_word(STMPE610_REG_TSC_DATA_X);
        *y = stmpe610_read_word(STMPE610_REG_TSC_DATA_Y);
        *z = stmpe610_read_byte(STMPE610_REG_TSC_DATA_Z);
        fifo_size = stmpe610_read_byte(STMPE610_REG_FIFO_SIZE);
    }
    for (uint8_t i=0; i < (fifo_size-1); i++) {
        *x = stmpe610_read_word(STMPE610_REG_TSC_DATA_X);
        *y = stmpe610_read_word(STMPE610_REG_TSC_DATA_Y);
        *z = stmpe610_read_byte(STMPE610_REG_TSC_DATA_Z);
    }

    *x = 4096 - *x;
    /*
    // Clear the rest of the fifo
    {
        stmpe610_write_reg(STMPE610_REG_FIFO_STA, 0x01);		// FIFO reset enable
        stmpe610_write_reg(STMPE610_REG_FIFO_STA, 0x00);		// FIFO reset disable
    }
    */
	return 1;
}

// ==== STMPE610 ===========================================================================


// Find maximum spi clock for successful read from display RAM
// ** Must be used AFTER the display is initialized **
//======================
uint32_t find_rd_speed()
{
	esp_err_t ret;
	color_t color;
	uint32_t max_speed = 1000000;
    uint32_t change_speed, cur_speed;
    int line_check;
    color_t *color_line = NULL;
    uint8_t *line_rdbuf = NULL;
    uint8_t gs = gray_scale;

    gray_scale = 0;
    cur_speed = spi_lobo_get_speed(disp_spi);

	color_line = malloc(_width*3);
    if (color_line == NULL) goto exit;

    line_rdbuf = malloc((_width*3)+1);
	if (line_rdbuf == NULL) goto exit;

	color_t *rdline = (color_t *)(line_rdbuf+1);

	// Fill test line with colors
	color = (color_t){0xEC,0xA8,0x74};
	for (int x=0; x<_width; x++) {
		color_line[x] = color;
	}

	// Find maximum read spi clock
	for (uint32_t speed=2000000; speed<=cur_speed; speed += 1000000) {
		change_speed = spi_lobo_set_speed(disp_spi, speed);
		if (change_speed == 0) goto exit;

		memset(line_rdbuf, 0, _width*sizeof(color_t)+1);

		if (disp_select()) goto exit;
		// Write color line
		send_data(0, _height/2, _width-1, _height/2, _width, color_line);
		if (disp_deselect()) goto exit;

		// Read color line
		ret = read_data(0, _height/2, _width-1, _height/2, _width, line_rdbuf, 0);

		// Compare
		line_check = 0;
		if (ret == ESP_OK) {
			for (int y=0; y<_width; y++) {
				if ((color_line[y].r & 0xFC) != (rdline[y].r & 0xFC)) line_check = 1;
				else if ((color_line[y].g & 0xFC) != (rdline[y].g & 0xFC)) line_check = 1;
				else if ((color_line[y].b & 0xFC) != (rdline[y].b & 0xFC)) line_check =  1;
				if (line_check) break;
			}
		}
		else line_check = ret;

		if (line_check) break;
		max_speed = speed;
	}

exit:
    gray_scale = gs;
	if (line_rdbuf) free(line_rdbuf);
	if (color_line) free(color_line);

	// restore spi clk
	change_speed = spi_lobo_set_speed(disp_spi, cur_speed);

	return max_speed;
}

//---------------------------------------------------------------------------
// Companion code to the initialization table.
// Reads and issues a series of LCD commands stored in byte array
//---------------------------------------------------------------------------
static void commandList(spi_lobo_device_handle_t spi, const uint8_t *addr) {
  uint8_t  numCommands, numArgs, cmd;
  uint16_t ms;

  numCommands = *addr++;				// Number of commands to follow
  while(numCommands--) {				// For each command...
    cmd = *addr++;						// save command
    numArgs  = *addr++;					// Number of args to follow
    ms       = numArgs & TFT_CMD_DELAY;	// If high bit set, delay follows args
    numArgs &= ~TFT_CMD_DELAY;			// Mask out delay bit

	disp_spi_transfer_cmd_data(cmd, (uint8_t *)addr, numArgs);

	addr += numArgs;

    if(ms) {
      ms = *addr++;              // Read post-command delay time (ms)
      if(ms == 255) ms = 500;    // If 255, delay for 500 ms
	  vTaskDelay(ms / portTICK_RATE_MS);
    }
  }
}

//==================================
void _tft_setRotation(uint8_t rot) {
	uint8_t rotation = rot & 3; // can't be higher than 3
	uint8_t send = 1;
	uint8_t madctl = 0;
	uint16_t tmp;

    if ((rotation & 1)) {
        // in landscape modes must be width > height
        if (_width < _height) {
            tmp = _width;
            _width  = _height;
            _height = tmp;
        }
    }
    else {
        // in portrait modes must be width < height
        if (_width > _height) {
            tmp = _width;
            _width  = _height;
            _height = tmp;
        }
    }
    #if TFT_INVERT_ROTATION
    switch (rotation) {
        case PORTRAIT:
        madctl = (MADCTL_MV | TFT_RGB_BGR);
        break;
        case LANDSCAPE:
        madctl = (MADCTL_MX | TFT_RGB_BGR);
        break;
        case PORTRAIT_FLIP:
        madctl = (MADCTL_MV | TFT_RGB_BGR);
        break;
        case LANDSCAPE_FLIP:
        madctl = (MADCTL_MY | TFT_RGB_BGR);
        break;
    }
    #elif TFT_INVERT_ROTATION1
    switch (rotation) {
        case PORTRAIT:
        madctl = (MADCTL_MY | MADCTL_MX | TFT_RGB_BGR);
        break;
        case LANDSCAPE:
        madctl = (MADCTL_MY | MADCTL_MV | TFT_RGB_BGR);
        break;
        case PORTRAIT_FLIP:
        madctl = (TFT_RGB_BGR);
        break;
        case LANDSCAPE_FLIP:
        madctl = (MADCTL_MX | MADCTL_MV | TFT_RGB_BGR);
        break;
    }
    #elif TFT_INVERT_ROTATION2
    switch (rotation) {
        case PORTRAIT:
        madctl = (MADCTL_MX | MADCTL_MV | TFT_RGB_BGR);
        break;
        case LANDSCAPE:
        madctl = (TFT_RGB_BGR);
        break;
        case PORTRAIT_FLIP:
        madctl = (MADCTL_MY | MADCTL_MV | TFT_RGB_BGR);
        break;
        case LANDSCAPE_FLIP:
        madctl = (MADCTL_MY | MADCTL_MX | TFT_RGB_BGR);
        break;
    }
    #else
    switch (rotation) {
        case PORTRAIT:
        madctl = (MADCTL_MX | TFT_RGB_BGR);
        break;
        case LANDSCAPE:
        madctl = (MADCTL_MV | TFT_RGB_BGR);
        break;
        case PORTRAIT_FLIP:
        madctl = (MADCTL_MY | TFT_RGB_BGR);
        break;
        case LANDSCAPE_FLIP:
        madctl = (MADCTL_MX | MADCTL_MY | MADCTL_MV | TFT_RGB_BGR);
        break;
    }
    #endif
	if (send) {
		if (disp_select() == ESP_OK) {
			disp_spi_transfer_cmd_data(TFT_MADCTL, &madctl, 1);
			disp_deselect();
		}
	}

}

//=================
void TFT_PinsInit()
{
    // Route all used pins to GPIO control
    gpio_pad_select_gpio(PIN_NUM_CS);
    gpio_pad_select_gpio(PIN_NUM_MISO);
    gpio_pad_select_gpio(PIN_NUM_MOSI);
    gpio_pad_select_gpio(PIN_NUM_CLK);
    gpio_pad_select_gpio(PIN_NUM_DC);

    gpio_set_direction(PIN_NUM_MISO, GPIO_MODE_INPUT);
    gpio_set_pull_mode(PIN_NUM_MISO, GPIO_PULLUP_ONLY);
    gpio_set_direction(PIN_NUM_CS, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_MOSI, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_CLK, GPIO_MODE_OUTPUT);
    gpio_set_direction(PIN_NUM_DC, GPIO_MODE_OUTPUT);
    gpio_set_level(PIN_NUM_DC, 0);
#if USE_TOUCH
    gpio_pad_select_gpio(PIN_NUM_TCS);
    gpio_set_direction(PIN_NUM_TCS, GPIO_MODE_OUTPUT);
#endif    
#if PIN_NUM_BCKL
    gpio_pad_select_gpio(PIN_NUM_BCKL);
    gpio_set_direction(PIN_NUM_BCKL, GPIO_MODE_OUTPUT);
    gpio_set_level(PIN_NUM_BCKL, PIN_BCKL_OFF);
#endif

#if PIN_NUM_RST
    gpio_pad_select_gpio(PIN_NUM_RST);
    gpio_set_direction(PIN_NUM_RST, GPIO_MODE_OUTPUT);
    gpio_set_level(PIN_NUM_RST, 0);
#endif
}

// Initialize the display
// ====================
void TFT_display_init()
{
    esp_err_t ret;

#if PIN_NUM_RST
    //Reset the display
    gpio_set_level(PIN_NUM_RST, 0);
    vTaskDelay(20 / portTICK_RATE_MS);
    gpio_set_level(PIN_NUM_RST, 1);
    vTaskDelay(150 / portTICK_RATE_MS);
#endif

    ret = disp_select();
    assert(ret==ESP_OK);
    //Send all the initialization commands
	if (tft_disp_type == DISP_TYPE_ILI9341) {
		commandList(disp_spi, ILI9341_init);
	}
	else if (tft_disp_type == DISP_TYPE_ILI9488) {
		commandList(disp_spi, ILI9488_init);
	}
	else if (tft_disp_type == DISP_TYPE_ST7789V) {
		commandList(disp_spi, ST7789V_init);
	}
	else if (tft_disp_type == DISP_TYPE_ST7735) {
		commandList(disp_spi, STP7735_init);
	}
	else if (tft_disp_type == DISP_TYPE_ST7735R) {
		commandList(disp_spi, STP7735R_init);
		commandList(disp_spi, Rcmd2green);
		commandList(disp_spi, Rcmd3);
	}
	else if (tft_disp_type == DISP_TYPE_ST7735B) {
		commandList(disp_spi, STP7735R_init);
		commandList(disp_spi, Rcmd2red);
		commandList(disp_spi, Rcmd3);
	    uint8_t dt = 0xC0;
		disp_spi_transfer_cmd_data(TFT_MADCTL, &dt, 1);
	}
	else assert(0);

    ret = disp_deselect();
	assert(ret==ESP_OK);

	// Clear screen
    _tft_setRotation(PORTRAIT);
	TFT_pushColorRep(0, 0, _width-1, _height-1, (color_t){0,0,0}, (uint32_t)(_height*_width));

	///Enable backlight
#if PIN_NUM_BCKL
    gpio_set_level(PIN_NUM_BCKL, PIN_BCKL_ON);
#endif
}

mercurial