начну писать экспериментальный скетч под ili9341 240*320 TFT
не взлетел у меня дисплей с резисторными делителями на Arduino Nano :( или я его загубил.
лог анализатором данные на SPI шине вижу, но не работает.
жду прихода дисплея 128*160 и конвертора уровней, потом продолжу эксперименты.
Я попробовал приём "четыре пикселя в одном". Когда картинка размером 80*64 выдаётся за 160*128. Для сравнения:
Мне кажется в анимации очень бы помогло, увеличив в 4 раза количество хранимых картинок. Тогда можно и мультики записывать.
Пробовал вариант в своих раскрасках.
// РИСУНОК контур 4 цвета (умноженный на 4 пиксель)
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#define TFT_CS 10
#define TFT_RST 9
#define TFT_DC 8
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
extern const unsigned char ris_1[];
void setup(void) {
// Используйте этот инициализатор, если вы используете 1,8-дюймовый TFT
tft.initR(INITR_BLACKTAB); // initialize a ST7735S chip, black tab
tft.fillScreen(ST7735_BLACK);
tft.setRotation(0);//ориентация экрана
bi_drawBitmap_(0,0,ris_1,64,80,COL1,COL2,COL3,COL4);
}
void loop() {
//delay(500);
}
/////////////////////////////////////////////////////////////////////
void bi_drawBitmap_(int x,int y, const uint8_t *bitmap,int w,int h,const uint16_t (*COLor_1)(int &I,int &J),const uint16_t (*COLor_2)(int &I,int &J),const uint16_t (*COLor_3)(int &I,int &J),const uint16_t (*COLor_4)(int &I,int &J)) {
bool flag=true;//
if(x<0||x+2*w>128||y<0||y+2*h>160){return;}
tft.setAddrWindow(x,y,x+2*w-1,y+2*h-1);
int byteWidth = (w + 7) / 8;
uint8_t byte_;uint8_t byte_st;
//
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
digitalWrite(TFT_DC, HIGH);
digitalWrite(TFT_CS, LOW);
for(int j=0; j<h; j++) {
//
for( int i=0; i<w; i++) {
if(i & 7) {byte_ <<= 1;byte_st <<= 1;}
else { byte_ = pgm_read_byte(bitmap + j * byteWidth + i / 8);if(j>0){byte_st = pgm_read_byte(bitmap + (j-1) * byteWidth + i / 8);}}
if(byte_ & 0x80)
{
if(flag||!(byte_st & 0x80)){SPI.transfer(COLor_3(i,j)>>8);SPI.transfer(COLor_3(i,j));SPI.transfer(COLor_3(i,j)>>8);SPI.transfer(COLor_3(i,j));}//цвет контура левого верхнего плана
else { SPI.transfer(COLor_1(i,j)>>8);SPI.transfer(COLor_1(i,j));SPI.transfer(COLor_1(i,j)>>8);SPI.transfer(COLor_1(i,j));}// цвет раскраски внутри контура
flag=false;
}
else {
if(!flag||byte_st & 0x80){SPI.transfer(COLor_4(i,j)>>8);SPI.transfer(COLor_4(i,j));SPI.transfer(COLor_4(i,j)>>8);SPI.transfer(COLor_4(i,j));}//цвет контура правого нижнего плана
else {SPI.transfer(COLor_2(i,j)>>8);SPI.transfer(COLor_2(i,j));SPI.transfer(COLor_2(i,j)>>8);SPI.transfer(COLor_2(i,j));} // цвет фона
flag=true;
}
}
//
//
for( int i=0; i<w; i++) {
if(i & 7) {byte_ <<= 1;byte_st <<= 1;}
else { byte_ = pgm_read_byte(bitmap + j * byteWidth + i / 8);if(j>0){byte_st = pgm_read_byte(bitmap + (j-1) * byteWidth + i / 8);}}
if(byte_ & 0x80)
{
if(flag||!(byte_st & 0x80)){SPI.transfer(COLor_3(i,j)>>8);SPI.transfer(COLor_3(i,j));SPI.transfer(COLor_3(i,j)>>8);SPI.transfer(COLor_3(i,j));}//цвет контура левого верхнего плана
else { SPI.transfer(COLor_1(i,j)>>8);SPI.transfer(COLor_1(i,j));SPI.transfer(COLor_1(i,j)>>8);SPI.transfer(COLor_1(i,j));}// цвет раскраски внутри контура
flag=false;
}
else {
if(!flag||byte_st & 0x80){SPI.transfer(COLor_4(i,j)>>8);SPI.transfer(COLor_4(i,j));SPI.transfer(COLor_4(i,j)>>8);SPI.transfer(COLor_4(i,j));}//цвет контура правого нижнего плана
else {SPI.transfer(COLor_2(i,j)>>8);SPI.transfer(COLor_2(i,j));SPI.transfer(COLor_2(i,j)>>8);SPI.transfer(COLor_2(i,j));} // цвет фона
flag=true;
}
}
//
}
digitalWrite(TFT_CS, HIGH);
SPI.endTransaction();
///
}
/////////////////////////////////////////////////////////////////////
//варианты функций - раскрасок
const uint16_t COL1(int &i,int &j){// 1 позиция в аргументах - цвет раскраски изображения внутри контура
return tft.Color565(255-j/1,190-2*j,0);
}
const uint16_t COL2(int &i,int &j){// 2 позиция в аргументах - цвет фона
return tft.Color565(225-i/1,160-2*i,0);
}
const uint16_t COL3(int &i,int &j){// 3 позиция в аргументах - цвет контура левого верхнего плана
return tft.Color565(200,200,100);
}
const uint16_t COL4(int &i,int &j){// 4 позиция в аргументах - цвет контура правого нижнего плана
return tft.Color565(75,75,75);
}
/////////////////////////////////////////////////////////////////////
В функции конечно чёрт ногу сломит, но идея каждый пиксель в строке повторять 2 раза подряд, а затем повторять строку второй раз. Я к тому чтоб попробовать реализовать идею масштабирования в ассемблерной вставке если есть желание и возможности.
ну теперь возможности появятся в Ноябре, как платки придут. Если я правильно посчитал, дисплей 128*160 это 40960 байт на всю картинку, при частоте SPI 8МГц получается максимальная теоретическая скорость отрисовки полного экрана 24 кадра в секунду, в реальности одноцветное заполнение будет максимум 20 кадров (попробую).
Думаю хранить фоновую картинку в Progmem в 16 цветной палитре, выведу ее фоном.
Потом уже баловаться вывод спрайтов (например вращающихся кубиков) без затирания фона, используя промежуточный буфер.
Вывод движущейся одноцветной картинки на весь экран мне как то не очень интересен (нет в этой задачи драйва).
Думаю хранить фоновую картинку в Progmem в 16 цветной палитре, выведу ее фоном.
Потом уже баловаться вывод спрайтов (например вращающихся кубиков) без затирания фона, используя промежуточный буфер.
Вывод движущейся одноцветной картинки на весь экран мне как то не очень интересен (нет в этой задачи драйва).
16 цветов это 10Кб на полноэкранную картинку-фон? А конвертер картинок?
фотожоп называется :)
А потом тупо в bitmap и из него в код Си (тут где то в Песочнице были недавно ссылки). В инете есть, если не найдете - у себя покапаюсь.
соответственно в Progmem пару фоновых картинок влезет легко.
И как выбирать цвет из этих трех картинок? В общей же сумме число бит на всю многоцветную картрнку одинаково? Не пойму логику....
Функция читает бит (пикселя) по местоположению в массиве из каждой картинки. Если, например, комбинация 1,1,0 то пусть будет жёлтый и т.д.
Готовить её тоже просто: берём картинку ч\б (хотя для удобства стирания можно и цветную.), копируем 3 раза и в каждой копии затираем стирашкой лишнее. Т.е. элемент оставленный только на первой и второй картинке будет жёлтый (или какой захотим).
lilik, все зависит от задачи, соответственно и решение задачи разное, я просто побаловаться, выжать максимум из МК, а что конкретно вы хотите нарисовать)?
Картинки "иконкаподобные" хранить в большом количестве и разукрашивать различными способами на экране используя ресурсы только Ардуино . Было бы здорово ещё их анимировать, но тут упирается всё в скорость. Ваша функция самая быстрая и анимация возможна, но без раскрашек.
А с самого начала интересовали игры на этом экранчике. Но из интересных побаловался только про птичку:
TFT SPI ili9341 240*320
Заполнение всего экрана случайным цветом, получился грустный результат 3 кадра в секунду, из теоретически возможных 6.5 кадров :(
Скетч использует 3922 байт (12%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1509 байт (73%) динамической памяти, оставляя 539 байт для локальных переменных. Максимум: 2048 байт.
Count fillscreen per sec = 3
#include "tft9341.h"
void setup() {
Serial.begin(9600);
// put your setup code here, to run once:
tftInit(240, 320, ROTATE_0);
// test fill screen
unsigned long starttime = millis();
unsigned char countpics = 0;
while ((millis() - starttime) <= 10000UL) {
tftFillScreen(rand());
++countpics;
}
Serial.print("Count fillscreen per sec = ");
Serial.println(countpics / 10);
}
void loop() {
// put your main code here, to run repeatedly:
}
#ifndef TFT9341_H_
#define TFT9341_H_
//-------------------------------------------------------------------
#define TFT9341_MADCTL_MY 0x80
#define TFT9341_MADCTL_MX 0x40
#define TFT9341_MADCTL_MV 0x20
#define TFT9341_MADCTL_ML 0x10
#define TFT9341_MADCTL_RGB 0x00
#define TFT9341_MADCTL_BGR 0x08
#define TFT9341_MADCTL_MH 0x04
#define TFT9341_ROTATION (TFT9341_MADCTL_MX | TFT9341_MADCTL_BGR)
//-------------------------------------------------------------------
#define swap(a,b) {int16_t t=a;a=b;b=t;}
//-------------------------------------------------------------------
#define TFT9341_BLACK 0x0000
#define TFT9341_BLUE 0x001F
#define TFT9341_RED 0xF800
#define TFT9341_GREEN 0x07E0
#define TFT9341_CYAN 0x07FF
#define TFT9341_MAGENTA 0xF81F
#define TFT9341_YELLOW 0xFFE0
#define TFT9341_WHITE 0xFFFF
//-------------------------------------------------------------------
#define tft_cs_pin 10
#define tft_dc_pin 9
//#define tft_rst_pin 7
#define size_buff_send_tft 1280
enum TFT9341_ROTATE_t {ROTATE_0, ROTATE_90, ROTATE_180, ROTATE_270};
void tftInit(unsigned short w_size, unsigned short h_size, enum TFT9341_ROTATE_t locTFT9341_ROTATE);
void tftFillRect(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, unsigned short color);
void tftFillScreen(unsigned short color);
#endif /* TFT9341_H_ */
#include "tft9341.h"
#include <arduino.h>
#include "SPI.h"
unsigned short tftWIDTH;
unsigned short tftHEIGHT;
unsigned char tftBuffSend[size_buff_send_tft];
void tftSetPinCS(unsigned char AstatePin) {
digitalWrite(tft_cs_pin, AstatePin);
}
void tftSetPinDC(unsigned char AstatePin) {
digitalWrite(tft_dc_pin, AstatePin);
}
void tftSendCommand(unsigned char cmd) {
tftSetPinDC(0);
tftSetPinCS(0); // говорим slave устройству что начинаем работать
SPI.transfer(cmd);
tftSetPinCS(1); // end transfer
}
void tftWriteData(unsigned char * buff, unsigned short buff_size) {
tftSetPinDC(1);
tftSetPinCS(0); // говорим slave устройству что начинаем работать
for (unsigned short i = 0; i < buff_size; ++i) {
SPI.transfer(buff[i]);
}
tftSetPinCS(1); // end transfer
}
void tftSetAddrWindow(unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1) {
static unsigned char pos_data[4];
// column address set
tftSendCommand(0x2A); // CASET
pos_data[0] = (x0 >> 8) & 0xFF;
pos_data[1] = x0 & 0xFF;
pos_data[2] = (x1 >> 8) & 0xFF;
pos_data[3] = x1 & 0xFF;
tftWriteData((unsigned char *)&pos_data, 4);
// row address set
tftSendCommand(0x2B); // RASET
pos_data[0] = (y0 >> 8) & 0xFF;
pos_data[1] = y0 & 0xFF;
pos_data[2] = (y1 >> 8) & 0xFF;
pos_data[3] = y1 & 0xFF;
tftWriteData((unsigned char *)&pos_data, 4);
// write to RAM
tftSendCommand(0x2C); // RAMWR
}
void tftFillRect(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, unsigned short color) {
if ((x1 >= tftWIDTH) || (y1 >= tftHEIGHT) || (x2 >= tftWIDTH) || (y2 >= tftHEIGHT)) return;
if ((x1 >= tftWIDTH) || (y1 >= tftHEIGHT) || (x2 >= tftWIDTH) || (y2 >= tftHEIGHT)) return;
if (x1 > x2) swap(x1, x2);
if (y1 > y2) swap(y1, y2);
unsigned short rowsCount = x2 - x1 + 1; // число столбцов
unsigned short countBytesPerLine = rowsCount * 2; // число байт в одной строке
unsigned short linesCount = y2 - y1 + 1; // число строк
unsigned short countLinesInOneBuff = size_buff_send_tft / countBytesPerLine; // сколько целиковых строк влезет в буфер
if (countLinesInOneBuff >= linesCount) { // весь квадрат влазит в буфер
unsigned short countFillBytes = countBytesPerLine * linesCount; // сколько байт надо отправить дисплею
tftSetAddrWindow(x1, y1, x2, y2);
for (unsigned short i = 0; i < countFillBytes; ++i) {
tftBuffSend[i] = color >> 8;
++i;
tftBuffSend[i] = color;
}
tftWriteData((unsigned char *)&tftBuffSend, countFillBytes);
} else {
unsigned short countFillBytes = countBytesPerLine * countLinesInOneBuff; // сколько байт надо отправить дисплею
for (unsigned short i = 0; i < countFillBytes; ++i) {
tftBuffSend[i] = color >> 8;
++i;
tftBuffSend[i] = color;
}
unsigned short countFullBuffs = linesCount / countLinesInOneBuff; // число блоков заполненных
for (unsigned short j = 0; j < countFullBuffs; ++j) {
tftSetAddrWindow(x1, y1 + j * countLinesInOneBuff, x2, y1 + j * countLinesInOneBuff + countLinesInOneBuff - 1);
tftWriteData((unsigned char *)&tftBuffSend, countFillBytes);
}
unsigned short linesFullBuffs = linesCount % countLinesInOneBuff; // сколько осталось заполнить строк
if (linesFullBuffs) {
tftSetAddrWindow(x1, y1 + countFullBuffs * countLinesInOneBuff, x2, y1 + countFullBuffs * countLinesInOneBuff + linesFullBuffs);
tftWriteData((unsigned char *)&tftBuffSend, linesFullBuffs * countBytesPerLine);
}
}
}
void tftInit(unsigned short w_size, unsigned short h_size, enum TFT9341_ROTATE_t locTFT9341_ROTATE) {
pinMode(11, OUTPUT);
pinMode(13, OUTPUT);
digitalWrite(11, HIGH);
digitalWrite(13, HIGH);
pinMode(tft_cs_pin, OUTPUT);
tftSetPinCS(1); // no transfer
pinMode(tft_dc_pin, OUTPUT);
SPI.begin();
SPI.setBitOrder(MSBFIRST); // MSB to be sent first
SPI.setDataMode(SPI_MODE0); // Set for clock rising edge
SPI.setClockDivider(SPI_CLOCK_DIV2);
/*digitalWrite(tft_rst_pin, LOW);
pinMode(tft_rst_pin, OUTPUT);
delay(5UL);
pinMode(tft_rst_pin, INPUT);*/
// ---
unsigned char _locDataSend[16];
tftSendCommand(0x01); //Software Reset
delay(1000UL); // delay 1 sec //Software Reset
//Power Control A
_locDataSend[0] = 0x39;
_locDataSend[1] = 0x2C;
_locDataSend[2] = 0x00;
_locDataSend[3] = 0x34;
_locDataSend[4] = 0x02;
tftSendCommand(0xCB);
tftWriteData((unsigned char *)_locDataSend, 5);
//Power Control B
_locDataSend[0] = 0x00;
_locDataSend[1] = 0xC1;
_locDataSend[2] = 0x30;
tftSendCommand(0xCF);
tftWriteData(_locDataSend, 3);
//Driver timing control A
_locDataSend[0] = 0x85;
_locDataSend[1] = 0x00;
_locDataSend[2] = 0x78;
tftSendCommand(0xE8);
tftWriteData(_locDataSend, 3);
//Driver timing control B
_locDataSend[0] = 0x00;
_locDataSend[1] = 0x00;
tftSendCommand(0xEA);
tftWriteData(_locDataSend, 2);
//Power on Sequence control
_locDataSend[0] = 0x64;
_locDataSend[1] = 0x03;
_locDataSend[2] = 0x12;
_locDataSend[3] = 0x81;
tftSendCommand(0xED);
tftWriteData(_locDataSend, 4);
//Pump ratio control
_locDataSend[0] = 0x20;
tftSendCommand(0xF7);
tftWriteData(_locDataSend, 1);
//Power Control,VRH[5:0]
_locDataSend[0] = 0x10;
tftSendCommand(0xC0);
tftWriteData(_locDataSend, 1);
//Power Control,SAP[2:0];BT[3:0]
_locDataSend[0] = 0x10;
tftSendCommand(0xC1);
tftWriteData(_locDataSend, 1);
//VCOM Control 1
_locDataSend[0] = 0x3E;
_locDataSend[1] = 0x28;
tftSendCommand(0xC5);
tftWriteData(_locDataSend, 2);
//VCOM Control 2
_locDataSend[0] = 0x86;
tftSendCommand(0xC7);
tftWriteData(_locDataSend, 1);
//Memory Acsess Control
switch (locTFT9341_ROTATE) {
case ROTATE_0: {
_locDataSend[0] = 0x58;
break;
}
case ROTATE_90: {
_locDataSend[0] = 0x28;
break;
}
case ROTATE_180: {
_locDataSend[0] = 0x88;
break;
}
case ROTATE_270: {
_locDataSend[0] = 0xE8;
break;
}
}
//_locDataSend[0] = 0x48;
tftSendCommand(0x36);
tftWriteData(_locDataSend, 1);
//Pixel Format Set
_locDataSend[0] = 0x55;//16bit
tftSendCommand(0x3A);
tftWriteData(_locDataSend, 1);
//Frame Rratio Control, Standard RGB Color
_locDataSend[0] = 0x00;
_locDataSend[1] = 0x18;
tftSendCommand(0xB1);
tftWriteData(_locDataSend, 2);
//Display Function Control
_locDataSend[0] = 0x08;
_locDataSend[1] = 0x82;
_locDataSend[2] = 0x27;//320 строк
tftSendCommand(0xB6);
tftWriteData(_locDataSend, 3);
//Enable 3G (пока не знаю что это за режим)
_locDataSend[0] = 0x00;//не включаем
tftSendCommand(0xF2);
tftWriteData(_locDataSend, 1);
//Gamma set
_locDataSend[0] = 0x01;//Gamma Curve (G2.2) (Кривая цветовой гаммы)
tftSendCommand(0x26);
tftWriteData(_locDataSend, 1);
//Positive Gamma Correction
_locDataSend[0] = 0x0F;
_locDataSend[1] = 0x31;
_locDataSend[2] = 0x2B;
_locDataSend[3] = 0x0C;
_locDataSend[4] = 0x0E;
_locDataSend[5] = 0x08;
_locDataSend[6] = 0x4E;
_locDataSend[7] = 0xF1;
_locDataSend[8] = 0x37;
_locDataSend[9] = 0x07;
_locDataSend[10] = 0x10;
_locDataSend[11] = 0x03;
_locDataSend[12] = 0x0E;
_locDataSend[13] = 0x09;
_locDataSend[14] = 0x00;
tftSendCommand(0xE0);
tftWriteData(_locDataSend, 15);
//Negative Gamma Correction
_locDataSend[0] = 0x00;
_locDataSend[1] = 0x0E;
_locDataSend[2] = 0x14;
_locDataSend[3] = 0x03;
_locDataSend[4] = 0x11;
_locDataSend[5] = 0x07;
_locDataSend[6] = 0x31;
_locDataSend[7] = 0xC1;
_locDataSend[8] = 0x48;
_locDataSend[9] = 0x08;
_locDataSend[10] = 0x0F;
_locDataSend[11] = 0x0C;
_locDataSend[12] = 0x31;
_locDataSend[13] = 0x36;
_locDataSend[14] = 0x0F;
tftSendCommand(0xE1);
tftWriteData(_locDataSend, 15);
tftSendCommand(0x11);//Выйдем из спящего режима
delay(120UL); // delay 120 msec
//Display ON
_locDataSend[0] = TFT9341_ROTATION;
tftSendCommand(0x29);
tftWriteData(_locDataSend, 1);
switch (locTFT9341_ROTATE) {
case ROTATE_0: {
tftWIDTH = w_size;
tftHEIGHT = h_size;
break;
}
case ROTATE_90: {
tftWIDTH = h_size;
tftHEIGHT = w_size;
break;
}
case ROTATE_180: {
tftWIDTH = w_size;
tftHEIGHT = h_size;
break;
}
case ROTATE_270: {
tftWIDTH = h_size;
tftHEIGHT = w_size;
break;
}
}
}
void tftFillScreen(unsigned short color) {
tftFillRect(0, 0, tftWIDTH - 1, tftHEIGHT - 1, color);
}
P.S. Вывод одной точки это вызов spi.transfer из массива памяти.
P.P.S. И я еще пока в начале пути, есть места куда оптимизироваться, в том числе выкинуть цикл и использовать spi_write, так что не все потеряно :)
P.P.P.S. Но конечно как выше писали коллеги, этот МК явно не для вывода полноцветных изображений.
andycat в листинг гляньте - сколько ваш код породил CALL RET для вывода одной точки ... ужаснетесь
А по аналогии как для st7735 на ассемблере нельзя написать вывод массива из прогмем? Для анимации функция хорошая получилась. Выше я высказывал идею о мультиках с картинками 0,25 бита на пиксель.
выкинул всякие буферы промежуточные, пишу сразу в регистры, код взял из SPI.h
пофиг, все равно три кадра в секунду.
пробовал не использовать ожидание готовности шины
while (!(SPSR & _BV(SPIF))) ;
просто nop задержки - плохо - идут артефакты, дисплей не воспринимает корректно данные.
void tftFillRect(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, unsigned short color) {
if ((x1 >= tftWIDTH) || (y1 >= tftHEIGHT) || (x2 >= tftWIDTH) || (y2 >= tftHEIGHT)) return;
if ((x1 >= tftWIDTH) || (y1 >= tftHEIGHT) || (x2 >= tftWIDTH) || (y2 >= tftHEIGHT)) return;
if (x1 > x2) swap(x1, x2);
if (y1 > y2) swap(y1, y2);
unsigned long drawPixels = (unsigned long)((unsigned long)(x2 - x1 + 1) * (unsigned long)(y2 - y1 + 1));
tftSetAddrWindow(x1, y1, x2, y2);
tftSetPinDC(1);
tftSetPinCS(0); // говорим slave устройству что начинаем работать
union {
uint16_t val;
struct {
uint8_t lsb;
uint8_t msb;
};
} in;
in.val = color;
while (drawPixels--) {
SPDR = in.msb;
asm volatile("nop"); // See transfer(uint8_t) function
while (!(SPSR & _BV(SPIF))) ;
SPDR = in.lsb;
asm volatile("nop");
while (!(SPSR & _BV(SPIF))) ;
}
tftSetPinCS(1); // end transfer
}
Попробовал я в фотошопе преобразовать полноцветную картинку в нужное количество бит/цветов на пиксель, потом просто сохраняется в raw формате + там же есть пункт сохранения палитры.
Проблема дальше вылезла: он все равно на каждый пиксель записывает 1 байт, соотвественно, например в моем случае 4х цветная картинка размером 240*320 получается 76800 байт что никак не влезет в Progmem, online конвертеров не нашел, сейчас Delphi поставлю, буду преобразователь писать.
Программа преобразования готова, отображение допишу/проверю - выложу. Палитру пока руками из rgb888 to rgb565 делать, потом дорисую.
По поводу картинок, крупные пиксели портят весь вид, лучше детализацию увеличить, цветов уменьшить. В во всяком случае на компе это круче смотрится.
В 16 битном цвете 128 на 160 влезет 3 картинки.
Учитывая ваши мультяшные картинки, то 4х цветных более чем хватит, соответственно и картинок больше влезет.
+замена палитры при одинаковых картинках дают интересные эффекты, один и тот же например цветок можно раскрасить во множестве вариантов.
И да, палитра так же в progmem живёт, но там копеечное место занимает.
итак результат что получилось, первая картинка 240*320 4 цвета, вторая 120*160 16 цветов в прямой и инверсной палитре, 128*160 16 цветов 10 кадров в секунду вполне нормально для простенькой игрушки, особенно если не весь экран занимать, и смотрится очень не плохо.
Compiling 'i9341a13' for 'ATmega328P (Arduino Nano)'
Program size: 22 982 bytes (used 75% of a 30 720 byte maximum) (3,97 secs)
Minimum Memory Usage: 247 bytes (12% of a 2048 byte maximum)
fillscreen time = 266
image 240*320 4 colors time = 438
Compiling 'i9341a13' for 'ATmega328P (Arduino Nano)'
Program size: 13 612 bytes (used 44% of a 30 720 byte maximum) (3,88 secs)
Minimum Memory Usage: 247 bytes (12% of a 2048 byte maximum)
fillscreen time = 266
image 120*160 16 colors time = 91
таким методом можно штук 50 персонажей игр, например размером 32*32 сохранить разной формы/направления движения, птички/кубики и прочее на что фантазии хватит, потом в зависимости от игровой ситуации выводить их в разных местах с разными палитрами.
Windows 10 x64
Пробовал просто exe файл перетащить на другой комп - все норм запускается, но само преобразование не пробовал, завтра протестирую.
А как вы определили что висит? Программа никаких сообщений не выводит, тупо файлик за секунду сделала и все.
Windows 10 x64 Пробовал просто exe файл перетащить на другой комп - все норм запускается, но само преобразование не пробовал, завтра протестирую. А как вы определили что висит? Программа никаких сообщений не выводит, тупо файлик за секунду сделала и все.
Так ничего не происходит, куда сохранить не спрашивает.
Дык он в тот же каталог исходного файла кидает)
И ниже и путь и имя конечного файла прописаны, посмотрите скрин выше внимательно.
ЗЫ. Лень делать лишние диалоговое окна.
В фотожопе есть готовый пункт меню, если не найдёте, завтра напишу.
По факту, act файл это тупо список rgb888 цветов.
Соответственно уж 32 цифры (16 цветов) можно в коде и руками нарисовать.
Заработало. Спасибо.
начну писать экспериментальный скетч под ili9341 240*320 TFT
не взлетел у меня дисплей с резисторными делителями на Arduino Nano :( или я его загубил.
лог анализатором данные на SPI шине вижу, но не работает.
жду прихода дисплея 128*160 и конвертора уровней, потом продолжу эксперименты.
... потом продолжу эксперименты.
Я попробовал приём "четыре пикселя в одном". Когда картинка размером 80*64 выдаётся за 160*128. Для сравнения:
Мне кажется в анимации очень бы помогло, увеличив в 4 раза количество хранимых картинок. Тогда можно и мультики записывать.
Пробовал вариант в своих раскрасках.
В функции конечно чёрт ногу сломит, но идея каждый пиксель в строке повторять 2 раза подряд, а затем повторять строку второй раз. Я к тому чтоб попробовать реализовать идею масштабирования в ассемблерной вставке если есть желание и возможности.
если есть желание и возможности.
ну теперь возможности появятся в Ноябре, как платки придут. Если я правильно посчитал, дисплей 128*160 это 40960 байт на всю картинку, при частоте SPI 8МГц получается максимальная теоретическая скорость отрисовки полного экрана 24 кадра в секунду, в реальности одноцветное заполнение будет максимум 20 кадров (попробую).
Думаю хранить фоновую картинку в Progmem в 16 цветной палитре, выведу ее фоном.
Потом уже баловаться вывод спрайтов (например вращающихся кубиков) без затирания фона, используя промежуточный буфер.
Вывод движущейся одноцветной картинки на весь экран мне как то не очень интересен (нет в этой задачи драйва).
Думаю хранить фоновую картинку в Progmem в 16 цветной палитре, выведу ее фоном.
Потом уже баловаться вывод спрайтов (например вращающихся кубиков) без затирания фона, используя промежуточный буфер.
Вывод движущейся одноцветной картинки на весь экран мне как то не очень интересен (нет в этой задачи драйва).
16 цветов это 10Кб на полноэкранную картинку-фон? А конвертер картинок?
А конвертер картинок?
фотожоп называется :)
А потом тупо в bitmap и из него в код Си (тут где то в Песочнице были недавно ссылки). В инете есть, если не найдете - у себя покапаюсь.
соответственно в Progmem пару фоновых картинок влезет легко.
:)
С такой технологией не сталкивался ещё. Ваша стратегия - полбайта на пиксель?
Мне как человеку далёкому от программирования и фотошопов другая мысль приходила в голову:
картинка в 3 экземплярах (бит на пиксель) и каждый несёт свой цвет и их комбинации тоже. Итого 8 цветов.
И как выбирать цвет из этих трех картинок? В общей же сумме число бит на всю многоцветную картрнку одинаково? Не пойму логику....
Функция читает бит (пикселя) по местоположению в массиве из каждой картинки. Если, например, комбинация 1,1,0 то пусть будет жёлтый и т.д.
Готовить её тоже просто: берём картинку ч\б (хотя для удобства стирания можно и цветную.), копируем 3 раза и в каждой копии затираем стирашкой лишнее. Т.е. элемент оставленный только на первой и второй картинке будет жёлтый (или какой захотим).
lilik, все зависит от задачи, соответственно и решение задачи разное, я просто побаловаться, выжать максимум из МК, а что конкретно вы хотите нарисовать)?
Картинки "иконкаподобные" хранить в большом количестве и разукрашивать различными способами на экране используя ресурсы только Ардуино . Было бы здорово ещё их анимировать, но тут упирается всё в скорость. Ваша функция самая быстрая и анимация возможна, но без раскрашек.
А с самого начала интересовали игры на этом экранчике. Но из интересных побаловался только про птичку:
https://forbiddenbit.com/en/arduino-game/flappy-bird-game-arduino/
Других интересных просто нет.
:)
С такой технологией не сталкивался ещё. Ваша стратегия - полбайта на пиксель?
Мне как человеку далёкому от программирования и фотошопов другая мысль приходила в голову:
картинка в 3 экземплярах (бит на пиксель) и каждый несёт свой цвет и их комбинации тоже. Итого 8 цветов.
Естественно, весь пиксель - в одном единственном месте, собирать его по частям из разных мест - решение, мягко говоря, странное.
Есть, правда, варианты, когда кодируется не один пиксель, а группа от 2*2 до 8*8 пикселей, причем цветов на всю группу выделяется не больше двух.
начну писать экспериментальный скетч под ili9341 240*320 TFT
не взлетел у меня дисплей с резисторными делителями на Arduino Nano :( или я его загубил.
сам дурак :(
лечение:
https://arduino.ru/forum/apparatnye-voprosy/displei-ili9341-brak-ili-kri...
Естественно, весь пиксель - в одном единственном месте, собирать его по частям из разных мест - решение, мягко говоря, странное.
Согласен, поэтому 128*480 :)
TFT SPI ili9341 240*320
Заполнение всего экрана случайным цветом, получился грустный результат 3 кадра в секунду, из теоретически возможных 6.5 кадров :(
andycat в листинг гляньте - сколько ваш код породил CALL RET для вывода одной точки ... ужаснетесь
листинг гляньте
в смысле ассемблерный листинг? где смотреть?
P.S. Вывод одной точки это вызов spi.transfer из массива памяти.
P.P.S. И я еще пока в начале пути, есть места куда оптимизироваться, в том числе выкинуть цикл и использовать spi_write, так что не все потеряно :)
P.P.P.S. Но конечно как выше писали коллеги, этот МК явно не для вывода полноцветных изображений.
andycat в листинг гляньте - сколько ваш код породил CALL RET для вывода одной точки ... ужаснетесь
А по аналогии как для st7735 на ассемблере нельзя написать вывод массива из прогмем? Для анимации функция хорошая получилась. Выше я высказывал идею о мультиках с картинками 0,25 бита на пиксель.
выкинул всякие буферы промежуточные, пишу сразу в регистры, код взял из SPI.h
пофиг, все равно три кадра в секунду.
пробовал не использовать ожидание готовности шины
while (!(SPSR & _BV(SPIF))) ;
просто nop задержки - плохо - идут артефакты, дисплей не воспринимает корректно данные.
на ассемблере нельзя написать вывод массива из прогмем?
вместо
SPI.transfer(COLor_4(i,j)>>8);SPI.transfer(COLor_4(i,j));
используйте
или как выше, просто вытащить часть этой функции себе в код, думаю быстрее уже не сделать.
А конвертер картинок?
Попробовал я в фотошопе преобразовать полноцветную картинку в нужное количество бит/цветов на пиксель, потом просто сохраняется в raw формате + там же есть пункт сохранения палитры.
Проблема дальше вылезла: он все равно на каждый пиксель записывает 1 байт, соотвественно, например в моем случае 4х цветная картинка размером 240*320 получается 76800 байт что никак не влезет в Progmem, online конвертеров не нашел, сейчас Delphi поставлю, буду преобразователь писать.
на каждый пиксель записывает 1 байт
А сколько бит нужно на один пиксель?
...сейчас Delphi поставлю, буду преобразователь писать.
Посмотрел картинки в 16 цветном конвертере. В принципе нормально смотрится. Но палитра цветов тоже должна как то храниться и выводиться.
на каждый пиксель записывает 1 байт
А сколько бит нужно на один пиксель?
Для 16 цветов 4 бита.
Ну, а я решил 8 цветную раскрашку в режиме 4 в 1 посмотреть. Угловатость конечно вылазит, но не критично.
Для 16 цветов 4 бита.
Всё-равно много 38400 байт.
Для 16 цветов 4 бита.
Всё-равно много 38400 байт.
Можно взять картинку поменьше, а раздуть во весь экран (как выше изображено).
... Вот льва по своей методе 3 пикселя-8 цветов сделал.
Программа преобразования готова, отображение допишу/проверю - выложу. Палитру пока руками из rgb888 to rgb565 делать, потом дорисую.
По поводу картинок, крупные пиксели портят весь вид, лучше детализацию увеличить, цветов уменьшить. В во всяком случае на компе это круче смотрится.
В 16 битном цвете 128 на 160 влезет 3 картинки.
Учитывая ваши мультяшные картинки, то 4х цветных более чем хватит, соответственно и картинок больше влезет.
+замена палитры при одинаковых картинках дают интересные эффекты, один и тот же например цветок можно раскрасить во множестве вариантов.
И да, палитра так же в progmem живёт, но там копеечное место занимает.
итак результат что получилось, первая картинка 240*320 4 цвета, вторая 120*160 16 цветов в прямой и инверсной палитре, 128*160 16 цветов 10 кадров в секунду вполне нормально для простенькой игрушки, особенно если не весь экран занимать, и смотрится очень не плохо.
код
-
программа преобразования файлов
https://drive.google.com/file/d/10ExeHGykMlk9_BmTJoOUx7oJI4UJBDX-/view?u...
Это сначала надо картинку в raw перевести?
Это сначала надо картинку в raw перевести?
raw это просто набор байтов картинки, чем ее создать совершенно без разницы.
1 байт - 1 пиксель
в байте столько значащих битов, сколько цветов в палитре
вот например 16 цветная и 4 цветная картинка
1 байт - 1 пиксель
в байте столько значащих битов, сколько цветов в палитре
4 цвета - 4 бита в байте?, 16 цветов - 16 битный байт?
ну нет....
4 цвета - в каждом байте картинке, т е в каждом пикселе, младшие 2 бита указывают номер цвета в палитре:
соответственно в 16 цветной картинке в каждом байте младшие 4 бита указывают на цвет в палитре:
Ясно. Номер элемента массива палитры?
Ясно. Номер элемента массива палитры?
да.
таким методом можно штук 50 персонажей игр, например размером 32*32 сохранить разной формы/направления движения, птички/кубики и прочее на что фантазии хватит, потом в зависимости от игровой ситуации выводить их в разных местах с разными палитрами.
Понял. Сначала картинку в raw, потом конвертером в массивы, потом скетч написать для st7735, потом посмотреть скорость отрисовки.
написать для st7735
насчет скетча не знаю, вам всего одна функция нужна добавить, должно работать без переделок
по поводу скорости, выше я ж привел данные в миллисекундах, посчитать не сложно
128*160*16 цветов ~ 100 миллисекунд
а мелкие элементы игры и того быстрее
Понял. Сначала картинку в raw, потом конвертером в массивы...
Не сработал конвертер, видимо у меня винда не той системы - висит преобразование и всё :)
Windows 10 x64
Пробовал просто exe файл перетащить на другой комп - все норм запускается, но само преобразование не пробовал, завтра протестирую.
А как вы определили что висит? Программа никаких сообщений не выводит, тупо файлик за секунду сделала и все.
Так ничего не происходит, куда сохранить не спрашивает.
Дык он в тот же каталог исходного файла кидает)
И ниже и путь и имя конечного файла прописаны, посмотрите скрин выше внимательно.
ЗЫ. Лень делать лишние диалоговое окна.
Точно :)
Всё работает.
Не ясно как массив палитры получить, расширение act.
В фотожопе есть готовый пункт меню, если не найдёте, завтра напишу.
По факту, act файл это тупо список rgb888 цветов.
Соответственно уж 32 цифры (16 цветов) можно в коде и руками нарисовать.
Понял, просто всегда "паинтом нет" пользуюсь.
Нашёл, как определять rgb.
Естественно, весь пиксель - в одном единственном месте, собирать его по частям из разных мест - решение, мягко говоря, странное.
Согласен, поэтому 128*480 :)
То есть вот это:
не по частям из трех разных мест?