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~BYTE1:00 0C,为 ASCII 圆角字符 B 的点阵宽度数据,即 12 位宽度。字符后面有 4 位空白区,可以在排版下一个字时考虑到这一点,将下一个字的起始位置前移。即 x 坐标加上该字符的宽度数据,即为下一个字符的 x 轴的起始位置。BYTE2~33: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,为 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_pBits 在 lcdram_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_Address 和 r_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);
}
}


