AI脑洞板
完整教案
自学课件
第 5 课 · 共 5/51 课

如何在这块屏幕上绘图 坐标、画点和屏幕总线

课时
待定
器材
ESP32-S3 触摸屏板 + USB 线
前置知识
无
配套代码
见项目 demo 目录

你好,我是吴老师。

这份是给你自己读的——不用等我讲,你一段一段往下看,看到让你停下来的地方,就照着做一遍。看不懂的整段复制给 AI,加一句"请一次只解释一个概念,讲完问我一个小问题"。

读完之后,你应该能:说出屏幕上任意一个点的坐标;用画线、画方框、画圆拼出一幅简单的图;解释为什么"只改一小块"比"整屏重画"快;说出 SPI 那几根线各自干什么;说出 LVGL 是什么、它和手绘各适合做什么。

用时:60–90 分钟。准备:板子、数据线、已经接入板卡 Skill 的 WorkBuddy。


一、今天只做一件很小的事

第 3 课,我们让整块屏幕变成一种颜色。你按一下 BOOT,七万六千八百个小点全都换了色,挺痛快的。

今天反过来,我们只做一件很小的事:点亮其中的一个点。

你可能会说,这有什么难的?整屏都变了,变一个点还不是小菜一碟?

那我就要问你了:屏幕上有七万六千八百个点,你要点亮哪一个?

"最中间那个。"——不行,板子听不懂"中间"。

板子只认数字。你得给它一个号码,这个号码叫坐标。

所以今天真正的问题不是"怎么画",而是——怎么说清楚我要哪一个。

二、坐标:就像电影院找座位

屏幕坐标系示意图

怎么给七万多个点编号?总不能一个一个起名字吧。

你可以把它想成一张电影票:上面写着"第几排、第几座"。两个数一确定,座位就唯一了。

屏幕也一样,只是这两个数有自己的名字:

  • X:从左边开始数。最左边是 0,往右挪一格加 1,一直数到 239
  • Y:从上边开始数。最上边是 0,往下挪一格加 1,一直数到 319

原点 (0, 0) 就在左上角。

位置XY像电影票上的
左上角00第 1 排第 1 座
右上角2390第 1 排最后一座
左下角0319最后一排第 1 座
右下角239319最后一排最后一座
正中间120160中间靠前一点

⚠️ 这里有个坑,几乎每个人都会踩

屏幕的 Y 是往下增长的,Y 越大越靠屏幕下方。这和你数学课上的坐标系正好相反。

数学课上,Y 往上是正方向;屏幕上,Y 往下才是正方向。

是不是谁写错了?不是。这只是约定不同。

屏幕为什么要这么定?你想想画面是怎么送过去的:从左上角那个点开始,一个一个数过去,第一行数完数第二行——数数的顺序,就决定了 Y 的方向。

你可以这样记:屏幕不是一张坐标纸,是一篇从左上角开始读的文章。

(顺带一提:正中间为什么是 (120, 160) 而不是 (120, 159.5)?因为点的编号是从 0 开始的整数,没有"半个点"这种东西。)

任务 1:点亮五个点

请新建一个屏幕坐标实验:
开机后整屏填黑,然后在下面五个位置各画一个方块:
左上角 (0,0)、右上角 (239,0)、左下角 (0,319)、右下角 (239,319)、正中间 (120,160)。
方块边长 ___ 个像素(你自己定,参考 8);
颜色用 ___(你自己定,参考亮黄),亮度不超过 80。
请说明你用的画方块函数叫什么、坐标是怎么传进去的。
编译烧录前先核对目标端口,保留旧程序。
想好了再点开:我们的参考数字

这不是标准答案。 参考值:边长 8 像素,颜色 亮黄。

边长为什么值得挑一挑? 它决定了你等一下能不能看出问题:

  • 填 8:右下角那块的缺角看得清清楚楚;
  • 填 4:太小了,缺的那一角不明显,这个实验就白做了;
  • 填 40:太大,五块会互相挨上,反而看不清楚。

这一节的重点不是"画出来",是看出右下角缺了一角。边长选小了,你就看不到。

颜色随便选,只有一个要求:和黑底对比要强。深蓝、深红在黑底上几乎看不见。

先猜再烧。 五个都出现以后,重点看右下角那个——它是不是缺了一角?

三、板子不会提醒你画到外面去了

我们做个小实验。假如我让板子在 (239, 319) 画一个边长 8 的方块。

这个方块要占横向 239 到 246、纵向 319 到 326。可是——屏幕最右边只到 239,最下边只到 319。

结果是什么?

方块缺了一大角。

而且板子不报错,不提醒,不闪灯。它只是把屏幕外面那部分安安静静地丢掉了。

这像什么?像你在作业本的最后一格写了个很大的字,超出纸边的那一半,本子上不会出现,老师也不会提醒你——它就那么没了。

所以在我们这门课上,检查的标准不只要看"有没有画出来",还要看"是不是和你预想的一模一样"。

你可能会遇到的情况先怀疑什么
方块缺了一角坐标加上宽高以后,超出 240×320 了吗?
东西出现在上下颠倒的位置Y 的方向是不是记反了?
左右反了屏幕方向和 Skill 里的配置一致吗?
什么都没出来是不是先用整屏黑色把它盖住了?顺序对吗?

四、不只是点:还能画线、方框和圆

会画点了,就能画更多东西。有意思的是:画的东西不一样,你要告诉板子的数字个数也不一样。

四种图形需要的参数

一条线:给板子两个点

画一个点要给 2 个数,画一条线要给 4 个数——起点一对,终点一对。

中间那些点呢?不用你操心。你给了两头,板子自己算出中间该点亮哪些点。就像你在一张纸上点了两个点,然后用尺子一连。

一个圆:只用圆规就够了

这里有个地方值得你想一想:为什么画圆只要 3 个数?

你想想圆规是怎么用的:把针尖扎在一个点上(圆心),把铅笔腿张开到某个宽度(半径),转一圈——圆就出来了。

圆规不需要你告诉它"第 10 行第 20 列要画、第 10 行第 21 列要画……"。它只需要圆心 + 半径。

板子也一样。圆的定义就是"到一个点的距离等于某个长度的所有点"。规则一定,圆就唯一了。

这个比喻有个地方要修正:圆规是"画"出来的,板子是"判断"出来的。它会拿每一个点去算:这个点到圆心的距离等于半径吗?等于就点亮,不等于就放过。板子手里只有加减乘除,它并不"懂"什么是圆。

把规则交给板子,它替你把点算出来——这是编程里特别省力的一个思路。

一张表收好

要画什么要给板子几个数分别是函数
一个点2X、YdrawPixel
一条线4起点 X/Y、终点 X/YdrawLine
空心方框4起点 X/Y、宽、高drawRect
实心方块4起点 X/Y、宽、高fillRect
圆3圆心 X/Y、半径drawCircle / fillCircle

(别忘了,这几个数之外,每次都还要再给一个颜色。)

任务 2:把四种图形都画一遍

请新建一个图形实验,黑底上画出这些图形方便对比:
1. 从 (0,0) 到 (239,319) 画一条 ___ 颜色的对角线;
2. 在 (60,60) 画一个 ___×___ 的红色方框,只画边框;
3. 在 (60,140) 画一个**同样大小**的红色方块,填满;
4. 在 (170,80) 画一个半径 ___ 的黄色圆,只画边框;
5. 在 (170,220) 画一个**同样半径**的黄色圆,填满。

注意 2 和 3 必须一样大、4 和 5 必须一样大 —— 这样比出来的差别才是"描边还是填充"造成的。
亮度都不超过 80。请用中文注释,保留旧程序。
想好了再点开:我们的参考数字

这不是标准答案。 参考值:方框 40×40,圆半径 30,对角线颜色 青色。

为什么 2 和 3 必须一样大? 这是"对照实验"的老规矩(第 3 课讲过):只能改一个条件。

  • 一个 40×40 的空心方框,和一个 50×50 的实心方块 —— 就算看出不一样, 你也说不清是"描边和填充"的差别,还是"大小不同"的差别。
  • 一样大,差别就只剩一个来源:描边还是填充。

看到空心圆的边有点毛糙?那是对的 —— 屏幕上的点是一格一格的, 画不出真正平滑的曲线,只能给一条锯齿状的"差不多是圆的线"。

凑近看一眼那个空心圆的边:是不是有点毛糙?

为什么?因为屏幕上的点是一格一格的,它画不出行云流水的曲线——只能给你一条锯齿状的"差不多是圆的线"。

五、描边和填充:看起来一样大,干的活差 10 倍

描边与填充的数据量对比

drawRect 只画四条边(描边),fillRect 把里面全填上(填充)。

它们要的数一样多,画出来的点却差很多。边长 40 的方框:

画法像什么要画多少个点要送多少字节
描边 drawRect用彩笔勾个边框约 156 个约 312 字节
填充 fillRect把里面全涂满1,600 个3,200 字节

就像画画:勾个轮廓几秒钟,把里面涂满要花好几分钟。屏幕上看起来差不多大,板子干的活差了约 10 倍。

六、所以,改多少就送多少

要做的事要送的数据打个比方
给一颗 RGB 灯换色3 字节递一张三个字的纸条
改屏幕上 1 个点2 字节比给灯换色还少
改 10×10 的小块200 字节抄一小段话
整块屏幕换色153,600 字节抄一本厚厚的书

第 3 课我们算过 15 万字节,你可能会以为"屏幕就是这么重"。不是的。

15 万是因为我们当时要求改整块屏幕。今天只改一个点,板子就只送 2 个字节。

改多少,就送多少。 这句话你先记住,第 6 课它会变成一条硬规矩。

任务 3:测一测,谁更快

请加上计时,分别测出这两件事各用了多少毫秒:
1. 整屏填充红蓝交替,一共 ___ 次;
2. 只填充中间 ___×___ 的小方块,红蓝交替**同样次数**。

结果同时显示在屏幕上、并通过串口打印出来。按 BOOT 可以重测一轮。
两次测试里,除了"填充范围",其它条件必须完全一样。
想好了再点开:我们的参考数字

这不是标准答案。 参考值:次数 100,小方块 40×40。

先自己算一下这两个数字意味着什么:

  • 整屏是 240×320 = 76,800 个点;
  • 40×40 的小方块是 1,600 个点;
  • 76,800 ÷ 1,600 = 48 倍。

所以你该预测:整屏用的时间大约是小方块的 48 倍。

次数也有讲究:

  • 填 100:能测出稳定的毫秒数;
  • 填 2:太快了,计时器可能显示 0 或 1 毫秒,什么都比不出来。

小方块改成 80×80 呢?点数变成 6,400,倍数就降到 12 倍。 方块越大,倍数越小——你可以改一个数字再测一次,验证这个规律。

先预测再烧录。 整屏 76,800 个点,小方块 1,600 个点——点数是 48 倍,那时间也会是 48 倍吗?

如果差得没那么多,想一想:这 100 次里,除了"送数据",还有什么在花时间?(提示:下命令、算颜色,也都要时间。)

七、数据走哪条路:SPI

好,现在知道要送什么了。接下来的问题是:这些东西是怎么从主控跑到屏幕上来的?

这条通道叫 SPI。你可以把它想成工厂里的一条传送带:

线的名字GPIO在传送带上扮演什么
SCLK 时钟12节拍器。它每响一下,带子往前推一格
MOSI 数据11传送带本身,一个字节一个字节往屏幕那头运
CS 片选10点名员。叫到谁,谁才接货
DC 命令/数据46贴在包裹上的标签:这是"指令"还是"货"

DC 这根线最巧

请你想一想:同一条传送带上运过去的字节,屏幕怎么知道它们是什么意思?

"准备接收"、"从第 120 列开始"、"这个点是红色"——这些都是不同的意思,可它们走的是同一条线。

所以每个包裹都要贴张标签。DC 就是这张标签:

  • 标签写命令 → 屏幕把它当指令,比如"从第几行开始"
  • 标签写数据 → 屏幕把它当颜色本身

这就是工程师常说的取舍:用一根线省下一根 GPIO,代价是每次发货都要先说一句"接下来是什么"。

还有一个坑:这条路只能往外走

SPI 本来还有一条反向的线叫 MISO(屏幕往主控送数据)。我们这块板上接是接了,但没打通。

这意味着:我们能给屏幕送东西,却不能把屏幕上的画面读回来。

就像一个只能往外寄信、收不到回信的邮筒。

所以以后你想把屏幕画面截个图存下来,不能指望"把屏幕上的点读出来",只能自己记住画了什么。这个坑以后做"你画我猜"的时候还会再遇到一次。

八、换一种画法:LVGL

前面七节,你一直在用"手绘"的方式:自己算坐标、自己选函数、自己一个一个点地画。

现在认识另一条路。

手绘 vs LVGL:一个自己算,一个说想法

先看一个对比

假设你要在屏幕正中间放一个按钮,上面写"开始"。

手绘的做法:

  1. 想好按钮多大——比如 120×40;
  2. 算出左上角坐标——横着居中就是 (240−120)÷2 = 60,竖着放中间就是 160−20 = 140;
  3. fillRect(60, 140, 120, 40, 蓝色) 把底填上;
  4. drawRect(60, 140, 120, 40, 白边) 描一圈边;
  5. 再算出文字该画在哪,把"开始"两个字画上去;
  6. 按钮被按下时要变个样子——你得自己重画一遍;
  7. 按钮要挪个位置——上面每一步全部重算。

LVGL 的做法:

lv_obj_t *btn = lv_btn_create(lv_scr_act());   // 造一个按钮
lv_obj_t *lbl = lv_label_create(btn);          // 按钮上放一行字
lv_label_set_text(lbl, "开始");                 // 字是"开始"
lv_obj_center(btn);                            // 居中

就这四行。注意最后一行——"居中"是你说的一句话,不是一个算出来的数字。

LVGL 到底是什么

LVGL 是一个界面工具箱(也叫"图形库""UI 框架")。

你可以把它想成一位很会做界面的助手:

  • 你说"我要一个按钮",它就给你一个按钮——你不需要知道按钮是多少个点拼出来的;
  • 你说"居中",它自己算坐标;
  • 你说"这里可以上下滑",它自己处理滑动、惯性、滚动条;
  • 屏幕什么时候刷新、重画哪一块,它自己决定。

前面手绘时你干的那些活——算坐标、描边、擦旧图、只重画变化的部分——LVGL 全替你做了。

前面算的那些账没有白算。第 3 课的 15 万字节、第五节"描边和填充差 10 倍"、第六节"改多少送多少"——LVGL 内部照样要做这些事,只是它替你做了。 你懂这些,画面卡的时候才知道该去哪儿找原因。

常用的 LVGL 指令

常用的 LVGL 指令

下面这些是 LVGL 最常用的一批。今天只要认得住名字、知道它是干什么的就够了,不用背。

① 开局(两条,缺一不可)

指令干什么
lv_init()启动 LVGL。开机时调用一次
lv_timer_handler()让 LVGL 干活。放在 loop() 里反复调用

lv_timer_handler() 是最容易被忘掉的一条。忘了它,屏幕会一动不动——因为 LVGL 根本没机会干活。

② 造东西(都要告诉它"放在谁里面")

指令造什么
lv_label_create(父)一行文字
lv_btn_create(父)一个按钮
lv_obj_create(父)一个空白容器(用来装别的)
lv_bar_create(父)一条进度条
lv_img_create(父)一张图片

③ 安排位置

指令干什么
lv_obj_set_pos(对象, x, y)放到指定坐标
lv_obj_center(对象)居中
lv_obj_align(对象, LV_ALIGN_TOP_MID, 0, 8)对齐到某条边,再加一点偏移
lv_obj_set_size(对象, 宽, 高)设置大小

④ 设置内容

指令干什么
lv_label_set_text(标签, "文字")设置文字
lv_label_set_text_fmt(标签, "第 %d 项", i)带数字的文字
lv_bar_set_value(进度条, 60, LV_ANIM_ON)设置进度值

⑤ 改外观

指令干什么
lv_obj_set_style_bg_color(对象, 颜色, 0)改背景色
lv_obj_set_style_text_font(对象, 字体, 0)改字体

它自己管的三件事

LVGL 替你管的三件事

这三件事最能说明"手绘和 LVGL 到底差在哪":

这件事手绘LVGL
按钮按下时变个样子你自己重画一遍自动(自带按下效果)
内容超出屏幕要能滑动自己算位移、自己模拟惯性说一句就行
屏幕什么时候刷新、重画哪块自己算、自己擦它自己决定

以后是不是都用 LVGL?

不是。两条路各有各的用处。

什么时候用手绘什么时候用 LVGL
画游戏画面(飞机、贪吃蛇)做界面(按钮、列表、菜单)
要精确控制每一个点页面元素多、要能滚动、要有标准外观
画面每帧都在变变化不快,但要好看、要好用

打个比方:

  • 手绘像自己拿笔画画——每一笔都在你手上,画什么像什么,但什么都要自己来;
  • LVGL 像用现成的积木搭——按钮、滑块、列表都是现成的,搭得快、长得正,但形状是它给的。

我们后面的游戏课(五子棋、贪吃蛇、飞机大战)都用手绘;做菜单、设置页、列表这类界面时,LVGL 能省很多力气。

什么时候用手绘,什么时候用 LVGL

什么时候才用得上它?

LVGL 要有触摸才真正好用——因为你搭出来的按钮,是要用手指去点的。

触摸我们还没学(第 8 课)。 所以今天先认识它、记住几条常用指令就够;第 8 课学完触摸,我们再回来用它做真正的界面。

不过——不点也能看。你可以先让 AI 搭一个界面出来,纯显示、不用点,先看看 LVGL 搭的东西长什么样。

请用 LVGL 8.3 给这块板搭一个纯显示的界面(ESP32-S3 + 2.8 寸 ILI9341,竖屏 240x320):
1. 顶部一行标题文字"我的第一个 LVGL 界面";
2. 中间一个按钮,按钮上写"点击我";
3. 底部一条进度条,值设成 ___(你自己定,参考 60)。

触摸还没学,所以这一版**按钮不用能点**,只要能看见就行。

请参考项目里已经跑通的 LVGL 示例(Example_34_lvgl_scroll_list)的初始化和配置,
特别是它怎么把 LVGL 和屏幕接起来的。别忘了 loop 里要有 lv_timer_handler()。
请用中文注释;烧录前先运行 arduino-cli board list 核对端口;另存一个新版本。
想好了再点开:我们的参考值

这不是标准答案。 参考值:进度条 60。

进度条的取值是 0 到 100,代表"完成了百分之多少"。所以:

  • 填 60:一看就是"过半了、还没满";
  • 填 100:是满的——好看,但看不出"进度"这个意思;
  • 填 0:是空的——同样看不出这条是个进度条。

想让它更像真进度条?让 AI 改成"每秒自动加一点,涨到 100 再回到 0"。 那样你就顺便看到 LVGL 的动画是怎么跑的——一行 lv_bar_set_value(..., LV_ANIM_ON) 的事。

如果界面没出来

现象先检查
屏幕一片黑/不动loop() 里有 lv_timer_handler() 吗?
编译报找不到 lvgl.hLVGL 库装了没?版本是 8.x 吗(别用 9.x)?
花屏、颜色不对lv_conf.h 的配置和示例一致吗?
出来一下就没了lv_scr_act() 拿到的屏幕对象对吗?

九、自检小测

先自己想,再点开对答案。

1. 屏幕的坐标原点在哪里?X 和 Y 各朝哪个方向增长?

原点在左上角。X 向右增长到 239,Y 向下增长到 319。

2. 在 (239, 319) 画一个边长 8 的方块,会完整显示吗?

不会。方块要占 239–246,超出屏幕的部分被丢掉,你会看到一个缺角的方块,而且板子不会报错。

3. 画一条线、一个方框、一个圆,分别要给板子几个数?

线 4 个(起点 X/Y + 终点 X/Y),方框 4 个(起点 X/Y + 宽高),圆 3 个(圆心 X/Y + 半径)。都还要再加一个颜色。

4. 画圆为什么只要圆心和半径?

因为圆的定义就是"到一个点距离等于半径的所有点"。规则一定,圆就唯一了。这就像用圆规:扎住一点、张开固定宽度、转一圈。

5. 40×40 的方框,描边和填充送的数据差多少?

描边约 156 个点 / 312 字节,填充 1,600 个点 / 3,200 字节,相差约 10 倍。

6. DC 这根线是干什么的?

告诉屏幕接下来送过去的字节是命令(比如"从第几行开始画")还是数据(颜色本身)。像包裹上的标签。

7. 为什么不能把屏幕上的画面读回来?

SPI 负责反向传输的 MISO(GPIO13)在这块板上没有真正打通,只能给屏幕送,不能从屏幕读。

8. LVGL 是什么?它和前面的"手绘"最大的区别在哪?

LVGL 是一个界面工具箱(图形库 / UI 框架)。

最大的区别:手绘要你自己算坐标、自己描边、自己擦旧图;LVGL 只要你说"我要什么"——"居中""可以上下滑""按钮带按下效果",这些它自己管。

所以手绘是"每一笔都在你手上",LVGL 是"用现成积木搭"。

9. 用 LVGL,`loop()` 里必须写哪一句?忘了会怎样?

lv_timer_handler()。它是让 LVGL 干活的那一句。

忘了它,LVGL 就没有机会处理刷新、动画和事件——屏幕会一动不动,而且不报错。这是新手最容易踩的坑。

10. 以后是不是所有画面都用 LVGL?

不是。两条路各有各的用处:

  • 手绘适合游戏画面(飞机、贪吃蛇)、要精确控制每个像素、每帧都在变的场合;
  • LVGL 适合做界面(按钮、列表、菜单)、元素多、要滚动、要标准外观的场合。

后面的游戏课都用手绘;做菜单和设置页时,LVGL 省力气。

十、还想玩点什么

方向可以描述给 AI 的效果值得继续想
像素画用方块拼出一个笑脸或房子你要怎样把"图案"翻译成一串坐标?
星空随机位置点亮几十个白点每次重画整屏,和只在旧点上盖黑色,哪种更好?
进度条底部长条按进度从左往右填充从 30% 涨到 31%,这一帧要改几个点?
心电图一条线从左到右移动,到头回到最左每一帧要擦掉什么、画上什么?
用 LVGL 搭个菜单一列按钮,选一个就变个颜色和手绘一个按钮比,省了哪几步?

挑战:坐标寻宝图

请做一个坐标寻宝游戏:
开机后随机藏一个 8×8 的方块(不显示)。我在串口输入 "x y"(如 "50 200"),
板子在我猜的位置画一个灰色标记,并按距离回答:
小于 ___ 回答"烫!",小于 ___ 回答"热",小于 ___ 回答"温",否则回答"冷"。
猜中(距离小于 8)时把宝藏显示成亮黄色,串口回答"找到了!你一共猜了__次"。
按 BOOT 开始新一轮。
想好了再点开:我们的参考数字

这不是标准答案。 参考值:20 / 60 / 120(三个数字必须从小到大)。

这三个数字决定的是游戏有多好猜。先想清楚:

  • 三个数都填得很大(比如 100/200/300):一猜就说"烫",几次就中了 —— 太简单,没意思;
  • 三个数都填得很小(比如 5/10/15):全屏几乎都是"冷",等于没有提示 —— 太难,只能瞎猜;
  • 参考值 20/60/120:把屏幕横着分成几个圈,"烫"的区域很小,得靠一点点逼近。

还有一个玩法可以想:提示能不能不只是"冷热",而是给个方向? 比如"往右下一点"。这样猜起来更快 —— 但那已经是另一套规则了,你来定。

玩完想一想这个:板子理解你在找东西吗?

不。它只是做了几次减法和比较。所有的"聪明"都来自你写下的规则——规则是你给的,不是它自己想出来的。

最后那道题留给下一课:让画面动起来,真正的难点不是"画",而是擦和只改该改的地方。


第 5 课 · 点亮一个点

第 5 课 · 点亮一个点

在黑底屏幕上点亮 5 个小方块,顺便看清"坐标"是怎么回事。

05坐标入门
第 5 课 · 画线、画方框、画圆

第 5 课 · 画线、画方框、画圆

屏幕上出现 5 个图形,看清每种图形要告诉板子几个数。

05图形描边与填充
第 5 课 · 谁画得慢(计时实验)

第 5 课 · 谁画得慢(计时实验)

整屏重画、小方块、空心圆、实心圆各做 100 次,比一比谁最慢。

05计时实验
第 5 课 · 坐标寻宝图

第 5 课 · 坐标寻宝图

屏幕里藏了一个宝贝,你在串口输入坐标,板子回答 烫 / 热 / 温 / 冷,直到找到它。

05坐标串口游戏