first commit
This commit is contained in:
commit
a2a0b9785a
41 files changed
+31663
No files matched your search
@@ -0,0 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(xInfo)
|
||||
@@ -0,0 +1,5 @@
|
||||
# 中文字体组件(Unifont 16×16 点阵,UTF-8 渲染引擎)
|
||||
# 字体数据由 tools/gen_font.py 自动生成
|
||||
idf_component_register(SRCS "src/ChineseFont.cpp" "src/font16cn_data.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES epdiy_bwr)
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file ChineseFont.h
|
||||
* @brief 自定义 UTF-8 中文字体系统(16×16 点阵,基于 Unifont)。
|
||||
*
|
||||
* 提供 Paint_DrawString_UTF8() 函数,支持中英文混合渲染:
|
||||
* - ASCII 字符 → 使用原有英文字体引擎
|
||||
* - 中文字符 → 查自定义 UTF-8 点阵表
|
||||
*
|
||||
* 字体数据由 tools/gen_font.py 从 Unifont hex 文件自动生成,
|
||||
* 存放在 font16cn_data.cpp 中。如需添加新字符,
|
||||
* 编辑脚本中的 CHARS 列表并重新运行即可。
|
||||
*/
|
||||
#ifndef CHINESE_FONT_H
|
||||
#define CHINESE_FONT_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/// 单个 UTF-8 中文字符的点阵数据
|
||||
struct UTF8_Char {
|
||||
uint8_t index[3]; ///< UTF-8 编码的 3 个字节
|
||||
uint8_t matrix[32]; ///< 16×16 单色位图 = 16 行 × 2 字节/行 = 32 字节
|
||||
};
|
||||
|
||||
/// UTF-8 中文字体描述
|
||||
struct UTF8_Font {
|
||||
const UTF8_Char *table; ///< 字符表(按 Unicode 排序)
|
||||
uint16_t size; ///< 字符表中的条目数
|
||||
uint16_t asciiWidth; ///< ASCII 字符宽度(像素)
|
||||
uint16_t charWidth; ///< 中文字符宽度(像素)
|
||||
uint16_t charHeight; ///< 字符高度(像素)
|
||||
};
|
||||
|
||||
/// 全局字体实例(定义在 font16cn_data.cpp 中)
|
||||
extern const UTF8_Font Font16CN;
|
||||
|
||||
/**
|
||||
* @brief 在墨水屏上绘制 UTF-8 中英文混合字符串。
|
||||
*
|
||||
* ASCII 部分使用英文字体(sFONT),中文部分查 UTF-8 字体表。
|
||||
* 不在字体表中的中文字符会被跳过(不显示,不会乱码)。
|
||||
*
|
||||
* @param x 起始 X 坐标
|
||||
* @param y 起始 Y 坐标
|
||||
* @param str UTF-8 编码的字符串
|
||||
* @param cnFont 中文字体(UTF8_Font)
|
||||
* @param fg 前景色
|
||||
* @param bg 背景色
|
||||
*/
|
||||
void Paint_DrawString_UTF8(uint16_t x, uint16_t y, const char *str,
|
||||
const UTF8_Font &cnFont,
|
||||
uint16_t fg, uint16_t bg);
|
||||
|
||||
#endif // CHINESE_FONT_H
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file ChineseFont.cpp
|
||||
* @brief UTF-8 中英文混合渲染实现。
|
||||
*
|
||||
* 核心逻辑:遍历 UTF-8 字符串,识别 ASCII 和多字节序列,
|
||||
* 分别调用英文字体引擎和自定义中文点阵渲染。
|
||||
*/
|
||||
#include "ChineseFont.h"
|
||||
#include "GUI_Paint.h"
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 在指定位置绲制单个中文字符的 16×16 点阵
|
||||
// --------------------------------------------------------------------------
|
||||
static void drawCNChar(uint16_t x, uint16_t y,
|
||||
const uint8_t *matrix, uint16_t w, uint16_t h,
|
||||
uint16_t fg, uint16_t bg) {
|
||||
// matrix 按行存储,每行 ceil(w/8) 字节,MSB 在左
|
||||
uint16_t bytesPerRow = (w + 7) / 8;
|
||||
|
||||
for (uint16_t row = 0; row < h; row++) {
|
||||
for (uint16_t col = 0; col < w; col++) {
|
||||
uint8_t byte = matrix[row * bytesPerRow + col / 8];
|
||||
bool pixel = (byte >> (7 - col % 8)) & 1;
|
||||
Paint_SetPixel(x + col, y + row, pixel ? fg : bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 在字体表中查找指定 UTF-8 编码的字符
|
||||
// --------------------------------------------------------------------------
|
||||
static const UTF8_Char *findChar(const UTF8_Font &font,
|
||||
uint8_t b0, uint8_t b1, uint8_t b2) {
|
||||
// 字体表按 Unicode 排序,可以用二分查找
|
||||
int lo = 0, hi = static_cast<int>(font.size) - 1;
|
||||
while (lo <= hi) {
|
||||
int mid = (lo + hi) / 2;
|
||||
const UTF8_Char &entry = font.table[mid];
|
||||
|
||||
// 按 3 字节逐个比较
|
||||
if (entry.index[0] < b0) { lo = mid + 1; continue; }
|
||||
if (entry.index[0] > b0) { hi = mid - 1; continue; }
|
||||
if (entry.index[1] < b1) { lo = mid + 1; continue; }
|
||||
if (entry.index[1] > b1) { hi = mid - 1; continue; }
|
||||
if (entry.index[2] < b2) { lo = mid + 1; continue; }
|
||||
if (entry.index[2] > b2) { hi = mid - 1; continue; }
|
||||
|
||||
return &entry; // 找到
|
||||
}
|
||||
return nullptr; // 未收录
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// UTF-8 中英文混合字符串渲染
|
||||
// --------------------------------------------------------------------------
|
||||
void Paint_DrawString_UTF8(uint16_t x, uint16_t y, const char *str,
|
||||
const UTF8_Font &cnFont,
|
||||
uint16_t fg, uint16_t bg) {
|
||||
uint16_t curX = x;
|
||||
const auto *p = reinterpret_cast<const uint8_t *>(str);
|
||||
|
||||
while (*p != 0) {
|
||||
if (*p <= 0x7F) {
|
||||
// ---- ASCII 字符:用英文字体引擎绘制 ----
|
||||
// 使用 Font16(与 16px 中文等高)
|
||||
Paint_DrawChar(curX, y, static_cast<char>(*p), &Font16, fg, bg);
|
||||
curX += cnFont.asciiWidth;
|
||||
p += 1;
|
||||
|
||||
} else if ((*p & 0xE0) == 0xC0) {
|
||||
// 2 字节 UTF-8(拉丁扩展等,跳过)
|
||||
p += 2;
|
||||
|
||||
} else if ((*p & 0xF0) == 0xE0) {
|
||||
// ---- 3 字节 UTF-8(CJK 汉字在此范围)----
|
||||
uint8_t b0 = p[0], b1 = p[1], b2 = p[2];
|
||||
const UTF8_Char *ch = findChar(cnFont, b0, b1, b2);
|
||||
|
||||
if (ch) {
|
||||
drawCNChar(curX, y, ch->matrix,
|
||||
cnFont.charWidth, cnFont.charHeight, fg, bg);
|
||||
}
|
||||
// 未找到的字符跳过(不显示,不会乱码)
|
||||
|
||||
curX += cnFont.charWidth;
|
||||
p += 3;
|
||||
|
||||
} else if ((*p & 0xF8) == 0xF0) {
|
||||
// 4 字节 UTF-8(emoji 等,跳过)
|
||||
p += 4;
|
||||
|
||||
} else {
|
||||
// 非法字节,跳过
|
||||
p += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,4 @@
|
||||
file(GLOB_RECURSE srcs "src/*.c" "src/*.cpp")
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_driver_gpio esp_driver_spi esp_timer driver)
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef _EPD_H_
|
||||
#define _EPD_H_
|
||||
|
||||
|
||||
#define EPD_WIDTH 400
|
||||
#define EPD_HEIGHT 300
|
||||
#define EPD_ARRAY EPD_WIDTH*EPD_HEIGHT/8
|
||||
|
||||
|
||||
//Full screen update display
|
||||
void EPD_HW_Init(void);
|
||||
void EPD_HW_Init_180(void);
|
||||
void EPD_WhiteScreen_ALL(const unsigned char* datasBW,const unsigned char* datasRW);
|
||||
void EPD_WhiteScreen_White(void);
|
||||
void EPD_WhiteScreen_Black(void);
|
||||
void EPD_DeepSleep(void);
|
||||
//Partial update display
|
||||
void EPD_SetRAMValue_BaseMap(const unsigned char* datasBW,const unsigned char* datasRW);
|
||||
void EPD_Dis_PartAll(const unsigned char * datas);
|
||||
void EPD_Dis_Part(unsigned int x_start,unsigned int y_start,const unsigned char * datas,unsigned int PART_COLUMN,unsigned int PART_LINE);
|
||||
void EPD_Dis_Part_Num(unsigned int x_startA,unsigned int y_startA,const unsigned char * datasA,
|
||||
unsigned int x_startB,unsigned int y_startB,const unsigned char * datasB,
|
||||
unsigned int x_startC,unsigned int y_startC,const unsigned char * datasC,
|
||||
unsigned int x_startD,unsigned int y_startD,const unsigned char * datasD,
|
||||
unsigned int PART_COLUMN,unsigned int PART_LINE
|
||||
);
|
||||
|
||||
//Fast full screen refresh display
|
||||
void EPD_HW_Init_Fast(void);
|
||||
void EPD_WhiteScreen_ALL_Fast(const unsigned char* datasBW,const unsigned char* datasRW);
|
||||
void EPD_WhiteScreen_White_Fast(void);
|
||||
|
||||
//GUI display
|
||||
void EPD_HW_Init_GUI(void);
|
||||
void EPD_Display(unsigned char *datasBW,unsigned char *datasRW);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef _EPD_SPI_H_
|
||||
#define _EPD_SPI_H_
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Pin Definitions for ESP32-C3
|
||||
// SPI2 Host is used for C3
|
||||
#define EPD_SCK_PIN GPIO_NUM_4
|
||||
#define EPD_MOSI_PIN GPIO_NUM_6
|
||||
#define EPD_CS_PIN GPIO_NUM_7
|
||||
#define EPD_DC_PIN GPIO_NUM_3
|
||||
#define EPD_RST_PIN GPIO_NUM_2
|
||||
#define EPD_BUSY_PIN GPIO_NUM_10
|
||||
|
||||
#define isEPD_W21_BUSY gpio_get_level(EPD_BUSY_PIN)
|
||||
#define EPD_W21_RST_0 gpio_set_level(EPD_RST_PIN, 0)
|
||||
#define EPD_W21_RST_1 gpio_set_level(EPD_RST_PIN, 1)
|
||||
#define EPD_W21_DC_0 gpio_set_level(EPD_DC_PIN, 0)
|
||||
#define EPD_W21_DC_1 gpio_set_level(EPD_DC_PIN, 1)
|
||||
#define EPD_W21_CS_0 gpio_set_level(EPD_CS_PIN, 0)
|
||||
#define EPD_W21_CS_1 gpio_set_level(EPD_CS_PIN, 1)
|
||||
|
||||
void EPD_SPI_Init(void);
|
||||
void SPI_Write(unsigned char value);
|
||||
void EPD_W21_WriteDATA(unsigned char datas);
|
||||
void EPD_W21_WriteCMD(unsigned char command);
|
||||
unsigned char EPD_W21_ReadDATA(void);
|
||||
|
||||
#define delay(ms) vTaskDelay((ms) / portTICK_PERIOD_MS)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,168 @@
|
||||
#ifndef GUI_PAINT_H
|
||||
#define GUI_PAINT_H
|
||||
|
||||
#include "fonts.h"
|
||||
#include <stdint.h>
|
||||
/**
|
||||
* Image attributes
|
||||
**/
|
||||
|
||||
|
||||
|
||||
#define UBYTE uint8_t
|
||||
#define UWORD uint16_t
|
||||
#define UDOUBLE uint32_t
|
||||
|
||||
/**
|
||||
* Image attributes
|
||||
**/
|
||||
typedef struct {
|
||||
UBYTE *Image;
|
||||
UWORD Width;
|
||||
UWORD Height;
|
||||
UWORD WidthMemory;
|
||||
UWORD HeightMemory;
|
||||
UWORD Color;
|
||||
UWORD Rotate;
|
||||
UWORD Mirror;
|
||||
UWORD WidthByte;
|
||||
UWORD HeightByte;
|
||||
UWORD Scale;
|
||||
} PAINT;
|
||||
extern PAINT Paint;
|
||||
|
||||
/**
|
||||
* Display rotate
|
||||
**/
|
||||
#define ROTATE_0 0
|
||||
#define ROTATE_90 90
|
||||
#define ROTATE_180 180
|
||||
#define ROTATE_270 270
|
||||
|
||||
/**
|
||||
* Display Flip
|
||||
**/
|
||||
typedef enum {
|
||||
MIRROR_NONE = 0x00,
|
||||
MIRROR_HORIZONTAL = 0x01,
|
||||
MIRROR_VERTICAL = 0x02,
|
||||
MIRROR_ORIGIN = 0x03,
|
||||
} MIRROR_IMAGE;
|
||||
#define MIRROR_IMAGE_DFT MIRROR_NONE
|
||||
|
||||
/**
|
||||
* image color
|
||||
**/
|
||||
|
||||
#define WHITE 0xFF
|
||||
#define BLACK 0x00
|
||||
#define RED 0x00
|
||||
// Color
|
||||
#define BLACK0 0x03
|
||||
#define WHITE0 0x00
|
||||
#define YELLOW0 0x01
|
||||
#define RED0 0x02
|
||||
|
||||
/*
|
||||
#define IMAGE_BACKGROUND WHITE
|
||||
#define FONT_FOREGROUND BLACK
|
||||
#define FONT_BACKGROUND WHITE
|
||||
*/
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
//4 Gray level
|
||||
#define GRAY1 0x03 //Blackest
|
||||
#define GRAY2 0x02
|
||||
#define GRAY3 0x01 //gray
|
||||
#define GRAY4 0x00 //white
|
||||
|
||||
/**
|
||||
* The size of the point
|
||||
**/
|
||||
typedef enum {
|
||||
DOT_PIXEL_1X1 = 1, // 1 x 1
|
||||
DOT_PIXEL_2X2 , // 2 X 2
|
||||
DOT_PIXEL_3X3 , // 3 X 3
|
||||
DOT_PIXEL_4X4 , // 4 X 4
|
||||
DOT_PIXEL_5X5 , // 5 X 5
|
||||
DOT_PIXEL_6X6 , // 6 X 6
|
||||
DOT_PIXEL_7X7 , // 7 X 7
|
||||
DOT_PIXEL_8X8 , // 8 X 8
|
||||
} DOT_PIXEL;
|
||||
#define DOT_PIXEL_DFT DOT_PIXEL_1X1 //Default dot pilex
|
||||
|
||||
/**
|
||||
* Point size fill style
|
||||
**/
|
||||
typedef enum {
|
||||
DOT_FILL_AROUND = 1, // dot pixel 1 x 1
|
||||
DOT_FILL_RIGHTUP , // dot pixel 2 X 2
|
||||
} DOT_STYLE;
|
||||
#define DOT_STYLE_DFT DOT_FILL_AROUND //Default dot pilex
|
||||
|
||||
/**
|
||||
* Line style, solid or dashed
|
||||
**/
|
||||
typedef enum {
|
||||
LINE_STYLE_SOLID = 0,
|
||||
LINE_STYLE_DOTTED,
|
||||
} LINE_STYLE;
|
||||
|
||||
/**
|
||||
* Whether the graphic is filled
|
||||
**/
|
||||
typedef enum {
|
||||
DRAW_FILL_EMPTY = 0,
|
||||
DRAW_FILL_FULL,
|
||||
} DRAW_FILL;
|
||||
|
||||
/**
|
||||
* Custom structure of a time attribute
|
||||
**/
|
||||
typedef struct {
|
||||
UWORD Year; //0000
|
||||
UBYTE Month; //1 - 12
|
||||
UBYTE Day; //1 - 30
|
||||
UBYTE Hour; //0 - 23
|
||||
UBYTE Min; //0 - 59
|
||||
UBYTE Sec; //0 - 59
|
||||
} PAINT_TIME;
|
||||
extern PAINT_TIME sPaint_time;
|
||||
|
||||
//init and Clear
|
||||
void Paint_NewImage(UBYTE *image, UWORD Width, UWORD Height, UWORD Rotate, UWORD Color);
|
||||
void Paint_SelectImage(UBYTE *image);
|
||||
void Paint_SetRotate(UWORD Rotate);
|
||||
void Paint_SetMirroring(UBYTE mirror);
|
||||
void Paint_SetPixel(UWORD Xpoint, UWORD Ypoint, UWORD Color);
|
||||
void Paint_SetScale(UBYTE scale);
|
||||
|
||||
void Paint_Clear(UWORD Color);
|
||||
void Paint_ClearWindows(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color);
|
||||
|
||||
//Drawing
|
||||
void Paint_DrawPoint(UWORD Xpoint, UWORD Ypoint, UWORD Color, DOT_PIXEL Dot_Pixel, DOT_STYLE Dot_FillWay);
|
||||
void Paint_DrawLine(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color, LINE_STYLE Line_Style, DOT_PIXEL Dot_Pixel);
|
||||
void Paint_DrawRectangle(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color, DRAW_FILL Draw_Fill, DOT_PIXEL Dot_Pixel);
|
||||
void Paint_DrawCircle(UWORD X_Center, UWORD Y_Center, UWORD Radius, UWORD Color, DRAW_FILL Draw_Fill, DOT_PIXEL Dot_Pixel);
|
||||
|
||||
//Display string
|
||||
void Paint_DrawChar(UWORD Xstart, UWORD Ystart, const char Acsii_Char, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background);
|
||||
void Paint_DrawString_EN(UWORD Xstart, UWORD Ystart, const char * pString, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background);
|
||||
void Paint_DrawString_CN(UWORD Xstart, UWORD Ystart, const char * pString, cFONT* font, UWORD Color_Foreground, UWORD Color_Background);
|
||||
void Paint_DrawNum(UWORD Xpoint, UWORD Ypoint, int32_t Nummber, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background);
|
||||
void Paint_DrawNumDecimals(UWORD Xpoint, UWORD Ypoint, double Nummber, sFONT* Font, UWORD Digit, UWORD Color_Foreground, UWORD Color_Background); // Able to display decimals
|
||||
void Paint_DrawTime(UWORD Xstart, UWORD Ystart, PAINT_TIME *pTime, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background);
|
||||
|
||||
//pic
|
||||
void Paint_DrawBitMap(const unsigned char* image_buffer);
|
||||
void Paint_DrawBitMap_Paste(const unsigned char* image_buffer, UWORD Xstart, UWORD Ystart, UWORD imageWidth, UWORD imageHeight, UBYTE flipColor);
|
||||
//void Paint_DrawBitMap_Half(const unsigned char* image_buffer, UBYTE Region);
|
||||
//void Paint_DrawBitMap_OneQuarter(const unsigned char* image_buffer, UBYTE Region);
|
||||
//void Paint_DrawBitMap_OneEighth(const unsigned char* image_buffer, UBYTE Region);
|
||||
void Paint_DrawBitMap_Block(const unsigned char* image_buffer, UBYTE Region);
|
||||
//Color setting
|
||||
void Paint_Color_Setting(void);
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,63 @@
|
||||
#ifndef FONTS_H
|
||||
#define FONTS_H
|
||||
|
||||
/*锟斤拷锟斤拷锟斤拷锟轿拷锟斤拷藕锟�24 (32x41) */
|
||||
#define MAX_HEIGHT_FONT 41
|
||||
#define MAX_WIDTH_FONT 32
|
||||
#define OFFSET_BITMAP
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ------------------------------------------------------------------*/
|
||||
#include <stdint.h>
|
||||
|
||||
//ASCII
|
||||
typedef struct _tFont
|
||||
{
|
||||
const uint8_t *table;
|
||||
uint16_t Width;
|
||||
uint16_t Height;
|
||||
|
||||
} sFONT;
|
||||
|
||||
|
||||
//GB2312
|
||||
typedef struct // 锟斤拷锟斤拷锟斤拷模锟斤拷锟捷结构
|
||||
{
|
||||
unsigned char index[2]; // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
|
||||
const char matrix[MAX_HEIGHT_FONT*MAX_WIDTH_FONT/8]; // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷
|
||||
}CH_CN;
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const CH_CN *table;
|
||||
uint16_t size;
|
||||
uint16_t ASCII_Width;
|
||||
uint16_t Width;
|
||||
uint16_t Height;
|
||||
|
||||
}cFONT;
|
||||
|
||||
extern sFONT Font24;
|
||||
extern sFONT Font20;
|
||||
extern sFONT Font16;
|
||||
extern sFONT Font12;
|
||||
extern sFONT Font8;
|
||||
|
||||
extern cFONT Font12CN;
|
||||
extern cFONT Font24CN;
|
||||
|
||||
|
||||
//锟街匡拷锟侥硷拷
|
||||
extern const CH_CN Font12CN_Table[];
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __FONTS_H */
|
||||
|
||||
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
@@ -0,0 +1,441 @@
|
||||
#include "EPD.h"
|
||||
#include <EPD_SPI.h>
|
||||
|
||||
// Delay Functions
|
||||
void delay_xms(unsigned int xms) { delay(xms); }
|
||||
|
||||
////////////////////////////////////E-paper
|
||||
///demo//////////////////////////////////////////////////////////
|
||||
// Busy function
|
||||
void Epaper_READBUSY(void) {
|
||||
delay_xms(10); // Wait for the display to assert the busy signal
|
||||
while (1) { //=1 BUSY
|
||||
if (isEPD_W21_BUSY == 0)
|
||||
break;
|
||||
vTaskDelay(10);
|
||||
}
|
||||
}
|
||||
// Full screen update initialization
|
||||
void EPD_HW_Init(void) {
|
||||
EPD_W21_RST_0; // Module reset
|
||||
delay_xms(10); // At least 10ms delay
|
||||
EPD_W21_RST_1;
|
||||
delay_xms(10); // At least 10ms delay
|
||||
|
||||
Epaper_READBUSY();
|
||||
EPD_W21_WriteCMD(0x12); // SWRESET
|
||||
Epaper_READBUSY();
|
||||
|
||||
EPD_W21_WriteCMD(0x01); // Driver output control
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
|
||||
EPD_W21_WriteCMD(0x11); // data entry mode
|
||||
EPD_W21_WriteDATA(0x01);
|
||||
|
||||
EPD_W21_WriteCMD(0x44); // set Ram-X address start/end position
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(EPD_WIDTH / 8 - 1);
|
||||
|
||||
EPD_W21_WriteCMD(0x45); // set Ram-Y address start/end position
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
|
||||
EPD_W21_WriteCMD(0x3C); // BorderWavefrom
|
||||
EPD_W21_WriteDATA(0x05);
|
||||
|
||||
EPD_W21_WriteCMD(0x18); // Read built-in temperature sensor
|
||||
EPD_W21_WriteDATA(0x80);
|
||||
|
||||
EPD_W21_WriteCMD(0x4E); // set RAM x address count to 0;
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteCMD(0x4F); // set RAM y address count to 0X199;
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
// Fast full screen update initialization
|
||||
void EPD_HW_Init_Fast(void) // 1.6s
|
||||
{
|
||||
EPD_W21_RST_0; // Module reset
|
||||
delay_xms(10); // At least 10ms delay
|
||||
EPD_W21_RST_1;
|
||||
delay_xms(10); // At least 10ms delay
|
||||
|
||||
EPD_W21_WriteCMD(0x12); // SWRESET
|
||||
Epaper_READBUSY();
|
||||
|
||||
EPD_W21_WriteCMD(0x01); // Driver output control
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
EPD_W21_WriteDATA(0x01); // mirror
|
||||
|
||||
EPD_W21_WriteCMD(0x11); // data entry mode
|
||||
EPD_W21_WriteDATA(0x01);
|
||||
|
||||
EPD_W21_WriteCMD(0x44); // set Ram-X address start/end position
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(EPD_WIDTH / 8 - 1);
|
||||
EPD_W21_WriteCMD(0x45); // set Ram-Y address start/end position
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
|
||||
EPD_W21_WriteCMD(0x3C); // BorderWavefrom
|
||||
EPD_W21_WriteDATA(0x05);
|
||||
|
||||
EPD_W21_WriteCMD(0x4E); // set RAM x address count to 0;
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteCMD(0x4F); // set RAM y address count to 0X199;
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
Epaper_READBUSY();
|
||||
|
||||
EPD_W21_WriteCMD(0x18); // Read built-in temperature sensor
|
||||
EPD_W21_WriteDATA(0x80);
|
||||
|
||||
EPD_W21_WriteCMD(0x22); // Load temperature value
|
||||
EPD_W21_WriteDATA(0xB1);
|
||||
EPD_W21_WriteCMD(0x20);
|
||||
Epaper_READBUSY();
|
||||
|
||||
EPD_W21_WriteCMD(0x1A); // Write to temperature register
|
||||
EPD_W21_WriteDATA(0x5a); // 90
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
|
||||
EPD_W21_WriteCMD(0x22); // Load temperature value
|
||||
EPD_W21_WriteDATA(0x91);
|
||||
EPD_W21_WriteCMD(0x20);
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
//////////////////////////////Display Update
|
||||
///Function///////////////////////////////////////////////////////
|
||||
// Full screen update update function
|
||||
void EPD_Update(void) {
|
||||
EPD_W21_WriteCMD(0x22); // Display Update Control
|
||||
EPD_W21_WriteDATA(0xF7);
|
||||
EPD_W21_WriteCMD(0x20); // Activate Display Update Sequence
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
// Fast full screen update update function
|
||||
void EPD_Update_Fast(void) {
|
||||
EPD_W21_WriteCMD(0x22); // Display Update Control
|
||||
EPD_W21_WriteDATA(0xC7);
|
||||
EPD_W21_WriteCMD(0x20); // Activate Display Update Sequence
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
// Full screen update update function (Basemap)
|
||||
void EPD_Update_BaseMap(void) {
|
||||
EPD_W21_WriteCMD(0x22); // Display Update Control
|
||||
EPD_W21_WriteDATA(0xF4);
|
||||
EPD_W21_WriteCMD(0x20); // Activate Display Update Sequence
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
// Partial update update function
|
||||
void EPD_Part_Update(void) {
|
||||
EPD_W21_WriteCMD(0x22); // Display Update Control
|
||||
EPD_W21_WriteDATA(0x1C);
|
||||
EPD_W21_WriteCMD(0x20); // Activate Display Update Sequence
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
//////////////////////////////Display Data Transfer
|
||||
///Function////////////////////////////////////////////
|
||||
// Full screen update display function
|
||||
void EPD_WhiteScreen_ALL(const unsigned char *datasBW,
|
||||
const unsigned char *datasRW) {
|
||||
unsigned int i;
|
||||
// Write Data
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(datasBW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(~datasRW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_Update();
|
||||
}
|
||||
// Fast full screen update display function
|
||||
void EPD_WhiteScreen_ALL_Fast(const unsigned char *datasBW,
|
||||
const unsigned char *datasRW) {
|
||||
unsigned int i;
|
||||
// Write Data
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(datasBW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(~datasRW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_Update_Fast();
|
||||
}
|
||||
// Clear screen display
|
||||
void EPD_WhiteScreen_White(void) {
|
||||
unsigned int i;
|
||||
// Write Data
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0xFF);
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0x00); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_Update();
|
||||
}
|
||||
// Clear screen display
|
||||
void EPD_WhiteScreen_White_Fast(void) {
|
||||
unsigned int i;
|
||||
|
||||
EPD_W21_WriteCMD(0x3c); // set border
|
||||
EPD_W21_WriteDATA(
|
||||
0x01); // 0x00=black £» 0x01=white £» 0x80=VCOM £» 0xc0=HiZ£¨default£©
|
||||
// Write Data
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0xFF);
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0x00); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_Update_Fast();
|
||||
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0xFF); // Transfer the actual displayed data
|
||||
}
|
||||
}
|
||||
// Display all black
|
||||
void EPD_WhiteScreen_Black(void) {
|
||||
unsigned int i;
|
||||
// Write Data
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0xFF);
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0xFF); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_Update();
|
||||
}
|
||||
|
||||
// Partial update of background display, this function is necessary, please do
|
||||
// not delete it!!!
|
||||
void EPD_SetRAMValue_BaseMap(const unsigned char *datasBW,
|
||||
const unsigned char *datasRW) {
|
||||
unsigned int i;
|
||||
// Write Data
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(datasBW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(~datasRW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_Update_BaseMap();
|
||||
Epaper_READBUSY();
|
||||
// Write Data (update)
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0xFF);
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(0xFF); // Transfer the actual displayed data
|
||||
}
|
||||
//////////////////////////////////////////////
|
||||
}
|
||||
// Partial update display
|
||||
void EPD_Dis_Part(unsigned int x_start, unsigned int y_start,
|
||||
const unsigned char *datas, unsigned int PART_COLUMN,
|
||||
unsigned int PART_LINE) {
|
||||
unsigned int i;
|
||||
unsigned int x_end, y_end;
|
||||
|
||||
x_start = x_start / 8; // x address start
|
||||
x_end = x_start + PART_LINE / 8 - 1; // x address end
|
||||
y_start = y_start; // Y address start
|
||||
y_end = y_start + PART_COLUMN - 1; // Y address end
|
||||
|
||||
EPD_W21_WriteCMD(0x44); // set RAM x address start/end
|
||||
EPD_W21_WriteDATA(x_start); // x address start
|
||||
EPD_W21_WriteDATA(x_end); // y address end
|
||||
EPD_W21_WriteCMD(0x45); // set RAM y address start/end
|
||||
EPD_W21_WriteDATA(y_start % 256); // y address start2
|
||||
EPD_W21_WriteDATA(y_start / 256); // y address start1
|
||||
EPD_W21_WriteDATA(y_end % 256); // y address end2
|
||||
EPD_W21_WriteDATA(y_end / 256); // y address end1
|
||||
|
||||
EPD_W21_WriteCMD(0x4E); // set RAM x address count to 0;
|
||||
EPD_W21_WriteDATA(x_start); // x start address
|
||||
EPD_W21_WriteCMD(0x4F); // set RAM y address count to 0X127;
|
||||
EPD_W21_WriteDATA(y_start % 256); // y address start2
|
||||
EPD_W21_WriteDATA(y_start / 256); // y address start1
|
||||
|
||||
EPD_W21_WriteCMD(0x24); // Write Black and White image to RAM
|
||||
for (i = 0; i < PART_COLUMN * PART_LINE / 8; i++) {
|
||||
EPD_W21_WriteDATA(datas[i]);
|
||||
}
|
||||
EPD_Part_Update();
|
||||
}
|
||||
|
||||
// Deep sleep function
|
||||
void EPD_DeepSleep(void) {
|
||||
EPD_W21_WriteCMD(0x10); // Enter deep sleep
|
||||
EPD_W21_WriteDATA(0x01);
|
||||
delay_xms(100);
|
||||
}
|
||||
|
||||
// Partial update write address and data
|
||||
void EPD_Dis_Part_RAM(unsigned int x_start, unsigned int y_start,
|
||||
const unsigned char *datas, unsigned int PART_COLUMN,
|
||||
unsigned int PART_LINE) {
|
||||
unsigned int i;
|
||||
unsigned int x_end, y_end;
|
||||
|
||||
x_start = x_start / 8; // x address start
|
||||
x_end = x_start + PART_LINE / 8 - 1; // x address end
|
||||
|
||||
y_start = y_start - 1; // Y address start
|
||||
y_end = y_start + PART_COLUMN - 1; // Y address end
|
||||
|
||||
EPD_W21_WriteCMD(0x44); // set RAM x address start/end
|
||||
EPD_W21_WriteDATA(x_start); // x address start
|
||||
EPD_W21_WriteDATA(x_end); // y address end
|
||||
EPD_W21_WriteCMD(0x45); // set RAM y address start/end
|
||||
EPD_W21_WriteDATA(y_start % 256); // y address start2
|
||||
EPD_W21_WriteDATA(y_start / 256); // y address start1
|
||||
EPD_W21_WriteDATA(y_end % 256); // y address end2
|
||||
EPD_W21_WriteDATA(y_end / 256); // y address end1
|
||||
|
||||
EPD_W21_WriteCMD(0x4E); // set RAM x address count to 0;
|
||||
EPD_W21_WriteDATA(x_start); // x start address
|
||||
EPD_W21_WriteCMD(0x4F); // set RAM y address count to 0X127;
|
||||
EPD_W21_WriteDATA(y_start % 256); // y address start2
|
||||
EPD_W21_WriteDATA(y_start / 256); // y address start1
|
||||
|
||||
EPD_W21_WriteCMD(0x24); // Write Black and White image to RAM
|
||||
for (i = 0; i < PART_COLUMN * PART_LINE / 8; i++) {
|
||||
EPD_W21_WriteDATA(datas[i]);
|
||||
}
|
||||
}
|
||||
// Num display
|
||||
void EPD_Dis_Part_Num(unsigned int x_startA, unsigned int y_startA,
|
||||
const unsigned char *datasA, unsigned int x_startB,
|
||||
unsigned int y_startB, const unsigned char *datasB,
|
||||
unsigned int x_startC, unsigned int y_startC,
|
||||
const unsigned char *datasC, unsigned int x_startD,
|
||||
unsigned int y_startD, const unsigned char *datasD,
|
||||
unsigned int PART_COLUMN, unsigned int PART_LINE) {
|
||||
EPD_Dis_Part_RAM(x_startA, y_startA, datasA, PART_COLUMN, PART_LINE);
|
||||
EPD_Dis_Part_RAM(x_startB, y_startB, datasB, PART_COLUMN, PART_LINE);
|
||||
EPD_Dis_Part_RAM(x_startC, y_startC, datasC, PART_COLUMN, PART_LINE);
|
||||
EPD_Dis_Part_RAM(x_startD, y_startD, datasD, PART_COLUMN, PART_LINE);
|
||||
EPD_Part_Update();
|
||||
}
|
||||
|
||||
////////////////////////////////Other newly added
|
||||
///functions////////////////////////////////////////////
|
||||
// Display rotation 180 degrees initialization
|
||||
void EPD_HW_Init_180(void) {
|
||||
EPD_W21_RST_0; // Module reset
|
||||
delay_xms(10); // At least 10ms delay
|
||||
EPD_W21_RST_1;
|
||||
delay_xms(10); // At least 10ms delay
|
||||
|
||||
Epaper_READBUSY();
|
||||
EPD_W21_WriteCMD(0x12); // SWRESET
|
||||
Epaper_READBUSY();
|
||||
|
||||
EPD_W21_WriteCMD(0x11); // data entry mode
|
||||
EPD_W21_WriteDATA(0x02);
|
||||
|
||||
EPD_W21_WriteCMD(0x44); // set Ram-X address start/end position
|
||||
EPD_W21_WriteDATA(EPD_WIDTH / 8 - 1);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
|
||||
EPD_W21_WriteCMD(0x45); // set Ram-Y address start/end position
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
|
||||
EPD_W21_WriteCMD(0x3C); // BorderWavefrom
|
||||
EPD_W21_WriteDATA(0x01);
|
||||
EPD_W21_WriteCMD(0x18);
|
||||
EPD_W21_WriteDATA(0X80);
|
||||
|
||||
EPD_W21_WriteCMD(0x4E); // set RAM x address count to 0;
|
||||
EPD_W21_WriteDATA(EPD_WIDTH / 8 - 1);
|
||||
EPD_W21_WriteCMD(0x4F); // set RAM y address count to 0X199;
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
// GUI initialization
|
||||
void EPD_HW_Init_GUI(void) {
|
||||
EPD_W21_RST_0; // Module reset
|
||||
delay_xms(10); // At least 10ms delay
|
||||
EPD_W21_RST_1;
|
||||
delay_xms(10); // At least 10ms delay
|
||||
|
||||
Epaper_READBUSY();
|
||||
EPD_W21_WriteCMD(0x12); // SWRESET
|
||||
Epaper_READBUSY();
|
||||
|
||||
EPD_W21_WriteCMD(0x01); // Driver output control
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
EPD_W21_WriteDATA(0x01); // mirror
|
||||
|
||||
EPD_W21_WriteCMD(0x11); // data entry mode
|
||||
EPD_W21_WriteDATA(0x01);
|
||||
|
||||
EPD_W21_WriteCMD(0x44); // set Ram-X address start/end position
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(EPD_WIDTH / 8 - 1);
|
||||
EPD_W21_WriteCMD(0x45); // set Ram-Y address start/end position
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
|
||||
EPD_W21_WriteCMD(0x3C); // BorderWavefrom
|
||||
EPD_W21_WriteDATA(0x05);
|
||||
EPD_W21_WriteCMD(0x18);
|
||||
EPD_W21_WriteDATA(0X80);
|
||||
|
||||
EPD_W21_WriteCMD(0x4E); // set RAM x address count to 0;
|
||||
EPD_W21_WriteDATA(0x00);
|
||||
EPD_W21_WriteCMD(0x4F); // set RAM y address count to 0X199;
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) % 256);
|
||||
EPD_W21_WriteDATA((EPD_HEIGHT - 1) / 256);
|
||||
Epaper_READBUSY();
|
||||
}
|
||||
|
||||
// GUI display
|
||||
void EPD_Display(unsigned char *datasBW, unsigned char *datasRW) {
|
||||
unsigned int i;
|
||||
// Write Data
|
||||
EPD_W21_WriteCMD(0x24); // Transfer old data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(datasBW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_W21_WriteCMD(0x26); // Transfer new data
|
||||
for (i = 0; i < EPD_ARRAY; i++) {
|
||||
EPD_W21_WriteDATA(~datasRW[i]); // Transfer the actual displayed data
|
||||
}
|
||||
EPD_Update();
|
||||
}
|
||||
|
||||
/***********************************************************
|
||||
end file
|
||||
***********************************************************/
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "EPD_SPI.h"
|
||||
|
||||
static spi_device_handle_t spi_handle;
|
||||
static const char *TAG = "EPD_SPI";
|
||||
|
||||
void EPD_SPI_Init(void) {
|
||||
esp_err_t ret;
|
||||
|
||||
// GPIO Config for DC, RST, CS
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL<<EPD_DC_PIN) | (1ULL<<EPD_RST_PIN) | (1ULL<<EPD_CS_PIN);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// GPIO Config for BUSY
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL<<EPD_BUSY_PIN);
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
EPD_W21_CS_1;
|
||||
|
||||
// SPI Bus Config
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.miso_io_num = -1;
|
||||
buscfg.mosi_io_num = EPD_MOSI_PIN;
|
||||
buscfg.sclk_io_num = EPD_SCK_PIN;
|
||||
buscfg.quadwp_io_num = -1;
|
||||
buscfg.quadhd_io_num = -1;
|
||||
buscfg.max_transfer_sz = 32;
|
||||
|
||||
// SPI Device Config
|
||||
spi_device_interface_config_t devcfg = {};
|
||||
devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10MHz
|
||||
devcfg.mode = 0; // SPI mode 0
|
||||
devcfg.spics_io_num = -1; // Use software CS pin
|
||||
devcfg.queue_size = 7;
|
||||
|
||||
ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPI bus");
|
||||
}
|
||||
|
||||
ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to add SPI device");
|
||||
}
|
||||
}
|
||||
|
||||
void SPI_Write(unsigned char value) {
|
||||
spi_transaction_t t = {};
|
||||
t.length = 8;
|
||||
t.tx_buffer = &value;
|
||||
spi_device_polling_transmit(spi_handle, &t);
|
||||
}
|
||||
|
||||
void EPD_W21_WriteCMD(unsigned char command) {
|
||||
EPD_W21_CS_0;
|
||||
EPD_W21_DC_0;
|
||||
SPI_Write(command);
|
||||
EPD_W21_CS_1;
|
||||
}
|
||||
|
||||
void EPD_W21_WriteDATA(unsigned char datas) {
|
||||
EPD_W21_CS_0;
|
||||
EPD_W21_DC_1;
|
||||
SPI_Write(datas);
|
||||
EPD_W21_CS_1;
|
||||
}
|
||||
|
||||
unsigned char EPD_W21_ReadDATA(void) {
|
||||
// Basic stub, typically not used for most writes.
|
||||
// Software SPI for reading is complex and omitted for standard display usage.
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,969 @@
|
||||
#include "GUI_Paint.h"
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h> //memset()
|
||||
|
||||
PAINT Paint;
|
||||
|
||||
unsigned char IMAGE_BACKGROUND;
|
||||
unsigned char FONT_FOREGROUND;
|
||||
unsigned char FONT_BACKGROUND;
|
||||
|
||||
/******************************************************************************
|
||||
function: Create Image
|
||||
parameter:
|
||||
image : Pointer to the image cache
|
||||
width : The width of the picture
|
||||
Height : The height of the picture
|
||||
Color : Whether the picture is inverted
|
||||
******************************************************************************/
|
||||
void Paint_NewImage(UBYTE *image, UWORD Width, UWORD Height, UWORD Rotate,
|
||||
UWORD Color) {
|
||||
Paint.Image = NULL;
|
||||
Paint.Image = image;
|
||||
|
||||
Paint.WidthMemory = Width;
|
||||
Paint.HeightMemory = Height;
|
||||
Paint.Color = Color;
|
||||
Paint.Scale = 2;
|
||||
|
||||
Paint.WidthByte = (Width % 8 == 0) ? (Width / 8) : (Width / 8 + 1);
|
||||
Paint.HeightByte = Height;
|
||||
// printf("WidthByte = %d, HeightByte = %d\r\n", Paint.WidthByte,
|
||||
// Paint.HeightByte); printf(" EPD_WIDTH / 8 = %d\r\n", 122 / 8);
|
||||
|
||||
Paint.Rotate = Rotate;
|
||||
Paint.Mirror = MIRROR_NONE;
|
||||
|
||||
if (Rotate == ROTATE_0 || Rotate == ROTATE_180) {
|
||||
Paint.Width = Width;
|
||||
Paint.Height = Height;
|
||||
} else {
|
||||
Paint.Width = Height;
|
||||
Paint.Height = Width;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Select Image
|
||||
parameter:
|
||||
image : Pointer to the image cache
|
||||
******************************************************************************/
|
||||
void Paint_SelectImage(UBYTE *image) { Paint.Image = image; }
|
||||
|
||||
/******************************************************************************
|
||||
function: Select Image Rotate
|
||||
parameter:
|
||||
Rotate : 0,90,180,270
|
||||
******************************************************************************/
|
||||
void Paint_SetRotate(UWORD Rotate) {
|
||||
if (Rotate == ROTATE_0 || Rotate == ROTATE_90 || Rotate == ROTATE_180 ||
|
||||
Rotate == ROTATE_270) {
|
||||
// Debug("Set image Rotate %d\r\n", Rotate);
|
||||
Paint.Rotate = Rotate;
|
||||
} else {
|
||||
// Debug("rotate = 0, 90, 180, 270\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Paint_SetScale(UBYTE scale) {
|
||||
if (scale == 2) {
|
||||
Paint.Scale = scale;
|
||||
Paint.WidthByte = (Paint.WidthMemory % 8 == 0)
|
||||
? (Paint.WidthMemory / 8)
|
||||
: (Paint.WidthMemory / 8 + 1);
|
||||
} else if (scale == 4) {
|
||||
Paint.Scale = scale;
|
||||
Paint.WidthByte = (Paint.WidthMemory % 4 == 0)
|
||||
? (Paint.WidthMemory / 4)
|
||||
: (Paint.WidthMemory / 4 + 1);
|
||||
} else if (scale == 7) { // Only applicable with 5in65 e-Paper
|
||||
Paint.Scale = scale;
|
||||
Paint.WidthByte = (Paint.WidthMemory % 2 == 0)
|
||||
? (Paint.WidthMemory / 2)
|
||||
: (Paint.WidthMemory / 2 + 1);
|
||||
;
|
||||
} else {
|
||||
// Debug("Set Scale Input parameter error\r\n");
|
||||
// Debug("Scale Only support: 2 4 7\r\n");
|
||||
}
|
||||
}
|
||||
/******************************************************************************
|
||||
function: Select Image mirror
|
||||
parameter:
|
||||
mirror :Not mirror,Horizontal mirror,Vertical mirror,Origin mirror
|
||||
******************************************************************************/
|
||||
void Paint_SetMirroring(UBYTE mirror) {
|
||||
if (mirror == MIRROR_NONE || mirror == MIRROR_HORIZONTAL ||
|
||||
mirror == MIRROR_VERTICAL || mirror == MIRROR_ORIGIN) {
|
||||
// Debug("mirror image x:%s, y:%s\r\n",(mirror & 0x01)? "mirror":"none",
|
||||
// ((mirror >> 1) & 0x01)? "mirror":"none");
|
||||
Paint.Mirror = mirror;
|
||||
} else {
|
||||
// Debug("mirror should be MIRROR_NONE, MIRROR_HORIZONTAL, MIRROR_VERTICAL
|
||||
// or MIRROR_ORIGIN\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Draw Pixels
|
||||
parameter:
|
||||
Xpoint : At point X
|
||||
Ypoint : At point Y
|
||||
Color : Painted colors
|
||||
******************************************************************************/
|
||||
void Paint_SetPixel(UWORD Xpoint, UWORD Ypoint, UWORD Color) {
|
||||
UWORD X, Y;
|
||||
UDOUBLE Addr;
|
||||
UBYTE Rdata;
|
||||
if (Xpoint > Paint.Width || Ypoint > Paint.Height) {
|
||||
// Debug("Exceeding display boundaries\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Paint.Rotate) {
|
||||
case 0:
|
||||
X = Xpoint;
|
||||
Y = Ypoint;
|
||||
break;
|
||||
case 90:
|
||||
X = Paint.WidthMemory - Ypoint - 1;
|
||||
Y = Xpoint;
|
||||
break;
|
||||
case 180:
|
||||
X = Paint.WidthMemory - Xpoint - 1;
|
||||
Y = Paint.HeightMemory - Ypoint - 1;
|
||||
break;
|
||||
case 270:
|
||||
X = Ypoint;
|
||||
Y = Paint.HeightMemory - Xpoint - 1;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Paint.Mirror) {
|
||||
case MIRROR_NONE:
|
||||
break;
|
||||
case MIRROR_HORIZONTAL:
|
||||
X = Paint.WidthMemory - X - 1;
|
||||
break;
|
||||
case MIRROR_VERTICAL:
|
||||
Y = Paint.HeightMemory - Y - 1;
|
||||
break;
|
||||
case MIRROR_ORIGIN:
|
||||
X = Paint.WidthMemory - X - 1;
|
||||
Y = Paint.HeightMemory - Y - 1;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (X > Paint.WidthMemory || Y > Paint.HeightMemory) {
|
||||
// Debug("Exceeding display boundaries\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Paint.Scale == 2) {
|
||||
Addr = X / 8 + Y * Paint.WidthByte;
|
||||
Rdata = Paint.Image[Addr];
|
||||
if (Color == 0x00) // BLACK
|
||||
Paint.Image[Addr] = Rdata & ~(0x80 >> (X % 8));
|
||||
else
|
||||
Paint.Image[Addr] = Rdata | (0x80 >> (X % 8));
|
||||
} else if (Paint.Scale == 4) {
|
||||
Addr = X / 4 + Y * Paint.WidthByte;
|
||||
Color = Color % 4; // Guaranteed color scale is 4 --- 0~3
|
||||
Rdata = Paint.Image[Addr];
|
||||
|
||||
Rdata = Rdata & (~(0xC0 >> ((X % 4) * 2)));
|
||||
Paint.Image[Addr] = Rdata | ((Color << 6) >> ((X % 4) * 2));
|
||||
} else if (Paint.Scale == 7) {
|
||||
Addr = X / 2 + Y * Paint.WidthByte;
|
||||
Rdata = Paint.Image[Addr];
|
||||
Rdata = Rdata & (~(0xF0 >> ((X % 2) * 4))); // Clear first, then set value
|
||||
Paint.Image[Addr] = Rdata | ((Color << 4) >> ((X % 2) * 4));
|
||||
// printf("Add = %d ,data = %d\r\n",Addr,Rdata);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Clear the color of the picture
|
||||
parameter:
|
||||
Color : Painted colors
|
||||
******************************************************************************/
|
||||
void Paint_Clear(UWORD Color) {
|
||||
UWORD X, Y;
|
||||
UDOUBLE Addr;
|
||||
if (Paint.Scale == 2) {
|
||||
for (Y = 0; Y < Paint.HeightByte; Y++) {
|
||||
for (X = 0; X < Paint.WidthByte; X++) { // 8 pixel = 1 byte
|
||||
Addr = X + Y * Paint.WidthByte;
|
||||
Paint.Image[Addr] = Color;
|
||||
}
|
||||
}
|
||||
} else if (Paint.Scale == 4) {
|
||||
for (Y = 0; Y < Paint.HeightByte; Y++) {
|
||||
for (X = 0; X < Paint.WidthByte; X++) {
|
||||
Addr = X + Y * Paint.WidthByte;
|
||||
Paint.Image[Addr] = (Color << 6) | (Color << 4) | (Color << 2) | Color;
|
||||
}
|
||||
}
|
||||
} else if (Paint.Scale == 7) {
|
||||
for (Y = 0; Y < Paint.HeightByte; Y++) {
|
||||
for (X = 0; X < Paint.WidthByte; X++) {
|
||||
Addr = X + Y * Paint.WidthByte;
|
||||
Paint.Image[Addr] = (Color << 4) | Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Clear the color of a window
|
||||
parameter:
|
||||
Xstart : x starting point
|
||||
Ystart : Y starting point
|
||||
Xend : x end point
|
||||
Yend : y end point
|
||||
Color : Painted colors
|
||||
******************************************************************************/
|
||||
void Paint_ClearWindows(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend,
|
||||
UWORD Color) {
|
||||
UWORD X, Y;
|
||||
for (Y = Ystart; Y < Yend; Y++) {
|
||||
for (X = Xstart; X < Xend; X++) { // 8 pixel = 1 byte
|
||||
Paint_SetPixel(X, Y, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Draw Point(Xpoint, Ypoint) Fill the color
|
||||
parameter:
|
||||
Xpoint : The Xpoint coordinate of the point
|
||||
Ypoint : The Ypoint coordinate of the point
|
||||
Color : Painted color
|
||||
Dot_Pixel : point size
|
||||
Dot_Style : point Style
|
||||
******************************************************************************/
|
||||
void Paint_DrawPoint(UWORD Xpoint, UWORD Ypoint, UWORD Color,
|
||||
DOT_PIXEL Dot_Pixel, DOT_STYLE Dot_Style) {
|
||||
int16_t XDir_Num, YDir_Num;
|
||||
if (Xpoint > Paint.Width || Ypoint > Paint.Height) {
|
||||
// Debug("Paint_DrawPoint Input exceeds the normal display range\r\n");
|
||||
// printf("Xpoint = %d , Paint.Width = %d \r\n ",Xpoint,Paint.Width);
|
||||
// printf("Ypoint = %d , Paint.Height = %d \r\n ",Ypoint,Paint.Height);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Dot_Style == DOT_FILL_AROUND) {
|
||||
for (XDir_Num = 0; XDir_Num < 2 * Dot_Pixel - 1; XDir_Num++) {
|
||||
for (YDir_Num = 0; YDir_Num < 2 * Dot_Pixel - 1; YDir_Num++) {
|
||||
if (Xpoint + XDir_Num - Dot_Pixel < 0 ||
|
||||
Ypoint + YDir_Num - Dot_Pixel < 0)
|
||||
break;
|
||||
// printf("x = %d, y = %d\r\n", Xpoint + XDir_Num - Dot_Pixel, Ypoint +
|
||||
// YDir_Num - Dot_Pixel);
|
||||
Paint_SetPixel(Xpoint + XDir_Num - Dot_Pixel,
|
||||
Ypoint + YDir_Num - Dot_Pixel, Color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (XDir_Num = 0; XDir_Num < Dot_Pixel; XDir_Num++) {
|
||||
for (YDir_Num = 0; YDir_Num < Dot_Pixel; YDir_Num++) {
|
||||
Paint_SetPixel(Xpoint + XDir_Num - 1, Ypoint + YDir_Num - 1, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Draw a line of arbitrary slope
|
||||
parameter:
|
||||
Xstart :Starting Xpoint point coordinates
|
||||
Ystart :Starting Xpoint point coordinates
|
||||
Xend :End point Xpoint coordinate
|
||||
Yend :End point Ypoint coordinate
|
||||
Color :The color of the line segment
|
||||
Dot_Pixel : Line width
|
||||
Line_Style: Solid and dotted lines
|
||||
******************************************************************************/
|
||||
void Paint_DrawLine(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend,
|
||||
UWORD Color, LINE_STYLE Line_Style, DOT_PIXEL Dot_Pixel) {
|
||||
UWORD Xpoint, Ypoint;
|
||||
int dx, dy;
|
||||
int XAddway, YAddway;
|
||||
int Esp;
|
||||
char Dotted_Len;
|
||||
Paint_Color_Setting(); // Color Setting*******************/
|
||||
if (Xstart > Paint.Width || Ystart > Paint.Height || Xend > Paint.Width ||
|
||||
Yend > Paint.Height) {
|
||||
// Debug("Paint_DrawLine Input exceeds the normal display range\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Xpoint = Xstart;
|
||||
Ypoint = Ystart;
|
||||
dx = (int)Xend - (int)Xstart >= 0 ? Xend - Xstart : Xstart - Xend;
|
||||
dy = (int)Yend - (int)Ystart <= 0 ? Yend - Ystart : Ystart - Yend;
|
||||
|
||||
// Increment direction, 1 is positive, -1 is counter;
|
||||
XAddway = Xstart < Xend ? 1 : -1;
|
||||
YAddway = Ystart < Yend ? 1 : -1;
|
||||
|
||||
// Cumulative error
|
||||
Esp = dx + dy;
|
||||
Dotted_Len = 0;
|
||||
|
||||
for (;;) {
|
||||
Dotted_Len++;
|
||||
// Painted dotted line, 2 point is really virtual
|
||||
if (Line_Style == LINE_STYLE_DOTTED && Dotted_Len % 3 == 0) {
|
||||
// Debug("LINE_DOTTED\r\n");
|
||||
Paint_DrawPoint(Xpoint, Ypoint, IMAGE_BACKGROUND, Dot_Pixel,
|
||||
DOT_STYLE_DFT);
|
||||
Dotted_Len = 0;
|
||||
} else {
|
||||
Paint_DrawPoint(Xpoint, Ypoint, Color, Dot_Pixel, DOT_STYLE_DFT);
|
||||
}
|
||||
if (2 * Esp >= dy) {
|
||||
if (Xpoint == Xend)
|
||||
break;
|
||||
Esp += dy;
|
||||
Xpoint += XAddway;
|
||||
}
|
||||
if (2 * Esp <= dx) {
|
||||
if (Ypoint == Yend)
|
||||
break;
|
||||
Esp += dx;
|
||||
Ypoint += YAddway;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Draw a rectangle
|
||||
parameter:
|
||||
Xstart :Rectangular Starting Xpoint point coordinates
|
||||
Ystart :Rectangular Starting Xpoint point coordinates
|
||||
Xend :Rectangular End point Xpoint coordinate
|
||||
Yend :Rectangular End point Ypoint coordinate
|
||||
Color :The color of the Rectangular segment
|
||||
Dot_Pixel: Line width
|
||||
Draw_Fill : Whether to fill the inside of the rectangle
|
||||
******************************************************************************/
|
||||
void Paint_DrawRectangle(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend,
|
||||
UWORD Color, DRAW_FILL Draw_Fill,
|
||||
DOT_PIXEL Dot_Pixel) {
|
||||
UWORD Ypoint;
|
||||
if (Xstart > Paint.Width || Ystart > Paint.Height || Xend > Paint.Width ||
|
||||
Yend > Paint.Height) {
|
||||
// Debug("Input exceeds the normal display range\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Draw_Fill) {
|
||||
for (Ypoint = Ystart; Ypoint < Yend; Ypoint++) {
|
||||
Paint_DrawLine(Xstart, Ypoint, Xend, Ypoint, Color, LINE_STYLE_SOLID,
|
||||
Dot_Pixel);
|
||||
}
|
||||
} else {
|
||||
Paint_DrawLine(Xstart, Ystart, Xend, Ystart, Color, LINE_STYLE_SOLID,
|
||||
Dot_Pixel);
|
||||
Paint_DrawLine(Xstart, Ystart, Xstart, Yend, Color, LINE_STYLE_SOLID,
|
||||
Dot_Pixel);
|
||||
Paint_DrawLine(Xend, Yend, Xend, Ystart, Color, LINE_STYLE_SOLID,
|
||||
Dot_Pixel);
|
||||
Paint_DrawLine(Xend, Yend, Xstart, Yend, Color, LINE_STYLE_SOLID,
|
||||
Dot_Pixel);
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Use the 8-point method to draw a circle of the
|
||||
specified size at the specified position->
|
||||
parameter:
|
||||
X_Center :Center X coordinate
|
||||
Y_Center :Center Y coordinate
|
||||
Radius :circle Radius
|
||||
Color :The color of the :circle segment
|
||||
Dot_Pixel: Line width
|
||||
Draw_Fill : Whether to fill the inside of the Circle
|
||||
******************************************************************************/
|
||||
void Paint_DrawCircle(UWORD X_Center, UWORD Y_Center, UWORD Radius, UWORD Color,
|
||||
DRAW_FILL Draw_Fill, DOT_PIXEL Dot_Pixel) {
|
||||
int16_t XCurrent, YCurrent;
|
||||
int16_t sCountY;
|
||||
int16_t Esp;
|
||||
if (X_Center > Paint.Width || Y_Center >= Paint.Height) {
|
||||
// Debug("Paint_DrawCircle Input exceeds the normal display range\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw a circle from(0, R) as a starting point
|
||||
XCurrent = 0;
|
||||
YCurrent = Radius;
|
||||
|
||||
// Cumulative error,judge the next point of the logo
|
||||
Esp = 3 - (Radius << 1);
|
||||
|
||||
if (Draw_Fill == DRAW_FILL_FULL) {
|
||||
while (XCurrent <= YCurrent) { // Realistic circles
|
||||
for (sCountY = XCurrent; sCountY <= YCurrent; sCountY++) {
|
||||
Paint_DrawPoint(X_Center + XCurrent, Y_Center + sCountY, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT); // 1
|
||||
Paint_DrawPoint(X_Center - XCurrent, Y_Center + sCountY, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT); // 2
|
||||
Paint_DrawPoint(X_Center - sCountY, Y_Center + XCurrent, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT); // 3
|
||||
Paint_DrawPoint(X_Center - sCountY, Y_Center - XCurrent, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT); // 4
|
||||
Paint_DrawPoint(X_Center - XCurrent, Y_Center - sCountY, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT); // 5
|
||||
Paint_DrawPoint(X_Center + XCurrent, Y_Center - sCountY, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT); // 6
|
||||
Paint_DrawPoint(X_Center + sCountY, Y_Center - XCurrent, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT); // 7
|
||||
Paint_DrawPoint(X_Center + sCountY, Y_Center + XCurrent, Color,
|
||||
DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
}
|
||||
if (Esp < 0)
|
||||
Esp += 4 * XCurrent + 6;
|
||||
else {
|
||||
Esp += 10 + 4 * (XCurrent - YCurrent);
|
||||
YCurrent--;
|
||||
}
|
||||
XCurrent++;
|
||||
}
|
||||
} else { // Draw a hollow circle
|
||||
while (XCurrent <= YCurrent) {
|
||||
Paint_DrawPoint(X_Center + XCurrent, Y_Center + YCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 1
|
||||
Paint_DrawPoint(X_Center - XCurrent, Y_Center + YCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 2
|
||||
Paint_DrawPoint(X_Center - YCurrent, Y_Center + XCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 3
|
||||
Paint_DrawPoint(X_Center - YCurrent, Y_Center - XCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 4
|
||||
Paint_DrawPoint(X_Center - XCurrent, Y_Center - YCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 5
|
||||
Paint_DrawPoint(X_Center + XCurrent, Y_Center - YCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 6
|
||||
Paint_DrawPoint(X_Center + YCurrent, Y_Center - XCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 7
|
||||
Paint_DrawPoint(X_Center + YCurrent, Y_Center + XCurrent, Color,
|
||||
Dot_Pixel, DOT_STYLE_DFT); // 0
|
||||
|
||||
if (Esp < 0)
|
||||
Esp += 4 * XCurrent + 6;
|
||||
else {
|
||||
Esp += 10 + 4 * (XCurrent - YCurrent);
|
||||
YCurrent--;
|
||||
}
|
||||
XCurrent++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Show English characters
|
||||
parameter:
|
||||
Xpoint :X coordinate
|
||||
Ypoint :Y coordinate
|
||||
Acsii_Char :To display the English characters
|
||||
Font :A structure pointer that displays a character size
|
||||
Color_Foreground : Select the foreground color
|
||||
Color_Background : Select the background color
|
||||
******************************************************************************/
|
||||
void Paint_DrawChar(UWORD Xpoint, UWORD Ypoint, const char Acsii_Char,
|
||||
sFONT *Font, UWORD Color_Foreground,
|
||||
UWORD Color_Background) {
|
||||
UWORD Page, Column;
|
||||
uint32_t Char_Offset;
|
||||
const unsigned char *ptr;
|
||||
Paint_Color_Setting(); // Color Setting*******************/
|
||||
if (Xpoint > Paint.Width || Ypoint > Paint.Height) {
|
||||
// Debug("Paint_DrawChar Input exceeds the normal display range\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Char_Offset = (Acsii_Char - ' ') * Font->Height *
|
||||
(Font->Width / 8 + (Font->Width % 8 ? 1 : 0));
|
||||
ptr = &Font->table[Char_Offset];
|
||||
|
||||
for (Page = 0; Page < Font->Height; Page++) {
|
||||
for (Column = 0; Column < Font->Width; Column++) {
|
||||
|
||||
// To determine whether the font background color and screen background
|
||||
// color is consistent
|
||||
if (FONT_BACKGROUND ==
|
||||
Color_Background) { // this process is to speed up the scan
|
||||
if (*ptr & (0x80 >> (Column % 8)))
|
||||
Paint_SetPixel(Xpoint + Column, Ypoint + Page, Color_Foreground);
|
||||
// Paint_DrawPoint(Xpoint + Column, Ypoint + Page, Color_Foreground,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
} else {
|
||||
if (*ptr & (0x80 >> (Column % 8))) {
|
||||
Paint_SetPixel(Xpoint + Column, Ypoint + Page, Color_Foreground);
|
||||
// Paint_DrawPoint(Xpoint + Column, Ypoint + Page, Color_Foreground,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
} else {
|
||||
Paint_SetPixel(Xpoint + Column, Ypoint + Page, Color_Background);
|
||||
// Paint_DrawPoint(Xpoint + Column, Ypoint + Page, Color_Background,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
}
|
||||
}
|
||||
// One pixel is 8 bits
|
||||
if (Column % 8 == 7)
|
||||
ptr++;
|
||||
} // Write a line
|
||||
if (Font->Width % 8 != 0)
|
||||
ptr++;
|
||||
} // Write all
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Display the string
|
||||
parameter:
|
||||
Xstart :X coordinate
|
||||
Ystart :Y coordinate
|
||||
pString :The first address of the English string to be displayed
|
||||
Font :A structure pointer that displays a character size
|
||||
Color_Foreground : Select the foreground color
|
||||
Color_Background : Select the background color
|
||||
******************************************************************************/
|
||||
void Paint_DrawString_EN(UWORD Xstart, UWORD Ystart, const char *pString,
|
||||
sFONT *Font, UWORD Color_Foreground,
|
||||
UWORD Color_Background) {
|
||||
UWORD Xpoint = Xstart;
|
||||
UWORD Ypoint = Ystart;
|
||||
|
||||
if (Xstart > Paint.Width || Ystart > Paint.Height) {
|
||||
// Debug("Paint_DrawString_EN Input exceeds the normal display range\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
while (*pString != '\0') {
|
||||
// if X direction filled , reposition to(Xstart,Ypoint),Ypoint is Y
|
||||
// direction plus the Height of the character
|
||||
if ((Xpoint + Font->Width) > Paint.Width) {
|
||||
Xpoint = Xstart;
|
||||
Ypoint += Font->Height;
|
||||
}
|
||||
|
||||
// If the Y direction is full, reposition to(Xstart, Ystart)
|
||||
if ((Ypoint + Font->Height) > Paint.Height) {
|
||||
Xpoint = Xstart;
|
||||
Ypoint = Ystart;
|
||||
}
|
||||
Paint_DrawChar(Xpoint, Ypoint, *pString, Font, Color_Foreground,
|
||||
Color_Background);
|
||||
|
||||
// The next character of the address
|
||||
pString++;
|
||||
|
||||
// The next word of the abscissa increases the font of the broadband
|
||||
Xpoint += Font->Width;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Display the string
|
||||
parameter:
|
||||
Xstart :X coordinate
|
||||
Ystart :Y coordinate
|
||||
pString :The first address of the Chinese string and English
|
||||
string to be displayed
|
||||
Font :A structure pointer that displays a character size
|
||||
Color_Foreground : Select the foreground color
|
||||
Color_Background : Select the background color
|
||||
******************************************************************************/
|
||||
void Paint_DrawString_CN(UWORD Xstart, UWORD Ystart, const char *pString,
|
||||
cFONT *font, UWORD Color_Foreground,
|
||||
UWORD Color_Background) {
|
||||
const char *p_text = pString;
|
||||
int x = Xstart, y = Ystart;
|
||||
int i, j, Num;
|
||||
Paint_Color_Setting(); // Color Setting*******************/
|
||||
/* Send the string character by character on EPD */
|
||||
while (*p_text != 0) {
|
||||
if (*p_text <= 0x7F) { // ASCII < 126
|
||||
for (Num = 0; Num < font->size; Num++) {
|
||||
if (*p_text == font->table[Num].index[0]) {
|
||||
const char *ptr = &font->table[Num].matrix[0];
|
||||
|
||||
for (j = 0; j < font->Height; j++) {
|
||||
for (i = 0; i < font->Width; i++) {
|
||||
if (FONT_BACKGROUND ==
|
||||
Color_Background) { // this process is to speed up the scan
|
||||
if (*ptr & (0x80 >> (i % 8))) {
|
||||
Paint_SetPixel(x + i, y + j, Color_Foreground);
|
||||
// Paint_DrawPoint(x + i, y + j, Color_Foreground,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
}
|
||||
} else {
|
||||
if (*ptr & (0x80 >> (i % 8))) {
|
||||
Paint_SetPixel(x + i, y + j, Color_Foreground);
|
||||
// Paint_DrawPoint(x + i, y + j, Color_Foreground,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
} else {
|
||||
Paint_SetPixel(x + i, y + j, Color_Background);
|
||||
// Paint_DrawPoint(x + i, y + j, Color_Background,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
}
|
||||
}
|
||||
if (i % 8 == 7) {
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
if (font->Width % 8 != 0) {
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Point on the next character */
|
||||
p_text += 1;
|
||||
/* Decrement the column position by 16 */
|
||||
x += font->ASCII_Width;
|
||||
} else { // Chinese
|
||||
for (Num = 0; Num < font->size; Num++) {
|
||||
if ((*p_text == font->table[Num].index[0]) &&
|
||||
(*(p_text + 1) == font->table[Num].index[1])) {
|
||||
const char *ptr = &font->table[Num].matrix[0];
|
||||
|
||||
for (j = 0; j < font->Height; j++) {
|
||||
for (i = 0; i < font->Width; i++) {
|
||||
if (FONT_BACKGROUND ==
|
||||
Color_Background) { // this process is to speed up the scan
|
||||
if (*ptr & (0x80 >> (i % 8))) {
|
||||
Paint_SetPixel(x + i, y + j, Color_Foreground);
|
||||
// Paint_DrawPoint(x + i, y + j, Color_Foreground,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
}
|
||||
} else {
|
||||
if (*ptr & (0x80 >> (i % 8))) {
|
||||
Paint_SetPixel(x + i, y + j, Color_Foreground);
|
||||
// Paint_DrawPoint(x + i, y + j, Color_Foreground,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
} else {
|
||||
Paint_SetPixel(x + i, y + j, Color_Background);
|
||||
// Paint_DrawPoint(x + i, y + j, Color_Background,
|
||||
// DOT_PIXEL_DFT, DOT_STYLE_DFT);
|
||||
}
|
||||
}
|
||||
if (i % 8 == 7) {
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
if (font->Width % 8 != 0) {
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Point on the next character */
|
||||
p_text += 2;
|
||||
/* Decrement the column position by 16 */
|
||||
x += font->Width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Display nummber
|
||||
parameter:
|
||||
Xstart :X coordinate
|
||||
Ystart : Y coordinate
|
||||
Nummber : The number displayed
|
||||
Font :A structure pointer that displays a character size
|
||||
Color_Foreground : Select the foreground color
|
||||
Color_Background : Select the background color
|
||||
******************************************************************************/
|
||||
#define ARRAY_LEN 255
|
||||
void Paint_DrawNum(UWORD Xpoint, UWORD Ypoint, int32_t Nummber, sFONT *Font,
|
||||
UWORD Color_Foreground, UWORD Color_Background) {
|
||||
|
||||
int16_t Num_Bit = 0, Str_Bit = 0;
|
||||
uint8_t Str_Array[ARRAY_LEN] = {0}, Num_Array[ARRAY_LEN] = {0};
|
||||
uint8_t *pStr = Str_Array;
|
||||
|
||||
if (Xpoint > Paint.Width || Ypoint > Paint.Height) {
|
||||
// Debug("Paint_DisNum Input exceeds the normal display range\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Converts a number to a string
|
||||
do {
|
||||
Num_Array[Num_Bit] = Nummber % 10 + '0';
|
||||
Num_Bit++;
|
||||
Nummber /= 10;
|
||||
} while (Nummber);
|
||||
|
||||
// The string is inverted
|
||||
while (Num_Bit > 0) {
|
||||
Str_Array[Str_Bit] = Num_Array[Num_Bit - 1];
|
||||
Str_Bit++;
|
||||
Num_Bit--;
|
||||
}
|
||||
|
||||
// show
|
||||
Paint_DrawString_EN(Xpoint, Ypoint, (const char *)pStr, Font,
|
||||
Color_Foreground, Color_Background);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Display nummber (Able to display decimals)
|
||||
parameter:
|
||||
Xstart :X coordinate
|
||||
Ystart : Y coordinate
|
||||
Nummber : The number displayed
|
||||
Font :A structure pointer that displays a character size
|
||||
Digit : Fractional width
|
||||
Color_Foreground : Select the foreground color
|
||||
Color_Background : Select the background color
|
||||
******************************************************************************/
|
||||
void Paint_DrawNumDecimals(UWORD Xpoint, UWORD Ypoint, double Nummber,
|
||||
sFONT *Font, UWORD Digit, UWORD Color_Foreground,
|
||||
UWORD Color_Background) {
|
||||
int16_t Num_Bit = 0, Str_Bit = 0;
|
||||
uint8_t Str_Array[ARRAY_LEN] = {0}, Num_Array[ARRAY_LEN] = {0};
|
||||
uint8_t *pStr = Str_Array;
|
||||
int temp = Nummber;
|
||||
float decimals;
|
||||
uint8_t i;
|
||||
if (Xpoint > Paint.Width || Ypoint > Paint.Height) {
|
||||
// Debug("Paint_DisNum Input exceeds the normal display range\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Digit > 0) {
|
||||
decimals = Nummber - temp;
|
||||
for (i = Digit; i > 0; i--) {
|
||||
decimals *= 10;
|
||||
}
|
||||
temp = decimals;
|
||||
// Converts a number to a string
|
||||
for (i = Digit; i > 0; i--) {
|
||||
Num_Array[Num_Bit] = temp % 10 + '0';
|
||||
Num_Bit++;
|
||||
temp /= 10;
|
||||
}
|
||||
Num_Array[Num_Bit] = '.';
|
||||
Num_Bit++;
|
||||
}
|
||||
|
||||
temp = Nummber;
|
||||
// Converts a number to a string
|
||||
do {
|
||||
Num_Array[Num_Bit] = temp % 10 + '0';
|
||||
Num_Bit++;
|
||||
temp /= 10;
|
||||
} while (temp);
|
||||
|
||||
// The string is inverted
|
||||
while (Num_Bit > 0) {
|
||||
Str_Array[Str_Bit] = Num_Array[Num_Bit - 1];
|
||||
Str_Bit++;
|
||||
Num_Bit--;
|
||||
}
|
||||
|
||||
// show
|
||||
Paint_DrawString_EN(Xpoint, Ypoint, (const char *)pStr, Font,
|
||||
Color_Foreground, Color_Background);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Display time
|
||||
parameter:
|
||||
Xstart :X coordinate
|
||||
Ystart : Y coordinate
|
||||
pTime : Time-related structures
|
||||
Font :A structure pointer that displays a character size
|
||||
Color_Foreground : Select the foreground color
|
||||
Color_Background : Select the background color
|
||||
******************************************************************************/
|
||||
void Paint_DrawTime(UWORD Xstart, UWORD Ystart, PAINT_TIME *pTime, sFONT *Font,
|
||||
UWORD Color_Foreground, UWORD Color_Background) {
|
||||
uint8_t value[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
|
||||
UWORD Dx = Font->Width;
|
||||
|
||||
// Write data into the cache
|
||||
Paint_DrawChar(Xstart, Ystart, value[pTime->Hour / 10], Font,
|
||||
Color_Background, Color_Foreground);
|
||||
Paint_DrawChar(Xstart + Dx, Ystart, value[pTime->Hour % 10], Font,
|
||||
Color_Background, Color_Foreground);
|
||||
Paint_DrawChar(Xstart + Dx + Dx / 4 + Dx / 2, Ystart, ':', Font,
|
||||
Color_Background, Color_Foreground);
|
||||
Paint_DrawChar(Xstart + Dx * 2 + Dx / 2, Ystart, value[pTime->Min / 10], Font,
|
||||
Color_Background, Color_Foreground);
|
||||
Paint_DrawChar(Xstart + Dx * 3 + Dx / 2, Ystart, value[pTime->Min % 10], Font,
|
||||
Color_Background, Color_Foreground);
|
||||
Paint_DrawChar(Xstart + Dx * 4 + Dx / 2 - Dx / 4, Ystart, ':', Font,
|
||||
Color_Background, Color_Foreground);
|
||||
Paint_DrawChar(Xstart + Dx * 5, Ystart, value[pTime->Sec / 10], Font,
|
||||
Color_Background, Color_Foreground);
|
||||
Paint_DrawChar(Xstart + Dx * 6, Ystart, value[pTime->Sec % 10], Font,
|
||||
Color_Background, Color_Foreground);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: Display monochrome bitmap
|
||||
parameter:
|
||||
image_buffer :A picture data converted to a bitmap
|
||||
info:
|
||||
Use a computer to convert the image into a corresponding array,
|
||||
and then embed the array directly into Imagedata.cpp as a .c file.
|
||||
******************************************************************************/
|
||||
void Paint_DrawBitMap(const unsigned char *image_buffer) {
|
||||
UWORD x, y;
|
||||
UDOUBLE Addr = 0;
|
||||
|
||||
for (y = 0; y < Paint.HeightByte; y++) {
|
||||
for (x = 0; x < Paint.WidthByte; x++) { // 8 pixel = 1 byte
|
||||
Addr = x + y * Paint.WidthByte;
|
||||
Paint.Image[Addr] = (unsigned char)image_buffer[Addr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
function: paste monochrome bitmap to a frame buff
|
||||
parameter:
|
||||
image_buffer :A picture data converted to a bitmap
|
||||
xStart: The starting x coordinate
|
||||
yStart: The starting y coordinate
|
||||
imageWidth: Original image width
|
||||
imageHeight: Original image height
|
||||
flipColor: Whether the color is reversed
|
||||
info:
|
||||
Use this function to paste image data into a buffer
|
||||
******************************************************************************/
|
||||
void Paint_DrawBitMap_Paste(const unsigned char *image_buffer, UWORD xStart,
|
||||
UWORD yStart, UWORD imageWidth, UWORD imageHeight,
|
||||
UBYTE flipColor) {
|
||||
UBYTE color, srcImage;
|
||||
UWORD x, y;
|
||||
UWORD width = (imageWidth % 8 == 0 ? imageWidth / 8 : imageWidth / 8 + 1);
|
||||
|
||||
for (y = 0; y < imageHeight; y++) {
|
||||
for (x = 0; x < imageWidth; x++) {
|
||||
srcImage = image_buffer[y * width + x / 8];
|
||||
if (flipColor)
|
||||
color = (((srcImage << (x % 8) & 0x80) == 0) ? 1 : 0);
|
||||
else
|
||||
color = (((srcImage << (x % 8) & 0x80) == 0) ? 0 : 1);
|
||||
Paint_SetPixel(x + xStart, y + yStart, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///******************************************************************************
|
||||
// function: SDisplay half of monochrome bitmap
|
||||
// parameter:
|
||||
// Region : 1 Upper half
|
||||
// 2 Lower half
|
||||
// info:
|
||||
//******************************************************************************/
|
||||
// void Paint_DrawBitMap_Half(const unsigned char* image_buffer, UBYTE Region)
|
||||
//{
|
||||
// UWORD x, y;
|
||||
// UDOUBLE Addr = 0;
|
||||
//
|
||||
// if(Region == 1){
|
||||
// for (y = 0; y < Paint.HeightByte; y++) {
|
||||
// for (x = 0; x < Paint.WidthByte; x++)
|
||||
//{//8 pixel = 1 byte Addr = x + y * Paint.WidthByte; Paint.Image[Addr] =
|
||||
//(unsigned char)image_buffer[Addr];
|
||||
// }
|
||||
// }
|
||||
// }else{
|
||||
// for (y = 0; y < Paint.HeightByte; y++) {
|
||||
// for (x = 0; x < Paint.WidthByte; x++)
|
||||
//{//8 pixel = 1 byte Addr = x + y * Paint.WidthByte ; Paint.Image[Addr] =
|
||||
// (unsigned
|
||||
//char)image_buffer[Addr+ (Paint.HeightByte)*Paint.WidthByte];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
///******************************************************************************
|
||||
// function: SDisplay half of monochrome bitmap
|
||||
// parameter:
|
||||
// Region : 1 Upper half
|
||||
// 2 Lower half
|
||||
// info:
|
||||
//******************************************************************************/
|
||||
// void Paint_DrawBitMap_OneQuarter(const unsigned char* image_buffer, UBYTE
|
||||
// Region)
|
||||
//{
|
||||
// UWORD x, y;
|
||||
// UDOUBLE Addr = 0;
|
||||
//
|
||||
// if(Region == 1){
|
||||
// for (y = 0; y < Paint.HeightByte; y++) {
|
||||
// for (x = 0; x < Paint.WidthByte; x++)
|
||||
//{//8 pixel = 1 byte Addr = x + y * Paint.WidthByte; Paint.Image[Addr] =
|
||||
//(unsigned char)image_buffer[Addr];
|
||||
// }
|
||||
// }
|
||||
// }else if(Region == 2){
|
||||
// for (y = 0; y < Paint.HeightByte; y++) {
|
||||
// for (x = 0; x < Paint.WidthByte; x++)
|
||||
//{//8 pixel = 1 byte Addr = x + y * Paint.WidthByte ; Paint.Image[Addr] =
|
||||
// (unsigned
|
||||
//char)image_buffer[Addr+ (Paint.HeightByte)*Paint.WidthByte];
|
||||
// }
|
||||
// }
|
||||
// }else if(Region == 3){
|
||||
// for (y = 0; y < Paint.HeightByte; y++) {
|
||||
// for (x = 0; x < Paint.WidthByte; x++)
|
||||
//{//8 pixel = 1 byte Addr = x + y * Paint.WidthByte ; Paint.Image[Addr] =
|
||||
// (unsigned
|
||||
//char)image_buffer[Addr+ (Paint.HeightByte)*Paint.WidthByte*2];
|
||||
// }
|
||||
// }
|
||||
// }else if(Region == 4){
|
||||
// for (y = 0; y < Paint.HeightByte; y++) {
|
||||
// for (x = 0; x < Paint.WidthByte; x++)
|
||||
//{//8 pixel = 1 byte Addr = x + y * Paint.WidthByte ; Paint.Image[Addr] =
|
||||
// (unsigned
|
||||
//char)image_buffer[Addr+ (Paint.HeightByte)*Paint.WidthByte*3];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
void Paint_DrawBitMap_Block(const unsigned char *image_buffer, UBYTE Region) {
|
||||
UWORD x, y;
|
||||
UDOUBLE Addr = 0;
|
||||
for (y = 0; y < Paint.HeightByte; y++) {
|
||||
for (x = 0; x < Paint.WidthByte; x++) { // 8 pixel = 1 byte
|
||||
Addr = x + y * Paint.WidthByte;
|
||||
Paint.Image[Addr] = (unsigned char)
|
||||
image_buffer[Addr +
|
||||
(Paint.HeightByte) * Paint.WidthByte * (Region - 1)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Paint_Color_Setting(void) {
|
||||
if (Paint.Scale == 2) // 2-color, 3-color
|
||||
{
|
||||
IMAGE_BACKGROUND = 0xFF;
|
||||
FONT_FOREGROUND = 0x00;
|
||||
FONT_BACKGROUND = 0xFF;
|
||||
}
|
||||
if (Paint.Scale == 4) // 4-color
|
||||
{
|
||||
IMAGE_BACKGROUND = 0x00;
|
||||
FONT_FOREGROUND = 0x03;
|
||||
FONT_BACKGROUND = 0x00;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,966 @@
|
||||
#include "fonts.h"
|
||||
|
||||
//
|
||||
// Font data for Courier New 12pt
|
||||
//
|
||||
|
||||
const uint8_t Font8_Table[] =
|
||||
{
|
||||
// @0 ' ' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @8 '!' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @16 '"' (5 pixels wide)
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @24 '#' (5 pixels wide)
|
||||
0x28, // # #
|
||||
0x50, // # #
|
||||
0xF8, // #####
|
||||
0x50, // # #
|
||||
0xF8, // #####
|
||||
0x50, // # #
|
||||
0xA0, // # #
|
||||
0x00, //
|
||||
|
||||
// @32 '$' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x30, // ##
|
||||
0x60, // ##
|
||||
0x30, // ##
|
||||
0x10, // #
|
||||
0x60, // ##
|
||||
0x20, // #
|
||||
0x00, //
|
||||
|
||||
// @40 '%' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x18, // ##
|
||||
0x60, // ##
|
||||
0x10, // #
|
||||
0x10, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @48 '&' (5 pixels wide)
|
||||
0x00, //
|
||||
0x38, // ###
|
||||
0x20, // #
|
||||
0x60, // ##
|
||||
0x50, // # #
|
||||
0x78, // ####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @56 ''' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @64 '(' (5 pixels wide)
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x00, //
|
||||
|
||||
// @72 ')' (5 pixels wide)
|
||||
0x40, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x40, // #
|
||||
0x00, //
|
||||
|
||||
// @80 '*' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @88 '+' (5 pixels wide)
|
||||
0x00, //
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0xF8, // #####
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @96 ',' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
|
||||
// @104 '-' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @112 '.' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @120 '/' (5 pixels wide)
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x80, // #
|
||||
0x00, //
|
||||
|
||||
// @128 '0' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @136 '1' (5 pixels wide)
|
||||
0x60, // ##
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0xF8, // #####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @144 '2' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x40, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @152 '3' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x60, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @160 '4' (5 pixels wide)
|
||||
0x10, // #
|
||||
0x30, // ##
|
||||
0x50, // # #
|
||||
0x78, // ####
|
||||
0x10, // #
|
||||
0x38, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @168 '5' (5 pixels wide)
|
||||
0x70, // ###
|
||||
0x40, // #
|
||||
0x60, // ##
|
||||
0x10, // #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @176 '6' (5 pixels wide)
|
||||
0x30, // ##
|
||||
0x40, // #
|
||||
0x60, // ##
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x60, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @184 '7' (5 pixels wide)
|
||||
0x70, // ###
|
||||
0x50, // # #
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @192 '8' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @200 '9' (5 pixels wide)
|
||||
0x30, // ##
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x30, // ##
|
||||
0x10, // #
|
||||
0x60, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @208 ':' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @216 ';' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x10, // #
|
||||
0x00, //
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @224 '<' (5 pixels wide)
|
||||
0x00, //
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0xC0, // ##
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @232 '=' (5 pixels wide)
|
||||
0x00, //
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @240 '>' (5 pixels wide)
|
||||
0x00, //
|
||||
0x40, // #
|
||||
0x20, // #
|
||||
0x18, // ##
|
||||
0x20, // #
|
||||
0x40, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @248 '?' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @256 '@' (5 pixels wide)
|
||||
0x30, // ##
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x58, // # ##
|
||||
0x48, // # #
|
||||
0x40, // #
|
||||
0x38, // ###
|
||||
0x00, //
|
||||
|
||||
// @264 'A' (5 pixels wide)
|
||||
0x60, // ##
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x70, // ###
|
||||
0x88, // # #
|
||||
0xD8, // ## ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @272 'B' (5 pixels wide)
|
||||
0xF0, // ####
|
||||
0x48, // # #
|
||||
0x70, // ###
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0xF0, // ####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @280 'C' (5 pixels wide)
|
||||
0x70, // ###
|
||||
0x50, // # #
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @288 'D' (5 pixels wide)
|
||||
0xF0, // ####
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0xF0, // ####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @296 'E' (5 pixels wide)
|
||||
0xF8, // #####
|
||||
0x48, // # #
|
||||
0x60, // ##
|
||||
0x40, // #
|
||||
0x48, // # #
|
||||
0xF8, // #####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @304 'F' (5 pixels wide)
|
||||
0xF8, // #####
|
||||
0x48, // # #
|
||||
0x60, // ##
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0xE0, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @312 'G' (5 pixels wide)
|
||||
0x70, // ###
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x58, // # ##
|
||||
0x50, // # #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @320 'H' (5 pixels wide)
|
||||
0xE8, // ### #
|
||||
0x48, // # #
|
||||
0x78, // ####
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0xE8, // ### #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @328 'I' (5 pixels wide)
|
||||
0x70, // ###
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @336 'J' (5 pixels wide)
|
||||
0x38, // ###
|
||||
0x10, // #
|
||||
0x10, // #
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @344 'K' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0x50, // # #
|
||||
0x60, // ##
|
||||
0x70, // ###
|
||||
0x50, // # #
|
||||
0xD8, // ## ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @352 'L' (5 pixels wide)
|
||||
0xE0, // ###
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x48, // # #
|
||||
0xF8, // #####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @360 'M' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0xD8, // ## ##
|
||||
0xD8, // ## ##
|
||||
0xA8, // # # #
|
||||
0x88, // # #
|
||||
0xD8, // ## ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @368 'N' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0x68, // ## #
|
||||
0x68, // ## #
|
||||
0x58, // # ##
|
||||
0x58, // # ##
|
||||
0xE8, // ### #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @376 'O' (5 pixels wide)
|
||||
0x30, // ##
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @384 'P' (5 pixels wide)
|
||||
0xF0, // ####
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x70, // ###
|
||||
0x40, // #
|
||||
0xE0, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @392 'Q' (5 pixels wide)
|
||||
0x30, // ##
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x30, // ##
|
||||
0x18, // ##
|
||||
0x00, //
|
||||
|
||||
// @400 'R' (5 pixels wide)
|
||||
0xF0, // ####
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x70, // ###
|
||||
0x48, // # #
|
||||
0xE8, // ### #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @408 'S' (5 pixels wide)
|
||||
0x70, // ###
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x50, // # #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @416 'T' (5 pixels wide)
|
||||
0xF8, // #####
|
||||
0xA8, // # # #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @424 'U' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @432 'V' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0x88, // # #
|
||||
0x48, // # #
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @440 'W' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0x88, // # #
|
||||
0xA8, // # # #
|
||||
0xA8, // # # #
|
||||
0xA8, // # # #
|
||||
0x50, // # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @448 'X' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0xD8, // ## ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @456 'Y' (5 pixels wide)
|
||||
0xD8, // ## ##
|
||||
0x88, // # #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @464 'Z' (5 pixels wide)
|
||||
0x78, // ####
|
||||
0x48, // # #
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x48, // # #
|
||||
0x78, // ####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @472 '[' (5 pixels wide)
|
||||
0x30, // ##
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
|
||||
// @480 '\' (5 pixels wide)
|
||||
0x80, // #
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x00, //
|
||||
|
||||
// @488 ']' (5 pixels wide)
|
||||
0x60, // ##
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x60, // ##
|
||||
0x00, //
|
||||
|
||||
// @496 '^' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x50, // # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @504 '_' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xF8, // #####
|
||||
|
||||
// @512 '`' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @520 'a' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x30, // ##
|
||||
0x10, // #
|
||||
0x70, // ###
|
||||
0x78, // ####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @528 'b' (5 pixels wide)
|
||||
0xC0, // ##
|
||||
0x40, // #
|
||||
0x70, // ###
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0xF0, // ####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @536 'c' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x70, // ###
|
||||
0x40, // #
|
||||
0x40, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @544 'd' (5 pixels wide)
|
||||
0x18, // ##
|
||||
0x08, // #
|
||||
0x38, // ###
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x38, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @552 'e' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x70, // ###
|
||||
0x70, // ###
|
||||
0x40, // #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @560 'f' (5 pixels wide)
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @568 'g' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x38, // ###
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x38, // ###
|
||||
0x08, // #
|
||||
0x30, // ##
|
||||
|
||||
// @576 'h' (5 pixels wide)
|
||||
0xC0, // ##
|
||||
0x40, // #
|
||||
0x70, // ###
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0xE8, // ### #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @584 'i' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x60, // ##
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @592 'j' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x00, //
|
||||
0x70, // ###
|
||||
0x10, // #
|
||||
0x10, // #
|
||||
0x10, // #
|
||||
0x10, // #
|
||||
0x70, // ###
|
||||
|
||||
// @600 'k' (5 pixels wide)
|
||||
0xC0, // ##
|
||||
0x40, // #
|
||||
0x58, // # ##
|
||||
0x70, // ###
|
||||
0x50, // # #
|
||||
0xD8, // ## ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @608 'l' (5 pixels wide)
|
||||
0x60, // ##
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @616 'm' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xD0, // ## #
|
||||
0xA8, // # # #
|
||||
0xA8, // # # #
|
||||
0xA8, // # # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @624 'n' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xF0, // ####
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0xC8, // ## #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @632 'o' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x30, // ##
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @640 'p' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xF0, // ####
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x70, // ###
|
||||
0x40, // #
|
||||
0xE0, // ###
|
||||
|
||||
// @648 'q' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x38, // ###
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x38, // ###
|
||||
0x08, // #
|
||||
0x18, // ##
|
||||
|
||||
// @656 'r' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x78, // ####
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x70, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @664 's' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x30, // ##
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x60, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @672 't' (5 pixels wide)
|
||||
0x00, //
|
||||
0x40, // #
|
||||
0xF0, // ####
|
||||
0x40, // #
|
||||
0x48, // # #
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @680 'u' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xD8, // ## ##
|
||||
0x48, // # #
|
||||
0x48, // # #
|
||||
0x38, // ###
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @688 'v' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xC8, // ## #
|
||||
0x48, // # #
|
||||
0x30, // ##
|
||||
0x30, // ##
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @696 'w' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xD8, // ## ##
|
||||
0xA8, // # # #
|
||||
0xA8, // # # #
|
||||
0x50, // # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @704 'x' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x48, // # #
|
||||
0x30, // ##
|
||||
0x30, // ##
|
||||
0x48, // # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @712 'y' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0xD8, // ## ##
|
||||
0x50, // # #
|
||||
0x50, // # #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x60, // ##
|
||||
|
||||
// @720 'z' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x78, // ####
|
||||
0x50, // # #
|
||||
0x28, // # #
|
||||
0x78, // ####
|
||||
0x00, //
|
||||
0x00, //
|
||||
|
||||
// @728 '{' (5 pixels wide)
|
||||
0x10, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x60, // ##
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x10, // #
|
||||
0x00, //
|
||||
|
||||
// @736 '|' (5 pixels wide)
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x00, //
|
||||
|
||||
// @744 '}' (5 pixels wide)
|
||||
0x40, // #
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x30, // ##
|
||||
0x20, // #
|
||||
0x20, // #
|
||||
0x40, // #
|
||||
0x00, //
|
||||
|
||||
// @752 '~' (5 pixels wide)
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x28, // # #
|
||||
0x50, // # #
|
||||
0x00, //
|
||||
0x00, //
|
||||
0x00, //
|
||||
};
|
||||
|
||||
sFONT Font8 = {
|
||||
Font8_Table,
|
||||
5, /* Width */
|
||||
8, /* Height */
|
||||
};
|
||||
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
@@ -0,0 +1,6 @@
|
||||
# xInfo 项目技术文档
|
||||
|
||||
本文档目录收录了 `xInfo` 环境监控终端的技术架构、硬件参数、接线定义以及墨水屏刷新机制说明:
|
||||
|
||||
- [硬件规格与引脚接线说明 (Hardware Specs & Pinout)](hardware_specs.md):记录 DEPG0420 / YMS400300 屏幕型号、400×300 分辨率、24P 排线、尺寸以及与 ESP32-C3 的完整引脚接线分配表。
|
||||
- [墨水屏刷新机制与显示管理技术文档 (E-Paper Refresh Mechanism)](epaper_refresh_mechanism.md):详细介绍全量/快速刷新波形特性(三色仅全刷、黑白支持快刷)、双图层管理与自适应残影消除调度算法。
|
||||
@@ -0,0 +1,153 @@
|
||||
# xInfo 墨水屏刷新机制与显示管理技术文档
|
||||
|
||||
本文档详细介绍了 `xInfo` 环境监控终端中 **DEPG0420 / YMS400300(400×300)电子墨水屏** 的硬件特性、引脚分配、多级刷新机制、显存架构以及应用层智能自适应调度策略。
|
||||
|
||||
---
|
||||
|
||||
## 1. 硬件规格与物理特性
|
||||
|
||||
| 参数项 | 参数值 / 说明 |
|
||||
| :--- | :--- |
|
||||
| **屏幕型号** | **DEPG0420 / YMS400300** |
|
||||
| **物理尺寸** | **91 × 77 mm**(不含排线) |
|
||||
| **排线规格** | **24 Pin (24P)** |
|
||||
| **屏幕分辨率** | **400 × 300 像素** |
|
||||
| **色彩模式** | **黑 / 白 / 红 三色(BWR)** 或 纯黑白模式(BW) |
|
||||
| **快刷特性** | **三色(BWR)模式下不支持快刷(必须全量三色刷新)**;在纯黑白(BW)模式下支持快速刷新 |
|
||||
| **显示特性** | 双稳态显示(Bistable Display):断电后画面永久保持,仅在刷新瞬间消耗电量 |
|
||||
| **通信接口** | 4-Wire SPI 接口(3.3V 供电) |
|
||||
| **显存占用** | BWR 模式约 30KB(双 15KB FrameBuffer),BW 模式约 15KB |
|
||||
|
||||
### 1.1 ESP32-C3 接线定义表
|
||||
|
||||
| 墨水屏引脚 | ESP32-C3 引脚 | 功能描述 | 物理特性说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **BUSY** | **GPIO 1** | 忙碌状态输出 | 高电平表示空闲(Idle),低电平表示忙碌中(Busy) |
|
||||
| **RST** | **GPIO 2** | 硬件复位引脚 | 低电平复位,高电平正常工作 |
|
||||
| **DC** | **GPIO 3** | 数据 / 命令控制 | 低电平写命令(CMD),高电平写数据(DATA) |
|
||||
| **CS** | **GPIO 4** | SPI 片选 (SPI CS) | 低电平有效选通 |
|
||||
| **CLK (SCL)** | **GPIO 5** | SPI 时钟 (SPI SCK) | 时钟输入 |
|
||||
| **DIN (SDA)** | **GPIO 6** | SPI 数据输入 (MOSI) | 数据输入 |
|
||||
| **GND** | **GND** | 电源地 | 系统参考地 |
|
||||
| **VCC** | **3.3V** | 供电正极 | 3.3V 电源 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 刷新机制三层架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 3. 业务调度层 (App.cpp) │
|
||||
│ - 数据变化检测 (跳过无意义刷新) │
|
||||
│ - 红色图层变更指纹检测 │
|
||||
│ - 连续快刷计数与周期性残影消除算法 │
|
||||
└──────────────────────────────┬──────────────────────────────┘
|
||||
│ 调度决策
|
||||
┌──────────────────────────────▼──────────────────────────────┐
|
||||
│ 2. 显示管理层 (DisplayManager.cpp) │
|
||||
│ - 400×300 区域布局与组件排版 │
|
||||
│ - 双图层/单图层显存分配与色彩切换 (switchColor) │
|
||||
│ - 中英文矢量/点阵字库渲染 (Unifont 16×16) │
|
||||
└──────────────────────────────┬──────────────────────────────┘
|
||||
│ 驱动调用
|
||||
┌──────────────────────────────▼──────────────────────────────┐
|
||||
│ 1. 硬件驱动层 (epdiy_bwr: EPD.cpp / EPD_SPI.cpp) │
|
||||
│ - 全量刷新波形 (Full Refresh Waveform, ~12-15s) │
|
||||
│ - 快速刷新波形 (Fast Refresh Waveform, ~1.5s, 仅黑白) │
|
||||
│ - 硬件控制器深度休眠 (Deep Sleep Command 0x10) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 底层硬件刷新波形与接口
|
||||
|
||||
在驱动层 `components/epdiy_bwr/` 中,针对电子墨水微胶囊粒子的电泳物理特性,实现了两种不同驱动波形:
|
||||
|
||||
### 3.1 全量刷新 (Full Refresh)
|
||||
* **对应 API**:`EPD_HW_Init_GUI()` + `EPD_Display(ImageBW, ImageRW)`
|
||||
* **耗时**:三色模式 ~12 ~ 15 秒;纯黑白模式 ~3 ~ 4 秒
|
||||
* **物理过程**:
|
||||
控制器连续输出多阶段高低反转电压脉冲,驱动黑、白、红三色微胶囊粒子充分迁移。屏幕会经历数次黑白红剧烈翻转闪烁。
|
||||
* **主要作用**:
|
||||
1. **驱动红色粒子**:全量波形是激活红色微胶囊电泳迁移的唯一方式(DEPG0420 三色模式必须使用此波形);
|
||||
2. **彻底清除残影**:中和残留在微胶囊两端的历史电荷,还原纯净白底。
|
||||
|
||||
### 3.2 快速刷新 (Fast Refresh)
|
||||
* **对应 API**:`EPD_HW_Init_Fast()` + `EPD_WhiteScreen_ALL_Fast(ImageBW, ImageRW)`
|
||||
* **耗时**:**约 1.5 秒**
|
||||
* **物理过程**:
|
||||
精简了电荷反转清屏步骤,仅针对黑白粒子施加单向短脉冲快速翻转。
|
||||
* **主要作用**:
|
||||
1. **极佳观感**:刷新极快且**完全不闪烁**,仅在**纯黑白模式(BW)**或仅更新黑白数据时适用;
|
||||
2. **局限性**:**不支持三色红白刷新**,若长时间仅用快刷,屏幕背景会微弱累积灰度残影。
|
||||
|
||||
### 3.3 控制器深度休眠 (Deep Sleep)
|
||||
* **对应 API**:`EPD_DeepSleep()`
|
||||
* **执行时机**:无论是全刷还是快刷,在数据写入屏幕完成后的第一时间,**必须**调用此函数向控制器发送 `0x10` 深度休眠命令。
|
||||
* **重要意义**:
|
||||
1. **极致省电**:休眠状态功耗仅为微安级(µA);
|
||||
2. **保护屏幕面板**:避免墨水屏长期处于工作电压导致液晶层极化损坏。
|
||||
|
||||
---
|
||||
|
||||
## 4. 显存与颜色图层管理
|
||||
|
||||
系统在编译期通过 `Kconfig`(`CONFIG_DISPLAY_BWR` / `CONFIG_DISPLAY_BW`)进行显存与图层模式切换:
|
||||
|
||||
### 4.1 黑白红三色模式 (BWR)
|
||||
* **显存分配**:
|
||||
* `ImageBW`:黑白位图缓冲区(400 × 300 / 8 = 15,000 字节),`0`=黑,`1`=白
|
||||
* `ImageRW`:红白位图缓冲区(15,000 字节),`0`=红,`1`=白/无色
|
||||
* **绘制流程**:
|
||||
调用 `switchColor(true)` 切换到红色图层绘制红底顶部横幅;调用 `switchColor(false)` 返回黑白图层绘制常规文字与数值。
|
||||
|
||||
### 4.2 纯黑白模式 (BW)
|
||||
* **显存分配**:
|
||||
仅分配单块 `ImageBW`(15KB),不分配红色图层,显著降低 ESP32 内存占用,同时原生支持快速刷新。
|
||||
|
||||
---
|
||||
|
||||
## 5. 应用层自适应刷新策略
|
||||
|
||||
在 [`App.cpp`](file:///Users/hongli/Workspace/xInfo/main/App.cpp) 的主循环中,系统整合了一套自适应仲裁算法:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([定时采集本地传感器]) --> Fetch[获取本地温湿度与空气质量数据]
|
||||
Fetch --> CheckDiff{数据与上一帧相比<br/>是否有变化?}
|
||||
CheckDiff -- 完全相同 --> Skip[跳过本次刷新<br/>延长墨水屏物理寿命]
|
||||
CheckDiff -- 有变化 --> CheckMode{当前色彩模式?}
|
||||
CheckMode -- BWR 三色模式 --> CheckCondition{是否满足全刷条件?<br/>1. 红色图层内容发生变动<br/>2. 快刷连续达阈值 约3小时}
|
||||
CheckMode -- BW 纯黑白模式 --> DoFast[执行 1.5s 极速快刷<br/>无闪烁更新]
|
||||
CheckCondition -- 是 --> DoFull[执行全量三色刷新<br/>消除残影 / 渲染红色图层]
|
||||
CheckCondition -- 否 --> DoFast
|
||||
DoFull --> Sleep[EPD_DeepSleep 硬件休眠]
|
||||
DoFast --> Sleep
|
||||
Skip --> Delay[等待下一个采样周期]
|
||||
Sleep --> Delay
|
||||
```
|
||||
|
||||
### 策略 1:数据无变化跳刷(Data Diff Bypass)
|
||||
* **逻辑**:每次采集后,将新生成的 `DashboardData` 与上一帧 `lastDashData_` 进行结构体全字段比对。
|
||||
* **优势**:如果室内环境稳定无变动,墨水屏完全不工作,极大延长电泳微胶囊的使用寿命。
|
||||
|
||||
### 策略 2:红色图层变更感知(Red Fingerprint Tracking)
|
||||
* **逻辑**:记录上一次全刷时红色区域的文本指纹 `lastRedContent_`(例如顶部 WiFi 连接状态)。
|
||||
* **优势**:当网络从 `WiFi: Home` 变为 `[Offline]` 时,由于涉及红底白字变化,系统自动由快刷升阶为全量三色刷新,确保红色像素不缺失。
|
||||
|
||||
### 策略 3:周期性全刷消残影(Anti-Ghosting Maintenance)
|
||||
* **逻辑**:维护快速刷新计数器 `fullRefreshCounter_`。
|
||||
* **计算公式**:
|
||||
$$\text{maxFastRefreshes} = \frac{180\text{ 分钟}}{\text{CONFIG\_DISPLAY\_REFRESH\_INTERVAL\_MIN}}$$
|
||||
* **优势**:日常采用 1.5 秒无感快刷,每累积运行约 **3 小时** 强制触发一次全量反转全刷,彻底清空物理残影,兼顾观感与画质纯净度。
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关配置文件与源码索引
|
||||
|
||||
* **硬件引脚分配规范**:[`docs/hardware_specs.md`](hardware_specs.md)
|
||||
* **应用主控状态机**:[`main/App.cpp`](file:///Users/hongli/Workspace/xInfo/main/App.cpp)、[`main/App.h`](file:///Users/hongli/Workspace/xInfo/main/App.h)
|
||||
* **UI 布局与图层管理**:[`main/DisplayManager.cpp`](file:///Users/hongli/Workspace/xInfo/main/DisplayManager.cpp)、[`main/DisplayManager.h`](file:///Users/hongli/Workspace/xInfo/main/DisplayManager.h)
|
||||
* **底层硬件波形驱动**:[`components/epdiy_bwr/src/EPD.cpp`](file:///Users/hongli/Workspace/xInfo/components/epdiy_bwr/src/EPD.cpp)、[`components/epdiy_bwr/include/EPD_SPI.h`](file:///Users/hongli/Workspace/xInfo/components/epdiy_bwr/include/EPD_SPI.h)
|
||||
* **Kconfig 参数配置**:[`main/Kconfig.projbuild`](file:///Users/hongli/Workspace/xInfo/main/Kconfig.projbuild)
|
||||
@@ -0,0 +1,62 @@
|
||||
# xInfo 硬件规格与引脚接线说明
|
||||
|
||||
本文档记录 `xInfo` 环境监控终端的完整硬件参数、屏幕型号、物理规格以及 ESP32-C3 所有外设的真实引脚接线对照表。
|
||||
|
||||
---
|
||||
|
||||
## 1. 墨水屏硬件规格与接线
|
||||
|
||||
### 1.1 屏幕基本参数
|
||||
| 参数项 | 参数值 / 说明 |
|
||||
| :--- | :--- |
|
||||
| **屏幕型号** | **DEPG0420 / YMS400300** |
|
||||
| **分辨率** | 400 × 300 像素 |
|
||||
| **显示色彩** | 黑 / 白 / 红 三色(Black / White / Red) |
|
||||
| **快刷支持** | **三色(BWR)模式下不支持快刷(需全量三色刷新)**;在纯黑白(BW)模式下支持快速刷新 |
|
||||
| **通信接口** | 4-Wire SPI 接口 |
|
||||
| **排线规格** | 24 Pin(FPC 24P) |
|
||||
| **外形尺寸** | 91 × 77 mm(不含 FPC 排线) |
|
||||
| **工作电压** | 3.3V |
|
||||
|
||||
### 1.2 墨水屏接线引脚对照表
|
||||
| 墨水屏引脚 | 接 ESP32-C3 针脚 | 功能描述 | 说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **BUSY** | **GPIO 1** | 忙碌状态输出 | 高电平表示空闲(Idle),低电平表示忙碌中(Busy) |
|
||||
| **RST** | **GPIO 2** | 复位引脚 | 硬件复位控制 |
|
||||
| **DC** | **GPIO 3** | 数据/命令控制 | 低电平写命令(CMD),高电平写数据(DATA) |
|
||||
| **CS** | **GPIO 4** | 片选信号 | SPI 片选 (CS) |
|
||||
| **CLK (SCL)** | **GPIO 5** | SPI 时钟 | SPI 时钟总线 (SCK) |
|
||||
| **DIN (SDA)** | **GPIO 6** | SPI 数据输入 | SPI 数据输入 (MOSI) |
|
||||
| **GND** | **GND** | 电源地 | 系统参考地 |
|
||||
| **VCC** | **3.3V** | 3.3V 供电 | 3.3V 稳压电源输入 |
|
||||
|
||||
---
|
||||
|
||||
## 2. TVOC 三合一空气质量传感器接线
|
||||
|
||||
| 传感器引脚 | 接 ESP32-C3 针脚 | 开发板引脚编号 | 功能与说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **GND** | **GND** | 01 | 传感器电源地 |
|
||||
| **5V** | **5V** | 16 | 传感器 5V 供电输入 |
|
||||
| **B (TXD)** → | **GPIO00 (UART1_RX)** | 02 | 传感器数据发送端 $\rightarrow$ ESP32 串口接收(9600 8N1) |
|
||||
| **A (RXD)** ← | **GPIO01 (UART1_TX)** | 03 | ESP32 串口发送端 $\rightarrow$ 传感器数据接收端 |
|
||||
|
||||
---
|
||||
|
||||
## 3. DHT11 温湿度传感器接线
|
||||
|
||||
| DHT11 传感器引脚 | 接 ESP32-C3 针脚名称 | 引脚编号(最优推荐) | 说明 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **VCC (电源正极)** | **3.3V** | 13 | ⚡ **请用 3.3V,不要用 5V** |
|
||||
| **GND (电源负极)** | **GND** | 07 | **必须接对,接反必烧!** |
|
||||
| **DO (数据输出)** | **GPIO05** | 27 | 单总线数字信号 IO 口 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 驱动与源码对应索引
|
||||
|
||||
* **墨水屏 SPI 引脚定义**:[`components/epdiy_bwr/include/EPD_SPI.h`](file:///Users/hongli/Workspace/xInfo/components/epdiy_bwr/include/EPD_SPI.h)
|
||||
* **墨水屏底层波形驱动**:[`components/epdiy_bwr/src/EPD.cpp`](file:///Users/hongli/Workspace/xInfo/components/epdiy_bwr/src/EPD.cpp)
|
||||
* **UART 空气质量传感器驱动**:[`main/SensorDriver.h`](file:///Users/hongli/Workspace/xInfo/main/SensorDriver.h)、[`main/SensorDriver.cpp`](file:///Users/hongli/Workspace/xInfo/main/SensorDriver.cpp)
|
||||
* **DHT11 温湿度驱动**:[`main/Dht11Driver.h`](file:///Users/hongli/Workspace/xInfo/main/Dht11Driver.h)、[`main/Dht11Driver.cpp`](file:///Users/hongli/Workspace/xInfo/main/Dht11Driver.cpp)
|
||||
* **主控初始化与调用流程**:[`main/App.cpp`](file:///Users/hongli/Workspace/xInfo/main/App.cpp)
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @file App.cpp
|
||||
* @brief xInfo 环境监控终端主应用逻辑。
|
||||
*/
|
||||
#include "App.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <ctime>
|
||||
|
||||
// ============ 编译期常量(来自 Kconfig / menuconfig)============
|
||||
static constexpr uint32_t kDisplayIntervalMs = CONFIG_DISPLAY_REFRESH_INTERVAL_MIN * 60 * 1000; // 墨水屏刷新间隔
|
||||
|
||||
static const char *TAG = "App";
|
||||
|
||||
App::App()
|
||||
: wifi_(CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD),
|
||||
dht11Driver_(GPIO_NUM_5),
|
||||
dashFetcher_(sensorDriver_, dht11Driver_) {}
|
||||
|
||||
App::~App() {}
|
||||
|
||||
void App::init() {
|
||||
ESP_LOGI(TAG, "正在初始化 xInfo 环境监控终端...");
|
||||
|
||||
// 步骤 1:连接 WiFi
|
||||
wifi_.connect();
|
||||
|
||||
// 步骤 2:SNTP 时间同步(获取网络时间用于显示准确时钟)
|
||||
ESP_LOGI(TAG, "正在同步 NTP 时间(服务器: %s)...", CONFIG_NTP_SERVER);
|
||||
setenv("TZ", "CST-8", 1);
|
||||
tzset();
|
||||
|
||||
esp_sntp_config_t sntpConfig = ESP_NETIF_SNTP_DEFAULT_CONFIG(CONFIG_NTP_SERVER);
|
||||
esp_netif_sntp_init(&sntpConfig);
|
||||
|
||||
constexpr int kMaxRetries = 3;
|
||||
for (int i = 0; i < kMaxRetries; ++i) {
|
||||
esp_err_t ret = esp_netif_sntp_sync_wait(pdMS_TO_TICKS(CONFIG_NTP_SYNC_TIMEOUT_SEC * 1000));
|
||||
if (ret == ESP_OK) {
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
localtime_r(&now, &timeinfo);
|
||||
char timeBuf[64];
|
||||
strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", &timeinfo);
|
||||
ESP_LOGI(TAG, "NTP 时间同步成功: %s", timeBuf);
|
||||
break;
|
||||
}
|
||||
ESP_LOGW(TAG, "NTP 同步第 %d 次超时...", i + 1);
|
||||
}
|
||||
|
||||
// 步骤 3:初始化墨水屏硬件
|
||||
display_.initHardware();
|
||||
|
||||
// 步骤 4:初始化空气质量传感器驱动
|
||||
sensorDriver_.init();
|
||||
|
||||
// 步骤 5:初始化 DHT11 温湿度传感器驱动
|
||||
dht11Driver_.init();
|
||||
}
|
||||
|
||||
void App::run() {
|
||||
ESP_LOGI(TAG, "进入主循环...");
|
||||
|
||||
std::string mac = wifi_.getMac();
|
||||
|
||||
while (true) {
|
||||
ESP_LOGI(TAG, "采集本地环境数据并刷新墨水屏...");
|
||||
auto dashData = dashFetcher_.fetch();
|
||||
|
||||
if (dashData && dashData != lastDashData_) {
|
||||
display_.beginDrawing();
|
||||
|
||||
// 绘制环境与网络监控界面
|
||||
std::string ssid = wifi_.getSsid();
|
||||
display_.drawTopBanner(ssid.empty() || ssid == "N/A" ? "Offline" : ssid);
|
||||
display_.drawThermoHygrometer(*dashData); // 核心温湿度看板与舒适度
|
||||
display_.drawAirQuality(*dashData); // 空气质量多维度指标(CO2/TVOC/甲醛)
|
||||
display_.drawBottomInfoBar(wifi_.getIp(), mac); // 底部 IP 与 MAC 网络参数
|
||||
|
||||
// 定期全刷消除墨水屏残影
|
||||
int maxFastRefreshes = 180 / CONFIG_DISPLAY_REFRESH_INTERVAL_MIN;
|
||||
if (maxFastRefreshes < 1) maxFastRefreshes = 1;
|
||||
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
std::string redContent = ssid;
|
||||
bool needFullRefresh = lastRedContent_.empty()
|
||||
|| (redContent != lastRedContent_)
|
||||
|| (fullRefreshCounter_ >= maxFastRefreshes);
|
||||
#else
|
||||
bool needFullRefresh = (fullRefreshCounter_ == 0)
|
||||
|| (fullRefreshCounter_ >= maxFastRefreshes);
|
||||
#endif
|
||||
|
||||
if (needFullRefresh) {
|
||||
display_.updateFullAndSleep();
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
lastRedContent_ = redContent;
|
||||
#endif
|
||||
fullRefreshCounter_ = 0;
|
||||
} else {
|
||||
display_.updateFastAndSleep();
|
||||
fullRefreshCounter_++;
|
||||
}
|
||||
|
||||
lastDashData_ = dashData;
|
||||
ESP_LOGI(TAG, "墨水屏刷新完成(%s)", needFullRefresh ? "全量" : "快刷");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "数据无变化,跳过本次墨水屏刷新");
|
||||
}
|
||||
|
||||
// 休眠等待下一个刷新周期
|
||||
vTaskDelay(pdMS_TO_TICKS(kDisplayIntervalMs));
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file App.h
|
||||
* @brief xInfo 环境监控终端主应用类。
|
||||
*
|
||||
* 负责协调系统初始化、WiFi 连接、NTP 网络授时、本地传感器数据采集聚合,
|
||||
* 以及墨水屏 UI 定时局部/全量刷新的完整生命周期与状态机管理。
|
||||
*
|
||||
* 系统运行机制:
|
||||
* 1. 启动与初始化(init()):
|
||||
* - 建立 WiFi 连接并获取网络配置;
|
||||
* - 启动 SNTP 同步网络北京时间,供本地环境数据生成时间戳;
|
||||
* - 初始化 E-Paper SPI/GPIO 硬件总线;
|
||||
* - 初始化 UART 空气质量传感器(TVOC/CH₂O/CO₂)与 DHT11 温湿度传感器驱动。
|
||||
* 2. 主循环(run()):
|
||||
* - 定时从本地硬件驱动采集传感器读数并聚合为 DashboardData;
|
||||
* - 比较当前帧与历史帧数据,有变化时才驱动墨水屏刷新(延长墨水屏使用寿命);
|
||||
* - 根据刷新次数与内容变化自适应选择快速黑白刷新(~1.6s)或全量刷新(消除残影);
|
||||
* - 进入低功耗休眠等待下一轮刷新周期。
|
||||
*/
|
||||
#ifndef APP_H
|
||||
#define APP_H
|
||||
|
||||
#include "DashboardFetcher.h"
|
||||
#include "Dht11Driver.h"
|
||||
#include "DisplayManager.h"
|
||||
#include "SensorDriver.h"
|
||||
#include "WifiManager.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
class App {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数,初始化基础组件引脚与配置。
|
||||
*/
|
||||
App();
|
||||
|
||||
/**
|
||||
* @brief 析构函数。
|
||||
*/
|
||||
~App();
|
||||
|
||||
/**
|
||||
* @brief 初始化系统所有子系统。
|
||||
*
|
||||
* 包含:
|
||||
* - WiFi 连接与网络参数就绪检测;
|
||||
* - SNTP 时间同步(重试机制);
|
||||
* - 墨水屏底层 SPI/GPIO 接口初始化;
|
||||
* - DHT11 与 UART 气体传感器硬件接口初始化。
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* @brief 主运行循环(阻塞)。
|
||||
*
|
||||
* 执行环境数据周期性采集、变化检测、墨水屏快刷/全刷决策与低功耗等待。
|
||||
*/
|
||||
void run();
|
||||
|
||||
private:
|
||||
WifiManager wifi_; ///< WiFi 连接管理器(负责连接建立、IP/MAC/SSID/状态维护)
|
||||
DisplayManager display_; ///< 墨水屏 UI 渲染与刷新驱动管理器
|
||||
SensorDriver sensorDriver_; ///< UART 空气质量传感器驱动(CO₂、TVOC、甲醛 CH₂O 读数)
|
||||
Dht11Driver dht11Driver_; ///< DHT11 单总线温湿度传感器驱动(室内温度、相对湿度)
|
||||
DashboardFetcher dashFetcher_; ///< 本地环境数据聚合器(聚合传感器与系统时间)
|
||||
|
||||
/**
|
||||
* @brief 缓存上一次刷新所使用的展示数据。
|
||||
* 用于数据变更比对,数据完全一致时跳过刷新以延长墨水屏寿命。
|
||||
*/
|
||||
std::optional<DashboardData> lastDashData_;
|
||||
|
||||
/**
|
||||
* @brief 缓存上一次红色图层区域的内容指纹(仅在 BWR 模式下生效)。
|
||||
* 当红色图层内容发生变化时,必须触发全量三色刷新以更新红色像素。
|
||||
*/
|
||||
std::string lastRedContent_;
|
||||
|
||||
/**
|
||||
* @brief 连续快速刷新计数器。
|
||||
* 墨水屏在多次快速黑白刷新后可能产生背景残影,
|
||||
* 计数器累计达到阈值(约每 3 小时)时强制触发一次全量刷新以消除残影。
|
||||
*/
|
||||
int fullRefreshCounter_ = 0;
|
||||
};
|
||||
|
||||
#endif // APP_H
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.cpp" "WifiManager.cpp" "DashboardFetcher.cpp" "SensorDriver.cpp" "DisplayManager.cpp" "App.cpp" "Dht11Driver.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES epdiy_bwr chinese_font nvs_flash esp_wifi esp_netif esp_event esp_driver_gpio esp_driver_uart esp_timer driver)
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file DashboardFetcher.cpp
|
||||
* @brief 本地环境数据聚合器实现(无需后端服务)。
|
||||
*/
|
||||
#include "DashboardFetcher.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "DashboardFetcher";
|
||||
|
||||
DashboardFetcher::DashboardFetcher(SensorDriver &sensorDriver,
|
||||
Dht11Driver &dht11Driver)
|
||||
: sensorDriver_(sensorDriver), dht11Driver_(dht11Driver) {}
|
||||
|
||||
DashboardFetcher::~DashboardFetcher() {}
|
||||
|
||||
std::optional<DashboardData> DashboardFetcher::fetch() {
|
||||
ESP_LOGI(TAG, "开始执行本地传感器采集与数据聚合...");
|
||||
|
||||
DashboardData data;
|
||||
char buf[32];
|
||||
|
||||
// 1. 读取 DHT11 温湿度
|
||||
float temp = NAN;
|
||||
float humi = NAN;
|
||||
bool dhtOk = dht11Driver_.read(temp, humi);
|
||||
|
||||
if (dhtOk && !std::isnan(temp)) {
|
||||
snprintf(buf, sizeof(buf), "%.1f", temp);
|
||||
data.temperature = buf;
|
||||
}
|
||||
if (dhtOk && !std::isnan(humi)) {
|
||||
snprintf(buf, sizeof(buf), "%.1f", humi);
|
||||
data.humidity = buf;
|
||||
}
|
||||
|
||||
// 2. 读取 UART 空气质量传感器(CO2, TVOC, CH2O)
|
||||
SensorReading air = sensorDriver_.read();
|
||||
if (!std::isnan(air.co2) && air.co2 > 0) {
|
||||
snprintf(buf, sizeof(buf), "%.0f", air.co2);
|
||||
data.co2 = buf;
|
||||
}
|
||||
if (!std::isnan(air.tvoc) && air.tvoc >= 0) {
|
||||
snprintf(buf, sizeof(buf), "%.2f", air.tvoc);
|
||||
data.tvoc = buf;
|
||||
}
|
||||
if (!std::isnan(air.ch2o) && air.ch2o >= 0) {
|
||||
snprintf(buf, sizeof(buf), "%.3f", air.ch2o);
|
||||
data.ch2o = buf;
|
||||
}
|
||||
|
||||
// 3. 读取本地系统时间(通过 NTP 同步)
|
||||
time_t now = time(nullptr);
|
||||
struct tm timeinfo;
|
||||
localtime_r(&now, &timeinfo);
|
||||
|
||||
if (timeinfo.tm_year >= (2020 - 1900)) {
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
|
||||
data.lastReport = buf;
|
||||
} else {
|
||||
data.lastReport = "--:--";
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "本地数据聚合完成: 温度=%s, 湿度=%s, CO2=%s, TVOC=%s, CH2O=%s, 时间=%s",
|
||||
data.temperature.c_str(), data.humidity.c_str(),
|
||||
data.co2.c_str(), data.tvoc.c_str(), data.ch2o.c_str(),
|
||||
data.lastReport.c_str());
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @file DashboardFetcher.h
|
||||
* @brief 本地环境数据聚合器。
|
||||
*
|
||||
* 无需后端服务器支持,直接从本地硬件传感器(DHT11 + UART 空气质量传感器)
|
||||
* 以及 ESP32 本地系统时钟(NTP)采集环境数据并生成墨水屏展示数据。
|
||||
*/
|
||||
#ifndef DASHBOARD_FETCHER_H
|
||||
#define DASHBOARD_FETCHER_H
|
||||
|
||||
#include "Dht11Driver.h"
|
||||
#include "SensorDriver.h"
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief 墨水屏展示所需的环境数据结构体。
|
||||
*/
|
||||
struct DashboardData {
|
||||
// 室内/本地环境传感器读数
|
||||
std::string temperature; ///< 温度(℃),如 "24.5"
|
||||
std::string humidity; ///< 湿度(%),如 "60.2"
|
||||
std::string co2; ///< CO₂ 浓度(ppm),如 "450"
|
||||
std::string tvoc; ///< TVOC 浓度(mg/m³),如 "0.08"
|
||||
std::string ch2o; ///< 甲醛浓度(mg/m³),如 "0.01"
|
||||
std::string lastReport; ///< 本地采集时间,如 "14:30"
|
||||
|
||||
/// 用于检测数据是否有变化,避免无意义的墨水屏全刷
|
||||
bool operator==(const DashboardData &other) const {
|
||||
return temperature == other.temperature &&
|
||||
humidity == other.humidity &&
|
||||
co2 == other.co2 &&
|
||||
tvoc == other.tvoc &&
|
||||
ch2o == other.ch2o &&
|
||||
lastReport == other.lastReport;
|
||||
}
|
||||
bool operator!=(const DashboardData &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 负责从本地传感器与系统时钟聚合环境数据。
|
||||
*/
|
||||
class DashboardFetcher {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数。
|
||||
* @param sensorDriver UART 空气质量传感器驱动引用
|
||||
* @param dht11Driver DHT11 温湿度传感器驱动引用
|
||||
*/
|
||||
DashboardFetcher(SensorDriver &sensorDriver, Dht11Driver &dht11Driver);
|
||||
~DashboardFetcher();
|
||||
|
||||
/**
|
||||
* @brief 执行本地采样并聚合生成展示数据。
|
||||
* @return 成功返回聚合好的 DashboardData
|
||||
*/
|
||||
std::optional<DashboardData> fetch();
|
||||
|
||||
private:
|
||||
SensorDriver &sensorDriver_;
|
||||
Dht11Driver &dht11Driver_;
|
||||
};
|
||||
|
||||
#endif // DASHBOARD_FETCHER_H
|
||||
@@ -0,0 +1,130 @@
|
||||
#include "Dht11Driver.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
static const char *TAG = "Dht11Driver";
|
||||
|
||||
Dht11Driver::Dht11Driver(gpio_num_t pin)
|
||||
: pin_(pin), lastTemperature_(NAN), lastHumidity_(NAN), lastReadTimeUs_(0) {}
|
||||
|
||||
Dht11Driver::~Dht11Driver() {}
|
||||
|
||||
void Dht11Driver::init() {
|
||||
ESP_LOGI(TAG, "初始化 DHT11 引脚: GPIO%d", pin_);
|
||||
gpio_reset_pin(pin_);
|
||||
gpio_set_pull_mode(pin_, GPIO_PULLUP_ONLY);
|
||||
gpio_set_direction(pin_, GPIO_MODE_INPUT);
|
||||
}
|
||||
|
||||
// 辅助函数:等待指定电平状态,超时返回 -1,成功返回耗时(us)
|
||||
static inline int wait_for_level(gpio_num_t pin, int level, int timeout_us) {
|
||||
int elapsed = 0;
|
||||
while (gpio_get_level(pin) != level) {
|
||||
if (elapsed >= timeout_us) return -1;
|
||||
esp_rom_delay_us(1);
|
||||
elapsed++;
|
||||
}
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
bool Dht11Driver::read(float& temperature, float& humidity) {
|
||||
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
int err_phase = 0; // 0=OK, 1=PhaseA, 2=PhaseB, 3=PhaseC, 4=LowTimeout, 5=HighTimeout
|
||||
int err_bit = 0;
|
||||
int64_t now = esp_timer_get_time();
|
||||
uint8_t data[5] = {0,0,0,0,0};
|
||||
|
||||
// 开机前 3 秒内,强制阻塞等待(而不是直接抛弃),确保开机第一次上报就有温湿度!
|
||||
if (now < 3000000) {
|
||||
int wait_ms = (3000000 - now) / 1000;
|
||||
ESP_LOGI(TAG, "传感器刚上电,强制耐心等待 %d 毫秒...", wait_ms);
|
||||
vTaskDelay(pdMS_TO_TICKS(wait_ms));
|
||||
now = esp_timer_get_time();
|
||||
}
|
||||
|
||||
if (now - lastReadTimeUs_ < 2200000 && lastReadTimeUs_ != 0) {
|
||||
ESP_LOGI(TAG, "距离上次采集太近,直接返回缓存");
|
||||
goto ret_cache;
|
||||
}
|
||||
|
||||
lastReadTimeUs_ = now;
|
||||
|
||||
// 1. 发送启动信号:MCU 拉低至少 18ms
|
||||
gpio_set_direction(pin_, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(pin_, 0);
|
||||
esp_rom_delay_us(20000); // 20ms
|
||||
|
||||
// 2. MCU 释放总线(拉高),等待 20-40us
|
||||
gpio_set_level(pin_, 1);
|
||||
esp_rom_delay_us(30);
|
||||
gpio_set_direction(pin_, GPIO_MODE_INPUT);
|
||||
|
||||
// 3. 开始临界区,在这个 4~5 毫秒的时间内,我们将暂停 Wi-Fi 中断和操作系统任务调度
|
||||
// 不然一旦 CPU 去处理网络包,几十微秒的时间窗口瞬间就会错过并导致“超时”
|
||||
|
||||
portENTER_CRITICAL(&mux); // 关闭中断保护时序
|
||||
|
||||
// 等待传感器拉低 (Phase A,传感器响应起始)
|
||||
if (wait_for_level(pin_, 0, 80) < 0) { err_phase = 1; goto end_critical; }
|
||||
|
||||
// 等待传感器拉高 (Phase B,传感器准备发送数据)
|
||||
if (wait_for_level(pin_, 1, 80) < 0) { err_phase = 2; goto end_critical; }
|
||||
|
||||
// 等待传感器再次拉低 (开始发送 40 bit 数据)
|
||||
if (wait_for_level(pin_, 0, 80) < 0) { err_phase = 3; goto end_critical; }
|
||||
|
||||
// 读取 40 个 bit (5 bytes)
|
||||
for (int i = 0; i < 40; i++) {
|
||||
// 等待低电平结束 (50us)
|
||||
if (wait_for_level(pin_, 1, 70) < 0) { err_phase = 4; err_bit = i; goto end_critical; }
|
||||
|
||||
// 高电平持续时间决定了这 bit 是 0 还是 1. (26-28us = 0, 70us = 1)
|
||||
int high_time = wait_for_level(pin_, 0, 100);
|
||||
if (high_time < 0) { err_phase = 5; err_bit = i; goto end_critical; }
|
||||
|
||||
data[i / 8] <<= 1;
|
||||
if (high_time > 40) {
|
||||
data[i / 8] |= 1;
|
||||
}
|
||||
}
|
||||
|
||||
end_critical:
|
||||
portEXIT_CRITICAL(&mux); // 退出临界区,恢复系统调度和所有的硬件中断
|
||||
|
||||
if (err_phase != 0) {
|
||||
if (err_phase == 1) ESP_LOGE(TAG, "读取失败: 传感器无响应 (Phase A timeout)");
|
||||
else if (err_phase == 2) ESP_LOGE(TAG, "读取失败: 传感器握手失败 (Phase B timeout)");
|
||||
else if (err_phase == 3) ESP_LOGE(TAG, "读取失败: 传感器未开始发数据 (Phase C timeout)");
|
||||
else if (err_phase == 4) ESP_LOGE(TAG, "位读取失败: 低电平超时 bit %d", err_bit);
|
||||
else if (err_phase == 5) ESP_LOGE(TAG, "位读取失败: 高电平超时 bit %d", err_bit);
|
||||
goto ret_fail;
|
||||
}
|
||||
|
||||
// 7. 校验和检验
|
||||
if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
|
||||
lastHumidity_ = data[0] + data[1] * 0.1f;
|
||||
lastTemperature_ = data[2] + data[3] * 0.1f;
|
||||
temperature = lastTemperature_;
|
||||
humidity = lastHumidity_;
|
||||
ESP_LOGI(TAG, "自写驱动读取成功: T=%.1f°C, H=%.1f%%", temperature, humidity);
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Checksum failed: %02x %02x %02x %02x = %02x", data[0], data[1], data[2], data[3], data[4]);
|
||||
goto ret_fail;
|
||||
}
|
||||
|
||||
ret_cache:
|
||||
temperature = lastTemperature_;
|
||||
humidity = lastHumidity_;
|
||||
return !std::isnan(lastTemperature_);
|
||||
|
||||
ret_fail:
|
||||
lastTemperature_ = NAN;
|
||||
lastHumidity_ = NAN;
|
||||
temperature = NAN;
|
||||
humidity = NAN;
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file Dht11Driver.h
|
||||
* @brief DHT11 温湿度传感器驱动封装。
|
||||
*
|
||||
* 基于 esp-idf-lib 的 dht 组件,专门设计了防堵塞和失败反馈机制,
|
||||
* 在读取失败或未连接传感器时安全退回 NAN。
|
||||
*/
|
||||
#ifndef DHT11_DRIVER_H
|
||||
#define DHT11_DRIVER_H
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include <cmath>
|
||||
|
||||
class Dht11Driver {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param pin DHT11 的数据引脚
|
||||
*/
|
||||
Dht11Driver(gpio_num_t pin);
|
||||
~Dht11Driver();
|
||||
|
||||
/**
|
||||
* @brief 初始化硬件和引脚状态
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* @brief 读取环境温度和相对湿度
|
||||
*
|
||||
* 内部使用 esp-idf-lib 的 dht_read_float_data() 读取。
|
||||
* 如果采样间隔过短(小于 2 秒),将返回上一次的缓存结果。
|
||||
*
|
||||
* @param [out] temperature 存放产出的温度(℃),失败为 NAN
|
||||
* @param [out] humidity 存放产出的湿度(%RH),失败为 NAN
|
||||
* @return true 成功读取有效数据(或返回缓存数据)
|
||||
* @return false 读取失败(比如未接传感器,或时序错乱),此时 temperature 和 humidity 为 NAN
|
||||
*/
|
||||
bool read(float& temperature, float& humidity);
|
||||
|
||||
private:
|
||||
gpio_num_t pin_;
|
||||
float lastTemperature_;
|
||||
float lastHumidity_;
|
||||
int64_t lastReadTimeUs_;
|
||||
};
|
||||
|
||||
#endif // DHT11_DRIVER_H
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* @file DisplayManager.cpp
|
||||
* @brief 环境监测与网络信息墨水屏 UI 绘制实现。
|
||||
*
|
||||
* 屏幕:400×300,通过 CONFIG_DISPLAY_BWR / CONFIG_DISPLAY_BW 编译宏
|
||||
* 区分三色(黑/白/红)和纯黑白两种模式。
|
||||
* 英文字体:Font8, Font12, Font16, Font20, Font24
|
||||
* 中文字体:Font16CN(Unifont 16×16)
|
||||
*/
|
||||
#include "DisplayManager.h"
|
||||
#include "ChineseFont.h"
|
||||
#include "EPD.h"
|
||||
#include "EPD_SPI.h"
|
||||
#include "GUI_Paint.h"
|
||||
#include "esp_log.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
static const char *TAG = "DisplayManager";
|
||||
|
||||
// 帧缓冲区
|
||||
static unsigned char ImageBW[EPD_ARRAY]; ///< 黑白像素缓冲
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
static unsigned char ImageRW[EPD_ARRAY]; ///< 红白像素缓冲(仅 BWR 模式使用)
|
||||
#endif
|
||||
|
||||
DisplayManager::DisplayManager() {}
|
||||
DisplayManager::~DisplayManager() {}
|
||||
|
||||
void DisplayManager::initHardware() {
|
||||
EPD_SPI_Init();
|
||||
}
|
||||
|
||||
void DisplayManager::beginDrawing() {
|
||||
Paint_NewImage(ImageBW, EPD_WIDTH, EPD_HEIGHT, 0, WHITE);
|
||||
Paint_SelectImage(ImageBW);
|
||||
Paint_Clear(WHITE);
|
||||
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
Paint_NewImage(ImageRW, EPD_WIDTH, EPD_HEIGHT, 0, WHITE);
|
||||
Paint_SelectImage(ImageRW);
|
||||
Paint_Clear(WHITE);
|
||||
|
||||
Paint_SelectImage(ImageBW);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DisplayManager::updateFullAndSleep() {
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
ESP_LOGI(TAG, "全量三色刷新墨水屏...");
|
||||
EPD_HW_Init_GUI();
|
||||
EPD_Display(ImageBW, ImageRW);
|
||||
#else
|
||||
ESP_LOGI(TAG, "全量黑白刷新墨水屏...");
|
||||
EPD_HW_Init_GUI();
|
||||
static unsigned char emptyRW[EPD_ARRAY] = {0};
|
||||
EPD_Display(ImageBW, emptyRW);
|
||||
#endif
|
||||
EPD_DeepSleep();
|
||||
}
|
||||
|
||||
void DisplayManager::updateFastAndSleep() {
|
||||
ESP_LOGI(TAG, "快速刷新墨水屏...");
|
||||
EPD_HW_Init_Fast();
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
EPD_WhiteScreen_ALL_Fast(ImageBW, ImageRW);
|
||||
#else
|
||||
static unsigned char emptyRW[EPD_ARRAY] = {0};
|
||||
EPD_WhiteScreen_ALL_Fast(ImageBW, emptyRW);
|
||||
#endif
|
||||
EPD_DeepSleep();
|
||||
}
|
||||
|
||||
void DisplayManager::switchColor(bool toRed) {
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
Paint_SelectImage(toRed ? ImageRW : ImageBW);
|
||||
#else
|
||||
(void)toRed;
|
||||
Paint_SelectImage(ImageBW);
|
||||
#endif
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 顶部横幅:标题 + WiFi 状态
|
||||
// Y: 0 ~ 28
|
||||
// =========================================================================
|
||||
void DisplayManager::drawTopBanner(const std::string &networkStatus) {
|
||||
#ifdef CONFIG_DISPLAY_BWR
|
||||
switchColor(true);
|
||||
Paint_DrawRectangle(0, 0, EPD_WIDTH, 26, RED, DRAW_FILL_FULL, DOT_PIXEL_1X1);
|
||||
Paint_DrawString_UTF8(10, 5, "xInfo \xe7\x8e\xaf\xe5\xa2\x83\xe7\x9b\x91\xe6\xb5\x8b\xe7\xbb\x88\xe7\xab\xaf", Font16CN, WHITE, RED); // xInfo 环境监测终端
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "[%s]", networkStatus.c_str());
|
||||
int strW = static_cast<int>(strlen(buf)) * 8;
|
||||
int offsetX = EPD_WIDTH - strW - 10;
|
||||
if (offsetX < 220) offsetX = 220;
|
||||
Paint_DrawString_EN(offsetX, 8, buf, &Font12, WHITE, RED);
|
||||
|
||||
switchColor(false);
|
||||
#else
|
||||
Paint_DrawRectangle(0, 0, EPD_WIDTH, 26, BLACK, DRAW_FILL_FULL, DOT_PIXEL_1X1);
|
||||
Paint_DrawString_UTF8(10, 5, "xInfo \xe7\x8e\xaf\xe5\xa2\x83\xe7\x9b\x91\xe6\xb5\x8b\xe7\xbb\x88\xe7\xab\xaf", Font16CN, WHITE, BLACK); // xInfo 环境监测终端
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "[%s]", networkStatus.c_str());
|
||||
int strW = static_cast<int>(strlen(buf)) * 8;
|
||||
int offsetX = EPD_WIDTH - strW - 10;
|
||||
if (offsetX < 220) offsetX = 220;
|
||||
Paint_DrawString_EN(offsetX, 8, buf, &Font12, WHITE, BLACK);
|
||||
#endif
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 核心温湿度看板区
|
||||
// Y: 32 ~ 148
|
||||
// =========================================================================
|
||||
void DisplayManager::drawThermoHygrometer(const DashboardData &data) {
|
||||
char buf[32];
|
||||
|
||||
// 更新时间(靠右显示)
|
||||
if (!data.lastReport.empty()) {
|
||||
char timeStr[48];
|
||||
snprintf(timeStr, sizeof(timeStr), "\xe6\x9b\xb4\xe6\x96\xb0: %s", data.lastReport.c_str()); // 更新:
|
||||
Paint_DrawString_UTF8(290, 34, timeStr, Font16CN, BLACK, WHITE);
|
||||
}
|
||||
|
||||
// 左侧:室内温度看板 (X: 10 ~ 190)
|
||||
Paint_DrawString_UTF8(15, 36, "\xe5\xae\xa4\xe5\x86\x85\xe6\xb8\xa9\xe5\xba\xa6", Font16CN, BLACK, WHITE); // 室内温度
|
||||
if (!data.temperature.empty()) {
|
||||
snprintf(buf, sizeof(buf), "%s", data.temperature.c_str());
|
||||
Paint_DrawString_EN(15, 68, buf, &Font24, BLACK, WHITE);
|
||||
int numW = static_cast<int>(strlen(buf)) * 17 + 12;
|
||||
Paint_DrawCircle(numW + 8, 72, 3, BLACK, DRAW_FILL_EMPTY, DOT_PIXEL_1X1);
|
||||
Paint_DrawString_EN(numW + 15, 70, "C", &Font20, BLACK, WHITE);
|
||||
|
||||
// 舒适度判断小标签
|
||||
float tempVal = strtof(data.temperature.c_str(), nullptr);
|
||||
if (tempVal < 18.0f) {
|
||||
Paint_DrawString_UTF8(15, 115, "[\xe5\x81\x8f\xe5\x87\x89]", Font16CN, BLACK, WHITE); // [偏凉]
|
||||
} else if (tempVal <= 26.0f) {
|
||||
Paint_DrawString_UTF8(15, 115, "[\xe8\x88\x92\xe9\x80\x82]", Font16CN, BLACK, WHITE); // [舒适]
|
||||
} else {
|
||||
Paint_DrawString_UTF8(15, 115, "[\xe5\x81\x8f\xe7\x83\xad]", Font16CN, BLACK, WHITE); // [偏热]
|
||||
}
|
||||
} else {
|
||||
Paint_DrawString_EN(15, 68, "--.-", &Font24, BLACK, WHITE);
|
||||
}
|
||||
|
||||
// 中间竖向分隔线
|
||||
Paint_DrawLine(200, 36, 200, 142, BLACK, LINE_STYLE_SOLID, DOT_PIXEL_1X1);
|
||||
|
||||
// 右侧:相对湿度看板 (X: 210 ~ 390)
|
||||
Paint_DrawString_UTF8(215, 36, "\xe7\x9b\xb8\xe5\xaf\xb9\xe6\xb9\xbf\xe5\xba\xa6", Font16CN, BLACK, WHITE); // 相对湿度
|
||||
if (!data.humidity.empty()) {
|
||||
snprintf(buf, sizeof(buf), "%s%%", data.humidity.c_str());
|
||||
Paint_DrawString_EN(215, 68, buf, &Font24, BLACK, WHITE);
|
||||
|
||||
// 湿度状态小标签
|
||||
float humiVal = strtof(data.humidity.c_str(), nullptr);
|
||||
if (humiVal < 40.0f) {
|
||||
Paint_DrawString_UTF8(215, 115, "[\xe5\xb9\xb2\xe7\x87\xa5]", Font16CN, BLACK, WHITE); // [干燥]
|
||||
} else if (humiVal <= 70.0f) {
|
||||
Paint_DrawString_UTF8(215, 115, "[\xe9\x80\x82\xe5\xae\x9c]", Font16CN, BLACK, WHITE); // [适宜]
|
||||
} else {
|
||||
Paint_DrawString_UTF8(215, 115, "[\xe6\xbd\xae\xe6\xb9\xbf]", Font16CN, BLACK, WHITE); // [潮湿]
|
||||
}
|
||||
} else {
|
||||
Paint_DrawString_EN(215, 68, "--.-%", &Font24, BLACK, WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 空气质量指标多维度展示区
|
||||
// Y: 152 ~ 265
|
||||
// =========================================================================
|
||||
void DisplayManager::drawAirQuality(const DashboardData &data) {
|
||||
// 横向分隔线
|
||||
Paint_DrawLine(10, 150, EPD_WIDTH - 10, 150, BLACK, LINE_STYLE_SOLID, DOT_PIXEL_1X1);
|
||||
|
||||
// 区域标题
|
||||
Paint_DrawString_UTF8(10, 156, "\xe3\x80\x90 \xe7\xa9\xba\xe6\xb0\x94\xe8\xb2\xb4\xe9\x87\x8f\xe6\x8c\x87\xe6\xa0\x87 \xe3\x80\x91", Font16CN, BLACK, WHITE); // 【 空气质量指标 】
|
||||
|
||||
// 第 1 列:CO2 (X: 15 ~ 125)
|
||||
Paint_DrawString_EN(15, 180, "CO", &Font16, BLACK, WHITE);
|
||||
Paint_DrawString_EN(37, 184, "2", &Font12, BLACK, WHITE);
|
||||
Paint_DrawString_EN(47, 182, "(ppm)", &Font12, BLACK, WHITE);
|
||||
|
||||
if (!data.co2.empty()) {
|
||||
Paint_DrawString_EN(15, 204, data.co2.c_str(), &Font20, BLACK, WHITE);
|
||||
float co2Val = strtof(data.co2.c_str(), nullptr);
|
||||
if (co2Val <= 800.0f) {
|
||||
Paint_DrawString_UTF8(15, 236, "\xe4\xbc\x98", Font16CN, BLACK, WHITE); // 优
|
||||
} else if (co2Val <= 1200.0f) {
|
||||
Paint_DrawString_UTF8(15, 236, "\xe8\x89\xaf", Font16CN, BLACK, WHITE); // 良
|
||||
} else {
|
||||
Paint_DrawString_UTF8(15, 236, "\xe9\x9c\x80\xe9\x80\x9a\xe9\xa3\x8e", Font16CN, BLACK, WHITE); // 需通风
|
||||
}
|
||||
} else {
|
||||
Paint_DrawString_EN(15, 204, "--", &Font20, BLACK, WHITE);
|
||||
}
|
||||
|
||||
// 列分割线 1
|
||||
Paint_DrawLine(135, 180, 135, 255, BLACK, LINE_STYLE_DOTTED, DOT_PIXEL_1X1);
|
||||
|
||||
// 第 2 列:TVOC (X: 145 ~ 255)
|
||||
Paint_DrawString_EN(145, 180, "TVOC", &Font16, BLACK, WHITE);
|
||||
Paint_DrawString_EN(193, 182, "(mg/m", &Font12, BLACK, WHITE);
|
||||
Paint_DrawString_EN(241, 179, "3", &Font8, BLACK, WHITE);
|
||||
Paint_DrawString_EN(248, 182, ")", &Font12, BLACK, WHITE);
|
||||
|
||||
if (!data.tvoc.empty()) {
|
||||
Paint_DrawString_EN(145, 204, data.tvoc.c_str(), &Font20, BLACK, WHITE);
|
||||
float tvocVal = strtof(data.tvoc.c_str(), nullptr);
|
||||
if (tvocVal <= 0.6f) {
|
||||
Paint_DrawString_UTF8(145, 236, "\xe6\xad\xa3\xe5\xb8\xb8", Font16CN, BLACK, WHITE); // 正常
|
||||
} else {
|
||||
Paint_DrawString_UTF8(145, 236, "\xe8\xb6\x85\xe6\xa0\x87", Font16CN, BLACK, WHITE); // 超标
|
||||
}
|
||||
} else {
|
||||
Paint_DrawString_EN(145, 204, "--", &Font20, BLACK, WHITE);
|
||||
}
|
||||
|
||||
// 列分割线 2
|
||||
Paint_DrawLine(265, 180, 265, 255, BLACK, LINE_STYLE_DOTTED, DOT_PIXEL_1X1);
|
||||
|
||||
// 第 3 列:CH2O (X: 275 ~ 385)
|
||||
Paint_DrawString_EN(275, 180, "CH", &Font16, BLACK, WHITE);
|
||||
Paint_DrawString_EN(297, 184, "2", &Font12, BLACK, WHITE);
|
||||
Paint_DrawString_EN(307, 180, "O", &Font16, BLACK, WHITE);
|
||||
Paint_DrawString_EN(320, 182, "(mg/m", &Font12, BLACK, WHITE);
|
||||
Paint_DrawString_EN(368, 179, "3", &Font8, BLACK, WHITE);
|
||||
Paint_DrawString_EN(375, 182, ")", &Font12, BLACK, WHITE);
|
||||
|
||||
if (!data.ch2o.empty()) {
|
||||
Paint_DrawString_EN(275, 204, data.ch2o.c_str(), &Font20, BLACK, WHITE);
|
||||
float ch2oVal = strtof(data.ch2o.c_str(), nullptr);
|
||||
if (ch2oVal <= 0.08f) {
|
||||
Paint_DrawString_UTF8(275, 236, "\xe5\xae\x89\xe5\x85\xa8", Font16CN, BLACK, WHITE); // 安全
|
||||
} else {
|
||||
Paint_DrawString_UTF8(275, 236, "\xe8\xb6\x85\xe6\xa0\x87", Font16CN, BLACK, WHITE); // 超标
|
||||
}
|
||||
} else {
|
||||
Paint_DrawString_EN(275, 204, "--", &Font20, BLACK, WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 底部网络及硬件信息栏
|
||||
// Y: 268 ~ 300
|
||||
// =========================================================================
|
||||
void DisplayManager::drawBottomInfoBar(const std::string &ip,
|
||||
const std::string &mac) {
|
||||
Paint_DrawLine(0, EPD_HEIGHT - 25, EPD_WIDTH, EPD_HEIGHT - 25, BLACK,
|
||||
LINE_STYLE_SOLID, DOT_PIXEL_1X1);
|
||||
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "IP: %s", ip.empty() ? "0.0.0.0" : ip.c_str());
|
||||
Paint_DrawString_EN(10, EPD_HEIGHT - 18, buf, &Font12, BLACK, WHITE);
|
||||
|
||||
snprintf(buf, sizeof(buf), "MAC: %s", mac.c_str());
|
||||
int macX = EPD_WIDTH - static_cast<int>(strlen(buf)) * 7 - 10;
|
||||
if (macX < 200) macX = 200;
|
||||
Paint_DrawString_EN(macX, EPD_HEIGHT - 18, buf, &Font12, BLACK, WHITE);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 错误页面
|
||||
// =========================================================================
|
||||
void DisplayManager::drawError(const std::string &errorMsg) {
|
||||
Paint_DrawString_UTF8(10, 80, "\xe7\xb3\xbb\xe7\xbb\x9f\xe5\xbc\x82\xe5\xb8\xb8", Font16CN, BLACK, WHITE); // 系统异常
|
||||
Paint_DrawString_EN(10, 120, errorMsg.c_str(), &Font16, BLACK, WHITE);
|
||||
Paint_DrawString_UTF8(10, 150, "\xe5\xb0\x86\xe5\x9c\xa8\xe4\xb8\x8b\xe4\xb8\xaa\xe5\x91\xa8\xe6\x9c\x9f\xe9\x87\x8d\xe8\xaf\x95...", Font16CN, BLACK, WHITE); // 将在下个周期重试...
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @file DisplayManager.h
|
||||
* @brief 墨水屏 UI 管理器,负责环境监测大屏数据的绘制。
|
||||
*
|
||||
* 屏幕尺寸:400 × 300 像素,三色(黑/白/红)或纯黑白模式。
|
||||
* 布局分区:
|
||||
* ┌──────────────────────────────────────────────┐
|
||||
* │ 顶部横幅(标题 + WiFi 连接状态) │ ~28px
|
||||
* ├──────────────────────────────────────────────┤
|
||||
* │ 【温湿度看板】 │
|
||||
* │ 室内温度 (Font24) | 相对湿度 (Font24) │ ~125px
|
||||
* ├──────────────────────────────────────────────┤
|
||||
* │ 【空气质量指标】 │
|
||||
* │ CO₂ ppm | TVOC mg/m³ | CH₂O mg/m³ │ ~110px
|
||||
* ├──────────────────────────────────────────────┤
|
||||
* │ 底部网络信息栏(IP + MAC 地址) │ ~25px
|
||||
* └──────────────────────────────────────────────┘
|
||||
*/
|
||||
#ifndef DISPLAY_MANAGER_H
|
||||
#define DISPLAY_MANAGER_H
|
||||
|
||||
#include "DashboardFetcher.h"
|
||||
#include <string>
|
||||
|
||||
class DisplayManager {
|
||||
public:
|
||||
DisplayManager();
|
||||
~DisplayManager();
|
||||
|
||||
/// 初始化 E-Paper SPI / GPIO 硬件
|
||||
void initHardware();
|
||||
|
||||
/// 清空帧缓冲区,准备新一帧绘制
|
||||
void beginDrawing();
|
||||
|
||||
/// 全量刷新并进入深度休眠
|
||||
void updateFullAndSleep();
|
||||
|
||||
/// 快速刷新并进入深度休眠(黑白模式或仅更新黑白图层)
|
||||
void updateFastAndSleep();
|
||||
|
||||
// ====== UI 绘制方法 ======
|
||||
|
||||
/// 绘制顶部横幅(应用标题 + WiFi 状态)
|
||||
void drawTopBanner(const std::string &networkStatus);
|
||||
|
||||
/// 绘制温湿度核心看板(大字号温度、湿度与采集时间)
|
||||
void drawThermoHygrometer(const DashboardData &data);
|
||||
|
||||
/// 绘制空气质量指标区(CO2、TVOC、甲醛三列卡片)
|
||||
void drawAirQuality(const DashboardData &data);
|
||||
|
||||
/// 绘制底部网络及设备信息栏(IP + MAC)
|
||||
void drawBottomInfoBar(const std::string &ip, const std::string &mac);
|
||||
|
||||
/// 绘制全屏错误提示(网络异常等)
|
||||
void drawError(const std::string &errorMsg);
|
||||
|
||||
private:
|
||||
/// 切换绘制目标到红色图层(true)或黑白图层(false)
|
||||
void switchColor(bool toRed);
|
||||
};
|
||||
|
||||
#endif // DISPLAY_MANAGER_H
|
||||
@@ -0,0 +1,55 @@
|
||||
menu " 网络配置"
|
||||
|
||||
config WIFI_SSID
|
||||
string "WiFi 名称"
|
||||
default "seaHi_2.4G"
|
||||
help
|
||||
WiFi 名称 (SSID)
|
||||
|
||||
config WIFI_PASSWORD
|
||||
string "WiFi 密码"
|
||||
default "mypassword"
|
||||
help
|
||||
WiFi 密码
|
||||
|
||||
config NTP_SERVER
|
||||
string "NTP 服务器地址"
|
||||
default "ntp1.aliyun.com"
|
||||
help
|
||||
用于系统时间同步的 NTP 服务器地址。
|
||||
国内推荐:ntp1.aliyun.com、ntp.tencent.com
|
||||
国际推荐:pool.ntp.org、time.google.com
|
||||
|
||||
config NTP_SYNC_TIMEOUT_SEC
|
||||
int "NTP 同步超时时间 (秒)"
|
||||
default 15
|
||||
range 5 60
|
||||
help
|
||||
等待 NTP 时间同步完成的最大超时时间(秒)。
|
||||
默认 15 秒,范围 5 ~ 60 秒。
|
||||
|
||||
endmenu
|
||||
|
||||
menu " 硬件与显示配置"
|
||||
|
||||
choice DISPLAY_COLOR_MODE
|
||||
prompt "墨水屏颜色模式"
|
||||
default DISPLAY_BWR
|
||||
help
|
||||
选择墨水屏的颜色类型。
|
||||
|
||||
config DISPLAY_BWR
|
||||
bool "黑白红三色 (BWR)"
|
||||
config DISPLAY_BW
|
||||
bool "纯黑白 (BW)"
|
||||
endchoice
|
||||
|
||||
config DISPLAY_REFRESH_INTERVAL_MIN
|
||||
int "墨水屏刷新间隔 (分钟)"
|
||||
default 15
|
||||
range 1 1440
|
||||
help
|
||||
每次采集数据并刷新墨水屏的时间间隔(分钟)。
|
||||
默认 15 分钟。范围 1 分钟 ~ 1440 分钟(1 天)。
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @file SensorDriver.cpp
|
||||
* @brief UART 空气质量传感器驱动实现。
|
||||
*
|
||||
* 使用 ESP-IDF UART 驱动接收传感器主动推送的 9 字节数据帧,
|
||||
* 解析 TVOC / CH₂O / CO₂ 三项空气质量指标。
|
||||
*/
|
||||
#include "SensorDriver.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "SensorDriver";
|
||||
|
||||
/// 使用 UART1(UART0 被 ESP32 系统日志占用)
|
||||
static constexpr uart_port_t kUartPort = UART_NUM_1;
|
||||
|
||||
/// UART 接收缓冲区大小(字节),足以容纳多帧数据
|
||||
static constexpr int kBufSize = 256;
|
||||
|
||||
/// 传感器数据引脚定义(与硬件接线对应)
|
||||
static constexpr int kRxPin = 0; ///< GPIO0 接传感器 B(TXD)
|
||||
static constexpr int kTxPin = 1; ///< GPIO1 接传感器 A(RXD)
|
||||
|
||||
SensorDriver::SensorDriver() {}
|
||||
SensorDriver::~SensorDriver() {}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 初始化 UART1:9600bps, 8N1
|
||||
// --------------------------------------------------------------------------
|
||||
void SensorDriver::init() {
|
||||
ESP_LOGI(TAG, "初始化 UART1 (RX=GPIO%d, TX=GPIO%d, 9600bps)", kRxPin, kTxPin);
|
||||
|
||||
uart_config_t uartConfig = {};
|
||||
uartConfig.baud_rate = 9600;
|
||||
uartConfig.data_bits = UART_DATA_8_BITS;
|
||||
uartConfig.parity = UART_PARITY_DISABLE;
|
||||
uartConfig.stop_bits = UART_STOP_BITS_1;
|
||||
uartConfig.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uartConfig.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
// 安装 UART 驱动并配置引脚
|
||||
ESP_ERROR_CHECK(uart_driver_install(kUartPort, kBufSize * 2, 0, 0, nullptr, 0));
|
||||
ESP_ERROR_CHECK(uart_param_config(kUartPort, &uartConfig));
|
||||
ESP_ERROR_CHECK(uart_set_pin(kUartPort, kTxPin, kRxPin,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
ESP_LOGI(TAG, "UART1 初始化完成");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 高低字节换算为浓度:(high * 256 + low) * 0.001 mg/m³
|
||||
// --------------------------------------------------------------------------
|
||||
float SensorDriver::toConcentration(uint8_t high, uint8_t low) const {
|
||||
return static_cast<float>(high * 256 + low) * 0.001f;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// 从 UART 缓冲区读取一帧数据并解析
|
||||
//
|
||||
// 读取策略:
|
||||
// 1. 先清空 UART 接收缓冲区中的旧数据
|
||||
// 2. 等待传感器推送新的一帧数据(短轮询,避免长时间阻塞)
|
||||
// 3. 读取实际可用的字节数(而非请求固定大小导致阻塞)
|
||||
// 4. 从后向前搜索最新的完整帧 → 校验和 → 解析浓度
|
||||
// --------------------------------------------------------------------------
|
||||
SensorReading SensorDriver::read(int timeoutMs) {
|
||||
// 默认返回全 NAN(表示读取失败)
|
||||
SensorReading result = { NAN, NAN, NAN };
|
||||
|
||||
// 清空 UART 缓冲区中的旧数据,确保后续读到的是最新帧
|
||||
uart_flush_input(kUartPort);
|
||||
|
||||
// 等待传感器新数据到达(轮询方式,每 100ms 检查一次)
|
||||
// 传感器通常每 1~2 秒推送一帧,所以最多等 timeoutMs 即可
|
||||
int waitedMs = 0;
|
||||
size_t available = 0;
|
||||
while (waitedMs < timeoutMs) {
|
||||
uart_get_buffered_data_len(kUartPort, &available);
|
||||
if (available >= static_cast<size_t>(kFrameSize)) {
|
||||
// 缓冲区中已有至少一帧数据,可以读取
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // 短暂休眠,释放 CPU
|
||||
waitedMs += 100;
|
||||
}
|
||||
|
||||
if (available < static_cast<size_t>(kFrameSize)) {
|
||||
ESP_LOGW(TAG, "UART 等待 %dms 超时,未收到足够数据(仅 %d 字节)",
|
||||
timeoutMs, static_cast<int>(available));
|
||||
return result;
|
||||
}
|
||||
|
||||
// 只读取实际可用的字节数(不超过缓冲区上限),避免阻塞
|
||||
int toRead = (available > static_cast<size_t>(kBufSize))
|
||||
? kBufSize
|
||||
: static_cast<int>(available);
|
||||
|
||||
uint8_t buf[kBufSize];
|
||||
int len = uart_read_bytes(kUartPort, buf, toRead, pdMS_TO_TICKS(100));
|
||||
|
||||
if (len <= 0) {
|
||||
ESP_LOGW(TAG, "UART 读取返回 %d,无有效数据", len);
|
||||
return result;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "UART 收到 %d 字节", len);
|
||||
|
||||
// 在缓冲区中从后向前搜索最后一个完整帧(取最新数据)
|
||||
int frameStart = -1;
|
||||
for (int i = len - kFrameSize; i >= 0; i--) {
|
||||
if (buf[i] == kHeaderB1 && buf[i + 1] == kHeaderB2) {
|
||||
frameStart = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (frameStart < 0) {
|
||||
ESP_LOGW(TAG, "未找到有效帧头 (0x2C 0xE4)");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 提取 9 字节帧
|
||||
uint8_t *frame = &buf[frameStart];
|
||||
|
||||
// 校验和验证:uint8(B1 + B2 + ... + B8) == B9
|
||||
uint8_t checksum = 0;
|
||||
for (int i = 0; i < kFrameSize - 1; i++) {
|
||||
checksum += frame[i];
|
||||
}
|
||||
|
||||
if (checksum != frame[kFrameSize - 1]) {
|
||||
ESP_LOGW(TAG, "校验和失败: 计算值=0x%02X, 接收值=0x%02X",
|
||||
checksum, frame[kFrameSize - 1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 解析各项浓度值
|
||||
// TVOC 和 CH₂O 用 ×0.001 换算为 mg/m³
|
||||
result.tvoc = toConcentration(frame[2], frame[3]);
|
||||
result.ch2o = toConcentration(frame[4], frame[5]);
|
||||
// CO₂ 直接用原始值(高×256+低)作为 ppm,不需要 ×0.001
|
||||
result.co2 = static_cast<float>(frame[6] * 256 + frame[7]);
|
||||
|
||||
ESP_LOGI(TAG, "解析成功: TVOC=%.3f mg/m³, CH2O=%.3f mg/m³, CO2=%.0f ppm",
|
||||
result.tvoc, result.ch2o, result.co2);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @file SensorDriver.h
|
||||
* @brief UART 空气质量传感器驱动,解析 TVOC / CH₂O / CO₂ 数据帧。
|
||||
*
|
||||
* 传感器通信协议:
|
||||
* 波特率 9600,8N1
|
||||
* 数据帧固定 9 字节:
|
||||
* B1(0x2C) B2(0xE4) B3(TVOC高) B4(TVOC低) B5(CH2O高) B6(CH2O低)
|
||||
* B7(CO2高) B8(CO2低) B9(校验和)
|
||||
*
|
||||
* 浓度值(mg/m³) = (高字节 × 256 + 低字节) × 0.001
|
||||
* 校验和:uint8(B1 + B2 + ... + B8)
|
||||
*
|
||||
* 硬件接线:
|
||||
* 传感器 B(TXD) → ESP32-C3 GPIO0 (UART1_RX)
|
||||
* 传感器 A(RXD) → ESP32-C3 GPIO1 (UART1_TX)
|
||||
*/
|
||||
#ifndef SENSOR_DRIVER_H
|
||||
#define SENSOR_DRIVER_H
|
||||
|
||||
#include <cmath>
|
||||
|
||||
/// 传感器一次采集的结果
|
||||
struct SensorReading {
|
||||
float tvoc; ///< TVOC 浓度 (mg/m³),无效时为 NAN
|
||||
float ch2o; ///< 甲醛浓度 (mg/m³),无效时为 NAN
|
||||
float co2; ///< CO₂ 浓度 (ppm),无效时为 NAN(注意:单位与 TVOC/CH₂O 不同)
|
||||
|
||||
/// 检查是否所有字段都无效(读取失败时返回全 NAN)
|
||||
bool isValid() const {
|
||||
return !std::isnan(tvoc) || !std::isnan(ch2o) || !std::isnan(co2);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief UART 空气质量传感器驱动。
|
||||
*
|
||||
* 使用 ESP-IDF 的 UART 驱动,在 UART1 上以 9600bps 接收传感器帧数据。
|
||||
* 传感器会主动推送数据,调用 read() 时从 UART 缓冲区读取并解析最新一帧。
|
||||
*/
|
||||
class SensorDriver {
|
||||
public:
|
||||
SensorDriver();
|
||||
~SensorDriver();
|
||||
|
||||
/// 初始化 UART1 硬件(GPIO0=RX, GPIO1=TX, 9600bps, 8N1)
|
||||
void init();
|
||||
|
||||
/**
|
||||
* @brief 从 UART 缓冲区读取并解析一帧传感器数据。
|
||||
*
|
||||
* 读取时会在缓冲区中搜索帧头 0x2C 0xE4,然后读取完整 9 字节帧,
|
||||
* 校验和通过后解析为 SensorReading。
|
||||
*
|
||||
* @param timeoutMs 等待数据的超时时间(毫秒),默认 3000ms
|
||||
* @return SensorReading 结构体,读取失败时所有字段为 NAN
|
||||
*/
|
||||
SensorReading read(int timeoutMs = 3000);
|
||||
|
||||
private:
|
||||
static constexpr int kFrameSize = 9; ///< 数据帧固定 9 字节
|
||||
static constexpr uint8_t kHeaderB1 = 0x2C; ///< 帧头字节 1
|
||||
static constexpr uint8_t kHeaderB2 = 0xE4; ///< 帧头字节 2
|
||||
|
||||
/**
|
||||
* @brief 将高低字节换算为浓度值。
|
||||
* @param high 高字节
|
||||
* @param low 低字节
|
||||
* @return 浓度值 (mg/m³)
|
||||
*/
|
||||
float toConcentration(uint8_t high, uint8_t low) const;
|
||||
};
|
||||
|
||||
#endif // SENSOR_DRIVER_H
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* @file WifiManager.cpp
|
||||
* @brief Implementation of the WifiManager class.
|
||||
*/
|
||||
#include "WifiManager.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_wifi.h"
|
||||
#include <cstring>
|
||||
|
||||
static const char *TAG = "WifiManager";
|
||||
|
||||
// Event bits used to signal completion or failure of Wi-Fi connection
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
|
||||
WifiManager::WifiManager(const std::string &ssid, const std::string &password)
|
||||
: ssid_(ssid), password_(password), wifi_event_group_(nullptr) {}
|
||||
|
||||
WifiManager::~WifiManager() {
|
||||
// Clean up registered event handlers and destroy the FreeRTOS event group.
|
||||
if (wifi_event_group_) {
|
||||
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
|
||||
instance_got_ip_);
|
||||
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
||||
instance_any_id_);
|
||||
vEventGroupDelete(wifi_event_group_);
|
||||
}
|
||||
}
|
||||
|
||||
bool WifiManager::connect() {
|
||||
// Create an event group to block main execution until connection finishes.
|
||||
wifi_event_group_ = xEventGroupCreate();
|
||||
|
||||
// Initialize the underlying TCP/IP stack and default event loop.
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
// Initialize the Wi-Fi driver with standard configuration values.
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
// Register internal event handlers to process Wi-Fi and IP events.
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(
|
||||
WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiManager::eventHandler, this,
|
||||
&instance_any_id_));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(
|
||||
IP_EVENT, IP_EVENT_STA_GOT_IP, &WifiManager::eventHandler, this,
|
||||
&instance_got_ip_));
|
||||
|
||||
// Set up the Wi-Fi station configuration structures.
|
||||
wifi_config_t wifi_config = {};
|
||||
strncpy((char *)wifi_config.sta.ssid, ssid_.c_str(),
|
||||
sizeof(wifi_config.sta.ssid) - 1);
|
||||
strncpy((char *)wifi_config.sta.password, password_.c_str(),
|
||||
sizeof(wifi_config.sta.password) - 1);
|
||||
|
||||
// Set the hardware mode to station mode (client) and commit settings.
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
ESP_LOGI(TAG, "wifi_init_sta finished.");
|
||||
|
||||
// Wait indefinitely until either the connected or failed bit is set.
|
||||
EventBits_t bits =
|
||||
xEventGroupWaitBits(wifi_event_group_, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||||
pdFALSE, pdFALSE, portMAX_DELAY);
|
||||
|
||||
// Evaluate the outcome of the connection process.
|
||||
if (bits & WIFI_CONNECTED_BIT) {
|
||||
ESP_LOGI(TAG, "connected to ap SSID:%s", ssid_.c_str());
|
||||
return true;
|
||||
} else if (bits & WIFI_FAIL_BIT) {
|
||||
ESP_LOGI(TAG, "Failed to connect to SSID:%s", ssid_.c_str());
|
||||
return false;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "UNEXPECTED EVENT");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void WifiManager::eventHandler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data) {
|
||||
// Recover the 'this' instance from the void pointer argument.
|
||||
WifiManager *instance = static_cast<WifiManager *>(arg);
|
||||
if (!instance)
|
||||
return;
|
||||
|
||||
// Dispatch the event to the appropriate handlers based on its base type.
|
||||
if (event_base == WIFI_EVENT) {
|
||||
instance->handleWifiEvent(event_id, event_data);
|
||||
} else if (event_base == IP_EVENT) {
|
||||
instance->handleIpEvent(event_id, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
void WifiManager::handleWifiEvent(int32_t event_id, void *event_data) {
|
||||
if (event_id == WIFI_EVENT_STA_START) {
|
||||
// When the station has started, immediately kick off a connection.
|
||||
esp_wifi_connect();
|
||||
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
// Attempt continuous reconnection, without giving up.
|
||||
ESP_LOGI(TAG, "retry to connect to the AP in 3 seconds...");
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
esp_wifi_connect();
|
||||
// Intentionally omit setting WIFI_FAIL_BIT to keep retrying indefinitely,
|
||||
// ensuring the application never completely terminates due to network
|
||||
// failure.
|
||||
}
|
||||
}
|
||||
|
||||
void WifiManager::handleIpEvent(int32_t event_id, void *event_data) {
|
||||
if (event_id == IP_EVENT_STA_GOT_IP) {
|
||||
// Received a DHCP lease from the AP.
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
|
||||
// Convert raw IP address into human-readable string.
|
||||
char ip_buf[20];
|
||||
sprintf(ip_buf, IPSTR, IP2STR(&event->ip_info.ip));
|
||||
ip_addr_ = ip_buf;
|
||||
|
||||
// Retrieve and format the MAC address.
|
||||
uint8_t mac[6];
|
||||
esp_wifi_get_mac(WIFI_IF_STA, mac);
|
||||
char mac_buf[20];
|
||||
sprintf(mac_buf, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2],
|
||||
mac[3], mac[4], mac[5]);
|
||||
mac_ = mac_buf;
|
||||
|
||||
// Fetch details like RSSI directly from the active AP connection record.
|
||||
wifi_ap_record_t ap_info;
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
rssi_ = ap_info.rssi;
|
||||
}
|
||||
|
||||
// Signal the main task that the connection sequence has successfully
|
||||
// completed.
|
||||
xEventGroupSetBits(wifi_event_group_, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
// Accessor implementations returning internal state strings.
|
||||
std::string WifiManager::getIp() const { return ip_addr_; }
|
||||
std::string WifiManager::getMac() const { return mac_; }
|
||||
std::string WifiManager::getSsid() const { return ssid_; }
|
||||
int8_t WifiManager::getRssi() const { return rssi_; }
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @file WifiManager.h
|
||||
* @brief Provides an interface for managing the ESP32 Wi-Fi connection.
|
||||
*/
|
||||
#ifndef WIFI_MANAGER_H
|
||||
#define WIFI_MANAGER_H
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief Class responsible for connecting to and monitoring Wi-Fi.
|
||||
*
|
||||
* This class abstracts the ESP-IDF Wi-Fi driver and event loop, providing
|
||||
* synchronous connection methods and accessors for network states like IP
|
||||
* and MAC addresses.
|
||||
*/
|
||||
class WifiManager {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a WifiManager instance.
|
||||
*
|
||||
* @param ssid The SSID of the target Wi-Fi network.
|
||||
* @param password The password for the target Wi-Fi network.
|
||||
*/
|
||||
WifiManager(const std::string &ssid, const std::string &password);
|
||||
|
||||
/**
|
||||
* @brief Destructor. Cleans up event handlers and FreeRTOS resources.
|
||||
*/
|
||||
~WifiManager();
|
||||
|
||||
/**
|
||||
* @brief Starts the Wi-Fi connection process and blocks until connected.
|
||||
*
|
||||
* @return true if successfully connected and an IP address was acquired,
|
||||
* false otherwise.
|
||||
*/
|
||||
bool connect();
|
||||
|
||||
/** @brief Returns the acquired IPv4 address as a string, or a fallback
|
||||
* message if not connected. */
|
||||
std::string getIp() const;
|
||||
|
||||
/** @brief Returns the station MAC address as a formatted string (e.g.,
|
||||
* "AA:BB:CC:DD:EE:FF"). */
|
||||
std::string getMac() const;
|
||||
|
||||
/** @brief Returns the SSID of the currently configured Wi-Fi network. */
|
||||
std::string getSsid() const;
|
||||
|
||||
/** @brief Returns the Received Signal Strength Indicator (RSSI) of the
|
||||
* connected AP. */
|
||||
int8_t getRssi() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Static event handler dispatcher registered with the ESP event loop.
|
||||
*/
|
||||
static void eventHandler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data);
|
||||
|
||||
/**
|
||||
* @brief Processes Wi-Fi related events (e.g., station start, disconnect).
|
||||
*/
|
||||
void handleWifiEvent(int32_t event_id, void *event_data);
|
||||
|
||||
/**
|
||||
* @brief Processes IP related events (e.g., getting an IP address).
|
||||
*/
|
||||
void handleIpEvent(int32_t event_id, void *event_data);
|
||||
|
||||
std::string ssid_; ///< Stored SSID payload.
|
||||
std::string password_; ///< Stored password payload.
|
||||
|
||||
std::string ip_addr_{"Connecting..."}; ///< Currently assigned IP.
|
||||
std::string mac_{"N/A"}; ///< Hardware MAC address.
|
||||
int8_t rssi_{0}; ///< Expected signal strength.
|
||||
|
||||
EventGroupHandle_t
|
||||
wifi_event_group_; ///< FreeRTOS event group to block on connection state.
|
||||
esp_event_handler_instance_t
|
||||
instance_any_id_; ///< Handler instance for generic Wi-Fi events.
|
||||
esp_event_handler_instance_t
|
||||
instance_got_ip_; ///< Handler instance for successful IP lease event.
|
||||
};
|
||||
|
||||
#endif // WIFI_MANAGER_H
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @file main.cpp
|
||||
* @brief Main entry point for the ESP-IDF E-paper Demo.
|
||||
*
|
||||
* This file handles the initial NVS flash initialization and bootstraps the
|
||||
* main application execution encapsulated within the App class.
|
||||
*/
|
||||
#include "App.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
static const char *TAG = "EPD_DEMO_MAIN";
|
||||
|
||||
/**
|
||||
* @brief Application entry point called by the ESP-IDF framework.
|
||||
*
|
||||
* It ensures the Non-Volatile Storage (NVS) is initialized before passing
|
||||
* control to the App instance.
|
||||
*/
|
||||
extern "C" void app_main(void) {
|
||||
ESP_LOGI(TAG, "Starting NVS...");
|
||||
|
||||
// Initialize NVS, erasing it first if it contains an unsupported version
|
||||
// or no free pages.
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
|
||||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// Instantiate and run the main application logic
|
||||
App app;
|
||||
app.init();
|
||||
app.run();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# 自定义分区表(适配中文字体数据,app 分区扩大到 2MB)
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 0x200000,
|
||||
|
@@ -0,0 +1,12 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) 5.5.1 Project Minimal Configuration
|
||||
#
|
||||
CONFIG_IDF_TARGET="esp32c3"
|
||||
CONFIG_WIFI_PASSWORD="seahi.me"
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
# 自定义分区表(app 分区扩大到 2MB,容纳中文字体数据)
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
# 墨水屏颜色模式(BWR=黑白红三色,BW=纯黑白)
|
||||
CONFIG_DISPLAY_BWR=y
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
从 Unifont hex 数据中提取中文 16×16 点阵,生成 ESP32 可用的 C++ 数组。
|
||||
|
||||
用法:
|
||||
python3 tools/gen_font.py <unifont.hex.gz>
|
||||
|
||||
输出:
|
||||
main/font16cn_data.cpp
|
||||
|
||||
默认收录 GB2312 一级字(3,755 个常用字)+ 自定义扩展字符,
|
||||
覆盖日常中文 99.7% 以上。如需添加额外字符,在 EXTRA_CHARS 中追加即可。
|
||||
"""
|
||||
import gzip
|
||||
import sys
|
||||
import os
|
||||
|
||||
# ========== GB2312 一级字 Unicode 码点列表 ==========
|
||||
# GB2312 一级字包含 3,755 个常用字,按拼音排序
|
||||
# 以下列表通过 GB2312 区位码 16-55 区转换得到
|
||||
def _gb2312_level1_codepoints():
|
||||
"""生成 GB2312 一级字(16-55 区)的 Unicode 码点集合"""
|
||||
import codecs
|
||||
codepoints = set()
|
||||
for qu in range(16, 56): # 16~55 区
|
||||
for wei in range(1, 95): # 1~94 位
|
||||
b1 = qu + 0xA0
|
||||
b2 = wei + 0xA0
|
||||
try:
|
||||
ch = bytes([b1, b2]).decode('gb2312')
|
||||
codepoints.add(ord(ch))
|
||||
except (UnicodeDecodeError, ValueError):
|
||||
continue
|
||||
return codepoints
|
||||
|
||||
|
||||
# ========== 额外扩展字符(不在 GB2312 一级字中但项目需要的字符)==========
|
||||
EXTRA_CHARS = "" # 在这里追加项目需要的特殊字符
|
||||
|
||||
|
||||
def load_unifont_hex(hex_path):
|
||||
"""加载 Unifont hex 文件,返回 {codepoint: bitmap_bytes} 字典"""
|
||||
font_map = {}
|
||||
opener = gzip.open if hex_path.endswith('.gz') else open
|
||||
with opener(hex_path, 'rt', encoding='ascii') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or ':' not in line:
|
||||
continue
|
||||
cp_str, hex_data = line.split(':', 1)
|
||||
cp = int(cp_str, 16)
|
||||
font_map[cp] = bytes.fromhex(hex_data)
|
||||
return font_map
|
||||
|
||||
|
||||
def char_to_utf8_bytes(cp):
|
||||
"""将 Unicode 码点转为 UTF-8 的 3 个字节(仅支持 U+0800~U+FFFF)"""
|
||||
assert 0x0800 <= cp <= 0xFFFF
|
||||
b1 = 0xE0 | (cp >> 12)
|
||||
b2 = 0x80 | ((cp >> 6) & 0x3F)
|
||||
b3 = 0x80 | (cp & 0x3F)
|
||||
return (b1, b2, b3)
|
||||
|
||||
|
||||
def generate_cpp(font_map, output_path):
|
||||
"""生成 C++ 源文件"""
|
||||
# 合并 GB2312 一级字 + 额外字符
|
||||
target_cps = _gb2312_level1_codepoints()
|
||||
for ch in EXTRA_CHARS:
|
||||
target_cps.add(ord(ch))
|
||||
|
||||
entries = []
|
||||
missing = 0
|
||||
|
||||
# 按 Unicode 排序(确保二分查找有效)
|
||||
for cp in sorted(target_cps):
|
||||
if cp not in font_map:
|
||||
missing += 1
|
||||
continue
|
||||
|
||||
bitmap = font_map[cp]
|
||||
# Unifont 16×16 汉字 = 32 字节
|
||||
if len(bitmap) != 32:
|
||||
missing += 1
|
||||
continue
|
||||
|
||||
utf8 = char_to_utf8_bytes(cp)
|
||||
hex_str = ', '.join(f'0x{b:02X}' for b in bitmap)
|
||||
entries.append(
|
||||
f' // U+{cp:04X} "{chr(cp)}"\n'
|
||||
f' {{ {{ 0x{utf8[0]:02X}, 0x{utf8[1]:02X}, 0x{utf8[2]:02X} }},\n'
|
||||
f' {{ {hex_str} }} }}'
|
||||
)
|
||||
|
||||
total_bytes = len(entries) * 35 # 每个条目约 35 字节
|
||||
cpp_content = f'''\
|
||||
/**
|
||||
* @file font16cn_data.cpp
|
||||
* @brief Unifont 16×16 中文点阵数据(自动生成,请勿手动编辑)。
|
||||
*
|
||||
* 由 tools/gen_font.py 从 Unifont hex 数据自动提取。
|
||||
* 收录 {len(entries)} 个常用中文字符(GB2312 一级字)。
|
||||
* 预计占用 Flash: ~{total_bytes // 1024} KB
|
||||
*
|
||||
* 如需添加字符,编辑 gen_font.py 中的 EXTRA_CHARS 并重新运行。
|
||||
*/
|
||||
#include "ChineseFont.h"
|
||||
|
||||
static const UTF8_Char kFont16CN_Table[] = {{
|
||||
{(","+chr(10)).join(entries)}
|
||||
}};
|
||||
|
||||
const UTF8_Font Font16CN = {{
|
||||
kFont16CN_Table,
|
||||
{len(entries)}, // 字符数
|
||||
8, // ASCII 字符宽度(半角)
|
||||
16, // 中文字符宽度
|
||||
16 // 字符高度
|
||||
}};
|
||||
'''
|
||||
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cpp_content)
|
||||
|
||||
print(f"✅ 已生成 {output_path}")
|
||||
print(f" 收录: {len(entries)} 个汉字")
|
||||
print(f" 未找到: {missing} 个")
|
||||
print(f" 预计 Flash: ~{total_bytes // 1024} KB")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(f"用法: {sys.argv[0]} <unifont.hex 或 unifont.hex.gz>")
|
||||
sys.exit(1)
|
||||
|
||||
hex_path = sys.argv[1]
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
output_path = os.path.join(script_dir, '..', 'components', 'chinese_font', 'src', 'font16cn_data.cpp')
|
||||
|
||||
print(f"📖 加载 Unifont 数据: {hex_path}")
|
||||
font_map = load_unifont_hex(hex_path)
|
||||
print(f" 共加载 {len(font_map)} 个码点")
|
||||
|
||||
generate_cpp(font_map, output_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user