Работаем с цветным TFT дисплеем ILI9341

Наконец добрались руки до дисплея. Купил его еще летом, а вот нормально заняться им вышло только сейчас.
Дисплеев на сегодняшний день огромное количество, мой выбор пал на решение "лоу кост". Такой дисплей у китайских друзей стоит в 5-6$.

В общем то ничего необычного. Разрешение всего 320х240, но зато он цветной и с последовательным интерфейсом, что нещадно бъет по скорости обновления, но сильно экономит ноги контролера (и нервы при соединении на макетке).
У данного дисплея есть контролер для обработки команд и обмена с микроконтроллером - ILI9341.

Из него можно узнать набор команд и последовательность данных.
У дисплея есть ноги для обмена по SPI, нога аппаратного сброса и нога, которая помогает ему понять когда принятые данные - команда, а когда - цвет пикселя.
Для всего этого "дергонога" я решил заготовить макросы:

#define TFT_RESET_PIN GPIO_Pin_2
#define TFT_DC_PIN    GPIO_Pin_3
#define TFT_CS_PIN    GPIO_Pin_4

#define TFT_DC_SET    GPIO_SetBits(GPIOA, TFT_DC_PIN);
#define TFT_DC_RESET  GPIO_ResetBits(GPIOA, TFT_DC_PIN);

#define TFT_RST_SET   GPIO_SetBits(GPIOA, TFT_RESET_PIN);
#define TFT_RST_RESET GPIO_ResetBits(GPIOA, TFT_RESET_PIN);

#define TFT_CS_SET    GPIO_SetBits(GPIOA, TFT_CS_PIN);
#define TFT_CS_RESET   GPIO_ResetBits(GPIOA, TFT_CS_PIN);

Этап настройки SPI я пожалуй пропущу. Тут все стандартно, берем настройку из любого примера для вашего камня в режиме 1 (CPOL = 0, CPHA = 0).
Когда это готово, делаем функции посылки команды и данных:

// шлем команду
void LCD_SendCommand(u8 com) {
  TFT_DC_RESET;
  TFT_CS_RESET;
  spi1_send(com);
  TFT_CS_SET;
}

// шлем данные
void LCD_SendData(u8 data) {
  TFT_DC_SET;
  TFT_CS_RESET;
  spi1_send(data);
  TFT_CS_SET;
}

Тут тоже все просто. Для посылки команды нужно чтобы линия DC была в 0, а для данных - в 1. С линией CS поступаем как всегда. Можно в принципе возложить эту ответственность на железо, если ваш контроллер такое умеет.

Инициализация дисплея

Прежде чем рисовать картинки нужно настроить дисплей. Для этого необходимо дернуть ногу аппаратного ресета дисплея, заслать команду сброса и подождать, после чего отправить пачку команд, расписывать их смысла нет, если интересно идем в документ по ссылке выше.

void LCD_Init(){
  // настраиваем ноги
  LCD_InitGPIO();
  
  TFT_CS_SET;
  spi1_init(); 
  
  // сброс дисплея
  TFT_RST_SET;
  LCD_SendCommand(ILI9341_RESET);
  Delay(100);
  
  /// настраиваем дисплей
  LCD_SendCommand(ILI9341_POWERA);
  LCD_SendData(0x39);
  LCD_SendData(0x2C);
  LCD_SendData(0x00);
  LCD_SendData(0x34);
  LCD_SendData(0x02);
  LCD_SendCommand(ILI9341_POWERB);
  LCD_SendData(0x00);
  LCD_SendData(0xC1);
  LCD_SendData(0x30);
  LCD_SendCommand(ILI9341_DTCA);
  LCD_SendData(0x85);
  LCD_SendData(0x00);
  LCD_SendData(0x78);
  LCD_SendCommand(ILI9341_DTCB);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendCommand(ILI9341_POWER_SEQ);
  LCD_SendData(0x64);
  LCD_SendData(0x03);
  LCD_SendData(0x12);
  LCD_SendData(0x81);
  LCD_SendCommand(ILI9341_PRC);
  LCD_SendData(0x20);
  LCD_SendCommand(ILI9341_POWER1);
  LCD_SendData(0x23);
  LCD_SendCommand(ILI9341_POWER2);
  LCD_SendData(0x10);
  LCD_SendCommand(ILI9341_VCOM1);
  LCD_SendData(0x3E);
  LCD_SendData(0x28);
  LCD_SendCommand(ILI9341_VCOM2);
  LCD_SendData(0x86);
  LCD_SendCommand(ILI9341_MAC);
  LCD_SendData(0x48);
  LCD_SendCommand(ILI9341_PIXEL_FORMAT);
  LCD_SendData(0x55);
  LCD_SendCommand(ILI9341_FRC);
  LCD_SendData(0x00);
  LCD_SendData(0x18);
  LCD_SendCommand(ILI9341_DFC);
  LCD_SendData(0x08);
  LCD_SendData(0x82);
  LCD_SendData(0x27);
  LCD_SendCommand(ILI9341_3GAMMA_EN);
  LCD_SendData(0x00);
  LCD_SendCommand(ILI9341_COLUMN_ADDR);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendData(0xEF);
  LCD_SendCommand(ILI9341_PAGE_ADDR);
  LCD_SendData(0x00);
  LCD_SendData(0x00);
  LCD_SendData(0x01);
  LCD_SendData(0x3F);
  LCD_SendCommand(ILI9341_GAMMA);
  LCD_SendData(0x01);
  LCD_SendCommand(ILI9341_PGAMMA);
  LCD_SendData(0x0F);
  LCD_SendData(0x31);
  LCD_SendData(0x2B);
  LCD_SendData(0x0C);
  LCD_SendData(0x0E);
  LCD_SendData(0x08);
  LCD_SendData(0x4E);
  LCD_SendData(0xF1);
  LCD_SendData(0x37);
  LCD_SendData(0x07);
  LCD_SendData(0x10);
  LCD_SendData(0x03);
  LCD_SendData(0x0E);
  LCD_SendData(0x09);
  LCD_SendData(0x00);
  LCD_SendCommand(ILI9341_NGAMMA);
  LCD_SendData(0x00);
  LCD_SendData(0x0E);
  LCD_SendData(0x14);
  LCD_SendData(0x03);
  LCD_SendData(0x11);
  LCD_SendData(0x07);
  LCD_SendData(0x31);
  LCD_SendData(0xC1);
  LCD_SendData(0x48);
  LCD_SendData(0x08);
  LCD_SendData(0x0F);
  LCD_SendData(0x0C);
  LCD_SendData(0x31);
  LCD_SendData(0x36);
  LCD_SendData(0x0F);
  LCD_SendCommand(ILI9341_SLEEP_OUT);
  
  Delay(100);
  LCD_SendCommand(ILI9341_DISPLAY_ON);
  LCD_SendCommand(ILI9341_GRAM);
}

Рисуем

Чтобы что-то нарисовать нужно поставить курсор в нужное место:

void LCD_SetCursorPosition(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
	LCD_SendCommand(ILI9341_COLUMN_ADDR);
	LCD_SendData(x1 >> 8);
	LCD_SendData(x1 & 0xFF);
	LCD_SendData(x2 >> 8);
	LCD_SendData(x2 & 0xFF);

	LCD_SendCommand(ILI9341_PAGE_ADDR);
	LCD_SendData(y1 >> 8);
	LCD_SendData(y1 & 0xFF);
	LCD_SendData(y2 >> 8);
	LCD_SendData(y2 & 0xFF);
}

Курсор поставили, можно рисовать:

void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
    LCD_SetCursorPosition(x, y, x, y);
    LCD_SendCommand(ILI9341_GRAM);
    LCD_SendData(color >> 8);
    LCD_SendData(color & 0xFF);
}

Ради интереса можно сохранить картинку в GIMP в формат C файла. И подтянуть его в проект. Там вы увидите массив. Его нужно просто вывести в цикле на экран.

void LCD_Image(unsigned char const *img) {
        uint32_t n;
        
	LCD_SetCursorPosition(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);

	LCD_SendCommand(ILI9341_GRAM);

	for (n = 0; n < LCD_PIXEL_COUNT; n++) {
                u8 color = *img;
                img++;
		LCD_SendData(*img);
		LCD_SendData(color);
                img++;
	}
}

Результат:

Шрифты

Вывести текст на экрна тоже очень просто. Нужно раздобыть массив со шрифтом. Сделать это можно при помощи утилиты BitFontCreator (она крутая, но денег немереных стоит) или же народной приблуды FontsGenerator.exe, которую я нашел на easyelectronics.
В массиве аккуратно уложены битовые маски шрифта. Например имеем 16 пикселей высотой и 8 шириной, тогда нам нужно последовательно выводить по пикселю и при этом следить каким цветом нужно сделать текущий пиксель.

uint32_t LCD_Putchar(uint32_t x, uint32_t y, char c) {
        uint32_t i, j;
        unsigned short Data;
        
        uint32_t offset = (c-32)*TimesNewRoman.height;
        uint16_t width = TimesNewRoman.width;
        
    	for (i = 0; i < TimesNewRoman.height; i++) {

            Data = TimesNewRoman.data_table[offset+i];    
            
            
            for (j = 0; j < width; j++) {
                if ((Data << j) & 0x8000) {
                    LCD_DrawPixel(x + j, (y + i), 0xFFFF);  //white
                } else {
                    LCD_DrawPixel(x + j, (y + i), 0x0000);  //black
                }
            }
        }
        
        return x+width;
}

Для описанного примера в массиве будет 16 двухбайтных чисел. В каждом из чисел информация о том, закрашен данный пиксель или нет.
BitFontCreator генерит таблици с шрифтами различной ширины. Т.е. каждая буква имеет различную ширину, это повышает читаемость и делает текст красивее для глаза, но принуждает нас каждый раз смотреть ширину в массиве.

Строку можно выводить обычным методом при помощи указателей:

void LCD_DrawString(uint32_t x, uint32_t y, char *str)
{
    while(*str) {
        x = LCD_Putchar(x,y,*str++);
    }
}

Получается вот так:

Пример можно посмотреть тут: https://www.dropbox.com/s/3q0tvjz2f6j399p/tft_lcd_spi.rar

Просмотров:   42682