墨水屏nrf52811程序解读(1)

       本人是正宗小白,对于大佬们一秒看懂的程序,要发很大功夫才能搞清楚,现在对学习过程中的一些理解写下来,供与我一样白又白的爱好者参考,高手莫喷啊!!

下面研究的是以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函数)
各头文件作用
  1. fonts.h

    • 定义了不同大小、风格的字体(如中文字体、英文字体),供界面显示文本使用。
    • 例:u8g2_font_helvB18_tn(18 号粗体英文)、u8g2_font_wqy9_t_lunar(9 号中文小字体)。
  2. Lunar.h

    • 包含公历与农历转换的函数(如LUNAR_SolarToLunar),以及农历相关数据(如天干、地支、生肖字符串)。
    • 用于实现农历日期、节气、生肖等信息的显示。
  3. 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_REDGFX_BLACK
    • bg:背景色,如GFX_WHITE
    • font:字体类型(如u8g2_font_helvB18_tn)。
    • ...:可变参数,即printf的格式化字符串和参数。
执行流程
  1. 设置文字颜色和背景色
    GFX_setTextColor(gfx, fg, bg) 用于配置文本的显示颜色和背景色。
  2. 设置字体
    GFX_setFont(gfx, font) 选择文本显示的字体(如大小、风格)。
  3. 格式化输出文本
    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, "年"); // 黑色小字体显示“年”
    

三、关键作用与优势

  1. 代码复用与简化

    • 避免重复编写GFX_setTextColorGFX_setFont的调用代码,提高开发效率。
    • 例如,显示日期时需要多次切换颜色和字体,使用宏可减少冗余。
  2. 界面样式统一管理

    • 通过宏的参数灵活调整颜色、字体,便于后期修改界面风格(如从红色主题改为蓝色主题)。
  3. 适配不同显示屏

    • 若更换显示屏驱动(如从 Adafruit_GFX 改为其他库),只需修改GUI.h中的底层接口,上层代码(宏的调用)无需变动。

四、潜在扩展与注意事项

  1. 扩展功能

    • 可添加更多样式参数,如文本对齐方式(左对齐、居中、右对齐):

      #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__);
      

      注意事项

    • 宏的作用域:宏定义需在所有调用它的函数之前声明,避免编译错误。
    • 参数类型匹配:确保fgbg为显示屏驱动支持的颜色枚举值(如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),用于计算电量百分比和显示文本。

二、核心逻辑解析

1. 计算电量百分比
uint8_t level = (uint8_t)(voltage * 100 / 4.2);
  • 逻辑:假设电池满电电压为 4.2V,将当前电压转换为百分比(voltage / 4.2 * 100),并强制转换为无符号 8 位整数(0~100)。
  • 注意:若实际满电电压不同(如锂电池常见为 4.35V),需修改分母 4.2 以适配硬件。
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),直观展示电压精度。
3. 绘制电池外框与背景
GFX_fillRect(gfx, x, y, 20, 10, GFX_WHITE);   // 白色背景矩形
GFX_drawRect(gfx, x, y, 20, 10, GFX_BLACK);   // 黑色边框矩形
  • 白色背景
    • 绘制一个左上角坐标为 (x, y),宽 20 像素、高 10 像素的白色矩形,作为电池的主体背景。
  • 黑色边框
    • 在白色背景外绘制同尺寸的黑色边框,增加图标轮廓清晰度。
4. 绘制电池正极(突出部分)
GFX_fillRect(gfx, x + 20, y + 4, 2, 2, GFX_BLACK);
  • 位置计算
    • x + 20:位于电池主体右侧(宽度 20 像素,右侧边缘为 x+20)。
    • y + 4:垂直方向居中(电池高度 10 像素,居中位置为 y+4 至 y+6)。
  • 形状:绘制一个 2x2 像素的黑色小矩形,模拟电池正极的凸起部分。
5. 绘制电量进度条
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 像素。
    • 高度:6 像素,垂直方向居中(10 像素总高 - 上下各 2 像素边距)。
  • 逻辑:当 level 为 100 时,进度条填满整个内部宽度(16 像素);当 level 为 0 时,不绘制进度条(宽度为 0)。

三、图标尺寸与布局

  • 整体尺寸
    • 电池主体:20x10 像素(含边框)。
    • 正极:2x2 像素。
    • 进度条:最大 16x6 像素(内部区域)。
  • 坐标关系
  • +----------------+  <- 外框(20x10,黑色)
    |                |
    |   +----------+ |
    |   |          | |  <- 进度条(16x6,黑色)
    |   |          | |
    |   +----------+ |
    |                |
    +----------------+
    ^                ^
    x,y            x+20,y
    (左上角)       (右侧边缘)
    ++++++++++++++++  <- 正极(2x2,黑色)
    x+20,y+4
    

四、优化与扩展建议

  1. 电压范围适配
    • 若电池电压范围非 0~4.2V,需修改百分比计算公式,例如:

      uint8_t level = (uint8_t)((voltage - 3.0) / (4.2 - 3.0) * 100); // 假设最低电压为3.0V
      
  2. 颜色动态调整

根据电量高低改变进度条颜色(如低电量时显示红色):

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

二、核心逻辑解析

1. 设置文本位置
GFX_setCursor(gfx, x, y);
  • 将文本绘制起点设置为 (x, y),后续文本将从该点开始渲染。
2. 设置字体
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
  • 使用 u8g2_font_wqy9_t_lunar 字体,这是一个 9 号中文字体,适合显示温度符号(℃)和数字。
3. 格式化输出温度值
GFX_printf(gfx, "%d℃", temp);
  • 使用 %d 格式化整数温度值,并追加  符号(摄氏度)。
  • 示例:若 temp = 25,则显示 25℃;若 temp = -5,则显示 -5℃

三、功能与应用场景

  • 功能:在指定位置绘制温度值,适用于环境监测、设备状态显示等场景。
  • 应用示例
    // 在坐标(100, 50)处显示当前温度
    DrawTemperature(gfx, 100, 50, 28); // 显示 "28℃"
    

四、潜在问题与优化建议

1. 温度范围限制
  • 问题int8_t 仅支持 -128 ~ 127℃,若实际温度超出此范围(如工业环境),需改用 int16_t
  • 优化
    static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t temp)
    
2. 温度单位国际化
  • 问题:固定使用  符号,无法适应华氏度(℉)等其他单位需求。
  • 优化:添加单位参数:
    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℉"
    }
    
3. 颜色与样式调整
  • 问题:文本颜色和背景固定,无法根据温度范围动态调整(如高温显示红色)。
  • 优化:添加颜色参数:
    static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t temp, uint16_t color)
    {
        GFX_setTextColor(gfx, color);
        // 其他代码保持不变
    }
    
4. 精度提升
  • 问题:仅显示整数温度,无法展示小数部分(如 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:指向时间结构的指针,包含年、月、日等信息。

二、核心逻辑解析

1. 设置起始位置
GFX_setCursor(gfx, x, y);
  • 将绘图光标定位到指定的起始坐标 (x, y)
2. 绘制年份(红色大字体)
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)。
3. 绘制 "年" 字(黑色小字体)
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "年");
  • 参数
    • GFX_BLACK:文字颜色为黑色。
    • u8g2_font_wqy12_t_lunar:12 号中文字体,用于显示中文单位。
4. 绘制月份(红色大字体)
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mon + 1);
  • 格式化%02d 确保月份以两位数字显示(例如 0112)。
  • 月份调整tm_mon 范围是 0-11,因此需加 1 转换为实际月份。
5. 绘制 "月" 字(黑色小字体)
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "月");
6. 绘制日期(红色大字体)
GFX_printf_styled(gfx, GFX_RED, GFX_WHITE, u8g2_font_helvB18_tn, "%02d", tm->tm_mday);
  • 格式化%02d 确保日期以两位数字显示(例如 0131)。
7. 绘制 "日" 字(黑色小字体)
GFX_printf_styled(gfx, GFX_BLACK, GFX_WHITE, u8g2_font_wqy12_t_lunar, "日 ");

三、样式与排版特点

  1. 颜色区分

    • 年份、月份、日期使用红色(GFX_RED)突出显示。
    • 中文单位(年、月、日)使用黑色(GFX_BLACK)作为辅助说明。
  2. 字体大小

    • 数字部分使用 u8g2_font_helvB18_tn(18 号粗体),视觉上更突出。
    • 中文单位使用 u8g2_font_wqy12_t_lunar(12 号),字体稍小。
  3. 对齐方式

    • 通过 %02d 确保月份和日期始终以两位数字显示,保证排版整齐。
    • 例如:2023年05月19日,而非 2023年5月19日

四、潜在问题与优化建议

1. 年份计算依赖全局宏
  • 问题YEAR0 未在代码中定义,需确保其在其他头文件中被正确定义(通常为 1900)。
  • 优化:添加注释或条件编译:
    // 确保YEAR0已定义(通常为1900)
    #ifndef YEAR0
    #define YEAR0 1900
    #endif
    
2. 缺少错误检查
  • 问题:未检查 tm 指针是否为 NULL,可能导致空指针引用。
  • 优化:添加空指针检查:
    if (tm == NULL) return;
    
3. 硬编码样式
  • 问题:颜色和字体直接硬编码,不利于统一修改。
  • 优化:使用常量或配置结构体:
    #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
    
4. 国际化支持不足
  • 问题:中文单位(年、月、日)硬编码,无法适应其他语言。
  • 优化:使用字符串数组或宏定义:
    #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 结构体的指针,包含系统数据(如电池电压)。

    二、核心逻辑解析

    1. 绘制公历日期(调用 DrawDate 函数)
    DrawDate(gfx, x, y, tm);
    
    • 复用之前分析的 DrawDate 函数,在 (x, y) 位置绘制形如 2023年05月19日 的公历日期。
    2. 绘制星期信息
    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 数组映射为中文(如 )。
    • 输出示例星期日星期一
    3. 绘制电池状态
    DrawBattery(gfx, 365, 4, data->voltage);
    
    • 在固定位置 (365, 4) 调用 DrawBattery 函数,显示电池电量和电压(如 3.7V)。
    4. 绘制农历信息
    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 像素开始绘制。
    • 内容示例闰四月廿三 庚子年(如果是闰月且农历日期为四月廿三)。
    5. 绘制生肖信息
    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)突出显示。
    • 输出示例鼠年牛年

    三、关键数据结构与依赖

    1. 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=周日)
          // 其他字段略...
      };
      
    2. 农历相关结构体与数组

      • struct Lunar_Date:存储农历信息(月份、日期、是否闰月等)。
      • Lunar_MonthString[]:农历月份名称数组(如 正月二月)。
      • Lunar_DateString[]:农历日期名称数组(如 初一十五)。
      • Lunar_StemStrig[] 和 Lunar_BranchStrig[]:天干地支数组(如 )。
      • Lunar_ZodiacString[]:生肖数组(如 )。

    四、样式与排版分析

    1. 字体与颜色

      • 公历日期:使用 DrawDate 函数的默认样式(红色数字 + 黑色单位)。
      • 星期:9 号中文字体,黑色。
      • 农历信息:9 号中文字体,黑色。
      • 生肖:红色文字,突出显示。
    2. 布局结构

      plaintext

      [公历日期] [星期]                  [电池图标]
      [农历月份和日期] [天干地支] [生肖年]
      
       
      • 公历与星期在左侧,电池在右上角(固定坐标 365, 4)。
      • 农历信息在公历日期右侧偏移 270 像素处。

    五、潜在问题与优化建议

    1. 硬编码坐标问题
    • 问题:电池位置 (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);
      
    2. 缺少边界检查
    • 问题:未检查 tmLunar 或 data 是否为 NULL,可能导致空指针异常。
    • 优化

      c

      if (tm == NULL || Lunar == NULL || data == NULL) return;
      
    3. 国际化支持不足
    • 问题:中文文本(如 星期)硬编码,无法适应其他语言。
    • 优化

      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]);
      
    4. 农历计算依赖
    • 问题:依赖 LUNAR_GetStemLUNAR_GetBranch 等外部函数,需确保这些函数正确实现。
    • 建议:检查 Lunar.h 头文件中是否包含这些函数的声明。

    六、总结

    DrawDateHeader 函数通过组合调用其他绘图函数(如 DrawDateDrawBattery),在界面顶部绘制了包含公历、星期、农历和电池状态的信息行。其设计特点是利用已有组件复用代码,并通过固定坐标实现布局。在实际应用中,建议根据屏幕尺寸和字体大小调整坐标参数,增强代码的可移植性。

    (接下一篇)

    你可能感兴趣的:(单片机)