esp-who/components/lcd/spi_lcd.c

258 lines
9.5 KiB
C
Raw Normal View History

2018-10-18 15:52:33 +08:00
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sys/param.h>
#include "spi_lcd.h"
#include "driver/gpio.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/xtensa_api.h"
#include "freertos/task.h"
#define SPIFIFOSIZE 16
/*
This struct stores a bunch of command values to be initialized for ILI9341
*/
typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;
DRAM_ATTR static const lcd_init_cmd_t ili_init_cmds[]={
{0xCF, {0x00, 0x83, 0x30}, 3},
{0xED, {0x64, 0x03, 0x12, 0x81}, 4},
{0xE8, {0x85, 0x01, 0x79}, 3},
{0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5},
{0xF7, {0x20}, 1},
{0xEA, {0x00, 0x00}, 2},
{0xC0, {0x26}, 1},
{0xC1, {0x11}, 1},
{0xC5, {0x35, 0x3E}, 2},
{0xC7, {0xBE}, 1},
{0x36, {0x28}, 1},
{0x3A, {0x55}, 1},
{0xB1, {0x00, 0x1B}, 2},
{0xF2, {0x08}, 1},
{0x26, {0x01}, 1},
{0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15},
{0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15},
{0x2A, {0x00, 0x00, 0x00, 0xEF}, 4},
{0x2B, {0x00, 0x00, 0x01, 0x3f}, 4},
{0x2C, {0}, 0},
{0xB7, {0x07}, 1},
{0xB6, {0x0A, 0x82, 0x27, 0x00}, 4},
{0x11, {0}, 0x80},
{0x29, {0}, 0x80},
{0, {0}, 0xff},
};
DRAM_ATTR static const lcd_init_cmd_t st7789_init_cmds[] = {
{0xC0, {0x00}, 1}, //LCMCTRL: LCM Control [2C] //sumpremely related to 0x36, MADCTL
{0xC2, {0x01, 0xFF}, 2}, //VDVVRHEN: VDV and VRH Command Enable [01 FF]
{0xC3, {0x13}, 1}, //VRHS: VRH Set VAP=???, VAN=-??? [0B]
{0xC4, {0x20}, 1}, //VDVS: VDV Set [20]
{0xC6, {0x0F}, 1}, //FRCTRL2: Frame Rate control in normal mode [0F]
{0xCA, {0x0F}, 1}, //REGSEL2 [0F]
{0xC8, {0x08}, 1}, //REGSEL1 [08]
{0x55, {0xB0}, 1}, //WRCACE [00]
{0x36, {0x00}, 1},
{0x3A, {0x55}, 1}, //this says 0x05
{0xB1, {0x40, 0x02, 0x14}, 3}, //sync setting not reqd
{0x26, {0x01}, 1},
{0x2A, {0x00, 0x00, 0x00, 0xEF}, 4},
{0x2B, {0x00, 0x00, 0x01, 0x3F}, 4},
{0x2C, {0x00}, 1},
{0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19}, 14}, //PVGAMCTRL: Positive Voltage Gamma control
{0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19}, 14}, //NVGAMCTRL: Negative Voltage Gamma control
{0x11, {0}, 0x80},
{0x29, {0}, 0x80},
{0, {0}, 0xff},
};
#define LCD_CMD_LEV (0)
#define LCD_DATA_LEV (1)
/*This function is called (in irq context!) just before a transmission starts.
It will set the D/C line to the value indicated in the user field */
void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
{
lcd_dc_t *dc = (lcd_dc_t *) t->user;
gpio_set_level((int)dc->dc_io, (int)dc->dc_level);
}
static SemaphoreHandle_t _spi_mux = NULL;
static esp_err_t _lcd_spi_send(spi_device_handle_t spi, spi_transaction_t* t)
{
xSemaphoreTake(_spi_mux, portMAX_DELAY);
esp_err_t res = spi_device_transmit(spi, t); //Transmit!
xSemaphoreGive(_spi_mux);
return res;
}
void lcd_cmd(spi_device_handle_t spi, const uint8_t cmd, lcd_dc_t *dc)
{
esp_err_t ret;
dc->dc_level = LCD_CMD_LEV;
spi_transaction_t t = {
.length = 8, // Command is 8 bits
.tx_buffer = &cmd, // The data is the cmd itself
.user = (void *) dc, // D/C needs to be set to 0
};
ret = _lcd_spi_send(spi, &t); // Transmit!
assert(ret == ESP_OK); // Should have had no issues.
}
void lcd_data(spi_device_handle_t spi, const uint8_t *data, int len, lcd_dc_t *dc)
{
esp_err_t ret;
if (len == 0) {
return; //no need to send anything
}
dc->dc_level = LCD_DATA_LEV;
spi_transaction_t t = {
.length = len * 8, // Len is in bytes, transaction length is in bits.
.tx_buffer = data, // Data
.user = (void *) dc, // D/C needs to be set to 1
};
ret = _lcd_spi_send(spi, &t); // Transmit!
assert(ret == ESP_OK); // Should have had no issues.
}
uint32_t lcd_init(lcd_conf_t* lcd_conf, spi_device_handle_t *spi_wr_dev, lcd_dc_t *dc, int dma_chan)
{
if (_spi_mux == NULL) {
_spi_mux = xSemaphoreCreateMutex();
}
//Initialize non-SPI GPIOs
gpio_pad_select_gpio(lcd_conf->pin_num_dc);
gpio_set_direction(lcd_conf->pin_num_dc, GPIO_MODE_OUTPUT);
//Reset the display
if (lcd_conf->pin_num_rst < GPIO_NUM_MAX) {
gpio_pad_select_gpio(lcd_conf->pin_num_rst);
gpio_set_direction(lcd_conf->pin_num_rst, GPIO_MODE_OUTPUT);
gpio_set_level(lcd_conf->pin_num_rst, (lcd_conf->rst_active_level) & 0x1);
vTaskDelay(100 / portTICK_RATE_MS);
gpio_set_level(lcd_conf->pin_num_rst, (~(lcd_conf->rst_active_level)) & 0x1);
vTaskDelay(100 / portTICK_RATE_MS);
}
if (lcd_conf->init_spi_bus) {
//Initialize SPI Bus for LCD
spi_bus_config_t buscfg = {
.miso_io_num = lcd_conf->pin_num_miso,
.mosi_io_num = lcd_conf->pin_num_mosi,
.sclk_io_num = lcd_conf->pin_num_clk,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_bus_initialize(lcd_conf->spi_host, &buscfg, dma_chan);
}
spi_device_interface_config_t devcfg = {
// Use low speed to read ID.
.clock_speed_hz = 1 * 1000 * 1000, //Clock out frequency
.mode = 0, //SPI mode 0
.spics_io_num = lcd_conf->pin_num_cs, //CS pin
.queue_size = 7, //We want to be able to queue 7 transactions at a time
.pre_cb = lcd_spi_pre_transfer_callback, //Specify pre-transfer callback to handle D/C line
};
spi_device_handle_t rd_id_handle;
spi_bus_add_device(lcd_conf->spi_host, &devcfg, &rd_id_handle);
uint32_t lcd_id = lcd_get_id(rd_id_handle, dc);
spi_bus_remove_device(rd_id_handle);
// Use high speed to write LCD
devcfg.clock_speed_hz = lcd_conf->clk_freq;
spi_bus_add_device(lcd_conf->spi_host, &devcfg, spi_wr_dev);
int cmd = 0;
const lcd_init_cmd_t* lcd_init_cmds = NULL;
if(lcd_conf->lcd_model == LCD_MOD_ST7789) {
lcd_init_cmds = st7789_init_cmds;
} else if(lcd_conf->lcd_model == LCD_MOD_ILI9341) {
lcd_init_cmds = ili_init_cmds;
} else if(lcd_conf->lcd_model == LCD_MOD_AUTO_DET) {
if (((lcd_id >> 8) & 0xff) == 0x42) {
lcd_init_cmds = st7789_init_cmds;
} else {
lcd_init_cmds = ili_init_cmds;
}
}
assert(lcd_init_cmds != NULL);
//Send all the commands
while (lcd_init_cmds[cmd].databytes!=0xff) {
lcd_cmd(*spi_wr_dev, lcd_init_cmds[cmd].cmd, dc);
lcd_data(*spi_wr_dev, lcd_init_cmds[cmd].data, lcd_init_cmds[cmd].databytes&0x1F, dc);
if (lcd_init_cmds[cmd].databytes&0x80) {
vTaskDelay(100 / portTICK_RATE_MS);
}
cmd++;
}
//Enable backlight
if (lcd_conf->pin_num_bckl < GPIO_NUM_MAX) {
gpio_pad_select_gpio(lcd_conf->pin_num_bckl);
gpio_set_direction(lcd_conf->pin_num_bckl, GPIO_MODE_OUTPUT);
gpio_set_level(lcd_conf->pin_num_bckl, (lcd_conf->bckl_active_level) & 0x1);
}
return lcd_id;
}
void lcd_send_uint16_r(spi_device_handle_t spi, const uint16_t data, int32_t repeats, lcd_dc_t *dc)
{
uint32_t i;
uint32_t word = data << 16 | data;
uint32_t word_tmp[16];
spi_transaction_t t;
dc->dc_level = LCD_DATA_LEV;
while (repeats > 0) {
uint16_t bytes_to_transfer = MIN(repeats * sizeof(uint16_t), SPIFIFOSIZE * sizeof(uint32_t));
for (i = 0; i < (bytes_to_transfer + 3) / 4; i++) {
word_tmp[i] = word;
}
memset(&t, 0, sizeof(t)); //Zero out the transaction
t.length = bytes_to_transfer * 8; //Len is in bytes, transaction length is in bits.
t.tx_buffer = word_tmp; //Data
t.user = (void *) dc; //D/C needs to be set to 1
_lcd_spi_send(spi, &t); //Transmit!
repeats -= bytes_to_transfer / 2;
}
}
uint32_t lcd_get_id(spi_device_handle_t spi, lcd_dc_t *dc)
{
//get_id cmd
lcd_cmd( spi, 0x04, dc);
spi_transaction_t t;
dc->dc_level = LCD_DATA_LEV;
memset(&t, 0, sizeof(t));
t.length = 8 * 4;
t.flags = SPI_TRANS_USE_RXDATA;
t.user = (void *) dc;
esp_err_t ret = _lcd_spi_send(spi, &t);
assert( ret == ESP_OK );
return *(uint32_t*) t.rx_data;
}