Ili9341 и сенсорный экран

Да процедуру надо провести. Я делал на есп32.

#include <XPT2046_Touchscreen.h>
#include <SPI.h>

#define CS_PIN  14
// MOSI=11, MISO=12, SCK=13
//T_DO-io23; T_DIN-io19; T_CLK-io18;
//XPT2046_Touchscreen ts(CS_PIN);
//#define TIRQ_PIN  2
XPT2046_Touchscreen ts(CS_PIN);  // Param 2 - NULL - No interrupts
//XPT2046_Touchscreen ts(CS_PIN, 255);  // Param 2 - 255 - No interrupts
//XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling

void setup() {
  Serial.begin(38400);
  ts.begin();
  // ts.begin(SPI1); // use alternate SPI port
  ts.setRotation(1);
  while (!Serial && (millis() <= 1000));
}

void loop() {
  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    Serial.print("Pressure = ");
    Serial.print(p.z);
    Serial.print(", x = ");
    Serial.print(p.x);
    Serial.print(", y = ");
    Serial.print(p.y);
    delay(100);
    Serial.println();
  }
}

Тыкал стилусом строго в углы экрана. Получил 4 пары координат: x1=240,y1=289;x1=203,y2=3875;x2=3740,y2=3807;x2=3705,y1=257. Потом усреднял значения x1,x2,y1,y2 и ставил им соответствие от 0 до 239 и от 0 до 319.
Потом экспериментировал с таким вариантом рисовалки:

// ПИКСЕЛЬНАЯ рисовалка 27 цветов
#include "Adafruit_GFX.h"     // Библиотека обработчика графики
#include "Adafruit_ILI9341.h" // Программные драйвера для дисплеев ILI9341
#include <XPT2046_Touchscreen.h>// Библиотека для работы с сенсорным экраном
#include <SPI.h>        

#define TFT_DC 20              // Пин подключения вывода D/C дисплея
#define TFT_CS 17               // Пин подключения вывода CS дисплея
#define CS_PIN  14
//#define TFT_RST -1            // Пин подключения вывода RESET 
//#define TFT_MISO 16           // Пин подключения вывода дисплея SDO(MISO)
//#define TFT_MOSI 19           // Пин подключения вывода дисплея SDI(MOSI)
//#define TFT_CLK 18            // Пин подключения вывода дисплея SCK

//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);  // Создаем объект дисплея и сообщаем библиотеке распиновку для работы с графикой
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);//Создаем объект дисплея и сообщаем библиотеке распиновку для работы с графикой
XPT2046_Touchscreen ts(CS_PIN);  // Param 2 - NULL - No interrupts

uint16_t color=tft.color565(255,255,255);


void setup(){
  tft.begin();                      // Инициализируем начало работы с графическим дисплеем
  tft.setRotation(1);               // Переводим дисплей в альбомную ориентацию
  ts.begin();
  ts.setRotation(1);
 tft.fillScreen(color);panel();setka();//начальная отрисовка зон экрана
 }
 
void loop()
{
  int x, y;                         // Переменные для работы с координатами нажатий
 
  if(ts.touched())         // Пока имеются данные с сенсорного модуля
  {
   TS_Point p = ts.getPoint();                      // Считываем с него данные
    x = map(p.x,220,3720,0,319);                  // Считываем и преобразуем координату нажатия X
    y =map(p.y,275,3830,0,240);                   // Считываем и преобразуем  координату нажатия Y
    if((x!=-1) && (y!=-1))          // Если обе координаты в положительном диапазоне (т.е. если есть нажатие) 
    {
      x += 0;                      // Корректируем координату с учетом калибровочных данных
      y += 0;                       // Корректируем координату с учетом калибровочных данных
   //   
      if(y<20&&x>290){tft.fillRect(0,21,320,240,color);setka();}//стираем рисунок
      if(y>20){x=10*(x/10);y=10*(y/10);tft.fillRect(x+1, y+2, 9, 9,color);}//рисуем пиксель выбранным цветом
      for(int i=0;i<27;i++){
      if(y<20&&x>10*i+15&&x<10*i+25){color=tft.color565(min(180*(i%3),255),min(180*((i/3)%3),255),min(180*((i/9)%3),255));tft.fillCircle(10,10,7,color);}//выбираем цвет рисования и отображаем на индикаторе выбранного цвета
        }
 
   //   
      }
  }
  delay(30);//
}
////////////////////
void setka(){
for(int j=0;j<32;j++){ 
tft.drawLine(10*j, 21, 10*j, 239,tft.color565(127,127,127));   
}
for(int j=0;j<22;j++){ 
tft.drawLine(0,21+10*j,319,21+10*j,tft.color565(127,127,127));   
}
}
////////////////////
void panel(){
 for(int i=0;i<27;i++){
 tft.fillRect(10*i+20,0,10,20,tft.color565(min(180*(i%3),255),min(180*((i/3)%3),255),min(180*((i/9)%3),255)));//строка кнопок - отрисовка   
 }
 tft.drawRect(290,0,20, 20,ILI9341_RED );//кнопка стирания рисунка 
  tft.drawLine(290,0,310, 20,ILI9341_RED );
  tft.drawLine(290,20,310, 0,ILI9341_RED );
}

Я поправил скетч с возможностью внесения поправок для калибровки тача и, ещё один косяк, непрерывность рисуемой линии, хотелось бы оторвался - новая линия начиналась с новой точки, а не с точки останова

:slight_smile:
Это не косяк, а дополнительная опция. Для прерывания серии отрезков надо повторно “макнуть” цвет. Такой подход реализует и плавные кривые для букв и “строгие” отрезки для фигур.


А чтобы стирать или рисовать толстой кистью надо клацнуть по индикатору цвета (кружок).

1 лайк

С раскрасками сейчас пробую работать.


1 лайк

и ты говорил, что не художник?
мои каракули стыдно показывать…)))
если сделать аккуратно, в коробочке то можно и внучкам подарить

Так это не рисунок, а картинка для раскрашивания :slight_smile:
Кстати раскрашка казепится. Вывод картинки происходит только если штатной функцией библиотеки пользоваться, а самописные варианты библиотека тачскрина не даёт работать. Т.е. без таскрина пожалуйста, а с ним нет.
А красивые можно выкладывать если по клеточкам рисовать как спрайты. Это вариант рисовалки из 41 поста.

Вроде заработало совместно.
VID_20240126_210017


Идея возникла создать имитацию кубиков-пазлов, чтобы можно было переворачивать, поворачивать и переставлять до получения готовой картинки по подобию таких реальных кубиков.

Хрень какая то. Пытаюсь сделать вывод не полной картинки, а фрагмента и выводится ерунда. Такое ощущение что не работает функция tft.setAddrWindow(x,y,w,h); т.к. не ограничивается окно вывода - оно всегда по ширине экрана. В библиотеку посмотрел. В файле h она объявлена как
void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
А в cpp уже так:

void Adafruit_ILI9341::setAddrWindow(uint16_t x1, uint16_t y1, uint16_t w,
                                     uint16_t h) {
  static uint16_t old_x1 = 0xffff, old_x2 = 0xffff;
  static uint16_t old_y1 = 0xffff, old_y2 = 0xffff;

  uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1);
  if (x1 != old_x1 || x2 != old_x2) {
    writeCommand(ILI9341_CASET); // Column address set
    SPI_WRITE16(x1);
    SPI_WRITE16(x2);
    old_x1 = x1;
    old_x2 = x2;
  }
  if (y1 != old_y1 || y2 != old_y2) {
    writeCommand(ILI9341_PASET); // Row address set
    SPI_WRITE16(y1);
    SPI_WRITE16(y2);
    old_y1 = y1;
    old_y2 = y2;
  }
  writeCommand(ILI9341_RAMWR); // Write to RAM
}

x,y заменены на x1,y1. Это может быть ошибка?

нет. Прототип можно вапще писать без имён переменных, только типы, например, прототип

int Add(int, int);

а в определении уже надо писать имена параметров

int Add(int x, int y)  {
  return  x+y;
};

поэтому имена в прототипе и в обьявлении могут быть какие угодно, главное, чтоб совпадали типы.

Вот же блин!!!

//вывод фотокартинок
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#include "ris_.h"//файл хранения массивов фотокартинок
#define TFT_CS     17
#define TFT_DC     20
//#define TFT_RST -1            // Пин подключения вывода RESET (ПРИ -1 СОЕДИНЯЕМ С +3,3В)
//#define TFT_MISO 16           // Пин подключения вывода дисплея SDO(MISO)
//#define TFT_MOSI 19           // Пин подключения вывода дисплея SDI(MOSI)
//#define TFT_CLK 18            // Пин подключения вывода дисплея SCK
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS,  TFT_DC);//


void setup() {
 //Serial.begin(9600);
  tft.begin();
  tft.setRotation(2);
 tft.fillScreen(tft.color565(0,0,0)); 
  }
void loop() {
drawFoto(0,0,ris_00,240,320);//смена картинок
drawFoto(0,0,ris_11,240,320);//
}
//
void drawFoto(int x,int y, const uint8_t *bitmap,int w,int h) {//функция вывода фотокартинки
if(x<0||x+w>240||y<0||y+h>320){return;} 
 tft.setAddrWindow(x,y,w,h);
 SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));
  digitalWrite(TFT_DC, HIGH);
  digitalWrite(TFT_CS, LOW); 
 for(int j=0; j<h; j++) {
    for(int i=0; i<2*w; i=i+2) {
    SPI.transfer(bitmap[i+1+j*2*w]);SPI.transfer(bitmap[i+j*2*w]);
  // SPI.transfer(pgm_read_byte(bitmap+i+1+j*2*w));SPI.transfer(pgm_read_byte(bitmap+i+j*2*w)); 
    }
  }
  digitalWrite(TFT_CS, HIGH);
  SPI.endTransaction();
 // 
}
//

Меняю в выводе картинки значения 240 и 320, но ширина окна всегда остаётся 240. Как так?
Ощущение, что по умолчанию задаёт размер всего экрана и изменить вызовом функции tft.setAddrWindow(x,y,w,h); не удаётся.

Уточню, к примеру drawFoto(0,0,ris_00,100,100); будет фрагмент шириной в 240.

А картинки какого размеру?

Картинка в полный экран. Когда делал так на st7735 картинка ожидаемо ломалась и искажалась, а тут нет, просто обрезается по высоте.

Вот тут намёк, что в последних версиях сама по себе функция не будет работать.

Сделал.


Функция сама по себе не работает. Только совместно с передачей данных по цветам пикселей. Зачем она вообще тогда в этой свежей версии библиотеки?

1 лайк

Ну да, перенос на несколько строк ниже (из 4 в 8) для её работы делает функцию “нерабочей” по сути.

//функция вывода фрагмента фотокартинки размером w*h,x,y - положение фрагмента на экране, x1,y1,w1,h1 - выбор фрагмента из массива
void drawFoto_fragment(int x,int y, const uint8_t *bitmap,int w,int h,int x1,int y1,int w1,int h1) {
if(x<0||x+w1>240||y<0||y+h1>320){return;} 
// tft.setAddrWindow(x,y,w1,h1);
 SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));
  digitalWrite(TFT_DC, HIGH);
  digitalWrite(TFT_CS, LOW);
  tft.setAddrWindow(x,y,w1,h1); 
 for(int j=0; j<h; j++) {
    for(int i=0; i<2*w; i=i+2) {
      if(i>=2*x1&&i<2*(x1+w1)&&j>=y1&&j<y1+h1){
       SPI.transfer(bitmap[i+1+j*2*w]);SPI.transfer(bitmap[i+j*2*w]);
      }  
   }
  }
  digitalWrite(TFT_CS, HIGH);
  SPI.endTransaction();
 //  
}

VID_20240127_155944

Опыты показали, что вполне работоспособен вариант и тыканья пальцем в экран. Устойчиво-удобно 12 кнопочная группа. Подумал графику задать двумя картинками - нажата кнопка, не нажата кнопка. И попробовать пульт сенсорный под танчик сделать. Но оказывается в библиотеке тачскрин нет фиксации события отпускания сенсора, а только нажатия.


VID_20240203_161333
Удалось таки программно фиксировать событие отпускания сенсора.

Опрашивать почаще, обновлять экран пореже :slightly_smiling_face: И лазер ему туда.

if(ts.touched()&&!FLAG) { // Если имеются данные с сенсорного модуля
FLAG=true;
Serial1.print("1");Serial1.print('\n');//едем
}
if(!ts.touched()&&FLAG){//если отпустили сенсор
FLAG=false;
Serial1.print("0");Serial1.print('\n');//стоим
}

Если схематично, то работает управление танком только так. Без флага всё ломается, команды проходят через 5-10 сек. после нажатия и удержания кнопки. Складывается впечатление, что “сериал” и чтение сенсора друг другу мешают.

Сериал забирает много времени МК и жутко его тормозит при частой передаче.
Имеет смесл включать только для дебага и ограничить отправку на 1 - 2 сообений в секунду.

Опрос Тача непрерывно в лупе тоже сильно тормозит МК, лучше по прерыванию.

Проблема с хардварным SPI на пятивольтовых Ардуинах скорее всего вызвана подключением - делитель или конвертер уровней + провода имеют паразитную индуктивность и емкость и начинают фильтровать высокочастотный сигнал.

С библиотекой Adafruit_ILI9341.h решается указанием частоты SPI при инициализации:
tft.begin(3000000);
С
При этом нельзя явно указывать пины MISO, MOSI и CLK- это переведет их в софтварный режим и все будет в разы медленней. Указывать нужно только эти пины:
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

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

ts.InitTouch();                   // Инициализируем сенсорный модуль дисплея
ts.setPrecision(PREC_EXTREME);        // Определяем необходимую точность обработки нажатий: PREC_LOW - низкая, PREC_MEDIUM - средняя, PREC_HI - высокая, PREC_EXTREME - максимальная
tft.begin(3000000);                      // Инициализируем начало работы с графическим дисплеем
  tft.setRotation(1);               // Переводим дисплей в альбомную ориентацию

Для работы с этим экраном рекомендую библиотеку:

Она отрисовывает на 10% быстрее адафрутовской и в целом обобщает разный функционал.
На arduino due работает с DMA скорость отрисовки космическая просто.

На RP2040 тоже вроде DMA есть…