Arduino:Примеры/soft spitftbitmap

Материал из Онлайн справочника
Версия от 12:39, 8 июля 2023; EducationBot (обсуждение | вклад)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Вывод изображения на 1,8-дюймовый TFT-дисплей[1]

Это скетч для библиотеки ST7735, который при помощи платы Arduino показывает на TFT-дисплее (1,8-дюймовом) заданную картинку. Для скетча потребуется 1,8-дюймовая TFT-плата с SD-картой или «голый» 1,8-дюймовый TFT-дисплей.

Код

 
/***************************************************

Вывод изображения на 1,8-дюймовый TFT-дисплей

Этот скетч предназначен для 1,8-дюймового TFT-дисплея, 
работающего через SPI-интерфейс (от Adafruit).

Для этого скетча подойдет 1,8-дюймовая TFT-плата с SD-картой...
----> http://www.adafruit.com/products/358
...а также «голый» 1,8-дюймовый TFT-дисплей
----> http://www.adafruit.com/products/618

Руководства и схемы подключения ищите по ссылкам выше. Этим дисплеям 
для коммуникации требуется SPI-интерфейс с 4 или 5 контактами (контакт 
RST опционален). 

Adafruit инвестировала время и ресурсы, создавая эту библиотеку с 
открытым кодом. Пожалуйста, поддержите Adafruit и оборудование с 
открытым кодом, покупая продукты Adafruit!

Библиотека написана Лимор Фрид (Limor Fried, Ladyada) для Adafruit 
Industries. Весь текст выше должен быть включен при любом повторном 
распространении.

****************************************************/

#include <Adafruit_GFX.h>    // подключаем графическую библиотеку
#include <Adafruit_ST7735.h> // подключаем библиотеку для управления дисплеем
#include <SPI.h>
#include <SD.h>

#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// Коммуникация будет вестись через аппаратный SPI-интерфейс,
// и им будут пользоваться сразу два устройства: TFT-дисплей
// и SD-карта. На платах Arduino аппаратный SPI всегда находится на
// одних и тех же контактах, и переназначить их нельзя. 
// На Arduino Uno, Duemilanove и т.д. за SPI отвечают следующие 
// контакты: 11-ый контакт – MOSI, 12-ый контакт – MISO,
// 13-ый контакт – SCK.
#define SPI_SCK 13
#define SPI_DI  12
#define SPI_DO  11

#define SD_CS    4    // CS-контакт для SD-карты
//#define TFT_CS  10  // CS-контакт для TFT-дисплея
//#define TFT_DC   9  // DC-контакт для дисплея (для переключения между передачей данных и команд)
//#define TFT_RST  8  // RESET-контакт для TFT-дисплея (или подключите к +5V)

// это контакты для подключения TFT-платы:
#define TFT_CS   10
#define TFT_DC   8
#define TFT_RST  0  // этот контакт также можно подключить к RESET-контакту Arduino

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, SPI_DO, SPI_SCK, TFT_RST);

void setup(void) {
  Serial.begin(9600);

  // С 10 января 2012 года поставщик начал отправлять Adafruit 
  // чуть измененные 1,8-дюймовые дисплеи, из-за чего выравнивание
  // экрана сдвинулось на несколько пикселей. Это значит, что для 
  // таких дисплеев нужно использовать другой код инициализации.
  // О том, какой именно код нужен, сообщает цвет ярлыка, приклеенного
  // к дисплею. Если верхний и левый края дисплея обрезаны или если
  // там есть дополнительные «случайные» пиксели, попробуйте другой    
  // вариант. Если видите инверсию красного и зеленого цветов,
  // попробуйте вариант с черным ярлычком.

  Serial.print("Initializing SD card...");  //  "Инициализация SD-карты... "
  if (!SD.begin(SD_CS, SPI_DO, SPI_DI, SPI_SCK)) {
    Serial.println("failed!");   //  "не удалась"
    return;
  }
  Serial.println("OK!");

  bmpDraw("parrot.bmp", 0, 0);
}

void loop() {
}

// Эта функция открывает BMP-файл и печатает его на заданных 
// координатах. Ее работу можно ускорить, считывая сразу несколько 
// пикселей, а не пиксель за пикселем. Для этого нужно увеличить 
// размер буфера, однако это, в свою очередь, потребует ресурсов 
// RAM-памяти Arduino. Разумным компромиссом будет 20 пикселей.

#define BUFFPIXEL 20

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

  File     bmpFile;
  int      bmpWidth, bmpHeight;   // ширина и высота в пикселях 
  uint8_t  bmpDepth;              // глубина цвета (по умолчанию должно стоять 24)
  uint32_t bmpImageoffset;        // место, с которого в файле начинается само изображение
  uint32_t rowSize;               // это значение не всегда будет равно bmpWidth; возможно, будет отступ 
  uint8_t  sdbuffer[3*BUFFPIXEL]; // буфер для пикселей (значения R, G и B для каждого пикселя)
  uint8_t  buffidx = sizeof(sdbuffer); // текущая позиция в буфере для пикселей
  boolean  goodBmp = false;       // если скетч определит нужный тип заголовка, он выставит здесь «true»
  boolean  flip    = true;        // данные BMP-файла хранятся по принципу «снизу вверх»
  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('\'');

  // открываем на SD-карте запрашиваемый файл:
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.print("File not found");  //  "Файл не найден"
    return;
  }

  // анализируем заголовок BMP-Файла:
  if(read16(bmpFile) == 0x4D42) { // сигнатура BMP-файла
    Serial.print("File size: ");  //  "Размер файла: "
    Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // считываем и игнорируем байты, вписанные при создании файла
    bmpImageoffset = read32(bmpFile); // место, с которого начинается в файле само изображение (смещение)
    Serial.print("Image Offset: ");  //  "Смещение: "
    Serial.println(bmpImageoffset, DEC);
    // считываем заголовок DIB:
    Serial.print("Header size: ");  //  "Размер заголовка: "
    Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile); 
    bmpHeight = read32(bmpFile);
    if(read16(bmpFile) == 1) {  //  количество цветовых плоскостей – должно быть «1»
      bmpDepth = read16(bmpFile);  // биты на пиксель
      Serial.print("Bit Depth: ");  //  "Глубина цвета: "
      Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // «0» – это без сжатия

        goodBmp = true; // поддерживаемый формат BMP – продолжаем дальше!
        Serial.print("Image size: ");  //  "Размер изображения: "
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // если нужно, границы BMP-изображения будут иметь 4-байтный отступ:
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // Если значение в bmpHeight отрицательное, то данные 
        // изображения хранятся по принципу «сверху вниз»
        // Это не канон, но иногда встречается. 
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        //  загружаем обрезанную область: 
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // задаем адресный промежуток для границ обрезанного изображения: 
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // для каждого пиксельного ряда...

          // Ищем начало пиксельного ряда. Возможно, делать это для 
          // каждой строки – слишком трудоемко, однако эта функция 
          // выполняет много «грязной работы» вроде обрезки 
          // изображения и создания отступа для нового пиксельного 
          // ряда. Кроме того, этот поиск имеет место только в том 
          // случае, если нужно поменять позицию файла (это позволяет 
          // избежать кластерных расчетов в библиотеке SD).

          if(flip) //  данные BMP-файла хранятся по принципу «снизу вверх» (нормальный BMP) 
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     //  BMP-файл хранится по принципу «сверху вниз»
            pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // нужно ли запустить поиск?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // перезагружаем буфер
          }

          for (col=0; col<w; col++) { // для каждого пикселя...
            // настало время считывать новые пиксельные данные?        
            if (buffidx >= sizeof(sdbuffer)) { // да!
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // выставляем индекс на начало 
            }

            // конвертируем пиксели из формата BMP в формат TFT,
            // а затем выводим их на дисплей:
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r,g,b));
          } // последний пиксель
        } // последняя строка пикселей 
        Serial.print("Loaded in ");  //  "Картинка загрузилась за "
        Serial.print(millis() - startTime);
        Serial.println(" ms");  // "миллисекунд"
      } // конец goodBMP
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println("BMP format not recognized.");  //  "Формат BMP не обнаружен" 
}

// Эти функции считывают из BMP-файла 16-битные и 32-битные данные.
// Данные BMP-файла, как и у Arduino, хранятся по принципу 
// «от младшего к старшему». При портировании куда-либо этот порядок, 
// возможно, придется поменять.

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // самый младший бит
  ((uint8_t *)&result)[1] = f.read(); // самый старший бит
  return result;
}

uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // самый младший бит
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // самый старший бит
  return result;
}

См.также

Внешние ссылки