528 lines
12 KiB
C++
528 lines
12 KiB
C++
#include "daisy.h"
|
|
|
|
#include "tft.hpp"
|
|
|
|
using namespace daisy;
|
|
|
|
|
|
static tft_cmd init_cmds[] = {
|
|
tft_cmd(0xEF, {0x03, 0x80, 0x02}),
|
|
tft_cmd(0xCF, {0x00, 0xC1, 0x30}),
|
|
tft_cmd(0xED, {0x64, 0x03, 0x12, 0x81}),
|
|
tft_cmd(0xE8, {0x85, 0x00, 0x78}),
|
|
tft_cmd(0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}),
|
|
tft_cmd(0xF7, {0x20}),
|
|
tft_cmd(0xEA, {0x00, 0x00}),
|
|
tft_cmd(ILI9341_PWCTR1, {0x23}), // Power control VRH[5:0]
|
|
tft_cmd(ILI9341_PWCTR2, {0x10}), // Power control SAP[2:0];BT[3:0]
|
|
tft_cmd(ILI9341_VMCTR1, {0x3e, 0x28}), // VCM control
|
|
tft_cmd(ILI9341_VMCTR2, {0x86}), // VCM control2
|
|
tft_cmd(ILI9341_MADCTL, {0x08}), // Memory Access Control
|
|
tft_cmd(ILI9341_VSCRSADD, {0x00}), // Vertical scroll zero
|
|
tft_cmd(ILI9341_PIXFMT, {0x55}), // 16 bit pixel fmt
|
|
tft_cmd(ILI9341_FRMCTR1, {0x00, 0x18}),
|
|
tft_cmd(ILI9341_DFUNCTR, {0x08, 0x82, 0x27}), // Display Function Control
|
|
tft_cmd(0xF2, {0x00}), // 3Gamma Function Disable
|
|
tft_cmd(ILI9341_GAMMASET, {0x01}), // Gamma curve selected
|
|
tft_cmd(ILI9341_GMCTRP1, {0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
|
|
0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00}),
|
|
tft_cmd(ILI9341_GMCTRN1, {0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
|
|
0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F}),
|
|
tft_cmd(ILI9341_SLPOUT, 150), // Exit Sleep
|
|
tft_cmd(ILI9341_DISPON, 150), // Display on
|
|
};
|
|
|
|
static tft_cmd RESET_CMD(ILI9341_SWRESET, 150);
|
|
|
|
int
|
|
tft_driver_ili9341::init(uint8_t *dma_buf, size_t dma_sz)
|
|
{
|
|
|
|
AC_PRECOND(_async_stat == async_status::INIT);
|
|
|
|
SpiHandle::Result res;
|
|
|
|
dcx_pin = daisy::seed::D12; // Pin(PORTB, 8);
|
|
dcx.Init(dcx_pin, GPIO::Mode::OUTPUT, GPIO::Pull::PULLUP,
|
|
GPIO::Speed::VERY_HIGH);
|
|
|
|
spi_conf.datasize = 8;
|
|
|
|
spi_conf.mode = SpiHandle::Config::Mode::MASTER;
|
|
spi_conf.direction = SpiHandle::Config::Direction::TWO_LINES;
|
|
|
|
spi_conf.clock_polarity = SpiHandle::Config::ClockPolarity::LOW;
|
|
spi_conf.clock_phase = SpiHandle::Config::ClockPhase::ONE_EDGE;
|
|
|
|
spi_conf.baud_prescaler = SpiHandle::Config::BaudPrescaler::PS_2;
|
|
|
|
spi_conf.periph = SpiHandle::Config::Peripheral::SPI_1;
|
|
spi_conf.nss = SpiHandle::Config::NSS::HARD_OUTPUT;
|
|
|
|
spi_conf.pin_config.nss = daisy::seed::D7; //Pin(PORTG, 10); // D7
|
|
spi_conf.pin_config.sclk = daisy::seed::D8; //Pin(PORTG, 11); // D8
|
|
spi_conf.pin_config.miso = daisy::seed::D9; // Pin(PORTB, 4); // D9
|
|
spi_conf.pin_config.mosi = daisy::seed::D10; //Pin(PORTB, 5); // D10
|
|
|
|
res = spi.Init(spi_conf);
|
|
|
|
if (res == SpiHandle::Result::ERR)
|
|
return (-1);
|
|
|
|
daisy_hw.PrintLine("resetting tft");
|
|
send_sync(&RESET_CMD);
|
|
|
|
for (auto cmd : init_cmds) {
|
|
daisy_hw.PrintLine("sending tft init command");
|
|
send_sync(&cmd);
|
|
}
|
|
|
|
_async_dma_buf = dma_buf;
|
|
_async_dma_sz = dma_sz;
|
|
_async_stat = async_status::READY;
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
tft_driver_ili9341::set_addr_window(uint16_t col_st, uint16_t width,
|
|
uint16_t row_st, uint16_t height)
|
|
{
|
|
uint16_t start, end;
|
|
|
|
start = col_st; end = col_st + width;
|
|
tft_cmd col_set(ILI9341_CASET,
|
|
{ (uint8_t)((start >> 8) & 0xff), (uint8_t)(start & 0xff),
|
|
(uint8_t)((end >> 8) & 0xff), (uint8_t)(end & 0xff) });
|
|
|
|
start = row_st; end = row_st + height;
|
|
tft_cmd row_set(ILI9341_PASET,
|
|
{ (uint8_t)((start >> 8) & 0xff), (uint8_t)(start & 0xff),
|
|
(uint8_t)((end >> 8) & 0xff), (uint8_t)(end & 0xff) });
|
|
|
|
send_sync(&col_set);
|
|
send_sync(&row_set);
|
|
}
|
|
|
|
|
|
void
|
|
tft_driver_ili9341::async_on_finish(async_on_finish_f cb, void *ctx)
|
|
{
|
|
_async_cb_fun = cb;
|
|
_async_cb_ctx = ctx;
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::async_start(void)
|
|
{
|
|
if (_async_stat != async_status::READY &&
|
|
_async_stat != async_status::DONE)
|
|
return (-1);
|
|
|
|
if (_async_cb_fun == NULL)
|
|
return (-1);
|
|
|
|
_async_curr_wr = (tft_dma_cmd *)_async_dma_buf;
|
|
_async_curr_rd = _async_curr_wr;
|
|
|
|
_async_curr_wr->size = 0;
|
|
|
|
_async_wr = async_wr::START;
|
|
_async_stat = async_status::NO_TX;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::async_end(void)
|
|
{
|
|
_async_wr = async_wr::END;
|
|
async_flush();
|
|
return (0);
|
|
}
|
|
|
|
tft_driver_ili9341::async_status
|
|
tft_driver_ili9341::async_stat(void)
|
|
{
|
|
return (_async_stat);
|
|
}
|
|
|
|
void
|
|
tft_driver_ili9341::async_flush(void)
|
|
{
|
|
async_tx();
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::async_hline_row_bg_fill(tft_color *bg, tft_color *fg,
|
|
uint16_t hline_idx_st, uint16_t hline_idx_end, uint16_t row_idx)
|
|
{
|
|
if (render_dma_buf_hline_row_bg_fill(bg, fg, hline_idx_st,
|
|
hline_idx_end, row_idx) != 0) {
|
|
return (-1);
|
|
}
|
|
|
|
async_tx_try();
|
|
|
|
return (0);
|
|
}
|
|
|
|
#define TFT_ASYNC_TRY_TX_MIN 4096
|
|
void
|
|
tft_driver_ili9341::async_tx_try(void)
|
|
{
|
|
if (dma_buf_sz_tx_pend() > TFT_ASYNC_TRY_TX_MIN)
|
|
return;
|
|
|
|
async_tx();
|
|
}
|
|
|
|
void
|
|
tft_driver_ili9341::async_tx(void)
|
|
{
|
|
if (_async_wr == async_wr::END && _async_stat == async_status::NO_TX &&
|
|
dma_buf_sz_tx_pend() == 0) {
|
|
_async_stat = async_status::DONE;
|
|
_async_cb_fun((void *)_async_cb_ctx);
|
|
return;
|
|
}
|
|
|
|
if (_async_stat != async_status::NO_TX)
|
|
return;
|
|
|
|
AC_ASSERT(_async_curr_rd->size > 0);
|
|
|
|
_async_stat = async_status::TX;
|
|
|
|
async_send_raw((const uint8_t *)_async_curr_rd->buf, 1,
|
|
async_tx_cmd_scb, async_tx_cmd_ecb, this);
|
|
}
|
|
|
|
void
|
|
tft_driver_ili9341::async_tx_cmd_scb(void *ctx)
|
|
{
|
|
tft_driver_ili9341 *tft;
|
|
|
|
tft = (tft_driver_ili9341 *)ctx;
|
|
|
|
AC_ASSERT(tft->_async_stat == async_status::TX);
|
|
|
|
tft->dcx_low();
|
|
}
|
|
|
|
void
|
|
tft_driver_ili9341::async_tx_cmd_ecb(void *ctx, SpiHandle::Result res)
|
|
{
|
|
tft_driver_ili9341 *tft;
|
|
const tft_dma_cmd *cmd;
|
|
|
|
tft = (tft_driver_ili9341 *)ctx;
|
|
cmd = tft->dma_buf_curr_rd();
|
|
|
|
tft->async_send_raw(&cmd->buf[1], cmd->size, async_tx_param_scb,
|
|
async_tx_param_ecb, tft);
|
|
}
|
|
|
|
void
|
|
tft_driver_ili9341::async_tx_param_scb(void *ctx)
|
|
{
|
|
tft_driver_ili9341 *tft;
|
|
|
|
tft = (tft_driver_ili9341 *)ctx;
|
|
tft->dcx_high();
|
|
}
|
|
|
|
void
|
|
tft_driver_ili9341::async_tx_param_ecb(void *ctx, SpiHandle::Result res)
|
|
{
|
|
tft_driver_ili9341 *tft;
|
|
const tft_dma_cmd *cmd;
|
|
|
|
|
|
tft = (tft_driver_ili9341 *)ctx;
|
|
cmd = tft->dma_buf_next_rd();
|
|
|
|
AUDIOC_ASSERT(tft->async_stat() == async_status::TX);
|
|
|
|
if (cmd->size == 0) {
|
|
tft->_async_stat = async_status::NO_TX;
|
|
if (tft->dma_buf_sz_tx_pend() == 0 &&
|
|
tft->_async_wr == async_wr::END) {
|
|
tft->_async_stat = async_status::DONE;
|
|
tft->_async_cb_fun((void *)tft->_async_cb_ctx);
|
|
}
|
|
return;
|
|
}
|
|
|
|
tft->async_send_raw(cmd->buf, 1, async_tx_cmd_scb, async_tx_cmd_ecb,
|
|
tft);
|
|
}
|
|
|
|
size_t
|
|
tft_driver_ili9341::dma_buf_sz_tx_pend(void)
|
|
{
|
|
return ((size_t)
|
|
((uint8_t *)_async_curr_wr - (uint8_t *)_async_curr_rd));
|
|
}
|
|
|
|
size_t
|
|
tft_driver_ili9341::dma_buf_sz_used(void)
|
|
{
|
|
size_t buf_fill;
|
|
|
|
buf_fill = (size_t)((uint8_t *)_async_curr_wr - _async_dma_buf);
|
|
if (_async_curr_wr->size > 0)
|
|
buf_fill += sizeof(tft_dma_cmd) + _async_curr_wr->size;
|
|
|
|
return (buf_fill);
|
|
}
|
|
|
|
tft_dma_cmd *
|
|
tft_driver_ili9341::dma_buf_curr_wr(void)
|
|
{
|
|
return ((tft_dma_cmd *)_async_curr_wr);
|
|
}
|
|
|
|
tft_dma_cmd *
|
|
tft_driver_ili9341::dma_buf_next_wr(void)
|
|
{
|
|
size_t sz;
|
|
uint8_t *bytes;
|
|
tft_dma_cmd *tmp;
|
|
|
|
sz = sizeof(tft_dma_cmd) + _async_curr_wr->size;
|
|
bytes = (uint8_t *)_async_curr_wr;
|
|
|
|
tmp = (tft_dma_cmd *)&bytes[sz];
|
|
tmp->size = 0;
|
|
|
|
return ((tft_dma_cmd *)(_async_curr_wr = tmp));
|
|
}
|
|
|
|
const tft_dma_cmd *
|
|
tft_driver_ili9341::dma_buf_curr_rd(void)
|
|
{
|
|
return ((const tft_dma_cmd *)_async_curr_rd);
|
|
}
|
|
|
|
const tft_dma_cmd *
|
|
tft_driver_ili9341::dma_buf_next_rd(void)
|
|
{
|
|
size_t sz;
|
|
uint8_t *bytes;
|
|
|
|
sz = sizeof(tft_dma_cmd) + _async_curr_rd->size;
|
|
bytes = (uint8_t *)_async_curr_rd;
|
|
|
|
return ((const tft_dma_cmd *)
|
|
(_async_curr_rd = (tft_dma_cmd *)&bytes[sz]));
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
tft_driver_ili9341::render_dma_buf_set_addr_window(uint16_t col_st,
|
|
uint16_t col_end, uint16_t row_st, uint16_t row_end)
|
|
{
|
|
size_t n;
|
|
tft_dma_cmd *cmd;
|
|
|
|
if (_async_dma_sz - dma_buf_sz_used() <
|
|
2 * (sizeof(tft_dma_cmd) + sizeof(uint8_t) + 2 * sizeof(uint16_t)))
|
|
return (-1);
|
|
|
|
cmd = dma_buf_curr_wr();
|
|
|
|
n = 0;
|
|
cmd->buf[n++] = ILI9341_CASET;
|
|
cmd->buf[n++] = (uint8_t)((col_st >> 8) & 0xff);
|
|
cmd->buf[n++] = (uint8_t)(col_st & 0xff);
|
|
cmd->buf[n++] = (uint8_t)((col_end >> 8) & 0xff);
|
|
cmd->buf[n++] = (uint8_t)(col_end & 0xff);
|
|
|
|
cmd->size = n;
|
|
|
|
cmd = dma_buf_next_wr();
|
|
|
|
n = 0;
|
|
cmd->buf[n++] = ILI9341_PASET;
|
|
cmd->buf[n++] = (uint8_t)((row_st >> 8) & 0xff);
|
|
cmd->buf[n++] = (uint8_t)(row_st & 0xff);
|
|
cmd->buf[n++] = (uint8_t)((row_end >> 8) & 0xff);
|
|
cmd->buf[n++] = (uint8_t)(row_end & 0xff);
|
|
|
|
cmd->size = n;
|
|
|
|
dma_buf_next_wr();
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::render_dma_buf_memwr(const tft_color *col, size_t num)
|
|
{
|
|
size_t i, n;
|
|
uint16_t c16;
|
|
tft_dma_cmd *cmd;
|
|
|
|
if (_async_dma_sz - dma_buf_sz_used() <
|
|
sizeof(tft_dma_cmd) + sizeof(uint8_t) + num * sizeof(uint16_t))
|
|
return (-1);
|
|
|
|
cmd = dma_buf_curr_wr();
|
|
|
|
c16 = col->u16();
|
|
i = 0;
|
|
cmd->buf[i++] = ILI9341_RAMWR;
|
|
for (n = 0; n < num; n++) {
|
|
cmd->buf[i++] = (uint8_t)((c16 >> 8) & 0xff);
|
|
cmd->buf[i++] = (uint8_t)(c16 & 0xff);
|
|
}
|
|
|
|
cmd->size = i;
|
|
|
|
dma_buf_next_wr();
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::render_dma_buf_memwr_cont(const tft_color *col, size_t num)
|
|
{
|
|
size_t i, n;
|
|
uint16_t c16;
|
|
tft_dma_cmd *cmd;
|
|
|
|
if (_async_dma_sz - dma_buf_sz_used() <
|
|
sizeof(tft_dma_cmd) + sizeof(uint8_t) + num * sizeof(uint16_t))
|
|
return (-1);
|
|
|
|
cmd = dma_buf_curr_wr();
|
|
|
|
c16 = col->u16();
|
|
i = 0;
|
|
cmd->buf[i++] = ILI9341_WRCONT;
|
|
for (n = 0; n < num; n++) {
|
|
cmd->buf[i++] = (uint8_t)((c16 >> 8) & 0xff);
|
|
cmd->buf[i++] = (uint8_t)(c16 & 0xff);
|
|
}
|
|
|
|
cmd->size = i;
|
|
|
|
dma_buf_next_wr();
|
|
|
|
return (0);
|
|
}
|
|
int
|
|
tft_driver_ili9341::render_dma_buf_hline(const tft_color *c, uint16_t col_s,
|
|
uint16_t col_e, uint16_t row)
|
|
{
|
|
if (render_dma_buf_set_addr_window(col_s, col_e, row, row + 1) == -1)
|
|
return (-1);
|
|
|
|
if (render_dma_buf_memwr(c, (size_t)(col_e - col_s)) == -1)
|
|
return (-1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::render_dma_buf_hline_row_bg_fill(const tft_color *bg,
|
|
const tft_color *fg, uint16_t col_s, uint16_t col_e, uint16_t row)
|
|
{
|
|
if (col_e < col_s || width() < col_e)
|
|
return (-1);
|
|
|
|
if (render_dma_buf_set_addr_window(0, width(), row, row+1) == -1)
|
|
return (-1);
|
|
|
|
if (render_dma_buf_memwr(bg, (size_t)col_s) == -1)
|
|
return (-1);
|
|
if (render_dma_buf_memwr_cont(fg, (size_t)(col_e - col_s)) == -1)
|
|
return (-1);
|
|
if (render_dma_buf_memwr_cont(bg, (size_t)(width() - col_e)) == -1)
|
|
return (-1);
|
|
|
|
return (0);
|
|
}
|
|
|
|
#define TFT_N_PIXELS (ILI9341_TFTWIDTH * ILI9341_TFTHEIGHT)
|
|
#define TFT_BLOCK_SZ ((size_t)16386)
|
|
int
|
|
tft_driver_ili9341::fill_screen(const tft_color *c)
|
|
{
|
|
uint8_t buf[TFT_BLOCK_SZ];
|
|
size_t n, i;
|
|
uint16_t c16;
|
|
|
|
set_addr_window(0, width(), 0, height());
|
|
if (send_sync(ILI9341_RAMWR, NULL, 0) == -1)
|
|
return (-1);
|
|
|
|
i = 0;
|
|
c16 = c->u16();
|
|
for (n = 0; n < sizeof(buf) / sizeof(uint16_t); n++) {
|
|
buf[i++] = (uint8_t)((c16 >> 8) & 0xff);
|
|
buf[i++] = (uint8_t)(c16 & 0xff);
|
|
}
|
|
|
|
for (n = 0;
|
|
n < TFT_N_PIXELS * sizeof(uint16_t);
|
|
n += i) {
|
|
if (send_raw_sync(buf,
|
|
std::min(i, (TFT_N_PIXELS * sizeof(uint16_t)) - n)) == -1)
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::send_sync(const tft_cmd *cmd)
|
|
{
|
|
if (send_sync(cmd->cmd, cmd->param_bytes(), cmd->param_len) == -1)
|
|
return (-1);
|
|
|
|
if (cmd->post_delay > 0)
|
|
System::Delay(cmd->post_delay);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::send_sync(uint8_t cmd, const uint8_t *buf, size_t len)
|
|
{
|
|
SpiHandle::Result res;
|
|
|
|
dcx_low();
|
|
res = spi.BlockingTransmit(&cmd, 1);
|
|
dcx_high();
|
|
|
|
if (res == SpiHandle::Result::ERR)
|
|
return (-1);
|
|
|
|
if (len == 0)
|
|
return (0);
|
|
|
|
res = spi.BlockingTransmit((uint8_t *)buf, len);
|
|
|
|
return (res == SpiHandle::Result::ERR ? -1 : 0);
|
|
}
|
|
|
|
int
|
|
tft_driver_ili9341::send_raw_sync(const uint8_t *buf, size_t len)
|
|
{
|
|
size_t block_st, transfer_sz;
|
|
SpiHandle::Result res;
|
|
|
|
res = SpiHandle::Result::OK;
|
|
for (block_st = 0; block_st < len; block_st += block_sz()) {
|
|
transfer_sz = std::min(block_sz(), len - block_st);
|
|
res = spi.BlockingTransmit((uint8_t *)&buf[block_st],
|
|
transfer_sz);
|
|
}
|
|
|
|
return (res == SpiHandle::Result::ERR ? -1 : 0);
|
|
}
|
|
|