目录
一、工程链接
二、简单介绍

此前笔者购得一块超市货架用的电子价签,拆开后发现排线丝印写着"WFT0290CZ10"型号是GDEW029T5D。

墨水屏使用需注意:
-
适用于更新周期从几十秒到几分钟的应用场景
-
非常适合静态图像无需持续供电的应用
-
超过6个月存储,每3个月刷新一次显示屏
-
存储期间显示白色图案可防止无法消除的残影并保持最佳性能
墨水屏可以局部刷新也可以全屏刷新:局部刷新耗时短但多次局部刷新后可能有屏幕显示残留,需要全屏刷新一下。刷新完毕后根据需要进行turn off或者进入deep sleep模式。
笔者采用STM32F103CBT6单片机编写驱动,同时也参考网上的资料。
三、阅读手册
参考电路

引脚定义

DC引脚低代表命令,高代表数据。
RES引脚低则复位。
BUSY引脚低代表忙,高代表空闲。
BS1用于选择3线还是4线SPI。

笔者采用4线SPI通信方式,多一条D/C线。

在时钟的上升沿采样,且时钟空闲电平为低。

3线SPI的通信方式下,没有DC线,用SDA的第一个bit的高低电平来表征传输的是命令还是数据。


后面还有寄存器定义,就不赘述了。
四、开发
cubemx配置

开启SPI和相应的GPIO。

通过SPI来对墨水屏写入数据或者命令。
cpp
static void EPD_Write(EPD_WriteType type, uint8_t send)
{
tx[0] = send;
if (type == EPD_CMD)
{
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET);
}
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, tx, 1, 10);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
}
操作流程

代码流程

根据操作流程,编写初始化函数。
cpp
void EPD_Init()
{
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET);
HAL_Delay(10);
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET);
HAL_Delay(10);
EPD_BoosterSoftStart();
EPD_Config();
EPD_Clear();
EPD_Refresh();
EPD_PartMode();
}
定义一个buffer存放屏幕画面信息,一个字节有8个bit,可以表示8个像素点。
cpp
uint8_t epdBuf[EPD_WIDTH][EPD_HEIGHT / 8];
想用来显示字符,图片等都需要显示像素点,因此最核心的函数是打点函数。屏幕默认刷新方向如下。

现在要横过来显示,就要对字节的每一个bit进行操作。
cpp
#if (ROTATION == 0)
/**
* x 0 - 295
* y 0 - 127
*/
void EPD_DrawPixel(uint16_t x, uint8_t y)
{
epdBuf[x][y / 8] |= (1 << (7 - y % 8));
}
#elif (ROTATION == 180)
void EPD_DrawPixel(uint16_t x, uint8_t y)
{
x = 295 - x;
y = 127 - y;
epdBuf[x][y / 8] |= (1 << (7 - y % 8));
}
#endif
把buffer填充好,调用SPI发送。
cpp
void EPD_Refresh()
{
uint16_t i,j = 0;
EPD_Write(EPD_CMD, 0x13);
for (i = 0; i < 296; i++)
{
for (j = 0; j < 16; j++)
{
EPD_Write(EPD_DATA, ~epdBuf[i][j]);
}
}
EPD_Write(EPD_CMD, 0x12);
EPD_CheckBusy();
}
发送后墨水屏进行处理,会处于busy状态,墨水屏将busy引脚拉低,此时就不能再对墨水屏发送任何内容。笔者这里作为演示,是同步操作,读者可按需改成异步操作。
cpp
static void EPD_CheckBusy()
{
while (HAL_GPIO_ReadPin(BUSY_GPIO_Port, BUSY_Pin) == GPIO_PIN_RESET);
}
有了打点函数后就可以编写画线,填充,字符,图片函数。
cpp
/**
* x 0 - 295
* y 0 - 127
*/
void EPD_DrawChar(uint16_t x, uint16_t y, char ch, GUI_CHARINFO_EXT* info)
{
ch -= START_CHAR;
uint16_t xbyts = ((info + ch)->XSize % 8 == 0) ? (info + ch)->XSize / 8 : (info + ch)->XSize / 8 + 1;
for (uint16_t k = 0; k < (info + ch)->YSize; k++)
{
for (uint16_t i = 0; i < xbyts; i++)
{
for (uint16_t j = 0; j < 8; j++)
{
if (((info + ch)->pData[i + k * xbyts] >> j) & 0x01 == 1)
{
EPD_DrawPixel(x + 8 * i + 7 - j, y + (info + ch)->YSize - 1 - k);
}
}
}
}
}
void EPD_DrawPic(uint16_t x, uint16_t y, GUI_BITMAP* bitmap)
{
for (uint16_t k = 0; k < bitmap->YSize; k++)
{
for (uint16_t i = 0; i < bitmap->BytesPerLine; i++)
{
for (uint16_t j = 0; j < 8; j++)
{
if ((bitmap->pData[i + k * bitmap->BytesPerLine] >> j) & 0x01 == 1)
{
EPD_DrawPixel(x + 8 * i + 7 - j, y + bitmap->YSize - 1 - k);
}
}
}
}
}
void EPD_DrawStr(uint16_t x, uint16_t y, char* str, GUI_CHARINFO_EXT* info)
{
uint16_t i = 0;
while (*(str + i + 1))
{
EPD_DrawChar(x, y, *(str + i), info);
x += ((info + *(str + i) - START_CHAR)->XDist + (info + *(str + i + 1) - START_CHAR)->XPos);
i++;
}
EPD_DrawChar(x, y, *(str + i), info);
}
void EPD_DrawHLine(uint16_t xs, uint16_t xe, uint16_t y)
{
for (uint16_t i = xs; i < xe; i++)
{
EPD_DrawPixel(i, y);
}
}
void EPD_DrawVLine(uint16_t x, uint16_t ys, uint16_t ye)
{
for (uint16_t i = ys; i < ye; i++)
{
EPD_DrawPixel(x, i);
}
}
void EPD_DrawRect(uint16_t xs, uint16_t xe, uint16_t ys, uint16_t ye)
{
for (uint16_t i = ys; i < ye; i++)
{
EPD_DrawHLine(xs, xe, i);
}
}
字符和图片的取模是使用segger的工具,如果安装了HAL固件包,一般在这个路径。




中文字符是由笔者自制的上位机生成。
main函数测试
cpp
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
EPD_Init();
for (uint8_t i = 0; i < 10; i++)
{
EPD_DrawChar(170 + 12 * i, 110, '0'+ i, GUI_FontMicrosoftYaHeiUI24_CharInfo);
}
EPD_DrawStr(170, 20, "hello world", GUI_FontMicrosoftYaHeiUI24_CharInfo);
sprintf(str, "0123456789");
EPD_DrawStr(170, 0, str, GUI_FontMicrosoftYaHeiUI24_CharInfo);
sprintf(str, "3.14");
EPD_DrawStr(170, 40, str, GUI_FontMicrosoftYaHeiUI24_CharInfo);
for (uint8_t i = 0; i < 4; i++)
{
EPD_DrawChar(170 + 24 * i, 60, i + ' ', GUI_FontMicrosoftYaHeiUI22_CharInfo);
}
EPD_DrawRect(170, 190, 85, 105);
EPD_DrawRect(210, 230, 85, 105);
EPD_DrawRect(250, 270, 85, 105);
EPD_DrawChar(110, 0, '1' - '-' + ' ', GUI_FontMicrosoftYaHeiUI200_CharInfo);
EPD_DrawPic(0, 0, &bmapple_logo_2d_bmp_graphics_graphics_88386);
EPD_Refresh();
HAL_Delay(2000);
EPD_FullMode();
EPD_Clear();
EPD_Refresh();
EPD_PartMode();
for (uint8_t i = 0; i < 10; i++)
{
EPD_BufReset();
EPD_DrawChar(110, 0, i + '0' - '-' + ' ', GUI_FontMicrosoftYaHeiUI200_CharInfo);
EPD_Refresh();
HAL_Delay(1000);
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}