/* * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. * * This file is part of PortaPack. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include "lcd_ili9341.hpp" #include "portapack_io.hpp" using namespace portapack; #include "utility.hpp" #include "ch.h" #include namespace lcd { namespace { void lcd_reset() { io.lcd_reset_state(false); chThdSleepMilliseconds(1); io.lcd_reset_state(true); chThdSleepMilliseconds(10); io.lcd_reset_state(false); chThdSleepMilliseconds(120); } void lcd_sleep_in() { io.lcd_data_write_command_and_data(0x10, {}); chThdSleepMilliseconds(5); } void lcd_sleep_out() { io.lcd_data_write_command_and_data(0x11, {}); chThdSleepMilliseconds(120); } void lcd_display_on() { io.lcd_data_write_command_and_data(0x29, {}); } void lcd_init() { // LCDs are configured for IM[2:0] = 001 // 8080-I system, 16-bit parallel bus // // 0x3a: DBI[2:0] = 101 // MDT[1:0] = XX (if not in 18-bit mode, right?) // Power control B // 0 // PCEQ=1, DRV_ena=0, Power control=3 io.lcd_data_write_command_and_data(0xCF, { 0x00, 0xD9, 0x30 }); // Power on sequence control io.lcd_data_write_command_and_data(0xED, { 0x64, 0x03, 0x12, 0x81 }); // Driver timing control A io.lcd_data_write_command_and_data(0xE8, { 0x85, 0x10, 0x78 }); // Power control A io.lcd_data_write_command_and_data(0xCB, { 0x39, 0x2C, 0x00, 0x34, 0x02 }); // Pump ratio control io.lcd_data_write_command_and_data(0xF7, { 0x20 }); // Driver timing control B io.lcd_data_write_command_and_data(0xEA, { 0x00, 0x00 }); io.lcd_data_write_command_and_data(0xB1, { 0x00, 0x1B }); // Blanking Porch Control // VFP = 0b0000010 = 2 (number of HSYNC of vertical front porch) // VBP = 0b0000010 = 2 (number of HSYNC of vertical back porch) // HFP = 0b0001010 = 10 (number of DOTCLOCK of horizontal front porch) // HBP = 0b0010100 = 20 (number of DOTCLOCK of horizontal back porch) io.lcd_data_write_command_and_data(0xB5, { 0x02, 0x02, 0x0a, 0x14 }); // Display Function Control // PT[1:0] = 0b10 // PTG[1:0] = 0b10 // ISC[3:0] = 0b0010 (scan cycle interval of gate driver: 5 frames) // SM = 0 (gate driver pin arrangement in combination with GS) // SS = 1 (source output scan direction S720 -> S1) // GS = 0 (gate output scan direction G1 -> G320) // REV = 1 (normally white) // NL = 0b100111 (default) // PCDIV = 0b000000 (default?) io.lcd_data_write_command_and_data(0xB6, { 0x0A, 0xA2, 0x27, 0x00 }); // Power Control 1 //VRH[5:0] io.lcd_data_write_command_and_data(0xC0, { 0x1B }); // Power Control 2 //SAP[2:0];BT[3:0] io.lcd_data_write_command_and_data(0xC1, { 0x12 }); // VCOM Control 1 io.lcd_data_write_command_and_data(0xC5, { 0x32, 0x3C }); // VCOM Control 2 io.lcd_data_write_command_and_data(0xC7, { 0x9B }); // Memory Access Control // Invert X and Y memory access order, so upper-left of // screen is (0,0) when writing to display. io.lcd_data_write_command_and_data(0x36, { (1 << 7) | // MY=1 (1 << 6) | // MX=1 (0 << 5) | // MV=0 (1 << 4) | // ML=1: reverse vertical refresh to simplify scrolling logic (1 << 3) // BGR=1: For Kingtech LCD, BGR filter. }); // COLMOD: Pixel Format Set // DPI=101 (16 bits/pixel), DBI=101 (16 bits/pixel) io.lcd_data_write_command_and_data(0x3A, { 0x55 }); //io.lcd_data_write_command_and_data(0xF6, { 0x01, 0x30 }); // WEMODE=1 (reset column and page number on overflow) // MDT[1:0] // EPF[1:0]=00 (use channel MSB for LSB) // RIM=0 (If COLMOD[6:4]=101 (65k color), 16-bit RGB interface (1 transfer/pixel)) // RM=0 (system interface/VSYNC interface) // DM[1:0]=00 (internal clock operation) // ENDIAN=0 (doesn't matter with 16-bit interface) io.lcd_data_write_command_and_data(0xF6, { 0x01, 0x30, 0x00 }); // 3Gamma Function Disable io.lcd_data_write_command_and_data(0xF2, { 0x00 }); // Gamma curve selected io.lcd_data_write_command_and_data(0x26, { 0x01 }); // Set Gamma io.lcd_data_write_command_and_data(0xE0, { 0x0F, 0x1D, 0x19, 0x0E, 0x10, 0x07, 0x4C, 0x63, 0x3F, 0x03, 0x0D, 0x00, 0x26, 0x24, 0x04 }); // Set Gamma io.lcd_data_write_command_and_data(0xE1, { 0x00, 0x1C, 0x1F, 0x02, 0x0F, 0x03, 0x35, 0x25, 0x47, 0x04, 0x0C, 0x0B, 0x29, 0x2F, 0x05 }); lcd_sleep_out(); lcd_display_on(); // Turn on Tearing Effect Line (TE) output signal. io.lcd_data_write_command_and_data(0x35, { 0b00000000 }); } void lcd_set(const uint_fast8_t command, const uint_fast16_t start, const uint_fast16_t end) { io.lcd_data_write_command_and_data(command, { static_cast(start >> 8), static_cast(start & 0xff), static_cast(end >> 8), static_cast(end & 0xff) }); } void lcd_ramwr_start() { io.lcd_data_write_command_and_data(0x2c, {}); } void lcd_ramrd_start() { io.lcd_data_write_command_and_data(0x2e, {}); io.lcd_read_word(); } void lcd_caset(const uint_fast16_t start_column, uint_fast16_t end_column) { lcd_set(0x2a, start_column, end_column); } void lcd_paset(const uint_fast16_t start_page, const uint_fast16_t end_page) { lcd_set(0x2b, start_page, end_page); } void lcd_start_ram_write( const ui::Point p, const ui::Size s ) { lcd_caset(p.x, p.x + s.w - 1); lcd_paset(p.y, p.y + s.h - 1); lcd_ramwr_start(); } void lcd_start_ram_read( const ui::Point p, const ui::Size s ) { lcd_caset(p.x, p.x + s.w - 1); lcd_paset(p.y, p.y + s.h - 1); lcd_ramrd_start(); } void lcd_start_ram_write( const ui::Rect& r ) { lcd_start_ram_write(r.pos, r.size); } void lcd_start_ram_read( const ui::Rect& r ) { lcd_start_ram_read(r.pos, r.size); } void lcd_vertical_scrolling_definition( const uint_fast16_t top_fixed_area, const uint_fast16_t vertical_scrolling_area, const uint_fast16_t bottom_fixed_area ) { io.lcd_data_write_command_and_data(0x33, { static_cast(top_fixed_area >> 8), static_cast(top_fixed_area & 0xff), static_cast(vertical_scrolling_area >> 8), static_cast(vertical_scrolling_area & 0xff), static_cast(bottom_fixed_area >> 8), static_cast(bottom_fixed_area & 0xff) }); } void lcd_vertical_scrolling_start_address( const uint_fast16_t vertical_scrolling_pointer ) { io.lcd_data_write_command_and_data(0x37, { static_cast(vertical_scrolling_pointer >> 8), static_cast(vertical_scrolling_pointer & 0xff) }); } } void ILI9341::init() { lcd_reset(); lcd_init(); io.lcd_backlight(1); } void ILI9341::shutdown() { io.lcd_backlight(0); lcd_reset(); } void ILI9341::sleep() { lcd_sleep_in(); } void ILI9341::wake() { lcd_sleep_out(); } void ILI9341::fill_rectangle(ui::Rect r, const ui::Color c) { const auto r_clipped = r.intersect(screen_rect()); if( !r_clipped.is_empty() ) { lcd_start_ram_write(r_clipped); size_t count = r_clipped.size.w * r_clipped.size.h; io.lcd_write_pixels(c, count); } } void ILI9341::render_line(const ui::Point p, const uint8_t count, const ui::Color* line_buffer) { lcd_start_ram_write(p, { count, 1 }); io.lcd_write_pixels(line_buffer, count); } void ILI9341::render_box(const ui::Point p, const ui::Size s, const ui::Color* line_buffer) { lcd_start_ram_write(p, s); io.lcd_write_pixels(line_buffer, s.w * s.h); } void ILI9341::drawBMP(const ui::Point p, const uint8_t * bitmap) { uint32_t pixel_data, pal_data; uint8_t pal, by, c, count; ui::Color linebuffer[240]; ui::Coord px = 0, py; ui::Color palette[16]; uint32_t bmpwidth, bmpheight; // RLE_4 BMP loader with hardcoded size and no delta :( if (bitmap[0x1E] != 2) return; // Bad compression type bmpwidth = static_cast( (bitmap[0x12]) | (bitmap[0x13] << 8) | (bitmap[0x14] << 16)| (bitmap[0x15] << 24) ); bmpheight = static_cast( (bitmap[0x16]) | (bitmap[0x17] << 8) | (bitmap[0x18] << 16)| (bitmap[0x19] << 24) ); pal_data = bitmap[0x0E] + 0x0E; pixel_data = bitmap[0x0A]; pal = 0; for (c = 0; c < (16*4); c+=4) { palette[pal++] = ui::Color(bitmap[c+2+pal_data], bitmap[c+1+pal_data], bitmap[c+pal_data]); } py = bmpheight + 16; do { by = bitmap[pixel_data++]; if (by) { count = by; by = bitmap[pixel_data++]; for (c = 0; c < count; c+=2) { linebuffer[px++] = palette[by >> 4]; if (px < bmpwidth) linebuffer[px++] = palette[by & 15]; } if (pixel_data & 1) pixel_data++; } else { by = bitmap[pixel_data++]; if (by == 0) { render_line({p.x, p.y + py}, bmpwidth, linebuffer); py--; px = 0; } else if (by == 1) { break; } else if (by == 2) { // Delta } else { count = by; for (c = 0; c < count; c+=2) { by = bitmap[pixel_data++]; linebuffer[px++] = palette[by >> 4]; if (px < bmpwidth) linebuffer[px++] = palette[by & 15]; } if (pixel_data & 1) pixel_data++; } } } while (1); } void ILI9341::draw_line(const ui::Point start, const ui::Point end, const ui::Color color) { int x0 = start.x; int y0 = start.y; int x1 = end.x; int y1 = end.y; int dx = std::abs(x1-x0), sx = x0dy ? dx : -dy)/2, e2; for(;;){ draw_pixel({static_cast(x0), static_cast(y0)}, color); if (x0==x1 && y0==y1) break; e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } } void ILI9341::fill_circle( const ui::Point center, const ui::Dim radius, const ui::Color foreground, const ui::Color background ) { const uint32_t radius2 = radius * radius; for(int32_t y=-radius; y(x + center.x), static_cast(y + center.y) }, color); } } } void ILI9341::draw_pixel( const ui::Point p, const ui::Color color ) { if( screen_rect().contains(p) ) { lcd_start_ram_write(p, { 1, 1 }); io.lcd_write_pixel(color); } } void ILI9341::draw_pixels( const ui::Rect r, const ui::Color* const colors, const size_t count ) { /* TODO: Assert that rectangle width x height < count */ lcd_start_ram_write(r.pos, r.size); io.lcd_write_pixels(colors, count); } void ILI9341::read_pixels( const ui::Rect r, ui::ColorRGB888* const colors, const size_t count ) { /* TODO: Assert that rectangle width x height < count */ lcd_start_ram_read(r); io.lcd_read_bytes( reinterpret_cast(colors), count * sizeof(ui::ColorRGB888) ); } void ILI9341::draw_bitmap( const ui::Point p, const ui::Size size, const uint8_t* const pixels, const ui::Color foreground, const ui::Color background ) { lcd_start_ram_write(p, size); const size_t count = size.w * size.h; for(size_t i=0; i> 3] & (1U << (i & 0x7)); io.lcd_write_pixel(pixel ? foreground : background); } } void ILI9341::draw_glyph( const ui::Point p, const ui::Glyph& glyph, const ui::Color foreground, const ui::Color background ) { draw_bitmap(p, glyph.size(), glyph.pixels(), foreground, background); } void ILI9341::scroll_set_area( const ui::Coord top_y, const ui::Coord bottom_y ) { scroll_state.top_area = top_y; scroll_state.bottom_area = height() - bottom_y; scroll_state.height = bottom_y - top_y; lcd_vertical_scrolling_definition(scroll_state.top_area, scroll_state.height, scroll_state.bottom_area); } ui::Coord ILI9341::scroll_set_position( const ui::Coord position ) { scroll_state.current_position = position % scroll_state.height; const uint_fast16_t address = scroll_state.top_area + scroll_state.current_position; lcd_vertical_scrolling_start_address(address); return address; } ui::Coord ILI9341::scroll(const int32_t delta) { return scroll_set_position(scroll_state.current_position + scroll_state.height - delta); } ui::Coord ILI9341::scroll_area_y(const ui::Coord y) const { const auto wrapped_y = (scroll_state.current_position + y) % scroll_state.height; return wrapped_y + scroll_state.top_area; } void ILI9341::scroll_disable() { lcd_vertical_scrolling_definition(0, height(), 0); lcd_vertical_scrolling_start_address(0); } } /* namespace lcd */