第51章 液晶显示中英文(任意大小)

第五十一章 液晶显示中英文(任意大小)

1. 导入

前文中无论是ASCII字符还是GB2312的字符,都只能显示字库中设定的字体大小,例如,我们想显示一些像素大小为48x48的字符, 那我们又得制作相应的字库,非常麻烦。为此我们编写了一些函数,简便地实现显示任意大小字符的目的。

2. 软件设计

2.1 编程目标

  • 编写缩放字模数据的函数

  • 编写利用缩放字模的结果进行字符显示的函数

  • 编写测试代码,控制显示不同大小的字符

2.2 代码分析

2.2.1 缩放字模数据

显示任意大小字符的功能,其核心是缩放字模,通过LCD_zoomChar函数对原始字模数据进行缩放

/***********************缩放字体****************************/
#define ZOOMMAXBUFF 16384
uint8_t zoomBuff[ZOOMMAXBUFF] = {0};  //用于缩放的缓存,最大支持到128*128
uint8_t zoomTempBuff[1024] = {0};

/*
缩放字模,缩放后的字模由1个像素点由8个数据位来表示
                    0x01表示笔迹,0x00表示空白区
in_width :原始字符宽度
in_heig :原始字符高度
out_width :缩放后的字符宽度
out_heig:缩放后的字符高度
in_ptr :字库输入指针 注意:1pixel 1bit
out_ptr :缩放后的字符输出指针 注意: 1pixel 8bit
out_ptr实际上没有正常输出,改成了直接输出到全局指针zoomBuff中
en_cn :0为英文,1为中文
*/
void ILI9341_zoomChar(uint16_t in_width,// 原始字符宽度
                     uint16_t in_heig,  // 原始字符高度
                     uint16_t out_width,// 缩放后的字符宽度
                     uint16_t out_heig, // 缩放后的字符高度
                     uint8_t *in_ptr,   // 字库输入指针  注意:1pixel 1bit
                     uint8_t *out_ptr,  // 缩放后的字符输出指针 注意: 1pixel 8bit
                     uint8_t en_cn)     // 0为英文,1为中文
{
    uint8_t *pts,*ots;
    //根据源字模及目标字模大小,设定运算比例因子,左移16是为了把浮点运算转成定点运算
    unsigned int xrIntFloat_16=(in_width<<16)/out_width+1;
    unsigned int yrIntFloat_16=(in_heig<<16)/out_heig+1;
    unsigned int srcy_16 = 0;
    unsigned int y,x;
    uint8_t *pSrcLine;
    uint16_t byteCount,bitCount;
    //检查参数是否合法
    if (in_width >= 32) return; // 源字库不允许超过32像素
    if (in_width * in_heig == 0) return;
    if (in_width * in_heig >= 1024 ) return; // 限制输入最大 32*32
    if (out_width * out_heig == 0) return;
    if (out_width * out_heig >= ZOOMMAXBUFF ) return; // 限制最大缩放 128*128
    pts = (uint8_t*)&zoomTempBuff;
    // 为方便运算,字库的数据由1 pixel/1bit 映射到1pixel/8bit
    // 0x01表示笔迹,0x00表示空白区
    if(en_cn == 0x00) { //英文
        //英文和中文字库上下边界不对,可在此处调整。需要注意tempBuff防止溢出
        for(byteCount=0; byteCount<in_heig*in_width/8; byteCount++) {
            for (bitCount=0; bitCount<8; bitCount++) {
                // 把源字模数据由位映射到字节
                // in_ptr里bitX为1,则pts里整个字节值为1
                // in_ptr里bitX为0,则pts里整个字节值为0
                *pts++ = (in_ptr[byteCount] & (0x80>>bitCount))?1:0;
            }
        }
    } else { //中文
        for (byteCount=0; byteCount<in_heig*in_width/8; byteCount++) {
            for (bitCount=0; bitCount<8; bitCount++) {
                // 把源字模数据由位映射到字节
                // in_ptr里bitX为1,则pts里整个字节值为1
                // in_ptr里bitX为0,则pts里整个字节值为0
                *pts++ = (in_ptr[byteCount] & (0x80>>bitCount))?1:0;
            }
        }
    }
    // zoom过程
    pts = (uint8_t*)&zoomTempBuff;// 映射后的源数据指针
    ots = (uint8_t*)&zoomBuff;  // 输出数据的指针
    for (y=0; y<out_heig; y++) {  // 行遍历
        unsigned int srcx_16=0;
        pSrcLine=pts+in_width*(srcy_16>>16);
        for (x=0; x<out_width; x++) { // 行内像素遍历
            ots[x]=pSrcLine[srcx_16>>16]; // 把源字模数据复制到目标指针中
            srcx_16+=xrIntFloat_16;     // 按比例偏移源像素点
        }
        srcy_16+=yrIntFloat_16;         //按比例偏移源像素点
        ots+=out_width;
    }
    // !!!缩放后的字模数据直接存储到全局指针zoomBuff里了
    // out_ptr没有正确传出,后面调用直接改成了全局变量指针!
    out_ptr = (uint8_t*)&zoomBuff;
    /* 实际中如果使用out_ptr不需要下面这一句!!!
    只是因为out_ptr没有使用,会导致warning。强迫症*/
    out_ptr++;
}

缩放字模的本质是按照缩放比例,减少或增加矩阵中的像素点,只要把左侧的矩阵隔一行、 隔一列地取出像素点,即可得到右侧按比例缩小了的矩阵,而右侧的小矩阵按比例填复制像素点即可得到左侧放大的矩阵,上述函数就是完成了这样的工作。

该函数的说明如下:

(1) 输入参数

函数包含输入参数源字模、缩放后字模的宽度及高度:in_width、inheig、out_width、out_heig。源字模数据指针in_ptr, 缩放后的字符指针out_ptr以及用于指示字模是英文还是中文的标志en_cn。其中out_ptr指针实质上没有用到,这个函数缩放后的数据最后直接存储在全局变量zoomBuff中了。

(2) 计算缩放比例

根据输入字模与要求的输出字模大小,计算出缩放比例到xrIntFloat_16及yrIntFloat_16变量中,运算式中的左移16位是典型的把浮点型运算转换成定点运算的处理方式。理解的时候可把左移16位的运算去掉,把它当成一个自然的数学小数运算即可。

(3) 检查输入参数

由于运算变量及数组的一些限制,函数中要检查输入参数的范围,本函数限制最大输出字模的大小为128*128像素,输入字模限制不可以超过32x32像素。

(4) 映射字模

输入源的字模都是1个数据位表示1个像素点的,为方便后面的运算,函数把输入字模转化成1个字节(8个数据位)表示1个像素点,该字节的值为0x01表示笔迹像素,0x00表示空白像素。把字模数据的1个数据位映射为1个字节,可以方便后面直接使用指针和数组索引运算。

(5) 缩放字符

缩放字符这部分代码比较难理解,但总的来说它就是利用前面计算得的比例因子,以它为步长复制源字模的数据到目标字模的缓冲区中, 具体的抽象运算只能意会了。其中的右移16位是把比例因子由定点数转换回原始的数值。如果还是觉得难以理解, 可以把函数的宽度及高度输入参数in_width、inheig、out_width及out_heig都设置成16,然后代入运算来阅读这段代码。

(6) 缩放结果

经过运算,缩放的结果存储在zoomBuff中,它只是存储了一个字模的缩放结果,所以每显示一个字模都需要先调用这个函数更新zoomBuff中的字模数据,而且它也是用1个字节表示1个像素位的。

2.2.2 利用缩放的字模数据显示字符

由于缩放后的字模数据格式与我们原来用的字模数据格式不一样,所以我们也要重新编写字符显示函数

/*
利用缩放后的字模显示字符
Xpos :字符显示位置x
Ypos :字符显示位置y
Font_width :字符宽度
Font_Heig:字符高度
c :要显示的字模数据
DrawModel :是否反色显示
*/
void ILI9341_DrawChar_Ex(uint16_t usX, // 字符显示位置x
                         uint16_t usY, // 字符显示位置y
                         uint16_t Font_width,// 字符宽度
                         uint16_t Font_Height,// 字符高度
                         uint8_t *c,          // 字模数据
                         uint16_t DrawModel)  // 是否反色显示
{
    uint32_t index = 0, counter = 0;
    //设置显示窗口
    ILI9341_OpenWindow(usX, usY, Font_width, Font_Height);
    ILI9341_Write_Cmd(CMD_SetPixel);
    // 按字节读取字模数据
    // 由于前面直接设置了显示窗口,显示数据会自动换行
    for(index = 0; index < Font_Height; index++){
        // 一位一位处理要显示的颜色
        for(counter = 0; counter < Font_width; counter++){
            // 缩放后的字模数据,以一个字节表示一个像素位
            // 整个字节值为1表示该像素为笔迹
            // 整个字节值为0表示该像素为背景
            if(*c++ == DrawModel)
                ILI9341_Write_Data(CurrentBackColor);
            else
                ILI9341_Write_Data(CurrentTextColor);
        }
    }
}

注意在这个函数中,它并没有对中英文模区分显示代码,因为本函数的字模是由输入参数c指针中获取的,在调用本函数时, 需要输入要显示的字模数据指针,而不是字符编码。在其它方面这个函数主体与前面介绍的字符显示函数都很类似, 只是它在判断字模数据位的时候,直接用一整个字节来判断,区分显示分支,而且还支持了反色显示模式

2.2.3 利用缩放的字模显示字符串

单个字符显示的函数并不包含字模的获取过程,为便于使用,我们把它直接封装成字符串显示函数

/*
利用缩放后的字模显示字符串
Xpos :字符显示位置x
Ypos :字符显示位置y
Font_width :字符宽度,英文字符在此基础上/2。注意为偶数
Font_Heig:字符高度,注意为偶数
c :要显示的字符串
DrawModel :是否反色显示
*/
void ILI9341_DisplayStringEx(uint16_t x, // 字符显示位置x
                             uint16_t y, // 字符显示位置y
                             uint16_t Font_width,
                             // 要显示的字体宽度,英文字符在此基础上/2。注意为偶数
                             uint16_t Font_Height,// 要显示的字体高度,注意为偶数
                             uint8_t *ptr,        // 显示的字符内容
                             uint16_t DrawModel)  // 是否反色显示
{
    uint16_t Charwidth = Font_width; // 默认为Font_width,英文宽度为中文宽度的一半
    uint8_t *psr;
    uint8_t Ascii; //英文
    uint16_t usCh; //中文
    uint8_t ucBuffer[ WIDTH_CH_CHAR*HEIGHT_CH_CHAR/8];
    while(*ptr != '\0'){
        /****处理换行*****/
        if((x-ILI9341_DispWindow_X_Star+Charwidth)>LCD_X_LENGTH) 
        {
            x = ILI9341_DispWindow_X_Star;
            y += Font_Height;
        }
        if((y-ILI9341_DispWindow_Y_Star+Font_Height)>LCD_Y_LENGTH) 
        {
            x = ILI9341_DispWindow_X_Star;
            y = ILI9341_DispWindow_Y_Star;
        }
        if(*ptr > 0x80) 
        { // 如果是中文
            Charwidth = Font_width;
            usCh = * ( uint16_t * ) ptr;
            usCh = ( usCh << 8 ) + ( usCh >> 8 );
            GetGBKCode ( ucBuffer, usCh );  // 取字模数据
            // 缩放字模数据,源字模为16*16
            ILI9341_zoomChar(WIDTH_CH_CHAR,HEIGHT_CH_CHAR,Charwidth,
            Font_Height,(uint8_t *)&ucBuffer,psr,1);
            // 显示单个字符
            ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,
            (uint8_t*)&zoomBuff,DrawModel);
            x+=Charwidth;
            ptr+=2;
        } 
        else 
        { // 英文
            Charwidth = Font_width / 2;
            Ascii = *ptr - 32;
            // 使用16*24字体缩放字模数据
            ILI9341_zoomChar(16,24,Charwidth,Font_Height,
            (uint8_t *)&Font16x24.table[Ascii * Font16x24.Height*Font16x24.Width/8],psr,0);
            // 显示单个字符
            ILI9341_DrawChar_Ex(x,y,Charwidth,Font_Height,(uint8_t*)&zoomBuff,DrawModel);
            x+=Charwidth;
            ptr++;
        }
    }
}

这个函数包含了从字符编码到源字模获取、字模缩放及单个字符显示的过程,多个这样的过程组合起来,就实现了简单易用的字符串显示函数。 而且可了解到它使用的英文源字模数据是Font16x24字体,而中文源字模数据仍是采用GetGBKCode函数获取,使得数据源的获取与上层分离, 支持从SPIFLASH及SD卡中获取数据源。

2.2.4 利用缩放的字模显示示例

利用缩放的字模显示时,液晶的初始化过程与前面的工程无异,以下我们给出LCD_Test函数中调用字符串函数显示不同字符时的示例

/*用于测试各种液晶的函数*/
void LCD_Test(void)
{
    // 演示显示变量
    static uint8_t testCNT = 0;
    char dispBuff[100];
    testCNT++;

    LCD_SetFont(&Font8x16);
    LCD_SetColors(RED,BLACK);

    ILI9341_Clear(0, 0, LCD_X_LENGTH,LCD_Y_LENGTH); // 清屏,显示全黑 
    /********显示字符串示例*******/
    ILI9341_DispStringLine_EN_CH(LINE(0),"野火BH");
    // 显示指定大小的字符
    ILI9341_DisplayStringEx(0, 1*24, 24, 24, (uint8_t *)"野火BH", 0);
    ILI9341_DisplayStringEx(2*48, 0*48, 48, 48, (uint8_t *)"野火BH", 0);
    /********显示变量示例*******/
    LCD_SetTextColor(GREEN);
    // 使用c标准库把变量转化成字符串
    sprintf(dispBuff,"显示变量: %d ",testCNT);
    LCD_ClearLine(LINE(5)); // 清除单行文字
    // 然后显示该字符串即可,其它变量也是这样处理
    ILI9341_DispStringLine_EN_CH(LINE(5),dispBuff);
    /*...以下部分省略*/
}

2024.9.29 第一次修订,后期不再维护

posted @ 2024-09-29 15:06  hazy1k  阅读(4)  评论(0编辑  收藏  举报