实用!Arduino平台最强大的“显卡”驱动:Adafruit GFX 图形库8000字详细使用指南

发布时间 2023-11-27 18:17:21作者: lovefast

实用!Arduino平台最强大的“显卡”驱动:Adafruit GFX 图形库8000字详细使用指南

 

 

概述

Arduino的Adafruit_GFX库为我们所有的LCD和OLED显示屏提供了通用语法和图形功能集。这使得Arduino示例程序可以很容易地在不同类型的显示屏之间进行调整,并且任何新特性、性能改进和错误修复都将立即应用于我们提供的完整的彩色显示。

Adafruit_GFX库可以使用Arduino库管理器安装……这是首选的方式。在Arduino IDE“工具”菜单中,选择“管理库…”

 

在搜索栏中输入“gfx”可以快速找到它:

 

 

在这里,也要搜索并安装Adafruit_BusIO库(或者…新的Arduino IDE版本自动安装这个依赖项)。

Adafruit_GFX库总是与每个特定显示驱动类型的附加库一起工作——例如,ST7735 1.8英寸的彩色LCD需要安装Adafruit_GFX、Adafruit_BusIO和Adafruit_ST7735库。目前支持下面这些库:

Adafruit-SSD1331-OLED-Driver-Library 兼容arduino的0.96" 16位彩色OLED w/带microSD卡槽。

这些库是为Arduino用c++编写的,但是可以通过重写底层pin访问功能轻松地移植到任何微控制器。

坐标系和单位

像素—图像元素,组成数字图像的块——通过它们的水平(X)和垂直(Y)坐标进行定位。坐标系将原点(0,0)放在左上角,正X向右递增,正Y向下递增。这与数学中的标准笛卡尔坐标系统是颠倒的,但在许多计算机图形系统中已经建立了实践(追溯到光栅扫描CRT图形的时代,它自上而下地工作)。要使用高大的“纵向”布局而不是宽的“横向”格式,或者如果物理安装方向限制了附件中显示的方向,也可以应用四个旋转设置中的一个,即显示的哪个角代表左上角。

同样与数学笛卡尔坐标系不同的是,这里的点有维数——它们的宽和高总是一个完整的整数像素。

 

 

坐标总是用像素单位表示;没有像毫米或英寸这样的现实测量的隐含比例,显示图形的大小将是特定显示的点间距或像素密度的函数。如果你的目标是真实的维度,你需要缩放你的坐标来适应。点间距通常可以在显示屏的数据手册中找到,或者通过测量屏幕宽度并将像素数除以这个测量值。

对于彩色显示器,颜色表示为无符号的16位值。 某些显示器实际上可能比这更多或更少,但是该库以16位值运行……这些对于Arduino来说很容易使用,同时还为所有不同的显示器提供一致的数据类型。 红色,绿色和蓝色等原色分量全部“打包”成单个16位变量,其中最高有效5位传达红色,中间6位传达绿色,最低5位传达蓝色。 多余的部分分配给绿色,因为我们的眼睛对绿光最敏感。 科学!

 

 

对于最常见的主色和副色,我们有这个有用的列表,可以包含在自己的代码中。当然,你可以从65,536种不同的颜色中选择任意一种,但下面的基本列表开始可能是最简单的:

// Color definitions
#define BLACK    0x0000
#define BLUE     0x001F
#define RED      0xF800
#define GREEN    0x07E0
#define CYAN     0x07FF
#define MAGENTA  0xF81F
#define YELLOW   0xFFE0 
#define WHITE    0xFFFF

对于单色显示,颜色总是简单地指定为1 (set)或0 (clear)。set/clear的语义是特定于显示类型的:像发光的OLED显示,“set”像素是发光的,而反光的LCD显示,“set”像素通常是暗的。可能会有例外,但通常您可以期望0 (clear)表示新初始化的显示的默认背景状态,无论结果如何。

基本图形

每个用于特定显示屏的显示库都有自己的构造函数和初始化函数。这些在每个显示屏类型的单独教程中都有记录,或者通常在特定的库头文件中有说明。本教程的其余部分将介绍与显示类型无关的通用图形函数。

下面的函数描述只是原型—假设显示对象是由特定于设备的库根据需要声明和初始化的。查看每个库的示例代码,了解它的实际使用情况。例如,在我们显示print(1234.56)的地方,你的实际代码会把对象名称放在这之前,例如,它可能会读做screen.print(1234.56)(如果你已经声明了显示对象的名称为:screen)。

  • 绘制像素(点)

首先是最基本的像素推送器。你可以调用它,并指定参数:X Y坐标和一种颜色,它会在屏幕上生成一个点:

void drawPixel(uint16_t x, uint16_t y, uint16_t color);

 

 

 

  • 绘制线

你也可以画一条线,指定起点和终点和颜色:

void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color);

 

对于水平或垂直的线,有优化的线绘制功能,避免角计算:

void drawFastVLine(uint16_t x0, uint16_t y0, uint16_t length, uint16_t color);
void drawFastHLine(uint8_t x0, uint8_t y0, uint8_t length, uint16_t color);
  • 绘制矩形

接下来,使用以下程序可以绘制和填充矩形和正方形。每个都可以输入矩形左上角的X、Y对、宽度和高度(以像素为单位)以及颜色。drawRect()只渲染矩形的框架(轮廓)-内部不受影响-而fillRect()用给定的颜色填充整个区域:

void drawRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t color);
void fillRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t color);

 

 

 

 

 

要创建具有对比轮廓的实线矩形,首先使用fillRect(),然后在其上使用drawRect()。

  • 绘制圆

同样,对于圆圈,你可以画边框和填充。每个函数输入一个X, Y对作为中心点,半径(以像素为单位)和颜色:

void drawCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);
void fillCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color);

 

 

 

 

  • 绘制圆角矩形

对于圆角矩形,绘制和填充功能都是可用的。每个矩形都以X、Y、宽度和高度(就像普通矩形一样)开始,然后是角半径(以像素为单位),最后是颜色值:

void drawRoundRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius, uint16_t color);
void fillRoundRect(uint16_t x0, uint16_t y0, uint16_t w, uint16_t h, uint16_t radius, uint16_t color);

 

 

这里还有一个额外的小技巧:因为画圆函数总是相对于一个中心点像素绘制的,所以最终的圆直径将总是一个奇数像素。如果需要一个均匀大小的圆(这将在像素之间放置中心点),可以使用一个圆角矩形函数来实现:传递相同的宽度和高度(均为偶数),以及刚好为该值一半的角半径。

  • 绘制三角形

对于三角形,同样有绘制和填充函数。每个函数都需要七个参数:定义三角形的三个角点的X、Y坐标,然后是颜色:

void drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);

 

 

 

  • 输出字符和文本

输出文本有两个基本的字符串绘制过程。第一个只是针对一个字符。你可以把这个字符放在任何位置并使用任意颜色。只有一种字体(为了节省空间),它应该是5x8像素,但是可以传递一个可选的大小参数,根据这个因素来缩放字体(例如size=2将渲染文本为每个字符10x16像素)。它是一个小块状,但只有一个字体有助于保持程序代码的占用空间。

void drawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg, uint8_t size);

 

 

文本非常灵活,但操作方式略有不同。文本大小、颜色和位置不是一个过程,而是在单独的函数中设置的,然后使用print()函数——这样做很简单,并且提供了我们非常熟悉的Serial.print()函数的所有字符串和数字格式化功能!

void setCursor(uint16_t x0, uint16_t y0);
void setTextColor(uint16_t color);
void setTextColor(uint16_t color, uint16_t backgroundcolor);
void setTextSize(uint8_t size);
void setTextWrap(boolean w);

从setCursor(x,y)开始,它将把文本的左上角放在您需要的任何位置。 最初将其设置为(0,0)(屏幕的左上角)。 然后使用setTextColor(color)设置文本颜色-默认情况下为白色。 文本通常以“清晰”的形式绘制-每个字符的开放部分均显示原始背景内容,但是如果您希望文本遮挡下面的内容,则可以将背景色指定为可选的第二个参数tosetTextColor()。 最后,setTextSize(size)将文本的比例乘以给定的整数因子。 在下面,您可以看到1(默认),2和3的比例尺。在较大的尺寸下,它看起来像块状的,因为我们仅以单一的简单字体提供了该库,以节省空间。

!注意,自定义字体不支持文本背景色。对于这些,您需要确定文本范围,并在绘制文本之前显式地绘制一个填充矩形。

 

 

 

 

 

 

在设置好所有内容之后,您可以使用print()或println()—就像您使用串行打印一样!例如,要打印一个字符串,使用print(“Hello world”)—这是上面图像的第一行。还可以对数字和变量使用print()—上面的第二行是print(1234.56)的输出,第三行是print(0xDEADBEEF, HEX)。

默认情况下,超出一行的长文本被设置为自动“换行”到最左边的列。要取消此行为(这样文本就会从显示屏的右侧运行——这对于滚动字幕效果很有用),请使用setTextWrap(false)。使用setTextWrap(true)恢复正常的“换行”行为。

请参阅“使用字体”页面,了解最新GFX库中的附加文本特性。

  • 显示位图

你可以绘制小的单色(单色)位图,适合精灵和其他迷你动画或图标:

void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);

这将向显示器发送一个连续的位块数据,其中每个“1”位将相应的像素设置为“color”指定的颜色,而跳过每个“0”位。x, y是绘制位图的左上角,w, h是以像素为单位的宽度和高度。

位图数据必须使用PROGMEM指令定位在程序内存中。这是一个比较高级的函数,建议初学者稍后再来使用。有关PROGMEM用法的介绍,请参阅Arduino教程。

可以使用一些工具来生成位图数据。

  • 清理或填充屏幕

fillScreen()函数将把整个显示设置为给定的颜色,删除所有显示屏现有的内容:

void fillScreen(uint16_t color);
  1. 旋转显示

你也可以旋转你的绘图。注意,这不会旋转你已经绘制的内容,但会改变任何新绘制的坐标系统。如果你必须把你的电路板或横向显示或倒置来适应一个特定的外壳,这是非常方便的。在大多数情况下,这只需要在setup()函数中执行一次。

我们只能旋转0,90,180或270度——其他角度的在硬件上是不可能的,而且对于Arduino来说在软件上计算太费力了。

 

void setRotation(uint8_t rotation);

旋转参数可以是0、1、2或3。对于属于Arduino屏蔽的显示,旋转值0将显示设置为竖屏(高)模式,USB插孔位于右上方。旋转值2也是纵向模式,USB插孔在左下角。旋转1是横屏模式,USB插孔在右下方,而旋转3也是横屏模式,但USB插孔在左上方。

对于其他的类型的显示屏,请尝试所有的4个方向,来弄清楚他们如何结束旋转,因为对齐将会根据每个显示器有所不同,一般来说旋转是逆时针的。

当旋转时,原点(0,0)会发生变化——其思想是,它应该被安排在显示器的左上方,以使其他图形函数具有一致的意义(并与上面所有的函数描述相匹配)。

如果您需要引用屏幕的大小(它会在纵向和横向模式之间变化),请使用width()和height()。

uint16_t width(); 
uint16_t height();

每个返回对应轴的尺寸(以像素为单位),该尺寸根据显示器的当前旋转设置进行了调整。

  1. 使用字体

Adafruit GFX库的最新版本提供了使用备用字体的能力,除了内置的标准的固定大小和间隔字体之外。包括几种备用字体,而且还可以添加新的字体。

所包含的字体来自GNU FreeFont项目。字体有三种:“Serif”(让人联想到Times New Roman)、“Sans”(让人联想到Helvetica或Arial)和“Mono”(让人联想到Courier)。每种都有几种样式(粗体、斜体等)和大小可供选择。所包括的字体是位图格式,而不是可伸缩的矢量,因为它是运行在一个小型微控制器里。

位于Adafruit_GFX的“字体”文件夹中,包含的文件(截至撰写本文时)如下:

 

位于Adafruit_GFX的“字体”文件夹中,包含的文件(截至撰写本文时)如下:

FreeMono12pt7b.h		FreeSansBoldOblique12pt7b.h
FreeMono18pt7b.h		FreeSansBoldOblique18pt7b.h
FreeMono24pt7b.h		FreeSansBoldOblique24pt7b.h
FreeMono9pt7b.h			FreeSansBoldOblique9pt7b.h
FreeMonoBold12pt7b.h		FreeSansOblique12pt7b.h
FreeMonoBold18pt7b.h		FreeSansOblique18pt7b.h
FreeMonoBold24pt7b.h		FreeSansOblique24pt7b.h
FreeMonoBold9pt7b.h		FreeSansOblique9pt7b.h
FreeMonoBoldOblique12pt7b.h	FreeSerif12pt7b.h
FreeMonoBoldOblique18pt7b.h	FreeSerif18pt7b.h
FreeMonoBoldOblique24pt7b.h	FreeSerif24pt7b.h
FreeMonoBoldOblique9pt7b.h	FreeSerif9pt7b.h
FreeMonoOblique12pt7b.h		FreeSerifBold12pt7b.h
FreeMonoOblique18pt7b.h		FreeSerifBold18pt7b.h
FreeMonoOblique24pt7b.h		FreeSerifBold24pt7b.h
FreeMonoOblique9pt7b.h		FreeSerifBold9pt7b.h
FreeSans12pt7b.h		FreeSerifBoldItalic12pt7b.h
FreeSans18pt7b.h		FreeSerifBoldItalic18pt7b.h
FreeSans24pt7b.h		FreeSerifBoldItalic24pt7b.h
FreeSans9pt7b.h			FreeSerifBoldItalic9pt7b.h
FreeSansBold12pt7b.h		FreeSerifItalic12pt7b.h
FreeSansBold18pt7b.h		FreeSerifItalic18pt7b.h
FreeSansBold24pt7b.h		FreeSerifItalic24pt7b.h
FreeSansBold9pt7b.h		FreeSerifItalic9pt7b.h

每个文件名以前缀名(“FreeMono”、“FreeSerif”等)开头,然后是样式(“粗体”、“斜体”、“无”等)、点字体大小(目前提供了9、12、18和24点大小)和“7b”,表示这些文件包含7位字符(ASCII码“ ”到“~”);8位字体(支持符号和/或国际字符)尚未提供,但可能稍后提供。

  • 在Arduino示例程序中使用GFX字体

在#including 后面指示Adafruit_GFX和特定于显示的库,包括你计划在程序中使用的字体文件。例如:

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <Fonts/FreeMonoBoldOblique12pt7b.h>
#include <Fonts/FreeSerif9pt7b.h>

每种字体都会占用一些程序存储空间;较大的字体通常需要更多的空间。这是一个有限的资源(对于字体数据和所有示例代码,在Arduino Uno上最大大约为32K),所以要仔细选择。太大,代码将拒绝编译(或者在某些边缘情况下,可能会编译,但不会上传到开发板)。如果出现这种情况,使用更少或更小的字体,或使用标准的内置字体。

在这些these.h文件中有几个数据结构,包括一个主字体结构,它通常与字体文件同名(去掉 .h)。要为后续的图形操作选择一种字体,使用setFont()函数,传递该结构的地址,例如:

tft.setFont(&FreeMonoBoldOblique12pt7b);

对tft.print()的后续调用,现在将使用这种字体。以前使用内置字体的大多数其他属性(颜色、大小等)在这里也是类似的。

要返回标准的固定大小的字体,调用setFont(),传递NULL或no参数:

tft.setFont();

一些文本属性的行为与这些新字体略有不同。不想破坏与现有代码的兼容性,“经典”字体继续像以前一样工作。

例如,使用经典字体打印时,光标位置标识在字符单元格的左上角,而使用新字体时,光标位置表示后续文本的基线——最下面一行。字符的大小和宽度可能不同,而且不一定是从光标列开始的(如下所示,该字符从光标左边的一个像素开始,但其他字符可能在它的右边或上面)。

 

 

有一个新字体需要注意的“缺陷”,没有background颜色选项……你可以设置这个值,但它会被忽略。

这是有意而为之的。

背景颜色特性有时与“经典”字体一起使用,用于用新数据覆盖旧屏幕内容。这之所以有效,是因为这些字符的大小是一致的;这不适用于比例间距的字体,在这种字体中,可能会有数量不确定的字符重叠在同一区域。字符绘制函数并不是这样设置的(在AVRs上,它在内存和速度上都是禁止的,而库仍然支持它)。

使用自定义字体时,可以替换之前绘制的测试:

使用getTextBounds()确定包含字符串的最小矩形,使用fillRect()擦除该区域,然后绘制新的文本:

int16_t x1, y1; uint16_t w, h; tft.getTextBounds(string, x, y, &x1, &y1, &w, &h);

getTextBounds需要一个字符串、初始光标的X&Y位置(当前光标的位置不会被改变)以及两个有符号和两个无符号16位整数的地址。最后四个值将包含文本所覆盖区域的左上角和宽度和高度——这些可以作为参数直接传递给fillRect()。

在擦除和重绘时,将会导致文本“闪烁”,但这是不可避免的。在同一通道中绘制背景像素的旧方案只会产生一系列新的问题。

或者:

为固定大小的区域创建一个GFXcanvax1对象(屏幕外位图),在其中绘制自定义文本并使用drawBitmap()复制到屏幕上。

// In global declarations:
GFXcanvas1 canvas(128, 32); // 128x32 pixel canvas
// In code later:
canvas.println("I like cake");
tft.drawBitmap(x, y, canvas, 128, 32, foreground, background); // Copy to screen

这将是无闪烁的,但需要更多的RAM(对于上面显示的128x32像素的画布,大约512字节),所以在2K的AVR板上并不都是是可用的。Arduino Mega或任何其它32位处理器的开发板应该可以管理。

添加新字体

如果您想要创建库中没有包含的新字体尺寸,或者修改全新的字体,我们有一个命令行工具(在“fontconvert”文件夹中)可以实现此目的。它应该可以在许多类似Linux或unix的系统上工作(覆盖了Pi, Mac OS X,也许是用于Windows的Cygwin,等等)。

构建这个工具需要gcc编译器和FreeType库。大多数Linux发行版默认情况下都包含这两种内容。对于其他人,可能需要安装开发人员工具并从源代码下载和构建FreeType。然后在调用“make”之前编辑Makefile以匹配您的设置。

fontconvert需要至少两个参数:一个字体文件名(例如一个可伸缩的TrueType矢量字体)和一个大小,以点为单位(72点= 1英寸;代码假设屏幕分辨率类似于Adafruit 2.8” TFT显示)。输出应该被重定向到一个。h文件…你可以叫它任何你喜欢人名字,但应具用一定的描述性:

./fontconvert myfont.ttf 12 > myfont12pt7b.h

GNU FreeFont文件并不包含在库存储库中,但是很容易下载。或者你可以转换任何你喜欢的字体。

该文件中分配给字体结构的名称基于输入文件名和字体大小,而不是输出。这就是为什么我建议使用包含字体基名、大小和“7p”的描述性文件名。然后。h文件名和字体结构名称可以匹配。

生成的.h文件可以复制到Adafruit_GFX/Fonts文件夹中,或者您可以使用sketch→Add file…命令在Arduino示例中导入该文件作为新选项卡。

如果在字体文件夹中,请在#include文件时使用以下语法:

#include <Fonts/myfont12pt7b.h>

如果在你的sketch中有一个标签,使用以下语法:

#include "myfont12pt7b.h"

载入图像

 

 

从SD卡(或Adafruit“Express”板上的闪存芯片)加载。bmp图像是我们大多数彩色显示器的选项……尽管它不是内置在Adafruit_GFX中,必须单独安装。

Adafruit_ImageReader库处理此任务。可以通过Arduino库管理器安装(在Arduino IDE“工具”菜单中,选择“管理库…”)在搜索框中输入“imageread”,库就很容易找到。

 

当你在那里,也寻找Adafruit_SPIFlash库并安装它类似。

还需要一个库,但是不能通过库管理器安装它。SdFat库的Adafruit分支需要以. zip文件的形式下载,解压缩后用Arduino老的安装库方式安装。

链接:

提取码:l6o1

使用Adafruit_ImageReader库

使用这个库(以及上面的独立安装)的语法确实有点奇怪……这是Arduino处理库方式的副作用。我们故意没有把它放到Adafruit_GFX中,因为只要提到一个SD卡库,就会引起该库相当大的内存需求……即使一个人的程序中根本不用SD卡!大多数图形项目都是独立的,不需要从卡上引用文件…不是每个人都需要这个功能。

在Adafruit_ImageReader/examples文件夹中有几个示例程序。建议您仔细分析这些内容,了解如何在自己的项目中使用库。

它们都以几个#includes…开头

#include <Adafruit_GFX.h>         // Core graphics library
#include <Adafruit_ILI9341.h>     // Hardware-specific library
#include <SdFat.h>                // SD card & FAT filesystem library
#include <Adafruit_SPIFlash.h>    // SPI / QSPI flash library
#include <Adafruit_ImageReader.h> // Image-reading functions

在不同的示例中,其中一行可能会有所不同,这取决于它编写用来支持的显示硬件。上面我们看到它和Adafruit_ILI9341显示屏库一起使用,这些显示库需要特定的套件,FeatherWings 系例板子或转接板。其他例子参考Adafruit_HX8357, Adafruit_ST7735,或其他彩色TFT或OLED显示库…使用你有的正确硬件。

大多数示例都可以在SD卡或Adafruit“Express”板上的小型闪存驱动器上使用。初始化一个方法或另一个方法的代码略有不同,这些示例将检查USE_SD_CARD是被#定义来选择一个方法还是选择另一个方法。如果您知道您自己的项目只需要在一种或另一种类型上运行,那么您实际上只需要相应的初始化

使用SD卡时,声明以下两个全局变量:

  SdFat                SD;         // SD card filesystem
  Adafruit_ImageReader reader(SD); // Image-reader object, pass in SD filesys

对于一个flash文件系统,有一些特殊的声明来帮助我们在不同的Express板上定位flash设备,然后声明三个全局变量:

  // SPI or QSPI flash filesystem (i.e. CIRCUITPY drive)
  #if defined(__SAMD51__) || defined(NRF52840_XXAA)
    Adafruit_FlashTransport_QSPI flashTransport(PIN_QSPI_SCK, PIN_QSPI_CS,
      PIN_QSPI_IO0, PIN_QSPI_IO1, PIN_QSPI_IO2, PIN_QSPI_IO3);
  #else
    #if (SPI_INTERFACES_COUNT == 1)
      Adafruit_FlashTransport_SPI flashTransport(SS, &SPI);
    #else
      Adafruit_FlashTransport_SPI flashTransport(SS1, &SPI1);
    #endif
  #endif
  Adafruit_SPIFlash    flash(&flashTransport);
  FatFileSystem        filesys;
  Adafruit_ImageReader reader(filesys); // Image-reader, pass in flash filesys

 

“reader”对象稍后将用于访问图像加载函数。

然后…我们按照通常的方式声明一个显示对象(大多数例子中称为“tft”)…例如Arduino的2.8英寸tft触屏为:

#define SD_CS   4 // SD card select pin
#define TFT_CS 10 // TFT select pin
#define TFT_DC  9 // TFT display/command pin
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

这一切都发生在全局变量部分,甚至在setup()函数之前。

现在我们需要在setup()中做一些工作,同样,SD卡和flash文件系统是不同的……

使用SD卡时,它可能是这样的:

  if(!SD.begin(SD_CS, SD_SCK_MHZ(25))) { // ESP32 requires 25 MHz limit
    Serial.println(F("SD begin() failed"));
    for(;;); // Fatal error, do not continue
  }

这个示例提供了一些非常基本的错误处理……检查SD.begin()的返回状态,并在出现问题时向串行监视器打印一条消息。

 

使用闪存文件系统需要两个步骤:

  if(!flash.begin()) {
    Serial.println(F("flash begin() failed"));
    for(;;);
  }
  if(!filesys.begin(&flash)) {
    Serial.println(F("filesys begin() failed"));
    for(;;);
  }

 

所有其他代码现在是相同的,不管使用的是SD卡还是闪存。设置需要一些额外的步骤,但现在一切顺利……

在SD(或flash)和TFT的begin()函数被调用后,你可以调用reader.drawBMP()将一个BMP图像从卡上加载到屏幕上:

ImageReturnCode stat;
stat = reader.drawBMP("/purple.bmp", tft, 0, 0);

这里可以接收4个参数:

一个“8.3”格式的文件名(你不需要提供一个绝对路径(前面的“/”),但是在一些前沿的板(比如ESP32)上,SD库存在一些问题,所以你还是继续这样做吧,包括现在这个)。

将绘制图像的显示对象(例如“tft”)。这就是前面提到的奇怪语法,它不是tft. drawbmp(),而是reader.drawBMP(tft)。

图片左上角的X和Y坐标(不需要在屏幕范围内…库会在图片加载时剪切)。0,0将在左上角绘制图像…因此,如果图像的尺寸与屏幕尺寸匹配,它将填满整个屏幕。

这个函数返回一个ImageReturnCode类型的值,您可以忽略它,也可以使用它来提供一些诊断功能。可能的值是:

IMAGE_SUCCESS -图片加载成功(或被完全删除,仍然被认为是“成功”,因为没有错误)。

IMAGE_ERR_FILE_NOT_FOUND -无法打开请求的文件(检查拼写,确认文件确实存在于卡上,确保它符合“8.3”文件命名约定(如“filename.bmp”)。

IMAGE_ERR_FORMAT—不支持的图像格式。目前只支持未压缩的24位彩色BMPs (后续可能会添加更多)。

IMAGE_ERR_MALLOC -无法为操作分配内存(drawBMP()不会生成此错误,但其他ImageReader函数可能会)。

你可以选择调用一个函数来显示一个基本的诊断消息到串行控制台,而不是自己处理这些值:

reader.printStatus(stat);

如果你需要知道一个BMP图像的大小而不实际加载它,有bmpDimensions()函数:

int32_t width, height;
stat = reader.bmpDimensions("/parrot.bmp", &width, &height);

这个函数可以接收3个参数:

文件名,与drawBMP()函数的规则相同。

指向两个32位整数的指针。成功完成后,它们的内容将被设置为图像的宽度和高度(以像素为单位)。发生任何错误时,这些值都应该被忽略(它们没有初始化)。

这个函数返回ImageReturnCode,正如上面的drawBMP()函数所解释的那样。

 

在RAM中加载和使用图像

根据图像大小和其他因素,从SD卡加载图像到屏幕可能需要几秒钟。小的图片,可以完全装进内存,可以载入一次并重复使用。这对于经常使用的图标或精灵来说非常方便,因为它通常比转换并将图像作为数组直接嵌入到代码中要容易得多……这是一个可怕的过程。

这引入了另一个ImageReader函数和一个新的对象类型Adafruit_Image:

Adafruit_Image img;
stat = reader.loadBMP("/wales.bmp", img);

loadBMP()接受两个参数:

  • 文件名,规则与前面的函数相同。
  • 一个Adafruit_Image对象。这种类型比GFX库中一些绘图函数使用的位图稍微灵活一些。

这将返回一个ImageReturnCode,如前所述。如果映像太大而无法装入可用RAM,则返回IMAGE_ERR_MALLOC值。彩色图像每个像素需要两个字节…例如,一个100x25像素的图像需要100*25*2 = 5,000字节的RAM。

成功后,img对象将在RAM中包含映像。

loadBMP()函数仅对具有RAM比较大的微控制器有用,如Adafruit“M0”和“M4”板,或ESP32。像Arduino Uno这样的小设备就无法做到这一点。在Arduino Mega上使用非常小的图像可能可以。

加载完成后,使用img.draw()函数在屏幕上显示图像:

img.draw(tft, x, y);

这个函数有3个参数:

  • 一个显示对象(例如大多数例子中的“tft”),类似于drawBMP()的工作方式。
  • 屏幕上图像左上角的X和Y坐标,同样类似于drawBMP()。

我们使用img.draw(tft,…)而不是tft. drawrgbbitmap(…)(或Adafruit_GFX库中的其他位图绘制函数),因为我们计划在图像文件格式和类型方面增加更多的灵活性。Adafruit_Image对象“understands”已加载的图像的一些信息,并将自动调用适当的位图渲染函数,您不必自己处理每个单独的情况。

如果图片因为任何原因加载失败,im .draw()仍然可以被调用,它只是不会做任何事情。但至少这个示例程序不会崩溃。