Некоторое время назад мы познакомились с цветными TFT-дисплеями на базе контроллера ST7735 . Дисплеи эти весьма неплохи, но не лишены недостатков. В частности, максимальная диагональ таких дисплеев составляет 1.8 дюйма, а разрешение ограничено 128 x 160 пикселями, что может подходить не для всех проектов. Поэтому сегодня речь пойдет о TFT-дисплеях на базе ILI9341, имеющих среди прочих преимуществ больший размер и большее разрешение.

Такие дисплеи, как правило, продаются в виде модулей с SPI-интерфейсом, имеющих разъем для SD-карт (полноразмерных, не MicroSD). Можно найти модули с диагональю 2.2″, 2.4″, 2.8″ и 3.2″. Независимо от диагонали, дисплеи на базе ILI9341 всегда имеют разрешение 240 x 320. Большинство модулей имеют резистивную сенсорную панель, но также встречаются модули и без нее. Наличие или отсутствие тачскрина несложно определить чисто визуально. Цена одного модуля составляет 7-9$.

ILI9341: как отличить модуль с тачскрином от модуля без тачскрина

Протокол, по которому работают данные модули, идентичен протоколу ST7735. Отличается только последовательность команд, которую необходимо выполнить для инициализации дисплея. Я лично подглядел эту последовательность в репозитории martnak/STM32-ILI9341 . Кстати, на вид это довольно адекватная библиотека для STM32 на базе HAL. Что, впрочем, не помешало мне сделать собственную библиотеку с интерфейсом, аналогичным интерфейсу моей библиотеки для ST7735. Для Arduino же можно рекомендовать библиотеку Adafruit_ILI9341 , а также связку UTFT и URTouch . Библиотека от Adafruit работает заметно быстрее UTFT. Но в отличие от UTFT, она написана специально для модулей производства Adafruit и, похоже, несовместима с тачскринами китайских модулей.

Важно! ILI9341 не понимает 5-и вольтовую логику от слова совсем. Если вы собираетесь использовать его из 5-и вольтовой Arduino, используйте ковертер уровня, например на базе TXS0108E ( даташит [PDF] ). Иначе ничего не будет работать. STM32 использует 3.3 вольтовую логику, поэтому для этих микроконтроллеров ковертер уровня не нужен.

Контроллер тачскрина на имеющихся у меня модулях имеет маркировку HR2046. Он также работает по SPI, притом протокол весьма незамысловат. Есть две команды — read X (0 x D0) и read Y (0 x 90). В ответ на каждую команду контроллер посылает 16 бит (число от 0 до 65535), соответствующих координате X или Y точки, которую нажал пользователь. Нажат ли сейчас тачскрин можно определить по состоянию дополнительного пина IRQ. Если он имеет низкое напряжение, значит тачскрин нажат, иначе пользователь его не касается.

Весь код работы с сенсорной панелью выглядит так:

#define READ_X 0xD0
#define READ_Y 0x90

static void ILI9341_TouchSelect ( ) {
HAL_GPIO_WritePin ( ILI9341_TOUCH_CS_GPIO_Port ,
ILI9341_TOUCH_CS_Pin , GPIO_PIN_RESET ) ;
}

void ILI9341_TouchUnselect ( ) {
HAL_GPIO_WritePin ( ILI9341_TOUCH_CS_GPIO_Port ,
ILI9341_TOUCH_CS_Pin , GPIO_PIN_SET ) ;
}

bool ILI9341_TouchPressed ( ) {
return HAL_GPIO_ReadPin ( ILI9341_TOUCH_IRQ_GPIO_Port ,
ILI9341_TOUCH_IRQ_Pin ) == GPIO_PIN_RESET ;
}

bool ILI9341_TouchGetCoordinates ( uint16_t * x , uint16_t * y ) {
static const uint8_t cmd_read_x [ ] = { READ_X } ;
static const uint8_t cmd_read_y [ ] = { READ_Y } ;
static const uint8_t zeroes_tx [ ] = { 0x00 , 0x00 } ;

ILI9341_TouchSelect ( ) ;

uint32_t avg_x = 0 ;
uint32_t avg_y = 0 ;
uint8_t nsamples = 0 ;
for ( uint8_t i = 0 ; i < 16 ; i ++ ) {
if ( ! ILI9341_TouchPressed ( ) )
break ;

nsamples ++;

HAL_SPI_Transmit ( & ILI9341_TOUCH_SPI_PORT ,
( uint8_t * ) cmd_read_y , sizeof ( cmd_read_y ) , HAL_MAX_DELAY ) ;
uint8_t y_raw [ 2 ] ;
HAL_SPI_TransmitReceive ( & ILI9341_TOUCH_SPI_PORT ,
( uint8_t * ) zeroes_tx , y_raw , sizeof ( y_raw ) , HAL_MAX_DELAY ) ;

HAL_SPI_Transmit ( & ILI9341_TOUCH_SPI_PORT ,
( uint8_t * ) cmd_read_x , sizeof ( cmd_read_x ) , HAL_MAX_DELAY ) ;
uint8_t x_raw [ 2 ] ;
HAL_SPI_TransmitReceive ( & ILI9341_TOUCH_SPI_PORT ,
( uint8_t * ) zeroes_tx , x_raw , sizeof ( x_raw ) , HAL_MAX_DELAY ) ;

avg_x += ( ( ( uint16_t ) x_raw [ 0 ] ) << 8 ) | ( ( uint16_t ) x_raw [ 1 ] ) ;
avg_y += ( ( ( uint16_t ) y_raw [ 0 ] ) << 8 ) | ( ( uint16_t ) y_raw [ 1 ] ) ;
}

ILI9341_TouchUnselect ( ) ;

if ( nsamples < 16 )
return false ;

uint32_t raw_x = ( avg_x / 16 ) ;
if ( raw_x < ILI9341_TOUCH_MIN_RAW_X )
raw_x = ILI9341_TOUCH_MIN_RAW_X ;
if ( raw_x > ILI9341_TOUCH_MAX_RAW_X )
raw_x = ILI9341_TOUCH_MAX_RAW_X ;

uint32_t raw_y = ( avg_y / 16 ) ;
if ( raw_y < ILI9341_TOUCH_MIN_RAW_X )
raw_y = ILI9341_TOUCH_MIN_RAW_Y ;
if ( raw_y > ILI9341_TOUCH_MAX_RAW_Y )
raw_y = ILI9341_TOUCH_MAX_RAW_Y ;

// Uncomment this line to calibrate touchscreen:
// UART_Printf(«raw_x = %d, raw_y = %drn», x, y);

* x = ( raw_x ILI9341_TOUCH_MIN_RAW_X ) * ILI9341_TOUCH_SCALE_X /
( ILI9341_TOUCH_MAX_RAW_X ILI9341_TOUCH_MIN_RAW_X ) ;
* y = ( raw_y ILI9341_TOUCH_MIN_RAW_Y ) * ILI9341_TOUCH_SCALE_Y /
( ILI9341_TOUCH_MAX_RAW_Y ILI9341_TOUCH_MIN_RAW_Y ) ;

return true ;
}

При использовании тачскрина следует учитывать, что для его надежной работы частота SPI-шины не должна превышать 1 МГц, иначе координаты начинают приходить с существенными искажениями. Но с дисплеем по тому же SPI вы наверняка захотите работать на большей частоте. Одно из решений заключается в том, чтобы использовать отдельные SPI-шины. Если же этого хочется избежать, частоту шины можно менять динамически:

void loop ( ) {
if ( HAL_SPI_DeInit ( & hspi1 ) != HAL_OK ) {
UART_Printf ( «HAL_SPI_DeInit failed! r n » ) ;
return ;
}

hspi1. Init . BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2 ;

if ( HAL_SPI_Init ( & hspi1 ) != HAL_OK ) {
UART_Printf ( «HAL_SPI_Init failed! r n » ) ;
return ;
}

/* … Пропущено: тут проходит тест дисплея,
полностью аналогичный тесту ST7735 … */

if ( HAL_SPI_DeInit ( & hspi1 ) != HAL_OK ) {
UART_Printf ( «HAL_SPI_DeInit failed! r n » ) ;
return ;
}

hspi1. Init . BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128 ;

if ( HAL_SPI_Init ( & hspi1 ) != HAL_OK ) {
UART_Printf ( «HAL_SPI_Init failed! r n » ) ;
return ;
}

int npoints = 0 ;
while ( npoints < 10000 ) {
uint16_t x , y ;

if ( ILI9341_TouchGetCoordinates ( & x , & y ) ) {
ILI9341_DrawPixel ( x , 320 y , ILI9341_WHITE ) ;
npoints ++;
}
}
}

Приведенный отрывок кода превращает дисплей в рисовалку:

Рисовалка на базе ILI9341

У меня не нашлось стилуса, поэтому вместо него я использовал ручку, тем концом, где нет стержня. К сожалению, работать с такими сенсорными панелями при помощи пальца крайне затруднительно. Точность определения координат в этом случае ни на что не годится.

Кстати, вы можете заметить, что на приведенном фото я использовал тот же шилд, что был использован для ST7735. Это возможно благодаря тому, что модули на базе ST7735 и ILI9341 имеют совместимое расположение пинов. Понадобилось только добавить на шилд дополнительные гнезда для тачскрина. Правда, из-за нехватки места на плате, гнезда мне пришлось повесить в воздухе, зафиксировав их термоклеем.

Полную версию исходников к этой заметке вы найдете на GitHub . Наиболее же полную информацию об ILI9341 можно найти в его даташите [PDF] .

EnglishRussianUkrainian