高通字库
版本 V1.0 · 更新于 2026-09-17

7. 字符显示及各种问题

读取到字符数据后,需要按照字库的排置方式将数据显示到屏幕上。本章介绍字符排置原理、TFT 彩屏与 STN 黑白屏的显示函数,以及点阵、矢量、灰度、不等宽、一维码、字符串混合显示等各类场景的用法。

7.1 字符排置解释(横置横排)

自定义字库芯片均为横置横排

横置横排:比如宽度是 16 bit、高度是 16 bit 的字符。「横置」指的就是两个字节水平放置,即宽度;「横排」指的是每个字节从左到右横向放两个字节,满两个字节换行继续放,一共有 16 排,即高度。如下图所示。

字符排置示意字符排置示意

7.2 显示函数(TFT 彩屏)

首先用户要确定使用的字库芯片是横置横排还是竖置横排。如果是竖置横排可以参考 Display_Y 函数,反之参考 Display_W 函数,这两个是打点显示的代码;LCD_Display_Area 是区域刷新显示代码。如果想快速显示,可以使用区域刷新显示代码。

/**
* @brief 竖置横排显示
*
* @param pBits 存放字符数据的数组
* @param x 竖轴坐标
* @param y 横轴坐标
* @param widt 字符宽度
* @param high 字符高度
*/
void Display_Y(unsigned char *pBits, unsigned int x, unsigned int y, unsigned int widt, unsigned int high)
{
    unsigned int i, j, k, n;
    unsigned char temp;
    n = 0;
    for( i = 0; i < ((high+7)>>3); i++) // 遍历每一行
    {
        for( j = 0; j < widt; j++)      // 遍历每个字节
        {
            temp = pBits[n++];
            for(k = 0; k < 8; k++)      // 遍历每一位
            {
                if(((temp >> k) & 0x01) == 0)
                {
                    /* 显示一个像素点 */
                    LCD_Fast_DrawPoint( x+j, y+k+(i*8), 0xFFFF);
                }
                else
                {
                    /* 显示一个像素点 */
                    LCD_Fast_DrawPoint( x+j, y+k+(i*8), 0x0000);
                }
            }
        }
    }
}

/**
* @brief 横置横排显示
*
* @param pBits 存放字符数据的数组
* @param x 竖轴坐标
* @param y 横轴坐标
* @param widt 字符宽度
* @param high 字符高度
*/
void Display_W(unsigned char *pBits, unsigned int x, unsigned int y, unsigned int widt, unsigned int high)
{
    unsigned int i, j, k, n;
    unsigned char temp;
    n = 0;
    for( i = 0; i < high; i++)              // 遍历每一行
    {
        for( j = 0; j < ((widt+7)>>3); j++) // 遍历每个字节
        {
            temp = pBits[n++];
            for(k = 0; k < 8; k++)          // 遍历每一位
            {
                if(((temp << k) & 0x80) == 0)
                {
                    /* 显示一个像素点 */
                    LCD_Fast_DrawPoint( x+k+(j*8), y+i, 0xFFFF);
                }
                else
                {
                    /* 显示一个像素点 */
                    LCD_Fast_DrawPoint( x+k+(j*8), y+i, 0x0000);
                }
            }
        }
    }
}

/**
* @brief 设置显示区域,快刷显示
*
* @param buff 存放字符数据的数组
* @param x 竖轴坐标
* @param y 横轴坐标
* @param widt 字符宽度
* @param high 字符高度
*/
void LCD_Display_Area(unsigned char* buff, unsigned int x, unsigned int y, unsigned int widt, unsigned int high)
{
    uint16_t i, j, k;
    uint16_t da, color;
    unsigned char* p = buff;
    unsigned char c;
    uint32_t t, h = high;
    k = 0;
    t = (widt+7)/8;
    LCD_Set_Window(x, y, (((widt+7)/8)*8), high); // 设置窗口
    LCD_WriteRAM_Prepare();                       // 开始写入GRAM
    for(i = 0; i < t*h; i++)
    {
        c = *p++;
        for(j = 0; j < 8; j++) // 循环判断每一位是1还是0
        {
            if(c & 0x80)
                color = 0x0000;
            else
                color = 0xffff;
            c <<= 1;
            // 写16位数据
            LCD_WriteRAM(color);
        }
    }
}

灰度矢量的显示函数可以参考下文代码。重点关注设置显示区域的代码和设置写入 GRAM 寄存器指令的代码,最后通过 LCD_WriteRAM 将每个像素点的灰度数据写到 GRAM 中。

注意:用户需根据使用的 LCD 进行代码调整。

#define DISMODE 1 // 1 刷图显示  0 打点显示

// 描点函数
#if DISMODE == 0
static void Gui_DrawPoint(unsigned int x, unsigned int y, unsigned int Data)
{
    // 显示一个点
    putPixel( x, y, Data);
}
#endif

/*----------------------------------------------------------------------------------------
* 灰度数据显示函数 1阶灰度/2阶灰度/4阶灰度
* 参数:
*   data 灰度数据; x,y=显示起始坐标; w 宽度, h 高度, grade 灰度阶级[1阶/2阶/4阶]
*   HB_par 1 白底黑字  0 黑底白字
*------------------------------------------------------------------------------------------*/
void Gray_Display_hz(unsigned char *data, unsigned short x, unsigned short y,
                     unsigned short w, unsigned short h, unsigned char grade, unsigned char HB_par)
{
    unsigned int temp = 0, gray, x_temp = x;
    unsigned int i = 0, j = 0, k = 0, t;
    unsigned char c, c2, *p;
    unsigned long color8bit, color4bit, color3bit[8], color2bit, color;
    t = (w+7)/8 * grade;
    p = data;
    t = (w+7)/8 * 4;
#if DISMODE
    LCD_Set_Window(x, y, (((w+7)/8)*8), h); // 设置显示区域
    LCD_WriteRAM_Prepare();                 // 设置写入GRAM寄存器指令
#endif
    if(grade == 2)  // 2bits
    {
        for(i = 0; i < t*h; i++)
        {
            c = *p++;
            for(j = 0; j < 4; j++)
            {
                color2bit = (c >> 6); // 获取像素点的2bit颜色值
                if(HB_par == 1)
                    color2bit = (3-color2bit)*250/3; // 白底黑字
                else
                    color2bit = color2bit*250/3;     // 黑底白字
                gray = color2bit/8;
                color = (0x001f & gray) << 11;              // r-5
                color = color | (((0x003f) & (gray*2)) << 5); // g-6
                color = color | (0x001f & gray);            // b-5
                temp = color;
                c <<= 2;
#if DISMODE
                // 写16位数据
                LCD_WriteRAM(temp);
#else
                // 打点
                if(x < (x_temp+w))
                {
                    Gui_DrawPoint(x, y, temp);
                }
                x++;
                if(x >= x_temp+(w+7)/8*8) { x = x_temp; y++; }
#endif
            }
        }
    }
    else if(grade == 3) // 3bits
    {
        for(i = 0; i < t*h; i += 3)
        {
            c = *p; c2 = *(p+1);
            color3bit[0] = (c >> 5) & 0x07;
            color3bit[1] = (c >> 2) & 0x07;
            color3bit[2] = ((c << 1) | (c2 >> 7)) & 0x07;
            p++;
            c = *p; c2 = *(p+1);
            color3bit[3] = (c >> 4) & 0x07;
            color3bit[4] = (c >> 1) & 0x07;
            color3bit[5] = ((c << 2) | (c2 >> 6)) & 0x07;
            p++;
            c = *p;
            color3bit[6] = (c >> 3) & 0x07;
            color3bit[7] = (c >> 0) & 0x07;
            p++;
            for(j = 0; j < 8; j++)
            {
                if(HB_par == 1)
                    color3bit[j] = (7-color3bit[j])*255/7; // 白底黑字
                else
                    color3bit[j] = color3bit[j]*255/7;     // 黑底白字
                gray = color3bit[j]/8;
                color = (0x001f & gray) << 11;                // r-5
                color = color | (((0x003f) & (gray*2)) << 5); // g-6
                color = color | (0x001f & gray);              // b-5
                temp = color;
#if DISMODE
                // 写16位数据
                LCD_WriteRAM(temp);
#else
                // 打点
                if(x < (x_temp+w))
                {
                    Gui_DrawPoint(x, y, temp);
                }
                x++;
                if(x >= x_temp+(w+7)/8*8) { x = x_temp; y++; }
#endif
            }
        }
    }
    else if(grade == 4) // 4bits
    {
        for(i = 0; i < t*h; i++)
        {
            c = *p++;
            for(j = 0; j < 2; j++)
            {
                color4bit = (c >> 4);
                if(HB_par == 1)
                    color4bit = (15-color4bit)*255/15; // 白底黑字
                else
                    color4bit = color4bit*255/15;      // 黑底白字
                gray = color4bit/8;
                color = (0x001f & gray) << 11;                // r-5
                color = color | (((0x003f) & (gray*2)) << 5); // g-6
                color = color | (0x001f & gray);              // b-5
                temp = color;
                c <<= 4;
#if DISMODE
                // 写16位数据
                LCD_WriteRAM(temp);
#else
                // 打点
                if(x < (x_temp+w))
                {
                    Gui_DrawPoint(x, y, temp);
                }
                x++;
                if(x >= x_temp+(w+7)/8*8) { x = x_temp; y++; }
#endif
            }
        }
    }
    else if(grade == 5 || grade == 6)
    {
        for(i = 0; i < t*h; i++)
        {
            c = *p++;
            for(j = 0; j < 2; j++)
            {
                color4bit = (c >> 4);
                if(HB_par == 1)
                    color4bit = (15-color4bit)*255/15; // 白底黑字
                else
                    color4bit = color4bit*255/15;      // 黑底白字
                if(color4bit > 0x00 && color4bit < 0xFF && grade == 5)
                    color4bit = color4bit - 0x11;
                else if(color4bit >= 0x11 && color4bit < 0xFF && grade == 6)
                    color4bit = (color4bit == 0x11) ? (color4bit - 0x11) : (color4bit - 0x22);
                gray = color4bit/8;
                color = (0x001f & gray) << 11;                // r-5
                color = color | (((0x003f) & (gray*2)) << 5); // g-6
                color = color | (0x001f & gray);              // b-5
                temp = color;
                c <<= 4;
#if DISMODE
                // 写16位数据
                LCD_WriteRAM(temp);
#else
                // 打点
                if(x < (x_temp+w))
                {
                    Gui_DrawPoint(x, y, temp);
                }
                x++;
                if(x >= x_temp+(w+7)/8*8) { x = x_temp; y++; }
#endif
            }
        }
    }
    else  // 1bits
    {
        for(i = 0; i < t*h; i++)
        {
            c = *p++;
            for(j = 0; j < 8; j++)
            {
                if(c & 0x80) color = 0x0000;
                else         color = 0xffff;
                c <<= 1;
#if DISMODE
                LCD_WriteRAM(color);
#else
                // 打点
                if(x < (x_temp+w))
                {
                    if(color == 0x0000 && HB_par == 1)
                    {
                        Gui_DrawPoint(x, y, color); // 打点
                    }
                    else if(HB_par == 0 && color == 0x0000)
                    {
                        Gui_DrawPoint(x, y, ~color); // 打点
                    }
                }
                x++;
                if(x >= x_temp+(w+7)/8*8) { x = x_temp; y++; }
#endif
            }
        }
    }
}

7.3 显示函数(STN 黑白屏)

/**
* @brief 设置坐标
*
* @param page 竖轴起始坐标
* @param column 横轴起始坐标
*/
void LCD_SetPos(uint8_t page, uint8_t column)
{
    column = column - 1; // 我们平常所说的第 1 列,在 LCD 驱动 IC 里是第 0 列,所以在这里减去 1
    page = page - 1;
    write_com(0xb0 + page); /* 设置页地址。每页是 8 行。一个画面的 64 行被分成 8 个页。
                               我们平常所说的第 1 页,在 LCD 驱动 IC 里是第 0 页,所以在这里减去 1 */
    write_com(column & 0x0f); // 设置列地址的低 4 位
    column >>= 4;
    column = column | 0x10;
    write_com(column); // 取高4位行地址
}

/**
* @brief 显示 16x16 点阵图像、汉字、生僻字或 16x16
*
* @param y 横轴坐标
* @param x 竖轴坐标
* @param dp 存放字符数据的数组
*/
void display_graphic_16x16(uint8_t y, uint8_t x, unsigned char *dp)
{
    uint8_t i;
    LCD_SetPos(y, x); // 发送地址,第一页
    for (i = 0; i < 16; i++) // 第一页的数据16字节,列号自动累加
    {
        write_data(*dp); // 写数据到LCD,如果要反色显示,就在*P前用~取反。
        dp++;
    }
    LCD_SetPos(y + 1, x); // 发送地址,第二页
    for (i = 0; i < 16; i++) // 第二页的数据16字节
    {
        write_data(*dp); // 写数据到LCD,如果要反色显示,就在*P前用~取反。
        dp++;
    }
}

7.4 点阵字库的显示

通过上述第 6.1 节「点阵字库 API 接口」,能获取到文字的点阵数据。数据会存放在定义的数组里,接下来就是将数组里的数据显示到屏幕上。以显示 8x16 字号大小的 ASCII 字符为例:

char pBits[16] = {0};
ASCII_GetData(0x41, ASCII_8X16, pBits);
Display_W(pBits, 100, 100, 8, 16);

7.5 矢量字库的显示

通过上述第 6.2 节「矢量字库 API 接口」,以下两种矢量字库 API 接口的使用方式。

使用 get_font_st 获取矢量字符时,注意宏定义 ZK_BUFFER_LEN 的大小,它决定了存放矢量字符数据的数组的大小,用户可以根据自己的需求进行改动。示例中调用的是 ASCII 中的「A」字符,type 选的是 ASCII 圆角类型,size 字号大小是 16,thick 粗细建议跟 size 一样,这里一样是 16,zkBuffer 是用来存矢量字符数据的数组。get_font_st 返回值表示字符显示时需要的宽度。

VECFONT_ST font_st = {.fontCode = 0x41, .type = VEC_FT_ASCII_STY, .size = 16, .thick = 16, .zkBuffer = {0}};
uint32_t width = get_font_st(&font_st);
Display_W(font_st.zkBuffer, 100, 100, width, 16);

使用 get_font 获取矢量字符的方法与 get_font_st 类似,这里定义了一个 256 字节大小的数组。get_font 返回值表示字符显示时需要的宽度。

char pBits[256] = {0};
uint32_t width = get_font(pBits, VEC_FT_ASCII_STY, 0x41, 16, 16, 16);
Display_W(pBits, 100, 100, width, 16);

7.6 灰度矢量字库/灰度字库的显示

通过上述第 6.2 节「矢量字库 API 接口」,使用 get_Font_Gray 函数获取灰度数据,并返回灰度字符的宽度数据和灰度值。显示的时候必须要用到这两个参数显示字符,否则无法正常显示。

unsigned int* width = NULL;
width = get_Font_Gray(pBits, VEC_SONG_STY, 0xB0A1, 24, 24); // 获取灰度矢量字符
Gray_Display_hz(pBits, 150, 100, width[0], 24, width[1], 1); // width[0]:宽度数据,width[1]:灰度数据

7.7 点阵、灰度、矢量不等宽字符的显示

7.7.1 点阵/灰度字库芯片不等宽字符显示处理

以 16 点阵 ASCII 不等宽字符为例(不等宽字符类型详见各字库芯片宏定义中的注释),16 点阵不等宽字符的信息需要 34 个字节(BYTE 0 – BYTE 33)来表示。

存储格式

由于字符是不等宽的,因此在存储格式中 BYTE0BYTE1 存放文字宽度数据,BYTE233 存放点阵数据。具体格式见下图。

不等宽字符存储格式不等宽字符存储格式

不等宽字符的点阵存储宽度是以 BYTE(8bit)为单位取整的,根据不同字符宽度会出现相应的空白区。根据 BYTE0~BYTE1 所存放点阵的实际宽度数据,即 x 坐标加上该字符的宽度数据,作为下一个字符显示的 x 轴的起始位置。

例如:ASCII 圆角字符 B,0~33 BYTE 的全部数据是:

00 0C 00 00 00 00 00 00 7F 80 7F C0 60 C0 60 C0 60 C0 7F 80 7F C0 60 E0 60 60 60 60 7F C0 7F 80 00 00

其中头两个字节:

  • BYTE0~BYTE100 0C,为 ASCII 圆角字符 B 的点阵宽度数据,即 12 位宽度。字符后面有 4 位空白区,可以在排版下一个字时考虑到这一点,将下一个字的起始位置前移。即 x 坐标加上该字符的宽度数据,即为下一个字符的 x 轴的起始位置。
  • BYTE2~3300 00 00 00 00 00 7F 80 7F C0 60 C0 60 C0 60 C0 7F 80 7F C0 60 E0 60 60 60 60 7F C0 7F 80 00 00,为 ASCII 圆角字符 B 的点阵数据。

不等宽示例代码

/*
* 示例:使用 ASCII_12_A 显示 "Ab"。
* ASCII_12_A 是不等宽 12x12 Arial 字体。
*/
unsigned char DZ_Data[512];
int x_position = 10, y_position = 10;
unsigned int a_width = 0;

/* 读取并显示字符 'A'。 */
memset(DZ_Data, 0, sizeof(DZ_Data));
ASCII_GetData(0x41, ASCII_12_A, DZ_Data);
a_width = (DZ_Data[0] << 8) | DZ_Data[1];
Gray_Display_hz(&DZ_Data[2], x_position, y_position, 16, 12, 1, 1);

/* 读取并显示字符 'b',x 坐标使用 'A' 的实际宽度计算。 */
memset(DZ_Data, 0, sizeof(DZ_Data));
ASCII_GetData(0x62, ASCII_12_A, DZ_Data);
Gray_Display_hz(&DZ_Data[2], x_position + a_width, y_position, 16, 12, 1, 1);

7.7.2 矢量字库芯片不等宽字符显示处理

如果使用 get_font 函数,则打开「05_屏幕显示资料\获取宽度.c」,直接拷贝获取宽度的代码;如果使用 get_font_gray 函数,则打开「05_屏幕显示资料\获取灰度字形的宽度」,拷贝获取灰度宽度的代码。具体的代码以及参数含义如下文所示。

获取宽度获取宽度

/**
* @brief 获取字符实际宽度
*
* @param p 字符数据数组首地址
* @param zfwidth 获取字符时填写的宽度
* @param zfhigh 获取字符时填写的高度
* @return unsigned int 实际宽度
*/
unsigned int get_width(unsigned char *p, unsigned int zfwidth, unsigned int zfhigh)
{
    unsigned char *q;
    unsigned int i, j, tem, tem1, witdh1 = 0, witdh2 = 0;
    q = p;
    for (i = 0; i < zfwidth/16; i++)
    {
        tem = 0;
        tem1 = 0;
        for (j = 0; j < zfhigh; j++)
        {
            tem = (*(q+j*(zfwidth/8)+i*2) | (*(q+1+j*(zfwidth/8)+i*2)) << 8);
            tem1 = tem1 | tem;
        }
        witdh1 = 0;
        for (j = 0; j < 16; j++)
        {
            if (((tem1 << j) & 0x8000) == 0x8000)
            {
                witdh1 = j;
            }
        }
        witdh2 += witdh1;
    }
    return witdh2;
}

/**
* @brief 获取灰度字库宽度
*
* @param data 带灰度的字符数据数组
* @param dot_w 获取字符时填写的宽度
* @param dot_h 获取字符时填写的高度
* @param gray 取值: 1 / 2 / 4
* @return int 实际宽度
*/
uint16_t get_width_gray(const uint8_t* data, uint16_t dot_w, uint16_t dot_h, uint8_t gray)
{
    uint16_t byte_w, i;
    int j, len;
    uint8_t tmp = 0;
    byte_w = ((dot_w + 7) >> 3) * gray;
    for(j = byte_w-1; j >= 0; j--) {
        tmp = 0;
        for(i = 0; i < dot_h; i++) {
            tmp |= data[i*byte_w + j];
        }
        if(0 != tmp) {
            for(i = 0; i < 8; i++){
                if((tmp >> i) & 0x01){
                    break;
                }
            }
            len = ((j / gray) * 8) + ((j%gray)*8+(8-i))/gray;
            if (len >= dot_h) {
                continue;
            }
            return len;
        }
    }
    return dot_w;
}

矢量输出灰度数据时获取宽度示例:

unsigned int *tmp, real_w = 0;
tmp = get_Font_Gray(pBits, (*text) < 0x80 ? VEC_FT_ASCII_STY : VEC_SONG_STY, *text, size, size);
real_w = get_width_gray(pBits, tmp[0], size, tmp[1]); // 获取灰度的宽度
Gray_Display_hz(pBits, x, y, tmp[0], size, tmp[1]);
x = x + real_w;

矢量输出点阵数据时获取宽度示例:

VECFONT_ST font_st = {.fontCode = 0x41, .type = VEC_FT_ASCII_STY, .size = 16, .thick = 16, .zkBuffer = {0}};
get_font_st(&font_st);  // 获取矢量字符数据
int width = get_width(font_st.zkBuffer, 16, 16);  // 获取实际的字符宽度
Display_W(font_st.zkBuffer, 100, 100, width, 16);

char pBits[256] = {0};
get_font(pBits, VEC_FT_ASCII_STY, 0x41, 16, 16, 16);    // 获取矢量字符数据
int width = get_width(pBits, 16, 16);  // 获取实际的字符宽度
Display_W(pBits, 100, 100, width, 16);

:无论点阵/灰度/矢量字库芯片,中文的宽度都是等宽。

7.8 字符显示的横竖转换问题

在上述内容中提到,显示字符前要确认使用的字库是横置横排还是竖置横排。比如字库里的字符都是横置横排的,但是显示的时候要用竖置横排的方式显示,那么就需要进行一次横竖转换。以 GT30L24A3W 为例,打开「05_屏幕显示资料\横竖转换.c」文件,具体代码如下文所示,可以直接拷贝到工程中。如果要实现横置横排转竖置横排,选择宏定义 HZ_MODE00 即可。

注意:目前横竖转换的函数只能用在点阵字库。

#define  HZ_MODE00 0  // 竖置横排(Y)
#define  HZ_MODE01 1  // 竖置竖排(Z)
#define  HZ_MODE10 2  // 横置横排(W)
#define  HZ_MODE11 3  // 横置竖排(X)
#define  HZ_MODE4  4  // Y-->W

/**
* 将 putdata 的数据转换后写入 getdate 的 LCD 显示缓冲 RAM 格式中
* @param getdate 转换后的字形数据
* @param putdata 转换前的字形数据
* @param width 字宽
* @param high  字高
* @param style 转换功能的选择:
*        HZ_MODE00 0 竖置横排(Y)
*        HZ_MODE01 1 竖置竖排(Z)
*        HZ_MODE10 2 横置横排(W)
*        HZ_MODE11 3 横置竖排(X)
*        HZ_MODE4  4 Y-->W
* 使用示例:lcdram_map(getdate, putdata, 32, 32, HZ_MODE4);
*           将32x32的字形数据由Y排置(竖置横排)转换成W排置(横置横排)保存在getdate数组中
*/
void lcdram_map(u8 *getdate, u8 *putdata, u8 width, u8 high, u8 style)
{
    u16 i, j, hbyte, wbyte;
    unsigned char i_8, j_8;
    wbyte = (width+7)/8;
    hbyte = (high+7)/8;

    //--------------------------------------------------------------------------
    // Y--> W; Y-->X; Y-->Z;
    //--------------------------------------------------------------------------
    if(style == HZ_MODE4)   // 竖置横排 转 横置横排 ( Y-->W )
    {
        for(i = 0; i < high; i++)
            for(j = 0; j < width; j++)
            {
                i_8 = i/8;
                if((*(putdata+i_8*width+j) & (0x01<<(i%8))) > 0)
                    getdate[wbyte*i+j/8] |= (0x80>>(j%8));
                else
                    getdate[wbyte*i+j/8] &= (~(0x80>>(j%8)));
            }
    }
    //--------------------------------------------------------------------------
    // W--> Y;  W-->Z; W-->X;
    //--------------------------------------------------------------------------
    if(style == HZ_MODE00)  // 竖置横排 (W--> Y)
    {
        for(i = 0; i < high; i++)
            for(j = 0; j < width; j++)
            {
                i_8 = i/8;
                if((*(putdata+wbyte*i+j/8) & (0x80>>(j%8))) > 0)
                    getdate[i_8*width+j] |= (0x01<<(i%8));
                else
                    getdate[i_8*width+j] &= (~(0x01<<(i%8)));
            }
    }
    else if(style == HZ_MODE01)   // 竖置竖排 (W-->Z)
    {
        for(i = 0; i < high; i++)
            for(j = 0; j < width; j++)
            {
                if((*(putdata+wbyte*i+j/8) & (0x80>>(j%8))) > 0)
                    getdate[j*hbyte+i/8] |= (0x01<<(i%8));
                else
                    getdate[j*hbyte+i/8] &= (~(0x01<<(i%8)));
            }
    }
    else if(style == HZ_MODE11) // 横置竖排 (W-->X)
    {
        for(i = 0; i < high; i++)
            for(j = 0; j < width; j++)
            {
                j_8 = j/8;
                if((*(putdata+wbyte*i+j/8) & (0x80>>(j%8))) > 0)
                    getdate[j_8*high+i] |= (0x80>>(j%8));
                else
                    getdate[j_8*high+i] &= (~(0x80>>(j%8)));
            }
    }
    else if(style == HZ_MODE10) // 横置横排 做镜像(W-->W')
    {
        for(i = 0; i < high; i++)
            for(j = 0; j < width; j++)
            {
                if((*(putdata+wbyte*i+j/8) & (0x80>>(j%8))) > 0)
                    *(getdate+wbyte*i+(width-j)/8) |= (0x80>>((width-j)%8));
                else
                    *(getdate+wbyte*i+(width-j)/8) &= ~(0x80>>((width-j)%8));
            }
    }
}

转换代码的使用方法示例如下:提前定义两个同样大小的数组,先使用 pBits 存放从字库芯片里读取到的数据,然后使用 get_pBitslcdram_map 中获取转换后的数据,接着再通过竖置横排的显示函数进行显示即可。

char pBits[16] = {0};
char get_pBits[16] = {0};
ASCII_GetData(0x41, ASCII_16_A, pBits); // 按照16x16大小获取
void lcdram_map(get_pBits, pBits, 16, 16, HZ_MODE00);  // 开始转换
Display_Y(pBits, 100, 100, 16, 16);  // 显示实际宽度

7.9 一维码获取及显示

打开「05_屏幕显示资料\一维码调用示例.c」,将代码拷贝到工程中即可。首先用户需要实现 SPI_Addressr_dat_bat 两个函数,参考代码如下。SPI_Address 函数中,用户只需将发送字节的函数替换成自己的 SPI 发送函数即可,r_dat_bat 函数的实现同理,并将 CS 拉高和拉低的代码替换成自己的即可。

void SPI_Address(unsigned char AddH, unsigned char AddM, unsigned char AddL)
{
    Send_Byte(AddH);    // 更换成开发者的spi发送字节函数;
    Send_Byte(AddM);
    Send_Byte(AddL);
}

/*******************************************************************************
*                    Get N bytes sub-pro  (STM8, STM32, 51)                    *
*******************************************************************************/
// 客户自己实现,从address地址读取len个字节的数据并存入到pBuff数组当中
unsigned char r_dat_bat(unsigned long address, unsigned long DataLen, unsigned char *pBuff)
{
    unsigned long i;
    unsigned char addrHigh;
    unsigned char addrMid;
    unsigned char addrLow;
    addrHigh = address >> 16;
    addrMid = address >> 8;
    addrLow = (unsigned char)address;
    Rom_csL;          // 更换成开发者的cs拉低,片选选中字库芯片
    Send_Byte(0x03);  // 更换成开发者的spi发送字节函数;普通读取首先送0x03,
                      // 然后发送地址高八位addrHigh,中八位addrMid,低八位addrLow。
    SPI_Address(addrHigh, addrMid, addrLow);
    for(i = 0; i < DataLen; i++)
        *(pBuff+i) = Get_Byte();
    Rom_csH;          // 这里更换成开发者的cs拉高,片选选中字库芯片
    return 0;
}

具体的一维码代码接口及调用示例如下。

/**
* @brief 条形码 EAN13 :将数组条形码转为对应条形码图形地址
* @param bar_num_p 条形码 EAN13 数字数组指针,数组包含13个数字。
* @return unsigned long * 数组条形码[13]对应条形码图形地址
*/
unsigned long *BAR_CODE_EAN13(unsigned char *bar_num_p);

/* code_128_type */
#define CODE_128_A         (1)
#define CODE_128_B         (2)
#define CODE_128_C         (3)

/**
* @brief GB/T 18347-2001(CODE128)条形码:将数组条形码转为对应条形码图形地址
* @param bar_num_p 条形码数字数组指针, bar_num_p[4]数组包含4个条形码ASCII符(数组取值为0~94)。
* @param bar_num_len 条形码数字数组 的长度
* @param code_type 起始符 编码格式类型 @ref code_128_type
* @return unsigned long * 对应条形码图形地址
*/
unsigned long *BAR_CODE128(unsigned char *bar_num_p, unsigned char bar_num_len, unsigned char code_type);
/**************************************
arr[] : 13个数组
**************************************/
void BAR_codeEAN13(unsigned int x, unsigned int y, unsigned char arr[])
{
    unsigned char buff_temp[60];
    unsigned long *tmp;
    unsigned char i = 0;
    tmp = BAR_CODE_EAN13(arr);
    for(i = 0; i < 13; i++)
    {
        memset(buff_temp, 0, sizeof(buff_temp));
        r_dat_bat(*tmp, 54, buff_temp);
        DisZK_DZ_W(x, y, 16, 28, BLACK, WHITE, &buff_temp[2], 1);
        x += buff_temp[1];
        tmp++;
    }
}

/**************************************
ascii_arr[] : 字符数组 ASCII
arr_len: 字符数组的长度
1、Code 39码只接受如下43个有效输入字符:
2、26个大写字母(A – Z);
3、十个数字(0 – 9);
连接号(-),句号(.),空格,美圆符号($),斜扛(/),加号(+)以及百分号(%)。其余的输入将被忽略。
**************************************/
void BAR_codeEAN39(unsigned int x, unsigned int y, unsigned char ascii_arr[], unsigned int arr_len) {
    unsigned char buff_tmp[50], i;
    for(i = 0; i < arr_len; i++) {
        BAR_CODE39(ascii_arr[i], buff_tmp);
        DisZK_DZ_W(x, y, 16, 22, BLACK, WHITE, &buff_tmp[0], 1);
        x += 11;
    }
}

/**************************************
arr[]: 条形码编码 整数
arr_len: 数组长度
flag: 起始符有3种模式
当flag=1时为Code-128-A;
当flag=2时为Code-128-B;
当flag=3时为Code-128-C;
Code128编码表参考: https://blog.csdn.net/rodulf/article/details/51276820
**************************************/
void BAR_codeEAN128(unsigned int x, unsigned y, unsigned char arr[], unsigned int arr_len, unsigned char flag) {
    unsigned char buff_tmp[60];
    unsigned long *tmp;
    unsigned char i = 0;
    const unsigned char verify = 3;
    tmp = BAR_CODE128(arr, arr_len, flag);
    for(i = 0; i < arr_len+verify; i++) {
        memset(buff_tmp, 0, sizeof(buff_tmp));
        r_dat_bat(*tmp, 40, buff_tmp);
        DisZK_DZ_W(x, y, 16, 20, BLACK, WHITE, &buff_tmp[0], 1); // 显示函数, 根据实际修改, 参考显示函数文件
        x += 11;  // 可修改
        tmp++;
    }
}

一维码的显示函数可以参考「05_屏幕显示资料\显示函数.txt」中的代码,具体代码如下。

/* 横置横排打点函数 -----------------------------------------------------------------*/
/*
Xpos, Ypos:显示的起点坐标
data:显示的数据
charColor:字体颜色
bkColor:背景颜色
sizeType:放大倍数,默认为1
*/
void WriteData(uint16_t Xpos, uint16_t Ypos, uint8_t data, uint16_t charColor, uint16_t bkColor, u8 sizeType)
{
    uint16_t j, i;
    unsigned char count = 0;
    for(j = 0; j < 8; j++)
    {
        if(((data >> (7-j)) & 0x01) == 0x01) {
            for(count = 0; count < sizeType; count++){
                for(i = 0; i < sizeType; i++){
                    LCD_SetPoint(Xpos + sizeType*j+count, Ypos+i, charColor); // 修改此函数
                }
            }
        }
        else {
            for(count = 0; count < sizeType; count++){
                for(i = 0; i < sizeType; i++){
                    LCD_SetPoint(Xpos + sizeType*j+count, Ypos+i, bkColor); // 修改此函数
                }
            }
        }
    }
}

/* 竖置横排打点函数 -----------------------------------------------------------------*/
/*
Xpos, Ypos:显示的起点坐标
data:显示的数据
charColor:字体颜色
bkColor:背景颜色
sizeType:放大倍数,默认为1
*/
void WriteDataY(uint16_t Xpos, uint16_t Ypos, uint8_t data, uint16_t charColor, uint16_t bkColor, u8 sizeType)
{
    uint16_t i, j;
    unsigned char count = 0;
    for(j = 0; j < 8; j++)
    {
        if(((data >> (7-j)) & 0x01) == 0x01) {
            for(count = 0; count < sizeType; count++){
                for(i = 0; i < sizeType; i++)
                {
                    LCD_SetPoint(Xpos+i, Ypos-sizeType*j+count, charColor);  // 修改此函数
                }
            }
        }
        else {
            for(count = 0; count < sizeType; count++){
                for(i = 0; i < sizeType; i++)
                {
                    LCD_SetPoint(Xpos+i, Ypos-sizeType*j+count, bkColor);  // 修改此函数
                }
            }
        }
    }
}

/******* 竖置横排显示函数 ***********/
/*
Xpos, Ypos:显示的起点坐标
W:字体显示的宽
H:字体显示的高
charColor:字体颜色
bkColor:背景颜色
DZ_Data:显示的字体数据
sizeType:放大倍数,默认为1
*/
void DisZK_DZ_Y(uint16_t Xpos, uint16_t Ypos, uint16_t W, uint16_t H, uint16_t charColor, uint16_t bkColor, uint8_t* DZ_Data, u8 sizeType)
{
    static u16 Vertical, Horizontal;
    u32 bit = 0;
    u8 temp = 0;
    Vertical = Ypos + 7*sizeType;
    Horizontal = Xpos;
    for(bit = 0; bit < ((H+7)/8*W); bit++) // data sizeof (byte)
    {
        if((bit % W == 0) && (bit > 0)) // W/8 sizeof
        {
            temp += 8 * sizeType;
            Horizontal = Xpos;
            Vertical = Ypos + 7*sizeType + temp;
        }
        else if(bit > 0)
        {
            Horizontal += sizeType;
            Vertical = Ypos + 7*sizeType + temp;
        }
        WriteDataY(Horizontal, Vertical, DZ_Data[bit], charColor, bkColor, sizeType);
    }
}

/******** 横置横排显示函数 ************/
/*
Xpos, Ypos:显示的起点坐标
W:字体显示的宽
H:字体显示的高
charColor:字体颜色
bkColor:背景颜色
DZ_Data:显示的字体数据
sizeType:放大倍数,默认为1
*/
void DisZK_DZ_W(uint16_t Xpos, uint16_t Ypos, uint16_t W, uint16_t H, uint16_t charColor, uint16_t bkColor, uint8_t* DZ_Data, u8 sizeType)
{
    static u16 Vertical, Horizontal;
    u32 bit = 0;
    Vertical = Ypos;
    Horizontal = Xpos;
    for(bit = 0; bit < ((W+7)/8*H); bit++) // data sizeof (byte)
    {
        if((bit % ((W+7)/8) == 0) && (bit > 0)) // W/8 sizeof
        {
            Vertical += sizeType;
            Horizontal = Xpos;
        }
        else if(bit > 0)
            Horizontal += sizeType*8;
        WriteData(Horizontal, Vertical, DZ_Data[bit], charColor, bkColor, sizeType);
    }
}

使用示例:

unsigned char code[13]  = {6, 9, 0, 1, 6, 8, 7, 5, 8, 2, 4, 4, 2};
unsigned char code1[13] = {6, 9, 3, 8, 7, 0, 9, 3, 0, 0, 4, 4, 9};
unsigned char code3[13] = {16, 33, 45, 31, 27, 26, 40};
BAR_codeEAN13(0, 0, code);
BAR_codeEAN13(0, 0, code1);
BAR_codeEAN128(0, 200, code3, 7, 1);

7.10 字符串及中英文混合显示

首先定义一个数组,用来存放字符的编码。可使用 GT-HMI Designer2 的「字库芯片 → 编码查询」进行编码查询(短句可选择「连续」模式一次转换整句,见第 5 章)。例如查询「我爱中国」:

大括号内,从左到右,每两个字节表示一个字符。

uint16_t buff[] = {0xCED2, 0xB0AE, 0xD6D0, 0xB9FA};
uint8_t msb = 0, lsb = 0, xpos = 100, ypos = 100;
for(int i = 0; i < sizeof(buff)/sizeof(buff[0]); i++)
{
    msb = buff[i] >> 8;
    lsb = buff[i] & 0xff;
    gt_16_GetData(msb, lsb, pBits);
    Display_W(pBits, xpos, ypos, 16, 16);
    xpos += 16;
}

注意:显示字符串不推荐在定义的数组里直接存放文字,例如 unsigned char buff = "我爱中国";。因为高通字库的中文使用的是 GB 编码格式,但用户使用的编译器在编译过程中可能会将这些中文编译成 Unicode 编码或者 UTF-8 编码,最后导致无法正常显示。

中英文混合显示示例。如果显示的字不是自己想要的字,则需要查看编译器默认编码是否支持中文。

void show_char(char *text, unsigned int x, unsigned int y, unsigned int size)
{
    unsigned int *tmp;
    while(*text != '\0') // 判断是否是结尾
    {
        memset(pBits, 0, 192*192/8);
        if((*text) < 0x80) {  // ASCII 码,*text是ASCII码
            tmp = get_Font_Gray(pBits, (*text) < 0x80 ? VEC_FT_ASCII_STY : VEC_SONG_STY, *text, size, size);    // 示例, 按实际ASCII调用函数名进行修改
            Gray_Display_hz(pBits, x, y, tmp[0], size, tmp[1]);
            x += size + 2;
            text++; // ASCII 码一个字节
        } else {  // 汉字编码,*text是汉字编码的高位,*(text+1)是汉字编码的低位
            tmp = get_Font_Gray(pBits, (*text) < 0x80 ? VEC_FT_ASCII_STY : VEC_SONG_STY, *text*256+*(text+1), size, size);  // 示例, 按实际中文调用函数名进行修改
            Gray_Display_hz(pBits, x, y, tmp[0], size, tmp[1]);
            x += size + 2;
            text++; // 汉字两个字节
            text++;
        }
    }
}

char *zf_code = "高通字库ABCabc";
show_char(zf_code, 0, 0, 24); // 在0,0坐标显示字符串 "高通字库ABCabc",点阵大小为24点

7.11 字节逆序

如果用户使用的 MCU 存储的方式是大端的存储方式(数据的低位存储在地址的高位),那么需要提前进行一次转换。转换函数可以在「05_屏幕显示资料\字节逆序.c」中查找到。具体代码如下:

void byte_cov(unsigned char *p, unsigned long len)
{
    unsigned long i;
    unsigned char tem;
    for (i = 0; i < len; i++)
    {
        tem = p[i];
        p[i] = ((tem & 0x01) << 7) | ((tem & 0x02) << 5) | ((tem & 0x04) << 3) | ((tem & 0x08) << 1)  \
             | ((tem & 0x10) >> 1) | ((tem & 0x20) >> 3) | ((tem & 0x40) >> 5) | ((tem & 0x80) >> 7);
    }
}