专栏首页安富莱嵌入式技术分享【STM32H7教程】第53章 STM32H7的LTDC应用之汉字小字库和全字库制作

【STM32H7教程】第53章 STM32H7的LTDC应用之汉字小字库和全字库制作

完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第53章       STM32H7的LTDC应用之汉字小字库和全字库制作

本章教程为大家讲解汉字小字库和全字库的制作方式,实际项目中用到的地方比较多。

53.1 初学者重要提示

53.2 使用MakeDot小软件生成C文件格式小字库方法

53.3 使用MakeDot小软件生成C文件格式全字库方法

53.4 C文件格式汉字使用方法

53.5 汉字显示方法解析

53.6 LCD驱动移植和使用

53.7 实验例程设计框架

53.8 实验例程说明(MDK)

53.9 实验例程说明(IAR)

5.10 总结

53.1 初学者重要提示

  1.   学习本章节前,务必优先学习第52章,需要对点阵字体字符编码有个认识。
  2.   LTDC驱动设计和相关问题在第51章有详细说明。
  3.   本章节为大家讲解的小字库和全字库方法,简单易用,是直接以C文件格式存储到内部Flash的。支持12点阵,16点阵,24点阵和32点阵的ASCII以及GB2312编码汉字显示。

53.2 使用MakeDot小软件生成C文件格式小字库方法

生成方法比较简单,这里做个介绍:

53.2.1 第1步,准备好显示的字符

比如要显示如下字符,采用16点阵格式:

安富莱电子,www.armfly.com
故人西辞黄鹤楼,烟花三月下扬州。
孤帆远影碧空尽,唯见长江天际流。

53.2.2 第2步,复制要显示的字符到MakeDot小软件

选择16点阵,并将要显示的字符复制到输入窗口:

点击生成数组按钮后的效果如下:

53.2.3 第3步,复制生成的数组到工程中

在输出窗口鼠标右击,选择“全选”,然后再次鼠标右击选择复制。

这样就可以粘贴到工程的hz.c文件里面:

将点阵数据放在相应的文件里面时要注意加上两个0XFF。hz.c文件的内容如下:

/*
    FLASH中内嵌小字库,只包括本程序用到的汉字点阵
    每行点阵数据,头2字节是汉子的内码,后面是16点阵汉子的字模数据。
*/

#ifdef USE_SMALL_FONT

unsigned char const g_Hz16[] = {

0xA1,0xA3, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 。 //
           0x00,0x00,0x00,0x00,0x18,0x00,0x24,0x00,0x24,0x00,0x18,0x00,0x00,0x00,0x00,0x00,

           
0xA3,0xAC, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// , //
           0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x30,0x00,0x10,0x00,0x20,0x00,0x00,0x00,

           
0xB0,0xB2, 0x02,0x00,0x01,0x00,0x3F,0xFC,0x20,0x04,0x42,0x08,0x02,0x00,0x02,0x00,0xFF,0xFE,// 安 //
           0x04,0x20,0x08,0x20,0x18,0x40,0x06,0x40,0x01,0x80,0x02,0x60,0x0C,0x10,0x70,0x08,

 /* 中间部分省略未写 */
           
0xD7,0xD3, 0x00,0x00,0x7F,0xF8,0x00,0x10,0x00,0x20,0x00,0x40,0x01,0x80,0x01,0x00,0xFF,0xFE,// 子 //
           0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00,

          
/* 最后一行必须用0xFF,0xFF结束,这是字库数组结束标志 */
0xFF,0xFF

};

#else
    unsigned char const g_Hz16[] = {0xFF, 0xFF};
#endif

添加完毕点阵数据后,在font.h文件里面使能使用小字库:

#define USE_SMALL_FONT     /* 定义此行表示使用小字库, 这个宏只在bsp_tft+lcd.c中使用 */

至此就完成了小字库的汉字添加,用户就可以在使用16点阵时显示第1步中转换的字符了。

53.3 使用MakeDot小软件生成C文件格式全字库方法

生成方法比较简单,这里做个介绍:

53.3.1 第1步,准备好GB2312字符集

GB2312字符集已经在MakeDot小软件里面存好,点击汉字编码按钮可以看到:

53.3.2 第2步,复制GB2312全部字符到MakeDot小软件

复制MakeDot小软件中GB2312所有字符到“输入窗口区”(在GB2312字符显示区,鼠标右击选择全选,之后就可以复制了),

点击生成数组按钮后的效果如下:

53.3.3 第3步,复制生成的数组到工程中

在输出窗口鼠标右击,选择“全选”,然后再次鼠标右击选择复制。

这样就可以粘贴到工程的hz.c文件里面:

将点阵数据放在相应的文件里面时要注意加上两个0XFF。hz.c文件的内容如下:

/*
    FLASH中内嵌小字库,只包括本程序用到的汉字点阵
    每行点阵数据,头2字节是汉子的内码,后面是16点阵汉子的字模数据。
*/

#ifdef USE_SMALL_FONT

unsigned char const g_Hz16[] = {



0xA1,0xA1, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//   //
           0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

           
0xA1,0xA2, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 、 //
           0x00,0x00,0x00,0x00,0x20,0x00,0x18,0x00,0x0C,0x00,0x04,0x00,0x00,0x00,0x00,0x00,

           
0xA1,0xA3, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 。 //
           0x00,0x00,0x00,0x00,0x18,0x00,0x24,0x00,0x24,0x00,0x18,0x00,0x00,0x00,0x00,0x00,

 /* 中间部分省略未写 */
           
0xF7,0xFB, 0x20,0x0E,0xCE,0xF0,0x82,0x22,0xEE,0x92,0x82,0x44,0x82,0x20,0xFE,0x44,0x00,0xF8,// 鼷 //
           0x92,0x10,0x92,0x24,0xDA,0xFE,0x92,0x10,0xDA,0xFE,0x92,0x28,0x93,0x44,0xD9,0x82,

           
0xF7,0xFC, 0x10,0x20,0x3E,0x20,0x22,0x20,0x3E,0x20,0x22,0xF8,0x3E,0x28,0x00,0x28,0x7F,0x28,// 鼽 //
           0x49,0x28,0x7F,0x28,0x49,0x28,0x7F,0x2A,0x00,0x2A,0xFF,0xCA,0x22,0x46,0x42,0x80,

           
0xF7,0xFD, 0x10,0x00,0x3E,0x00,0x22,0x7C,0x3E,0x10,0x22,0x10,0x3E,0x10,0x00,0x10,0x7F,0x10,// 鼾 //
           0x49,0xFE,0x7F,0x10,0x49,0x10,0x7F,0x10,0x00,0x10,0xFF,0x90,0x22,0x10,0x42,0x10,

           
0xF7,0xFE, 0x10,0x10,0x3E,0x10,0x22,0xFE,0x3E,0x38,0x22,0x54,0x3E,0x92,0x00,0x00,0x7F,0x7C,// 齄 //
           0x49,0x44,0x7F,0x7C,0x49,0x44,0x7F,0x7C,0x00,0x44,0xFF,0x80,0x22,0xFE,0x42,0x00,

          
/* 最后一行必须用0xFF,0xFF结束,这是字库数组结束标志 */
0xFF,0xFF

};

#else
    unsigned char const g_Hz16[] = {0xFF, 0xFF};
#endif

添加完毕点阵数据后,在font.h文件里面使能宏定义:

#define USE_SMALL_FONT     /*这个宏只在bsp_tft+lcd.c中使用 */

至此就完成了全字库的汉字添加,用户就可以使用16点阵的汉字了。

53.4 C文件格式汉字使用方法

汉字的显示方法比较简单。

  •   定义一个FONT_T类型变量:
FONT_T tFont12;        /* 定义一个12点阵字体结构体变量,用于设置字体参数 */
FONT_T tFont16;        /* 定义一个16点阵字体结构体变量,用于设置字体参数 */
FONT_T tFont24;        /* 定义一个24点阵字体结构体变量,用于设置字体参数 */

FONT_T的原始定义如下:

typedef struct
{
    FONT_CODE_E FontCode;    /* 字体代码 FONT_CODE_E  */
    uint16_t FrontColor;   /* 字体颜色 */
    uint16_t BackColor;    /* 文字背景颜色,透明 */
    uint16_t Space;        /* 文字间距,单位 = 像素 */
}FONT_T;
  •   初始化变量tFont:

设置12,16和24点阵。

/* 设置字体属性 */
tFont.FontCode = FC_ST_12;        /* 字体选择宋体12点阵,高12 x宽11) */
tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
tFont.BackColor = CL_MASK;         /* 文字背景颜色,透明 */
tFont.Space = 0;                /* 字符水平间距, 单位 = 像素 */

tFont.FontCode = FC_ST_16;        /* 字体选择宋体16点阵,高16 x宽15) */
tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
tFont.BackColor = CL_MASK;         /* 文字背景颜色,透明 */
tFont.Space = 0;                /* 字符水平间距, 单位 = 像素 */

tFont.FontCode = FC_ST_24;        /* 字体选择宋体24点阵,高24 x宽23) */
tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
tFont.BackColor = CL_MASK;         /* 文字背景颜色,透明 */
tFont.Space = 0;                /* 字符水平间距, 单位 = 像素 */
  •   调用函数LCD_DispStr显示字符:

下面显示了12,16和24点阵字符。

LCD_DispStr(5, 3,  "故人西辞黄鹤楼,烟花三月下扬州。www.armfly.com", &tFont12); 
LCD_DispStr(5, 20, "孤帆远影碧空尽,唯见长江天际流。www.armfly.com", &tFont12); 
LCD_DispStr(5, 38, "故人西辞黄鹤楼,烟花三月下扬州。", &tFont16); 
LCD_DispStr(5, 68, "孤帆远影碧空尽,唯见长江天际流。", &tFont16); 
LCD_DispStr(5, 98, "故人西辞黄鹤楼烟花三月下扬州", &tFont24); 
LCD_DispStr(5, 128, "孤帆远影碧空尽唯见长江天际流", &tFont24);

53.5 汉字显示方法解析

下面将汉字的显示流程做个说明,几个函数的调用关系如下:

LCD_DispStr ----> LCD_DispStrEx ----->_LCD_ReadAsciiDot

_LCD_ReadHZDot

53.5.1 函数LCD_DispStr

中英文显示都是调用的如下函数实现:

/*
*********************************************************************************************************
*    函 数 名: LCD_DispStr
*    功能说明: 在LCD指定坐标(左上角)显示一个字符串
*    形    参:
*        _usX : X坐标
*        _usY : Y坐标
*        _ptr  : 字符串指针
*        _tFont : 字体结构体,包含颜色、背景色(支持透明)、字体代码、文字间距等参数
*    返 回 值: 无
*********************************************************************************************************
*/
void LCD_DispStr(uint16_t _usX, uint16_t _usY, char *_ptr, FONT_T *_tFont)
{
    LCD_DispStrEx(_usX, _usY, _ptr, _tFont, 0, 0);
}

这个函数的注释已经比较详细,这里就不再赘述了。而这个函数是通过调用LCD_DispStrEx实现。

53.5.2 函数LCD_DispStrEx

此函数的源码如下:

1.    /*
2.    ******************************************************************************************************
3.    *    函 数 名: LCD_DispStrEx
4.    *    功能说明: 在LCD指定坐标(左上角)显示一个字符串。 增强型函数。支持左\中\右对齐,支持定长清屏。
5.    *    形    参:
6.    *        _usX : X坐标
7.    *        _usY : Y坐标
8.    *        _ptr  : 字符串指针
9.    *        _tFont : 字体结构体,包含颜色、背景色(支持透明)、字体代码、文字间距等参数。可以指定RA8875字库
10.    *                 显示汉字。
11.    *        _Width : 字符串显示区域的宽度. 0 表示不处理留白区域,此时_Align无效
12.    *        _Align :字符串在显示区域的对齐方式,
13.    *                ALIGN_LEFT = 0,
14.    *                ALIGN_CENTER = 1,
15.    *                ALIGN_RIGHT = 2
16.    *    返 回 值: 无
17.    ******************************************************************************************************
18.    */
19.    void LCD_DispStrEx(uint16_t _usX, uint16_t _usY, char *_ptr, FONT_T *_tFont, uint16_t _Width,
20.        uint8_t _Align)
21.    {
22.        uint32_t i;
23.        uint8_t code1;
24.        uint8_t code2;
25.        uint8_t buf[32 * 32 / 8];    /* 最大支持32点阵汉字 */
26.        uint8_t width;
27.        uint16_t m;
28.        uint8_t font_width = 0;
29.        uint8_t font_height = 0;
30.        uint16_t x, y;
31.        uint16_t offset;
32.        uint16_t str_width;    /* 字符串实际宽度  */
33.    
34.        switch (_tFont->FontCode)
35.        {
36.            case FC_ST_12:        /* 12点阵 */
37.                font_height = 12;
38.                font_width = 12;
39.                break;
40.            
41.            case FC_ST_16:
42.                font_height = 16;
43.                font_width = 16;
44.                break;
45.    
46.            case FC_ST_24:
47.                font_height = 24;
48.                font_width = 24;
49.                break;
50.                            
51.            case FC_ST_32:    
52.                font_height = 32;
53.                font_width = 32;
54.                break;                    
55.        }
56.        
57.        str_width = LCD_GetStrWidth(_ptr, _tFont);/* 计算字符串实际宽度(RA8875内部ASCII点阵宽度为变长 */
58.        offset = 0;
59.        if (_Width > str_width)
60.        {
61.            if (_Align == ALIGN_RIGHT)    /* 右对齐 */
62.            {
63.                offset = _Width - str_width;
64.            }
65.            else if (_Align == ALIGN_CENTER)    /* 居中 */
66.            {
67.                offset = (_Width - str_width) / 2;
68.            }
69.            else    /* 左对齐 ALIGN_LEFT */
70.            {
71.                ;
72.            }
73.        }
74.    
75.        /* 左侧填背景色, 中间对齐和右边对齐  */
76.        if (offset > 0)
77.        {
78.            LCD_Fill_Rect(_usX, _usY, LCD_GetFontHeight(_tFont), offset,  _tFont->BackColor);
79.            _usX += offset;
80.        }
81.        
82.        /* 右侧填背景色 */
83.        if (_Width > str_width)
84.        {
85.            LCD_Fill_Rect(_usX + str_width, _usY, LCD_GetFontHeight(_tFont), _Width - str_width - offset,
86.                            _tFont->BackColor);
87.        }
88.        
89.        /* 使用CPU内部字库. 点阵信息由CPU读取 */
90.        {
91.            /* 开始循环处理字符 */
92.            while (*_ptr != 0)
93.            {
94.                code1 = *_ptr;    /* 读取字符串数据, 该数据可能是ascii代码,也可能汉字代码的高字节 */
95.                if (code1 < 0x80)
96.                {
97.                    /* 将ascii字符点阵复制到buf */
98.                    //memcpy(buf, &pAscDot[code1 * (font_bytes / 2)], (font_bytes / 2));
99.                    _LCD_ReadAsciiDot(code1, _tFont->FontCode, buf);    /* 读取ASCII字符点阵 */
100.                    width = font_width / 2;
101.                }
102.                else
103.                {
104.                    code2 = *++_ptr;
105.                    if (code2 == 0)
106.                    {
107.                        break;
108.                    }
109.                    /* 读1个汉字的点阵 */
110.                    _LCD_ReadHZDot(code1, code2, _tFont->FontCode, buf);
111.                    width = font_width;
112.                }
113.        
114.                y = _usY;
115.                /* 开始刷LCD */
116.                for (m = 0; m < font_height; m++)    /* 字符高度 */
117.                {
118.                    x = _usX;
119.                    for (i = 0; i < width; i++)    /* 字符宽度 */
120.                    {
121.                        if ((buf[m * ((2 * width) / font_width) + i / 8] & (0x80 >> (i % 8 ))) != 0x00)
122.                        {
123.                            LCD_PutPixel(x, y, _tFont->FrontColor);    /* 设置像素颜色为文字色 */
124.                        }
125.                        else
126.                        {
127.                            if (_tFont->BackColor != CL_MASK)    /* 透明色 */
128.                            {
129.                                LCD_PutPixel(x, y, _tFont->BackColor);/* 设置像素颜色为文字背景色 */
130.                            }
131.                        }
132.        
133.                        x++;
134.                    }
135.                    y++;
136.                }
137.        
138.                if (_tFont->Space > 0)
139.                {
140.                    /* 如果文字底色按_tFont->usBackColor,并且字间距大于点阵的宽度,那么需要在文字之间填
141.                          充(暂时未实现) */
142.                }
143.                _usX += width + _tFont->Space;    /* 列地址递增 */
144.                _ptr++;            /* 指向下一个字符 */
145.            }
146.        }
147.    }

下面将代码中几个关键地方做个阐释:

  1.   第34-55行,根据使用的的12,16,24和32点阵字体,设置字体的高度变量font_height和宽度变量font_width。
  2.   第57-73行,通过函数LCD_GetStrWidth计算字符串的长度,然后根据设置的字符总宽度和实际宽度做比较,来实现左对齐,右对齐和居中显示。由于函数LCD_DispStr调用LCD_DispStrEx时,将形参_Width设置为0,所以这部分代码功能未用到。
  3.   第76-87行,用于填充显示字符以外区域的背景色,只有设置的字符总宽度大于实际宽度时才会用到,填充的就是实际宽度以外的区域。
  4.   第90-146行,显示所有字符。
    •   第95行,如果编码值小于0x80,表示ASCII字符。
    •   第99行,根据编码值读取ASCII值对应的点阵数据到数组buf里面。
    •   第100行,显示ASCII字符仅需要一半宽度即可,比如显示12*12点阵字符,显示成ASCII仅需6*12即可。
    •   第102行,如果编码值大于等于0x80,汉字编码在这个范围。
    •   第104行,因为GB编码需要两个字节表示,所以这里再读取一个字节。
    •   第110行,根据汉字编码值对应的点阵数据到数组buf里面。
    •   第116-136行,采用从左到右,从上到下的方式刷新字符。这里特别注意点阵数据位置的获取:buf[m * ((2 * width) / font_width) + i / 8] & (0x80 >> (i % 8 )

对于这个公式,大家通过代数法,代入两次数值就好理解了。

53.5.3 函数_LCD_ReadAsciiDot

此函数的作用是根据ASCII编码值,读取对应的点阵数据出来。

1.    /*
2.    ******************************************************************************************************
3.    *    函 数 名: _LCD_ReadAsciiDot
4.    *    功能说明: 读取1个ASCII字符的点阵数据
5.    *    形    参:
6.    *        _code : ASCII字符的编码,1字节。1-128
7.    *        _fontcode :字体代码
8.    *        _pBuf : 存放读出的字符点阵数据
9.    *    返 回 值: 文字宽度
10.    ******************************************************************************************************
11.    */
12.    static void _LCD_ReadAsciiDot(uint8_t _code, uint8_t _fontcode, uint8_t *_pBuf)
13.    {
14.        const uint8_t *pAscDot;
15.        uint8_t font_bytes = 0;
16.    
17.        pAscDot = 0;
18.        switch (_fontcode)
19.        {
20.            case FC_ST_12:        /* 12点阵 */
21.                font_bytes = 24;
22.                pAscDot = g_Ascii12;    
23.                break;
24.            
25.            case FC_ST_24:
26.            case FC_ST_32:
27.            case FC_ST_16:
28.                /* 缺省是16点阵 */
29.                font_bytes = 32;
30.                pAscDot = g_Ascii16;
31.                break;
32.            
33.            case FC_RA8875_16:
34.            case FC_RA8875_24:
35.            case FC_RA8875_32:
36.                return;
37.        }    
38.    
39.        /* 将CPU内部Flash中的ascii字符点阵复制到buf */
40.        memcpy(_pBuf, &pAscDot[_code * (font_bytes / 2)], (font_bytes / 2));    
41.    }

下面将此函数涉及到的知识点为大家做个阐释:

  •   第20-23行,显示12点阵ASCII,每个字符需要24个字节,存储在数组g_Ascii12里面。
  •   第25-31行,显示16,24和32点阵ASCII,这里采用同一大小字符进行显示。每个字符需要32个字节,存储在数组g_Ascii16里面。
  •   第33-35行,这个是RA8875的字库处理,V7开发板用不到。
  •   第40行,将ASCII点阵数据复制到缓冲_pBuf里面。

53.5.4 函数_LCD_ReadHZDot

此函数的作用是根据ASCII编码值,读取对应的点阵数据出来。

1.    /*
2.    ******************************************************************************************************
3.    *    函 数 名: _LCD_ReadHZDot
4.    *    功能说明: 读取1个汉字的点阵数据
5.    *    形    参:
6.    *        _code1, _cod2 : 汉字内码. GB2312编码
7.    *        _fontcode :字体代码
8.    *        _pBuf : 存放读出的字符点阵数据
9.    *    返 回 值: 无
10.    ******************************************************************************************************
11.    */
12.    static void _LCD_ReadHZDot(uint8_t _code1, uint8_t _code2,  uint8_t _fontcode, uint8_t *_pBuf)
13.    {
14.        #ifdef USE_SMALL_FONT    /* 使用CPU 内部Flash 小字库 */
15.            uint8_t *pDot;
16.            uint8_t font_bytes = 0;
17.            uint32_t address;
18.            uint16_t m;
19.    
20.            pDot = 0;    /* 仅仅用于避免告警 */
21.            switch (_fontcode)
22.            {
23.                case FC_ST_12:        /* 12点阵 */
24.                    font_bytes = 24;
25.                    pDot = (uint8_t *)g_Hz12;    
26.                    break;
27.                
28.                case FC_ST_16:
29.                    font_bytes = 32;
30.                    pDot = (uint8_t *)g_Hz16;
31.                    break;
32.        
33.                case FC_ST_24:
34.                    font_bytes = 72;
35.                    pDot = (uint8_t *)g_Hz24;
36.                    break;            
37.                    
38.                case FC_ST_32:    
39.                    font_bytes = 128;
40.                    pDot = (uint8_t *)g_Hz32;
41.                    break;                        
42.                
43.                case FC_RA8875_16:
44.                case FC_RA8875_24:
45.                case FC_RA8875_32:
46.                    return;
47.            }    
48.    
49.            m = 0;
50.            while(1)
51.            {
52.                address = m * (font_bytes + 2);
53.                m++;
54.                if ((_code1 == pDot[address + 0]) && (_code2 == pDot[address + 1]))
55.                {
56.                    address += 2;
57.                    memcpy(_pBuf, &pDot[address], font_bytes);
58.                    break;
59.                }
60.                else if ((pDot[address + 0] == 0xFF) && (pDot[address + 1] == 0xFF))
61.                {
62.                    /* 字库搜索完毕,未找到,则填充全FF */
63.                    memset(_pBuf, 0xFF, font_bytes);
64.                    break;
65.                }
66.            }
67.        #else    /* 用全字库 */
68.            uint8_t *pDot = 0;
69.            uint8_t font_bytes = 0;
70.                
71.            switch (_fontcode)
72.            {
73.                case FC_ST_12:        /* 12点阵 */
74.                    font_bytes = 24;
75.                    pDot = (uint8_t *)HZK12_ADDR;    
76.                    break;
77.                
78.                case FC_ST_16:
79.                    font_bytes = 32;
80.                    pDot = (uint8_t *)HZK16_ADDR;
81.                    break;
82.        
83.                case FC_ST_24:
84.                    font_bytes = 72;
85.                    pDot = (uint8_t *)HZK24_ADDR;
86.                    break;            
87.                    
88.                case FC_ST_32:    
89.                    font_bytes = 128;
90.                    pDot = (uint8_t *)HZK32_ADDR;
91.                    break;                        
92.                
93.                case FC_RA8875_16:
94.                case FC_RA8875_24:
95.                case FC_RA8875_32:
96.                    return;
97.            }            
98.        
99.            /* 此处需要根据字库文件存放位置进行修改 */
100.            if (_code1 >=0xA1 && _code1 <= 0xA9 && _code2 >=0xA1)
101.            {
102.                pDot += ((_code1 - 0xA1) * 94 + (_code2 - 0xA1)) * font_bytes;
103.            }
104.            else if (_code1 >=0xB0 && _code1 <= 0xF7 && _code2 >=0xA1)
105.            {
106.                pDot += ((_code1 - 0xB0) * 94 + (_code2 - 0xA1) + 846) * font_bytes;
107.            }
108.            memcpy(_pBuf, pDot, font_bytes);
109.        #endif
110.    }

下面将此函数涉及到的知识点为大家做个阐释:

  •   第15-66行,小字库显示,这个方案既可以显示小字库,也可以显示全字库。
    •   第23-41行,获取12点阵,16点阵,24点阵和32点阵汉字显示需要的字节数以及存储点阵数据的缓冲地址。
    •   第49-66行,这里是通过比较汉字的编码值找到点阵数据位置,如果遇到两个0xFF,表示检索到数组末尾了也没有找到汉字点阵数组。找到数据后,将其复制到缓冲_pBuf里面。
  •   第68-108行,本章暂时用不到这种方案,后面章节用到这种方案了再为大家做说明。

53.6 LCD驱动移植和使用

与第51章51.7小节相同,这里就不再赘述了。

53.7 实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

  第1阶段,上电启动阶段:

  • 这部分在第14章进行了详细说明。

  第2阶段,进入main函数:

  •   第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED,串口,LCD,SDRAM等。
  •   第2步,LCD应用程序设计部分,显示汉字。通过按键实现三种界面的处理,其中GB2312有几十个界面。

53.8 实验例程说明(MDK)

配套例子:

V7-033_LCD的汉字小字库和全字库制作实验

实验目的:

  1. 学习LCD的汉字小字库和全字库制作实验。

实验内容:

  1. 小字库和全字库通过此软件生成:http://www.armbbs.cn/forum.php?mod=viewthread&tid=202
  2. LCD界面上展示ASCII字符和GB2312编码汉字。
  3. 启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。

实验操作:

  1. 摇杆上键,增加LCD背景光亮度。
  2. 摇杆下键,降低LCD背景光亮度。
  3. 摇杆左键,显示上一页汉字。
  4. 摇杆右键,显示下一页汉字。
  5. 摇杆OK键,返回首页。

LCD的界面显示效果如下:

部分截图:

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

程序设计:

系统栈大小分配:

RAM空间用的DTCM:

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
*    函 数 名: bsp_Init
*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
       - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
       - 设置NVIV优先级分组为4。
     */
    HAL_Init();

    /* 
       配置系统时钟到400MHz
       - 切换使用HSE。
       - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并开启 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    bsp_InitTimer();      /* 初始化滴答定时器 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
    bsp_InitLed();        /* 初始化LED */    

    bsp_InitI2C();     /* 初始化I2C总线 */
    TOUCH_InitHard();   /* 初始化触摸芯片,LCD面板型号的检查也在此函数,所以要在函数LCD_InitHard前调用 */ 
    LCD_InitHard();     /* 初始化LCD */
}

MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和SDRAM。由于SDRAM要用于LCD的显存,方便起见,直接将其配置为WT模式。

/*
*********************************************************************************************************
*    函 数 名: MPU_Config
*    功能说明: 配置MPU
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

    /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
    /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置SDRAM的MPU属性为Write through, read allocate,no write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0xC0000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_32MB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 数 名: CPU_CACHE_Enable
*    功能说明: 使能L1 Cache
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

主功能:

主程序实现如下操作:

  •   LCD界面上展示ASCII字符和GB2312编码汉字
  •   启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。
  •   通过按键来实现翻页功能,方便查看所有GB2312编码汉字。
/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: c程序入口
*    形    参: 无
*    返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
    uint16_t ucBright;           /* 背光亮度(0-255) */
    uint8_t ucKeyCode;        /* 按键代码 */
    uint8_t ucStatus;        /* 主程序状态字 */
    uint8_t fRefresh;        /* 刷屏请求标志,1表示需要刷新 */
    
    
    bsp_Init();    /* 硬件初始化 */
    PrintfLogo();    /* 打印例程名称和版本等信息 */
    PrintfHelp();    /* 打印操作提示 */

    /* 延迟200ms再点亮背光,避免瞬间高亮 */
    bsp_DelayMS(200); 
    
    DispFirstPage();    /* 显示第1页 */
    
    /* 界面整体显示完毕后,再打开背光,设置为缺省亮度 */
    bsp_DelayMS(100); 
    ucBright = BRIGHT_DEFAULT;
    LCD_SetBackLight(ucBright);
    
    bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */
    
    /* 进入主程序循环体 */
    ucStatus = 0;
    fRefresh = 0;    
    while (1)
    {
        /* 判断软件定时器0是否超时 */
        if(bsp_CheckTimer(0))
        {
            /* 每隔200ms 进来一次 */  
            bsp_LedToggle(2);
        }
        
        if (fRefresh == 1)
        {
            fRefresh = 0;

            switch (ucStatus)
            {
                case 0:
                    DispFirstPage();    /* 显示第1页 */
                    break;

                case 1:
                    DispAsciiDot();        /* 显示ASCII点阵 */
                    break;

                default:
                    /* 区码范围 :1 - 87 */
                    if (ucStatus <= 89)
                    {
                        DispHZK16(ucStatus);    /* 显示一个区的94个汉字 */
                    }
                    break;
            }
        }

        ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            /* 有键按下 */
            switch (ucKeyCode)
            {
                case JOY_DOWN_L:        /* 摇杆LEFT键按下 */
                    if (ucStatus > 0)
                    {
                        ucStatus--;
                    }
                    fRefresh = 1;        /* 请求刷新LCD */
                    break;

                case JOY_DOWN_R:        /* 摇杆RIGHT键按下 */
                    if (ucStatus < DEMO_PAGE_COUNT - 1)
                    {
                        ucStatus++;
                    }
                    fRefresh = 1;        /* 请求刷新LCD */
                    break;

                case  JOY_DOWN_OK:        /* 摇杆OK键 */
                    ucStatus = 0;        /* 返回首页 */                    
                    fRefresh = 1;        /* 请求刷新LCD */
                    break;

                case JOY_DOWN_U:        /* 摇杆UP键按下 */
                    ucBright += BRIGHT_STEP;
                    if (ucBright > BRIGHT_MAX)
                    {
                        ucBright = BRIGHT_MAX;
                    }
                    LCD_SetBackLight(ucBright);
                    printf("当前背景光亮度 : %d\r\n", ucBright);
                    break;

                case JOY_DOWN_D:        /* 摇杆DOWN键按下 */
                    if (ucBright < BRIGHT_STEP)
                    {
                        ucBright = 0;
                    }
                    else
                    {
                        ucBright -= BRIGHT_STEP;
                    }
                    LCD_SetBackLight(ucBright);
                    printf("当前背景光亮度 : %d\r\n", ucBright);
                    break;

                default:
                    break;
            }
        }
    }
}

下面的代码用于LCD首页显示:

/*
*********************************************************************************************************
*    函 数 名: DispFirstPage
*    功能说明: 显示操作提示
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
static void DispFirstPage(void)
{
    FONT_T tFont;            /* 定义一个字体结构体变量,用于设置字体参数 */
    uint16_t y;            /* Y坐标 */
    uint16_t usLineCap;    /* 行高 */
    uint8_t buf[50];

    LCD_ClrScr(CL_BLUE);          /* 清屏,背景蓝色 */
    
    
    /* 设置字体属性 */
    tFont.FontCode = FC_ST_16;        /* 字体选择宋体16点阵,高16x宽15) */
    tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
    tFont.BackColor = CL_MASK;         /* 文字背景颜色,透明 */
    tFont.Space = 0;                /* 字符水平间距, 单位 = 像素 */

    y = 0;
    usLineCap = 18; /* 行间距 */
    LCD_DispStr(5, y, "安富莱STM32-V7开发板  www.armfly.com", &tFont);
    y += usLineCap;
    LCD_DispStr(5, y, "汉字小字库和全字库测试实验", &tFont);
    
    y += 2 * usLineCap;
    LCD_DispStr(30, y, "操作提示:", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆上键 = 增加背光亮度", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆下键 = 降低背光亮度", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆左键 = 向前翻页", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆右键 = 向后翻页", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆OK键 = 返回首页", &tFont);

    y += 2 * usLineCap;    
    
    sprintf((char *)buf, "显示器分辨率 :%dx%d", g_LcdWidth, g_LcdHeight);
    LCD_DispStr(5, y, (char *)buf, &tFont);
    
    y += usLineCap;
    LCD_DispStr(5, y, "每行可以显示25个汉字,或50个字符", &tFont);
}

下面是ASCII字符显示:

/*
*********************************************************************************************************
*    函 数 名: DispAsciiDot
*    功能说明: 显示ASCII点阵
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
static void DispAsciiDot(void)
{
    uint8_t i,k;
    FONT_T tFont;        /* 定义一个字体结构体变量,用于设置字体参数 */
    uint16_t x;        /* X坐标 */
    uint16_t y;        /* Y坐标 */    
    char buf[32 + 1];
    uint8_t ascii;

    LCD_ClrScr(CL_BLUE);          /* 清屏,背景蓝色 */
    
    /* 设置字体属性 */
    tFont.FontCode = FC_ST_16;        /* 字体选择宋体16点阵,高16x宽15) */
    tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
    tFont.BackColor = CL_MASK;         /* 文字背景颜色,透明 */
    tFont.Space = 2;                /* 字符水平间距, 单位 = 像素 */

    LCD_DispStr(50, 0, "16点阵ASCII码字库,代码1-127", &tFont);

    x = 50;
    y = 40;
    ascii = 0;
    for (i = 0; i < 4; i++)
    {
        for (k = 0; k < 32; k++)
        {
            buf[k] = ascii++;
        }
        buf[32] = 0;
        if (buf[0] == 0)
        {
            buf[0] = ' ';
        }
        LCD_DispStr(x, y, buf, &tFont);
        y += 20;
    }
}

下面是GB2312编码字符显示:

/*
*********************************************************************************************************
*    函 数 名: DispHZK16
*    功能说明: 显示16点阵汉字阵
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
static void DispHZK16(uint8_t _ucIndex)
{
    uint8_t i,k;
    uint16_t x,y;
    char buf[50 + 1];
    uint8_t code1,code2;    /* 汉字内码 */
    uint8_t usLineCap = 18;
    FONT_T tFont;            /* 定义一个字体结构体变量,用于设置字体参数 */

    printf(" Display HZK Area Code = %d\r\n", _ucIndex - 1);

    if (_ucIndex == 2)
    {
        /* 第1次清屏,以后显示位置不变,可以不清屏,避免闪烁 */
        LCD_ClrScr(CL_BLUE);          /* 清屏,背景蓝色 */
    }
    
    /* 设置字体属性 */
    tFont.FontCode = FC_ST_16;        /* 字体选择宋体16点阵,高16x宽15) */
    tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
    tFont.BackColor = CL_BLUE;         /* 文字背景颜色,蓝色 */
    tFont.Space = 0;                /* 字符水平间距, 单位 = 像素 */

    y = 0;
    LCD_DispStr(20, y, "国标GB2312 16点阵汉字库(区码1-87,位码1-94)", &tFont);

    code1 = _ucIndex - 1; /* 得到区码 */
    code2 = 1;            /* 位码从1开始 */

    y += usLineCap;
    sprintf((char *)buf, (char *)"当前区码: %2d, 本页位码:1-94, 第10-15区无字符", code1);
    LCD_DispStr(20, y, buf, &tFont);
    y += (2 * usLineCap);

    /*
        机内码高位 = 区码 + 0xA0
        机内码低位 = 位码 + 0xA0

        区码范围 :1 - 87
        位码范围 : 1 - 94

        每行显示20个汉字,一个区是94个汉字,需要5行显示,第5行显示14个汉字
    */
    
    x = 40;
    code1 += 0xA0; /* 换算到内码高位 */
    code2 = 0xA1;  /* 内码低位起始 */
    for (i = 0; i < 5; i++)
    {
        for (k = 0; k < 20; k++)
        {
            buf[2 * k] = code1;
            buf[2 * k + 1] = code2;
            code2++;
            if ((i == 4) && (k == 13))
            {
                k++;
                break;
            }
        }
        buf[2 * k] = 0;
        LCD_DispStr(x, y, buf, &tFont);
        y += usLineCap;
    }
}

53.9 实验例程说明(IAR)

配套例子:

V7-033_LCD的汉字小字库和全字库制作实验

实验目的:

  1. 学习LCD的汉字小字库和全字库制作实验。

实验内容:

  1. 小字库和全字库通过此软件生成:http://www.armbbs.cn/forum.php?mod=viewthread&tid=202
  2. LCD界面上展示ASCII字符和GB2312编码汉字。
  3. 启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。

实验操作:

  1. 摇杆上键,增加LCD背景光亮度。
  2. 摇杆下键,降低LCD背景光亮度。
  3. 摇杆左键,显示上一页汉字。
  4. 摇杆右键,显示下一页汉字。
  5. 摇杆OK键,返回首页。

LCD的界面显示效果如下:

部分截图:

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

程序设计:

系统栈大小分配:

RAM空间用的DTCM:

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*
*********************************************************************************************************
*    函 数 名: bsp_Init
*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
       - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
       - 设置NVIV优先级分组为4。
     */
    HAL_Init();

    /* 
       配置系统时钟到400MHz
       - 切换使用HSE。
       - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并开启 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    bsp_InitTimer();      /* 初始化滴答定时器 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
    bsp_InitLed();        /* 初始化LED */    

    bsp_InitI2C();     /* 初始化I2C总线 */
    TOUCH_InitHard();   /* 初始化触摸芯片,LCD面板型号的检查也在此函数,所以要在函数LCD_InitHard前调用 */ 
    LCD_InitHard();     /* 初始化LCD */
}

MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和SDRAM。由于SDRAM要用于LCD的显存,方便起见,直接将其配置为WT模式。

/*
*********************************************************************************************************
*    函 数 名: MPU_Config
*    功能说明: 配置MPU
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

    /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
    /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置SDRAM的MPU属性为Write through, read allocate,no write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0xC0000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_32MB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 数 名: CPU_CACHE_Enable
*    功能说明: 使能L1 Cache
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

主功能:

主程序实现如下操作:

  •   LCD界面上展示ASCII字符和GB2312编码汉字
  •   启动1个200ms的自动重装定时器,让LED2每200ms翻转一次。
  •   通过按键来实现翻页功能,方便查看所有GB2312编码汉字。
/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: c程序入口
*    形    参: 无
*    返 回 值: 错误代码(无需处理)
*********************************************************************************************************
*/
int main(void)
{
    uint16_t ucBright;           /* 背光亮度(0-255) */
    uint8_t ucKeyCode;        /* 按键代码 */
    uint8_t ucStatus;        /* 主程序状态字 */
    uint8_t fRefresh;        /* 刷屏请求标志,1表示需要刷新 */
    
    
    bsp_Init();    /* 硬件初始化 */
    PrintfLogo();    /* 打印例程名称和版本等信息 */
    PrintfHelp();    /* 打印操作提示 */

    /* 延迟200ms再点亮背光,避免瞬间高亮 */
    bsp_DelayMS(200); 
    
    DispFirstPage();    /* 显示第1页 */
    
    /* 界面整体显示完毕后,再打开背光,设置为缺省亮度 */
    bsp_DelayMS(100); 
    ucBright = BRIGHT_DEFAULT;
    LCD_SetBackLight(ucBright);
    
    bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */
    
    /* 进入主程序循环体 */
    ucStatus = 0;
    fRefresh = 0;    
    while (1)
    {
        /* 判断软件定时器0是否超时 */
        if(bsp_CheckTimer(0))
        {
            /* 每隔200ms 进来一次 */  
            bsp_LedToggle(2);
        }
        
        if (fRefresh == 1)
        {
            fRefresh = 0;

            switch (ucStatus)
            {
                case 0:
                    DispFirstPage();    /* 显示第1页 */
                    break;

                case 1:
                    DispAsciiDot();        /* 显示ASCII点阵 */
                    break;

                default:
                    /* 区码范围 :1 - 87 */
                    if (ucStatus <= 89)
                    {
                        DispHZK16(ucStatus);    /* 显示一个区的94个汉字 */
                    }
                    break;
            }
        }

        ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            /* 有键按下 */
            switch (ucKeyCode)
            {
                case JOY_DOWN_L:        /* 摇杆LEFT键按下 */
                    if (ucStatus > 0)
                    {
                        ucStatus--;
                    }
                    fRefresh = 1;        /* 请求刷新LCD */
                    break;

                case JOY_DOWN_R:        /* 摇杆RIGHT键按下 */
                    if (ucStatus < DEMO_PAGE_COUNT - 1)
                    {
                        ucStatus++;
                    }
                    fRefresh = 1;        /* 请求刷新LCD */
                    break;

                case  JOY_DOWN_OK:        /* 摇杆OK键 */
                    ucStatus = 0;        /* 返回首页 */                    
                    fRefresh = 1;        /* 请求刷新LCD */
                    break;

                case JOY_DOWN_U:        /* 摇杆UP键按下 */
                    ucBright += BRIGHT_STEP;
                    if (ucBright > BRIGHT_MAX)
                    {
                        ucBright = BRIGHT_MAX;
                    }
                    LCD_SetBackLight(ucBright);
                    printf("当前背景光亮度 : %d\r\n", ucBright);
                    break;

                case JOY_DOWN_D:        /* 摇杆DOWN键按下 */
                    if (ucBright < BRIGHT_STEP)
                    {
                        ucBright = 0;
                    }
                    else
                    {
                        ucBright -= BRIGHT_STEP;
                    }
                    LCD_SetBackLight(ucBright);
                    printf("当前背景光亮度 : %d\r\n", ucBright);
                    break;

                default:
                    break;
            }
        }
    }
}

下面的代码用于LCD首页显示:

/*
*********************************************************************************************************
*    函 数 名: DispFirstPage
*    功能说明: 显示操作提示
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
static void DispFirstPage(void)
{
    FONT_T tFont;            /* 定义一个字体结构体变量,用于设置字体参数 */
    uint16_t y;            /* Y坐标 */
    uint16_t usLineCap;    /* 行高 */
    uint8_t buf[50];

    LCD_ClrScr(CL_BLUE);          /* 清屏,背景蓝色 */
    
    
    /* 设置字体属性 */
    tFont.FontCode = FC_ST_16;        /* 字体选择宋体16点阵,高16x宽15) */
    tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
    tFont.BackColor = CL_MASK;         /* 文字背景颜色,透明 */
    tFont.Space = 0;                /* 字符水平间距, 单位 = 像素 */

    y = 0;
    usLineCap = 18; /* 行间距 */
    LCD_DispStr(5, y, "安富莱STM32-V7开发板  www.armfly.com", &tFont);
    y += usLineCap;
    LCD_DispStr(5, y, "汉字小字库和全字库测试实验", &tFont);
    
    y += 2 * usLineCap;
    LCD_DispStr(30, y, "操作提示:", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆上键 = 增加背光亮度", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆下键 = 降低背光亮度", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆左键 = 向前翻页", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆右键 = 向后翻页", &tFont);
    
    y += usLineCap;
    LCD_DispStr(50, y, "摇杆OK键 = 返回首页", &tFont);

    y += 2 * usLineCap;    
    
    sprintf((char *)buf, "显示器分辨率 :%dx%d", g_LcdWidth, g_LcdHeight);
    LCD_DispStr(5, y, (char *)buf, &tFont);
    
    y += usLineCap;
    LCD_DispStr(5, y, "每行可以显示25个汉字,或50个字符", &tFont);
}

下面是ASCII字符显示:

/*
*********************************************************************************************************
*    函 数 名: DispAsciiDot
*    功能说明: 显示ASCII点阵
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
static void DispAsciiDot(void)
{
    uint8_t i,k;
    FONT_T tFont;        /* 定义一个字体结构体变量,用于设置字体参数 */
    uint16_t x;        /* X坐标 */
    uint16_t y;        /* Y坐标 */    
    char buf[32 + 1];
    uint8_t ascii;

    LCD_ClrScr(CL_BLUE);          /* 清屏,背景蓝色 */
    
    /* 设置字体属性 */
    tFont.FontCode = FC_ST_16;        /* 字体选择宋体16点阵,高16x宽15) */
    tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
    tFont.BackColor = CL_MASK;         /* 文字背景颜色,透明 */
    tFont.Space = 2;                /* 字符水平间距, 单位 = 像素 */

    LCD_DispStr(50, 0, "16点阵ASCII码字库,代码1-127", &tFont);

    x = 50;
    y = 40;
    ascii = 0;
    for (i = 0; i < 4; i++)
    {
        for (k = 0; k < 32; k++)
        {
            buf[k] = ascii++;
        }
        buf[32] = 0;
        if (buf[0] == 0)
        {
            buf[0] = ' ';
        }
        LCD_DispStr(x, y, buf, &tFont);
        y += 20;
    }
}

下面是GB2312编码字符显示:

/*
*********************************************************************************************************
*    函 数 名: DispHZK16
*    功能说明: 显示16点阵汉字阵
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
static void DispHZK16(uint8_t _ucIndex)
{
    uint8_t i,k;
    uint16_t x,y;
    char buf[50 + 1];
    uint8_t code1,code2;    /* 汉字内码 */
    uint8_t usLineCap = 18;
    FONT_T tFont;            /* 定义一个字体结构体变量,用于设置字体参数 */

    printf(" Display HZK Area Code = %d\r\n", _ucIndex - 1);

    if (_ucIndex == 2)
    {
        /* 第1次清屏,以后显示位置不变,可以不清屏,避免闪烁 */
        LCD_ClrScr(CL_BLUE);          /* 清屏,背景蓝色 */
    }
    
    /* 设置字体属性 */
    tFont.FontCode = FC_ST_16;        /* 字体选择宋体16点阵,高16x宽15) */
    tFont.FrontColor = CL_WHITE;    /* 字体颜色设置为白色 */
    tFont.BackColor = CL_BLUE;         /* 文字背景颜色,蓝色 */
    tFont.Space = 0;                /* 字符水平间距, 单位 = 像素 */

    y = 0;
    LCD_DispStr(20, y, "国标GB2312 16点阵汉字库(区码1-87,位码1-94)", &tFont);

    code1 = _ucIndex - 1; /* 得到区码 */
    code2 = 1;            /* 位码从1开始 */

    y += usLineCap;
    sprintf((char *)buf, (char *)"当前区码: %2d, 本页位码:1-94, 第10-15区无字符", code1);
    LCD_DispStr(20, y, buf, &tFont);
    y += (2 * usLineCap);

    /*
        机内码高位 = 区码 + 0xA0
        机内码低位 = 位码 + 0xA0

        区码范围 :1 - 87
        位码范围 : 1 - 94

        每行显示20个汉字,一个区是94个汉字,需要5行显示,第5行显示14个汉字
    */
    
    x = 40;
    code1 += 0xA0; /* 换算到内码高位 */
    code2 = 0xA1;  /* 内码低位起始 */
    for (i = 0; i < 5; i++)
    {
        for (k = 0; k < 20; k++)
        {
            buf[2 * k] = code1;
            buf[2 * k + 1] = code2;
            code2++;
            if ((i == 4) && (k == 13))
            {
                k++;
                break;
            }
        }
        buf[2 * k] = 0;
        LCD_DispStr(x, y, buf, &tFont);
        y += usLineCap;
    }
}

53.10   总结

本章节涉及到的知识点比较多,需要大家花点时间去掌握,直至可以独立驱动一个显示屏。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【STM32H7教程】第26章 STM32H7的TCM,SRAM等五块内存的超方便使用方式

    完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980

    armfly
  • 【STM32H7教程】第23章 STM32H7的MPU内存保护单元(重要)

    本章节为大家讲解STM32H7学习中的一个重要知识点MPU(Memory Protection Unit,内存保护单元),早在STM32F1和F4芯片上面也是有...

    armfly
  • 【STM32H7的DSP教程】第18章 DSP控制函数-更好用的SIN,COS计算

    完整版教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547

    armfly
  • tataUFO 大数据应用实践

    感谢tataUFO各位同仁的大力支持,帮助我顺利完成了在QingCloud Insigt 2016技术大会上的演讲,特将讲稿PPT分享出来,共勉!

    半吊子全栈工匠
  • GetFaviconAPI(获取站点Favicon)

    FHYC
  • android 仿qq手写板涂鸦

    以前博客的链接:点击打开链接 附上关键代码: MainView.java package com.tszy.views;   import java.io....

    xiangzhihong
  • Serverless+SCF=打倒服务器,解放程序员

    在很多外行人的眼里,程序员就是神一样的存在。他们全年996,节假日无休,不仅Java、PHP、C++要样样精通,还要会修电脑修音响修手机,做前端要懂运维,做后台...

    云加社区技术沙龙
  • 纯代码实现给网站添加单曲播放和列表播放 网易云音乐

    进入本站首页或者本文章时相信你已经看到我的音乐播放器了吧,想不想在自己网站也按照上呢?这个功能其实很简单,经过全百科网搜集已经为朋友们整理好了特别精简的播放器代...

    于飞云计算
  • Python 开发环境管理

    虚拟环境主要合适 virtualenv 和 virtualenvwrapper 来管理,在 Windows 系统上,则使用的是 virtualenvwrappe...

    zucchiniy
  • 存储同步过程中断,阵列失效,卷无法挂载解决过程

    7月9日,我公司前台接到一个客户咨询电话,客户的存储设备瘫痪了,咨询可否恢复存储里的数据。客服人员立刻安排存储数据恢复工程师了解存储状况,经过数据恢复工程师的简...

    北亚数据恢复中心

扫码关注云+社区

领取腾讯云代金券