Использование TFT-дисплеев на базе ILI9341 с тачскрином
25 июня 2018
Некоторое время назад мы познакомились с цветными TFT-дисплеями на базе контроллера ST7735. Дисплеи эти весьма неплохи, но не лишены недостатков. В частности, максимальная диагональ таких дисплеев составляет 1.8 дюйма, а разрешение ограничено 128x160 пикселями, что может подходить не для всех проектов. Поэтому сегодня речь пойдет о TFT-дисплеях на базе ILI9341, имеющих среди прочих преимуществ больший размер и большее разрешение.
Такие дисплеи, как правило, продаются в виде модулей с SPI-интерфейсом, имеющих разъем для SD-карт (полноразмерных, не MicroSD). Можно найти модули с диагональю 2.2", 2.4", 2.8" и 3.2". Независимо от диагонали, дисплеи на базе ILI9341 всегда имеют разрешение 240x320. Большинство модулей имеют резистивную сенсорную панель, но также встречаются модули и без нее. Наличие или отсутствие тачскрина несложно определить чисто визуально. Цена одного модуля составляет 7-9$.
Протокол, по которому работают данные модули, идентичен протоколу 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 (0xD0) и read Y (0x90). В ответ на каждую команду контроллер посылает 16 бит (число от 0 до 65535), соответствующих координате X или Y точки, которую нажал пользователь. Нажат ли сейчас тачскрин можно определить по состоянию дополнительного пина IRQ. Если он имеет низкое напряжение, значит тачскрин нажат, иначе пользователь его не касается.
Весь код работы с сенсорной панелью выглядит так:
#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 = %d\r\n", 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-шины. Если же этого хочется избежать, частоту шины можно менять динамически:
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++;
}
}
}
Приведенный отрывок кода превращает дисплей в рисовалку:
У меня не нашлось стилуса, поэтому вместо него я использовал ручку, тем концом, где нет стержня. К сожалению, работать с такими сенсорными панелями при помощи пальца крайне затруднительно. Точность определения координат в этом случае ни на что не годится.
Кстати, вы можете заметить, что на приведенном фото я использовал тот же шилд, что был использован для ST7735. Это возможно благодаря тому, что модули на базе ST7735 и ILI9341 имеют совместимое расположение пинов. Понадобилось только добавить на шилд дополнительные гнезда для тачскрина. Правда, из-за нехватки места на плате, гнезда мне пришлось повесить в воздухе, зафиксировав их термоклеем.
Полную версию исходников к этой заметке вы найдете на GitHub. Наиболее же полную информацию об ILI9341 можно найти в его даташите [PDF].
Метки: STM32, Электроника.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.