#include "daisy.h" #include "audioc.hpp" #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::NOPULL, GPIO::Speed::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_curr_rd = _async_curr_wr = (tft_dma_cmd *)dma_buf; _async_id = 0; _async_stat = async_status::INIT; 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_ready(void) { async_status stat; stat = (async_status)_async_stat; switch (stat) { case async_status::INIT: case async_status::DONE: if (_async_cb_fun == NULL || _async_cb_ctx == NULL || _async_id < 0) return (-1); _async_stat = async_status::READY; __attribute__((fallthrough)); case async_status::READY: return (0); default: break; } return (-1); } int tft_driver_ili9341::dma_buf_reset(void) { async_status stat; tft_dma_cmd *cmd; if (dma_buf_sz_tx_pend() > 0) return (-1); stat = _async_stat; switch (stat) { case async_status::INIT: case async_status::TX_NONE: case async_status::TX_ACTIVE: return (-1); default: break; } cmd = (tft_dma_cmd *)_async_dma_buf; cmd->size = 0; _async_curr_wr = cmd; _async_curr_rd = cmd; return (0); } int tft_driver_ili9341::async_start(void) { if (async_ready() == -1) return (-1); if (dma_buf_reset() == -1) return (-1); _async_wr = async_wr::START; _async_stat = async_status::TX_NONE; return (async_id()); } int tft_driver_ili9341::async_end(void) { _async_wr = async_wr::END; async_id_next(); async_flush(); return (0); } tft_driver_ili9341::async_status tft_driver_ili9341::async_stat(void) { return (_async_stat); } void tft_driver_ili9341::async_finalize(void) { AC_PRECOND(_async_cb_fun != NULL && _async_cb_ctx != NULL); AC_PRECOND(_async_stat == async_status::TX_NONE || _async_stat == async_status::TX_INACTIVE || _async_stat == async_status::TX_ACTIVE); AC_PRECOND(_async_wr == async_wr::END); AC_PRECOND(dma_buf_sz_tx_pend() == 0); _async_stat = async_status::DONE; _async_cb_fun((void *)_async_cb_ctx); } void tft_driver_ili9341::async_flush(void) { async_status stat; async_wr wr; stat = _async_stat; wr = _async_wr; switch (stat) { case async_status::TX_NONE: case async_status::TX_INACTIVE: if (dma_buf_sz_tx_pend() > 0) { async_tx(); return; } if (wr == async_wr::END) { async_finalize(); return; } dma_buf_reset(); break; default: break; } } #define TFT_ASYNC_TRY_TX_MIN 2048 void tft_driver_ili9341::async_tx_try(void) { if (dma_buf_sz_tx_pend() < (_async_dma_sz / 2)) return; async_flush(); } void tft_driver_ili9341::async_tx(void) { async_status stat; stat = _async_stat; AC_ASSERT(stat == async_status::TX_NONE || stat == async_status::TX_INACTIVE); AC_ASSERT(dma_buf_sz_tx_pend() > sizeof(tft_dma_cmd)); AC_ASSERT(_async_curr_rd->size > 0); _async_stat = async_status::TX_ACTIVE; async_send_raw((const uint8_t *)_async_curr_rd->buf, 1, async_tx_cmd_scb, async_tx_cmd_ecb, this); } /* ret < 0: if error * ret = 0: if success * ret > 0: need reentrant call, ret == async_id that should be used in next * call */ int tft_driver_ili9341::async_hline_row_bg_fill(int id, tft_color *bg, tft_color *fg, uint16_t hline_idx_st, uint16_t hline_idx_end, uint16_t row_idx) { int ret; if (id != _async_id) return (-1); ret = render_dma_buf_hline_row_bg_fill(bg, fg, hline_idx_st, hline_idx_end, row_idx); if (ret < 0) return (ret); if (ret > 0) async_flush(); async_tx_try(); return (ret); } 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_ACTIVE); 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(); AC_ASSERT(tft->_async_stat == async_status::TX_ACTIVE); 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; AC_ASSERT(tft->_async_stat == async_status::TX_ACTIVE); 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; AC_PRECOND(tft->_async_stat == async_status::TX_ACTIVE); cmd = tft->dma_buf_next_rd(); if (cmd->size == 0) { if (tft->_async_wr == async_wr::END) { AC_ASSERT(tft->dma_buf_sz_tx_pend() == 0); tft->async_finalize(); } else { tft->_async_stat = async_status::TX_INACTIVE; } return; } AC_ASSERT(tft->dma_buf_sz_tx_pend() > sizeof(tft_dma_cmd)); 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) + sizeof(tft_dma_cmd); 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(size_t curr_sz) { size_t tot_sz; volatile tft_dma_cmd *curr, *next; volatile uint8_t *bytes; AC_PRECOND(_async_dma_sz - dma_buf_sz_used() >= curr_sz + sizeof(tft_dma_cmd)); curr = (volatile tft_dma_cmd *)_async_curr_wr; tot_sz = sizeof(*curr) + curr_sz; bytes = (volatile uint8_t *)curr; bytes += tot_sz; next = (volatile tft_dma_cmd *)bytes; next->size = 0; curr->size = curr_sz; _async_curr_wr = next; return ((tft_dma_cmd *)next); } 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; volatile const tft_dma_cmd *cmd; volatile const uint8_t *bytes; cmd = (volatile const tft_dma_cmd *)_async_curr_rd; sz = sizeof(*cmd) + cmd->size; bytes = (volatile const uint8_t *)cmd; bytes += sz; cmd = (volatile const tft_dma_cmd *)bytes; _async_curr_rd = cmd; return ((const tft_dma_cmd *)cmd); } #define SET_ADDR_WINDOW_SZ (sizeof(tft_dma_cmd) + 5 + \ sizeof(tft_dma_cmd) + 5) 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() < SET_ADDR_WINDOW_SZ) 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 = dma_buf_next_wr(n); 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); dma_buf_next_wr(n); return (0); } #define MEMWR_SZ(num) (sizeof(tft_dma_cmd) + 1 + (num) * sizeof(uint16_t)) 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() < MEMWR_SZ(num)) 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); } dma_buf_next_wr(i); return (0); } #define MEMWR_CONT_SZ(num) MEMWR_SZ(num) 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() < MEMWR_CONT_SZ(num)) { 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); } dma_buf_next_wr(i); 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) { int ret; AC_PRECOND(col_s <= col_e); if (_async_dma_sz - dma_buf_sz_used() < SET_ADDR_WINDOW_SZ + MEMWR_SZ(col_e - col_s)) return (1); ret = render_dma_buf_set_addr_window(col_s, col_e, row, row + 1); if (ret != 0) return (ret); ret = render_dma_buf_memwr(c, (size_t)(col_e - col_s)); return (ret); } 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) { int ret; AC_PRECOND(col_s <= col_s && col_e <= width()); if (_async_dma_sz - dma_buf_sz_used() < SET_ADDR_WINDOW_SZ + MEMWR_SZ(col_s) + MEMWR_CONT_SZ(col_e - col_s)) return (1); ret = render_dma_buf_set_addr_window(0, width(), row, row+1); if (ret != 0) return (ret); ret = render_dma_buf_memwr(bg, (size_t)col_s); if (ret != 0) return (ret); ret = render_dma_buf_memwr_cont(fg, (size_t)(col_e - col_s)); if (ret != 0) return (ret); ret = render_dma_buf_memwr_cont(bg, (size_t)(width() - col_e)); return (ret); } #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 += TFT_BLOCK_SZ) { transfer_sz = std::min(TFT_BLOCK_SZ, len - block_st); res = spi.BlockingTransmit((uint8_t *)&buf[block_st], transfer_sz); } return (res == SpiHandle::Result::ERR ? -1 : 0); }