ESP8266+экран ST7735, вывод изображения из памяти без SD карты.

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

Встал вопрос о выводе изображения без использования внешнего модуля SD(microSD) карт.

Имеем ESP8266, модуль ST7735S 0.96" 80x160 SPI

Заливаю файлы, предварительно подготовленные для этого экрана (с SD всё работало) в память модуля spiffs, по инструкции из простор интернета.

#include "FS.h"
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#define TFT_CS         D2
#define TFT_RST        D3
#define TFT_DC         D4
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);

void setup(void) {
  Serial.begin(115200);
  tft.initR(INITR_MINI160x80);
  tft.initR(INITR_GREENTAB); //инвертирует цвета в норму
  tft.invertDisplay(true);
  tft.setRotation(1); // положение картинки горизонтально
}

void loop() {
  bmpDraw("1.bmp", 0, 0);
  delay(4000);

}


#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint8_t y) {

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3 * BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();

  if ((x >= tft.width()) || (y >= tft.height())) return;

  Serial.println();
  Serial.print("Loading image '");
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  if (File file = SPIFFS.open("/test_example.txt", "r") == NULL) {
    Serial.print("File not found");
    return;
  }

  // Parse BMP header
  if (read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print("Image Offset: "); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print("Header size: "); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if (read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print("Bit Depth: "); Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print("Image size: ");
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if (bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if ((x + w - 1) >= tft.width())  w = tft.width()  - x;
        if ((y + h - 1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x + w - 1, y + h - 1);

        for (row = 0; row < h; row++) { // For each scanline...

          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if (bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col = 0; col < w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r, g, b));
          } // end pixel
        } // end scanline
        Serial.print("Loaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

  bmpFile.close();
  if (!goodBmp) Serial.println("BMP format not recognized.");
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

 

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

Смущает 50 строка кода, да может и сам скетч неправильно. Поправьте если неправильно написал. Там 



  if (File file = SPIFFS.open("/1.bmp", "r")) {
    Serial.print("File not found");
    return;
  }

Пишет Loading image '1.bmp'

BMP format not recognized.
 

Не могу поправить шапку, не понимаю как изменить.

vde69
Offline
Зарегистрирован: 10.01.2016

никогда не пишите все в одну строчку...

правильно будет так:

File file = SPIFFS.open("/test_example.txt", "r");
if (file == NULL) {

 

 

IVAN222
Offline
Зарегистрирован: 19.04.2017

Для этого лучше пользоваться библиотекой TFT_eSPI она проще и в настройках удобнее  ее можно настроить под любой монитор. И скорость хорошая вот пример: https://yadi.sk/i/OvliKj5UOToVNw

Это простой код для демонстрации фото.

//Не забудьте изменить User_Setup.h внутри библиотеки TFT_eSPI!

#include <TFT_eSPI.h>
#include "1.h" //(Bitmap generated with LCD Image Converter)

#define DISPLAY_DC      D3 // Data/command pin for BOTH displays
#define DISPLAY_RESET   D4 // Reset pin for BOTH displays

TFT_eSPI tft = TFT_eSPI();   // Вызвать библиотеку

void setup(void) {
  tft.begin();     // initialize a ST7789 chip
  tft.setSwapBytes(true); 

void loop() {
 // tft.fillScreen(TFT_BLACK);
 
  tft.pushImage(0,0,128,160,mercy1);
  delay(1000);
  
  
}
#include "1.h" - это ваша картинка находится в той же папке где скеч. создайте при помощи программки lcd-image-converter. 
В ESP можно запихать картинок 128-128 22 штуки, 240-320 6 штук. Но для этого Вам нужно в ARDUINO IDE изменить заходите в инструменты - Flash size - FS:3M. 
Это для того что бы на фото отводилось больше памяти.

1.h код будет примерно такой.

const uint16_t mercy1 [] PROGMEM = {


Сюда вставите изображение созданное программкой в формате "С"



};

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Nikolaha53rus, есть такой анекдот про физиков:

Задача 1

дано: пустой чайник, кран с водой, газовая горелка, спички.

нужно: вскипятить воду.

решение:

- открыть кран,

- налить в чайник воды,

- закрыть кран,

- зажечь спичку,

- от спички зажечь горелку,

- поставить чайник на горелку,

- ждать, пока вода закипит.

Задача 2

дано: кран, чайник с водой, зажженная горелка, спички.

нужно: вскипятить воду.

решение:

- выливаем воду из чайника,
- тушим горелку,
- далее - см. решение задачи 1.
 
Вот Вы поступаете именно по этому алгоритму. Файловая система - достаточно затратное мероприятие, связанное с необходимостью организации иерархического хранения разнородной информации. Она Вам в Вашей задаче совершенно не нужна, но Вы ее используете вопреки здравому смыслу ради совместимости с чем-то другим, в частности со способом хранения информации на SD.
Далее: в BMP файле на пиксель приходится 3 байта, для экрана нужно только 2. Зачем Вы тратите на хранение изображения в памяти ESP в полтора раза больше места, чем это нужно? 
Это - только место, далее - производительность: правильно организованный процесс должен выглядеть примерно так:
- на ПК происходит преобразование формата BMP в массив в формате экрана, используемого с ESP, это нужно сделать 1 раз на компьютере в десятки, а то и сотни раз мощнее ESP.
- в процессе работы ESP просто берет данные из массива и копирует их на экран без преобразования.
Вместо этого Вы:
- на ПК делаете какие-то хитрые манипуляции, которые по сути ни для чего не нужны,
- на ESP каждый раз производите сложную и, кроме того, неоптимальную ни по памяти, ни по вычислительным ресурсам работу, связанную с преобразованием изображения из одного формата в другой.
 
По исходнику из исходного сообщения: при правильной организации работы строка 19 заменяются на одну или две строки кода, выводящего все необходимое на экран, а строки с 23 по 155 напрочь удаляются из исходника. Ну и первая строка тоже не нужна. Таким образом, исходник должен сократиться с 155 до 21-22 строк кода.
Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

Уважаемый Аndriano!!! Ответ очень прост, я чайник, мне никто, никогда не объяснял принципы работы и программирования МК, я пытаюсь учится с нуля, и помочь мне как здесь некому, не судите строго, я думаю Вы тоже не сразу взяли и начали программировать.

sadman41
Offline
Зарегистрирован: 19.10.2016

Уважаемый Nikolaha53rus, никто и никогда не объяснял тут andriano принципы работы и программирования МК. Знаете почему? Потому что над форумом не висит табличка "образовательное учреждение".

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

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

Ладно, проехали, я просто прошу помощи. Поможете, очень хорошо, не поможете, тоже не обижусь.

Я просто не понимаю как это работает, и хочу разобраться, с Вашей помощью. Не кидайтесь тапками. Извините если что не так сказал.

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

IVAN222 Сюда вставите изображение созданное программкой в формате "С"

Подскажите чем конвертировать?

Странный форум, не пойму почему текст синий.

Отправьте на правила форума, киньте ссылку. Покопался, не вижу.

IVAN222
Offline
Зарегистрирован: 19.04.2017

Вы можете скачать любой конвертер я предлагаю такую lcd-image-converter. с помощью ее конвертируете изображения с BMP,JPG в "С" для ESP это будет лучше а главное очень быстро.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Nikolaha53rus пишет:

я думаю Вы тоже не сразу взяли и начали программировать.

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

Nikolaha53rus пишет:

Я просто не понимаю как это работает, и хочу разобраться, с Вашей помощью. 

Видите ли, ситуация очень сильно зависит от того, что Вы на само деле хотите. Варианта всего два:

1. Вы хотите научиться работать с Ардуино.

2. Вы хотите реализовать конкретный проект.

Так вот, традиции (и Правила) форума диктуют совершенно различную реакцию на эти варианты.

Если Вы хотите научиться, Вам следует отложить в сторону вышеупомянутый проект и начать с мигания светодиодом. К проекту Вы вернетесь не ранее, чем через год-два. Естественно, этот варианта подразумевает самостоятельное чтение учебников (без просьб к форуму пересказать, что в этих учебниках написано). В этом случае Вам здесь помогут преодолеть все сложности, с которыми Вы столкнетесь в процессе обучения.

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

 

PS. Да, Правила содержатся в прикрепленной теме в разделе "Песочница". Если Вы собираетесь идти по первому пути, найдете там много полезной информации. Впрочем, если по второму, то изложенная там информация тоже окажется полезной.

vde69
Offline
Зарегистрирован: 10.01.2016

Да ладно, даже если человек 40 лет программировал для него ардуинка может быть темным лесом, например я в начале 90х годов программировал на CLARION + Pascal, сейчас для меня основной язык это 1С, и для меня Cи очень тяжелый язык, немного выручает, то что недавно делал несколько больших проектов на PHP, там синтаксис близок...

Ну и про год самообучения до минимального уровня - это слишком страшно, я никогда в жизни не видя Си за пару месяцев сделал вполне рабочий проект http://arduino.ru/forum/proekty/kontroller-mufelnoi-pechi Ардуино тем и хороша, что очень простая и в нее порог входа вообще смешной...

На самом известном форуме по 1с я модератор и навидался много, главное чего надо требовать от вопрошающего - это воспринимать те советы которые ему дают и пытаться точно сформулировать проблему

в САБЖе автор разумеется не имеет классических навыков программирования, по этому он идет путем проб и ошибок. Его надо направлять туда где он сможет понять чего ему делать (например меня в последней моей ветке про сканер пальцев именно так и толкали). А вот выдавая ему точные советы которые он еще не в состоянии понять просто бесполезно (так например мне вряд ли помогут советы про схемотехнику всяких усилителей и прочего, я все равно не буду разбиратся в конкретной схемотехнике, по тому, что это не мое и я выбираю именно ардуинку как целый модуль а не микроконтроллер который надо прикрутить куда то).

vde69
Offline
Зарегистрирован: 10.01.2016

Nikolaha53rus пишет:

Ладно, проехали, я просто прошу помощи. Поможете, очень хорошо, не поможете, тоже не обижусь.

Я просто не понимаю как это работает, и хочу разобраться, с Вашей помощью. Не кидайтесь тапками. Извините если что не так сказал.

если действительно хочешь разобраться, делается это так

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

Так далее весь код

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

IVAN222 пишет:

//Не забудьте изменить User_Setup.h внутри библиотеки TFT_eSPI!

Раскомментировал 

#define ST7735_DRIVER   

#define TFT_WIDTH  160

#define TFT_HEIGHT 80

#define ST7735_GREENTAB

#define TFT_CS   PIN_D8  // Chip select control pin D8
#define TFT_DC   PIN_D3  // Data Command control pin
#define TFT_RST  PIN_D4  // Reset pin (could connect to NodeMCU RST, see next line)
Не заводится, даже не подаёт признаков жизни, пытался просто экран красным залить. 
scl подключаю к D7
sda к D6
Может упустил что то?

 

b707
Offline
Зарегистрирован: 26.05.2017

vde69 пишет:

если действительно хочешь разобраться, делается это так

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

Так далее весь код

и куда потом этот комментированный код? в форум? - не надо :)

Или это для самообразования?

Nikolaha53rus - вы зря так негативно реагируете на советы andriano. Он говорит очень правильные вещи. Если предварительно обработаете свои картинки на компе и в ЕСП загрузите уже готовые массивы - программа станет в несколько раз проще и вам самому, как новичку - будет прощее ее отлаживать.

IVAN222
Offline
Зарегистрирован: 19.04.2017

sck-D5
sda-D7
ao-D3
reset-D4
cs-D8
gnd--
vcc-+3,3 или 5 в
led-+3,3 в

раскаментируй

#define ST7735_DRIVER
#define TFT_WIDTH 80
#define TFT_HEIGHT 160
#define TFT_CS PIN_D8 // Chip select control pin D8
#define TFT_DC PIN_D3 // Data Command control pin
#define TFT_RST PIN_D4 // Reset pin (could connect to NodeMCU RST, see next line)
попробуй так

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

у меня нет таких выводов

Есть вот такие scl,sda,res,dc,cs

Feofan
Offline
Зарегистрирован: 28.05.2017
Попытаюсь адаптировать пины к #15:
sck->scl
sda->sda
ao->dc
reset->res
cs->cs
Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

Не работает. вообще тишина. Ни примеры из библиотеки, ни скетч из 3-го поста. Экран 100% рабочий, с Adafruit на ESP8266 работает, на UNO c SD картой работает.

Скетч заливается, но ничего не происходит.

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

vde69 пишет:

Я не негативно реагирую, я просто что то не понимаю, и прошу мне объяснить. Я с Уважаемым andriano полностью согласен, много лишних действий, только мне не хватает мозгов всё это реализовать. Кто то шарит в машинах, кто то в контроллерах, кто то в локальных сетях, видеонаблюдении и т.д.,  и когда что то непонятно просят помощи. Вот так и я. Это для самообразования. Много чего знаю, а вот с МК пробел. Именно с данным ST7735S. Другие проекты я мог подсмотреть примеры, и настроить под себя. Тут засада.

 

IVAN222
Offline
Зарегистрирован: 19.04.2017

Проверти все контакты если плохой контакт то вообще не запустится.
сначала раскоментируйте
#define ST7735_DRIVER
запустите .
потом
#define TFT_RGB_ORDER TFT_RGB
попробуйте это
#define ST7735_GREENTAB160x80
надо пробовать и другие .у меня именно такого монитора нет и я немогу сказать что именно раскоментировать.

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

ОК. Переводил комментарии в setup, вроде всё правильно делаю. Ещё раз проверю.

sadman41
Offline
Зарегистрирован: 19.10.2016

https://github.com/olikraus/u8g2/blob/master/sys/arduino/u8g2_full_buffe...
Тут вон вроде крутят битмапы, библиотека мощная.

Nikolaha53rus
Offline
Зарегистрирован: 17.04.2016

Она не поддерживает ST7735.

XanderEVG
Offline
Зарегистрирован: 06.10.2017

Если кто тоже столкнется с ошибкой "BMP format not recognized." (гугл ведет сюда).

Итак, исходные данные:

- либа TFT_eSPI.

-cкетч - копипаста из примера TFT_eSPI/examples/Generic/TFT_SPIFFS_BMP

-картинка сгенерирована в гимпе, сохранена в data, загружена в  SPIFFS.

Заливаем картинку, заливаем скетч, открываем монитор порта, видим ошибку BMP format not recognized.

 

Такой текст можно найти в файле проекта "bmpFunctions":

if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))
 {
     // ...
 } else {
    Serial.println("BMP format not recognized.");
}

Открываем нашу картинку в hex-редакторе, видим, что там нет этих значений на своем определенном месте, а в картинке из примера  - эти байты есть.

Решил путем научного тыка, пересохраняя картинку с разными параметрами, нужные параметры:

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

XanderEVG пишет:

if ((read16(bmpFS) == 1) && (read16(bmpFS) == 24) && (read32(bmpFS) == 0))

Ну так это как бы стандартный формат полноцветного изображения: одна плоскость 24 bpp и несжатый.

Вот только код стремный: я сейчас навскидку не помню, оговорен ли в Си порядок вычисления логического выражения. Если "да", причем "слева направо" - все нормально, а если "нет" - можно обломаться. Лично я, чтобы не путаться в особенностях разных языков, предпочел бы сначала вычитать в нужном порядке переменные, и только потом делать проверки.

 

PS. И еще: на мой взгляд, хранить для МК картинки в одном из стандартных форматов вряд ли целесообразно. Гораздо лучше написать для ПК утилиту, которая перекодирует BMP в удобный для МК формат, и уже перекодированные картинки размещать на карте. Заодно можно решить и проблему с поддержкой всех вариантов формата BMP. А заодно и не только BMP, можно добавить JPG, PNG, GIF и другие форматы.