電子墨水屏又稱電子紙, 其結構是兩片基板, 上面分佈著微小透明顆粒, 顆粒是一種帶正負電的黑色, 紅色和白色粒子密封於內部液態微膠囊. 不同顏色的帶電粒子會因施加電場的不同, 朝不同的方向運動,在顯示屏表面呈現出黑或白, 紅或白的效果, 這樣,在電子紙的表面就可以顯示出圖案和文字, 視覺效果與紙張極為類似, 不發光, 只有畫素顏色變化時(例如從黑轉到白)才耗電, 掉電後屏上畫面仍保留, 這個特性使其特別適合於在路牌, 標籤, 價籤這樣的場合使用. 但是這個特性也會帶來一些副作用, 例如長時間展示同一畫面, 會導致顯示顆粒老化, 畫面不易清除, 以及重新整理慢等情況. 在長時間不使用時, 建議清屏(白屏)後再斷電, 放置時顯示面朝上. 如果需要長時間展示一個畫面, 最好設定為每隔數小時重新整理一次, 減緩老化.
之前在合宙上買了一片1.54寸的墨水屏一直在吃灰, 這次趁點亮的機會把AIR32F103上的驅動範例給做了.
微雪可能是因為墨水屏才被大家熟知, 其實這家做了相當多的電路模組, 主要做外銷, 國內瞭解的比較少.
他們維護了一個品類眾多(45種)的墨水屏型號列表, 在 GitHub 上有一個專門的程式碼倉庫
https://github.com/waveshare/e-Paper
微雪的這個驅動庫程式碼質量還是不錯的. 裡面帶了針對 RaspberryPi, Arduino 和 STM32 的驅動, STM32的這個驅動, 用的硬體是 STM32F103ZET6, 遷移到AIR32F103很方便.
驅動庫當前支援以下的45種墨水屏型號, 從1.54寸到7.5寸, 其命名方式是 1N54 代表 1.54英寸, 如果同一尺寸有多個, 用 B, C, D, V2, V3 等字尾區分.
EPD_1IN54
EPD_1IN54B_V2
EPD_1IN54B
EPD_1IN54C
EPD_1IN64G
EPD_2IN7_V2
EPD_2IN7
EPD_2IN7B_V2
EPD_2IN7B
EPD_2IN9_V2
EPD_2IN9
EPD_2IN9B_V3
EPD_2IN9BC
EPD_2IN9D
EPD_2IN13_V2
EPD_2IN13_V3
EPD_2IN13
EPD_2IN13B_V3
EPD_2IN13B_V4
EPD_2IN13BC
EPD_2IN13D
EPD_2IN36G
EPD_2IN66
EPD_2IN66B
EPD_3IN0G
EPD_3IN7
EPD_3IN52
EPD_4IN01F
EPD_4IN2
EPD_4IN2B_V2
EPD_4IN2BC
EPD_4IN37G
EPD_5IN65F
EPD_5IN83_V2
EPD_5IN83
EPD_5IN83B_V2
EPD_5IN83BC
EPD_7IN3F
EPD_7IN3G
EPD_7IN5_HD
EPD_7IN5_V2
EPD_7IN5
EPD_7IN5B_HD
EPD_7IN5B_V2
EPD_7IN5BC
將微雪倉庫匯出, 需要的部分都在 STM32/STM32-F103ZET6/User 目錄下, 將其中程式碼部分複製到AIR32F103的庫目錄下, 形成目錄結構為
Libraries
├── AIR32F10xLib
├── CMSIS
├── Debug
├── DeviceSupport
├── EPaper
│ ├── Config # 組態檔
│ ├── e-Paper # 對應每一個型號的 .c 和 .h 檔案, 驅動的核心
│ ├── Examples # 對應每一個型號的測試範例, 都實現了 EPD_test(void) 這個方法
│ ├── Fonts # 字型, 5個英文字型, 2箇中文字型(只是少數幾個漢字)
│ └── GUI # 點線面的繪製方法
因為目的是要在 GNU GCC 下使用 Makefile 編譯, 所以有些地方需要優化一下
變成這樣的結構
├── EPaper
│ ├── EPD_Config_Template.h
│ ├── Examples
│ ├── Fonts
│ ├── GUI
│ └── Lib
進一步將每一個型號提取為宏, 然後對 Lib 和 Examples 下的每個驅動和測試 c 檔案, 增加宏判斷
#ifdef EPD_1IN54
...
#endif
這樣可以在 EPD_Config.h 中使用宏設定啟用哪一個型號, 例如對於合宙這塊1.54的屏, 只需要啟用 1N54 這個宏
/**
* Uncomment the part number to enable
*/
// #define EPD_1IN02
// #define EPD_1IN54_V2
#define EPD_1IN54
// #define EPD_1IN54B_V2
// #define EPD_1IN54B
// #define EPD_1IN54C
// #define EPD_1IN64G
...
...
// #define EPD_7IN5B_V2
// #define EPD_7IN5BC
這樣編譯時未啟用的型號, 其驅動和測試會直接跳過
編輯專案模板的 Makefile, 增加設定項, y 代表啟用墨水屏驅動
# Build with Waveshare e-paper lib, y:yes, n:no
USE_EPAPER ?= y
以及對應的編譯包含項, 這裡使用的是修改過名稱後的目錄名
ifeq ($(USE_EPAPER),y)
CDIRS += Libraries/EPaper/Lib \
Libraries/EPaper/Examples \
Libraries/EPaper/Fonts \
Libraries/EPaper/GUI
INCLUDES += Libraries/EPaper/Lib \
Libraries/EPaper/Examples \
Libraries/EPaper/Fonts \
Libraries/EPaper/GUI
endif
接線是典型的 SPI 接線方式, 和普通LCD一樣, 但是沒有背光, 增加了一個 Busy 腳
* Waveshare 1.54' E-Paper Demo
*
* AIR32 E-Paper
* - PA2 BUSY
* - PA3 CS
* - PA4 DC(Data/Command)
* - PA5 SCK/SCL
* - PA6 RES
* - PA7 SI/SDA
* - GND GND
* - 3.3V VCC
EPD_1IN54
, 將其反註釋EPD_DEBUG
設為1
, 可以開啟串列埠紀錄檔輸出
/**
* Uncomment to enable the part
*/
// #define EPD_1IN02
// #define EPD_1IN54_V2
#define EPD_1IN54
// #define EPD_1IN54B_V2
// ...
// #define EPD_7IN5BC
#define EPD_DEBUG 1
/**
* e-Paper GPIO
*/
#define EPD_RST_PIN GPIOA, GPIO_Pin_6
#define EPD_DC_PIN GPIOA, GPIO_Pin_4
#define EPD_CS_PIN GPIOA, GPIO_Pin_3
#define EPD_BUSY_PIN GPIOA, GPIO_Pin_2
/**
* GPIO read and write
*/
#define EPD_Digital_Write(_pin, _value) GPIO_WriteBit(_pin, _value == 0? Bit_RESET:Bit_SET)
#define EPD_Digital_Read(_pin) GPIO_ReadInputDataBit(_pin)
#define EPD_SPI_WriteByte(_value) SPI_TxRx(_value)
#define EPD_Delay_ms(__xms) Delay_Ms(__xms)
#endif
因為是範例專案, 就不單獨分檔案了, 都新增到 main.c.
初始化普通 GPIO, PA2是輸入, PA3, PA4, PA6 都是輸出
void APP_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_6);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
初始化SPI1, 這裡用 PA5作為SCL, PA7作為SDA
void APP_SPI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_5 | GPIO_Pin_7);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 0;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
對應 EPD_Config.h 中的 EPD_SPI_WriteByte() 宏定義, 建立 SPI 的位元組讀寫方法
uint8_t SPI_TxRx(uint8_t data)
{
uint8_t retry = 0;
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET && ++retry);
SPI_I2S_SendData(SPI1, data);
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET && ++retry);
return SPI_I2S_ReceiveData(SPI1);
}
在 main() 中初始化後直接呼叫微雪自帶的測試函數. 這個測試會寫入圖, 然後寫入文字, 區域性重新整理, 最後清屏, 進入睡眠.
int main(void)
{
//...
APP_GPIO_Config();
APP_SPI_Config();
EPD_test();
while (1);
}
原驅動庫中文使用的是 GB2312 的編碼, 而我在 Ubuntu 下肯定是不用 GBK 的, 所以會亂碼, 我把這部分都改成 UTF-8 了, 因此對應的漢字的位元組數也從2變成了3, 需要做對應的修改
fonts.h
typedef struct
{
unsigned char index[3]; //<-- 從 2 改成 3
const char matrix[MAX_HEIGHT_FONT*MAX_WIDTH_FONT/8];
} CH_CN;
GUI_Paint.c
void Paint_DrawString_CN(UWORD Xstart, UWORD Ystart, const char * pString, cFONT* font,
UWORD Color_Foreground, UWORD Color_Background)
{
//...
/* Point on the next character */
p_text += 3; //<-- 從 2 改成 3
//...
原先的字型定義方式為
/*-- 文字: 好 --*/
/*-- 微軟雅黑12; 此字型下對應的點陣為:寬x高=16x21 --*/
{"好",
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x73,0xFF,0x70,0x0F,0xFE,0x1E,
0x7E,0x3C,0x6E,0x38,0xEE,0x30,0xEF,0xFF,0xFC,0x30,0x7C,0x30,0x38,0x30,0x3E,0x30,
0x7E,0x30,0xE0,0x30,0xC1,0xF0,0x00,0x00,0x00,0x00},
這種初始化賦值應該是 ARM GCC 支援, 但是 GUN GCC 不支援, 編譯會報錯, 兩個struct成員變數不能用同一個花括號, 需要改為
/*-- 文字: 好 --*/
/*-- 微軟雅黑12; 此字型下對應的點陣為:寬x高=16x21 --*/
{
index:"好",
matrix: {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x73,0xFF,0x70,0x0F,0xFE,0x1E,
0x7E,0x3C,0x6E,0x38,0xEE,0x30,0xEF,0xFF,0xFC,0x30,0x7C,0x30,0x38,0x30,0x3E,0x30,
0x7E,0x30,0xE0,0x30,0xC1,0xF0,0x00,0x00,0x00,0x00}
},
或者下面這種形式
/*-- 文字: 好 --*/
/*-- 微軟雅黑12; 此字型下對應的點陣為:寬x高=16x21 --*/
{
"好",
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x73,0xFF,0x70,0x0F,0xFE,0x1E,
0x7E,0x3C,0x6E,0x38,0xEE,0x30,0xEF,0xFF,0xFC,0x30,0x7C,0x30,0x38,0x30,0x3E,0x30,
0x7E,0x30,0xE0,0x30,0xC1,0xF0,0x00,0x00,0x00,0x00}
},
經過以上的修改, 就可以正常編譯了.
程式碼已經提交到 GitHub 倉庫
將目錄下的檔案複製替換掉 User 目錄下的檔案, 再編輯 Makefile, 開啟 EPaper 庫就能編譯.
如果需要驅動其它型號的墨水屏, 編輯 EPD_Config.h 將#define EPD_1IN54
註釋掉, 再將需要啟用的型號取消註釋即可.