ESP32教學系列(九):實戰 SPI ePaper 電子紙模組

前言

上一篇我們曾提及常見的SPI周邊設備,像是Flash(快閃記憶體)、RTC(實時時鐘)與LCD(液晶顯示器),這篇要帶大家使用的是顯示設備的其中一種 —「電子紙」,我們會以微雪電子的這片2.9吋電子墨水螢幕為例,希望能提供一份可以幫助大家快速上手的使用指南。

這篇文章將會講解如何用原廠提供的範例程式測試,並以最常用的文字與圖案為例,說明如何自訂顯示的內容。

電子紙簡介

電子紙(ePaper)是一種顯示器的技術,具有與傳統紙張相似的外觀和閱讀體驗,它裡面使用了電子墨水(Electronic Ink)來顯示出文字或圖案。目前電子紙顯示的主流技術是使用電泳顯示器(electrophoretic display, EPD)技術,其原理是透過施加電場,改變帶電染色粒子在有色或透明的溶液中的排列與位置,就能在畫面上顯示出文字或圖形。

雙色電子墨水的顯示原理(圖片來源:元太科技)

與3C產品中常見的LCD、OLED或micro LED顯示器有根本上的不同,電子紙在顯示時不需要主動發光,所以幾乎不耗電。它的特性如同一般紙張,需要在外部有光源的環境下才能閱讀,就算是在大太陽下都能清晰可見。而且電子紙產生的藍光頻譜最接近一般紙張,加上成像後畫面不會閃爍的特性,幾乎是長時間閱讀最理想的選擇,這也是為什麼電子書閱讀器的螢幕都用電子紙的原因!

但電子紙的缺陷是它無法顯示豐富且鮮豔的色彩,而且與一般的液晶顯示器相比,電子紙的畫面更新速度較慢,在顯示內容需要快速變化時可能會出現延遲,也不適合用來觀看與播放影片。此外,盡管電子紙目前被大量的應用在電子書閱讀器和貨架標籤,但它在商業的應用仍相對有限。由於還沒實現大規模生產,所以電子紙的價格也會比相同尺寸的液晶顯示器貴上許多,這些主要的原因使電子紙只在特定的領域使用。

外觀

本文中使用的是採用微膠囊(microcap)電泳顯示器技術的2.9吋的黑白雙色電子紙,可視角度幾乎可以達到180度。

我們將模組與ESP32開發板放在一起,提供讀者大致的尺寸與比例參考。

這個模組是帶有驅動板的版本,也有一顆電壓準位轉換的IC,支援3.3V/5V輸入。背面在左側引出了PH2.0 8PIN的接口用來與控制器連接。通訊介面支援3-Wire 和 4-Wire SPI(預設)。購買時包裝盒內會附上圖中的轉接線。

通訊方式

下圖是電子紙通訊的時序圖,CSB其實就是CS(Chip Select),D/C是Data/Command。當CSB為低電位時選擇設備,D/C在低電位時寫入命令(command),高電位時寫入資料(data/parameter)。

從圖中也可以看出,傳輸開始和結束時,SCL的狀態都是低電位,並且訊號在SCL的上升緣進行資料取樣,因此其傳訊模式是Mode0。

SPI時序圖,由微雪電子提供(來源)

注意事項

電子紙雖然也是一種顯示裝置,但畢竟與液晶顯示器的特性不同,在使用上還是有一些限制。以下擷取官方提供的幾點注意事項;為方便理解,部分用語已修改為繁體詞彙。

  • 不能一直局部刷新螢幕,需要在做幾次局部刷新之後,對螢幕進行一次全部刷新。否則會造成顯示效果異常。
  • 注意螢幕不能長時間上電,在螢幕不刷新的時候,要將螢幕設置成睡眠模式,或者進行斷電處理。否則螢幕長時間保持高電壓狀態,會損壞膜片,無法修復。
  • 建議刷新時間間隔至少是180秒, 並且至少每24小時做一次刷新,如果長期不使用的話,要將電子紙刷白存放。
  • 避免跌落、碰撞、用力按壓螢幕。
  • 螢幕的 FPC 排線比較脆弱,請注意:不要沿屏幕垂直方向彎曲排線,避免排線被撕裂;不要反覆過度彎曲排線,避免排線斷裂;不要往螢幕正面方向彎曲排線,避免排線與面板的連接斷開。除錯研發時建議固定排線後使用。
  • 螢幕進入睡眠模式之後,會忽略發送的圖片數據,只有重新初始化才能正常刷新。
  • 如果發現製作的圖片在螢幕上顯示錯誤,建議檢查一下圖片大小設置是否正確,調換一下寬度和高度設置再試一下。

使用電子紙

下載範例程式碼

在開始之前,請先到微雪電子的官網下載範例函式庫與程式碼(點我直接下載),並將資料夾解壓縮到Arduino Library的目錄底下(C:\Users\USER\Documents\Arduino\libraries)。

硬體連接

將電子紙螢幕排線的接腳與ESP32的GPIO照著下表連接

電子紙ESP32說明
VCC3V3電源正極(3.3V電源輸入)
GNDGND電源負極
DINGPIO 14Data IN
SCLKGPIO 13時脈訊號
CSGPIO 15低電位有效
DCGPIO 27Data/Command
0: Command; 1: Data
RSTGPOP 26Reset,低電位有效
BUSYGPIO 25

接好以後可以用範例程式碼來測試螢幕是否正常。

螢幕測試

範例程式碼的位置在Arduino IDE選單的 File> Examples> waveshare-e-Paper> epd2in9_V2-demo

測試結果

成功上傳後,應該可以看到如下畫面:

顯示方向

電子紙可以自由設定旋轉角度,分別有0、90、180、270度可以設定。

每個旋轉角度的起始位置與零點座標如下:

顯示方向可以在Paint_NewImage修改,範例程式碼對圖片的顯示方向預設是270度。

Paint_NewImage(BlackImage, EPD_2IN9_V2_WIDTH, EPD_2IN9_V2_HEIGHT, 270, WHITE); // 旋轉270度

範例1: 自定義圖片

這個範例會在螢幕上顯示自定義的圖片,等待2秒鐘後會自動清除畫面,進入到睡眠模式。

首先,我們需要使用Img2Lcd這個軟體將圖片轉換成點陣圖(Bitmap)後,才能顯示在螢幕上。

Img2Lcd載點 (由微雪提供)

筆者在這裡分享使用Canva匯出指定大小圖片的方法:

  1. 找好要使用的圖片,接著到Canva新增一個296×128 像素的空白檔案,然後匯入圖片。
  1. 調整到適合的大小後,以JPG格式儲存。 PS. 不是付費會員,沒辦法用高品質下載也沒關係,因為電子紙的解析度沒這麼高 ~( ̄▽ ̄)~
  1. 下載到電腦後,使用Image2Lcd打開剛才的圖片,根據轉換結果微調亮度跟對比度。
  2. Img2Lcd左邊選單的參數要設定成垂直掃描模式,單色輸出,寬度和高度設定成296×128 ,選取顏色反轉
  1. 按下儲存,會自動開啟轉換完成的檔案。

  1. 將陣列的內容複製到Arduino 範例中的ImageData.C,替換預設變數 gImage_2in9的內容

  1. 複製下方程式,取代原有的範例程式碼
  1. 將程式上傳到開發板,就可以讓電子紙顯示圖片囉!

💡Tips: 使用電子紙顯示圖片時,圖片尺寸一定要和螢幕大小相同,否則畫面會無法正常顯示。

程式碼說明

在使用前,我們要先初始化螢幕本身

DEV_Module_Init();
EPD_2IN9_V2_Init();
EPD_2IN9_V2_Clear();

然後初始化顯示圖片的參數

UBYTE *BlackImage;  // 建立新的圖片快取(cache)
// 動態分配存放圖片的記憶體空間給BlackImage
if ((BlackImage = (UBYTE *)malloc(Imagesize)) == NULL) {
  printf("Failed to apply for black memory...\r\n");
  while (1);
}

Paint_NewImage(BlackImage, EPD_2IN9_V2_WIDTH, EPD_2IN9_V2_HEIGHT, 270, WHITE); // 建立image
Paint_SelectImage(BlackImage); // 選擇BlackImage

接著將螢幕刷白後,顯示點陣圖並延遲2秒

Paint_Clear(WHITE); // 清空螢幕
Paint_DrawBitMap(gImage_2in9); // 顯示點陣圖
EPD_2IN9_V2_Display(BlackImage); // 顯示BlackImage
DEV_Delay_ms(2000); // 延遲2000ms

重置並清除螢幕畫面,讓螢幕恢復初始狀態

EPD_2IN9_V2_Init();
EPD_2IN9_V2_Clear();

最後讓螢幕進入睡眠模式,並釋放記憶體空間

EPD_2IN9_V2_Sleep();  // 進入睡眠模式
free(BlackImage);  // 釋放記憶體空間
BlackImage = NULL;  // 設為空指標

字體大小

原廠提供的函式庫也可以顯示文字,並提供了不同大小的文字可以選擇。下圖比對了不同大小的字體與顯示效果。

原則上可以顯示簡體中文與英文,只是在實測後發現函式庫能顯示的中文字相當少,如果要顯示繁體中文,可以考慮自行製作字模匯入,或是以圖片的方式替代。

範例2: 顯示圖片與文字

下面的範例說明如何同時顯示圖片與文字,放置圖片的過程與範例1相同。

程式碼說明

範例2與範例1非常類似,運作流程就不再贅述。這裡用到了兩個新函式Paint_DrawImagePaint_DrawString_CN

Paint_DrawImage可以用來在指定位置顯示圖片。

void Paint_DrawImage(const unsigned char *image_buffer, UWORD xStart, UWORD yStart, UWORD W_Image, UWORD H_Image)

參數說明:

  • image_buffer: 指向圖片起始記憶體位置的指標
  • xStart: 圖片起始位置的x座標
  • yStart: 圖片起始位置的y座標
  • W_Image: 圖片寬度
  • H_Image: 圖片高度

Paint_DrawString_EN函式用來顯示文字,但是只能顯示英文。

另外還有一個函式Paint_DrawString_CN,可以同時顯示簡體中文與英文。

// 顯示英文
Paint_DrawString_EN(168, 50, "Your Best Choice!", &Font20, WHITE, BLACK);

// 顯示簡體中文與英文
Paint_DrawString_CN(130, 0, "你好abc", &Font12CN, BLACK, WHITE);

Paint_DrawString_EN的定義與參數如下:

以(Xpoint, Ypoint)為左上原點顯示文字。可以選擇字體大小與顏色。

void Paint_DrawString_EN(UWORD Xstart, UWORD Ystart, const char * pString,
                         sFONT* Font, UWORD Color_Foreground, UWORD Color_Background)

參數說明:

  • Xstart: 文字起始位置的x座標
  • Ystart: 文字起始位置的y座標
  • pString: 指向字串起始記憶體位置的指標
  • Font: 字體大小,型態為結構指標。可選擇font8、font12、font16、font20、font24
  • Color_Foreground: 字體顏色
  • Color_Background: 背景顏色

Paint_DrawString_CN的定義與參數如下:

以(Xpoint, Ypoint)為左上原點顯示文字。可以選擇字體大小與顏色。

void Paint_DrawString_CN(UWORD Xstart, UWORD Ystart, const char * pString, cFONT* font,
                        UWORD Color_Foreground, UWORD Color_Background)

參數說明:

  • Xstart: 文字起始位置的x座標
  • Ystart: 文字起始位置的y座標
  • pString: 指向字串起始記憶體位置的指標
  • Font: 字體大小,型態為結構指標
    • 可選擇font12CN、font24CN
  • Color_Foreground: 字體顏色
  • Color_Background: 背景顏色

其他常用的Library API

畫點

從(Xpoint, Ypoint)畫點,可以選擇顏色、點的大小與風格

void Paint_DrawPoint(UWORD Xpoint, UWORD Ypoint, UWORD Color, DOT_PIXEL Dot_Pixel, DOT_STYLE Dot_Style)

參數說明:

  • Xpoint: 點的x座標
  • Ypoint: 點的y座標
  • Color: 填充的顏色
  • Dot_Pixel: 點的大小 (DOT_PIXEL_1X1 ~ DOT_PIXEL_8X8)
  • Dot_Style 點的風格
    • DOT_FILL_AROUND 以點為中心擴大
    • DOT_FILL_RIGHTUP 向點的右上角擴大

畫一條線,從(Xstart, Ystart)到(Xend, Yend)。可以選擇顏色、線寬與線的風格

void Paint_DrawLine(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color, LINE_WIDTH Line_width, LINE_STYLE Line_Style)

參數說明:

  • Xstart: x座標起始位置
  • Ystart: y座標起始位置
  • Xend: x座標結束位置
  • Yend: y座標結束位置
  • Color: 填充的顏色
  • Line_width: 線寬,可選8種寬度 (DOT_PIXEL_1X1 ~ DOT_PIXEL_8X8)
  • Line_Style: 可選直線或虛線
    • LINE_STYLE_SOLID 直線
    • LINE_STYLE_DOTTED 虛線

畫圓

以(X_Center, Y_Center)為圓心,畫一個半徑為Radius的圓。可以選擇顏色、線寬,以及是否填滿圓心

void Paint_DrawCircle(UWORD X_Center, UWORD Y_Center, UWORD Radius, UWORD Color, DOT_PIXEL Line_width, DRAW_FILL Draw_Fill)

參數說明:

  • X_Center: 圓心的x座標
  • Y_Center: 圓心的y座標
  • Radius: 圓的半徑
  • Color: 填充的顏色
  • Line_width: 圓弧的寬度,可選8種寬度 (DOT_PIXEL_1X1 ~ DOT_PIXEL_8X8)
  • Draw_Fill: 可選擇是否填充圓的內部
    • DRAW_FILL_EMPTY 不填充
    • DRAW_FILL_FULL 填滿

顯示數字

以(Xpoint, Ypoint)為左上原點顯示數字。可以選擇字體大小與顏色

void Paint_DrawNum(UWORD Xpoint, UWORD Ypoint, int32_t Number, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background)

參數說明:

  • Xpoint: 數字的x座標
  • Ypoint: 數字的y座標
  • Number: 要顯示的數字,最大可顯示到2,147,483,647 (int32)
  • Font: 字體。可選擇font8、font12、font16、font20、font24
  • Color_Foreground: 字體顏色
  • Color_Background: 背景顏色

顯示時間

以(Xpoint, Ypoint)為左上原點顯示一段時間。可以選擇字體大小與顏色

void Paint_DrawTime(UWORD Xstart, UWORD Ystart, PAINT_TIME *pTime, sFONT* Font, UWORD Color_Background, UWORD Color_Foreground)

參數說明:

  • Xstart: 文字的x座標
  • Ystart: 文字的y座標
  • pTime: 時間
  • Font: 字體。可選擇font8、font12、font16、font20、font24
  • Color_Background: 背景顏色
  • Color_Foreground: 字體顏色

使用範例

// 宣告結構體
PAINT_TIME sPaint_time;  

// 參數設定
sPaint_time.Hour = 12;
sPaint_time.Min = 34;
sPaint_time.Sec = 56;

// 顯示時間
Paint_DrawTime(148, 80, &sPaint_time, &Font20, WHITE, BLACK);

填滿畫面

將畫面填充為特定顏色,一般作為清除螢幕使用。

void Paint_Clear(UWORD Color)

參數說明:

  • color: 要填滿的顏色

使用範例

Paint_Clear(WHITE); // 清除螢幕

填充部分畫面

將部分畫面填充為特定顏色,一般作為更新部分畫面使用。

void Paint_ClearWindows(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color)

參數說明:

  • Xstart: x座標起始位置
  • Ystart: y座標起始位置
  • Xend: x座標結束位置
  • Yend: y座標結束位置
  • Color: 填充的顏色

由於篇幅,只列出比較常用的Library API,有興趣的讀者可以參考範例程式碼,以及函式庫檔案中的說明。

補充:檔案結構說明

esp32-waveshare-epd函式庫的檔案位於src資料夾內,對此範例函式庫有興趣的讀者可以參考資料夾裡面的檔案。

檔案名稱與對應的內容的整理如下表:

類型名稱內容
資料夾utility各型號電子紙的底層驅動程式碼
檔案DEV_Config定義基本的資料型別、GPIO接腳
檔案EPD.h引入所有型號電子紙標頭檔的檔案
檔案fontx字體定義檔
檔案GUI_Paint裡面包含了可以在電子紙上繪圖的API

小結

我們在上一篇文章中說明了SPI的通訊原理,也在這一篇文章解析了這款電子紙的通訊方式,以及如何測試,並以兩個範例實際示範如何顯示自訂的內容。

在實際測試過後,這片電子紙在有光源的環境下都有不錯的顯示效果,2.9吋的大小也足夠清楚,可以用來顯示不需要一直更新資料的內容,像是標籤、名牌,或是看板等。但這片電子紙的缺點是解析度不夠高,也不支援灰階顯示,沒辦法呈現比較複雜的圖案或圖片。像是筆者曾試著顯示一部分的漫畫內容,但只能看到大致的人物輪廓,這部分略為可惜。

搭配範例程式操作下來,老實說筆者認為原廠函式庫寫得並不完整,要製作進階的功能時會有些不方便。雖然也有找到網路上其他電子紙的函式庫,但由於時間因素來不及測試,如果有用過其他函式庫的朋友也歡迎分享使用心得!