前言

参考博主/文章

STM32物联网项目-HMI串口屏

水比赛系列-HMI串口屏的使用

【我的电赛日记(一)】HMI USART串口屏

资源网站

陶晶驰资料中心

陶晶驰官网

LVGL

HMI串口屏

基本操作

  • 选型表(可在官网查找)

  • 下载上位机

首先去官网资料那下载最新版的上位机软件,解压安装即可

  • 新建工程选择串口屏的型号

下载完直接打开,然后点击左上角【新建】,选择工程要保存的路径(可提前在盘下新建一个文件夹存放),然后弹出选择型号界面,选择型号(可看屏幕的背面)

显示方向的话看个人需要

最好一开始就确定编码,不建议修改工程编码,会导致乱码等问题

  • 进去后默认空白,右边的 page 0 表示上电显示第一个页面(可更改这个名字)

调试/下载

  • 外部供电接法

  • 下载程序到屏幕

需要用USB转TTL模块连接屏幕,芯片为cp2102或者ft232芯片,ch340也不是不可以但是假货太多可能会导致无法下载

然后点击下载,下载波特率的话选择最高921600(这个跟通信波特率是分开的互不干扰),可能第一次下载会弹出固件版本低啥的需要更新点击更新即可

  • 连接STM32,使用到串口1引脚 PA9(TX)PA10(RX)

  • 调试

使用模拟器调试的话,直接在左下方的框写就行了,不需要写结束符(自带了),右边会显示模拟器返回的数据,如果没有返回表示成功,返回其他的红色报错表示有问题

通讯协议

单片机to串口屏

首先需要单片机已经调通串口,重定向printf

发完一次数据要发一个结束符: 发送3个0xFF作为结束符,需要注意转义字符需要加 \

属性栏中显示为 绿色 的属性都可以在运行中修改, 黑色 的属性只能在编辑时修改,不能在运行中修改

// 格式一般是:页面名.控件名.属性值 = xxx
printf("Main.g0.txt=\"你好\"\xFF\xFF\xFF");

对应十六进制是:

如果是 program.s 中使用int定义的整形,例如sys0,我们要让sys0显示为123,则通过单片机发送:

printf("sys0=123\xFF\xFF\xFF")

串口屏to单片机

没有固定格式,用户自定义即可

常见是 帧头+主命令+副1命令+副2命令+副3命令+校验和+帧尾

一般使用指令集 printh 或者 prints ,发送16进制格式需要必须写成偶数,不能单数,0x前缀不需要写,也可以发送控件的值回去

控件使用

认识控件

  • 控件是什么

在串口屏上位机的界面编辑窗口中,一切你能看到的,都是控件

有些控件是看不见的,比如定时器,触摸捕捉等控件,位于底部的特殊控件栏

  • 控件事件

控件事件,指的是这个控件被操作时要执行的功能。

  1. 触摸被按下:对应名称叫做【按下事件】

  2. 触摸被按下后弹起:对应名称叫做【弹起事件】

  3. 滑块控件被滑动:对应的名称叫做【滑动事件】

  4. 定时器定时运行:对应的名称叫做【定时事件】

  5. 动画播放完成:对应的名称叫做【播放完成事件】

  6. 视频播放完成:对应的名称叫做【播放完成事件】

  • 控件属性

【type】

控件类型,可读,不可更改,不同类型的控件type属性不一样,相同类型的控件type属性一样

使用场景:

  1. 在键盘页面判断跳转过来的控件是什么类型的控件,不同的控件有不同的转换方法

  2. 在触摸捕捉中判断按下去的控件是什么类型

【id】

控件id,可通过上位机左上角的上下箭头置顶或置底,不能通过指令进行修改

每个页面中最底层都有一个页面控件,其id固定为0,可以设置当前页面的背景图片,背景色等操作

除了页面控件,每个页面最多放250个控件,占据id 1-250 ,每个页面各个控件之间id号是唯一的,不会重复

可以理解为控件的图层,id大的控件会挡住id小的控件

使用场景:

通过名称组操作控件

【objname】

控件名称,可通过上位机进行修改,不可通过指令更改,不可读取

【vscope】

内存占用: 0-私有;1-全局; 可通过上位机进行修改,可读取,不可通过指令更改

私有占用只能在当前页面被访问,全局占用可以在所有页面被访问

当设置为私有时,跳转页面后,该控件占用的内存会被释放,重新返回该页面后该控件会恢复到最初的设置

页面控件

页面控件拥有以下五个事件:

  1. 前初始化事件:每次在执行page命令时,在执行页面刷新操作以前,串口屏会自动执行一次《前初始化事件》中的代码
  2. 后初始化事件:每次在执行page命令时,在页面刷新操作完成以后,串口屏会自动执行一次《后初始化事件》中的代码
  3. 页面按下事件:显示区域内且没有控件的区域,触摸被按下的瞬间,串口屏会自动执行一次页面的《按下事件》中的代码
  4. 页面弹起事件:显示区域内且没有控件的区域(以触摸按下是的坐标为准),触摸按下以后松开触摸的的瞬间,串口屏会自动执行一次页面的《弹起事件》中的代码
  5. 页面离开事件:每次在执行page切换新的页面前,串口屏会自动执行一次当前页面的《页面离开事件》中的代码
  • 常用属性

up:上滑翻页页面ID(255为无滑动翻页) [X_TRUE]

down :下滑翻页页面ID(255为无滑动翻页) [X_TRUE]

left :左滑翻页页面ID(255为无滑动翻页) [X_TRUE]

right :右滑翻页页面ID(255为无滑动翻页) [X_TRUE]

sta :背景填充方式: 0–无背景;1–单色;2–图片

bco :背景色,仅sta设置为单色时可用

pic :背景图片(必须是全屏图片),仅sta设置为图片时可用,不允许为空

文本控件

想要显示文本信息,包括数字,字母,符号,汉字和其他各国语言,使用前需要 提前导入字库

然后字库的选择那如果选择 所有字符 的话生成的文件很大,一般是需要什么字就指定什么字

GB2312编码下,一个英文或者数字占1个字节,一个中文占2个字节

utf-8编码下,一个英文或者数字占1个字节,一个中文占3个字节

字符串属性-数值型属性,将会删除字符串属性后面若干个字符

单片机跟串口屏赋值什么的需要保证单片机与串口屏的编码相同,否则串口屏会显示乱码,或者不显示(串口屏默认编码是gb2312)

屏幕返回 1C FF FF FF 表示赋值语句格式错误

// 例如一个字符串是: "你好呀"
// 则发送下面指令
printf("main.t0.txt=\"测试\"-1\xFF\xFF\xFF");
// 结果是字符串还剩下:"你好"

目前仅有 txt、path、dir、filter 这4个属性属性是字符串型属性,其他属性一般都是数值型属性

数值型属性和字符串属性互相转换需要使用 covx 指令

  • 创建字库(指定)

点击【生成字库】然后把它保存在工程目录下即可

  • 文本控件跨页面使用

假设page0页面的t0文本控件,如果需要跨页面使用,需要将该控件的 vscope 设置为全局

  • 文本和数字之间相互转换

使用这个的前提是对应控件设置为全局

lenth始终表示的是字符串长度, 数值转字符串的时候是目标变量的长度,字符串转数值的时候是源变量长度

当原变量是数值属性时,目标变量必须是字符串属性,当原变量是字符串属性时,目标变量必须是数值属性。

如果目标变量和源变量类型相同,转换失败

串口屏上仅有两种数据类型,即数值和字符串类型

// 示例格式
covx att1,att2,lenth,format

att1:源变量

att2:目标变量

lenth:字符串的长度(0为自动长度,非0为固定长度)

format:申明数值类型(0-数字;1-货币;2-Hex)

  • 在文本控件后面追加文本
// 示例
t0.txt+="哈哈哈"

//将t1.txt追加在t0.txt后面
t0.txt+=t1.txt

//可以跨页面追加文本,前提是对应控件设置为全局
main.t0.txt+=page0.t1.txt
  • 删除文本控件后面的几个字符(只能从后面开始删除)
//删除1个字符
 t0.txt-=1

 //删除n0.val个字符
 t0.txt-=n0.val

 //可以跨页面删除,前提是对应控件设置为全局,假设这个汉字只有2个,但是-了10个字节也不会报错可能内部有判断
 main.t0.txt-=main.n0.val
  • 清空文本控件
//将文本赋值为空
t0.txt=""
  • 键盘

某个控件绑定了键盘时,当点击了该控件时,仅会触发控件的 按下事件,接下来键盘 跳转事件 会被触发, 弹起事件 中的内容将永远不会被执行

键盘并不是一个单纯的控件,键盘是一个独立的页面,调用键盘时,会触发当前页面的 <页面离开事件> 以及键盘页面的 <前初始化事件><后初始化事件>

点击键盘的OK按键时,会触发键盘页面的 <页面离开事件> 以及控件调用页面的 <前初始化事件><后初始化事件>

当触发了键盘控件跳转了页面时,当前页面私有的控件会被回收,从键盘页面返回时,私有的控件初始化并变回初始状态

  • 换行

在控件的事件中给文本赋值时用 \r

单片机通过串口发送给屏幕时用 \r\n

  • 常用属性

key :绑定键盘

sta :背景填充方式: 0–切图;1–单色;2–图片;3–透明(仅x系列支持透明)

pco :字体色 [TRUE]

pw :是否显示为密码(内容仍为实际内容,仅仅显示出来为*): 0–否;1–是 [TRUE]

txt:字符内容 [TRUE]

isbr:是否自动换行:0–否;1–是 [TRUE]

style:显示风格: 0–平面;1–边框;2–3D_Down;3–3D_Up;4–3D_Auto,sta为单色时才有这个属性

borderc:边框颜色。当style设置为边框时可用 [X_TRUE]

borderw:边框粗细。当style设置为边框时可用。最大值:255 [X_TRUE]

滚动控件

需要提前导入字库

滚动文本不建议换行显示,会导致显示效果有问题

  • 常用属性值

txt:填写内容的地方 [TRUE]

txt_maxl:字符最大长度(即分配内存空间)

spax:字符横向间距(最小0,最大255) [X_TRUE]

spay:字符纵向间距(最小0,最大255) [X_TRUE]

tim:滚动周期(单位ms,最小80,最大65534) [TRUE]

dir:滚动方向: 0–从左往右; 1–从右往左; 2–从上往下; 3–从下往上 [TRUE]

xcen:水平对齐: 0-靠左;1-居中;2-靠右 [TRUE]

ycen:垂直对齐: 0-靠上;1-居中;2-靠下 [TRUE]

style:显示风格: 0–平面;1–边框;2–3D_Down;3–3D_Up;4–3D_Auto,sta为单色时才有这个属性

borderc:边框颜色。当style设置为边框时可用 [X_TRUE]

borderw:边框粗细。当style设置为边框时可用。最大值:255 [X_TRUE]

sta:背景填充方式: 0–切图;1–单色;2–图片;3–透明(仅x系列支持透明)

font:控件调用的字库id,调用不同的字库会显示不同的字体或字号 [TRUE]

数字控件

需要提前导入字库

给数字控件赋值必须是整形(int),否则会出错

数字控件val赋值范围 -2147483648~2147483647

字控件是可以显示负数的,如果负号没有显示,请检查当前调用的字库是否有”-”

跨页使用的话需要设置为全局

  • 使用数字控件显示RTC时钟数据

仅k0,x5系列支持,参考:http://wiki.tjc1688.com/widgets/Number.html

  • 显示小数

使用虚拟浮点数控件或者文本控件

  • 数字控件如何在一定情况不显示默认值

解决方法一:数字控件默认值是一直有的,不能像文本控件为空。要实现数字控件默认值为空的话,可以先将数字控件属性font(字库)设置为不含数字的字库。赋值的时候再将font(字库)设置为包含数字的字库。

解决方法二:通过一个空白控件挡住,当数字控件不显示为0时,将空白控件通过vis指令隐藏

  • 常用属性

val :初始值(最小-2147483648,最大2147483647) [TRUE]

length: 显示位数(0为自动,最大15位) [TRUE]

format :格式化类型: 0–数字;1–货币;2–Hex [TRUE]

虚拟浮点数控件

需要提前导入字库

虚拟浮点数控件是可以显示负数的,如果负号没有显示,请检查当前调用的字库是否有”-”

用于在串口屏上显示浮点型(float)类型的数据,但是本质上仍然是整形(int)

虚拟浮点数本质上还是整数,只是额外显示了一个小数点,但是这个小数点其实是假的

  • 虚拟浮点数计算

需要把所有浮点数的小数位设置为相等

  • 浮点数发送给单片机
// 屏幕发送数值,此时单片机使用float类型变量x接收存储
prints x0.val,0
// 然后单片机发送小数位,此时单片机将这个值保存到u8类型变量j
prints x0.vvs1,0
// 单片机将乘以j次10
uint32_t j2 = 1;    
for(i=0;i<j;i++)
{
    j2=j2*10;
}    
// 然后将float变量除以j2得到最终浮点数
x = x / j2;
j2 = 1;
  • 常用属性

val :初始值(最小-2147483648,最大2147483647) [TRUE]

vvs0 :整数位数(0为自动,最大10位) [TRUE]

vvs1 :小数位数(0为无小数,最大8位) [TRUE]

按钮控件

需要提前导入字库

  • 通过按钮跳转Display页面
// 以下内容写在按钮控件的【弹起事件】里
page Display
  • 让按钮在开始和停止之间切换
// 以下内容写在按钮控件的【弹起事件】里
if(Main.b0.txt=="开始")
{
  Main.b0.txt="停止"
}
else
{
  Main.b0.txt="开始"
}
  • 按钮禁用触摸功能

使用 tsw 指令来实现

// b0是按钮控件,不需要在前面加页面名否则报错
// 禁用触摸
tsw b0,0
// 开启触摸
tsw b0,1    

也可以使用其他方法来实现,比如文本控件下的按下事件或者弹起事件里判断bco或者pic属性,只有属性为自己需要时才可触发对应的代码

  • 使用按钮实现长按功能

需配合定时器使用

// 点击左边栏创建一个定时器,设置定时2000,en=0禁用先
// 在按钮控件的【按下事件】写
tm0.tim=2000
tm0.en=1
// 在按钮控件的【弹起事件】写   
tm0.en=0
// 在定时器tm0的定时事件中写上想操作的长按事件代码
...    
  • 使用按钮实现双击功能

需配合定时器使用和数字控件使用

// 先创建一个定时器,定时200,en=0
// 创建一个数字控件用来调试
// 在按钮控件【按下事件】写
tm0.en=1
va0.val++
if(va0.val>=2)
{
  if(Main.b0.txt=="开始")
  {
    Main.b0.txt="停止"
  }else
  {
    Main.b0.txt="开始"
  }
}
// 在定时器事件写
tm0.en=0
va0.val=0
// 调试直到想要的效果然后把数字控件替换成变量控件    
  • 常用属性

bco :按钮没按下时背景色,sta为单色时才有这个属性 [TRUE]

bco2 :按钮按下时背景色 ,sta为单色时才有这个属性 [TRUE]

pco:按钮没按下时字体色 [TRUE]

pco2:按钮按下时字体色 [TRUE]

进度条控件

用于在串口屏上显示进度(例如开机进度)

给进度条控件赋值必须是整形(int),否则会出错

  • 搭配定时器控件实现进度条自动增长并在进度为100时跳转页面
// 新建一个定时器tm0,en属性设置为1,定时器的tim属性建议设置为200
// 在定时器里写
if(j0.val<100)
{
   j0.val++
}else
{
   page main
}
  • 使用定时器控件实现进度条和数值变量之间同步
// 创建一个变量控件
// 在按钮控件里【弹起事件】写
if(j0.val<100)
{
  va0.val+=10
}
// 在定时器里写,同步数值
j0.val=va0.val
  • 常用属性

sta :进度方式:0–横向;1–竖向

val :初始进度值 [TRUE]

dis :外形圆角半径百分比(0-100,0为直角) sta为单色时可用 [TRUE]

bco :进度条背景色 [TRUE]

pco :进度条前景色 [TRUE]

图片控件

图片的话类型只能是 JPGbmpPNG(其中T系列和K系列不支持PNG透明图层)

注意图片的分辨率,超出大小会导入失败(可使用笔记本自带画图工具修改分辨率),具体分辨率可以点击上面【设备】,查看当前型号那的分辨率,我的是 320x240

  • 导入图片

左下角 + 即可导入,注意分辨率不能太大,导入后可以修改分辨率,双击图片点击【编辑】即可

  • 设置为界面背景图片

点击页面空白处-设置页面控件的sta为图片,将页面控件的pic属性设置为你想要显示的背景图片

  • 显示自定义大小图片

点击左边栏的图片即可,然后填写图片ID到 pic

切图控件

作用是将全屏图片的一部分抠出来显示,请注意切图控件pic属性对应的一定要是全屏图片

先把页面背景换成全屏图片,然后点击左栏的切图控件,然后拉这个方框到你想切的最终位置即可,然后把页面背景改为单色,即可看到切图控件已经切好了

触摸热区控件

触摸热区控件可以理解为一个看不见的按钮控件

  • 实现一个隐藏按钮功能,当点击的次数足够多时,进入后台页面
// 下面例子是1s内触摸热区被触摸5次以上触发隐藏,1s后计数清0
// 首先在program.s中定义一个count变量
int count
// 新建一个触摸热区---在触摸热区控件中编写以下代码   
count++
if(count>5)
{
     //跳转到Display页面
     page Display
}
tm0.en=1    
// 新建一个定时器,en属性设置为1,定时器的tim属性建议设置为1000,在定时器中编写以下代码
count=0    

触摸捕捉控件

触摸捕捉控件可以获取到被点击控件的id

click指令触发控件时,不会导致触摸捕捉控件被触发,只有用手去点击屏幕时才会被捕捉

每个页面虽然能创建多个触摸捕捉控件,但只有1个会生效(id值最高且有事件代码的触摸捕捉控件会生效),因此请勿在同一个页面创建多个触摸捕捉控件

  • 触摸捕捉控件实现屏保功能
// 下面例子可以实现不触摸屏幕10s以上进入屏保界面,中途任意触摸都会触发触摸捕捉导致计数清0
// 需要搭配定时器控件和数字控件实现实现
// 新建一个触摸捕捉控件tc0和数字控件n0,定时器控件tm0,tm0的en设置为1,tim设置为1000
// 因为点击任何控件都会触发触摸捕捉控件,在触摸捕捉控件编写以下代码
n0.val=0
// 在定时器里写
n0.val++
if(n0.val > 10)
{
    //超过10秒,跳转到屏保页面
    page Display
}   

滑块控件

用于在串口屏上滑动调整数值,例如亮度,音量

音量属性是默认关机保存的,设置了,下次开机也是这个值

  • 滑块控件修改亮度

将滑块的minval设置为20,避免将dim设置为0后,结果重启后发现屏幕不亮,以为屏幕坏了

将亮度赋值给滑块控件

在滑块控件的滑动事件编写以下代码
dim=h0.val
在滑块控件的弹起事件编写以下代码    
dim=h0.val    
  • 滑块控件修改音量大小(仅x3、x5系列支持)

跟上面操作流程一样

  • 发送滑块的数据

在滑块控件的弹起事件编写即可

定时器控件

定时器控件用于定时执行某些代码,或者延时执行某些代码,当定时被使能时,定时器里的代码会定时执行。

每个页面的定时器数量不能超过12个

RTC:rtc0~rtc6分别代表年月日时分秒星期

定时器只能在当前页面运行,不可后台运行,如果想要定时器一直运行,每个页面都加个定时器

在定时器开启的情况下,对定时器的任意属性赋值(设置tim属性或者en属性),都会导致定时器重新计时

若不想定时器重新计时,请使用以下操作:

//判断定时器是否打开,如果没打开,则打开
if(tm0.en!=1)
{
    tm0.en=1
}
  • 定时器定时刷新RTC到数字控件上

定时器的tim设置为300(定时器每300ms执行一次定时事件中的代码),en设置为1(定时器开启)

  • 使用定时器配合图片控件循环播放图片
if(p0.pic<20)
{
     p0.pic++
}else
{
     p0.pic=10
}
  • RTC功能
// 开一个定时器,开一个变量控件,在Program.s里定义一个变量
int lastSecond=-1
// 在定时器里写    
if(lastSecond!=rtc5)
{
  lastSecond=rtc5
  va0.val++
  n0.val=va0.val/60
  n1.val=va0.val%60
}
// 开两个数字控件显示

变量控件

变量控件将sta设置为数值时,相当于一个看不见的数字控件,使用方法参考数字控件

变量控件将sta设置为字符串时,相当于一个看不见的文本控件,使用方法参考文本控件

双态按钮控件

此按钮类似于自锁开关,使用前需要提前导入字库

就是只有两种状态 OFFON,按下颜色也会变化

  • 实现按键锁定功能
// 按下锁屏图标后,禁用所有控件的触摸事件,只有再次触摸解锁图标解锁才可以触摸其他按键
if(bt0.val==1)
{
  bt0.txt="解锁"
  tsw 255,0   //失能所有控件
  tsw bt0,1  //使能btn_lock
}else
{
  bt0.txt="锁定"
  tsw 255,1   //使能所有控件
}
  • 常用属性

bco :按钮没按下时背景色,sta为单色时才有这个属性 [TRUE]

bco2 :按钮按下时背景色 ,sta为单色时才有这个属性 [TRUE]

val :当前状态(0或1),0为弹起,1为按下 [TRUE]

txt :字符内容(按钮上显示的文本)[TRUE]

复选框控件

也是只有0和1两种状态

// 初始复选框为0,则变成1表示被选中
if(c0.val==1)
{

}
  • 常用属性

bco :按钮没按下时背景色,sta为单色时才有这个属性 [TRUE]

pco :按钮没按下时字体色 [TRUE]

val :当前状态(0或1),即是否被选中 [TRUE]

单选框控件

一般要求互斥性,即所有单选框只能存在一个被选中

// 开一个触摸捕获,放在按下事件,单选框6个id必须要连续
if(tc0.val>=r0.id&&tc0.val<=r5.id)
{
   for(sys0=r0.id;sys0<=r5.id;sys0++)
   {
      if(sys0!=tc0.val)
      {
         b[sys0].val=0
      }
   }
}

二维码控件

用于在串口屏上显示动态的二维码,如果二维码是固定不变的,或者只有几个,则建议使用图片控件来代替

可以通过按键重新赋值txt

直接在txt里填写内容即可,可以是图片链接,文字,网址等等

  • 常用属性

bco :背景色 [TRUE]

pco :前景色 [TRUE]

txt :二维码中的字符内容

txt_maxl :字符最大长度(即分配内存空间:0-192)

状态开关控件

跟双态按钮差不多的(仅X系列支持)

在txt填写两种状态时的文字即可,也可以不填写,仅显示按钮

  • 常用属性

val :当前值 [X_TRUE]

bco :状态0背景色 [X_TRUE]

pco :状态0圆心色 [X_TRUE]

bco2 :状态1背景色 [X_TRUE]

pco2 :状态1圆心色 [X_TRUE]

pco1 :字体色 [X_TRUE]

font :字库 [X_TRUE]

下拉框控件

在串口屏上显示文本信息,包括数字,字母,符号,汉字和其他各国语言,使用前需要提前导入字库

  • 将下拉框置顶

由于下拉框是可展开的,因此在展开是可能会被其他控件挡住,此时就需要将下拉框置顶

//将下拉框cb0置于最顶层
setlayer cb0,255
  • 当点击某个下拉框时让其他下拉框缩回
//下拉框cb0的按下事件中写,其他的也是这样
cb1.down=0
cb2.down=0
  • 获取下拉框被当前被选中的值

直接获取cb0.txt即可

  • 常用属性

txt :字符内容(运行中会根据val值的变化自动改变)。可读,可通过上位机修改,可通过指令修改。

txt_maxl :字符最大长度(即分配内存空间)。GB2312下,ascii字符占用1个字节,一个汉字占2字节。UTF8下,ascii字符占用1个字节,一个汉字占3字节。可读,可通过上位机修改,不可通过指令修改。

up :字符框是否显示箭头:0-不显示;1-显示。可读,可通过上位机修改,可通过指令修改。

pco3 :字符框箭头颜色。可读,可通过上位机修改,可通过指令修改。

bco1 :单元格背景色。可读,可通过上位机修改,可通过指令修改。

pco1 :单元格字体色。可读,可通过上位机修改,可通过指令修改。

path :单元格内容集合(请使用多行输入,每行为一条单元格内容)。可读,可通过上位机修改,可通过指令修改。

path_m :单元格内容集合最大长度(即分配内存空间)。可读,可通过上位机修改,不可通过指令修改。

dir :单元格展开方向:0-上;1-下;2-左;3-右。可读,可通过上位机修改,可通过指令修改。

qty :最大展开单元格数量(1-254)。可读,可通过上位机修改,可通过指令修改。

vvs0 :展开单元格间距(最小0,最大255)。可读,可通过上位机修改,可通过指令修改。

val :当前选中单元格ID(运行中每次改变会将相应的内容同步到txt属性)。可读,可通过上位机修改,可通过指令修改。

选择文本控件

用于在串口屏上展示语言选择,设置日期等,使用前需要提前导入字库

  • 获取数值或者txt
select0.val=select0.val
t0.txt=select0.txt
//如果需要显示在另一个文本控件需要把val转字符串    
  • 常用属性

dis :选中项加横线:0-否;1-是。可读,可通过上位机修改,可通过指令修改。

pco1 :横线颜色。可读,可通过上位机修改,可通过指令修改。

txt :当前val值指向的文本(自动随val值改变,只可获取不可设置)。

val :当前选择项ID(每改变一次,txt值会相应变化)。可读,可通过上位机修改,可通过指令修改。

ch :滑动惯性力度(0-32,0为无惯性)。可读,可通过上位机修改,可通过指令修改。

path :滑动选择项集合(每行为一项)。可读,可通过上位机修改,可通过指令修改。

path_m :滑动选择项集合最大长度(即分配内存空间)。可读,可通过上位机修改,不可通过指令修改。

滑动文本控件

用于在串口屏上显示文本信息,包括数字,字母,符号,汉字和其他各国语言,使用前需要提前导入字库

数据记录控件

数据记录控件有4个方法,分别为insert,delete,up,clear

insert:追加一条记录(成功返回1,失败返回0)

delete:删除数据(成功返回1,失败返回0)

up:修改一条记录(成功返回1,失败返回0)

clear:清除所有数据记录

  • 插入/删除/清空/修改

插入需要先创建一个文本控件用来组合你的数据,需要注意这个组合的控件的txt_maxl设置得大一点

注意如果是数字的话需要转为字符串才能组合

// 按钮1--增加数据(增加是从上往下)
t9.txt=t6.txt+"^"+t7.txt+"^"+t8.txt
data0.insert(t9.txt)
// 按钮2--删除数据第1行(删除是从下往上)    
data0.delete(data0.val,1)   
// 按钮3--清空    
data0.clear()
// 按钮4--修改某行数据
t9.txt=t6.txt+"^"+t7.txt+"^"+t8.txt
//更新被选中的数据
data0.up(t9.txt,data0.val)    
//数据记录控件修改某一行的背景色和字体色
//将背景颜色设置为1024(绿色),将字体颜色设置为63488(红色),添加内容为1 2 3
data0.insert("<font b=1024,p=63488>1^2^3")    

  • 数据记录控件由3列修改为4列或更多列

需要修改数据记录控件的dez属性,只能在上位机中修改,不能通过指令修改,修改后数据记录控件会显示黑屏和报错信息

此时你需要到SD卡或者虚拟SD卡的目录下删除原有的.data文件,重新调试运行后,控件会自动生成新的.data文件

修改了length属性或者maxval属性导致的黑屏也是同样的操作进行解决

  • 隐藏数据记录控件表头

删除数据记录控件的dir属性里的值即可

  • 出现控件黑屏

原因:因为记录的字段和所指定的.data文件中的字段数量不符导致的

解决方法:

  1. 你改动了数据记录控件的 length、maxval、dez属性,将其恢复即可

  2. 把存储卡或者虚拟sd卡文件夹中的原本绑定的对应的的 .data 文件删除

  3. 修改数据记录控件的path属性,将其指向一个不存在的文件(将会自动创建),例如原本是 sd0/1.data,改为 sd0/123.data

  • 提示file lost

没插micro sd(tf)卡

卡的格式不对(非fat32格式)

尝试换一张卡

  • 多个数据记录打开同一个路径下文件导致黑屏

这是因为文件已经被上一个数据记录打开导致新的数据记录无法打开文件

解决方法是:即每次跳转时,将当前页面的数据记录控件指向一个临时的文件,从而解除目标文件的占用状态,再将即将跳转到的页面的数据记录控件指向目标文件

// 示例
// 当需要从page0跳转到page1时,在对应的控件中写下
page0.data0.path="sd0/tmp0.data"
page1.data0.path="sd0/1.data"
page page1

动画控件

动画控件用于显示gif动画,仅X3/X5系列支持

使用动画前,需要先用动画制作工具将gif图片转换为特定格式才能导入串口屏上位机中

  • 导入动画

点击左下角动画添加即可,导入 一张gif动图,并等待动画转换完成

  • 在动画播放完成后跳转页面

在动画控件gm0的播放完成事件编写以下代码

1 page main  //跳转到main页面

视频控件

视频控件用于播放视频,仅X5系列支持

当需要在上位机的模拟器里调试时,请将资源复制到虚拟SD卡文件夹中,需要在串口屏实物上调试时,请将资源复制到SD卡里,并插到串口屏上(SD卡不能超过32GB)

视频的后缀名要 .video,可以点击上面的工具栏进行视频转换把MP4转换为需要的格式

曲线波形控件

每个页面不能超过5个曲线波形控件

无论是使用add还是会用addt,添加的值范围为0-255,需要注意的是,最大值不要超过控件高度,即这个值必须小于255的同时也要小于控件高度

从曲线控件的底部开始算,在dis(缩放比例)为默认100%的情况下,添加的值是多少,就代表距离曲线控件底部的高度

曲线控件只能添加0-255之间的数据,因此不能添加负数

但是曲线控件只是表示一个趋势

用户不会在意你添加进去的值是多少,只会去看坐标

直接用控件做的话比较丑,可以用ps做在背景图片上,甚至刻度也可以不是线性的

指令集

page-页面跳转

page指令后面的代码将不会被执行,所以千万不要把代码写在page指令后面

page 页面ID或者页面名称
    
// 串口发送示例
printf("page main\xff\xff\xff");    

prints从串口打印变量/常量

当att为数值类型时,length最大为4,length为0和length为4的效果是完全相同的

当att为字符串类型时,length为0会发出整个完整的字符串,当length小于字符串长度时,发出指定的字节数(不是字符数),在99%的情况下,当att为字符串时,length都是为0。

从串口打印一个Hex请使用printh

使用prints指令获取数据的时候,设备仅仅只发送数据内容,没有起始标示符,也没有结束符

prints指令可以配合printh指令在前面加一段自定义标示来告诉单片机此变量是属于哪个控件的

prints att,lenth

att:变量名称

lenth:发送长度(0为自动长度)
    
// 示例
prints t0.txt,0    

printh从串口打印16进制

需要发送多个固定的16进制数据时,建议用一个printh发送即可,不建议使用多个printh进行发送

使用printh指令发送数据的时候,设备仅仅只发送指定的字符

参数中每组字符间必须有且只能有一个空格隔开,16进制的字符串表达式大小写均支持

printh只能发送固定格式的16进制数据,不能发送变量,变量需要使用prints指令

printh hex

hex:需要发送的固定的16进制,hex可以是多个16进制,必须写成偶数个的形式

正确:printh 0d 0a

错误:printh d a

click激活控件的按下/弹起事件

激活当前页面控件的按下或弹起事件,不支持跨页面激活控件的按下或弹起事件

控件被隐藏后,无法使用手指去触发控件,但是仍然可以通过click指令触发

由于串口屏上没有函数的概念,因此有大量重复的代码需要调用时,可以将代码写在触摸热区内,然后用click指令去触发

控件的按下/弹起事件在屏幕上触摸的时候会自动激活,如果在没有触摸的情况下想要手动激活,就使用click指令即可

请勿click控件自身,否则会因为递归过深导致问题

click指令触发控件时,不会导致触摸捕捉控件被触发

click obj,event

obj:控件ID或控件名称

event:事件序号:0为弹起,1为按下

vis-隐藏/显示控件

隐藏/显示当前页面控件,不支持跨页面显示或隐藏控件

控件被隐藏后,无法使用手指去触发控件,但是仍然可以通过click指令触发。

可以利用全局变量来跨页面隐藏/显示控件

vis obj,state

obj:控件名称或控件ID

state:状态(0或1)
  • 隐藏/显示所有控件
//隐藏所有控件
vis 255,0
//显示所有控件
vis 255,1    

tsw-控件触摸使能

使能或失能当前页面控件,不支持跨页面使能或失能控件

可以利用全局变量来跨页面使能或失能控件

使用了tsw命令将控件失能了之后,控件就无法触摸了,但是可以使用click命令触发

tsw obj,state

obj:控件名称或控件ID

state:状态(0或1)
  • 使能/失能所有控件
//让所有控件触摸失效
tsw 255,0
//让所有控件触摸有效
tsw 255,1    

randset-随机数范围设置

使用随机数之前需要先使用randset指令设定一次随机数产生范围,如果不设置,默认是最小0,最大2147483647。设置完范围以后,每读取一次系统变量rand将会得到一个随机数

使用randset指令每设定一次范围,将一直有效,直到重新上电或者设备复位才会恢复默认

随机数设定范围的数据类型为int类型(即:最小-2147483648,最大2147483647)

randset minval,maxval

minval:最小值

maxval:最大值
// 示例
//设置当前随机数产生范围为最小1,最大100
randset 1,100
n0.val=rand

covx-变量类型转换

如果目标变量和源变量类型相同,转换失败

covx att1,att2,lenth,format

att1:源变量

att2:目标变量

lenth:字符串的长度(0为自动长度,非0为固定长度)

format:申明数值类型(0-数字;1-货币;2-Hex)

strlen-字符串变量字符长度测试

strlen测试的是以字符为单位的长度,而btlen测试的是以字节为单位的长度,比如一个汉字用btlen测试出来的长度是2字节,用strlen测试出来的长度是1字符

被测试的变量必须是字符串类型,写入的变量必须是数值类型,否则会报错

strlen att0,att1

att0:需要测试的字符串变量

att1:把测试结果赋值给此变量
// 示例
//把字符串变量t0.txt的实际字符长度赋值给n0.val
strlen t0.txt,n0.val

btlen-字符串变量字节长度测试

btlen测试的是以字节为单位的长度,而strlen测试的是以字符为单位的长度,比如在GB2312下,一个汉字用btlen测试出来的长度是2字节,用strlen测试出来的长度是1字符。

汉字在不同编码下占用的字节数量不一样,例如在gb2312编码下,1个汉字占用2字节,但是在utf8编码下,1个汉字占用3字节。

被测试的变量必须是字符串类型,写入测试结果的变量必须是数值类型,否则会报错。

btlen att0,att1

att0:需要被测试的字符串变量

att1:把测试结果赋值给此变量

substr-字符串截取

substr att0,att1,star,lenth

att0:源变量(必须是字符串变量)

att1:目标变量(必须是字符串变量)

star:在源变量中的字符起始位置

lenth:截取字符串长度
// 示例
//从t0.txt中的0位置开始截取2个字符赋值给t1.txt
substr t0.txt,t1.txt,0,2

spstr-字符串分割

spstr src,dec,key,index

src:源变量(必须是字符串变量)

dec:目标变量(必须是字符串变量)

key:分隔符字符串(必须是字符串变量)

index:取第几份(在src字符串中用key字符串做分割后,取第index份字符内容赋值给dec变量)
// 示例
// 把t6字符串"123.567.891" 以.分割然后取下标是0那份赋值给t7.txt
spstr t6.txt,t7.txt,".",0

touch_j-触摸校准

仅电阻屏实物支持该指令

不支持在模拟器中使用

不支持在电容屏和无触摸款串口屏中使用

没啥用这功能

add-往曲线控件添加数据

往当前页面的曲线控件添加数据,不支持跨页面添加曲线控件数据

使用s0.id的好处:避免因为置顶/置底或者删除控件导致id改变从而出错

当曲线波形控件的ch属性为1(通道数量为1)时,只有一个通道0可以使用,而不是通道1

add objid,ch,val

objid:曲线控件ID序号(此处必须是ID号,不支持使用控件名称),推荐使用s0.id这种形式

ch:曲线控件通道号(范围:0-3)

val:数据 (最小0,最大255,还取决于曲线控件的实际高度,例如曲线控件实际高200像素
,当你添加的值超过200的部分就显示不出来。超过255软件会自动取低8位,即对255取余)
//往s0曲线控件的0通道添加数据30
add s0.id,0,30
//往s0曲线控件的0通道随机添加数据
add s0.id,0,rand    

addt-曲线数据透传指令

往当前页面的曲线控件透传数据,不支持跨页面透传曲线控件数据

曲线数据只支持8位数据,最小0,最大255。单次透传数据量最大1024字节

发完透传指令后,用户需要等待设备响应才能开始透传数据,设备收到透传指令后,准备透传初始化数据大概需要 5ms左右(如果在透传指令执行前串口缓冲区还有很多别的指令,那时间会更长,且t系列和k系列会更久),设备透传初始化准备好以后会发送一个透传就绪的数据给用户 (0XFE+结束符),表示设备已经准备好,此时可以开始发送透传数据。透传数据为纯16进制数据,不再使用字符串,也不再需要结束符,设备收完指定的数据量以后,才会恢复指令接收状态。否则一直处于数据透传状态,透传数据完成以后,设备会发送结束标记给用户 (0XFD+结束符)

在指定的透传数量传输完成以前,曲线不会刷新,透传完毕之后会立即自动刷新

addt  objid,ch,qyt

objid: 曲线控件ID序号(此处必须是ID号,不支持使用控件名称)

ch:曲线控件中的通道号

qyt:本次透传数据的点数量
//s0曲线控件的通道0进入数据透传模式,透传点数为100点
//推荐使用这种方式,不会因为s0的id号改变而报错
addt s0.id,0,100
//向曲线s0的通道0透传100个数据,addt指令不支持跨页面
printf("addt s0.id,0,100\xff\xff\xff");

//等待适量时间
delay_ms(100);

for(int i =0;i<100;i++)
{
    printf("%c",(int)(rand() % 256));
}

//确保透传结束,以免影响下一条指令
printf("\x01\xff\xff\xff");

cle-清除曲线控件中的数据

用于清除当前页面曲线控件的数据

只能清除当前页面曲线控件的数据/不支持跨页面清除曲线控件的数据/不能清除其他页面曲线控件的数据

cle objid,ch

objid:曲线控件ID序号(此处必须是ID号,不支持使用控件名称)

ch:曲线控件通道号(255表示所有通道)
//清除s0曲线控件的0通道数据
cle s0.id,0

rest-复位串口屏

禁止在program.s以及program.s里跳转的页面初始化事件里写rest指令,会导致屏幕不断重启!!!

如果你已经这么做了,请用sd卡烧写一个正常的工程进入即可

rest

wepo-写入数据掉电存储

仅k0系列/x系列支持

使用掉电存储功能前需要先对存储空间初始化,否则未初始化的掉电存储空间里面有什么数据是不确定的,可能会导致程序运行出错,例如会导致模拟器中的效果与串口屏实物的效果不一致

存储空间的读写范围是0-1023,当读写的是val属性时,最后一个读写的位置是1020,因为当读写1020时,其读写范围是1020-1023

掉电存储空间写入寿命有限,请勿频繁擦写,只建议存储低频次修改的数据,例如用户名,密码等,写入消耗掉电存储空间寿命,读取不消耗掉电存储空间寿命

写入数据后不能马上从原有位置读取数据,需等待几秒后再读取

写入数据后不允许立刻断电,需等待几秒后再断电

  1. 写入内容为变量字符串的时候,在储存区中的占用空间为 此变量的最大字符数+1;写入内容为常量字符串的时候,在储存区中的占用空间为此常量字符串的实际字符数+1,多出来的一个字节是为了存储字符串 结束符0x00

  2. 写入内容为变量数值或常量数值的时候,在储存区中的占用空间统一为 4字节

  3. 使用用户存储区读写操作过程中请切记规划好数据区位置,以免位置交错引起数据覆盖错乱。

  4. 用户存储区大小为1k,位置为0-1023

// 以下代码写在program.s里
int restFlag     //自定义的变量
//从掉电存储区最后一个位置读取数据出来
repo restFlag,1020
//如果不是指定的数据,说明掉电存储区未初始化
if(restFlag!=0x12345678)
{
  //用for循环初始化掉电存储空间
  for(sys0=0;sys0<1020;sys0+=4)
  {
    wepo 0,sys0
  }
  wepo 0x12345678,1020
}
wepo att,add

att:变量/常量

add: 用户存储区位置(从0开始)
//将t0.txt的内容写入用户存储区的第10位置,在储存区中的占用空间为t0.txt的最大设置值+1,即t0的txt_maxl属性表示的大小+1,例如控件的txt_maxl等于10,则使用wepo写入时总共需要占用11个字节,占用的地址为10-20
wepo t0.txt,10

repo-读取数据掉电存储

读取到txt属性时遇到字符串结束符0才停止,因此之前通过wepo或者wept写入字符串类型的变量时,一定要留有空间存储0,否则读取时就会出错

读入内容为变量字符串的时候,在储存区中的读取数据量为此变量的最大字符数+1。

读入内容为变量数值时候,在储存区中的读取数据量统一为4字节。

使用用户存储区读写操作过程中请切记规划好数据区位置,以免位置交错引起数据覆盖错乱。

repo att,add

att:目标变量

add: 用户存储区位置(从0开始)
//从用户存储区的10位置读数据到t0.txt变量中,直到遇到字符串结束符\0才停止
repo t0.txt,10

系统变量

program.s中创建的变量均为全局变量

sys0-sys2是生成工程时默认可以使用变量

dp-当前页面ID

// 等效于page 1
dp=1

volume-系统音量

仅x5 x3系列才支持,音量设置范围为0-100,每次设置会自动保存,断电后再开机依然有效

dims-上电默认背光亮度值

dimS范围:0-100

dims掉电后保存,下次上电后继续有效

dim和dims区别:

dim:不会自动关机保存

dims:会自动保存,下次开机时还是此亮度值

dim-当前背光亮度值

亮度范围:0-100

bauds-上电默认波特率值(掉电后保存,重启后继续有效)

baud-当前波特率值(重启后丢失)

ussp-无串口数据自动睡眠时间

单位:秒,最小3,最大65535,上电默认0(0为关闭串口数据超时自动睡眠)

// 示例
//30秒无串口数据自动进入睡眠模式
ussp=30

thsp-无触摸操作自动睡眠时间

(单位:秒,最小3,最大65535,上电默认0[0为关闭触摸超时自动睡眠])

// 示例
//30秒无触摸操作自动进入睡眠模式
thsp=30

thup-睡眠模式下触摸自动唤醒开关

上电默认0,不管thup为0还是1,睡眠模式下有触摸操作的时候设备均会发送触摸坐标到串口

usup-睡眠模式下串口数据自动唤醒开关

此指令仅在淘晶字符串指令模式下起作用,在主动解析下串口屏无法被唤醒

如果设置为1,串口收到任何数据(需要加上结束符16进制3个ff)都会立刻自动唤醒

wup-睡眠唤醒后刷新页面设置

设备已经在睡眠状态下,也可以执行串口传过来的wup=X赋值

sleep-睡眠

用于配置串口屏睡眠、休眠

进入睡眠状态下,亮度为0,可以执行如下指令:get,prints, printh。也可以执行 sleep=0,wup=X 的赋值语句,并且支持上位软件联机,其他指令不会执行

bkcmd-串口指令执行状态数据返回

(上电默认为2)

0:不返回结果
1:只返回成功的结果
2:只返回失败的结果
3:成功或者失败都返回结果    

delay-延时

串口屏是单线程的,调用delay会直接死等,时间过长就会造成很明显的卡顿现象,延时超过50ms时不建议使用delay,超过50ms时建议用定时器控件来延时

//让设备停顿10ms
delay=10

rand-随机数

使用随机数之前需要先使用randset指令设定一次随机数产生范围,如果不设置,默认是最小0,最大2147483647。设置完范围以后,每读取一次系统变量rand将会得到一个随机数

//设置随机数取值范围0-100
 randset 0,100

 //把一个随机数赋值给n0.val变量
 n0.val=rand

crcval-crc校验结果(只可读取不可设置,使用前请先用crcrest指令复位初始值)

先使用crcrest复位CRC值,复位之后,可使用crcputs或crcputh或crcputu校验指定数据,检验完毕读取系统变量crcval获得校验结果)

完整的CRC校验实例代码请参考: 程序中使用CRC校验数据

//复位CRC初始值为0xffff,以便后续检验数据
crcrest 1,0xffff
//CRC校验hex:0x03,0x25
crcputh 03 25
//CRC校验hex:0x0d,0x0a
crcputh 0d 0a
//发送校验值
prints crcval,2

rtc0~rtc6-RTC时钟变量

//获取年
 n0.val=rtc0

 //获取月
 n1.val=rtc1

 //获取日
 n2.val=rtc2

 //获取小时
 n3.val=rtc3

 //获取分钟
 n4.val=rtc4

 //获取秒钟
 n5.val=rtc5
 //获取星期
 if(rtc6==0)
 {
   t0.txt="星期日"
 }else if(rtc6==1)
 {
   t0.txt="星期一"
 }else if(rtc6==2)
 {
   t0.txt="星期二"
 }else if(rtc6==3)
 {
   t0.txt="星期三"
 }else if(rtc6==4)
 {
   t0.txt="星期四"
 }else if(rtc6==5)
 {
   t0.txt="星期五"
 }else if(rtc6==6)
 {
   t0.txt="星期六"
 }

注意/问题

以上内容中有 [TRUE] 表示可通过指令修改,[X_TRUE] 表示只有X系列才支持且可通过指令修改,[X] 表示只有X系列才支持,但不能指令修改

喇叭型号与接法参考:http://wiki.tjc1688.com/QA/QA37.html#id4

sd卡旁边j5接口是否接有喇叭

外部图片控件用于显示SD卡中的图片,仅X系列支持,需要插入SD卡

hmi_color示例

代号 10进制 所表示的颜色
RED 63488 红色
BLUE 31 蓝色
GRAY 33840 灰色
BLACK 0 黑色
WHITE 65535 白色
GREEN 2016 绿色
BROWN 48192 橙色
YELLOW 65504 黄色

常见错误返回或者其他数据返回

  • 单片机上电后为什么要先发一次 0x00 0xff 0xff 0xff给屏幕?

单片机初始化后,先给一定时间的延时给屏幕初始化,具体延时时间可以参考上面一条。

延时之后,第一次发指令前先发一次0x00 0xff 0xff 0xff,是因为上电过程中有可能串口引脚上产生了杂波导致屏幕已经收到一个或

者多个错误数据了,所以先发一次0x00 0xff 0xff 0xff来结束当前指令,后面就可以正常操作了。

printf("\x00\xff\xff\xff");
  • 串口屏如何制作弹窗

参考:http://wiki.tjc1688.com/QA/QA73.html

  • 屏保制作

参考:http://wiki.tjc1688.com/QA/QA82.html

需要注意的是在单片机里发送指令到串口屏要有\0结束符不然没效果

附1- 项目1

  1. 选择型号,方向选择 横屏90
  2. 修改主页面名为 Main,新建界面 DisplayStep_Motor

Main页面

t1和t2按钮勾【弹起事件】里的 发送键值

然后在【弹起事件】里写跳转页面

Display页面

b0按钮勾【弹起事件】里的 发送键值

然后在【弹起事件】里写跳转页面

Step_Motor页面

bt0,bt1,b0,b1按钮勾【弹起事件】里的 发送键值

/*******************bt0【弹起事件】**************************/
if(bt0.val==0)
{
  bt0.txt="关"
  n0.val=0
}else
{
  bt0.txt="开"
  n0.val=3
}
/*******************bt1【弹起事件】**************************/
if(bt1.val==0)
{
  bt1.txt="正"
}else
{
  bt1.txt="反"
}
/*******************b0【弹起事件】**************************/
if(n1.val<9)
{
  n1.val+=1
}
/*******************b1【弹起事件】**************************/
if(n1.val>1)
{
  n1.val-=1
}

程序编写

MX的话打开串口1DMA接收,勾空闲中断即可,不需要DMA中断

HMI.h
#ifndef __HMI_H
#define __HMI_H
#include "AllHead.h"

#define huart_HMI	huart1
#define FALSE	0
#define TRUE	1
//显示屏页面
typedef enum
{
	Page_Main = (uint8_t)0x00,
	Page_Display = (uint8_t)0x01,
	Page_Step_Motor = (uint8_t)0x02,
}HMI_Page_t;

// 定义结构体类型
typedef struct
{
	HMI_Page_t Page;	// 显示屏页面
	uint8_t* pucRec_Buffer;	// 接收缓存
	uint8_t Page_Step_Motor_KEY_Flag;	// 按键标志位:用于区分按键中断与显示屏键值信息
	void (*Init)(void);	// HMI初始化
	void (*SendString)(uint8_t*);	// 发送字符串给HMI
	void (*Protocol)(void);	// 接口协议
}HMI_t;

extern HMI_t HMI;

#endif
HMI.c
#include "AllHead.h"

#define HMI_Rec_Buffer_LENGTH	(uint8_t)20
// 键值信息是7个字节
#define Protocol_Data_LEN	(uint8_t)7
static uint8_t ucHMI_Rec_Buffer[HMI_Rec_Buffer_LENGTH] = {0x00};
static uint8_t ucHMI_EndData[3] = {0xFF,0xFF,0xFF};

void HAL_UART_IdleCallback(UART_HandleTypeDef *huart);static void HMI_Init(void);
static void HMI_SendString(uint8_t*);
static void HMI_Protocol(void);
static void HMI_SendEndData(void);

HMI_t HMI = 
{
	Page_Main,
	ucHMI_Rec_Buffer,
	FALSE,
	HMI_Init,
	HMI_SendString,
	HMI_Protocol
};

static void HMI_Init(void)
{
	HMI_SendEndData();
	// 显示屏默认显示主页面
	HMI.SendString((uint8_t*)"page 0");
}

static void HMI_SendString(uint8_t* pucStr)
{
	HAL_UART_Transmit(&huart_HMI,pucStr,strlen((const char*)pucStr),100);
	HMI_SendEndData();
}

static void HMI_Protocol(void)
{
	uint8_t Temp_Array[7] = {0x00};
	uint8_t i = 0,Index = 0;
	
	// 串口停止DMA接收
	HAL_UART_DMAStop(&huart_HMI);
	// 读取HMI缓存数据,共7字节起始值为0x65
	for(i = 0;i < HMI_Rec_Buffer_LENGTH;i++)
	{
		//检测键值起始数据0x65
		if(0 == Index)
		{
			if(*(HMI.pucRec_Buffer + i) != 0x65)
			{
				continue;
			}
		}
		Temp_Array[Index] = *(HMI.pucRec_Buffer + i);
		// 已读取7字节
		if(Protocol_Data_LEN == Index)
		{
			break;
		}
		Index++;
	}
	// 串口开启DMA接收
	HAL_UART_Receive_DMA(&huart1,HMI.pucRec_Buffer,(uint16_t)20);
	// 处理数据
	if(Protocol_Data_LEN == Index)
	{
		// 主页面的键值信息
		if(0x00 == Temp_Array[1])
		{
			// 控件t1弹起事件--数码管
			if((0x02 == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				// 切换到数码管页面
				HMI.Page = Page_Display;				
				//操作...
			}
			// 控件t2弹起事件--步进电机
			if((0x03 == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				// 切换到电机页面
				HMI.Page = Page_Step_Motor;				
				//操作...显示电机圈数
				//显示电机速度
			}			
		}
		// 数码管页面的键值信息
		if(0x01 == Temp_Array[1])
		{
			// 控件b0弹起事件--返回
			if((0x08 == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				// 切换到主页面
				HMI.Page = Page_Main;
				//操作...
			}
		}
		// 步进电机页面的键值信息
		if(0x02 == Temp_Array[1])
		{
			// 控件b2弹起事件--返回
			if((0x0B == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				// 切换到主页面
				HMI.Page = Page_Main;
				//操作...
				//关闭步进电机
			}
			// 控件bt0弹起事件--开关
			if((0x01 == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				HMI.Page_Step_Motor_KEY_Flag = TRUE;
				// 调用按键1按下函数(里面需要有下面这段,其他类似)
				if(FALSE == HMI.Page_Step_Motor_KEY_Flag)
				{
					HMI.SendString((uint8_t*)"click bt0,1");
					HMI.SendString((uint8_t*)"click bt0,0");
				}
				HMI.Page_Step_Motor_KEY_Flag = FALSE;
			}
			// 控件bt1弹起事件--正反
			if((0x02 == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				HMI.Page_Step_Motor_KEY_Flag = TRUE;
				// 调用按键2按下函数(这里面会判断Page_Step_Motor_KEY_Flag是否为false,是就表示是单片机触发的不是触摸导致的,不是表示触摸了串口屏)
				if(FALSE == HMI.Page_Step_Motor_KEY_Flag)
				{
					HMI.SendString((uint8_t*)"click bt1,1");
					HMI.SendString((uint8_t*)"click bt1,0");
				}				
				HMI.Page_Step_Motor_KEY_Flag = FALSE;
			}
			// 控件b0弹起事件--加速
			if((0x03 == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				HMI.Page_Step_Motor_KEY_Flag = TRUE;
				// 调用按键3按下函数
				if(FALSE == HMI.Page_Step_Motor_KEY_Flag)
				{
					HMI.SendString((uint8_t*)"click b0,1");
					HMI.SendString((uint8_t*)"click b0,0");
				}				
				HMI.Page_Step_Motor_KEY_Flag = FALSE;
			}
			// 控件b1弹起事件--减速
			if((0x04 == Temp_Array[2]) && (0x00 == Temp_Array[3]))
			{
				HMI.Page_Step_Motor_KEY_Flag = TRUE;
				// 调用按键4按下函数
				if(FALSE == HMI.Page_Step_Motor_KEY_Flag)
				{
					HMI.SendString((uint8_t*)"click b1,1");
					HMI.SendString((uint8_t*)"click b1,0");
				}				
				HMI.Page_Step_Motor_KEY_Flag = FALSE;
			}			
		}		
		
	}
}

static void HMI_SendEndData(void)
{
	//连续发送3个0xFF
	HAL_UART_Transmit(&huart_HMI,ucHMI_EndData,(uint8_t)3,0x0A);
}

// 自己写的空闲中断回调函数
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == huart1.Instance)
	{
		// 解析协议
		HMI.Protocol();
	}
}

void USART1_IRQHandler(void)
{
    if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != 0x00u)
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        HAL_UART_IdleCallback(&huart1);
    }

    HAL_UART_IRQHandler(&huart1);
}
main.c
static void Fun_Page_Main(void);
static void Fun_Page_Display(void);
static void Fun_Page_Step_Motor(void);
void Run(void);
void vHardware_Init(void);

void vHardware_Init(void)
{
	HAL_TIM_Base_Start_IT(&htim4);
	// 使能HMI串口空闲中断
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
	// 串口1开启DMA接收
	HAL_UART_Receive_DMA(&huart1,HMI.pucRec_Buffer,(uint16_t)20);
	// HMI屏幕初始化
	HMI.Init();
}

void Run(void)
{
	// 根据HMI串口屏的页面执行相应功能
	switch(HMI.Page)
	{
		case Page_Main:
		{
			Fun_Page_Main();
			break;
		}
		case Page_Display:
		{
			Fun_Page_Display();
			break;
		}
		case Page_Step_Motor:
		{
			Fun_Page_Step_Motor();
			break;
		}
		default:
		{
			Fun_Page_Main();
			break;
		}			
	}
}

//主页面显示
static void Fun_Page_Main(void)
{
	
}
// 数码管显示页面
static void Fun_Page_Display(void)
{
	static uint32_t Cnt = 0;
	uint8_t i = 0;
	// 这里 '\0' 不能漏!!!
	uint8_t Disp_Str[9] = {'n','0','.','v','a','l','=','0','\0'};
	uint8_t Cnt_Arr[6] = {0x00};
	Cnt_Arr[0] = Cnt % 10;	// 个位
	Cnt_Arr[1] = Cnt / 10 % 10;	// 十位
	Cnt_Arr[2] = Cnt / 100 % 10;	// 百位
	Cnt_Arr[3] = Cnt / 1000 % 10;	// 千位
	Cnt_Arr[4] = Cnt / 10000 % 10;	// 万位
	Cnt_Arr[5] = Cnt / 100000;	// 十万位
	//数码管显示计数值...省略
	
	//显示完把数据传给HMI显示屏(因为是显示ASCII所以需要+'1',文本控件名称要连续0~5摆放)
	for(i = 0;i < 6;i++)
	{
		Disp_Str[1] = i + '0';
		Disp_Str[7] = Cnt_Arr[i] + '0';
		HMI.SendString(Disp_Str);
	}
	//更新计数值
	if(++Cnt > 999999)
	{
		Cnt = 0;
	}
	//测试
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
	HAL_Delay(50);
}

static void Fun_Page_Step_Motor(void)
{
	// 不需要了已经在按键里高了
	//测试
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
}

int main(void)
{
    vHardware_Init();
    while(1)
    {
        Run();
    }
}

实验现象

数字正常,灯正常

TFT

ILI9341介绍

  • 硬件连接

STM32管脚 LCD管脚 功能
PD7 LCD_CS PD7/FSMC_NE1/FSMC_NCE2/USART2_CK
PF0 LCD_RS PF0/FSMC_A0
PD14 DB0 FSMC_D0
PD15 DB1 FSMC_D1
PD0 DB2 FSMC_D2
PD1 DB3 FSMC_D3
PE7 DB4 FSMC_D4
PE8 DB5 FSMC_D5
PE9 DB6 FSMC_D6
PE10 DB7 FSMC_D7
PE11 DB8 FSMC_D8
PE12 DB9 FSMC_D9
PE13 DB10 FSMC_D10
PE14 DB11 FSMC_D11
PE15 DB12 FSMC_D12
PD8 DB13 FSMC_D13
PD9 DB14 FSMC_D14
PD10 DB15 FSMC_D15
PG2 LCD_BL(背光) 推挽输出,默认低电平
  • LCD控制器

LCD控制器的作用:驱动TFT-LCD显示器,将接收到的信号转换成TFT-LCD屏幕需要的各种信号,一般集成在TFT-LCD显示模组上。我们单片机通信的话跟这个控制器通信就行了

ILI9341的8080接口:

RESX:复位信号线

CSX:片选信号线

D/CX:数据与命令信号线

WEX:写使能信号线

RDX:读使能信号线

DB[17:0]:数据总线,支持8/9/16/18位

这个GRAM的话是缓冲区,里面存储了屏幕所有像素的数据,一般我们去读它就可以读屏幕数据了,写也是

  • FSMC控制器
  • 通过FSMC控制TFT-LCD控制器

FSMC的SRAM信号与8080信号线匹配:

① 片选信号 : NEx -> CSX

② 写使能 : WRX -> NWR

③ 读使能 : RDX -> NOE

④ 数据信号 : D[15:0] -> D[15:0]

⑤ 数据命令选择 : D/CX -> A[25:0](任意一根地址线)

说明:前四种信号线完全匹配,仅仅8080接口的数据命令选择线与FSMC的地址信号线有区别。为了模拟出8080时序,可以将FSMC的任何一根地址线连接8080接口的D/CX。比如接A0,当FSMC控制器写地址0的时候,那么A0为0,此时对8080接口来说是写命令;当FSMC控制器写地址1的时候,那么A0为1,此时对8080接口来说是写数据,这样就区分开数据与命令了

  • FSMC–MX配置

这次我们选择块1里的LCD1(2/3/4也可以的)

PG2(背光引脚)设置为 推挽输出,默认低电平(屏幕默认不亮)

  • 注意点

下面代码的地址由来

#define	FSMC_LCD_CMD_ADDR		((uint32_t)0x60000000)  //FSMC_Bank1_NORSRAM1用于LCD命令操作的地址
#define	FSMC_LCD_DATA_ADDR  ((uint32_t)0x60000002)  //FSMC_Bank1_NORSRAM1用于LCD数据操作的地址  

//LCD读写函数宏定义
//先将这个命令地址强制类型转换为一个uint16_t类型的指针,然后解引用(用于读取或者写入指针指向的内存地址中存储的值),再把uint16_t类型的数据赋值给它
#define	LCD_Write_CMD(CMD) 	  *((__IO uint16_t *)FSMC_LCD_CMD_ADDR)  = (uint16_t)CMD
#define	LCD_Write_DATA(DATA)  *((__IO uint16_t *)FSMC_LCD_DATA_ADDR) = (uint16_t)DATA  
#define	LCD_Read_DATA()       *((__IO uint16_t *)FSMC_LCD_DATA_ADDR)

NOR/PSRAM存储区地址(看手册FSMC图):
64MB:FSMC_Bank1_NORSRAM1: 0x6000 0000 ~ 0x63FF FFFF
64MB:FSMC_Bank1_NORSRAM2: 0x6400 0000 ~ 0x67FF FFFF
64MB:FSMC_Bank1_NORSRAM3: 0x6800 0000 ~ 0x6BFF FFFF
64MB:FSMC_Bank1_NORSRAM4: 0x6C00 0000 ~ 0x6FFF FFFF

选择BANK1-BORSRAM1 连接 TFT,地址范围为 0x6000 0000 ~ 0x63FF FFFF
这里示例程序是选择 FSMC_A0 接LCD的 D/CX(命令/数据选择)脚
命令地址 = 0x6000 0000 ----让A0=0
数据地址 = 0x6000 0002 = 0x6000 0000+(1<<(0+1)) ----让A0=1,但是这里为什么不是0x6000 0001呢?这里涉及到地址对齐问题:

注意事项:地址对齐

HADDR是字节地址,而储存器不一定按字节访问,因此接到存储器的地址线根据存储器的数据有所不同。

① 如果TFT-LCD的数据宽度为 8 位, HADDR[25:0]与FSMC_A[25:0]相连

② 如果TFT-LCD的数据宽度为 16 位, HADDR[25:1]与FSMC_A[24:0]相连,HADDR[0]不接。(相当于左移一位了)

如果电路设计时选择不同的地址线时,地址要重新计算
eg:选择 A10,则数据基地址 = 0x6000 0000+(1<<(10+1)) = 0x6000 0800

需要注意以下所写的 大小:16*8 或者其他等等取模的大小都表示,左边是长右边是宽,这里长是16即16行,8是8个像素即1个字节,那这个数组占的大小是16, uint8_t ucAscii_1608[X][16],X看你有多少个字符

assert_param 是宏定义断言,判断指针是否为空,在 stm32f1xx_hal_confh 里有

字体换行需要注意中文的话直接 += font_CHN即可,然后+= 2指向下一个中文地址。ASCII的话是占中文的一半(具体看取模截图那字宽长) += font_ASCII/2

屏幕填充色彩

RGB565数据格式:图像数据的像素点由红绿蓝(RGB)三原色构成,按照不同的比例混合形成色彩。ILI9341的数据总线最高支持18位,为了方便传输,一般采用 16位 ,由于人眼对绿色较为敏感,16位数据描述像素点的三原色比例为 R:G:B = 5:6:5

像素点三原色与数据线的对应关系:

数据传输宽度:16位

红色: D11 - D15

绿色: D5 - D10

蓝色: D0 - D4

// 举例,比如红色11111数据越大越红
像素点显示纯红色: 0b11111 000000 00000 = 0xF800

像素点显示纯绿色: 0b00000 111111 00000 = 0x07E0

像素点显示纯蓝色: 0b00000 000000 11111 = 0x001F

像素点显示纯黑色: 0b00000 000000 00000 = 0x0000

像素点显示纯白色: 0b11111 111111 11111 = 0xFFFF
  • MX配置

在介绍那

  • 程序编写
TFT_LCD.h
#ifndef __TFT_LCD_H
#define __TFT_LCD_H

//头文件
#include "AllHead.h"

////宏定义
//TFT屏幕背光控制(打开/关闭)
#define	TFT_LCD_BL_ON	  HAL_GPIO_WritePin(TFT_LCD_BL_GPIO_Port,TFT_LCD_BL_Pin,GPIO_PIN_SET)
#define	TFT_LCD_BL_OFF	HAL_GPIO_WritePin(TFT_LCD_BL_GPIO_Port,TFT_LCD_BL_Pin,GPIO_PIN_RESET)

#define	FSMC_LCD_CMD_ADDR		((uint32_t)0x60000000)  //FSMC_Bank1_NORSRAM1用于LCD命令操作的地址
#define	FSMC_LCD_DATA_ADDR  ((uint32_t)0x60000002)  //FSMC_Bank1_NORSRAM1用于LCD数据操作的地址  

//LCD读写函数宏定义
#define	LCD_Write_CMD(CMD) 	  *((__IO uint16_t *)FSMC_LCD_CMD_ADDR)  = (uint16_t)CMD
#define	LCD_Write_DATA(DATA)  *((__IO uint16_t *)FSMC_LCD_DATA_ADDR) = (uint16_t)DATA
#define	LCD_Read_DATA()       *((__IO uint16_t *)FSMC_LCD_DATA_ADDR)

//显示方向选择,可选(1,2,3,4)四个方向
#define LCD_DIRECTION                  1  // 竖屏,逆时针旋转0度, 原点在屏幕左上角 X*Y=240*320
//#define LCD_DIRECTION                  2  // 横屏,逆时针旋转90度, 原点在屏幕右上角 X*Y=320*240
//#define LCD_DIRECTION                  3  // 竖屏,逆时针旋转180度,原点在屏幕右下角 X*Y=240*320
//#define LCD_DIRECTION                  4  // 横屏,逆时针旋转270度,原点在屏幕左下角 X*Y=320*240

//LCD屏幕的宽度与高度(看上面横屏和竖屏像素比例相反)
#if (LCD_DIRECTION == 1)||(LCD_DIRECTION == 3)
#define LCD_WIDTH		         240  // X轴长度
#define LCD_HEIGTH         	 320  // Y轴长度
#else
#define LCD_WIDTH		         320  // X轴长度
#define LCD_HEIGTH         	 240  // Y轴长度 
#endif

//LCD命令
// 设置xy坐标命令
#define LCD_CMD_SETxOrgin		0x2A
#define LCD_CMD_SETyOrgin   0x2B
// 读写Graphics RAM
#define LCD_CMD_WRgram      0x2C
#define LCD_CMD_RDgram      0x2E

////定义枚举类型
//颜色定义 RGB比例是5:6:5
typedef enum
{
    Color_BLACK   = 0x0000,	   //黑色 0b00000 000000 00000
    Color_WHITE   = 0xFFFF,	   //白色
    Color_RED     = 0xF800,	   //红色 0b11111 000000 00000
    Color_GREEN   = 0x07E0,	   //绿色
    Color_BLUE    = 0x001F,	   //蓝色
    Color_YELLOW  = 0xFFE0,    //黄色
    Color_GRAY    =	0X8430,    //灰色
} LCD_Color_t;

//定义结构体类型
typedef struct
{
    uint32_t ID; //屏幕ID(通过命令读取屏幕ID)

    void (*Init)(void);                                                 //LCD屏幕初始化
    void (*FillColor)(uint16_t, uint16_t, uint16_t, uint16_t, LCD_Color_t); //LCD屏幕填充颜色
} TFT_LCD_t;

extern TFT_LCD_t  TFT_LCD;

#endif

读ID的话它有4个ID可以读,这里我们读ID4,用命令 0xD3

设置窗口X的话看手册,首先发命令,然后写X的起始点高8位再写低8位,再写X的终点高8位再写低8位,Y步骤一样只是命令不同

程序里终点减1是因为像素坐标都是从0开始的, 例如,当设置一个起点坐标为 (10, 20)、宽度为 50、高度为 30 的窗口时,实际的起点坐标应该是 (10, 20),而终点坐标则应该是 (59, 49),其中 59 和 49 分别是 (10+50-1) 和 (20+30-1) 计算得出的

初始化的话直接拿厂家程序就行了,然后看看哪里需要改比如屏幕方向等等

TFT_LCD.c
/* Includes ------------------------------------------------------------------*/
#include "AllHead.h"

static void LCD_Init(void);                                                 //LCD屏幕初始化
static void LCD_FillColor(uint16_t, uint16_t, uint16_t, uint16_t, LCD_Color_t); //LCD屏幕填充颜色

TFT_LCD_t TFT_LCD =
{
    0,

    LCD_Init,
    LCD_FillColor
};

static uint32_t LCD_ReadID(void);                                //LCD读取ID
static void LCD_Disp_Direction(void);                            //LCD显示方向
static void LCD_SetWindows(uint16_t, uint16_t, uint16_t, uint16_t); //设置LCD显示窗口

/*
	* @name   LCD_ReadID
	* @brief  LCD读取ID
	* @param  None
	* @retval LCD_ID -> 返回LCD屏幕ID
*/
static uint32_t LCD_ReadID(void)
{
    uint32_t LCD_ID = 0;
    uint32_t buf[4];

    LCD_Write_CMD(0xD3);
    buf[0] = LCD_Read_DATA();        // 第一个读取数据无效
    buf[1] = LCD_Read_DATA() & 0x00FF; // 只有低8位数据有效
    buf[2] = LCD_Read_DATA() & 0x00FF; // 只有低8位数据有效
    buf[3] = LCD_Read_DATA() & 0x00FF; // 只有低8位数据有效

    LCD_ID = (buf[1] << 16) + (buf[2] << 8) + buf[3];
    return LCD_ID;
}

/*
	* @name   LCD_Init
	* @brief  LCD屏幕初始化
	* @param  None
	* @retval None
*/
static void LCD_Init(void)
{
    //读取LCD屏幕ID
    TFT_LCD.ID = LCD_ReadID();
    printf("The ID of TFT LCD is 0x%.6X\r\n", TFT_LCD.ID);

    //2.8inch ILI9341初始化
    LCD_Write_CMD(0xCF);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0xC9);   //C1
    LCD_Write_DATA(0x30);
    LCD_Write_CMD(0xED);
    LCD_Write_DATA(0x64);
    LCD_Write_DATA(0x03);
    LCD_Write_DATA(0X12);
    LCD_Write_DATA(0X81);
    LCD_Write_CMD(0xE8);
    LCD_Write_DATA(0x85);
    LCD_Write_DATA(0x10);
    LCD_Write_DATA(0x7A);
    LCD_Write_CMD(0xCB);
    LCD_Write_DATA(0x39);
    LCD_Write_DATA(0x2C);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x34);
    LCD_Write_DATA(0x02);
    LCD_Write_CMD(0xF7);
    LCD_Write_DATA(0x20);
    LCD_Write_CMD(0xEA);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x00);
    LCD_Write_CMD(0xC0);    //Power control
    LCD_Write_DATA(0x1B);   //VRH[5:0]
    LCD_Write_CMD(0xC1);    //Power control
    LCD_Write_DATA(0x00);   //SAP[2:0];BT[3:0] 01
    LCD_Write_CMD(0xC5);    //VCM control
    LCD_Write_DATA(0x30); 	//3F
    LCD_Write_DATA(0x30); 	//3C
    LCD_Write_CMD(0xC7);    //VCM control2
    LCD_Write_DATA(0XB7);
    //LCD_Write_CMD(0x36);    // Memory Access Control
    //LCD_Write_DATA(0x08);
    LCD_Disp_Direction();   //设置LCD显示方向
    LCD_Write_CMD(0x3A);
    LCD_Write_DATA(0x55);
    LCD_Write_CMD(0xB1);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x1A);
    LCD_Write_CMD(0xB6);    // Display Function Control
    LCD_Write_DATA(0x0A);
    LCD_Write_DATA(0xA2);
    LCD_Write_CMD(0xF2);    // 3Gamma Function Disable
    LCD_Write_DATA(0x00);
    LCD_Write_CMD(0x26);    //Gamma curve selected
    LCD_Write_DATA(0x01);
    LCD_Write_CMD(0xE0);    //Set Gamma
    LCD_Write_DATA(0x0F);
    LCD_Write_DATA(0x2A);
    LCD_Write_DATA(0x28);
    LCD_Write_DATA(0x08);
    LCD_Write_DATA(0x0E);
    LCD_Write_DATA(0x08);
    LCD_Write_DATA(0x54);
    LCD_Write_DATA(0XA9);
    LCD_Write_DATA(0x43);
    LCD_Write_DATA(0x0A);
    LCD_Write_DATA(0x0F);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x00);
    LCD_Write_CMD(0XE1);    //Set Gamma
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x15);
    LCD_Write_DATA(0x17);
    LCD_Write_DATA(0x07);
    LCD_Write_DATA(0x11);
    LCD_Write_DATA(0x06);
    LCD_Write_DATA(0x2B);
    LCD_Write_DATA(0x56);
    LCD_Write_DATA(0x3C);
    LCD_Write_DATA(0x05);
    LCD_Write_DATA(0x10);
    LCD_Write_DATA(0x0F);
    LCD_Write_DATA(0x3F);
    LCD_Write_DATA(0x3F);
    LCD_Write_DATA(0x0F);
    LCD_Write_CMD(0x2B);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x01);
    LCD_Write_DATA(0x3f);
    LCD_Write_CMD(0x2A);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0x00);
    LCD_Write_DATA(0xef);
    LCD_Write_CMD(0x11); //Exit Sleep
    HAL_Delay(120);
    LCD_Write_CMD(0x29); //display on

    TFT_LCD_BL_ON; //打开背光
}

/*
	* @name   LCD_Disp_Directio
	* @brief  LCD显示方向
	* @param  None
	* @retval None
*/
static void LCD_Disp_Direction()
{
    switch(LCD_DIRECTION)
    {
    case 1:
        LCD_Write_CMD(0x36);
        LCD_Write_DATA(1 << 3);
        break;
    case 2:
        LCD_Write_CMD(0x36);
        LCD_Write_DATA((1 << 3) | (1 << 5) | (1 << 6));
        break;
    case 3:
        LCD_Write_CMD(0x36);
        LCD_Write_DATA((1 << 3) | (1 << 7) | (1 << 4) | (1 << 6));
        break;
    case 4:
        LCD_Write_CMD(0x36);
        LCD_Write_DATA((1 << 3) | (1 << 7) | (1 << 5) | (1 << 4));
        break;
    default:
        LCD_Write_CMD(0x36);
        LCD_Write_DATA(1 << 3);
        break;
    }
}

/*
	* @name   LCD_SetWindows
	* @brief  设置LCD显示窗口
	* @param  xStar  ->窗口的起点X坐标
						yStar  ->窗口的起点Y坐标
						xWidth ->窗口的宽度
						yHeight->窗口的高度
	* @retval None
*/
static void LCD_SetWindows(uint16_t xStar, uint16_t yStar, uint16_t xWidth, uint16_t yHeight)
{
    LCD_Write_CMD(LCD_CMD_SETxOrgin);
    LCD_Write_DATA(xStar >> 8);
    LCD_Write_DATA(0x00FF & xStar);
    LCD_Write_DATA((xStar + xWidth - 1) >> 8);
    LCD_Write_DATA((xStar + xWidth - 1) & 0xFF);

    LCD_Write_CMD(LCD_CMD_SETyOrgin);
    LCD_Write_DATA(yStar >> 8);
    LCD_Write_DATA(0x00FF & yStar);
    LCD_Write_DATA((yStar + yHeight - 1) >> 8);
    LCD_Write_DATA((yStar + yHeight - 1) & 0xFF);

    LCD_Write_CMD(LCD_CMD_WRgram); //开始写入GRAM
}

/*
	* @name   LCD_FillColor
	* @brief  LCD屏幕填充颜色
	* @param  xStar  ->窗口的起点X坐标
						yStar  ->窗口的起点Y坐标
						xWidth ->窗口的宽度
						yHeight->窗口的高度
						FillColor -> 填充色
	* @retval None
*/
static void LCD_FillColor(uint16_t xStar, uint16_t yStar, uint16_t xWidth, uint16_t yHeight, LCD_Color_t FillColor)
{
    uint16_t i, j;
    //uint16_t k;

    //设置窗口
    LCD_SetWindows(xStar, yStar, xWidth, yHeight);
    //填充颜色---一行行写 写完换行
    for(i = xStar; i < (xStar + xWidth); i++)
    {
        for(j = 0; j < (yStar + yHeight); j++)
        {
            LCD_Write_DATA(FillColor);
            //动态观看屏幕显示过程
            //for(k=0;k<100;k++);
        }
    }
}
main.c
static void Run()
{
    TFT_LCD.FillColor(0, 0, LCD_WIDTH, LCD_HEIGTH, Color_RED);
    HAL_Delay(1000);
    TFT_LCD.FillColor(0, 0, LCD_WIDTH, LCD_HEIGTH, Color_GREEN);
    HAL_Delay(1000);
    TFT_LCD.FillColor(0, 0, LCD_WIDTH, LCD_HEIGTH, Color_BLUE);
    HAL_Delay(1000);
}

实验现象

屏幕间隔1S循环变换颜色

显示ASCII

  • 使用取模软件 PCtoLCD2002

// 生成 0123456789 示例
{0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00},/*"0",0*/
{0x00,0x00,0x00,0x10,0x1C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00},/*"1",1*/
{0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x40,0x20,0x10,0x08,0x04,0x42,0x7E,0x00,0x00},/*"2",2*/
{0x00,0x00,0x00,0x3C,0x42,0x42,0x40,0x20,0x18,0x20,0x40,0x42,0x42,0x3C,0x00,0x00},/*"3",3*/
{0x00,0x00,0x00,0x20,0x30,0x30,0x28,0x24,0x24,0x22,0xFE,0x20,0x20,0xF8,0x00,0x00},/*"4",4*/
{0x00,0x00,0x00,0x7E,0x02,0x02,0x02,0x1E,0x22,0x40,0x40,0x42,0x22,0x1C,0x00,0x00},/*"5",5*/
{0x00,0x00,0x00,0x18,0x24,0x02,0x02,0x3A,0x46,0x42,0x42,0x42,0x44,0x38,0x00,0x00},/*"6",6*/
{0x00,0x00,0x00,0x7E,0x42,0x20,0x20,0x10,0x10,0x08,0x08,0x08,0x08,0x08,0x00,0x00},/*"7",7*/
{0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00},/*"8",8*/
{0x00,0x00,0x00,0x1C,0x22,0x42,0x42,0x42,0x62,0x5C,0x40,0x40,0x24,0x18,0x00,0x00},/*"9",9*/
  • MX配置

在介绍那

  • 程序编写(在之前的程序基础上添加修改)

取模的话需要从ASCII码表十进制 32 ~ 126 按顺序取模

TFT_LCD.h
//ASCII码字体
typedef enum
{
    ASCII_font_16 = 16,
    ASCII_font_24 = 24,
} ASCII_font_t;
#define IS_ASCII_font(font)   (((font) == ASCII_font_16) || ((font) == ASCII_font_24))

//定义结构体类型
typedef struct
{
    uint32_t ID; //屏幕ID

    void (*Init)(void);                                                 //LCD屏幕初始化
    void (*FillColor)(uint16_t, uint16_t, uint16_t, uint16_t, LCD_Color_t); //LCD屏幕填充颜色
    void (*LCD_ShowChar)(uint16_t, uint16_t, const char, uint16_t, uint16_t, ASCII_font_t);        //在LCD屏幕上显示一个英文字符
    void (*LCD_ShowString)(uint16_t, uint16_t, const char *, uint16_t, uint16_t, ASCII_font_t);    //在LCD屏幕上显示英文字符串
} TFT_LCD_t;

extern TFT_LCD_t  TFT_LCD;
TFT_LCD.c
/*
	* @name   LCD_ShowChar
	* @brief  在LCD屏幕上显示一个英文字符
	* @param  usX: 起始X坐标
*           usY :起始Y坐标
*           cChar :要显示的英文字符
*           usColor_Background :选择英文字符的背景色
*           usColor_Foreground :选择英文字符的前景色
*           font:字体选择
*             参数:ASCII_font_16 :16号字体
*                   ASCII_font_24 :24号字体
	* @retval None
*/

static void LCD_ShowChar(uint16_t usX, uint16_t usY, const char cChar, uint16_t usColor_Background, uint16_t usColor_Foreground, ASCII_font_t font)
{
    uint8_t ucTemp, ucIndex, ucPage, ucColumn;

    //检查输入参数是否合法
    assert_param(IS_ASCII_font(font));
    //ASCII字符集数组索引,需要减去偏移量(' ' -> 空格键的码值)
    ucIndex = cChar - ' ';

    //判断字体 - 16号字体
    if(font == ASCII_font_16)
    {
        //设置窗口,大小为8x16
        LCD_SetWindows(usX, usY, 8, 16);
        //逐行写入数据,共16行,每行8个像素点
        for(ucPage = 0; ucPage < 16; ucPage++)
        {
            //从ASCII字符集数组获取像素数据
            //像素点数据为1时,写入字符颜色,为0时,写入背景颜色
            ucTemp = ucAscii_1608[ucIndex][ucPage];
            for(ucColumn = 0; ucColumn < 8; ucColumn++)
            {
                if((ucTemp & BIT0) == 1)
                    LCD_Write_DATA(usColor_Foreground);
                else
                    LCD_Write_DATA(usColor_Background);
                ucTemp >>= 1;
            }
        }
    }
    //判断字体 - 24号字体
    if(font == ASCII_font_24)
    {
        //设置窗口,大小为12x24
        LCD_SetWindows(usX, usY, 12, 24);
        //逐行写入数据,共24行,每行12个像素点(占2个字节)
        for(ucPage = 0; ucPage < 48; ucPage += 2)
        {
            //从ASCII字符集数组获取像素数据,前8个像素点
            //像素点数据为1时,写入字符颜色,为0时,写入背景颜色
            ucTemp = ucAscii_2412[ucIndex][ucPage];
            for(ucColumn = 0; ucColumn < 8; ucColumn++)
            {
                if((ucTemp & 1) == 1)
                    LCD_Write_DATA(usColor_Foreground);
                else
                    LCD_Write_DATA(usColor_Background);
                ucTemp >>= 1;
            }
            //从ASCII字符集数组获取像素数据,后4个像素点
            //像素点数据为1时,写入字符颜色,为0时,写入背景颜色
            ucTemp = ucAscii_2412[ucIndex][ucPage + 1];
            for(ucColumn = 0; ucColumn < 4; ucColumn++)
            {
                if((ucTemp & 1) == 1)
                    LCD_Write_DATA(usColor_Foreground);
                else
                    LCD_Write_DATA(usColor_Background);
                ucTemp >>= 1;
            }
        }
    }
}
/*
	* @name   LCD_ShowString
	* @brief  在LCD屏幕上显示英文字符串
	* @param  usX: 起始X坐标
*           usY :起始Y坐标
*           pStr:要显示的英文字符串的首地址
*           usColor_Background :选择英文字符的背景色
*           usColor_Foreground :选择英文字符的前景色
*           font:字体选择
*             参数:ASCII_font_16 :16号字体
*                   ASCII_font_24 :24号字体
	* @retval None
*/
static void LCD_ShowString(uint16_t usX, uint16_t usY, const char *pStr, uint16_t usColor_Background, uint16_t usColor_Foreground, ASCII_font_t font)
{
    while (* pStr != '\0')
    {
        //自动换行
        if ((usX + font / 2) > LCD_WIDTH)
        {
            usX = 0;
            usY += font;
        }
        //自动换页
        if ((usY + font) > LCD_HEIGTH)
        {
            usX = 0;
            usY = 0;
        }
        //显示字符
        TFT_LCD.LCD_ShowChar(usX, usY, * pStr, usColor_Background, usColor_Foreground, font);
        //更新位置
        pStr ++;
        usX += font / 2;
    }
}
main.c
static void Run()
{	
	//竖屏显示
	if((LCD_DIRECTION == 1) || (LCD_DIRECTION == 3))
	{
		//字符串显示在中间位置
		TFT_LCD.LCD_ShowString(36,136,"Happy New Year",Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowString(104,160,"2021",Color_GRAY,Color_BLUE,ASCII_font_16);
	}
	//横屏显示
	else
	{
		//字符串显示在中间位置
		TFT_LCD.LCD_ShowString(76,96,"Happy New Year",Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowString(144,120,"2021",Color_GRAY,Color_BLUE,ASCII_font_16);
	}
}
font.h
//ASCII字符集
//偏移量32(' ' -> 空格键的码值)
//大小:16*8
//字体:Default(宋体)
//逐行式(从第一行开始向右每取8个点作为一个字节,不足8个点就补0)
//逆向(取模顺序从低到高,即第一个点作为最低位)
const uint8_t ucAscii_1608[95][16] =
{     
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
	{0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x08,0x08,0x08,0x08,0x00,0x18,0x18,0x00,0x00},/*"!",1*/
	{0x00,0x00,0x00,0x3C,0x24,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*""",2*/
	{0x00,0x00,0x00,0x44,0x24,0x24,0xFF,0x24,0x24,0x24,0xFF,0x22,0x12,0x12,0x00,0x00},/*"#",3*/
	{0x00,0x00,0x08,0x3C,0x2E,0x6A,0x0E,0x0C,0x38,0x68,0x6A,0x6A,0x2E,0x1C,0x08,0x00},/*"$",4*/
	{0x00,0x00,0x00,0x26,0x25,0x15,0x1D,0x16,0x68,0x58,0x54,0x54,0x52,0x62,0x00,0x00},/*"%",5*/
	{0x00,0x00,0x00,0x1C,0x34,0x34,0x14,0x0C,0x0E,0x4A,0x53,0x63,0xF6,0x1C,0x00,0x00},/*"&",6*/
	{0x00,0x00,0x00,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",7*/
	{0x00,0x00,0x40,0x20,0x30,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x20,0x20,0x40,0x00},/*"(",8*/
	{0x00,0x00,0x02,0x04,0x04,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x04,0x06,0x03,0x00},/*")",9*/
	{0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x3C,0x5A,0x18,0x18,0x00,0x00,0x00,0x00,0x00},/*"*",10*/
	{0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x08,0xFF,0x08,0x08,0x08,0x00,0x00,0x00,0x00},/*"+",11*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x02,0x00},/*",",12*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"-",13*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x00,0x00},/*".",14*/
	{0x00,0x00,0x00,0x40,0x20,0x20,0x10,0x10,0x08,0x08,0x04,0x04,0x02,0x01,0x00,0x00},/*"/",15*/
	{0x00,0x00,0x00,0x3C,0x26,0x42,0x42,0x42,0x42,0x42,0x42,0x62,0x34,0x18,0x00,0x00},/*"0",16*/
	{0x00,0x00,0x00,0x10,0x18,0x1E,0x1A,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00},/*"1",17*/
	{0x00,0x00,0x00,0x3C,0x66,0x42,0x60,0x20,0x30,0x10,0x08,0x04,0x7E,0x7E,0x00,0x00},/*"2",18*/
	{0x00,0x00,0x00,0x3C,0x66,0x42,0x60,0x30,0x30,0x60,0x40,0x42,0x26,0x18,0x00,0x00},/*"3",19*/
	{0x00,0x00,0x00,0x20,0x30,0x38,0x28,0x24,0x26,0x22,0xFF,0x20,0x20,0x20,0x00,0x00},/*"4",20*/
	{0x00,0x00,0x00,0x7C,0x06,0x02,0x0A,0x3E,0x62,0x40,0x40,0x63,0x36,0x1C,0x00,0x00},/*"5",21*/
	{0x00,0x00,0x00,0x10,0x18,0x08,0x0C,0x3E,0x46,0xC2,0xC2,0x42,0x66,0x3C,0x00,0x00},/*"6",22*/
	{0x00,0x00,0x00,0x7E,0x40,0x60,0x20,0x30,0x10,0x18,0x08,0x08,0x0C,0x0C,0x00,0x00},/*"7",23*/
	{0x00,0x00,0x00,0x3C,0x62,0x42,0x62,0x3E,0x3E,0x42,0x43,0x42,0x66,0x3C,0x00,0x00},/*"8",24*/
	{0x00,0x00,0x00,0x3C,0x62,0x43,0x43,0x63,0x36,0x3C,0x10,0x18,0x08,0x0C,0x00,0x00},/*"9",25*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00},/*":",26*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x08,0x00},/*";",27*/
	{0x00,0x00,0x00,0x40,0x20,0x10,0x08,0x06,0x02,0x04,0x08,0x10,0x20,0x40,0x00,0x00},/*"<",28*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00},/*"=",29*/
	{0x00,0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x06,0x02,0x00,0x00},/*">",30*/
	{0x00,0x00,0x00,0x3C,0x66,0x62,0x60,0x30,0x10,0x08,0x00,0x00,0x08,0x08,0x00,0x00},/*"?",31*/
	{0x00,0x00,0x00,0x3C,0x42,0x79,0x69,0x65,0x55,0x55,0x55,0x29,0x02,0x3C,0x00,0x00},/*"@",32*/
	{0x00,0x00,0x00,0x18,0x18,0x18,0x3C,0x24,0x24,0x3E,0x66,0x42,0x42,0xC3,0x00,0x00},/*"A",33*/
	{0x00,0x00,0x00,0x3E,0x62,0x42,0x42,0x3E,0x3E,0x42,0x42,0x42,0x7E,0x1E,0x00,0x00},/*"B",34*/
	{0x00,0x00,0x00,0x3C,0x66,0x42,0x42,0x02,0x02,0x42,0x42,0x46,0x6C,0x38,0x00,0x00},/*"C",35*/
	{0x00,0x00,0x00,0x1E,0x32,0x62,0x42,0x42,0x42,0x42,0x42,0x62,0x3E,0x0E,0x00,0x00},/*"D",36*/
	{0x00,0x00,0x00,0x7E,0x02,0x02,0x02,0x7E,0x7E,0x02,0x02,0x02,0x7E,0x7E,0x00,0x00},/*"E",37*/
	{0x00,0x00,0x00,0x7E,0x02,0x02,0x02,0x02,0x3E,0x02,0x02,0x02,0x02,0x02,0x00,0x00},/*"F",38*/
	{0x00,0x00,0x00,0x3C,0x66,0x42,0x42,0x02,0x72,0x72,0x42,0x46,0x6C,0x58,0x00,0x00},/*"G",39*/
	{0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x7E,0x7E,0x42,0x42,0x42,0x42,0x42,0x00,0x00},/*"H",40*/
	{0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00},/*"I",41*/
	{0x00,0x00,0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x42,0x62,0x3E,0x1C,0x00,0x00},/*"J",42*/
	{0x00,0x00,0x00,0x62,0x22,0x12,0x1A,0x0E,0x1E,0x12,0x32,0x22,0x62,0xC2,0x00,0x00},/*"K",43*/
	{0x00,0x00,0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x7E,0x7E,0x00,0x00},/*"L",44*/
	{0x00,0x00,0x00,0x66,0x66,0x66,0x66,0x76,0x5E,0x5A,0x5A,0x5A,0x5A,0x4A,0x00,0x00},/*"M",45*/
	{0x00,0x00,0x00,0x42,0x46,0x46,0x4E,0x4A,0x5A,0x52,0x72,0x62,0x62,0x62,0x00,0x00},/*"N",46*/
	{0x00,0x00,0x00,0x3C,0x66,0x42,0x42,0x43,0x43,0x43,0x42,0x62,0x26,0x18,0x00,0x00},/*"O",47*/
	{0x00,0x00,0x00,0x3E,0x62,0x42,0x42,0x42,0x7E,0x0E,0x02,0x02,0x02,0x02,0x00,0x00},/*"P",48*/
	{0x00,0x00,0x00,0x3C,0x66,0x42,0x42,0x43,0x43,0x43,0x52,0x72,0x26,0x78,0x00,0x00},/*"Q",49*/
	{0x00,0x00,0x00,0x3E,0x62,0x42,0x42,0x62,0x3E,0x12,0x32,0x22,0x62,0x42,0x00,0x00},/*"R",50*/
	{0x00,0x00,0x00,0x3C,0x66,0x62,0x06,0x0C,0x38,0x60,0x42,0x42,0x66,0x3C,0x00,0x00},/*"S",51*/
	{0x00,0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00},/*"T",52*/
	{0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x66,0x3C,0x00,0x00},/*"U",53*/
	{0x00,0x00,0x00,0x43,0x42,0x62,0x66,0x26,0x24,0x34,0x1C,0x18,0x18,0x18,0x00,0x00},/*"V",54*/
	{0x00,0x00,0x00,0xD9,0x5B,0x5B,0x5A,0x5A,0x56,0x56,0x66,0x66,0x26,0x26,0x00,0x00},/*"W",55*/
	{0x00,0x00,0x00,0x62,0x26,0x24,0x1C,0x18,0x18,0x1C,0x34,0x26,0x62,0x43,0x00,0x00},/*"X",56*/
	{0x00,0x00,0x00,0x43,0x62,0x26,0x34,0x1C,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00},/*"Y",57*/
	{0x00,0x00,0x00,0x7E,0x60,0x20,0x30,0x10,0x08,0x0C,0x04,0x06,0x7E,0x7E,0x00,0x00},/*"Z",58*/
	{0x00,0x78,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x78,0x00},/*"[",59*/
	{0x00,0x00,0x00,0x02,0x04,0x04,0x04,0x08,0x08,0x10,0x10,0x10,0x20,0x20,0x40,0x40},/*"\",60*/
	{0x00,0x1E,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x1E,0x00},/*"]",61*/
	{0x00,0x18,0x34,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"^",62*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF},/*"_",63*/
	{0x00,0x0C,0x18,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x62,0x70,0x6E,0x62,0x72,0x5C,0x00,0x00},/*"a",65*/
	{0x00,0x00,0x00,0x02,0x02,0x02,0x02,0x3E,0x62,0x42,0x42,0x62,0x66,0x1A,0x00,0x00},/*"b",66*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x62,0x02,0x02,0x42,0x66,0x1C,0x00,0x00},/*"c",67*/
	{0x00,0x00,0x00,0x40,0x40,0x40,0x40,0x7E,0x62,0x42,0x42,0x62,0x66,0x5C,0x00,0x00},/*"d",68*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x62,0x7E,0x02,0x42,0x66,0x38,0x00,0x00},/*"e",69*/
	{0x00,0x00,0x00,0x78,0x08,0x08,0x08,0x7E,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00},/*"f",70*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x22,0x22,0x3C,0x02,0x3E,0x62,0x42,0x3E},/*"g",71*/
	{0x00,0x00,0x00,0x02,0x02,0x02,0x02,0x7A,0x46,0x42,0x42,0x42,0x42,0x42,0x00,0x00},/*"h",72*/
	{0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00},/*"i",73*/
	{0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x32,0x1E},/*"j",74*/
	{0x00,0x00,0x00,0x02,0x02,0x02,0x02,0x32,0x1A,0x1E,0x16,0x22,0x62,0x42,0x00,0x00},/*"k",75*/
	{0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00},/*"l",76*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEF,0xDB,0xDB,0xDB,0xDB,0xDB,0xDB,0x00,0x00},/*"m",77*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7A,0x46,0x42,0x42,0x42,0x42,0x42,0x00,0x00},/*"n",78*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x62,0x42,0x42,0x42,0x66,0x18,0x00,0x00},/*"o",79*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x62,0x42,0x42,0x62,0x66,0x1A,0x02,0x02},/*"p",80*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x62,0x42,0x42,0x62,0x66,0x5C,0x40,0x40},/*"q",81*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x34,0x0C,0x04,0x04,0x04,0x04,0x04,0x00,0x00},/*"r",82*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x66,0x06,0x38,0x42,0x66,0x3C,0x00,0x00},/*"s",83*/
	{0x00,0x00,0x00,0x00,0x08,0x08,0x08,0x3F,0x08,0x08,0x08,0x08,0x48,0x70,0x00,0x00},/*"t",84*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x62,0x76,0x5C,0x00,0x00},/*"u",85*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x66,0x24,0x24,0x1C,0x18,0x18,0x00,0x00},/*"v",86*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xDB,0x5B,0x5A,0x56,0x66,0x26,0x24,0x00,0x00},/*"w",87*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x34,0x18,0x18,0x3C,0x26,0x42,0x00,0x00},/*"x",88*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x66,0x24,0x24,0x1C,0x18,0x18,0x08,0x0E},/*"y",89*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x30,0x10,0x08,0x04,0x06,0x7E,0x00,0x00},/*"z",90*/
	{0x00,0x60,0x30,0x30,0x30,0x30,0x30,0x30,0x10,0x30,0x30,0x30,0x30,0x30,0x20,0x00},/*"{",91*/
	{0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18},/*"|",92*/
	{0x00,0x06,0x04,0x04,0x04,0x04,0x04,0x04,0x08,0x04,0x04,0x04,0x04,0x04,0x04,0x00},/*"}",93*/
	{0x04,0x4E,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} /*"~",94*/
};

//ASCII字符集
//偏移量32(' ' -> 空格键的码值)
//大小:24*12
//字体:Default(宋体)
//逐行式(从第一行开始向右每取8个点作为一个字节,不足8个点就补0)
//逆向(取模顺序从低到高,即第一个点作为最低位)
const uint8_t ucAscii_2412[95][48] =
{
	{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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0x70,0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x98,0x01,0x98,0x01,0x98,0x01,0x98,0x01,0x98,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},/*""",2*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x03,0x10,0x03,0x10,0x03,0x18,0x03,0x18,0x01,0xFF,0x07,0xFF,0x07,0x08,0x01,0x88,0x01,0x88,0x01,0xFF,0x07,0xFF,0x07,0x8C,0x00,0x8C,0x00,0x8C,0x00,0x84,0x00,0xC4,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"#",3*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0xF0,0x00,0xF8,0x01,0xAC,0x03,0x2C,0x03,0x2C,0x03,0x2C,0x00,0x3C,0x00,0x78,0x00,0xE0,0x00,0xE0,0x01,0xA0,0x03,0x26,0x03,0x26,0x03,0x2E,0x03,0xAC,0x03,0xFC,0x01,0xF0,0x00,0x20,0x00,0x20,0x00,0x00,0x00},/*"$",4*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x02,0x1E,0x01,0x12,0x01,0x12,0x01,0x92,0x00,0x92,0x00,0x52,0x00,0x5E,0x00,0x2C,0x03,0xA0,0x07,0x90,0x04,0x90,0x04,0xD0,0x04,0x88,0x04,0x88,0x04,0x84,0x07,0x04,0x03,0x00,0x00,0x00,0x00,0x00,0x00},/*"%",5*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xF8,0x00,0x8C,0x00,0x8C,0x01,0x8C,0x00,0xCC,0x00,0x78,0x00,0x38,0x00,0x3C,0x00,0x3E,0x06,0x66,0x06,0xE6,0x02,0xC6,0x03,0x86,0x03,0x8E,0x07,0xFC,0x06,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"&",6*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,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},/*"'",7*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x03,0x80,0x01,0x80,0x00,0xC0,0x00,0xC0,0x00,0x40,0x00,0x40,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0xC0,0x00,0x80,0x00,0x80,0x01,0x00,0x03,0x00,0x06,0x00,0x00,0x00,0x00},/*"(",8*/
	{0x00,0x00,0x00,0x00,0x02,0x00,0x06,0x00,0x0C,0x00,0x08,0x00,0x18,0x00,0x10,0x00,0x30,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x30,0x00,0x10,0x00,0x18,0x00,0x08,0x00,0x0C,0x00,0x06,0x00,0x02,0x00,0x00,0x00},/*")",9*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x62,0x04,0x66,0x06,0xFE,0x07,0xF8,0x01,0xF0,0x00,0xFC,0x03,0x6E,0x07,0x62,0x04,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"*",10*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0xFF,0x07,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"+",11*/
	{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,0x00,0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x08,0x00,0x04,0x00,0x00,0x00},/*",",12*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"-",13*/
	{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,0x00,0x00,0x00,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*".",14*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x04,0x00,0x02,0x00,0x02,0x00,0x01,0x80,0x01,0x80,0x00,0xC0,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x10,0x00,0x10,0x00,0x08,0x00,0x0C,0x00,0x04,0x00,0x06,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"/",15*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xF8,0x01,0x9C,0x03,0x0C,0x03,0x06,0x03,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x03,0x0C,0x03,0x9C,0x03,0xF8,0x01,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"0",16*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x60,0x00,0x70,0x00,0x78,0x00,0x6C,0x00,0x64,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"1",17*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x00,0xFC,0x01,0x8C,0x03,0x06,0x03,0x06,0x03,0x00,0x03,0x00,0x03,0x80,0x01,0x80,0x01,0xC0,0x00,0x60,0x00,0x70,0x00,0x38,0x00,0x1C,0x00,0x0C,0x00,0xFE,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"2",18*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x00,0xF8,0x01,0x0C,0x03,0x06,0x03,0x06,0x03,0x00,0x03,0x00,0x03,0xE0,0x01,0xE0,0x01,0x80,0x03,0x00,0x03,0x00,0x06,0x06,0x06,0x06,0x03,0x8C,0x03,0xF8,0x01,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"3",19*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x01,0xC0,0x01,0xC0,0x01,0xE0,0x01,0xB0,0x01,0xB0,0x01,0x98,0x01,0x8C,0x01,0x8C,0x01,0x86,0x01,0xFF,0x0F,0xFF,0x0F,0x80,0x01,0x80,0x01,0x80,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"4",20*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0x03,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x04,0x00,0xFE,0x01,0xCE,0x03,0x06,0x03,0x00,0x03,0x00,0x06,0x00,0x06,0x03,0x03,0x06,0x03,0x86,0x03,0xFC,0x01,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"5",21*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x01,0xC0,0x00,0x60,0x00,0x60,0x00,0x30,0x00,0x18,0x00,0xF8,0x00,0xFC,0x03,0x0C,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x0C,0x03,0xF8,0x03,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"6",22*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x07,0x00,0x06,0x00,0x03,0x00,0x03,0x80,0x01,0x80,0x01,0xC0,0x00,0xC0,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"7",23*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xFC,0x01,0x0C,0x03,0x06,0x03,0x06,0x03,0x06,0x03,0x8C,0x03,0xFC,0x01,0xFC,0x01,0x8E,0x03,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x0E,0x03,0xFC,0x01,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"8",24*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xFC,0x01,0x8E,0x03,0x06,0x03,0x06,0x07,0x03,0x07,0x06,0x03,0x06,0x03,0x8E,0x03,0xFC,0x01,0xF0,0x01,0xC0,0x00,0xE0,0x00,0x60,0x00,0x30,0x00,0x30,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"9",25*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*":",26*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x20,0x00,0x20,0x00,0x00,0x00},/*";",27*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x03,0x80,0x01,0xC0,0x00,0x60,0x00,0x30,0x00,0x18,0x00,0x0C,0x00,0x06,0x00,0x0C,0x00,0x18,0x00,0x30,0x00,0x60,0x00,0xC0,0x00,0x80,0x01,0x00,0x03,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00},/*"<",28*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x07,0xFF,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"=",29*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x06,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0xC0,0x00,0x80,0x01,0x00,0x03,0x00,0x06,0x00,0x03,0x80,0x01,0xC0,0x00,0x60,0x00,0x30,0x00,0x08,0x00,0x06,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*">",30*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xF8,0x01,0x9C,0x03,0x0C,0x03,0x06,0x03,0x00,0x03,0x80,0x03,0xC0,0x01,0xC0,0x00,0x60,0x00,0x60,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"?",31*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x18,0x01,0x04,0x02,0x04,0x04,0xE2,0x05,0x92,0x05,0x9A,0x05,0x99,0x04,0x89,0x04,0x89,0x04,0xC9,0x04,0xCA,0x02,0xBA,0x03,0x02,0x00,0x04,0x00,0x08,0x01,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"@",32*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x70,0x00,0xF0,0x00,0xF0,0x00,0xD8,0x00,0x98,0x01,0x98,0x01,0x98,0x01,0x8C,0x01,0xFC,0x03,0xFC,0x03,0x0E,0x03,0x06,0x07,0x06,0x06,0x06,0x06,0x07,0x0E,0x00,0x00,0x00,0x00,0x00,0x00},/*"A",33*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x00,0xFE,0x01,0x86,0x03,0x06,0x03,0x06,0x07,0x06,0x07,0x06,0x03,0xFE,0x01,0xFE,0x01,0x06,0x03,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x86,0x03,0xFE,0x03,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"B",34*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0xF8,0x01,0x1C,0x03,0x0C,0x06,0x0E,0x06,0x06,0x06,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x06,0x06,0x06,0x0E,0x06,0x0C,0x07,0x1C,0x03,0xF8,0x01,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"C",35*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0xFE,0x00,0xC6,0x01,0x86,0x03,0x06,0x03,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x06,0x03,0x86,0x03,0xC6,0x01,0xFE,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"D",36*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x03,0xFE,0x03,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0xFE,0x03,0xFE,0x03,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0xFE,0x07,0xFE,0x07,0x00,0x00,0x00,0x00,0x00,0x00},/*"E",37*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x07,0xFE,0x07,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0xFE,0x01,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"F",38*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0xF8,0x01,0x9C,0x03,0x0C,0x03,0x0E,0x07,0x06,0x06,0x06,0x00,0x06,0x00,0xC6,0x07,0xC6,0x07,0x06,0x06,0x06,0x06,0x0E,0x06,0x0C,0x07,0x1C,0x07,0xF8,0x07,0x60,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"G",39*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xFE,0x07,0xFE,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"H",40*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"I",41*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x06,0x07,0x06,0x03,0x06,0x03,0x0E,0x03,0x9C,0x03,0xF8,0x01,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"J",42*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x03,0x86,0x01,0xC6,0x01,0xE6,0x00,0x66,0x00,0x36,0x00,0x3E,0x00,0x7E,0x00,0xEE,0x00,0xC6,0x00,0xC6,0x01,0x86,0x01,0x86,0x03,0x06,0x03,0x06,0x07,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"K",43*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0xFE,0x07,0xFE,0x07,0x00,0x00,0x00,0x00,0x00,0x00},/*"L",44*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x07,0x0E,0x07,0x0E,0x07,0x8E,0x07,0x9E,0x07,0x9E,0x07,0x9E,0x07,0x9E,0x06,0x96,0x06,0xF6,0x06,0xF6,0x06,0xF6,0x06,0x76,0x06,0x66,0x06,0x66,0x06,0x66,0x06,0x66,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"M",45*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x0E,0x06,0x0E,0x06,0x1E,0x06,0x1E,0x06,0x1E,0x06,0x36,0x06,0x36,0x06,0x66,0x06,0x66,0x06,0xC6,0x06,0xC6,0x06,0x86,0x07,0x86,0x07,0x86,0x07,0x06,0x07,0x06,0x07,0x00,0x00,0x00,0x00,0x00,0x00},/*"N",46*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xF8,0x01,0x9C,0x03,0x0E,0x03,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x0E,0x03,0x9C,0x03,0xF8,0x01,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"O",47*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x00,0xFE,0x01,0x86,0x03,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0xFE,0x03,0xFE,0x01,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"P",48*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xF8,0x01,0x9C,0x03,0x0E,0x03,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0xC6,0x06,0xC6,0x07,0x8E,0x03,0x9C,0x03,0xF8,0x03,0x70,0x03,0x00,0x00,0x00,0x00,0x00,0x00},/*"Q",49*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x00,0xFE,0x01,0x86,0x03,0x06,0x07,0x06,0x06,0x06,0x06,0x06,0x07,0x86,0x03,0xFE,0x01,0xFE,0x00,0xC6,0x00,0x86,0x01,0x86,0x01,0x06,0x03,0x06,0x03,0x06,0x07,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"R",50*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x00,0xF8,0x01,0x8C,0x03,0x0E,0x03,0x06,0x03,0x0E,0x00,0x1C,0x00,0x78,0x00,0xF0,0x01,0xC0,0x03,0x00,0x07,0x06,0x06,0x06,0x06,0x06,0x07,0x0E,0x03,0xFC,0x01,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"S",51*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x07,0xFE,0x07,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"T",52*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x8C,0x03,0xFC,0x03,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"U",53*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x06,0x07,0x06,0x06,0x06,0x06,0x07,0x0E,0x03,0x0C,0x03,0x0C,0x03,0x8C,0x01,0x9C,0x01,0x98,0x01,0x98,0x01,0xD8,0x00,0xF0,0x00,0xF0,0x00,0x70,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"V",54*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x0E,0x63,0x06,0x63,0x06,0x73,0x06,0x76,0x06,0xF6,0x06,0xD6,0x06,0xD6,0x06,0xD6,0x06,0x96,0x02,0x9E,0x03,0x9E,0x03,0x9C,0x03,0x8C,0x03,0x8C,0x03,0x8C,0x03,0x0C,0x01,0x00,0x00,0x00,0x00,0x00,0x00},/*"W",55*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x0E,0x03,0x0C,0x03,0x8C,0x01,0x98,0x01,0xD8,0x00,0xF0,0x00,0x70,0x00,0x60,0x00,0xF0,0x00,0xF0,0x00,0x98,0x01,0x9C,0x01,0x0C,0x03,0x0E,0x03,0x06,0x07,0x07,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"X",56*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x06,0x06,0x06,0x06,0x03,0x0C,0x03,0x8C,0x01,0x98,0x01,0xD8,0x00,0xF0,0x00,0x70,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"Y",57*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x07,0xFE,0x07,0x00,0x03,0x00,0x03,0x80,0x01,0xC0,0x01,0xC0,0x00,0x60,0x00,0x60,0x00,0x30,0x00,0x38,0x00,0x18,0x00,0x0C,0x00,0x0C,0x00,0x06,0x00,0xFE,0x07,0xFE,0x07,0x00,0x00,0x00,0x00,0x00,0x00},/*"Z",58*/
	{0x00,0x00,0x00,0x00,0xE0,0x07,0xE0,0x07,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE0,0x07,0xE0,0x07,0x00,0x00},/*"[",59*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x04,0x00,0x0C,0x00,0x08,0x00,0x18,0x00,0x18,0x00,0x10,0x00,0x30,0x00,0x20,0x00,0x60,0x00,0x60,0x00,0x40,0x00,0xC0,0x00,0x80,0x00,0x80,0x01,0x00,0x01,0x00,0x01,0x00,0x03,0x00,0x02,0x00,0x00},/*"\",60*/
	{0x00,0x00,0x00,0x00,0x7E,0x00,0x7E,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00},/*"]",61*/
	{0x00,0x00,0x00,0x00,0xF0,0x00,0xD8,0x01,0x8C,0x01,0x06,0x02,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,0x00,0x00,0x00,0x00},/*"^",62*/
	{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,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x0F},/*"_",63*/
	{0x00,0x00,0x00,0x00,0x30,0x00,0x60,0x00,0xC0,0x00,0xC0,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,0x00,0x00,0x00,0x00,0x00},/*"`",64*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x01,0xFC,0x03,0x0C,0x03,0x00,0x03,0xF0,0x03,0x7C,0x03,0x0E,0x03,0x06,0x03,0x86,0x03,0xFC,0x03,0x38,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"a",65*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0xF6,0x01,0xDE,0x03,0x0E,0x03,0x06,0x03,0x06,0x07,0x06,0x06,0x06,0x07,0x06,0x03,0x8E,0x03,0xFE,0x01,0xE6,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"b",66*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x01,0xFC,0x03,0x0E,0x03,0x06,0x03,0x06,0x00,0x06,0x00,0x06,0x06,0x06,0x07,0x0C,0x03,0xF8,0x01,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"c",67*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0x00,0x03,0xF8,0x03,0xDC,0x03,0x0E,0x03,0x06,0x03,0x06,0x03,0x06,0x03,0x06,0x03,0x06,0x03,0x8C,0x03,0xFC,0x03,0x70,0x03,0x00,0x00,0x00,0x00,0x00,0x00},/*"d",68*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x01,0x9C,0x03,0x0C,0x03,0x06,0x03,0xFE,0x07,0x06,0x00,0x06,0x00,0x06,0x07,0x0C,0x03,0xF8,0x01,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"e",69*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x03,0xE0,0x07,0x30,0x04,0x30,0x00,0x30,0x00,0x30,0x00,0xFE,0x03,0xFE,0x03,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"f",70*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x07,0xDC,0x01,0x8C,0x01,0x8C,0x01,0x8C,0x01,0xDC,0x01,0xFC,0x00,0x06,0x00,0x7C,0x00,0xF8,0x03,0x06,0x06,0x06,0x06,0x0E,0x03,0xFC,0x01},/*"g",71*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0xE6,0x01,0xB6,0x03,0x0E,0x03,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"h",72*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"i",73*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x01,0x80,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0x80,0x01,0xFC,0x00,0x7C,0x00},/*"j",74*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x06,0x00,0x86,0x03,0xC6,0x01,0xE6,0x00,0x76,0x00,0x7E,0x00,0xDE,0x00,0xCE,0x00,0x86,0x01,0x06,0x03,0x06,0x03,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"k",75*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"l",76*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3B,0x07,0xFF,0x07,0x67,0x04,0x63,0x04,0x63,0x04,0x63,0x04,0x63,0x04,0x63,0x04,0x63,0x04,0x63,0x04,0x63,0x04,0x00,0x00,0x00,0x00,0x00,0x00},/*"m",77*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0x01,0xB6,0x03,0x0E,0x03,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"n",78*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0xFC,0x03,0x0E,0x03,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x8C,0x03,0xF8,0x01,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"o",79*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF6,0x00,0xFE,0x03,0x0E,0x03,0x06,0x03,0x06,0x07,0x06,0x06,0x06,0x07,0x06,0x03,0x8E,0x03,0xFE,0x01,0xE6,0x00,0x06,0x00,0x06,0x00,0x06,0x00},/*"p",80*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x03,0xFC,0x03,0x0E,0x03,0x06,0x03,0x06,0x03,0x06,0x03,0x06,0x03,0x06,0x03,0x8C,0x03,0xFC,0x03,0x70,0x03,0x00,0x03,0x00,0x03,0x00,0x03},/*"q",81*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x8C,0x01,0xEC,0x01,0x3C,0x00,0x1C,0x00,0x1C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"r",82*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x01,0x9C,0x03,0x0C,0x03,0x0C,0x00,0x7C,0x00,0xF0,0x01,0x00,0x03,0x04,0x03,0x0C,0x03,0xFC,0x03,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"s",83*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0xFE,0x03,0xFE,0x03,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x30,0x00,0xE0,0x03,0xC0,0x03,0x00,0x00,0x00,0x00,0x00,0x00},/*"t",84*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x07,0x06,0x07,0x8E,0x07,0xFC,0x06,0x38,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"u",85*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x03,0x0C,0x03,0x0C,0x03,0x8C,0x01,0x98,0x01,0x98,0x00,0xD0,0x00,0xF0,0x00,0x70,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"v",86*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x06,0x63,0x06,0x72,0x06,0x76,0x06,0xD6,0x06,0xD6,0x02,0x94,0x03,0x9C,0x03,0x9C,0x03,0x8C,0x01,0x8C,0x01,0x00,0x00,0x00,0x00,0x00,0x00},/*"w",87*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x03,0x8C,0x01,0x98,0x01,0xF0,0x00,0x70,0x00,0x70,0x00,0xF0,0x00,0xD8,0x01,0x8C,0x01,0x0C,0x03,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00},/*"x",88*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x03,0x0C,0x03,0x0C,0x03,0x8C,0x01,0x98,0x01,0x98,0x00,0xF0,0x00,0xF0,0x00,0x70,0x00,0x60,0x00,0x60,0x00,0x3C,0x00,0x1C,0x00},/*"y",89*/
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0x03,0xFC,0x03,0x80,0x01,0xC0,0x00,0x60,0x00,0x70,0x00,0x38,0x00,0x18,0x00,0x0C,0x00,0xFE,0x03,0xFE,0x03,0x00,0x00,0x00,0x00,0x00,0x00},/*"z",90*/
	{0x00,0x00,0x00,0x00,0x80,0x03,0x80,0x01,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xC0,0x00,0xE0,0x00,0xC0,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x01,0x80,0x03,0x00,0x00},/*"{",91*/
	{0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00,0x60,0x00},/*"|",92*/
	{0x00,0x00,0x00,0x00,0x0E,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x30,0x00,0x30,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x18,0x00,0x0E,0x00,0x00,0x00},/*"}",93*/
	{0x00,0x00,0x38,0x04,0x7C,0x06,0xC6,0x03,0x80,0x01,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,0x00,0x00,0x00,0x00,0x00,0x00} /*"~",94*/
};

显示中英文

  • 使用取模软件 PCtoLCD2002

程序里的16号字体就是红色框那修改成 16x16 即可,24号也是

  • MX配置

在介绍那

  • 程序编写

在上面基础上添加

TFT_LCD.h
//中文字体
typedef enum
{
  CHN_font_16 = 16,
  CHN_font_24 = 24,
} CHN_font_t;
#define IS_CHN_font(font)   (((font) == CHN_font_16) || ((font) == CHN_font_24))

//定义结构体类型
typedef struct
{
	uint32_t ID; //屏幕ID
	
	void (*Init)(void);                                                 //LCD屏幕初始化
	void (*FillColor)(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t); //LCD屏幕填充颜色
	void (*LCD_ShowChar)(uint16_t, uint16_t, const char, uint16_t, uint16_t,ASCII_font_t);         //在LCD屏幕上显示一个英文字符
  void (*LCD_ShowString)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,ASCII_font_t);     //在LCD屏幕上显示英文字符串	
	void (*LCD_ShowCHN)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,CHN_font_t);          //在LCD屏幕上显示一个中文字符
	void (*LCD_ShowCHNandENGstring)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,CHN_font_t,ASCII_font_t); //在LCD屏幕上显示中英文字符串
} TFT_LCD_t;
TFT_LCD.c
/*
	* @name   LCD_ShowCHN
	* @brief  在LCD屏幕上显示1个中文字符
	* @param  usX: 起始X坐标
*           usY :起始Y坐标
*           pStr:要显示的英文字符串的首地址
*           usColor_Background :选择英文字符的背景色
*           usColor_Foreground :选择英文字符的前景色
*           font:字体选择
*             参数:CHN_font_16 :16号字体
*                   CHN_font_24 :24号字体
	* @retval None
*/
static void LCD_ShowCHN(uint16_t usX, uint16_t usY, const char *pStr, uint16_t usColor_Background, uint16_t usColor_Foreground, CHN_font_t font)
{
    uint8_t ucTemp, ucPage, ucColumn;
    uint16_t usIndex; //字库中的汉字索引
    uint16_t CHN_Num; //字库中的汉字数量

    //检查输入参数是否合法
    assert_param(IS_ASCII_font(font));
    //判断字体 - 16号字体
    if(font == CHN_font_16)
    {
        //统计汉字数量
        CHN_Num = sizeof(FONT_CHN16) / sizeof(FONT_CHN16_t);
        //for循环查找汉字位置
        for(usIndex = 0; usIndex < CHN_Num; usIndex++)
        {
            // 一个汉字占两个字节
            if((FONT_CHN16[usIndex].Index[0] == *pStr) && (FONT_CHN16[usIndex].Index[1] == *(pStr + 1)))
            {
                //设置窗口,大小为16x16
                LCD_SetWindows(usX, usY, 16, 16);
                //逐行写入数据,共16行,每行16个像素点
                for(ucPage = 0; ucPage < 32; ucPage++)
                {
                    //从ASCII字符集数组获取像素数据
                    //像素点数据为1时,写入字符颜色,为0时,写入背景颜色
                    ucTemp = FONT_CHN16[usIndex].CHN_code[ucPage];
                    for(ucColumn = 0; ucColumn < 8; ucColumn++)
                    {
                        if((ucTemp & 1) == 1)
                            LCD_Write_DATA(usColor_Foreground);
                        else
                            LCD_Write_DATA(usColor_Background);
                        ucTemp >>= 1;
                    }
                }

                break; //已找到并显示了汉字,退出循环
            }
        }
    }

    //判断字体 - 24号字体
    if(font == CHN_font_24)
    {
        //统计汉字数量
        CHN_Num = sizeof(FONT_CHN24) / sizeof(FONT_CHN24_t);
        //for循环查找汉字位置
        for(usIndex = 0; usIndex < CHN_Num; usIndex++)
        {
            if((FONT_CHN24[usIndex].Index[0] == *pStr) && (FONT_CHN24[usIndex].Index[1] == *(pStr + 1)))
            {
                //设置窗口,大小为24x24
                LCD_SetWindows(usX, usY, 24, 24);
                //逐行写入数据,共24行,每行24个像素点
                for(ucPage = 0; ucPage < 72; ucPage++)
                {
                    //从ASCII字符集数组获取像素数据
                    //像素点数据为1时,写入字符颜色,为0时,写入背景颜色
                    ucTemp = FONT_CHN24[usIndex].CHN_code[ucPage];
                    for(ucColumn = 0; ucColumn < 8; ucColumn++)
                    {
                        if((ucTemp & BIT0) == BIT0)
                            LCD_Write_DATA(usColor_Foreground);
                        else
                            LCD_Write_DATA(usColor_Background);
                        ucTemp >>= 1;
                    }
                }

                break; //已找到并显示了汉字,退出循环
            }
        }
    }
}

/*
	* @name   LCD_ShowCHNandENGstring
	* @brief  在LCD屏幕上显示中英文字符串
	* @param  usX: 起始X坐标
*           usY :起始Y坐标
*           pStr:要显示的中英文字符串的首地址
*           usColor_Background :选择字符的背景色
*           usColor_Foreground :选择字符的前景色
*           font_CHN:  中文字体选择
*             参数:CHN_font_16 :16号字体
*                   CHN_font_24 :24号字体
*           font_ASCII:ASCII码字体选择
*             参数:ASCII_font_16 :16号字体
*                   ASCII_font_24 :24号字体
	* @retval None
*/
static void LCD_ShowCHNandENGstring(uint16_t usX, uint16_t usY, const char *pStr, uint16_t usColor_Background, uint16_t usColor_Foreground, CHN_font_t font_CHN, ASCII_font_t font_ASCII)
{
    while (* pStr != '\0')
    {
        //中文字符
        if((* pStr) > 127)
        {
            //自动换行
            if ((usX + font_CHN) > LCD_WIDTH)
            {
                usX = 0;
                usY += font_CHN;
            }
            //自动换页
            if ((usY + font_CHN) > LCD_HEIGTH)
            {
                usX = 0;
                usY = 0;
            }
            //显示中文字符
            TFT_LCD.LCD_ShowCHN(usX, usY, pStr, usColor_Background, usColor_Foreground, font_CHN);
            //更新位置
            pStr += 2;
            usX += font_CHN;
        }
        //英文字符
        else
        {
            // 看前面字体是啥字体避免换行重叠
            if((* pStr == '\r') | (* pStr == '\n'))
            {
                //前面的字符为中文
                if((* (pStr - 1)) > 127)
                {
                    //换行
                    usX = 0;
                    usY += font_CHN;
                }
                //前面的字符为英文
                else
                {
                    //换行
                    usX = 0;
                    usY += font_ASCII;
                }
            }
            else
            {
                //自动换行
                if ((usX + font_ASCII / 2) > LCD_WIDTH)
                {
                    usX = 0;
                    usY += font_ASCII;
                }
                //自动换页(回到左上角起点写)
                if ((usY + font_ASCII) > LCD_HEIGTH)
                {
                    usX = 0;
                    usY = 0;
                }
                //显示字符
                TFT_LCD.LCD_ShowChar(usX, usY, * pStr, usColor_Background, usColor_Foreground, font_ASCII);
                //更新位置
                usX += font_ASCII / 2;
            }
            //指向下一个字符
            pStr ++;
        }
    }
}
font_CHN.h
//16号中文字体
//大小:16*16
//字体:Default(宋体)
//逐行式(从第一行开始向右每取8个点作为一个字节,不足8个点就补0)
//逆向(取模顺序从低到高,即第一个点作为最低位)
typedef struct
{
	uint8_t Index[2];
	uint8_t CHN_code[32];
} FONT_CHN16_t;

const FONT_CHN16_t FONT_CHN16[] = 
{
	{{"硬"},{0x00,0x00,0x80,0x7F,0x3F,0x04,0x08,0x04,0x88,0x3F,0x84,0x24,0xBC,0x24,0xA6,0x3F,0xA6,0x24,0xA5,0x24,0xA4,0x3F,0xA4,0x04,0x3C,0x05,0x24,0x02,0x04,0x0D,0xC0,0x70}},/*"硬",0*/
	{{"件"},{0x10,0x04,0x10,0x04,0x90,0x04,0x88,0x04,0x88,0x3F,0x4C,0x04,0x4C,0x04,0x2A,0x04,0x09,0x04,0xE8,0x7F,0x08,0x04,0x08,0x04,0x08,0x04,0x08,0x04,0x08,0x04,0x08,0x04}},/*"件",1*/
	{{"家"},{0x40,0x00,0x80,0x00,0xFE,0x7F,0x02,0x40,0x01,0x20,0xFE,0x3F,0x40,0x00,0xB0,0x10,0x8E,0x09,0x40,0x05,0x30,0x03,0x8E,0x05,0x60,0x19,0x18,0x61,0x47,0x01,0x80,0x00}},/*"家",2*/
	{{"园"},{0x00,0x00,0xFE,0x3F,0x02,0x20,0xF2,0x27,0x02,0x20,0x02,0x20,0xFA,0x2F,0x22,0x21,0x22,0x21,0x22,0x21,0x12,0x29,0x12,0x29,0x0A,0x2E,0x02,0x20,0xFE,0x3F,0x02,0x20}},/*"园",3*/
	{{"单"},{0x08,0x08,0x10,0x04,0x20,0x02,0xFC,0x1F,0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x10,0x84,0x10,0xFC,0x1F,0x80,0x00,0x80,0x00,0xFF,0x7F,0x80,0x00,0x80,0x00,0x80,0x00}},/*"单",4*/
	{{"片"},{0x00,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0xF8,0x3F,0x08,0x00,0x08,0x00,0x08,0x00,0xF8,0x07,0x08,0x04,0x08,0x04,0x08,0x04,0x04,0x04,0x04,0x04,0x02,0x04}},/*"片",5*/
	{{"机"},{0x08,0x00,0x88,0x0F,0x88,0x08,0x88,0x08,0xBF,0x08,0x88,0x08,0x8C,0x08,0x9C,0x08,0xAA,0x08,0xAA,0x08,0x89,0x08,0x88,0x48,0x88,0x48,0x48,0x48,0x48,0x70,0x28,0x00}},/*"机",6*/
	{{"实"},{0x40,0x00,0x80,0x00,0xFE,0x7F,0x02,0x40,0x11,0x21,0x20,0x01,0x20,0x01,0x08,0x01,0x10,0x01,0x10,0x01,0xFF,0x7F,0x80,0x02,0x40,0x04,0x20,0x08,0x18,0x10,0x06,0x20}},/*"实",7*/
	{{"战"},{0x08,0x04,0x08,0x14,0x08,0x24,0x78,0x24,0x08,0x04,0x08,0x7C,0x88,0x07,0x08,0x24,0x7E,0x24,0x42,0x14,0x42,0x14,0x42,0x08,0x42,0x4C,0x7E,0x52,0x42,0x61,0x80,0x40}},/*"战",8*/
	{{"项"},{0x00,0x00,0x80,0x7F,0x00,0x04,0x3F,0x02,0x88,0x3F,0x88,0x20,0x88,0x24,0x88,0x24,0x88,0x24,0x88,0x24,0x88,0x24,0xB8,0x22,0x07,0x0A,0x02,0x11,0x80,0x20,0x40,0x40}},/*"项",9*/
	{{"目"},{0x00,0x00,0xFC,0x1F,0x04,0x10,0x04,0x10,0x04,0x10,0xFC,0x1F,0x04,0x10,0x04,0x10,0x04,0x10,0xFC,0x1F,0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0xFC,0x1F,0x04,0x10}},/*"目",10*/
	{{"教"},{0x10,0x04,0x10,0x04,0x7E,0x05,0x90,0x7C,0x50,0x22,0xFF,0x22,0x10,0x22,0x7E,0x25,0x24,0x14,0x12,0x14,0xF1,0x08,0x1E,0x08,0x10,0x14,0x10,0x12,0x14,0x21,0x88,0x40}},/*"教",11*/
	{{"学"},{0x44,0x10,0x88,0x10,0x88,0x08,0x00,0x04,0xFE,0x7F,0x02,0x40,0x01,0x20,0xF8,0x07,0x00,0x02,0x80,0x01,0xFF,0x7F,0x80,0x00,0x80,0x00,0x80,0x00,0xA0,0x00,0x40,0x00}},/*"学",12*/
	{{"平"},{0x00,0x00,0xFE,0x3F,0x80,0x00,0x80,0x00,0x88,0x08,0x90,0x08,0x90,0x04,0x80,0x00,0xFF,0x7F,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}},/*"平",13*/
	{{"台"},{0x40,0x00,0x40,0x00,0x20,0x00,0x10,0x04,0x08,0x08,0x04,0x10,0xFE,0x3F,0x04,0x20,0x00,0x00,0xF8,0x0F,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0xF8,0x0F,0x08,0x08}} /*"台",14*/
};


//24号中文字体
//大小:24*24
//字体:Default(宋体)
//逐行式(从第一行开始向右每取8个点作为一个字节,不足8个点就补0)
//逆向(取模顺序从低到高,即第一个点作为最低位)
typedef struct
{
	uint8_t Index[2];
	uint8_t CHN_code[72];
} FONT_CHN24_t;

const FONT_CHN24_t FONT_CHN24[] = 
{
	{{"硬"},{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x20,0xFE,0xFF,0x3F,0x20,0x00,0x01,0x20,0x00,0x01,0x30,0xF8,0x3F,0x10,0x08,0x11,0x10,0x08,0x11,0xF8,0xFB,0x1F,0x18,0x09,
		       0x11,0x18,0x09,0x11,0x14,0x89,0x11,0x12,0xF9,0x1F,0x10,0x89,0x11,0x10,0x89,0x00,0x10,0x91,0x00,0xF0,0xE1,0x00,0x10,0x61,0x00,0x10,0xA0,0x01,0x10,0x18,0x07,0x00,
	         0x06,0x3C,0x80,0x01,0x00,0x00,0x00,0x00}},/*"硬",0*/
	{{"件"},{0x00,0x00,0x00,0x80,0x80,0x00,0xC0,0x81,0x00,0xC0,0x80,0x00,0x40,0x8C,0x00,0x60,0x8C,0x00,0x20,0x84,0x00,0x30,0xFC,0x1F,0x30,0x84,0x00,0x28,0x82,0x00,0x28,0x82,
		       0x00,0x24,0x81,0x00,0x22,0x81,0x00,0x20,0xFF,0x3F,0x20,0x80,0x00,0x20,0x80,0x00,0x20,0x80,0x00,0x20,0x80,0x00,0x20,0x80,0x00,0x20,0x80,0x00,0x20,0x80,0x00,0x20,
	         0x80,0x00,0x20,0x80,0x00,0x00,0x00,0x00}},/*"件",1*/
	{{"家"},{0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x18,0x00,0x10,0x10,0x00,0xF0,0xFF,0x3F,0x18,0x00,0x10,0x0C,0x00,0x08,0xE0,0xFF,0x03,0x00,0x04,0x00,0x00,0x06,0x00,0x00,0x09,
		       0x06,0xC0,0x98,0x01,0x30,0xDC,0x00,0x0C,0xA2,0x00,0x80,0x31,0x01,0x40,0x38,0x01,0x30,0x6C,0x02,0x0C,0x66,0x06,0x00,0x61,0x1C,0xC0,0x60,0x38,0x38,0x20,0x00,0x06,
	         0x3C,0x00,0x00,0x18,0x00,0x00,0x00,0x00}},/*"家",2*/
	{{"园"},{0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0xFF,0x1F,0x08,0x00,0x10,0x08,0x80,0x10,0x88,0xFF,0x11,0x08,0x00,0x10,0x08,0x00,0x10,0x08,0x00,0x12,0xE8,0xFF,0x17,0x08,0x22,
		       0x10,0x08,0x22,0x10,0x08,0x22,0x10,0x08,0x22,0x10,0x08,0x23,0x10,0x08,0x21,0x14,0x08,0x21,0x14,0x88,0x20,0x16,0x48,0xE0,0x17,0x28,0x00,0x10,0xF8,0xFF,0x1F,0x08,
	         0x00,0x10,0x08,0x00,0x10,0x00,0x00,0x00}},/*"园",3*/
	{{"单"},{0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x81,0x01,0x00,0xC3,0x00,0x00,0x42,0x00,0xE0,0xFF,0x07,0x20,0x08,0x04,0x20,0x08,0x04,0x20,0x08,0x04,0xE0,0xFF,0x07,0x20,0x08,
		       0x04,0x20,0x08,0x04,0x20,0x08,0x04,0xE0,0xFF,0x07,0x20,0x08,0x04,0x10,0x08,0x10,0xFE,0xFF,0x3F,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,
	         0x08,0x00,0x00,0x08,0x00,0x00,0x00,0x00}},/*"单",4*/
	{{"片"},{0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0xC0,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x10,0xC0,0xFF,0x3F,0x40,0x00,0x00,0x40,0x00,
		       0x00,0x40,0x00,0x00,0xC0,0xFF,0x01,0x40,0x80,0x01,0x40,0x80,0x01,0x60,0x80,0x01,0x20,0x80,0x01,0x20,0x80,0x01,0x10,0x80,0x01,0x10,0x80,0x01,0x08,0x80,0x01,0x04,
	         0x80,0x01,0x00,0x80,0x00,0x00,0x00,0x00}},/*"片",5*/
	{{"机"},{0x00,0x00,0x00,0x20,0x00,0x00,0x60,0x00,0x00,0x20,0xF8,0x07,0x20,0x18,0x02,0x20,0x18,0x02,0x20,0x18,0x02,0xFE,0x1B,0x02,0x20,0x18,0x02,0x20,0x18,0x02,0x70,0x18,
		       0x02,0xB0,0x19,0x02,0x30,0x1B,0x02,0x30,0x1A,0x02,0x28,0x18,0x02,0x28,0x18,0x02,0x24,0x08,0x02,0x24,0x08,0x02,0x22,0x0C,0x22,0x20,0x04,0x22,0x60,0x06,0x22,0x60,
	         0x02,0x66,0x60,0x01,0x3E,0xA0,0x00,0x00}},/*"机",6*/
	{{"实"},{0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x18,0x00,0x00,0x30,0x00,0x00,0x10,0x00,0xF0,0xFF,0x3F,0x08,0x00,0x30,0x88,0x20,0x08,0x0C,0x63,0x00,0x00,0x26,0x00,0x00,0x22,
		       0x00,0x60,0x20,0x00,0xC0,0x20,0x00,0x80,0x30,0x00,0x80,0x30,0x10,0xFC,0xFF,0x3F,0x00,0x10,0x00,0x00,0x38,0x00,0x00,0xC8,0x00,0x00,0x04,0x07,0x00,0x03,0x0C,0xE0,
	         0x00,0x18,0x1C,0x00,0x10,0x00,0x00,0x00}},/*"实",7*/
	{{"战"},{0x00,0x00,0x00,0x00,0x40,0x00,0xC0,0xC0,0x00,0x40,0x40,0x0E,0x40,0x40,0x18,0x40,0x40,0x18,0xC0,0x4F,0x00,0x40,0x40,0x30,0x40,0xF0,0x0F,0x40,0xCC,0x00,0x40,0xC0,
		       0x10,0x40,0xC0,0x18,0xFC,0x87,0x08,0x04,0x84,0x0C,0x04,0x84,0x04,0x04,0x84,0x07,0x04,0x84,0x03,0x04,0x04,0x23,0x04,0x84,0x23,0xFC,0x47,0x26,0x04,0x34,0x2C,0x04,
	         0x08,0x38,0x00,0x06,0x20,0x00,0x00,0x00}},/*"战",8*/
	{{"项"},{0x00,0x00,0x00,0x00,0x00,0x10,0x00,0xFE,0x3F,0x00,0x81,0x00,0xFE,0x83,0x00,0x20,0x40,0x00,0x20,0xF8,0x1F,0x20,0x08,0x10,0x20,0x08,0x10,0x20,0x88,0x11,0x20,0x88,
		       0x11,0x20,0x88,0x11,0x20,0x88,0x11,0x20,0x88,0x11,0x20,0x8B,0x10,0xE0,0x88,0x10,0x1C,0x88,0x11,0x04,0xC0,0x02,0x00,0x40,0x0C,0x00,0x60,0x18,0x00,0x30,0x30,0x00,
	         0x0C,0x20,0x00,0x03,0x20,0x00,0x00,0x00}},/*"项",9*/
	{{"目"},{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xC0,0xFF,0x07,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0xC0,0xFF,0x03,0x40,0x00,0x02,0x40,0x00,
		       0x02,0x40,0x00,0x02,0x40,0x00,0x02,0xC0,0xFF,0x03,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0xC0,0xFF,0x03,0x40,0x00,0x02,0x40,
	         0x00,0x06,0x00,0x00,0x02,0x00,0x00,0x00}},/*"目",10*/
	{{"教"},{0x00,0x00,0x00,0x40,0x00,0x01,0xC0,0x00,0x01,0xC0,0x00,0x01,0xC0,0x98,0x01,0xF8,0x8B,0x00,0xC0,0x8C,0x3F,0xC0,0x94,0x18,0xFE,0x7F,0x08,0x00,0xC3,0x08,0x80,0xA1,
		       0x08,0xF8,0xAF,0x08,0x40,0x96,0x0C,0x30,0x01,0x05,0x88,0x01,0x05,0x84,0x19,0x07,0x80,0x07,0x03,0xFC,0x01,0x03,0x80,0x01,0x07,0x80,0xC1,0x0C,0x80,0x21,0x18,0xE0,
	         0x18,0x70,0xC0,0x04,0x00,0x00,0x00,0x00}},/*"教",11*/
	{{"学"},{0x00,0x00,0x00,0x00,0x04,0x02,0x20,0x0C,0x06,0x40,0x18,0x03,0xC0,0x18,0x01,0x80,0x18,0x01,0x80,0x80,0x00,0xF8,0xFF,0x3F,0x08,0x00,0x30,0x08,0x00,0x08,0xEC,0xFF,
		       0x03,0x00,0x80,0x01,0x00,0x40,0x00,0x00,0x20,0x00,0x00,0x10,0x10,0xFE,0xFF,0x3F,0x00,0x10,0x00,0x00,0x10,0x00,0x00,0x10,0x00,0x00,0x10,0x00,0x00,0x19,0x00,0x00,
	         0x1E,0x00,0x00,0x08,0x00,0x00,0x00,0x00}},/*"学",12*/
	{{"平"},{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xF8,0xFF,0x1F,0x00,0x18,0x00,0x00,0x18,0x04,0x20,0x18,0x06,0xC0,0x18,0x02,0x80,0x18,0x01,0x80,0x98,0x00,0x00,0x58,
		       0x10,0xFE,0xFF,0x3F,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00,
	         0x18,0x00,0x00,0x08,0x00,0x00,0x00,0x00}},/*"平",13*/
	{{"台"},{0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x1C,0x00,0x00,0x06,0x00,0x00,0x02,0x00,0x00,0x01,0x00,0x80,0x00,0x01,0x40,0x00,0x06,0x20,0x00,0x0C,0x18,0xF0,0x1F,0xF8,0x0F,
		       0x18,0x10,0x00,0x10,0x00,0x00,0x00,0xC0,0xFF,0x07,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0x40,0x00,0x02,0xC0,0xFF,0x03,0x40,
	         0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x00}} /*"台",14*/
};
main.c
static void Run()
{
    //文字显示在中间位置
    TFT_LCD.LCD_ShowCHNandENGstring(92, 64, "Happy Day\n        硬件家园", Color_GRAY, Color_RED, CHN_font_24, ASCII_font_24);
    TFT_LCD.LCD_ShowCHNandENGstring(72, 120, "单片机实战项目教学", Color_GRAY, Color_BLUE, CHN_font_16, ASCII_font_24);
    while(1);
}

显示图片

  • 使用取模软件 Img2Lcd(PCtoLCD2002的话也可以但是只有黑白)

注意像素要匹配,生成的图片大概占150KB

  • MX配置

看介绍

  • 程序编写
TFT_LCD.h
//定义结构体类型
typedef struct
{
	uint32_t ID; //屏幕ID
	
	void (*Init)(void);                                                 //LCD屏幕初始化
	void (*FillColor)(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t); //LCD屏幕填充颜色
	void (*LCD_ShowChar)(uint16_t, uint16_t, const char, uint16_t, uint16_t,ASCII_font_t);         //在LCD屏幕上显示一个英文字符
  void (*LCD_ShowString)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,ASCII_font_t);     //在LCD屏幕上显示英文字符串	
	void (*LCD_ShowCHN)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,CHN_font_t);          //在LCD屏幕上显示一个中文字符
	void (*LCD_ShowCHNandENGstring)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,CHN_font_t,ASCII_font_t); //在LCD屏幕上显示中英文字符串
	void (*LCD_ShowPicture)(uint16_t, uint16_t, uint16_t, uint16_t,uint8_t);                       //在LCD屏幕上显示图片
} TFT_LCD_t;

这里的话需要注意const的用法

注意一下:Pic_H * Pic_V * 2 刚刚好等于 153600,为什么需要 *2,因为一个像素2个字节,ulIndex += 2 表示每次写2个字节

LCD_Write_DATA((pic[ulIndex] << 8) | pic[ulIndex + 1]) 因为在取模那我们勾了 高位在前,所以需要把高位左移8位再或上低位构成一个16位数据然后传入

TFT_LCD.c
#include "AllHead.h"
// 注意这个图片头文件不能丢总头文件里因为它不是声明是定义,否则会报错重复定义
#include "Pic1.h"

static void LCD_ShowPicture(uint16_t, uint16_t, uint16_t, uint16_t,uint8_t);

/*
	* @name   LCD_ShowPicture
	* @brief  在LCD屏幕上显示图片
	* @param  usX    :起始X坐标
*           usY    :起始Y坐标
*           Pic_H  :图片水平分辨率
*           Pic_V  :图片垂直分辨率
*           Pic_num:图片序号

	* @retval None
*/
static void LCD_ShowPicture(uint16_t usX, uint16_t usY, uint16_t Pic_H, uint16_t Pic_V, uint8_t Pic_num)
{
    uint32_t ulIndex;
    const uint8_t *pic = NULL;

    //设置窗口,大小为Pic_HxPic_V
    LCD_SetWindows(usX, usY, Pic_H, Pic_V);
    //获取图像数据首地址
    switch(Pic_num)
    {
    case 1:
        pic = gImage_Pic1;
        break;
    case 2:
        pic = gImage_Pic2;
        break;
    default:
        pic = gImage_Pic1;
    }

    //逐行写入图片数据
    for(ulIndex = 0; ulIndex < Pic_H * Pic_V * 2; ulIndex += 2)
    {
        LCD_Write_DATA((pic[ulIndex] << 8) | pic[ulIndex + 1]);
    }
}
main.c
static void Run()
{	
	//间隔1s循环显示2张图片
	TFT_LCD.LCD_ShowPicture(0,0,320,240,1);
	HAL_Delay(1000);
	TFT_LCD.LCD_ShowPicture(0,0,320,240,2);
	HAL_Delay(1000);
}
Pic1.h
const uint8_t gImage_Pic1[153600] = 
{
    // 太长省略了...
}

画矩形/圆形

  • MX配置

看介绍

  • 程序编写
TFT_LCD.h
//画图填充
typedef enum
{
  Filled   = 1,
  unFilled = 0,
} Filled_t;
#define IS_Filled(ucFilled)   (((ucFilled) == Filled) || ((ucFilled) == Unfilled))

//定义结构体类型
typedef struct
{
	uint32_t ID; //屏幕ID
	
	void (*Init)(void);                                                 //LCD屏幕初始化
	void (*FillColor)(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t); //LCD屏幕填充颜色
	//Show
	void (*LCD_ShowChar)(uint16_t, uint16_t, const char, uint16_t, uint16_t,ASCII_font_t);         //在LCD屏幕上显示一个英文字符
  void (*LCD_ShowString)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,ASCII_font_t);     //在LCD屏幕上显示英文字符串	
	void (*LCD_ShowCHN)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,CHN_font_t);          //在LCD屏幕上显示一个中文字符
	void (*LCD_ShowCHNandENGstring)(uint16_t, uint16_t, const char *, uint16_t, uint16_t,CHN_font_t,ASCII_font_t); //在LCD屏幕上显示中英文字符串
	void (*LCD_ShowPicture)(uint16_t, uint16_t, uint16_t, uint16_t,uint8_t);                       //在LCD屏幕上显示图片
	//Shape
	uint16_t (*LCD_GetPointPiexl)(uint16_t,uint16_t);                                    //获取像素点
	void     (*LCD_SetPointPiexl)(uint16_t,uint16_t,LCD_Color_t);                        //设置像素点
	void (*LCD_DrawLine)(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t);               //画直线
	void (*LCD_DrawRectangle)(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t,Filled_t); //画矩形
	void (*LCD_DrawCircle)(uint16_t,uint16_t,uint16_t,LCD_Color_t,Filled_t);             //画圆形
} TFT_LCD_t;
TFT_LCD.c
static uint16_t LCD_GetPointPiexl(uint16_t,uint16_t);                                    //获取像素点
static void     LCD_SetPointPiexl(uint16_t,uint16_t,LCD_Color_t);                        //设置像素点
static void LCD_DrawLine(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t);               //画直线
static void LCD_DrawRectangle(uint16_t,uint16_t,uint16_t,uint16_t,LCD_Color_t,Filled_t); //画矩形
static void LCD_DrawCircle(uint16_t,uint16_t,uint16_t,LCD_Color_t,Filled_t);             //画圆形



/*
	* @name   LCD_GetPointPiexl
	* @brief  获取像素点
	* @param  usX    :X坐标
*           usY    :Y坐标

  * @retval usColor :色彩数据,RGB565格式      
*/
static uint16_t LCD_GetPointPiexl(uint16_t usX, uint16_t usY)  
{
	uint16_t usR=0, usG=0, usB=0;
	uint16_t usColor = 0; //颜色,数据格式RGB565
	
	//判断坐标是否有效
	if((usX < LCD_WIDTH) && (usY < LCD_HEIGTH))
	{
		//设置窗口,大小为1x1
		LCD_SetWindows(usX,usY,1,1);
		//写入读GRAM命令
		LCD_Write_CMD(LCD_CMD_RDgram);
		//开始读数据
		usR = LCD_Read_DATA();  //读Dummy数据,舍弃	
		usR = LCD_Read_DATA();  //读RED数据 5 
		usB = LCD_Read_DATA();  //读BLUE数据 5
		usG = LCD_Read_DATA();  //读GREEN数据	6	
		//数据转化为RGB565
        //usR读出来可能是1101 0001 0000 0000 -> 
        //0000 0000 0001 1010 -> 1101 0000 0000 0000
		usColor = (((usR>>11)<<11) | ((usG>>10)<<5) | (usB>>11));
		printf("The Pixel color is 0x%.4X\r\n",usColor);
	}
	//返回颜色数据
	return	usColor;
}	

/*
	* @name   LCD_SetPointPiexl
	* @brief  设置像素点
	* @param  usX     :X坐标
*           usY     :Y坐标
*           usColor :颜色
  * @retval none
*/
static void  LCD_SetPointPiexl(uint16_t usX, uint16_t usY, LCD_Color_t usColor)
{
    //判断坐标是否有效
    if((usX < LCD_WIDTH) && (usY < LCD_HEIGTH))
    {
        //设置窗口,大小为1x1
        LCD_SetWindows(usX, usY, 1, 1);
        //写入颜色数据
        LCD_Write_DATA(usColor);
    }
}

/*
	* @name   LCD_DrawLine
	* @brief  画直线
	* @param  usX_Start :X起点坐标
*           usY_Start :Y起点坐标
*           usX_End   :X终点坐标
*           usY_End   :Y终点坐标
*           usColor   :颜色
  * @retval none
*/
static void LCD_DrawLine(uint16_t usX_Start, uint16_t usY_Start, uint16_t usX_End, uint16_t usY_End, LCD_Color_t usColor)
{
    uint16_t i;
    //uint16_t k;
    uint16_t usX_Current, usY_Current;
    int16_t sError_X = 0, sError_Y = 0, sDelta_X, sDelta_Y, sDistance;
    int16_t sIncrease_X, sIncrease_Y;

    /* 计算坐标增量 */
    sDelta_X = usX_End - usX_Start;
    sDelta_Y = usY_End - usY_Start;
    usX_Current = usX_Start;
    usY_Current = usY_Start;

    if(sDelta_X > 0)
    {
        /* 设置单步方向 */
        sIncrease_X = 1;
    }
    else if(sDelta_X == 0)
    {
        /* 垂直线 */
        sIncrease_X = 0;
    }
    else
    {
        sIncrease_X = -1;
        sDelta_X    = -sDelta_X;
    }

    if(sDelta_Y > 0)
    {
        sIncrease_Y = 1;
    }
    else if(sDelta_Y == 0)
    {
        /* 水平线  */
        sIncrease_Y = 0;
    }
    else
    {
        sIncrease_Y = -1;
        sDelta_Y    = -sDelta_Y;
    }

    if(sDelta_X > sDelta_Y)
    {
        /* 选取基本增量坐标轴 */
        sDistance = sDelta_X;
    }
    else
    {
        sDistance = sDelta_Y;
    }

    //画线输出
    for(i = 0; i <= sDistance + 1; i++)
    {
        //画点
        TFT_LCD.LCD_SetPointPiexl(usX_Current, usY_Current, usColor);
        sError_X += sDelta_X;
        sError_Y += sDelta_Y;
        if(sError_X > sDistance)
        {
            sError_X    -= sDistance;
            usX_Current += sIncrease_X;
        }
        if(sError_Y > sDistance)
        {
            sError_Y    -= sDistance;
            usY_Current += sIncrease_Y;
        }

        //动态看画线效果
        //for(k=0;k<20000;k++);
    }
}

/*
	* @name   LCD_DrawRectangle
	* @brief  画矩形
	* @param  usX_Start :X起始坐标
*           usY_Start :Y起始坐标
*           usWidth   :矩形的宽度
*           usHeight  :矩形的高度
*           usColor   :矩形的颜色
*           ucFilled  :选择是否填充矩形
							参数Filled   :填充
*                   unFilled :不填充
  * @retval none
*/
static void LCD_DrawRectangle(uint16_t usX_Start, uint16_t usY_Start, uint16_t usWidth, uint16_t usHeight, LCD_Color_t usColor, Filled_t ucFilled)
{
    //矩形填充
    if(ucFilled == Filled)
    {
        TFT_LCD.FillColor(usX_Start, usY_Start, usWidth, usHeight, usColor);
    }
    //矩形不填充
    else
    {
        //画四条线
        TFT_LCD.LCD_DrawLine(usX_Start, usY_Start, usX_Start + usWidth - 1, usY_Start, usColor);
        TFT_LCD.LCD_DrawLine(usX_Start + usWidth - 1, usY_Start, usX_Start + usWidth - 1, usY_Start + usHeight - 1, usColor);
        TFT_LCD.LCD_DrawLine(usX_Start + usWidth - 1, usY_Start + usHeight - 1, usX_Start, usY_Start + usHeight - 1, usColor);
        TFT_LCD.LCD_DrawLine(usX_Start, usY_Start + usHeight - 1, usX_Start, usY_Start, usColor);
    }
}

/*
	* @name   LCD_DrawCircle
	* @brief  在LCD屏幕上使用 Bresenham 算法画圆
	* @param  usX_Center :圆心X坐标
*           usY_Center :圆心Y坐标
*           usRadius   :圆的半径(单位像素)
*           usColor    :矩形的颜色
*           ucFilled   :选择是否填充圆形
							参数Filled   :填充
*                   unFilled :不填充
  * @retval none
*/
static void LCD_DrawCircle(uint16_t usX_Center, uint16_t usY_Center, uint16_t usRadius, LCD_Color_t usColor, Filled_t ucFilled)
{
    int16_t sCurrentX, sCurrentY;
    int16_t sError;

    sCurrentX = 0;
    sCurrentY = usRadius;
    sError    = 3 - (usRadius << 1); //判断下个点位置的标志

    while(sCurrentX <= sCurrentY)
    {
        int16_t sCountY;
        if(ucFilled)
        {
            for(sCountY = sCurrentX; sCountY <= sCurrentY; sCountY++)
            {
                TFT_LCD.LCD_SetPointPiexl(usX_Center + sCurrentX, usY_Center + sCountY, usColor);     //1,研究对象
                TFT_LCD.LCD_SetPointPiexl(usX_Center - sCurrentX, usY_Center + sCountY, usColor);     //2
                TFT_LCD.LCD_SetPointPiexl(usX_Center - sCountY,   usY_Center + sCurrentX, usColor);   //3
                TFT_LCD.LCD_SetPointPiexl(usX_Center - sCountY,   usY_Center - sCurrentX, usColor);   //4
                TFT_LCD.LCD_SetPointPiexl(usX_Center - sCurrentX, usY_Center - sCountY, usColor);     //5
                TFT_LCD.LCD_SetPointPiexl(usX_Center + sCurrentX, usY_Center - sCountY, usColor);     //6
                TFT_LCD.LCD_SetPointPiexl(usX_Center + sCountY,   usY_Center - sCurrentX, usColor);   //7
                TFT_LCD.LCD_SetPointPiexl(usX_Center + sCountY,   usY_Center + sCurrentX, usColor);   //0
            }
        }
        else
        {
            TFT_LCD.LCD_SetPointPiexl(usX_Center + sCurrentX, usY_Center + sCurrentY, usColor);     //1,研究对象
            TFT_LCD.LCD_SetPointPiexl(usX_Center - sCurrentX, usY_Center + sCurrentY, usColor);     //2
            TFT_LCD.LCD_SetPointPiexl(usX_Center - sCurrentY, usY_Center + sCurrentX, usColor);     //3
            TFT_LCD.LCD_SetPointPiexl(usX_Center - sCurrentY, usY_Center - sCurrentX, usColor);     //4
            TFT_LCD.LCD_SetPointPiexl(usX_Center - sCurrentX, usY_Center - sCurrentY, usColor);     //5
            TFT_LCD.LCD_SetPointPiexl(usX_Center + sCurrentX, usY_Center - sCurrentY, usColor);     //6
            TFT_LCD.LCD_SetPointPiexl(usX_Center + sCurrentY, usY_Center - sCurrentX, usColor);     //7
            TFT_LCD.LCD_SetPointPiexl(usX_Center + sCurrentY, usY_Center + sCurrentX, usColor);     //0
        }
        sCurrentX++;
        if(sError < 0)
        {
            sError += (4 * sCurrentX + 6);
        }
        else
        {
            sError += (10 + 4 * (sCurrentX - sCurrentY));
            sCurrentY--;
        }
    }
}
main.c
static void Run()
{	
	uint8_t i;
	
	//动态画圆形与矩形
	for(i=10;i<=80;i+=10)
	{
		TFT_LCD.LCD_DrawCircle(80,120,i,Color_RED,unFilled);
		TFT_LCD.LCD_DrawRectangle(220,60+i,20,20,Color_BLUE,Filled);
		HAL_Delay(100);
	}	
	
	for(i=80;i>=10;i-=10)
	{
		TFT_LCD.LCD_DrawCircle(80,120,i,Color_GRAY,unFilled);
		TFT_LCD.LCD_DrawRectangle(220,60+i,20,20,Color_GRAY,Filled);
		HAL_Delay(100);
	}
}

图片存储W25QXX

  • MX配置

在介绍

  • 程序编写

基于上面,还有协议篇SPI驱动外部Flash

先新建一个工程把图片写进去先

main.c
#include "Pic1.h"
#include "Pic2.h"
#include "Pic3.h"

//3张图片在外部Flash的地址,共占用150k*3 = 450k
#define Pic_Size          (uint32_t)153600
#define Flash_Pic1_Addr   (uint32_t)0
#define Flash_Pic2_Addr   Flash_Pic1_Addr + Pic_Size 
#define Flash_Pic3_Addr   Flash_Pic2_Addr + Pic_Size

static void Run()
{		
  uint32_t ulIndex;
	const uint8_t* pic = NULL;
	
	//扇区整个擦除
	TFT_LCD.LCD_ShowString(0,48,"Erase total flash chip..",Color_GRAY,Color_RED,ASCII_font_24);
	SPI_Flash.EraseTotal();
	
	TFT_LCD.LCD_ShowString(0,72,"Writing the first  image..",Color_GRAY,Color_RED,ASCII_font_24);
	pic = gImage_Pic1;
	//写入不定长度数据
	for(ulIndex=0;ulIndex<Pic_Size;ulIndex++)
	{
		SPI_Flash.WritePage((uint8_t*)pic++,(Flash_Pic1_Addr+ulIndex),1);
	}	

	TFT_LCD.LCD_ShowString(0,96,"Writing the second image..",Color_GRAY,Color_RED,ASCII_font_24);
	pic = gImage_Pic2;
	//写入不定长度数据
	for(ulIndex=0;ulIndex<Pic_Size;ulIndex++)
	{
		SPI_Flash.WritePage((uint8_t*)pic++,(Flash_Pic2_Addr+ulIndex),1);
	}	
	
	TFT_LCD.LCD_ShowString(0,120,"Writing the three  image..",Color_GRAY,Color_RED,ASCII_font_24);
	pic = gImage_Pic3;
	//写入不定长度数据
	for(ulIndex=0;ulIndex<Pic_Size;ulIndex++)
	{
		SPI_Flash.WritePage((uint8_t*)pic++,(Flash_Pic3_Addr+ulIndex),1);
	}	
	
	TFT_LCD.LCD_ShowString(0,144,"Writing finish!",Color_GRAY,Color_RED,ASCII_font_24);
	
	while(1);
}

然后再新建一个工程是以下代码

SPI_Flash.h
//6张图片在外部Flash的地址,共占用150k*9 = 1350k < 1.32M-Bytes
#define Pic_Size          (uint32_t)153600
#define Flash_Pic1_Addr   (uint32_t)0
#define Flash_Pic2_Addr   Flash_Pic1_Addr + Pic_Size 
#define Flash_Pic3_Addr   Flash_Pic2_Addr + Pic_Size

typedef struct
{
  uint32_t  JedecID;	                                //设备标识符 -> 制造商+内存类型+容量
	
	void (*ReadJedecID)(void);                          //读取设备标识符
	void (*EraseSector)(uint32_t);                      //擦除扇区(4kB)
	void (*EraseTotal)(void);                           //擦除全部
  void (*WritePage)(uint8_t*,uint32_t,uint16_t);      //写入页(256Bytes),写入长度不超过256字节
  void (*WriteUnfixed)(uint8_t*,uint32_t,uint32_t);   //写入不固定长度数据
	void (*ReadUnfixed)(uint8_t*,uint32_t,uint32_t);    //读取不固定长度数据
	void (*TransferPictureToTFT_LCD)(uint8_t);          //传输图片至TFT LCD屏幕
} SPI_Flash_t;

extern SPI_Flash_t  SPI_Flash ;
SPI_Flash.c
static void TransferPictureToTFT_LCD(uint8_t);                    //传输图片至TFT LCD屏幕

/*
	* @name   TransferPictureToTFT_LCD
	* @brief  读取不固定长度数据
	* @param  Pic_Num:图片序号
	* @retval None
*/
static void TransferPictureToTFT_LCD(uint8_t Pic_Num)
{
    uint32_t  DataLength = Pic_Size;
    uint32_t  Pic_Addr   = NULL;
    uint16_t  Pic_Data;

    switch(Pic_Num)
    {
    case 1:
        Pic_Addr = Flash_Pic1_Addr;
        break;
    case 2:
        Pic_Addr = Flash_Pic2_Addr;
        break;
    case 3:
        Pic_Addr = Flash_Pic3_Addr;
        break;
    default:
        Pic_Addr = Flash_Pic1_Addr;
    }

    //选择Flash芯片: CS输出低电平
    CLR_SPI_Flash_CS;

    //发送命令:读取数据
    SPI_Flash_WriteByte(W25X_ReadData);
    //发送地址高字节
    SPI_Flash_WriteByte((Pic_Addr & 0xFF0000) >> 16);
    //发送地址中字节
    SPI_Flash_WriteByte((Pic_Addr & 0xFF00) >> 8);
    //发送地址低字节
    SPI_Flash_WriteByte(Pic_Addr & 0xFF);

    //设置窗口,大小为320x240
    TFT_LCD.LCD_SetWindows(0, 0, 320, 240);
    //开始传输数据
    while (DataLength)
    {
        Pic_Data = SPI_Flash_ReadByte();
        // 合并成2字节
        Pic_Data <<= 8;
        Pic_Data += SPI_Flash_ReadByte();

        LCD_Write_DATA(Pic_Data);
        DataLength -= 2;
    }

    //禁用Flash芯片: CS输出高电平
    SET_SPI_Flash_CS;
}
main.c
static void Run()
{		
	//循环显示3张图片,图片来源于外部flash
	SPI_Flash.TransferPictureToTFT_LCD(1);
	HAL_Delay(1000);
	SPI_Flash.TransferPictureToTFT_LCD(2);
	HAL_Delay(1000);
	SPI_Flash.TransferPictureToTFT_LCD(3);
	HAL_Delay(1000);
}

屏幕触摸

  • 硬件连接

SPI接口

  • 驱动IC

是一个AD转换芯片 XPT2046,看手册比较重要的地方:

工作电压范围为 2.2V~5.25V

支持 1.5V~5.25V 的数字 I/O 口

内建 2.5V 参考电压源

  • 触摸校准

为什么需要触摸校准?

  1. 教程用的2.8寸TFT,分辨率为240*320,而触摸屏的ADC值为0-4096,由于是线性变化的,两者之间存在比例关系,需要计算出比例因素 xFactoryFactor

  2. TFT屏幕的坐标原点(0,0),并非与触摸屏的原点完全对应,有一定的偏移量,需要计算出偏移量 xOffsetyOffset

校准后,如何根据比例因素与偏移量计算出TFT屏幕坐标?

LCD_X = (ADC_X * xFactor) - xOffset

LCD_Y = (ADC_Y * yFactor) - yOffset

如何校准?

第一步:在屏幕上指定如下图5个位置。 其中位置1-4用于计算比例因子与偏移量,位置5用于检验校准是否ok;

第二步:通过在5个位置分别显示十字光标,等待用户触摸十字光标,依次获取5个位置的ADC坐标值, ADC_x1至ADC_x5ADC_y1至ADC_ y5

第三步:通过位置1-4的坐标计算出对角的两个坐标( 说明:不直接使用x1与x3,而是计算出来,是为了减小触摸误差)

第四步:通过对角坐标与中间位置的坐标值,可以检验校准是否ok

第五步:根据对角坐标与屏幕分辨率,计算出factor与offset

第六步:将校准参数存储在flash

  • MX配置

  • 程序编写

在上面代码基础上写,SPI程序在协议篇编程示例1

main.c
// 初始化
static void Peripheral_Set()
{
    TFT_LCD.Init();           //TFT LCD屏幕初始化
    //触摸屏幕校准
    SPI_Flash.ReadUnfixed(&Touch_Calibrate_Para.Calibrate_Flag, Touch_Calibrate_Para_Addr, sizeof(Touch_Calibrate_Para));
    // 则重新校准
    if(Touch_Calibrate_Para.Calibrate_Flag != Touch_Calibrate_OK)
    {
        while(!Touch.Calibrate());
    }
    // 画板初始化
    Touch.Palette_Init();    
}

static void Run()
{		
	//强制触摸校准
	if(Touch.Force_Calibrate_Flag == TRUE)
	{
		Touch.Force_Calibrate_Flag = FALSE;
		
		SPI_Flash.EraseSector(Touch_Calibrate_Para_Addr);
		while(!Touch.Calibrate()); //触摸校准
		Touch.Palette_Init();      //画板初始化
	}
	
	//扫描触摸按键
	if(Touch.Scan() == TRUE)
	{
		printf("触摸屏坐标值(LCD):xLCD=%u,yLCD=%u\r\n",Touch.LCD_X,Touch.LCD_Y);
		
		//画图
		if(Touch.LCD_Y < LCD_HEIGTH-48)
		{
			Touch.Palette_DrawPoint(Touch.LCD_X,Touch.LCD_Y,Touch.Color_PEN);
		}
		//调整彩笔颜色
		if((Touch.LCD_Y >= LCD_HEIGTH-48) && (Touch.LCD_Y < LCD_HEIGTH-24))
		{
            // 获取像素颜色
			Touch.Color_PEN = TFT_LCD.LCD_GetPointPiexl(Touch.LCD_X,Touch.LCD_Y);
			TFT_LCD.FillColor(LCD_WIDTH-48, LCD_HEIGTH-24,24,24,Touch.Color_PEN);
		}
		//清除绘图区域
		if((Touch.LCD_X >= LCD_WIDTH-24) && (Touch.LCD_Y >= LCD_HEIGTH-24))
		{
			//绘图区域填充白色
			TFT_LCD.FillColor(0,0,240,LCD_HEIGTH-48,Color_WHITE);
		}			
	}
}

0xD00x90 看上面图片配置即可

touch.h
#ifndef __TOUCH_H
#define __TOUCH_H

#include "AllHead.h"

//CS管脚控制
#define SET_SPI_Touch_CS    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_SET)
#define CLR_SPI_Touch_CS    HAL_GPIO_WritePin(GPIOA,GPIO_PIN_15,GPIO_PIN_RESET)

//XT2046读X轴与Y轴的命令
#define 	Touch_X_CMD        0xD0  //读取X轴命令
#define 	Touch_Y_CMD        0x90  //读取Y轴命令
//说明:通过调整Touch_READ_TIMES与Touch_Error,可以调整灵敏度与准确性
//      数字越小,灵敏度越高,但准确性越差,按实际需要调整。
#define   Touch_READ_TIMES   5     //一次读取触摸值的次数
#define   Touch_Error        15    //误差
//X轴与Y轴坐标边界设定(通过打印测出来的)
#define   Touch_X_MAX        3950  //X轴最大值
#define   Touch_X_MIN        100   //X轴最小值
#define   Touch_Y_MAX        3800  //Y轴最大值
#define   Touch_Y_MIN        100   //Y轴最小值

#define   Touch_Calibrate_Para_Addr   0
#define   Touch_Calibrate_OK         'Y'
#define  	Dummy_Byte                  0xFF  //假数据

//定义枚举类型

//定义结构体类型
typedef struct
{
    uint8_t Calibrate_Flag; //校准标志位
    uint8_t xOffset;     //X轴偏移量
    uint8_t yOffset;     //Y轴偏移量
    float   xFactor;     //X轴比例因子
    float   yFactor;     //Y轴比例因子
} Touch_Calibrate_Para_t;

typedef struct
{
    uint8_t Touch_Flag;
    uint8_t Force_Calibrate_Flag;

    uint16_t ADC_X; //ADC转换X坐标值
    uint16_t ADC_Y; //ADC转换Y坐标值
    uint16_t LCD_X; //LCD转换X坐标值
    uint16_t LCD_Y; //LCD转换Y坐标值

    LCD_Color_t Color_PEN; //画笔颜色

    void (*Palette_Init)(void);                               //调色板初始化
    void (*Palette_DrawPoint)(uint16_t, uint16_t, LCD_Color_t); //绘图区域画点
    uint8_t (*Read_ADC_XY)(uint16_t *, uint16_t *);           //读触摸IC的XY坐标值
    uint8_t (*Calibrate)(void);                               //触摸屏校准
    uint8_t (*Scan)(void);                                    //触摸屏扫描
} Touch_t;


extern Touch_t Touch;
extern Touch_Calibrate_Para_t Touch_Calibrate_Para;

#endif

Touch_Read_ADC 函数的话就是看上面时序进行写,里面读时序是先获取前面7个字节然后把最高位去掉左移8位再把后面5个字节合并一起,但是最后3个字节是不要的所以去掉,得整个数据右移3位

touch.c
#include "AllHead.h"

static void Palette_Init(void);                               //调色板初始化
static void Palette_DrawPoint(uint16_t, uint16_t, LCD_Color_t); //绘图区域画点
static uint8_t Touch_Read_ADC_XY(uint16_t *, uint16_t *);     //读触摸IC的XY坐标值
static uint8_t Touch_Calibrate(void);                         //触摸屏校准
static uint8_t Touch_Scan(void);                              //触摸屏扫描

Touch_Calibrate_Para_t Touch_Calibrate_Para;

Touch_t Touch =
{
    FALSE,
    FALSE,
    0,
    0,
    0,
    0,
    Color_RED,

    Palette_Init,
    Palette_DrawPoint,
    Touch_Read_ADC_XY,
    Touch_Calibrate,
    Touch_Scan
};

static uint8_t  SPI_Touch_ReadByte(void);        //从Flash读1个字节
static void     SPI_Touch_WriteByte(uint8_t);    //给Flash写1个字节
static uint16_t Touch_Read_ADC(uint8_t);         //读取触摸屏的ADC值

static void     LCD_DrawCross(uint16_t, uint16_t); //校正触摸时画十字专用

/*
	* @name   SPI_Touch_ReadByte
	* @brief  从触摸IC读1个字节
	* @param  None
	* @retval 读取到的数据
*/
static uint8_t SPI_Touch_ReadByte()
{
    uint8_t ReceiveByte;
    //等待模式读出1个字节
    if(HAL_SPI_Receive(&hspi2, &ReceiveByte, 1, 0x0A) != HAL_OK)
        // Dummy_Byte是无用数据
        ReceiveByte = Dummy_Byte;
    //返回字节
    return ReceiveByte;
}

/*
	* @name   SPI_Touch_ReadByte
	* @brief  给触摸IC写1个字节
	* @param  Byte -> 待写入的字节
	* @retval 读取到的数据
*/
static void SPI_Touch_WriteByte(uint8_t Byte)
{
    uint8_t SendByte = Byte;
    //等待模式写入1个字节
    HAL_SPI_Transmit(&hspi2, &SendByte, 1, 0x0A);
}

/*
	* @name   Palette_Init
	* @brief  调色板初始化
	* @param  None
	* @retval None
*/
static void Palette_Init()
{
    //屏幕填充灰色
    TFT_LCD.FillColor(0, 0, LCD_WIDTH, LCD_HEIGTH, Color_GRAY);

    //绘图区域填充白色
    TFT_LCD.FillColor(0, 0, 240, LCD_HEIGTH - 48, Color_WHITE);

    //显示调色板
    TFT_LCD.FillColor(0,  LCD_HEIGTH - 48, 24, 24, Color_RED);
    TFT_LCD.FillColor(24, LCD_HEIGTH - 48, 24, 24, Color_GREEN);
    TFT_LCD.FillColor(48, LCD_HEIGTH - 48, 24, 24, Color_BLUE);
    TFT_LCD.FillColor(72, LCD_HEIGTH - 48, 24, 24, Color_YELLOW);
    TFT_LCD.FillColor(96, LCD_HEIGTH - 48, 24, 24, Color_MAGENTA);
    TFT_LCD.FillColor(120, LCD_HEIGTH - 48, 24, 24, Color_CYAN);
    TFT_LCD.FillColor(144, LCD_HEIGTH - 48, 24, 24, Color_BROWN);
    TFT_LCD.FillColor(168, LCD_HEIGTH - 48, 24, 24, Color_LIGHTBLUE);
    TFT_LCD.FillColor(192, LCD_HEIGTH - 48, 24, 24, Color_GRAY);
    TFT_LCD.FillColor(216, LCD_HEIGTH - 48, 24, 24, Color_BLACK);

    //显示坐标信息
    TFT_LCD.LCD_ShowString(0,  LCD_HEIGTH - 24, "X:",  Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowString(24, LCD_HEIGTH - 24, "----", Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowString(96, LCD_HEIGTH - 24, "Y:",  Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowString(120, LCD_HEIGTH - 24, "----", Color_GRAY, Color_RED, ASCII_font_24);

    //显示画笔色彩
    TFT_LCD.FillColor(LCD_WIDTH - 48, LCD_HEIGTH - 24, 24, 24, Touch.Color_PEN);

    //显示清除画板信息
    TFT_LCD.FillColor(LCD_WIDTH - 24, LCD_HEIGTH - 24, 24, 24, Color_WHITE);
    TFT_LCD.LCD_ShowString(LCD_WIDTH - 20, LCD_HEIGTH - 20, "CL", Color_WHITE, Color_RED, ASCII_font_16);
}

/*
	* @name   Palette_DrawPoint
	* @brief  绘图区域画点
	* @param  x:x坐标
  *         y:y坐标
  *         Color:点的颜色
	* @retval None
*/
static void Palette_DrawPoint(uint16_t x, uint16_t y, LCD_Color_t Color)
{
    // 一次画8个点这样粗点
    TFT_LCD.LCD_SetPointPiexl ( x - 1, y - 1, Color);
    TFT_LCD.LCD_SetPointPiexl (   x, y - 1, Color);
    TFT_LCD.LCD_SetPointPiexl ( x + 1, y - 1, Color);
    TFT_LCD.LCD_SetPointPiexl ( x - 1,   y, Color);
    TFT_LCD.LCD_SetPointPiexl (   x,   y, Color);
    TFT_LCD.LCD_SetPointPiexl ( x + 1,   y, Color);
    //调色板边界处理
    if((y + 1) < LCD_HEIGTH - 48)
    {
        TFT_LCD.LCD_SetPointPiexl ( x - 1, y + 1, Color);
        TFT_LCD.LCD_SetPointPiexl (   x, y + 1, Color);
        TFT_LCD.LCD_SetPointPiexl ( x + 1, y + 1, Color);
    }
}
/*
	* @name   Touch_Read_ADC
	* @brief  读取触摸屏的ADC值
	* @param  XT2046_CMD:触摸IC命令
  * @retval ADC值
*/
static uint16_t Touch_Read_ADC(uint8_t XT2046_CMD)
{
    uint8_t  i, j;
    uint16_t Value_Buf[Touch_READ_TIMES], usTemp;
    uint32_t SumValue = 0;

    //通过SPI接口循环读取Touch_READ_TIMES次数
    for(i = 0; i < Touch_READ_TIMES; i++)
    {
        //选择触摸芯片: CS输出低电平
        CLR_SPI_Touch_CS;

        /* 在差分模式下,XPT2046转换需要24个时钟,8个时钟输入命令,延时一会,
        之后1个时钟去除忙信号,接着输出12位转换结果,剩下3个时钟是忽略位*/

        //发送控制命令
        SPI_Touch_WriteByte(XT2046_CMD);
        //延时一会,等待ADC转换
        for(j = 0; j < 100; j++);
        //读取数据
        Value_Buf[i] = SPI_Touch_ReadByte(); //获取前面7个字节,其中最高位无效
        Value_Buf[i] &= (~0x80);             //最高位无效
        Value_Buf[i] <<= 8;                  //左移至高字节
        Value_Buf[i] += SPI_Touch_ReadByte();//获取后面5字节,其中低3位无效
        Value_Buf[i] >>= 3;	                 //右移3位,得到12位有效数据
        //禁用触摸芯片: CS输出高电平
        SET_SPI_Touch_CS;
    }

    //采样值从大到小排序排序
    for(i = 0; i < (Touch_READ_TIMES - 1); i++)
    {
        for(j = i + 1; j < Touch_READ_TIMES; j++)
        {
            if(Value_Buf[i] < Value_Buf[j])
            {
                usTemp        = Value_Buf[i];
                Value_Buf[i]  = Value_Buf[j];
                Value_Buf[j]  = usTemp;
            }
        }
    }

    //去掉最大与最小值,求和
    for(i = 1; i < Touch_READ_TIMES - 1; i++)
    {
        SumValue += Value_Buf[i];
    }

    //返回平均值
    return  SumValue / (Touch_READ_TIMES - 2);
}

/*
	* @name   Touch_Read_ADC_XY
	* @brief  读触摸IC的XY坐标值
	* @param  *xValue:保存读取到X轴ADC值的地址
  *         *yValue:保存读取到Y轴ADC值的地址
  * @retval TRUE:读取成功,FALSE:读取失败
*/
static uint8_t Touch_Read_ADC_XY(uint16_t *xValue, uint16_t *yValue)
{
    uint16_t xValue_Buf[2], yValue_Buf[2];
    uint16_t xValue_Error, yValue_Error;

    //读取坐标X,Y的值,各2次
    xValue_Buf[0] = Touch_Read_ADC(Touch_X_CMD);
    yValue_Buf[0] = Touch_Read_ADC(Touch_Y_CMD);
    xValue_Buf[1] = Touch_Read_ADC(Touch_X_CMD);
    yValue_Buf[1] = Touch_Read_ADC(Touch_Y_CMD);

    //计算2次采样的差值
    if(xValue_Buf[0] >= xValue_Buf[1])
        xValue_Error = xValue_Buf[0] - xValue_Buf[1];
    else
        xValue_Error = xValue_Buf[1] - xValue_Buf[0];

    if(yValue_Buf[0] >= yValue_Buf[1])
        yValue_Error = yValue_Buf[0] - yValue_Buf[1];
    else
        yValue_Error = yValue_Buf[1] - yValue_Buf[0];

    //两次采样差值超过误差,丢弃
    if((xValue_Error > Touch_Error) || (yValue_Error > Touch_Error))
    {
        return  FALSE;
    }

    //求平均
    *xValue = (xValue_Buf[0] + xValue_Buf[1]) / 2;
    *yValue = (yValue_Buf[0] + yValue_Buf[1]) / 2;

    //判断值是否有效
    if((*xValue > Touch_X_MAX) || (*xValue < Touch_X_MIN))
        return  FALSE;
    if((*yValue > Touch_Y_MAX) || (*yValue < Touch_Y_MIN))
        return  FALSE;

    //打印坐标值
    //printf("触摸屏坐标值(ADC):X=%u,Y=%u\r\n",*xValue,*yValue);

    //返回
    return TRUE;
}

/*
	* @name   LCD_DrawCross
	* @brief  校正触摸时画十字专用
	* @param  x:十字中点x轴
  *         y:十字中点y轴
  * @retval None
*/
static void LCD_DrawCross(uint16_t x, uint16_t y)
{
    TFT_LCD.LCD_DrawLine(x - 10, y, x + 10, y, Color_RED);
    TFT_LCD.LCD_DrawLine(x, y - 10, x, y + 10, Color_RED);
}

/*
	* @name   Touch_ReadCalibrateValue
	* @brief  读取校准点坐标值
	* @param  x:x轴
  *         y:y轴
	*         *xValue:x轴坐标值
	*         *xValue:y轴坐标值
  * @retval None
*/
static uint8_t Touch_ReadCalibrateValue(uint16_t x, uint16_t y, uint16_t *xValue, uint16_t *yValue)
{
    uint8_t Cnt = 0;

    //显示校准位置
    LCD_DrawCross(x, y);
    while(1)
    {
        if(Touch_Read_ADC_XY(xValue, yValue))
        {
            //取第十次读到的值,数据更稳定
            if(Cnt++ > 10)
            {
                TFT_LCD.FillColor(0, 0, 240, 320, Color_BLACK);
                return TRUE;
            }
        }
    }
}


/*
	* @name   Touch_Calibrate
	* @brief  触摸屏幕校准
	* @param  None
  * @retval TRUE:校准成功,FALSE:校准失败
*/

static uint8_t Touch_Calibrate()
{
    //5个校准位置,中间的校验用
    uint16_t Calibrate_xyLCD[5][2] =
    {
        {20, 20},
        {20, LCD_HEIGTH - 20},
        {LCD_WIDTH - 20, LCD_HEIGTH - 20},
        {LCD_WIDTH - 20, 20},
        {LCD_WIDTH / 2, LCD_HEIGTH / 2} //屏幕中央,校验用
    };
    uint16_t xValue[5], yValue[5];        //5个校准位置对应的坐标值
    uint16_t xOpposite[2], yOpposite[2];  //计算得到对角的坐标
    uint16_t Avr_xOpposite, Avr_yOpposite; //对角坐标的平均值,用于与屏幕中央的坐标值比较
    uint8_t  i;

    //读取5个校准点的坐标值
    TFT_LCD.FillColor(0, 0, 240, 320, Color_BLACK);
    for(i = 0; i < 5; i++)
    {
        Touch_ReadCalibrateValue(Calibrate_xyLCD[i][0], Calibrate_xyLCD[i][1], &xValue[i], &yValue[i]);
        //适当延时,读取下一个校准点
        HAL_Delay(800);
    }

    //将正方形的4个校准点整合成对角两点,减小触摸误差
    xOpposite[0] = (xValue[0] + xValue[1]) / 2;
    yOpposite[0] = (yValue[0] + yValue[3]) / 2;
    xOpposite[1] = (xValue[2] + xValue[3]) / 2;
    yOpposite[1] = (yValue[1] + yValue[2]) / 2;
    //计算对角两点的平均值
    Avr_xOpposite = (xOpposite[0] + xOpposite[1]) / 2;
    Avr_yOpposite = (yOpposite[0] + yOpposite[1]) / 2;

    printf("触摸屏坐标值(ADC):xAvr=%u,yAvr=%u\r\n", Avr_xOpposite, Avr_yOpposite);
    printf("触摸屏坐标值(ADC):xMid=%u,yMid=%u\r\n", xValue[4], yValue[4]);

    //对校准点进行校验
    if(Avr_xOpposite >= xValue[4])
    {
        if((Avr_xOpposite - xValue[4]) > 100)
        {
            printf("校准失败\r\n");
            TFT_LCD.LCD_ShowString(24, 160, "Calibrate Fail", Color_BLACK, Color_RED, ASCII_font_24);
            HAL_Delay(1000);
            return FALSE;
        }
    }
    else
    {
        if((xValue[4] - Avr_xOpposite) > 100)
        {
            printf("校准失败\r\n");
            TFT_LCD.LCD_ShowString(24, 160, "Calibrate Fail", Color_BLACK, Color_RED, ASCII_font_24);
            HAL_Delay(1000);
            return FALSE;
        }
    }

    if(Avr_yOpposite >= yValue[4])
    {
        if((Avr_yOpposite - yValue[4]) > 100)
        {
            printf("校准失败\r\n");
            TFT_LCD.LCD_ShowString(24, 160, "Calibrate Fail", Color_BLACK, Color_RED, ASCII_font_24);
            HAL_Delay(1000);
            return FALSE;
        }
    }
    else
    {
        if((yValue[4] - Avr_yOpposite) > 100)
        {
            printf("校准失败\r\n");
            TFT_LCD.LCD_ShowString(24, 160, "Calibrate Fail", Color_BLACK, Color_GREEN, ASCII_font_24);
            HAL_Delay(1000);
            return FALSE;
        }
    }

    //计算比例因素
    Touch_Calibrate_Para.xFactor = (float)(LCD_WIDTH - 40)  / (xOpposite[1] - xOpposite[0]);
    Touch_Calibrate_Para.yFactor = (float)(LCD_HEIGTH - 40) / (yOpposite[1] - yOpposite[0]);

    //计算偏移量
    Touch_Calibrate_Para.xOffset = (uint8_t)(Touch_Calibrate_Para.xFactor * Avr_xOpposite - LCD_WIDTH / 2);
    Touch_Calibrate_Para.yOffset = (uint8_t)(Touch_Calibrate_Para.yFactor * Avr_yOpposite - LCD_HEIGTH / 2);

    //设置校准标志位
    Touch_Calibrate_Para.Calibrate_Flag = Touch_Calibrate_OK;

    printf("校准成功\r\n");
    printf("校准因素xFactor:%.2f\r\n", Touch_Calibrate_Para.xFactor);
    printf("校准因素yFactor:%.2f\r\n", Touch_Calibrate_Para.yFactor);
    printf("偏移量xOffset:  %u\r\n", Touch_Calibrate_Para.xOffset);
    printf("偏移量yOffset:  %u\r\n", Touch_Calibrate_Para.yOffset);
    TFT_LCD.LCD_ShowString(12, 160, "Calibrate Success", Color_BLACK, Color_GREEN, ASCII_font_24);
    HAL_Delay(1000);

    ////保存参数
    //扇区擦除
    SPI_Flash.EraseSector(Touch_Calibrate_Para_Addr);
    SPI_Flash.WriteUnfixed(&Touch_Calibrate_Para.Calibrate_Flag, Touch_Calibrate_Para_Addr, sizeof(Touch_Calibrate_Para));

    return TRUE;
}

/*
	* @name   Touch_Scan
	* @brief  触摸屏扫描
	* @param  None
  * @retval TRUE:有触摸,获取到坐标值,FALSE:无触摸
   说明:降低干扰,每次获取两次LCD屏幕坐标值,计算误差,求平均。
*/
static uint8_t Touch_Scan()
{
    uint16_t LCD_X1, LCD_Y1, LCD_X2, LCD_Y2;
	// 原本可以看中断引脚判断是否有按下但是不太稳定故注释
    //if(HAL_GPIO_ReadPin(TP_NIRQ_GPIO_Port,TP_NIRQ_Pin) == GPIO_PIN_RESET)
    //{
    //第一次读取触摸屏的坐标值
    if(Touch.Read_ADC_XY(&Touch.ADC_X, &Touch.ADC_Y) == FALSE)
    {
        return FALSE;
    }

    //第二次计算LCD屏幕坐标值
    LCD_X1 = (uint16_t)(Touch.ADC_X * Touch_Calibrate_Para.xFactor) - Touch_Calibrate_Para.xOffset;
    LCD_Y1 = (uint16_t)(Touch.ADC_Y * Touch_Calibrate_Para.yFactor) - Touch_Calibrate_Para.yOffset;

    if(Touch.Read_ADC_XY(&Touch.ADC_X, &Touch.ADC_Y) == FALSE)
    {
        return FALSE;
    }

    //第二次计算LCD屏幕坐标值
    LCD_X2 = (uint16_t)(Touch.ADC_X * Touch_Calibrate_Para.xFactor) - Touch_Calibrate_Para.xOffset;
    LCD_Y2 = (uint16_t)(Touch.ADC_Y * Touch_Calibrate_Para.yFactor) - Touch_Calibrate_Para.yOffset;

    //误差检查
    if(LCD_X1 >= LCD_X2)
    {
        if((LCD_X1 - LCD_X2) > 2)
            return FALSE;
    }
    else
    {
        if((LCD_X2 - LCD_X1) > 2)
            return FALSE;
    }

    if(LCD_Y1 >= LCD_Y2)
    {
        if((LCD_Y1 - LCD_Y2) > 2)
            return FALSE;
    }
    else
    {
        if((LCD_Y2 - LCD_Y1) > 2)
            return FALSE;
    }

    //计算两次的平均值,得到LCD屏幕坐标值
    Touch.LCD_X = (LCD_X1 + LCD_X2) / 2;
    Touch.LCD_Y = (LCD_Y1 + LCD_Y2) / 2;

    if(Touch.LCD_X > (LCD_WIDTH - 1))
    {
        Touch.LCD_X = LCD_WIDTH - 1;
    }

    if(Touch.LCD_Y > (LCD_HEIGTH - 1))
    {
        Touch.LCD_X = LCD_WIDTH - 1;
    }


    //LCD屏幕上显示触摸屏的坐标值
    TFT_LCD.LCD_ShowChar(24, LCD_HEIGTH - 24, Touch.ADC_X / 1000 + '0',    Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowChar(36, LCD_HEIGTH - 24, Touch.ADC_X % 1000 / 100 + '0', Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowChar(48, LCD_HEIGTH - 24, Touch.ADC_X % 100 / 10 + '0',  Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowChar(60, LCD_HEIGTH - 24, Touch.ADC_X % 10 + '0',      Color_GRAY, Color_RED, ASCII_font_24);

    TFT_LCD.LCD_ShowChar(120, LCD_HEIGTH - 24, Touch.ADC_Y / 1000 + '0',    Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowChar(132, LCD_HEIGTH - 24, Touch.ADC_Y % 1000 / 100 + '0', Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowChar(144, LCD_HEIGTH - 24, Touch.ADC_Y % 100 / 10 + '0',  Color_GRAY, Color_RED, ASCII_font_24);
    TFT_LCD.LCD_ShowChar(156, LCD_HEIGTH - 24, Touch.ADC_Y % 10 + '0',      Color_GRAY, Color_RED, ASCII_font_24);

    return TRUE;

    //}
    //else
    //{
    //return FALSE;
    //}
}

实验现象

LVGL移植

  • 前提

首先准备一个Keil工程,在上面的工程基础上移植(需要那个工程已经做好基本显示和触摸功能)

重点:需要在MX里设置 Minimum Stack Size 调整为 0x800,Keil编译器支持C99 Mode

  • 步骤1-- 修改栈大小,C99

  • 步骤2-- LVGL资料下载(源码和示例下载,版本必须要匹配,这里没有选择最新版本只是使用V7示范)

  • 步骤3-- 在工程目录新建文件夹GUI,在GUI文件夹下新建3个文件夹放置LVGL,分别为 源代码驱动代码官方例程,然后使能配置文件

GUI文件夹下的内容:

文件夹/文件名称 存放内容
lvgl lv_demos-release-v7.zip 解压,把里面内容复制到这
lvgl_examples lv_demos-release-v7.zip 解压,把里面内容复制到这
lvgl_driver lvgl\examples\porting\ 下的6个文件复制到这,然后由于没有用到SD卡所以把 lv_port_fs_template.c/.h 删除,然后把剩下的4个文件名字改一下去掉后缀 _template
lv_conf.h lvgl 文件夹下的 lv_conf_template.h 复制过来顺便改名
lv_ex_conf.h lvgl_examples 文件夹下的 lv_ex_conf_template.h 复制过来顺便改名
  • 步骤4-- keil工程中新建3个group,名称与上面一致,将LVGL源码添加至工程中

把蓝色框里面的 .c 全部添加到 lvgl

在 【C/C++】那添加头文件路径

添加完后编译,会报警告,则可以在【魔法棒】–【C/C++】–【Misc Controls】里添加屏蔽警告语句: --diag_suppress=111

  • 步骤5-- 修改 lv_conf.h 文件,编译工程

LV_HOR_RES_MAXLV_VER_RES_MAX 设置跟屏幕分辨率对应即可,这个不是实际显示分辨率!!

//23行
#define LV_HOR_RES_MAX          (240)
#define LV_VER_RES_MAX          (320)

LV_COLOR_DEPTH 设置颜色格式,看LCD,这里选择16位–RGB565

//32行
#define LV_COLOR_DEPTH     16

LV_COLOR_16_SWAP 16位的话置0即可,8位则置1

//36行
#define LV_COLOR_16_SWAP   0

不需要改一般,默认,这个是分配给GUI的管理内存,默认32K

// 86行
#  define LV_MEM_SIZE    (32U * 1024U)

GPU,文件系统我们不用可以关掉

// 194行
#define LV_USE_GPU              0
// 214行
#define LV_USE_FILESYSTEM       0
  • 步骤6-- 定时器放置 LVGL心跳 函数
//添加头文件
#include "lvgl.h"

// 在你的定时器里面添加即可--定时器定时1ms
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == htim6.Instance)
	{
        // 参数是你定时器中断时间,可以是其他
		lv_tick_inc(1);
	}
}
  • 步骤7-- 修改底层显示驱动代码

打开 lv_port_disp.c ,使能它, #if 1,把定义的头文件名称改为 #include "lv_port_disp.h" 跟你文件名一样,打开头文件 lv_port_disp.h , 使能它, #if 1,然后还需要把 #include "lvgl/lvgl.h" 改成 #include "../lvgl/lvgl.h",不然报错找不到这个头文件,然后主要改两个函数,一个是初始化,一个是画面刷新

// 这是它的函数,里面没有东西需要用户添加进去
static void disp_init(void)
{
    /*You code here*/
}

// 添加我们屏幕的初始化函数进去
static void disp_init(void)
{
    TFT_LCD.Init();
}

添加初始化后,还要在 void lv_port_disp_init(void) 函数下面修改一下,下面有3种内存分配(影响流畅度),这个看单片机的内存选择,这里我们单片机内存不大所以选第1种,把剩下两种屏蔽即可

把这替换成另一种方法,设大点
- static lv_color_t draw_buf_1[LV_HOR_RES_MAX * 10]; 
替换为
+ static lv_color_t draw_buf_1[LV_HOR_RES_MAX * LV_VER_RES_MAX / 10]; 
//继续往下找到这两个!!!它们是实际显示的分辨率,还有这个一定要小于或者等于上面设置的LV_HOR_RES_MAX 和 LV_VER_RES_MAX
//104行
disp_drv.hor_res = 240;
disp_drv.ver_res = 320;
//111行
//这个跟上面选择的分配方案有关,这里是第一种方案
disp_drv.buffer = &draw_buf_dsc_1;

修改 static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) 函数里面

修改为:

static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

    uint16_t x, y;

    //挂起systick,提高GUI刷新速率
    HAL_SuspendTick();

    //设置窗口
    TFT_LCD.LCD_SetWindows(area->x1, area->y1, area->x2 - area->x1 + 1, area->y2 - area->y1 + 1);

    for(y = area->y1; y <= area->y2; y++)
    {
        for(x = area->x1; x <= area->x2; x++)
        {
            /* Put a pixel to the display. For example: */
            /* put_px(x, y, *color_p)*/
            LCD_Write_DATA(color_p->full);
            color_p++;
        }
    }

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);

    //恢复systick
    HAL_ResumeTick();
}
  • 步骤8-- 修改底层触摸驱动代码

打开 lv_port_indev.c ,使能它, #if 1,把定义的头文件名称改为 #include "lv_port_indev.h" 跟你文件名一样,打开头文件 lv_port_indev.h , 使能它, #if 1,然后还需要把 #include "lvgl/lvgl.h" 改成 #include "../lvgl/lvgl.h",不然报错找不到这个头文件

里面的话用到的只有触摸,其他的用不到

直接把 .c 改成下面

#if 1

#include "lv_port_indev.h"
#include "MyApplication.h"

static bool touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);


void lv_port_indev_init(void)
{
    lv_indev_drv_t indev_drv;

    /*------------------
     * Touchpad
     * -----------------*/

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    lv_indev_drv_register(&indev_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/* Will be called by the library to read the touchpad */
static bool touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
    static uint16_t last_x = 0;
    static uint16_t last_y = 0;
	// 触摸标志位
    if(Touch.Touch_Flag == TRUE)
    {
        Touch.Touch_Flag = FALSE;
		// 把获取的坐标赋值给data结构体
        data->point.x = Touch.LCD_X;
        data->point.y = Touch.LCD_Y;
        // 标志位置一下
        data->state = LV_INDEV_STATE_PR;

        last_x = data->point.x;
        last_y = data->point.y;
    }
    else
    {
        data->point.x = last_x;
        data->point.y = last_y;
        data->state = LV_INDEV_STATE_REL;
    }

    return false;
}

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif

初始化,需要在总头文件添加对应头文件

AllHead.h
#include "GUI.h"
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lv_ex_get_started/lv_ex_get_started.h"
#include "lv_demo_widgets/lv_demo_widgets.h"
#include "lv_demo_keypad_encoder/lv_demo_keypad_encoder.h"

lv_port_disp_initlv_port_indev_init 两个函数需要到对应的头文件里声明一下,测试一下官方例程

void Hardware_Init(void)
{
    Timer6.Timer6_Start_IT(); //启动定时器6     
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();

    //触摸屏幕校准
    SPI_Flash.ReadUnfixed(&Touch_Calibrate_Para.Calibrate_Flag, Touch_Calibrate_Para_Addr, sizeof(Touch_Calibrate_Para));
    if(Touch_Calibrate_Para.Calibrate_Flag != Touch_Calibrate_OK)
    {
        while(!Touch.Calibrate());
    }   
}
  • 步骤9-- 测试官方例程

测试 lv_ex_get_started 这个文件夹例程

把文件夹里的 .c 添加到 lvgl_examples 里(一开始已经添加了),然后添加初始化代码,在 lv_ex_get_started.h

#include ""

void Hardware_Init(void)
{
    Timer6.Timer6_Start_IT(); //启动定时器6     
    lv_init();
    lv_port_disp_init();
    lv_port_indev_init();

    //触摸屏幕校准
    SPI_Flash.ReadUnfixed(&Touch_Calibrate_Para.Calibrate_Flag, Touch_Calibrate_Para_Addr, sizeof(Touch_Calibrate_Para));
    if(Touch_Calibrate_Para.Calibrate_Flag != Touch_Calibrate_OK)
    {
        while(!Touch.Calibrate());
    }  
    //测试官方例程1,2,3,大工程demo
    lv_ex_get_started_1();
	lv_ex_get_started_2();
	lv_ex_get_started_3();
    // 需要去例程的头文件lv_exconf.h打开宏否则添加了头文件也报错
    //#define LV_USE_DEMO_WIDGETS        1
    lv_demo_widgets();    
}

需要到 Touch.Scan() 函数里把显示坐标屏蔽,我们这不需要显示坐标

static void Run()
{		
	//获取触摸板坐标
	if(Touch.Scan() == TRUE)
	{
		Touch.Touch_Flag = TRUE;
	}
	
	//GUI任务
	lv_task_handler();
}

实验效果

  • 基于GUI写一个按键控制蜂鸣器
GUI.h
#ifndef __GUI_H__
#define __GUI_H__

#include "AllHead.h"

//定义枚举类型

//定义结构体类型
typedef struct
{
	void (*Button)(void); 
	void (*Label)(void);
} GUI_t;

extern GUI_t  GUI;

#endif
GUI.c
#include "AllHead.h"

static void Button(void);  //按钮
static void Label(void);   //标签

GUI_t  GUI =
{
    Button,
    Label
};


//按钮事件回调函数
static void btn_event_cb(lv_obj_t *btn, lv_event_t event)
{
    if(event == LV_EVENT_CLICKED)
    {
        //控制蜂鸣器
        if(Buzzer.Status == ON_Status)
        {
            Buzzer.OFF();
        }
        else
        {
            Buzzer.ON();
        }
    }
}

/*
	* @name   Button
	* @brief  按钮
	* @param  None
	* @retval None
*/
static void Button(void)
{
    lv_obj_t *btn = lv_btn_create(lv_scr_act (), NULL);  //往当前屏幕增加按钮
    lv_obj_set_pos(btn, 50, 160);                        //设置按钮的位置
    lv_obj_set_size(btn, 140, 50);                       //设置按钮的尺寸

    lv_obj_set_event_cb(btn, btn_event_cb);              //为按钮指定回调函数

    lv_obj_t *label = lv_label_create(btn, NULL);        //按钮上增加标签
    lv_label_set_text(label, "Buzzer");                  //标签显示文本
}

/*
	* @name   Label
	* @brief  标签
	* @param  None
	* @retval None
*/
static void Label(void)
{
    lv_obj_t *label = lv_label_create(lv_scr_act (), NULL);
    lv_obj_set_pos(label, 30, 120);
    lv_label_set_text(label, "Hello world!");
}

字体大小设置可以在 lv_conf.h 里找

需要使能

这里看上面改,改后面数字就行了

C8T6驱动LCD

注意点:由于C8T6引脚少,驱动一块LCD差不多就用了一半引脚了,推荐还是不要用它

该模块硬件支持8位和16位并口数据总线模式切换(如上面图1中红框所示):

  1. 将R5焊接0欧电阻或者直接短接,并将R4断开:选择16位数据总线模式(默认),使用 DB0~DB15 数据引脚
  2. 将R4焊接0欧电阻或者直接短接,并将R5断开:选择8位数据总线模式,使用 DB8~DB15 数据引脚

输入电压的话如果是模块可以3.3/5V,裸屏只能3.3V

  • 程序接线图

注意:不需要触摸功能,则不需要下面这个表的触摸屏接线

STM32引脚 LCD引脚
PC1 PEN(触摸屏触摸中断信号)
PC2 MISO(触摸屏SPI总线读信号)
PC3 MOSI(触摸屏SPI总线写信号)
PC13 T_CS(触摸屏片选控制信号)
PC0 CLK(触摸屏SPI总线时钟信号)

正常显示接线

注意:PB2用在BOOT1了,最小系统板两排没有引出,可直接插BOOT1上中间的排针上

STM32引脚 LCD引脚
3.3V/5V VDD
GND GND
PB0-PB15 DB0-DB15(液晶屏16位并口数据信号)
PA0 WR(液晶屏写数据控制信号)
PA1 RD(液晶屏读数据控制信号)
PA2 RS(液晶屏数据/命令控制信号)
PA3 RST(液晶屏复位控制信号)
PA4 CS(液晶屏片选控制信号)
PA5 BL(液晶屏背光控制信号)
  • 程序注意

显示字符串函数:Show_Str

16号中文取模

24号中文取模

32号中文取模

TFT_LCD.h
TFT_LCD.c
TFT_LCD.h
TFT_LCD.c
TFT_LCD.h
TFT_LCD.c
TFT_LCD.h
TFT_LCD.c
TFT_LCD.h
TFT_LCD.c
TFT_LCD.h
TFT_LCD.c