From 84485d98abf084d35d4bf4816d3b602f1e125d0c Mon Sep 17 00:00:00 2001 From: Mahyar Koshkouei Date: Sun, 27 Mar 2022 14:03:39 +0100 Subject: [PATCH] Add drawing to ILI9225 LCD Signed-off-by: Mahyar Koshkouei --- CMakeLists.txt | 4 +- inc/mk_ili9225.h | 135 ++++++++++++ src/hello-world.c | 32 --- src/main.c | 208 +++++++++++++++-- src/mk_ili9225.c | 552 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 883 insertions(+), 48 deletions(-) create mode 100644 inc/mk_ili9225.h delete mode 100644 src/hello-world.c create mode 100644 src/mk_ili9225.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bb9b07..07189b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,12 @@ project(RP2040_GB C CXX) # Initialize the Raspberry Pi Pico SDK pico_sdk_init() -add_executable(RP2040_GB src/main.c src/hello-world.c) +add_executable(RP2040_GB src/main.c src/rom.c src/mk_ili9225.c) target_include_directories(RP2040_GB PRIVATE inc) target_link_libraries(RP2040_GB pico_stdlib pico_stdio pico_bootrom pico_multicore pico_stdio hardware_clocks hardware_pio hardware_vreg hardware_pio - hardware_sync hardware_pll hardware_spi hardware_irq + hardware_sync hardware_pll hardware_spi hardware_irq hardware_dma pico_binary_info) target_compile_definitions(RP2040_GB PRIVATE PARAM_ASSERTIONS_DISABLE_ALL=1 diff --git a/inc/mk_ili9225.h b/inc/mk_ili9225.h new file mode 100644 index 0000000..d0e731d --- /dev/null +++ b/inc/mk_ili9225.h @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2019-2022 by Mahyar Koshkouei + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +//#define NDEBUG +#ifndef MK_ILI9225_READ_AVAILABLE +# define MK_ILI9225_READ_AVAILABLE 0 +#endif + +#include +#include +#include + +#define SCREEN_SIZE_X 176u +#define SCREEN_SIZE_Y 220u + +typedef enum { + ILI9225_COLOR_MODE_FULL = 0, + ILI9225_COLOR_MODE_8COLOR = 1 +} ili9225_color_mode_e; + +/** + * Controls the reset pin of the ILI9225. + * \param state Set to 0 on low output, else high. + */ +extern void mk_ili9225_set_rst(bool state); + +/** + * Controls state of RS pin. + * \param state Set to 0 on low output, else high. + */ +extern void mk_ili9225_set_rs(bool state); + +/** + * Controls state of CS pin. + * \param state Set to 0 on low output, else high. + */ +extern void mk_ili9225_set_cs(bool state); + +/** + * Sends data to the ILI9225 using SPI. Return only after sending data. + * \param halfword Data to send. + */ +void mk_ili9225_spi_write16(const uint16_t *halfwords, size_t len); + +#if MK_ILI9225_READ_AVAILABLE +/** + * Reads 16-bit data from the ILI9225 using SPI. + * \return 16-bit data. + */ +extern uint16_t mk_ili9225_spi_read16(void); +#endif + +/** + * Delays execution in milliseconds. + * \param ms Duration to sleep for. + */ +extern void mk_ili9225_delay_ms(unsigned ms); + +/** + * Initialise ILI9225 LCD with default settings. + * \return 0 on success, else error due to invalid LCD identification. + */ +unsigned mk_ili9225_init(void); + +#if MK_ILI9225_READ_AVAILABLE +/** + * Read the current line being driven by the LCD. Can help with tearing + * mitigation. + * \return Line driven by LCD. + */ +unsigned mk_ili9225_read_driving_line(void); +#endif + +/** + * Set the window that pixel will be written to. Address will loop within the + * window. + * + * \param hor_start + * \param hor_end + * \param vert_start + * \param vert_end + */ +void mk_ili9225_set_window(uint16_t hor_start, uint16_t hor_end, + uint16_t vert_start, uint16_t vert_end); + +/** + * Set address pointer in GRAM. Must be within window. + * \param x + * \param y + */ +void mk_ili9225_set_address(uint8_t x, uint8_t y); + +/** + * Write pixels to ILI9225 GRAM. These pixels will be displayed on screen at + * next vsync. + * \param pixels Pixel data in RGB565 format to write to LCD. + * \param nmemb Number of pixels. + */ +void mk_ili9225_write_pixels(const uint16_t *pixels, uint_fast16_t nmemb); + +/** + * Inverts the display. + * @param invert + */ +void mk_ili9225_invert_display(bool invert); + +void mk_ili9225_write_pixels_start(void); +void mk_ili9225_write_pixels_end(void); + +void mk_ili9225_set_gate_scan(uint16_t hor_start, uint16_t hor_end); + +void mk_ili9225_display_control(bool invert, ili9225_color_mode_e colour_mode); + +void mk_ili9225_power_control(uint8_t drive_power, bool sleep); + +void mk_ili9225_set_drive_freq(uint16_t f); + +/** + * Exit and stop using LCD. Currently does nothing. + */ +void mk_ili9225_exit(void); diff --git a/src/hello-world.c b/src/hello-world.c deleted file mode 100644 index 4e6b623..0000000 --- a/src/hello-world.c +++ /dev/null @@ -1,32 +0,0 @@ -const unsigned char hello_world_gb[] = { - 0x1a, 0xe0, 0x01, 0x13, 0x05, 0x3e, 0x81, 0xe0, 0x02, 0x76, 0x00, 0x20, - 0xf3, 0xc9, 0x3e, 0x09, 0xe0, 0xff, 0x76, 0x00, 0x3e, 0x00, 0xe0, 0x40, - 0x11, 0x61, 0x00, 0x06, 0x3d, 0xc7, 0x3e, 0x80, 0xe0, 0x40, 0xc3, 0x25, - 0x00, 0x3e, 0x00, 0xe0, 0xff, 0x76, 0x00, 0x18, 0xfc, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x25, 0x00, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd9, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xd9, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x20, 0x69, 0x73, 0x20, 0x62, 0x65, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x72, - 0x69, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x77, - 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x47, 0x61, 0x6d, 0x65, - 0x20, 0x42, 0x6f, 0x79, 0x20, 0x45, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x6f, - 0x72, 0x21, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xfb, 0xc3, 0x50, 0x01, 0xce, 0xed, 0x66, 0x66, - 0xcc, 0x0d, 0x00, 0x0b, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0c, 0x00, 0x0d, - 0x00, 0x08, 0x11, 0x1f, 0x88, 0x89, 0x00, 0x0e, 0xdc, 0xcc, 0x6e, 0xe6, - 0xdd, 0xdd, 0xd9, 0x99, 0xbb, 0xbb, 0x67, 0x63, 0x6e, 0x0e, 0xec, 0xcc, - 0xdd, 0xdc, 0x99, 0x9f, 0xbb, 0xb9, 0x33, 0x3e, 0x68, 0x65, 0x6c, 0x6c, - 0x6f, 0x2d, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7d, 0xd8, 0x3e, - 0xaf, 0xe0, 0x26, 0xc3, 0x0e, 0x00 -}; -unsigned int hello_world_gb_len = 342; diff --git a/src/main.c b/src/main.c index 2b72094..e6b1791 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ -#define ENABLE_LCD 0 +#define ENABLE_LCD 1 #define ENABLE_SOUND 0 +#define USE_DMA 0 /** * Reducing VSYNC calculation to lower multiple. @@ -20,12 +21,27 @@ #include #include #include +#include +#include +#include +#include /* Project headers */ #include "hedley.h" #include "peanut_gb.h" +#include "mk_ili9225.h" -extern const unsigned char hello_world_gb[]; +/* LCD Connections. */ +#define GPIO_CS 1 +#define GPIO_CLK 2 +#define GPIO_SDA 3 +#define GPIO_RS 4 +#define GPIO_RST 5 + +static uint dma_lcd; +extern const unsigned char rom[]; +static uint8_t ram[32768]; +static int shift = 0; struct priv_t { @@ -35,13 +51,38 @@ struct priv_t uint8_t *cart_ram; }; +void mk_ili9225_set_rst(bool state) +{ + gpio_put(GPIO_RST, state); +} + +void mk_ili9225_set_rs(bool state) +{ + gpio_put(GPIO_RS, state); +} + +void mk_ili9225_set_cs(bool state) +{ + gpio_put(GPIO_CS, state); +} + +void mk_ili9225_spi_write16(const uint16_t *halfwords, size_t len) +{ + spi_write16_blocking(spi0, halfwords, len); +} + +void mk_ili9225_delay_ms(unsigned ms) +{ + sleep_ms(ms); +} + /** * Returns a byte from the ROM file at the given address. */ uint8_t gb_rom_read(struct gb_s *gb, const uint_fast32_t addr) { //const struct priv_t * const p = gb->direct.priv; - return hello_world_gb[addr]; + return rom[addr]; } /** @@ -49,7 +90,7 @@ uint8_t gb_rom_read(struct gb_s *gb, const uint_fast32_t addr) */ uint8_t gb_cart_ram_read(struct gb_s *gb, const uint_fast32_t addr) { - return 0xFF; + return ram[addr]; //const struct priv_t * const p = gb->direct.priv; //return p->cart_ram[addr]; } @@ -60,7 +101,7 @@ uint8_t gb_cart_ram_read(struct gb_s *gb, const uint_fast32_t addr) void gb_cart_ram_write(struct gb_s *gb, const uint_fast32_t addr, const uint8_t val) { - return; + ram[addr] = val; //const struct priv_t * const p = gb->direct.priv; //p->cart_ram[addr] = val; } @@ -89,8 +130,7 @@ void gb_error(struct gb_s *gb, const enum gb_error_e gb_err, const uint16_t val) void gb_serial_tx(struct gb_s *gb, const uint8_t tx) { (void) gb; - putchar_raw(tx); - //printf("0x%02X ", tx); + //putchar_raw(tx); } enum gb_serial_rx_ret_e gb_serial_rx(struct gb_s *gb, uint8_t *rx) @@ -100,10 +140,33 @@ enum gb_serial_rx_ret_e gb_serial_rx(struct gb_s *gb, uint8_t *rx) return GB_SERIAL_RX_NO_CONNECTION; } +void lcd_draw_line(struct gb_s *gb, const uint8_t pixels[160], + const uint_fast8_t line) +{ + struct priv_t *priv = gb->direct.priv; + const uint16_t palette[3][4] = { + { 0x7FFF, 0x5294, 0x294A, 0x0000 }, + { 0x7FFF, 0x5294, 0x294A, 0x0000 }, + { 0x7FFF, 0x5294, 0x294A, 0x0000 } + }; + static uint16_t fb[LCD_WIDTH] = { 0 }; + + //dma_channel_wait_for_finish_blocking(dma_lcd); + for(unsigned int x = 0; x < LCD_WIDTH; x++) + { + fb[x] = palette[(pixels[x] & LCD_PALETTE_ALL) >> 4][pixels[x] & 3]; + } + + mk_ili9225_set_address(0, 15 + line); + mk_ili9225_write_pixels(fb, LCD_WIDTH + shift); + //dma_channel_transfer_from_buffer_now(dma_lcd, &fb[0], LCD_WIDTH); +} + int main(void) { static struct gb_s gb; enum gb_init_error_e ret; + static const uint16_t clear = 0x0000; { /* The value for VCO set here is meant for least power @@ -121,6 +184,49 @@ int main(void) (void)getchar(); puts("Starting"); + gpio_set_function(GPIO_CS, GPIO_FUNC_SIO); + gpio_set_function(GPIO_CLK, GPIO_FUNC_SPI); + gpio_set_function(GPIO_SDA, GPIO_FUNC_SPI); + gpio_set_function(GPIO_RS, GPIO_FUNC_SIO); + gpio_set_function(GPIO_RST, GPIO_FUNC_SIO); + + gpio_set_dir(GPIO_CS, true); + gpio_set_dir(GPIO_RS, true); + gpio_set_dir(GPIO_RST, true); + gpio_set_slew_rate(GPIO_CLK, GPIO_SLEW_RATE_FAST); + gpio_set_slew_rate(GPIO_SDA, GPIO_SLEW_RATE_FAST); + + puts("Initialising SPI"); + clock_configure(clk_peri, 0, + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, + 125 * 1000 * 1000, 125 * 1000 * 1000); + spi_init(spi0, 32*1000*1000); + printf("SPI baudrate = %u\n", spi_get_baudrate(spi0)); + spi_set_format(spi0, 16, + SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + + puts("Initialising LCD"); + mk_ili9225_init(); + + dma_lcd = dma_claim_unused_channel(true); + dma_channel_config c2 = dma_channel_get_default_config(dma_lcd); + channel_config_set_transfer_data_size(&c2, DMA_SIZE_16); + channel_config_set_dreq(&c2,DREQ_SPI0_TX); // select SPI TX as Dreq for pacing data transfer + channel_config_set_read_increment(&c2, false); + channel_config_set_write_increment(&c2, false); + channel_config_set_ring(&c2, false, 0); + + /* Clear LCD screen. */ + mk_ili9225_write_pixels_start(); + dma_channel_configure(dma_lcd, + &c2, + &spi_get_hw(spi0)->dr, + &clear, + SCREEN_SIZE_X*(SCREEN_SIZE_Y+1), + true); + dma_channel_wait_for_finish_blocking(dma_lcd); + mk_ili9225_write_pixels_end(); + /* Initialise context. */ ret = gb_init(&gb, &gb_rom_read, &gb_cart_ram_read, &gb_cart_ram_write, &gb_error, NULL); @@ -131,27 +237,101 @@ int main(void) goto sleep; } + dma_channel_set_trans_count(dma_lcd, LCD_WIDTH, false); + channel_config_set_read_increment(&c2, true); + + mk_ili9225_set_window(15, 144+15, 29, 160+29); + #if ENABLE_LCD - //gb_init_lcd(&gb, &lcd_draw_line); - // gb.direct.interlace = 1; + gb_init_lcd(&gb, &lcd_draw_line); + //gb.direct.interlace = 1; #endif - gb_init_serial(&gb, gb_serial_tx, gb_serial_rx); + //gb_init_serial(&gb, gb_serial_tx, gb_serial_rx); - uint_fast32_t instrs = 0; +#if USE_DMA + mk_ili9225_write_pixels_start(); +#endif + + uint_fast32_t frames = 0; uint64_t start_time = time_us_64(); uint64_t end_time; - while(!gb.direct.ended) + while(1) { + int cmd; + __gb_step_cpu(&gb); - instrs++; + __gb_step_cpu(&gb); + __gb_step_cpu(&gb); + __gb_step_cpu(&gb); + __gb_step_cpu(&gb); + __gb_step_cpu(&gb); + __gb_step_cpu(&gb); + __gb_step_cpu(&gb); + + if(gb.gb_frame == 0) + continue; + + frames++; + gb.gb_frame = 0; + + cmd = getchar_timeout_us(0); + if(cmd == PICO_ERROR_TIMEOUT) + continue; + + switch(cmd) + { +#if USE_DMA == 0 + static bool invert = false; + static bool sleep = false; + static uint8_t freq = 1; + static ili9225_color_mode_e colour = ILI9225_COLOR_MODE_FULL; + + case 'i': + invert = !invert; + mk_ili9225_display_control(invert, colour); + break; + + case 'c': + colour = !colour; + mk_ili9225_display_control(invert, colour); + break; + + case 'f': + freq++; + freq &= 0x0F; + mk_ili9225_set_drive_freq(freq); + printf("Freq %u\n", freq); + break; + + case 'u': + shift++; + break; + + case 'd': + shift--; + break; +#endif + case 'q': + goto out; + + default: + break; + } + + //mk_ili9225_write_pixels_start(); } +out: end_time = time_us_64(); puts("\nEmulation Ended"); printf("Instructions: %u\n" - "Time: %llu us\n", instrs, end_time-start_time); + "Time: %llu us\n", frames, end_time-start_time); + + mk_ili9225_write_pixels_end(); + mk_ili9225_set_rst(true); + reset_usb_boot(0, 0); /* Sleep forever. */ sleep: diff --git a/src/mk_ili9225.c b/src/mk_ili9225.c new file mode 100644 index 0000000..8fd1d96 --- /dev/null +++ b/src/mk_ili9225.c @@ -0,0 +1,552 @@ +/** + * Copyright (C) 2019-2022 by Mahyar Koshkouei + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "mk_ili9225.h" + +/* Register Descriptions. */ +/** + * The index register (IR) stored the register address. + * The register selection signal (RS) selects register access. + * The read/write signals (nRD/nWR) select read or write command, respectively. + * The data bus D0-D17 are used to read or write data. Register access is + * 16-bit, so 18-bit bus is reduced to 16-bit only (see page 45). + * On writing data GRAM, the internal address is automatically incremented. + */ + +/* ILI9225 Registers. */ +/** + * [Register Name] ([RO,WO, or WR]) + * R ([Read mask]): [Description of what can be read at mask] + * W ([Write mask]): [Description of what can be written at mask] + */ +/** + * Driver Code Read (RW) + * R (0xFFFF): Reads the device code integer 0x9225. + * W: (0xXXX1): Starts (0x1) or stops (0x0) internal oscillator. + * At least 10ms must pass after starting the internal oscillator to allow it + * to stabilise. + */ +#define MK_ILI9225_REG_DRIVER_CODE_READ 0x00 +#define MK_ILI9225_REG_START_OSCILLATION 0x00 +/** + * Driver Output Control (WO) + * W (0x8XXX): VSPL: Inverts the polarity of VSYNC pin. "1" is high active. + * W (0x4XXX): HSPL: Inverts the polarity of HSYNC pin. "1" is high active. + * W (0x2XXX): DPL: Same as above for DOTCLK. + * W (0x1XXX): EPL: Same as above for ENABLE. + * W (0xX4XX): SM: Scan order by gate driver. + * W (0xX2XX): GS: Shift direction of gate driver. + * W (0xX1XX): SS: Shift direction from source driver. + * W (0xXX1F): NL: Set active gate driver line. Default: 0b11011. + */ +#define MK_ILI9225_REG_DRIVER_OUTPUT_CTRL 0x01 +/** + * LCD Driving Waveform Control (WO) + * Set LCD inversion method. + * W (0xX3XX): INV: Sets inversion method. + * W (0xXXX1): FLD: 3-field interlaced scanning function. + */ +#define MK_ILI9225_REG_LCD_AC_DRIVING_CTRL 0x02 +/** + * Entry Mode (WO) + * W (0x1XXX): BGR: Swap Red with Blue. + * W (0xX3XX): MDT: Data transmission format for 80-mode. + * W (0xXX2X): ID1: Control horizontal address counter increment (1) or + * decrement (0). + * W (0xXX1X): ID0: Control vertical address counter increment (1) or + * decrement (0). + * W (0xXXX8): AM: GRAM update direction. "0" is horizontal. + */ +#define MK_ILI9225_REG_ENTRY_MODE 0x03 +/** + * Display Control 1 (WO) + * W (0x1XXX): TEMON: Sets the frame flag output signal FLM. + * W (0xXX1X): GON: Set output level of gate driver. VGL = 0, Normal = 1. + * W (0xXXX8): CL: Selects 8-colour display mode when 1. Else 2^18 colours. + * W (0xXXX4): REV: Reverses greyscale levels if set. + * W (0xXXX3): D: Control display output. 0b11 to turn on display, else display + * is off. + */ +#define MK_ILI9225_REG_DISPLAY_CTRL 0x07 +/** + * Display Control 2 (WO) + * Used for I80, M68, and RGB modes only. + * W (0xXFXX): FP: Specify number of front porch lines. + * W (0xXXXF): BP: Specify number of back porch lines. + */ +#define MK_ILI9225_REG_BLANK_PERIOD_CTRL 0x08 +/** + * Frame Cycle Control (WO) + * Used for RGB mode only. + * W (0xFXXX): NO: Set amount of non-overlay for gate output. + * W (0xXFXX): SDT: Set delay from gate edge to source output. + * W (0xXXXF): RTN: Set number of clock cycles for one display line. + */ +#define MK_ILI9225_REG_FRAME_CYCLE_CTRL 0x0B +/** + * RGB Input Interface Control (W) + * Used for RGB mode only. + */ +#define MK_ILI9225_REG_INTERFACE_CTRL 0x0C +/** + * Oscillator Control (W) + * W (0xXFXX): FOSC: Oscillation frequency. + * W (0xXXX1): OSC_EN: Starts oscillation from halt state. 10ms wait required. + */ +#define MK_ILI9225_REG_OSC_CTRL 0x0F +/** + * Power Control 1 (W) + * W (0xXFXX): SAP: Set current level of driver. + * W (0xXXX2): DSTB: Enable deep standby mode. Writing to GRAM is prohibited. + * W (0xXXX1): STB: Enable sleep mode. Stops display and internal oscillator. + */ +#define MK_ILI9225_REG_PWR_CTRL1 0x10 +/** + * Power Control 2 (W) + * W (0x1XXX): APON: Use automatic boosting operation. + * W (0xXFXX): PON: Set boosting operation for specific circuits. + * W (0xXX2X): AON: Set operation of amplifier. + * W (0xXX1X): VCL1EN: Set operation of generation amplifier. + * W (0xXXXF): VC: Set VCl1 voltage output. + */ +#define MK_ILI9225_REG_PWR_CTRL2 0x11 +/** + * Power Control 3 (W) + * W (0x7XXX): BT: Set output factor of boost converter. + * W (0xX7XX): DC1: Operation frequency of circuit 1 boost converter. + * W (0xXX7X): DC2: Operation frequency of circuit 2 boost converter. + * W (0XXXX7): DC3: Operation frequency of circuit 3 boost converter. + */ +#define MK_ILI9225_REG_PWR_CTRL3 0x12 +/** + * Power Control 4 (W) + * W (0xXX7F): GVD: Set gamma voltage (GVDD) from 2.66V to 5.5V. + */ +#define MK_ILI9225_REG_PWR_CTRL4 0x13 +/** + * Power Control 5 (W) + * W (0x8XXX): VCOMG: Sets amplitude of VCOM signal. + * W (0x7FXX): VCM: Sets VCOMH voltage, from 0.4015 to 1.1000 * GVDD. + * W (0xXX7F): VML: Sets alternate amplitudes of VCOM from 0.534 to 1.200*GVDD. + */ +#define MK_ILI9225_REG_PWR_CTRL5 0x14 +/** + * VCI Recycling (W) + * W (0xXX7X): VCIR: Number of clock cycles in VCI recycling period. + */ +#define MK_ILI9225_REG_VCI_RECYCLING 0x15 +/** + * RAM Address Set 1 (W) + * W (0xXXFF): AD[7:0]: Set the initial value of the address counter. + */ +#define MK_ILI9225_REG_RAM_ADDR_SET1 0x20 +/** + * RAM Address Set 2 (W) + * W (0xXXFF): AD[15:8]: Set the initial value of the address counter. + */ +#define MK_ILI9225_REG_RAM_ADDR_SET2 0x21 +/** + * Read/Write GRAM Data (RW) + * W (0x3FFFF): Write data to GRAM. + * R (0x3FFFF): Read data from GRAM. + */ +#define MK_ILI9225_REG_GRAM_RW 0x22 +/** + * Software Reset (W) + * W (0xFFFF): Perform software reset when 0x00CE is written. + */ +#define MK_ILI9225_REG_SOFT_RESET 0x28 +/** + * Gate Scan Control (W) + * W (0xXX1F): SCN: Line to start gate scan from. + */ +#define MK_ILI9225_REG_GATE_SCAN_CTRL 0x30 +/** + * Vertical Scroll Control 1 (W) + * W (0xXXFF): SEA: Scroll end address. + */ +#define MK_ILI9225_REG_VERT_SCROLL_CTRL1 0x31 +/** + * Vertical Scroll Control 2 (W) + * W (0xXXFF): SSA: Scroll start address. + */ +#define MK_ILI9225_REG_VERT_SCROLL_CTRL2 0x32 +/** + * Vertical Scroll Control 3 (W) + * W (0xXXFF): SST: Scroll step. + */ +#define MK_ILI9225_REG_VERT_SCROLL_CTRL3 0x33 +/** + * Partial Screen Driving Position (W) + * W (0xXXFF): SE: High byte of screen end position. + */ +#define MK_ILI9225_REG_PART_DRIVING_POS1 0x34 +/** + * Partial Screen Driving Position (W) + * W (0xXXFF): SS: High byte of screen start position. + */ +#define MK_ILI9225_REG_PART_DRIVING_POS2 0x35 +/** + * Horizontal RAM Address Position (W) + * W (0xXXFF): HEA: End horizontal position. + */ +#define MK_ILI9225_REG_HORI_WIN_ADDR1 0x36 +/** + * Horizontal RAM Address Position (W) + * W (0xXXFF): HSA: Start horizontal position. + */ +#define MK_ILI9225_REG_HORI_WIN_ADDR2 0x37 +/** + * Vertical RAM Address Position (W) + * W (0xXXFF): VEA: End vertical position. + */ +#define MK_ILI9225_REG_VERT_WIN_ADDR1 0x38 +/** + * Vertical RAM Address Position (W) + * W (0xXXFF): VSA: Start vertical position. + */ +#define MK_ILI9225_REG_VERT_WIN_ADDR2 0x39 +/** + * Gamma Control (W) + */ +#define MK_ILI9225_REG_GAMMA_CTRL1 0x50 +#define MK_ILI9225_REG_GAMMA_CTRL2 0x51 +#define MK_ILI9225_REG_GAMMA_CTRL3 0x52 +#define MK_ILI9225_REG_GAMMA_CTRL4 0x53 +#define MK_ILI9225_REG_GAMMA_CTRL5 0x54 +#define MK_ILI9225_REG_GAMMA_CTRL6 0x55 +#define MK_ILI9225_REG_GAMMA_CTRL7 0x56 +#define MK_ILI9225_REG_GAMMA_CTRL8 0x57 +#define MK_ILI9225_REG_GAMMA_CTRL9 0x58 +#define MK_ILI9225_REG_GAMMA_CTRL10 0x59 + +#define MK_ILI9225_NV_MEM_DATA_PROG 0x60 +#define MK_ILI9225_NV_MEM_CTRL 0x61 +#define MK_ILI9225_NV_MEM_STAT 0x62 +#define MK_ILI9225_NV_MEM_PROTECTION_KEY 0x63 +#define MK_ILI9225_NV_MEM_ID_CODE 0x65 + +#define MK_ILI9225_REG_MTP_TEST_KEY 0x80 +#define MK_ILI9225_REG_MTP_CTRL_REG 0x81 +#define MK_ILI9225_REG_MTP_DATA_READ 0x82 + +/* Useful macros. */ +#define ARRAYSIZE(array) (sizeof(array)/sizeof(array[0])) + +/* Structure definitions. */ +struct reg_dat_pair { + uint16_t reg; + uint16_t dat; +}; + +/* Local functions. */ +static void write_register(uint16_t cmd); +static void write_data(uint16_t dat); +#if MK_ILI9225_READ_AVAILABLE +static uint16_t read_data(void); +static uint16_t get_register(uint16_t reg); +#endif + +static void write_register(uint16_t cmd) +{ + mk_ili9225_set_rs(0); + mk_ili9225_set_cs(0); + mk_ili9225_spi_write16(&cmd, 1); + mk_ili9225_set_cs(1); +} + +static void write_data(uint16_t dat) +{ + mk_ili9225_set_rs(1); + mk_ili9225_set_cs(0); + mk_ili9225_spi_write16(&dat, 1); + mk_ili9225_set_cs(1); +} + +#if MK_ILI9225_READ_AVAILABLE +static uint16_t read_data(void) +{ + uint16_t ret; + mk_ili9225_set_rs(1); + mk_ili9225_set_cs(0); + ret = mk_ili9225_spi_read16(); + mk_ili9225_set_cs(1); + return ret; +} + +static uint16_t get_register(uint16_t reg) +{ + write_register(reg); + return read_data(); +} +#endif + +static void set_register(uint16_t reg, uint16_t dat) +{ + write_register(reg); + write_data(dat); +} + +#if MK_ILI9225_READ_AVAILABLE +unsigned mk_ili9225_read_driving_line(void) +{ + unsigned line; + mk_ili9225_set_rs(0); + mk_ili9225_set_cs(0); + line = read_data(); + mk_ili9225_set_cs(1); + return (line >> 8); +} +#endif + +/* Public functions. */ +unsigned mk_ili9225_init(void) +{ + unsigned ret = 0x9225; + + /* Initialise reset pin as not reset. RST must be high before reset. */ + mk_ili9225_set_rst(1); + /* Initialise other pins too. */ + mk_ili9225_set_cs(1); + mk_ili9225_set_rs(0); + /* Make sure pin is initialised by setting small delay. */ + mk_ili9225_delay_ms(1); + + /* Put ILI9225 into reset. */ + mk_ili9225_set_rst(0); + /* Reset low-level width (Tres) is at least 1ms. */ + mk_ili9225_delay_ms(10); + + /* Bring ILI9225 out of reset. */ + mk_ili9225_set_rst(1); + mk_ili9225_delay_ms(50); + + /* Initialise power registers. */ + /* FIXME: This may not be required. All are initialised to 0x00, + * FIXME: but VCOMG is set to 1 on init apparently. */ + { + const uint8_t regs[] = { + MK_ILI9225_REG_PWR_CTRL1, MK_ILI9225_REG_PWR_CTRL2, + MK_ILI9225_REG_PWR_CTRL3, MK_ILI9225_REG_PWR_CTRL4, + MK_ILI9225_REG_PWR_CTRL5 + }; + + for(uint8_t i = 0; i < sizeof(regs); i++) + set_register(regs[i], 0x0000); + } + mk_ili9225_delay_ms(40); + + /* Switch on power control. */ + { + /* VCI set to 2.58V. */ + /* FIXME: CTRL3 VGH is set to 2.58*6=15.48, but it could be + * FIXME: lower ( 9 < VGH < 16.5). + * FIXME: CTRL3 VGL is set to 2.58*-4=-10.32, but it could be + * FIXME: higher ( -4 < VGL < -16.5). + * FIXME: BT could be set to 0b000 for VGH=10, VGL=-7.5. */ + /* CTRL4: GVDD is set to 4.68V. */ + /* CTRL5: Set VCM to 0.8030V. Set VML to 1.104V. */ + /* CTRL1: Set driving capability to "Medium Fast 1". */ + const uint8_t regs[] = { + MK_ILI9225_REG_PWR_CTRL2, MK_ILI9225_REG_PWR_CTRL3, + MK_ILI9225_REG_PWR_CTRL4, MK_ILI9225_REG_PWR_CTRL5, + MK_ILI9225_REG_PWR_CTRL1 + }; + const uint16_t dat[] = { + 0x0018, 0x6121, 0x006F, 0x495F, 0x0800 + }; + + for(uint8_t i = 0; i < sizeof(regs); i++) + set_register(regs[i], dat[i]); + } + mk_ili9225_delay_ms(10); + + /* Enable automatic booster operation, and amplifiers. + * Set VCI1 to 2.76V. + * FIXME: why is VCI1 changed from 2.58 to 2.76V? */ + set_register(MK_ILI9225_REG_PWR_CTRL2, 0x103B); + mk_ili9225_delay_ms(50); + + { + const struct reg_dat_pair cmds[] = { + /* Set shift direction SS from S528 to S1. + * Set active lines NL to 528 * 220 dots. */ + { MK_ILI9225_REG_DRIVER_OUTPUT_CTRL, 0x011C }, + /* Set LCD inversion to disabled. */ + { MK_ILI9225_REG_LCD_AC_DRIVING_CTRL, 0x0100 }, + /* Use BGR mode (swap blue with red). + * Increment vertical and horizontal address. + * Use vertical image. + * FIXME: Why is BGR enabled? */ + /* TODO: Change to horizontal mode. */ + { MK_ILI9225_REG_ENTRY_MODE, 0x1000 }, + /* Turn off all display outputs. */ + { MK_ILI9225_REG_DISPLAY_CTRL, 0x0000 }, + /* Set porches to 8 lines. */ + { MK_ILI9225_REG_BLANK_PERIOD_CTRL, 0x0808 }, + /* Use 1-clock delay to gate output and edge. */ + { MK_ILI9225_REG_FRAME_CYCLE_CTRL, 0x1100 }, + /* Ignore RGB interface settings. */ + { MK_ILI9225_REG_INTERFACE_CTRL, 0x0000 }, + /* Set oscillation frequency to 266.6 kHz. */ + { MK_ILI9225_REG_OSC_CTRL, 0x0701 }, + /* Set VCI recycling to 2 clocks. */ + { MK_ILI9225_REG_VCI_RECYCLING, 0x0020 }, + /* Initialise RAM Address to 0x0 px. */ + { MK_ILI9225_REG_RAM_ADDR_SET1, 0x0000 }, + { MK_ILI9225_REG_RAM_ADDR_SET2, 0x0000 }, + + /* Set scanning position to full screen. */ + { MK_ILI9225_REG_GATE_SCAN_CTRL, 0x0000 }, + /* Set end scan position to 219 + 1 px (0xDB). */ + { MK_ILI9225_REG_VERT_SCROLL_CTRL1, 0x00DB }, + /* Set start scan position to 0 px. */ + { MK_ILI9225_REG_VERT_SCROLL_CTRL2, 0x0000 }, + /* Set scroll step to 0 px (no scrolling). */ + { MK_ILI9225_REG_VERT_SCROLL_CTRL3, 0x0000 }, + /* Set partial screen driving end to 219 + 1 px + * (0xDB). */ + { MK_ILI9225_REG_PART_DRIVING_POS1, 0x00DB }, + /* Set partial screen driving start to 0 px. */ + { MK_ILI9225_REG_PART_DRIVING_POS2, 0x0000 }, + /* Set window to 176 x 220 px (full screen). */ + { MK_ILI9225_REG_HORI_WIN_ADDR1, 0x00AF }, + { MK_ILI9225_REG_HORI_WIN_ADDR2, 0x0000 }, + { MK_ILI9225_REG_VERT_WIN_ADDR1, 0x00DB }, + { MK_ILI9225_REG_VERT_WIN_ADDR2, 0x0000 }, + + /* Gamma curve data. */ + { MK_ILI9225_REG_GAMMA_CTRL1, 0x0000 }, + { MK_ILI9225_REG_GAMMA_CTRL2, 0x0808 }, + { MK_ILI9225_REG_GAMMA_CTRL3, 0x080A }, + { MK_ILI9225_REG_GAMMA_CTRL4, 0x000A }, + { MK_ILI9225_REG_GAMMA_CTRL5, 0x0A08 }, + { MK_ILI9225_REG_GAMMA_CTRL6, 0x0808 }, + { MK_ILI9225_REG_GAMMA_CTRL7, 0x0000 }, + { MK_ILI9225_REG_GAMMA_CTRL8, 0x0A00 }, + { MK_ILI9225_REG_GAMMA_CTRL9, 0x0710 }, + { MK_ILI9225_REG_GAMMA_CTRL10, 0x0710 }, + + /* Enable full colour display. */ + { MK_ILI9225_REG_DISPLAY_CTRL, 0x0012 } + }; + + for(uint_fast8_t i = 0; i < (uint_fast8_t)ARRAYSIZE(cmds); i++) + set_register(cmds[i].reg, cmds[i].dat); + } + mk_ili9225_delay_ms(50); + + /** + * FIXME: TEMON is enabled but FLM isn't exposed? + * GON: Enable display. + * CL: Use full colour. + * REV: reverse greyscale levels. + * D: Switch on display. + */ + set_register(MK_ILI9225_REG_DISPLAY_CTRL, 0x1017); + mk_ili9225_delay_ms(50); + +#if MK_ILI9225_READ_AVAILABLE + /* ret should be 0 if the returned ID was 0x9225. */ + ret -= get_register(MK_ILI9225_REG_DRIVER_CODE_READ); +#else + ret = 0; +#endif + + return ret; +} + +void mk_ili9225_display_control(bool invert, ili9225_color_mode_e colour_mode) +{ + uint16_t dat = 0x0013; + dat |= ((uint16_t)invert << 2); + dat |= ((uint16_t)colour_mode << 3); + set_register(MK_ILI9225_REG_DISPLAY_CTRL, dat); +} + +void mk_ili9225_set_window(uint16_t hor_start, uint16_t hor_end, + uint16_t vert_start, uint16_t vert_end) +{ + assert(hor_start < hor_end); + assert(hor_end < SCREEN_SIZE_X); + assert(vert_start < vert_end); + assert(vert_end < SCREEN_SIZE_Y); + + set_register(MK_ILI9225_REG_RAM_ADDR_SET1, hor_start); + set_register(MK_ILI9225_REG_RAM_ADDR_SET2, vert_start); + set_register(MK_ILI9225_REG_HORI_WIN_ADDR1, hor_end); + set_register(MK_ILI9225_REG_HORI_WIN_ADDR2, hor_start); + set_register(MK_ILI9225_REG_VERT_WIN_ADDR1, vert_end); + set_register(MK_ILI9225_REG_VERT_WIN_ADDR2, vert_start); +} + +void mk_ili9225_set_address(uint8_t x, uint8_t y) +{ + set_register(MK_ILI9225_REG_RAM_ADDR_SET1, x); + set_register(MK_ILI9225_REG_RAM_ADDR_SET2, y); +} + +void mk_ili9225_write_pixels(const uint16_t *pixels, uint_fast16_t nmemb) +{ + assert(pixels != NULL); + assert(nmemb > 0); + + write_register(MK_ILI9225_REG_GRAM_RW); + mk_ili9225_set_rs(1); + mk_ili9225_set_cs(0); + mk_ili9225_spi_write16(pixels, nmemb); + mk_ili9225_set_cs(1); +} + +void mk_ili9225_write_pixels_start(void) +{ + write_register(MK_ILI9225_REG_GRAM_RW); + mk_ili9225_set_rs(1); + mk_ili9225_set_cs(0); +} + +void mk_ili9225_write_pixels_end(void) +{ + mk_ili9225_set_cs(1); +} + +void mk_ili9225_power_control(uint8_t drive_power, bool sleep) +{ + uint16_t data; + data = (uint16_t)drive_power << 8; + data |= sleep; + set_register(MK_ILI9225_REG_PWR_CTRL1, data); +} + +void mk_ili9225_set_gate_scan(uint16_t hor_start, uint16_t hor_end) +{ + uint16_t dat = 0x0100; + uint16_t lcd_line_start = hor_end / 8; + set_register(MK_ILI9225_REG_DRIVER_OUTPUT_CTRL, dat | lcd_line_start); + set_register(MK_ILI9225_REG_GATE_SCAN_CTRL, hor_start / 8); +} + +void mk_ili9225_set_drive_freq(uint16_t f) +{ + f &= 0x000F; + f <<= 8; + f |= 1; + set_register(MK_ILI9225_REG_OSC_CTRL, f); +} + +void mk_ili9225_exit(void) +{ +}