TFT экран. Подвижный элемент на сложном фоне

Картинка в скетче представляет собой одномерный массив, где его элементами являются числа - два байта на одну точку экрана. Есть приложения-конвертеры для пк.
Например:

Вообще на точку экрана можно отводить “любое число” даже один бит. Вся эта стратегия - точечная отрисовка.

Это я реализовал еще до создания темы. Вопрос в считывании нужного фрагмента картинки. У вас в примере используется указатель на выводимый фрагмент. Могу предположить, что: 1. Картинка хранится в памяти, а не файле 2. картинка хранится фрагментами.
Я не спрашиваю готовую библиотеку. Не вижу проблем самому ее написать. Хочется красивый алгоритм. Не в виде кода, а прям словами можно.

Вы потеряли нить разговора. У меня картинка целиком в память не помещается.

рекомендую начать с простеньких графических примитивов, что бы понимать как все в памяти расположено, как можно выводить часть экрана, точки, линии, перенос изображения с места на место, как заполнять произвольную область и т д…
пример:
https://arduino.ru/forum/pesochnitsa-razdel-dlya-novichkov/st7735-160-na-128-podsvetka-barakhlit?page=10#comment-670119

1 лайк

какая картинка? фон или спрайт?
в какую память? в EEPROM?

Спасибо за пояснение. Хоть формат похожий придумал :slight_smile: В моей реализации файл состоит из строк, каждая соответствует 1 строке дисплея. Когда считываю, то загоняю в 2х мерный массив.
Сейчас решил датащит почитать. Лучше поздно, чем никогда. Может еще какие мысли появятся.

Если сможете, попробуйте функцию написать для чтения данных по пикселям из экрана…
…Ну а вообще желательно динамику графики сначала в голове представлять, что вы хотите. Есть два подхода - заготовки (фоны, спрайты,символы) хранятся на флеш мк или изображение генерируется с помощью математики и нигде как в памяти экрана не хранится, ну ещё в буфере (копия экрана в ОЗУ мк).

Это лишнее. Одномерный массив можно “разбить” на двухмерный и трёх мерный. Пример в реальности это лента из умных пикселей - можно ленту, можно сложить плоскость, можно и куб из неё сделать.

1 лайк

Это как?
И, кстати, в UNO три разных вида памяти. В какую?

А файл то откуда взялся?

Неправильно Вы знаете. Для доступа к любой позиции в файле служит функция seek.

И, опять же, откуда взялись в файлы?

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

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

Поясните, что именно Вы подразумеваете под строкой (в программировании это многозначный термин).

Почему 2-мерный?

1 лайк

Вариант

Спасибо. Попробую.

Математику обработки проще представить в голове.

Экран 2х мерный и массив 2х мерный. Как-то проще сопоставить.

То есть у Вас образ экрана целиком помещается в оперативной памяти?
Если “нет”, то аналогия неочевидна.

И, все-таки, хотелось бы видеть подробное изложение задачи конкретно, а не в общем виде (т.к. в общем виде совет один: объем оперативной памяти должен в несколько раз превышать объем информации на экране).

Только фрагмент

Ну тогда научиться и нас научить читать пиксели с экрана. Попытка по подобию:
https://www.radiokot.ru/forum/viewtopic.php?p=2818678
https://forum.mikroe.com/viewtopic.php?p=255898&sid=3624a986435e3df3171c31eb7ea7e539#p255898

Я опять боюсь кого-то задеть, но зачем “бегать в мешке”? Только если нужно освоить технику бега в мешке.
Я все про то, с чего тема началась. Есть Ардуино-совместимые контроллеры за те же 200 рублей, но с нормальным количеством памяти. RP2040 к примеру.

Если в рамках подготовки к “Концу Света”, и нужно учиться работать с 4К ОЗУ… ну тогда ОК. Но тут же все равно цепляется SD карта и файл. Весь зоопарк получается дороже RP2040 или ESP32. А памяти все равно в разы меньше и медленнее.
Я думаю, что даже в случае “зомби-апокалипсиса” еще останутся запасы 32 битных контроллеров… и даже большие, чем 8 битных.

1 лайк

Если заговорили о “Конце Света”, то вспомним о начале компьютерной эпохи. Важно не количество ресурсов : памяти и мощи процессора, а умение их оперативно распределять. Так для рисования графических картинок требуется много ресурсов, а когда рисовать не надо, то эти ресурсы не к чему. Вот и происходят рывки в работе устройств, которые видны визуально. Так что говоря о графике надо или говорить о создании отдельного графического ядра или программирование с фоновой работой.

Да, тема очень интересная и далеко не всё понятно. RP2040 как раз подходит, по цене более чем выгодно :slight_smile:
ТС, а в каком расширении хранятся файлы графики?

Не, там чёрт ногу сломит, как всё сложно. Вот функция чтения пикселя и как это применить к UNO - хрен знает :).

/* Читает в буфер data данные о цвете пикселей из окна дисплея с координатами левого верхнего угла (x, y), шириной w, высотой h.
* Доступны 2 режима работы со spi: полный дуплекс (full-duplex) и полудуплекс (half-duplex).
* Полный дуплекс применяется на дисплеях, подключаемых к МК по двум однонаправленным линиям данных: MOSI и MISO.
* Полудуплекс применяется для чтения данных с контроллеров дисплеев, содержащих только один вывод SDA, совмещающий линии
* in/out. Вывод CS в режиме полудуплекса СТРОГО ОБЯЗАТЕЛЕН, т.к. только после установки в высокий уровень этого сигнала
* контроллер дисплея выйдет из режима передачи данных MCU и будет готов к приему новых команд от MCU. При отсутствии
* вывода CS возврат в режим приема команд контроллером дисплея может быть осуществлен только после сброса (reset)
* контроллера дисплея, который в данном драйвере/библиотеке выполняется командой инициализации LCD_Init(lcd),
* где lcd - указатель на обработчик дисплея.
* Управление режимом работы процедуры осуществляется параметром SPI_HALF_DUPLEX_READ в файле display.h.
* Для включения полудуплекса:
* #define SPI_HALF_DUPLEX_READ	1
* Для включения полного дуплекса:
* #define SPI_HALF_DUPLEX_READ	0
* Скорость чтения, как правило, ниже (иногда значительно) скорости записи данных в контроллер поэтому введен параметр,
* определяющий скорость чтения из контроллера дисплея SPI_SPEED_DISPLAY_READ. Принимает значение от 0 до 7 и настраивается
* в display.h. Причем, 0 соответствует clk/2, 1 - clk/4, ... 7 - clk/256. Где clk - частота тактирования spi.
*/
void LCD_ReadImage(LCD_Handler* lcd, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *data)
{
	uint16_t x1 = x + w - 1, y1 = y + h - 1; //Определяем координаты правого нижнего угла окна
	//Проверяем параметры окна, т.к. за пределами размерности дисплея не работаем
	if (x >= lcd->Width || y >= lcd->Height || x1 >= lcd->Width || y1 >= lcd->Height) return;
	//Пересчет координат окна в зависимости от характеристик матрицы дисплея и контроллера.
	//Соответствующий offs в драйвере дисплея определяет эти характеристики, учитывая разницу размерностей,
	//начальное смещение матрицы дисплея относительно поля памяти контроллера дисплея и ориентацию изображения на матрице
	x += lcd->x_offs;
	y += lcd->y_offs;
	x1 += lcd->x_offs;
	y1 += lcd->y_offs;
	//Создаем управляющую строку для интерпретатора драйвера дисплея, которая укажет контроллеру дисплея,
	//что мы определяем область памяти и хотим прочитать эту область. Предварительно переведем дисплей в
	//режим цвета 18 бит, так как команда Memory Read (0x2E) должна работать только с режимом цвета 18 бит.
	uint8_t	set_win_and_read[] = { //Команда выбора режима цвета
								   LCD_UPR_COMMAND, 0x3A, 1, 0x66, //0x66 - 18-битный цвет, 0x55 - 16-битный
								   //Установка адресов, определяющих блок:
								   LCD_UPR_COMMAND, 0x2A, 4, 0, 0, 0, 0, //столбец
								   LCD_UPR_COMMAND, 0x2B, 4, 0, 0, 0, 0, //строка
								   //Команда Memory Read (читать память)
								   LCD_UPR_COMMAND, 0x2E, 0,
								   LCD_UPR_END	};
	//Вписываем в управляющую строку координаты заданного окна
	set_win_and_read[7]  = x >> 8;  set_win_and_read[8]  = x & 0xFF;
	set_win_and_read[9]  = x1 >> 8; set_win_and_read[10]  = x1 & 0xFF;
	set_win_and_read[14] = y >> 8;  set_win_and_read[15] = y & 0xFF;
	set_win_and_read[16] = y1 >> 8; set_win_and_read[17] = y1 & 0xFF;
	//Ждем, когда дисплей освободится и будет готов к приему новых команд и данных
	while (LCD_GetState(lcd) != LCD_STATE_READY) { __NOP(); }
	lcd->cs_control = 1; //Отключаем управление линией CS со стороны интерпретатора управляющих строк
	SPI_TypeDef *spi = lcd->spi_data.spi;
	uint32_t spi_param = spi->CR1; //Запоминаем параметры spi
	//Настраиваем spi (spi у нас уже выключен)
	//настройки полный дуплекс
	spi->CR1 &= ~(SPI_CR1_BIDIMODE |  //2 линии, однонаправленный режим
				  SPI_CR1_RXONLY   |  //прием и передача
				  SPI_CR1_CRCEN    |
				  SPI_CR1_BR_Msk   |  //Маска скорости spi
				  SPI_CR1_DFF); 	  //Ширина кадра 8 бит
	//Установим скорость spi для чтения дисплея.
	//Параметр SPI_SPEED_DISPLAY_READ настраивается в display.h
	//Дело в том, что согласно спецификаций на контроллеры дисплея, скорость
	//в режиме чтения из контроллера, как правило, ниже скорости в режиме записи данных
	//в контроллер.
	spi->CR1 |= (uint32_t)((SPI_SPEED_DISPLAY_READ & 7) << 3);
	//Отправляем через интерпретатор управляющую строку на контроллер дисплея
	//"Дергать" выводом CS после отправки команды 0x2E нельзя, т.к. контроллер
	//дисплея может "подумать", что мы хотим прерывать чтение.
	LCD_CS_LOW //Подключаем контроллер дисплея к МК
	LCD_String_Interpretator(lcd, set_win_and_read);
	LCD_DC_HI //Вывод DC контроллера дисплея установим в положении "данные". Но, мои эксперименты показывают,
	//что чтение работает и в положении "команда", что странно, т.к. согласно спецификации, первая команда, в т.ч.,
	//NOP должна прерывать операцию чтения памяти контроллера. В общем, чтение идет до тех пор, пока мы не прочитаем
	//всю выбранную нами область, либо пока тупо не прервем процесс чтения.
	uint32_t len = w * h; //Количество пикселей для чтения
	uint16_t *data_ptr = data; //Указатель на местоположение буфера для хранения пикселей
	uint8_t r, g, b; //Переменные с цветовыми составляющими
#if (SPI_HALF_DUPLEX_READ == 1) //Если используется полудуплекс, то скорректируем настройки spi
	//Настройки для полудуплекса (только прием):
	spi->CR1 |= SPI_CR1_BIDIMODE; //Двунаправленная линия данных
	spi->CR1 &= ~SPI_CR1_BIDIOE;  //Режим приема
	spi->CR1 ^= SPI_CR1_CPHA_Msk; //Согласно спецификации при приеме меняем фазу на противоположную
#endif
	//Включаем spi. Для полудуплексного режима прием стартует сразу же, как только включим spi
	spi->CR1 |= SPI_CR1_SPE;
	//16 холостых тактов для подготовки контроллера дисплея к отправке данных MCU
	int i = 2;
	while (i--) {
#if (SPI_HALF_DUPLEX_READ == 0) //В полудуплексе при приеме заливать данные в DR для старта тактирования не надо
		LL_SPI_TransmitData8(spi, 0x00); //NOP
#endif
		while (!(spi->SR & SPI_SR_RXNE)) { __NOP(); } //Ожидаем прием ответа от контроллера дисплея
		r = LL_SPI_ReceiveData8(spi);
	}
	//------------------------------ Читаем данные о цвете len пикселей --------------------------
	while (len--) {
		//Считываем последовательно цветовые составляющие
		//По спецификации последовательность считываемых составляющих цветов заявлена r, g, b,
		//Если считываемые цвета будут не соответствовать фактическим, то снизьте скорость spi для чтения,
		//но иногда стабильности чтения помогает подтяжка к питанию линии MISO spi.
#if (SPI_HALF_DUPLEX_READ == 0)
		LL_SPI_TransmitData8(spi, 0x00); //NOP
#endif
		while (!(spi->SR & SPI_SR_RXNE)) { __NOP(); }//Ожидаем прием ответа от контроллера дисплея
		r = LL_SPI_ReceiveData8(spi);
#if (SPI_HALF_DUPLEX_READ == 0)
		LL_SPI_TransmitData8(spi, 0x00); //NOP
#endif
		while (!(spi->SR & SPI_SR_RXNE)) { __NOP(); }
		g = LL_SPI_ReceiveData8(spi);
#if (SPI_HALF_DUPLEX_READ == 0)
		LL_SPI_TransmitData8(spi, 0x00); //NOP
#endif
		while (!(spi->SR & SPI_SR_RXNE)) { __NOP(); }
		b = LL_SPI_ReceiveData8(spi);
		*data_ptr++ = LCD_Color(lcd, r, g, b); //Преобразуем цвет из R8G8B8 в R5G6B5 и запоминаем его
	}
	LCD_CS_HI //Отключаем контроллер дисплея от МК.
	  	  	  //В режиме полудуплекса, согласно спецификации, это будет еще и сигналом для
	  	  	  //переключения направления линии SDA на прием информации от MCU. Так что, без
	  	  	  //линии CS на дисплее НЕ ОБОЙТИСЬ.
#if (SPI_HALF_DUPLEX_READ == 0)
	while (spi->SR & SPI_SR_BSY) { __NOP(); } //Ждем когда spi освободится
											  //А в полудуплексе ждать не надо (см. спецификацию MCU),
											  //но надо дочитывать...
#endif
	spi->CR1 &= ~SPI_CR1_SPE; //spi выключаем
#if (SPI_HALF_DUPLEX_READ == 1)
	//Обязательное дочитывание после выключения spi для полудуплексного режима, иначе будет "не гуд"
	while (!(spi->SR & SPI_SR_RXNE)) { __NOP(); }
#endif
	spi->CR1 = spi_param; //Восстанавливаем параметры spi
	//Восстанавливаем 16-битный режим цвета
	lcd->cs_control = 0; //Включаем управление линией CS со стороны интерпретатора управляющих строк
	uint8_t	color_restore[]  = { LCD_UPR_COMMAND, 0x3A, 1, 0x55,
								 LCD_UPR_END };
	LCD_String_Interpretator(lcd, color_restore);
}

Народ, просто уточнение - у автора не Уно, а ЕСП8266
Сообщение №30

@Valery22
Не считайте зазорным отвечать на вопросы, даже если вам их задают по два-три раза. Люди в ветку приходят и уходят, по мере того как сообщений все больше - многие теряют нить разговора.

Уточните, то у вас за файл - в файловой системе, образованной во флеше ЕСП8266?

Сразу споткнулся о строку 33.
Можно как-то аргументировать это высказывание фрагментом дэйташита?

Наверное можно. Я спросил у автора как читать данные пикселей из ILI9341 на UNO с использованием SPI.h. Он мне ответил - Не пользуюсь Ардуино и готовыми библиотеками для работы с дисплеями…на моём гитхабе есть демка чтения gram памяти.

:slight_smile:
Короче послал туда откуда я к нему пришёл.