本人是正宗小白,对于大佬们一秒看懂的程序,要发很大功夫才能搞清楚,现在对学习过程中的一些理解写下来,供与我一样白又白的爱好者参考,高手莫喷啊!!
下面研究的是以nrf52811为主控的4.2寸墨水屏的部分程序.
文件名为GUI.C的程序代码如下:源码来源:GitHub - tsl0922/EPD-nRF5: 4.2-inch e-ink display firmware for Nordic nRF51/nRF52, with support for Bluetooth image transfer, NFC wake-up.
#include "fonts.h"
#include "Lunar.h"
#include "GUI.h"
#include
#define GFX_printf_styled(gfx, fg, bg, font, ...) \
GFX_setTextColor(gfx, fg, bg); \
GFX_setFont(gfx, font); \
GFX_printf(gfx, __VA_ARGS__);
static void DrawBattery(Adafruit_GFX *gfx, int16_t x, int16_t y, float voltage)
{
uint8_t level = (uint8_t)(voltage * 100 / 4.2);
GFX_setCursor(gfx, x - 26, y + 9);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "%.1fV", voltage);
GFX_fillRect(gfx, x, y, 20, 10, GFX_WHITE);
GFX_drawRect(gfx, x, y, 20, 10, GFX_BLACK);
GFX_fillRect(gfx, x + 20, y + 4, 2, 2, GFX_BLACK);
GFX_fillRect(gfx, x + 2, y + 2, 16 * level / 100, 6, GFX_BLACK);
}
static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int8_t temp)
{
GFX_setCursor(gfx, x, y);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "%d℃", temp);
}
static void DrawDate(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm)
{
GFX_setCursor(gfx, x, y);
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%d", tm->tm_year + YEAR0);
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "年");
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mon + 1);
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "月");
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mday);
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "日 ");
}
static void DrawDateHeader(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm, struct Lunar_Date *Lunar, gui_data_t *data)
{
DrawDate(gfx, x, y, tm);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "星期%s", Lunar_DayString[tm->tm_wday]);
DrawBattery(gfx, 365, 4, data->voltage);
GFX_setCursor(gfx, x + 270, y);
GFX_printf(gfx, "%s%s%s %s%s", Lunar_MonthLeapString[Lunar->IsLeap], Lunar_MonthString[Lunar->Month],
Lunar_DateString[Lunar->Date], Lunar_StemStrig[LUNAR_GetStem(Lunar)],
Lunar_BranchStrig[LUNAR_GetBranch(Lunar)]);
GFX_setTextColor(gfx, GFX_RED, GFX_WHITE);
GFX_printf(gfx, "%s", Lunar_ZodiacString[LUNAR_GetZodiac(Lunar)]);
GFX_setTextColor(gfx, GFX_BLACK, GFX_WHITE);
GFX_printf(gfx, "年");
}
static void DrawWeekHeader(Adafruit_GFX *gfx, int16_t x, int16_t y)
{
GFX_fillRect(gfx, x, y, 380, 24, GFX_RED);
GFX_fillRect(gfx, x + 50, y, 280, 24, GFX_BLACK);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
for (int i = 0; i < 7; i++) {
GFX_setTextColor(gfx, GFX_WHITE, (i > 0 && i < 6) ? GFX_BLACK : GFX_RED);
GFX_setCursor(gfx, x + 15 + i * 55, y + 16);
GFX_printf(gfx, "%s", Lunar_DayString[i]);
}
}
static void DrawMonthDays(Adafruit_GFX *gfx, tm_t *tm, struct Lunar_Date *Lunar)
{
uint8_t firstDayWeek = get_first_day_week(tm->tm_year + YEAR0, tm->tm_mon + 1);
uint8_t monthMaxDays = thisMonthMaxDays(tm->tm_year + YEAR0, tm->tm_mon + 1);
uint8_t monthDayRows = 1 + (monthMaxDays - (7 - firstDayWeek) + 6) / 7;
for (uint8_t i = 0; i < monthMaxDays; i++) {
uint8_t day = i + 1;
int16_t w = (firstDayWeek + i) % 7;
bool weekend = (w == 0) || (w == 6);
int16_t x = 22 + w * 55;
int16_t y = (monthDayRows > 5 ? 69 : 72) + (firstDayWeek + i) / 7 * (monthDayRows > 5 ? 39 : 48);
if (day == tm->tm_mday) {
GFX_fillCircle(gfx, x + 11, y + (monthDayRows > 5 ? 10 : 12), 20, GFX_RED);
GFX_setTextColor(gfx, GFX_WHITE, GFX_RED);
} else {
GFX_setTextColor(gfx, weekend ? GFX_RED : GFX_BLACK, GFX_WHITE);
}
GFX_setFont(gfx, u8g2_font_helvB14_tn);
GFX_setCursor(gfx, x + (day < 10 ? 6 : 2), y + 10);
GFX_printf(gfx, "%d", day);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_setCursor(gfx, x, y + 24);
uint8_t JQdate;
if (GetJieQi(tm->tm_year + YEAR0, tm->tm_mon + 1, day, &JQdate) && JQdate == day) {
uint8_t JQ = (tm->tm_mon + 1 - 1) * 2;
if (day >= 15) JQ++;
if (day != tm->tm_mday) GFX_setTextColor(gfx, GFX_RED, GFX_WHITE);
GFX_printf(gfx, "%s", JieQiStr[JQ]);
} else {
LUNAR_SolarToLunar(Lunar, tm->tm_year + YEAR0, tm->tm_mon + 1, day);
if (Lunar->Date == 1)
GFX_printf(gfx, "%s", Lunar_MonthString[Lunar->Month]);
else
GFX_printf(gfx, "%s", Lunar_DateString[Lunar->Date]);
}
}
}
static void DrawCalendar(Adafruit_GFX *gfx, tm_t *tm, struct Lunar_Date *Lunar, gui_data_t *data)
{
DrawDateHeader(gfx, 10, 28, tm, Lunar, data);
DrawWeekHeader(gfx, 10, 32);
DrawMonthDays(gfx, tm, Lunar);
}
/* Routine to Draw Large 7-Segment formated number
Contributed by William Zaggle.
int n - The number to be displayed
int xLoc = The x location of the upper left corner of the number
int yLoc = The y location of the upper left corner of the number
int cS = The size of the number.
fC is the foreground color of the number
bC is the background color of the number (prevents having to clear previous space)
nD is the number of digit spaces to occupy (must include space for minus sign for numbers < 0).
width: nD*(11*cS+2)-2*cS
height: 20*cS+4
https://forum.arduino.cc/t/fast-7-segment-number-display-for-tft/296619/4
*/
static void Draw7Number(Adafruit_GFX *gfx, int n, unsigned int xLoc, unsigned int yLoc, char cS, unsigned int fC, unsigned int bC, char nD) {
unsigned int num=abs(n),i,t,w,col,h,a,b,j=1,d=0,S2=5*cS,S3=2*cS,S4=7*cS,x1=cS+1,x2=S3+S2+1,y1=yLoc+x1,y3=yLoc+S3+S4+1;
unsigned int seg[7][3]={{x1,yLoc,1},{x2,y1,0},{x2,y3+x1,0},{x1,(2*y3)-yLoc,1},{0,y3+x1,0},{0,y1,0},{x1,y3,1}};
unsigned char nums[12]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40},c=(c=abs(cS))>10?10:(c<1)?1:c,cnt=(cnt=abs(nD))>10?10:(cnt<1)?1:cnt;
for (xLoc+=cnt*(d=S2+(3*S3)+2);cnt>0;cnt--){
for (i=(num>9)?num%10:((!cnt)&&(n<0))?11:((nD<0)&&(!num))?10:num,xLoc-=d,num/=10,j=0;j<7;++j){
col=(nums[i]&(1<tm_hour, x, y, cS, GFX_BLACK, GFX_WHITE, nD);
x += (nD*(11*cS+2)-2*cS) + 2*cS;
GFX_fillRect(gfx, x, y + 4.5*cS+1, 2*cS, 2*cS, GFX_BLACK);
GFX_fillRect(gfx, x, y + 13.5*cS+3, 2*cS, 2*cS, GFX_BLACK);
x += 4*cS;
Draw7Number(gfx, tm->tm_min, x, y, cS, GFX_BLACK, GFX_WHITE, nD);
}
static void DrawClock(Adafruit_GFX *gfx, tm_t *tm, struct Lunar_Date *Lunar, gui_data_t *data)
{
DrawDate(gfx, 40, 36, tm);
GFX_setCursor(gfx, 40, 58);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "周%s", Lunar_DayString[tm->tm_wday]);
GFX_setCursor(gfx, 138, 58);
GFX_printf(gfx, "%s%s%s", Lunar_MonthLeapString[Lunar->IsLeap], Lunar_MonthString[Lunar->Month],
Lunar_DateString[Lunar->Date]);
DrawBattery(gfx, 330, 25, data->voltage);
DrawTemperature(gfx, 330, 58, data->temperature);
GFX_drawFastHLine(gfx, 30, 68, 330, GFX_BLACK);
DrawTime(gfx, tm, 70, 98, 5, 2);
GFX_drawFastHLine(gfx, 30, 232, 330, GFX_BLACK);
GFX_setCursor(gfx, 40, 275);
GFX_setFont(gfx, u8g2_font_wqy12_t_lunar);
GFX_printf(gfx, "%s%s%s年", Lunar_StemStrig[LUNAR_GetStem(Lunar)], Lunar_BranchStrig[LUNAR_GetBranch(Lunar)],
Lunar_ZodiacString[LUNAR_GetZodiac(Lunar)]);
uint8_t day = 0;
uint8_t JQday = GetJieQiStr(tm->tm_year + YEAR0, tm->tm_mon + 1, tm->tm_mday, &day);
if (day == 0) {
GFX_setCursor(gfx, 320, 275);
GFX_printf(gfx, "%s", JieQiStr[JQday % 24]);
} else {
GFX_setCursor(gfx, 300, 265);
GFX_printf(gfx, "离%s", JieQiStr[JQday % 24]);
GFX_setCursor(gfx, 290, 285);
GFX_printf(gfx, "还有%d天", day);
}
}
void DrawGUI(gui_data_t *data, buffer_callback draw, display_mode_t mode)
{
tm_t tm = {0};
struct Lunar_Date Lunar;
transformTime(data->timestamp, &tm);
Adafruit_GFX gfx;
if (data->bwr)
GFX_begin_3c(&gfx, data->width, data->height, PAGE_HEIGHT);
else
GFX_begin(&gfx, data->width, data->height, PAGE_HEIGHT);
GFX_firstPage(&gfx);
do {
GFX_fillScreen(&gfx, GFX_WHITE);
LUNAR_SolarToLunar(&Lunar, tm.tm_year + YEAR0, tm.tm_mon + 1, tm.tm_mday);
switch (mode) {
case MODE_CALENDAR:
DrawCalendar(&gfx, &tm, &Lunar, data);
break;
case MODE_CLOCK:
DrawClock(&gfx, &tm, &Lunar, data);
break;
default:
break;
}
} while(GFX_nextPage(&gfx, draw));
GFX_end(&gfx);
}
第一
#include "fonts.h"
#include "Lunar.h"
#include "GUI.h"
#include
#define GFX_printf_styled(gfx, fg, bg, font, ...) \
GFX_setTextColor(gfx, fg, bg); \
GFX_setFont(gfx, font); \
GFX_printf(gfx, __VA_ARGS__);
这段代码是嵌入式图形界面程序的预处理部分,主要包含头文件引入和带样式打印功能的宏定义。以下是逐行解析和功能说明:
#include "fonts.h" // 自定义字体文件
#include "Lunar.h" // 农历计算相关函数
#include "GUI.h" // 图形界面基础功能(如画线、矩形、文本等)
#include // 标准输入输出(用于printf函数)
fonts.h
u8g2_font_helvB18_tn
(18 号粗体英文)、u8g2_font_wqy9_t_lunar
(9 号中文小字体)。Lunar.h
LUNAR_SolarToLunar
),以及农历相关数据(如天干、地支、生肖字符串)。GUI.h
GFX_setTextColor
设置文字颜色、GFX_fillRect
绘制矩形),通常与特定显示屏驱动(如 Adafruit_GFX 库)配合使用。GFX_begin
/GFX_end
等函数初始化和释放图形上下文。printf
函数用于文本输出,在嵌入式场景中可能被重定向到串口或显示屏。#define GFX_printf_styled(gfx, fg, bg, font, ...) \
GFX_setTextColor(gfx, fg, bg); \
GFX_setFont(gfx, font); \
GFX_printf(gfx, __VA_ARGS__);
GFX_printf_styled
gfx
:图形上下文指针(如Adafruit_GFX *
),指向显示屏驱动对象。fg
:前景色(文字颜色),如GFX_RED
、GFX_BLACK
。bg
:背景色,如GFX_WHITE
。font
:字体类型(如u8g2_font_helvB18_tn
)。...
:可变参数,即printf
的格式化字符串和参数。GFX_setTextColor(gfx, fg, bg)
用于配置文本的显示颜色和背景色。GFX_setFont(gfx, font)
选择文本显示的字体(如大小、风格)。GFX_printf(gfx, __VA_ARGS__)
按指定格式将文本绘制到显示屏上,__VA_ARGS__
表示传递给宏的可变参数。GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "2023"); // 红色大字体显示年份
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "年"); // 黑色小字体显示“年”
代码复用与简化:
GFX_setTextColor
和GFX_setFont
的调用代码,提高开发效率。界面样式统一管理:
适配不同显示屏:
GUI.h
中的底层接口,上层代码(宏的调用)无需变动。扩展功能:
#define GFX_printf_styled(gfx, fg, bg, font, align, ...) \
GFX_setTextColor(gfx, fg, bg); \
GFX_setFont(gfx, font); \
GFX_setCursorByAlign(gfx, align); // 自定义对齐函数 \
GFX_printf(gfx, __VA_ARGS__);
注意事项:
fg
、bg
为显示屏驱动支持的颜色枚举值(如GFX_RED
需在GUI.h
中定义)。这部分代码是图形界面的基础配置,通过头文件引入必要功能,并利用宏定义封装带样式的文本输出逻辑。它为后续界面绘制(如日历、时钟、状态信息)提供了统一的文本显示接口,使代码结构更清晰、维护更便捷。在实际开发中,可结合具体显示屏尺寸和需求,进一步调整字体、颜色和布局参数。
第二
static void DrawBattery(Adafruit_GFX *gfx, int16_t x, int16_t y, float voltage)
{
uint8_t level = (uint8_t)(voltage * 100 / 4.2);
GFX_setCursor(gfx, x - 26, y + 9);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "%.1fV", voltage);
GFX_fillRect(gfx, x, y, 20, 10, GFX_WHITE);
GFX_drawRect(gfx, x, y, 20, 10, GFX_BLACK);
GFX_fillRect(gfx, x + 20, y + 4, 2, 2, GFX_BLACK);
GFX_fillRect(gfx, x + 2, y + 2, 16 * level / 100, 6, GFX_BLACK);
}
这段代码定义了一个名为 DrawBattery
的函数,用于在嵌入式设备的显示屏上绘制电池图标并显示电压值。以下是对代码的详细解析:
static void DrawBattery(Adafruit_GFX *gfx, int16_t x, int16_t y, float voltage)
static
:表示该函数为静态函数,作用域仅限于当前源文件,避免与其他文件中的同名函数冲突。
Adafruit_GFX *gfx
:图形上下文指针,指向显示屏驱动对象(如 Adafruit_ST7735
等),用于调用绘图函数。int16_t x, y
:电池图标的左上角坐标(以像素为单位)。float voltage
:当前电池电压值(单位:V),用于计算电量百分比和显示文本。uint8_t level = (uint8_t)(voltage * 100 / 4.2);
4.2V
,将当前电压转换为百分比(voltage / 4.2 * 100
),并强制转换为无符号 8 位整数(0~100
)。4.35V
),需修改分母 4.2
以适配硬件。GFX_setCursor(gfx, x - 26, y + 9);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "%.1fV", voltage);
x - 26
:将文本绘制位置左移 26 像素,确保电压值显示在电池图标的左侧或上方。y + 9
:垂直方向下移 9 像素,调整文本垂直居中。u8g2_font_wqy9_t_lunar
字体(9 号小字体),适合显示小字信息。"%.1fV"
表示保留一位小数(如 3.7V
),直观展示电压精度。GFX_fillRect(gfx, x, y, 20, 10, GFX_WHITE); // 白色背景矩形
GFX_drawRect(gfx, x, y, 20, 10, GFX_BLACK); // 黑色边框矩形
(x, y)
,宽 20 像素、高 10 像素的白色矩形,作为电池的主体背景。GFX_fillRect(gfx, x + 20, y + 4, 2, 2, GFX_BLACK);
x + 20
:位于电池主体右侧(宽度 20 像素,右侧边缘为 x+20
)。y + 4
:垂直方向居中(电池高度 10 像素,居中位置为 y+4
至 y+6
)。GFX_fillRect(gfx, x + 2, y + 2, 16 * level / 100, 6, GFX_BLACK);
(x+2, y+2)
,距离电池边框左侧和顶部各 2 像素,形成内边距。16 * level / 100
。其中,16
是电池主体内部可用宽度(20 像素总宽 - 左右各 2 像素边距),level
是百分比(0~100
),因此宽度范围为 0~16
像素。level
为 100 时,进度条填满整个内部宽度(16 像素);当 level
为 0 时,不绘制进度条(宽度为 0)。+----------------+ <- 外框(20x10,黑色)
| |
| +----------+ |
| | | | <- 进度条(16x6,黑色)
| | | |
| +----------+ |
| |
+----------------+
^ ^
x,y x+20,y
(左上角) (右侧边缘)
++++++++++++++++ <- 正极(2x2,黑色)
x+20,y+4
0~4.2V
,需修改百分比计算公式,例如: uint8_t level = (uint8_t)((voltage - 3.0) / (4.2 - 3.0) * 100); // 假设最低电压为3.0V
根据电量高低改变进度条颜色(如低电量时显示红色):
uint16_t barColor = (level < 20) ? GFX_RED : ((level < 50) ? GFX_YELLOW : GFX_BLACK);
GFX_fillRect(gfx, x+2, y+2, 16*level/100, 6, barColor);
3.电量文本优化:
同时显示百分比(如 3.7V (88%)
):
GFX_printf(gfx, "%.1fV (%d%%)", voltage, level);
4.抗锯齿处理:
GFX_drawRoundRect
绘制圆角矩形,提升视觉效果: GFX_drawRoundRect(gfx, x, y, 20, 10, 2, GFX_BLACK); // 圆角半径2像素
DrawBattery
函数通过简单的几何图形组合(矩形、文本)实现了电池状态的可视化,适用于嵌入式系统的状态监控界面。其核心逻辑是通过电压计算电量百分比,再通过填充矩形的宽度变化直观展示电量,并配合文本显示精确数值。在实际应用中,可根据硬件特性和界面风格调整坐标、尺寸、颜色等参数,以适配不同场景需求。
第三
static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int8_t temp)
{
GFX_setCursor(gfx, x, y);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "%d℃", temp);
}
这段代码定义了一个名为 DrawTemperature
的函数,用于在显示屏上绘制温度值。以下是对代码的详细解析:
static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int8_t temp)
static
:表示该函数为静态函数,仅在当前文件内可见。Adafruit_GFX *gfx
:图形上下文指针,用于调用绘图 API。int16_t x, y
:温度文本的起始坐标(左上角)。int8_t temp
:温度值(单位:℃),范围为 -128 ~ 127
。GFX_setCursor(gfx, x, y);
(x, y)
,后续文本将从该点开始渲染。GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
u8g2_font_wqy9_t_lunar
字体,这是一个 9 号中文字体,适合显示温度符号(℃)和数字。GFX_printf(gfx, "%d℃", temp);
%d
格式化整数温度值,并追加 ℃
符号(摄氏度)。temp = 25
,则显示 25℃
;若 temp = -5
,则显示 -5℃
。// 在坐标(100, 50)处显示当前温度
DrawTemperature(gfx, 100, 50, 28); // 显示 "28℃"
int8_t
仅支持 -128 ~ 127℃
,若实际温度超出此范围(如工业环境),需改用 int16_t
。static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t temp)
℃
符号,无法适应华氏度(℉)等其他单位需求。static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t temp, const char *unit)
{
GFX_setCursor(gfx, x, y);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "%d%s", temp, unit); // 显示 "28℃" 或 "82℉"
}
static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t temp, uint16_t color)
{
GFX_setTextColor(gfx, color);
// 其他代码保持不变
}
25.5℃
)。static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, float temp)
{
GFX_printf(gfx, "%.1f℃", temp); // 保留一位小数
}
DrawTemperature
函数通过简单的三步(设置坐标、字体、输出文本)实现了温度值的可视化显示。其设计简洁,但可通过参数扩展增强灵活性,以适应不同场景需求。在实际应用中,建议根据硬件特性和业务需求选择合适的数据类型(如 int16_t
或 float
)和显示格式(如是否需要小数精度)。
第四
static void DrawDate(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm)
{
GFX_setCursor(gfx, x, y);
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%d", tm->tm_year + YEAR0);
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "年");
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mon + 1);
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "月");
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mday);
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "日 ");
}
这段代码定义了一个用于绘制日期的函数 DrawDate
,它使用指定的图形上下文在屏幕上以格式化方式显示年、月、日信息。以下是对代码的详细解析:
static void DrawDate(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm)
static
:表明该函数是文件内部静态函数,仅在当前文件可见。Adafruit_GFX *gfx
:图形上下文指针,用于调用绘图 API。int16_t x, y
:绘制起始坐标(左上角)。tm_t *tm
:指向时间结构的指针,包含年、月、日等信息。GFX_setCursor(gfx, x, y);
(x, y)
。GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%d", tm->tm_year + YEAR0);
GFX_RED
:文字颜色为红色。GFX_WHITE
:背景色为白色。u8g2_font_helvB18_tn
:18 号粗体 Helvetica 字体(适合显示数字)。tm->tm_year + YEAR0
将时间结构中的年份转换为实际年份(例如 2023
)。GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "年");
GFX_BLACK
:文字颜色为黑色。u8g2_font_wqy12_t_lunar
:12 号中文字体,用于显示中文单位。GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mon + 1);
%02d
确保月份以两位数字显示(例如 01
、12
)。tm_mon
范围是 0-11
,因此需加 1 转换为实际月份。GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "月");
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mday);
%02d
确保日期以两位数字显示(例如 01
、31
)。GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "日 ");
颜色区分:
GFX_RED
)突出显示。GFX_BLACK
)作为辅助说明。字体大小:
u8g2_font_helvB18_tn
(18 号粗体),视觉上更突出。u8g2_font_wqy12_t_lunar
(12 号),字体稍小。对齐方式:
%02d
确保月份和日期始终以两位数字显示,保证排版整齐。2023年05月19日
,而非 2023年5月19日
。YEAR0
未在代码中定义,需确保其在其他头文件中被正确定义(通常为 1900
)。// 确保YEAR0已定义(通常为1900)
#ifndef YEAR0
#define YEAR0 1900
#endif
tm
指针是否为 NULL
,可能导致空指针引用。if (tm == NULL) return;
#define DATE_NUMBER_COLOR GFX_RED
#define DATE_UNIT_COLOR GFX_BLACK
#define DATE_NUMBER_FONT u8g2_font_helvB18_tn
#define DATE_UNIT_FONT u8g2_font_wqy12_t_lunar
#ifdef LANG_CHINESE
#define DATE_YEAR_STR "年"
#define DATE_MONTH_STR "月"
#define DATE_DAY_STR "日 "
#endif
假设当前时间为 2023年5月19日
,调用:
DrawDate(gfx, 10, 10, ¤t_time);
DrawDate
函数通过交替使用不同字体和颜色,实现了日期信息的分层显示,视觉上更加清晰。其核心逻辑是利用 GFX_printf_styled
宏快速切换样式,并通过 tm
结构体获取系统时间。在实际应用中,可根据显示需求调整字体大小、颜色或添加国际化支持。
第五
static void DrawDateHeader(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm, struct Lunar_Date *Lunar, gui_data_t *data)
{
DrawDate(gfx, x, y, tm);
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "星期%s", Lunar_DayString[tm->tm_wday]);
DrawBattery(gfx, 365, 4, data->voltage);
GFX_setCursor(gfx, x + 270, y);
GFX_printf(gfx, "%s%s%s %s%s", Lunar_MonthLeapString[Lunar->IsLeap], Lunar_MonthString[Lunar->Month],
Lunar_DateString[Lunar->Date], Lunar_StemStrig[LUNAR_GetStem(Lunar)],
Lunar_BranchStrig[LUNAR_GetBranch(Lunar)]);
GFX_setTextColor(gfx, GFX_RED, GFX_WHITE);
GFX_printf(gfx, "%s", Lunar_ZodiacString[LUNAR_GetZodiac(Lunar)]);
GFX_setTextColor(gfx, GFX_BLACK, GFX_WHITE);
GFX_printf(gfx, "年");
}
这段代码定义了一个绘制日期头部信息的函数 DrawDateHeader
,用于在图形界面上显示公历日期、星期、农历信息和电池电量。以下是对代码的详细解析:
static void DrawDateHeader(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm, struct Lunar_Date *Lunar, gui_data_t *data)
gfx
:图形上下文指针,用于调用绘图函数。x, y
:绘制起始坐标(左上角)。tm
:指向 tm_t
结构体的指针,包含公历日期信息。Lunar
:指向农历日期结构体的指针,包含农历信息。data
:指向 gui_data_t
结构体的指针,包含系统数据(如电池电压)。DrawDate
函数)DrawDate(gfx, x, y, tm);
DrawDate
函数,在 (x, y)
位置绘制形如 2023年05月19日
的公历日期。GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
GFX_printf(gfx, "星期%s", Lunar_DayString[tm->tm_wday]);
u8g2_font_wqy9_t_lunar
(9 号中文字体)。tm->tm_wday
返回 0-6
(周日到周六),通过 Lunar_DayString
数组映射为中文(如 日
, 一
, 二
)。星期日
、星期一
。DrawBattery(gfx, 365, 4, data->voltage);
(365, 4)
调用 DrawBattery
函数,显示电池电量和电压(如 3.7V
)。GFX_setCursor(gfx, x + 270, y);
GFX_printf(gfx, "%s%s%s %s%s",
Lunar_MonthLeapString[Lunar->IsLeap], // 是否闰月
Lunar_MonthString[Lunar->Month], // 农历月份
Lunar_DateString[Lunar->Date], // 农历日期
Lunar_StemStrig[LUNAR_GetStem(Lunar)], // 天干
Lunar_BranchStrig[LUNAR_GetBranch(Lunar)] // 地支
);
270
像素开始绘制。闰四月廿三 庚子年
(如果是闰月且农历日期为四月廿三)。GFX_setTextColor(gfx, GFX_RED, GFX_WHITE);
GFX_printf(gfx, "%s", Lunar_ZodiacString[LUNAR_GetZodiac(Lunar)]);
GFX_setTextColor(gfx, GFX_BLACK, GFX_WHITE);
GFX_printf(gfx, "年");
GFX_RED
)突出显示。鼠年
、牛年
。tm_t
结构体(来自
):
struct tm {
int tm_sec; // 秒 (0-60)
int tm_min; // 分 (0-59)
int tm_hour; // 时 (0-23)
int tm_mday; // 日 (1-31)
int tm_mon; // 月 (0-11,0=一月)
int tm_year; // 年 (自1900年起的年数)
int tm_wday; // 星期 (0-6,0=周日)
// 其他字段略...
};
农历相关结构体与数组:
struct Lunar_Date
:存储农历信息(月份、日期、是否闰月等)。Lunar_MonthString[]
:农历月份名称数组(如 正月
, 二月
)。Lunar_DateString[]
:农历日期名称数组(如 初一
, 十五
)。Lunar_StemStrig[]
和 Lunar_BranchStrig[]
:天干地支数组(如 甲
, 子
)。Lunar_ZodiacString[]
:生肖数组(如 鼠
, 牛
)。字体与颜色:
DrawDate
函数的默认样式(红色数字 + 黑色单位)。布局结构:
plaintext
[公历日期] [星期] [电池图标]
[农历月份和日期] [天干地支] [生肖年]
365, 4
)。270
像素处。(365, 4)
和农历偏移 270
像素是固定值,可能导致在不同屏幕尺寸下显示异常。c
// 定义常量或宏
#define BATTERY_X_POS 365
#define BATTERY_Y_POS 4
#define LUNAR_OFFSET_X 270
DrawBattery(gfx, BATTERY_X_POS, BATTERY_Y_POS, data->voltage);
GFX_setCursor(gfx, x + LUNAR_OFFSET_X, y);
tm
、Lunar
或 data
是否为 NULL
,可能导致空指针异常。c
if (tm == NULL || Lunar == NULL || data == NULL) return;
星期
、年
)硬编码,无法适应其他语言。c
#ifdef LANG_CHINESE
#define WEEK_PREFIX "星期"
#define YEAR_SUFFIX "年"
#else
#define WEEK_PREFIX "Week "
#define YEAR_SUFFIX " Year"
#endif
GFX_printf(gfx, "%s%s", WEEK_PREFIX, Lunar_DayString[tm->tm_wday]);
LUNAR_GetStem
、LUNAR_GetBranch
等外部函数,需确保这些函数正确实现。Lunar.h
头文件中是否包含这些函数的声明。DrawDateHeader
函数通过组合调用其他绘图函数(如 DrawDate
、DrawBattery
),在界面顶部绘制了包含公历、星期、农历和电池状态的信息行。其设计特点是利用已有组件复用代码,并通过固定坐标实现布局。在实际应用中,建议根据屏幕尺寸和字体大小调整坐标参数,增强代码的可移植性。
(接下一篇)