/* * Copyright (C) 2018 Koen Zandberg * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup drivers_ili9341 * @{ * * @file * @brief Device driver implementation for the ili9341 display controller * * @author Koen Zandberg * * @} */ #include #include #include "byteorder.h" #include "periph/spi.h" #include "xtimer.h" #include "ili9341.h" #include "ili9341_internal.h" #define ENABLE_DEBUG (0) #include "debug.h" static void _ili9341_spi_acquire(const ili9341_t *dev) { spi_acquire(dev->params->spi, dev->params->cs_pin, dev->params->spi_mode, dev->params->spi_clk); } static void _ili9341_cmd_start(const ili9341_t *dev, uint8_t cmd, bool cont) { gpio_clear(dev->params->dcx_pin); spi_transfer_byte(dev->params->spi, dev->params->cs_pin, cont, cmd); gpio_set(dev->params->dcx_pin); } /* datasheet page 178, table converted to equation. * gvdd in 1mv increments: 4850 = 4.85V */ static uint8_t _ili9341_calc_pwrctl1(uint16_t gvdd) { return (gvdd - 2850) / 50; } static uint8_t _ili9341_calc_vmh(uint16_t vcomh) { return (vcomh - 2700) / 25; } static uint8_t _ili9341_calc_vml(int16_t vcoml) { return (vcoml + 2500) / 25; } static void _write_cmd(const ili9341_t *dev, uint8_t cmd, const uint8_t *data, size_t len) { _ili9341_cmd_start(dev, cmd, len ? true : false); if (len) { spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, false, data, NULL, len); } } static void _ili9341_set_area(const ili9341_t *dev, uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2) { be_uint16_t params[2]; params[0] = byteorder_htons(x1); params[1] = byteorder_htons(x2); _write_cmd(dev, ILI9341_CMD_CASET, (uint8_t *)params, sizeof(params)); params[0] = byteorder_htons(y1); params[1] = byteorder_htons(y2); _write_cmd(dev, ILI9341_CMD_PASET, (uint8_t *)params, sizeof(params)); } int ili9341_init(ili9341_t *dev, const ili9341_params_t *params) { assert(params->lines >= 16 && params->lines <= 320 && !(params->lines & 0x7)); dev->params = params; uint8_t command_params[4] = { 0 }; gpio_init(dev->params->dcx_pin, GPIO_OUT); int res = spi_init_cs(dev->params->spi, dev->params->cs_pin); if (res != SPI_OK) { DEBUG("[ili9341] init: error initializing the CS pin [%i]\n", res); return -1; } if (dev->params->rst_pin != GPIO_UNDEF) { gpio_init(dev->params->rst_pin, GPIO_OUT); gpio_clear(dev->params->rst_pin); xtimer_usleep(120 * US_PER_MS); gpio_set(dev->params->rst_pin); } xtimer_usleep(120 * US_PER_MS); /* Acquire once at release at the end */ _ili9341_spi_acquire(dev); /* Soft Reset */ _write_cmd(dev, ILI9341_CMD_SWRESET, NULL, 0); xtimer_usleep(120 * US_PER_MS); /* Display off */ _write_cmd(dev, ILI9341_CMD_DISPOFF, NULL, 0); /* PWRCTL1/2 */ command_params[0] = _ili9341_calc_pwrctl1(ILI9341_GVDD); _write_cmd(dev, ILI9341_CMD_PWCTRL1, command_params, 1); command_params[0] = 0x10; /* PWRCTL 0 0 0 */ _write_cmd(dev, ILI9341_CMD_PWCTRL2, command_params, 1); /* VCOMCTL */ command_params[0] = _ili9341_calc_vmh(ILI9341_VCOMH); command_params[1] = _ili9341_calc_vml(ILI9341_VCOML); _write_cmd(dev, ILI9341_CMD_VMCTRL1, command_params, 2); command_params[0] = 0x86; _write_cmd(dev, ILI9341_CMD_VMCTRL2, command_params, 1); /* Memory access CTL */ command_params[0] = ILI9341_MADCTL_HORZ_FLIP; command_params[0] |= dev->params->rgb ? 0 : ILI9341_MADCTL_BGR; _write_cmd(dev, ILI9341_CMD_MADCTL, command_params, 1); /* Frame control */ command_params[0] = 0x00; command_params[1] = 0x18; _write_cmd(dev, ILI9341_CMD_FRAMECTL1, command_params, 2); /* Display function control */ command_params[0] = 0x08; command_params[1] = 0x82; /* number of lines, see datasheet p. 166 (DISCTRL::NL) */ command_params[2] = (params->lines >> 3) - 1; _write_cmd(dev, ILI9341_CMD_DFUNC, command_params, 3); /* Pixel format */ command_params[0] = 0x55; /* 16 bit mode */ _write_cmd(dev, ILI9341_CMD_PIXSET, command_params, 1); command_params[0] = 0x01; _write_cmd(dev, ILI9341_CMD_GAMSET, command_params, 1); /* Gamma correction */ { static const uint8_t gamma_pos[] = { 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00 }; _write_cmd(dev, ILI9341_CMD_PGAMCTRL, gamma_pos, sizeof(gamma_pos)); } { static const uint8_t gamma_neg[] = { 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F }; _write_cmd(dev, ILI9341_CMD_NGAMCTRL, gamma_neg, sizeof(gamma_neg)); } if (dev->params->inverted) { _write_cmd(dev, ILI9341_CMD_DINVON, NULL, 0); } /* Sleep out (turn off sleep mode) */ _write_cmd(dev, ILI9341_CMD_SLPOUT, NULL, 0); /* Display on */ _write_cmd(dev, ILI9341_CMD_DISPON, NULL, 0); spi_release(dev->params->spi); return 0; } void ili9341_write_cmd(const ili9341_t *dev, uint8_t cmd, const uint8_t *data, size_t len) { _ili9341_spi_acquire(dev); _write_cmd(dev, cmd, data, len); spi_release(dev->params->spi); } void ili9341_read_cmd(const ili9341_t *dev, uint8_t cmd, uint8_t *data, size_t len) { assert(len); _ili9341_spi_acquire(dev); _ili9341_cmd_start(dev, cmd, true); /* Dummy transfer */ spi_transfer_byte(dev->params->spi, dev->params->cs_pin, true, 0x00); spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, false, NULL, data, len); spi_release(dev->params->spi); } void ili9341_fill(const ili9341_t *dev, uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2, uint16_t color) { /* Send fill area to the display */ /* Calculate number of pixels */ int32_t num_pix = (x2 - x1 + 1) * (y2 - y1 + 1); DEBUG("[ili9341]: Write x1: %" PRIu16 ", x2: %" PRIu16 ", " "y1: %" PRIu16 ", y2: %" PRIu16 ". Num pixels: %lu\n", x1, x2, y1, y2, (unsigned long)num_pix); /* Send fill area to the display */ _ili9341_spi_acquire(dev); _ili9341_set_area(dev, x1, x2, y1, y2); /* Memory access command */ _ili9341_cmd_start(dev, ILI9341_CMD_RAMWR, true); #if ILI9341_LE_MODE color = htons(color); #endif for (int i = 0; i < (num_pix - 1); i++) { spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, true, (uint8_t *)&color, NULL, sizeof(color)); } spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, false, (uint8_t *)&color, NULL, sizeof(color)); spi_release(dev->params->spi); } void ili9341_pixmap(const ili9341_t *dev, uint16_t x1, uint16_t x2, uint16_t y1, uint16_t y2, const uint16_t *color) { size_t num_pix = (x2 - x1 + 1) * (y2 - y1 + 1); DEBUG("[ili9341]: Write x1: %" PRIu16 ", x2: %" PRIu16 ", " "y1: %" PRIu16 ", y2: %" PRIu16 ". Num pixels: %lu\n", x1, x2, y1, y2, (unsigned long)num_pix); _ili9341_spi_acquire(dev); /* Send fill area to the display */ _ili9341_set_area(dev, x1, x2, y1, y2); /* Memory access command */ _ili9341_cmd_start(dev, ILI9341_CMD_RAMWR, true); #if ILI9341_LE_MODE for (size_t i = 0; i < num_pix - 1; i++) { uint16_t ncolor = htons(*(color + i)); spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, true, &ncolor, NULL, sizeof(uint16_t)); } uint16_t ncolor = htons(*(color + num_pix - 1)); spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, false, &ncolor, NULL, sizeof(uint16_t)); #else spi_transfer_bytes(dev->params->spi, dev->params->cs_pin, false, (const uint8_t *)color, NULL, num_pix * 2); #endif spi_release(dev->params->spi); } void ili9341_invert_on(const ili9341_t *dev) { uint8_t command = (dev->params->inverted) ? ILI9341_CMD_DINVOFF : ILI9341_CMD_DINVON; ili9341_write_cmd(dev, command, NULL, 0); } void ili9341_invert_off(const ili9341_t *dev) { uint8_t command = (dev->params->inverted) ? ILI9341_CMD_DINVON : ILI9341_CMD_DINVOFF; ili9341_write_cmd(dev, command, NULL, 0); } void ili9341_set_brightness(const ili9341_t *dev, uint8_t brightness) { ili9341_write_cmd(dev, ILI9341_CMD_WRDISBV, &brightness, 1); uint8_t param = 0x26; ili9341_write_cmd(dev, ILI9341_CMD_WRCTRLD, ¶m, 1); }