为了更好的实现我们的贪吃蛇游戏系统,我们需要对项目进行功能分析,利于我们对整个系统的分析、架构。
首先,对于整个游戏系统,我们需要界面来引导我们的用户进行下一步的操作,这里我设想的界面包含有开始界面、模式选择界面、难度选择、游戏失败、游戏成功等界面,用户通过按键进入不同的界面。
然后,就是整个系统的重点,对于贪吃蛇的控制该具备怎样的功能。这里,我选择了通过不同的难度来决定我们进入的贪吃蛇模块不同。
最后,就是我们如何将需要显示的数据给到我们的WS2812B,这就涉及到我们的WS2812B的接口模块。此外,还有我们的按键模块用于界面选择及蛇身控制。
为了满足以上需求,本文设计的贪吃蛇游戏系统其主要功能包括以下模块:
这里我们会设计四个滚动的界面,包括有START、MODEL、WIN、LOSE。三个静态界面,包括1、2、3,用于选择我们的难度系数,数字越大难度系数越高。难度一的游戏界面,只是将蛇的速度加快了,没有障碍物。难度二的情况下是只加了障碍物,没有加快蛇的移动速度。难度三则是既加速了蛇的移动速度也加入了障碍物的限制。
首先,上电之后WS2812B滚动显示我们的START字符,按下任意键后进入模式选择界面。接着按下任意键进入难度选择界面,通过按键一可以来回切换选择的难度,
按键二按下即看作选择该难度,随即指示我们的贪吃蛇控制模块开始工作。
然后,通过对贪吃蛇控制模块返回的游戏失败或游戏失败信号,我们的界面模块跳转滚动显示WIN或LOSE字符,如果需要开启新一轮游戏按下任意键即可回到最初的START字符滚动界面进行下一步操作即可。
贪吃蛇的控制模块
对于贪吃蛇的控制,我们需要根据界面模块给到我们的难度系数来决定我们的贪吃蛇移动速度、以及是否有路障。
首先,我们需要通过按键控制蛇的方向。这里我只采用了两个按键进行操控,根据蛇的移动方向,我们按键控制的转向不同,便于我们对蛇身的操控。
然后,就是我们的蛇头在吃到苹果之后,蛇身会变长,此时新的苹果产生,在蛇身达到8时,我们认为其通关。如果我们的蛇撞到墙、自身或障碍物则判断游戏失败。
WS2812B的接口模块
通过WS2812B的接口模块可以将我们的24位宽的RGB颜色数据按照一定的0、1码型方波信号将信号传输到我们的WS2812B用于驱动显示,对WS2812B的具体介绍看下一章的详细介绍。接口模块需要外部给到24位的颜色数据,以及是否需要复位的信号即可,接口模块输出方波信号以及24bit数据发送结束信号用于指示我们可以发送下一个颜色数据了。
通过按键模块实现与WS2812B的交互,一般情况下我们的按键任意键有效,直接进行界面的切换。只有两种情况下需要区别;
其一,在我们的难度选择界面,通过我们的按键一实现难度的切换,按键二进行确定进入该难度游戏。
其二,在我们的贪吃蛇游戏界面,我们通过按键一和按键三对贪吃蛇进行控制。贪吃蛇的移动方向不同,两个按键的移动方向不同。例如,在蛇头向右移动时,按键三则为向上转向,按键一为向下转向。
综合上面几个模块,项目总体设计就是通过按键对界面以及蛇身进行控制,在选择不同的难度系数后进入不同的游戏界面,在游戏通关或者游戏失败回到对应界面滚动显示相应的字符。字符的显示以及蛇身的数据通过接口模块进行实时输出。
针对本项目,我个人认为对于WS2812的界面滚动显示是我在这个项目里较为有难度的一个地方。我设想的效果是上电界面处于滚动显示START字符状态,任意按键按下后,其仍旧处于滚动显示其他界面的效果,这需要我们对一些零碎的条件考虑清楚。但对于整体界面滚动思想不变的肯定是每一帧将我们的显示数据按照行(向上或者向下滚动)或者列(向左或者向右滚动)将我们要显示的数据进行滑动即可。接下来的图1.1向我们展示了界面滑动的思想:
上面的红色表示我们的界面初始数据,下一帧我们的界面显示串口向右移动一列就是蓝色方框下的数据,依次类推实现界面的滚动。
贪吃蛇的控制是我们项目的重中之中,在贪吃蛇控制模块我们首先需要考虑的有一下几个方面:
正常情况下我们对蛇的显示难度不高,棘手的地方是在我们吃到苹果后我们应该如何加长我们的蛇身进行显示。这里我初步设想到的方法是只对蛇头进行控制,其余蛇身的位置我们进行打拍寄存,根据我们的长度来决定该蛇身位置是否亮起。
蛇的方向
蛇的方向相对就比较好处理,可以使用状态机标记我的蛇头当前的方向,不同的方向蛇头移动的步长大小不同。可以设定其初始位置为中间,以防止一开始就撞墙,初始方向为右,按键按下改变状态,同时改变我们的蛇头移动步长实现对蛇的移动方向的控制。
苹果的随机产生
这里我们可以通过一个计数器一直在0至63的范围进行计数,等到我们的蛇头与我们的苹果位置重合时(吃食物),直接对当前的计数值取出作为我们新苹果的位置,这实际上也是随机的,但是我们需要考虑的是新苹果的位置会与我们的障碍物或蛇身重合的情况,后续再工程中再进行下一步的讨论。
游戏失败
在贪吃蛇游戏界面,如果我们的蛇头越过了我们WS2812B矩阵的边界、撞到自己的蛇身或者撞到障碍物则判为失败,给出一个标志信号回到我们的界面控制模块用于显示界面的切换。
首先,对于撞墙的情况,我们可以考虑如下情况。如果我们的蛇头已经位于右边界,如果这时候我们的蛇头方向还未改变,仍旧向右移动则判定为撞墙。其余边界的判断方法类似。
然后,对于障碍物则是只要我们的蛇头与我们的障碍物重合则判定为失败,不用考虑我们蛇头的方向。
最后,对于蛇身缠绕的情况,我考虑的是蛇头在撞到自己的身体后,同样游戏失败。这里我们需要注意的点是,蛇身至少得有五个以后才会出现蛇身撞到自己的情况。这里我们可以考虑在蛇身达到五个长度以后才对贪吃蛇判断是否撞到自身,撞到自身的条件可以看蛇头的下一位置是否是蛇身第五个后的任一位置即可。
游戏成功
在我们蛇头吃到苹果后,蛇身的长度会增加,在蛇身大于八的时候表示游戏通关。这指明,我们应该吃到8个苹果,最后包括蛇头应该是9个长度,才能赢得游戏。同时给出标志信号,指示我们的界面切换到WIN字符界面进行滚动。
速度控制
我们可以根据界面模块输入进来的难度选择信号对我们的帧刷新时间进行改变即可实现对蛇移动速度的改变。
路障添加
可以使用一个64位宽的寄存器,同样根据界面模块输入进来的难度选择信号对我们的寄存的值进行改变,全为0则无障碍,后续在位置计数器显示输出时进行处理即可。
这里我们可以根据WS2812B传输0、1的码型产生两个固定的方波,在我们的接口模块只需要判断外部输入的24位颜色数据的各位是0或1将对应的方波进行输出即可,并将对应的比特计数器加1,直至24位数据发送结束,返回一个标志信号指示新的一个像素点数据输入。
按键模块这里就相对而言比较简单了,我们只需要一个按键消抖模块对我们的外部输入信号进行消抖即可,消抖模块会给出消抖后的按键有效标志信号。用于指示我们的界面切换、难度系数选择、蛇头移动方向切换等操作。
本章节主要是描述了自己在第一次接触到这个项目时,自己对项目初步的一些设计分析,后续具体的数据处理以及操作,我们在后续实现过程中进行单独介绍,这里只是提供部分模块的设计分析。
WS2812B是一种智能控制LED光源,控制电路和RGB芯片集成在5050组件的封装中。它内部包括智能数字端口数据锁存器和信号整形放大驱动电路。还包括精密内部振荡器和电压可编程恒流控制部分,有效保证像素点光颜色高度一致。数据传输协议使用单个 NZR 通信模式。下面简单介绍其应用以及内部数据的处理流程:
像素上电复位后,DIN端口接收来自控制器的数据,第一个像素采集初始24位数据然后发送到内部数据锁存器,其余通过内部信号整形放大电路通过DO端口发送到下一个级联像素。传输后为每像素,信号减少24bit。像素采用自动整形传输技术,使像素级联数不受信号传输的限制,只取决于信号传输的速度。
全彩模组,LED全彩软/硬灯条,LED护栏管。
LED装饰照明,LED屏幕,室内/室外LED视频不规则屏幕,各种电子产品,电气设备大篷灯。
三原色每像素可实现256亮度显示,完成16777216彩色全彩显示,扫描频率为2KHz。
单线级联端口传输信号。
刷新率为30fps时,级联数不小于1024像素。
以800Kbps的速度发送数据。
在我们动态切换界面时,由于我们的WS2812B是8x8 的64个级联的RGB灯,我们不满足1024点,所以我们的刷新速率不能超过30帧/s(33.3ms)即我们在切换不同界面的内容时,不能快于33.3ms,否则可能出现显示效果的重叠。
下面这张图包含了WS2812B传输码型电平以及RGB LED灯的级联示意图。
图 2
通过对上面的码型分析得知,对于0码,我们的低电平时间要长于我们的高电平时间,1码则相反,对于复位则是全为低电平。右边的灯级联方法则是RGB 数据依次往下传输。具体对于传输0、1码的周期看下图:
这里我们的频率是1秒发送800kbit,那么发送一个bit的需要62次晶振,对于50M的晶振,周期则为1240ns。那么传输0码时要求我们的低电平维持时间长于高电平:我们设为低电平为940ns,计数为47,高电平为300ns,计数为 15。传输1码时要求我们的高电平维持时间长于低电平,与我们的0码相反即可。下图是我们的数据在经过每一个RGB LED灯后数据的变化示意图:
由上图结合我们前面的级联示意图分析可知,我们的数据是源源不断从din端口输入的,在经过前一个RGB LED灯后被截留了24bit的像素数据,其余数据依旧往后传输,就这样每经过一个RGB LED灯都会被截留,直至64个点全部有数据后一整张8*8的矩阵就显示成功了,如果这时需要显示新的一帧图像则需要进行刷新。
下图展示了RGB LED的24位数据组成,我们虽然习惯称之为RGB,实际上其内部数据的分布是以GRB构成的,数据传输顺序为GRB,并且先传的是高位数据。
本章主要是对WS2812B进行了简单的介绍,以及进行了小小的总结。通过本章的简单介绍使得读者能够对WS2812B有一个简单的认识,并知晓如何驱动我们的WS2812B及内部数据的处理流向,利于我们后续考虑如何去设计我们的WS2812B的接口模块较为合适。
下图是我们整个系统的模块框图,通过下图我们可以对整个系统的数据流向有一定的了解:
首先,我们的按键信号经过按键消抖模块后输出三个按键有效信号,该信号即可以控制我们的界面模块,也可以控制我们的贪吃蛇模块,这里面主要是通过状态机来进行区别。当我们处于paly状态,即游戏状态,我们的按键才会对我们的贪吃蛇模块具备有效的操作,否则用于我们的状态选择、游戏难度系数的选择。
然后,我们的界面模块根据按键引起的状态改变,输出不同的24位颜色数据给到我们的接口模块,但是贪吃蛇模块也会输出24位的数据,于是我在顶层对24位宽的两组数据进行了按位与操作,才传到接口模块进行下一步的处理,对于我们的一帧结束信号tx_24x64_done同样进行了或运算。
最后,还需要提的一点就是我们的游戏界面无论是在失败还是成功后,都会返回一个标志信号用于指示我们的界面模块用于状态切换。还有就是我们的接口模块会直接将对应0/1码的方波输出到WS2812B灯光矩阵,在发送完一个像素点后输出标志信号指示我们的贪吃蛇模块或者是界面控制模块更新下一个像素点的值。
下图展示的是我们的端口信号
这里的端口主要是用于将我们的工程进行一个汇总,将必要信号引出与我们的外设进行交互,不做过多解释。
这里由于我的贪吃蛇模块是在新的工程调试通过以后再进行封装的,所以两个模块输出的复位信号、数据信号均会驱动我们的接口进行工作,于是在顶层进行了一定的处理,但是两者是不会同时有效的,我们在界面模块通过状态进行了控制。
顶层大多是连线位置,但是由于我的界面模块和贪吃蛇控制模块是分开进行测试的,所以这里我们需要在顶层对给到接口模块的信号进行一定的处理。
首先,是对两个模块输出的24比特数据进行按位与的操作,但本质上两者是不会同时为显示的情况的,我们在界面模块的状态机中进行了限定,从上面仿真可以看出,最终给到接口模块的信号一定是有值的那一方。
然后,就是我们的复位信号,我们的界面模块与我们的贪吃蛇控制模块均可对接口模块进行复位,这里使用逻辑或,只要任一方的复位信号有效即进行复位,仿真图的效果也是如此,仿真通过。
下图展示的是我们的界面控制模块的模块框图:
针对上图,我来对该模块的具体实现思路进行一定的讲解,关于各端口的信号在我们的端口信号列表有详细解释。
对于界面模块,在不同的状态,我们需要滚动显示我们的字符,这就涉及到我们的数据怎么来的。我这里对数据的处理还和别人稍有不同,我是从字模软件提取出来的0/1的8x8的64位点阵,1代表该点需要我们点亮该位置,反之则为灭的状态。下面我来稍微介绍下我的取模方法:
首先,我需要设定好一定的字模格式,方便我们后面对数据的处理:
新建8x8的图像
点击生成对应字模
将对应字符的16进制转为对应的0、1序列,代表该位置是否为亮
最后,如果只是单纯的显示一张8*8的图片,直接将64位的0/1序列赋值给对应寄存器即可。在处理滚动界面时,我们还需要通过c语言将一长串的0/1序列转化为一行一个数据的格式,最后添加mif文件的说明,用于对ROM进行初始化即可得到我们界面的数据。于是,我将几个不同滚动界面需要显示的字符分为START、MODEL、WIN、LOSE存了四个ROM,三个静态界面则是直接用三个64位宽的寄存器进行寄存即可。我们通过状态判定应该从那个ROM获取我们的界面数据。
状态条件
首先,在我们的界面控制模块,我定义了有8个状态,包括有start、model、难度1、难度2、难度3、游戏状态、游戏胜利、游戏失败等状态。这里简单介绍下各个状态下的作用即可,具体状态跳转看下面的状态机即可:
Start状态:我们上电就是这个状态,界面开始滚动START字符,直至任意键按下。
Model状态:滚动显示我们的Model字符,直至任意键按下。
难度系数界面:分别静态显示1、2、3,代表游戏难度,直到按键2选中难度。
游戏状态:选中难度后进入我们的游戏界面,这时界面模块不再向WS2812B输出数据,由贪吃蛇控制模块工作。
游戏结束界面:贪吃模块在游戏失败或通关后返回标志信号指示我们的界面模块开始WIN或LOSE的滚动显示。
可移动帧数
对于界面的滚动显示,我们通过一个帧计数器来指示我们下一次对界面数据取出的开始位置。比如,我们在初始情况下,帧计数器为0,我们通过一帧发送结束后,需要往后一列提取数据达到滚动的效果,这时我们将帧计数器加1,这时候就可以指示我们的数据开始位置为第二列开始提取8x8的矩阵。具体计算看下一点的地址计数器。
地址计数器
这里我通过任务调用的方式实现地址的计数,计算公式为:ROM地址 = ((当前位置计数值 - 行数*8) + 行数*跨行数) + 帧计数值。仔细计算后会发现其提取出的数据刚好为8x8的矩阵,并且随着帧计数值地址向右滑动,从而使得数据产生一列的偏差。
读使能信号
在我们的界面模块主要是以读使能信号作为主要的驱动的,其是阶段性的高低电平。我们需要在处于游戏玩耍状态、界面跳转时将其置低(这时需要复位)、一帧显示结束等情况下置低,在我们的复位计数器达到最大值时置1指示我们开始新的一帧数据计数。
位置计数器
在我们的界面模块中有8x8的点阵计数器,在0到64之间重复往返的进行计数,在记到我们64的数值时计数器维持不变,直到复位结束信号才开始新一轮计数。在我们记到的该位置坐标值下,判断我们64位宽的data数据的位为1时,给到24位的颜色值,否则为24位的0。
下图是我们的界面模块的端口信号列表:
输入信号tx_done_flag指示我们的位置计数器计数,开始下一个位置的像素数据传输。g_over和g_win是从我们的游戏界面返回的游戏状态,指示我们的界面开始滚动。通过输出难度系数来指示我们的游戏界面开启,任一难度有效即为被选中,所以地下的en_snake使能信号可以丢弃。
下图是界面模块的状态转移图,简单描述了状态跳转过程
一般情况下是任意键进行界面切换,只有在难度模式选择是需要特定键进行切换难度和选中难度系数开始游戏玩耍界面,后续根据游戏状态切换显示结束界面即可。
然后根据贪吃蛇控制模块返回的游戏结束标志信号决定是跳转到游戏成功还是失败状态。
帧刷新时序图
我们这里的帧刷新计数器用于指示我们从ROM中读数据的地址,用于向右滑动,从而引起数据滚动的效果。然而,这里的帧计数器仅在我们的START、MODEL、WIN、LOSE等界面需要滚动的状态下才会计数。这里的帧计数器由一个帧计数end_cnt作为驱动,在一帧数据显示完毕,产生标志信号,帧计数器加1。
读使能信号
这个信号是作为这个界面的驱动信号,他为高电平时这个模块的信号均开始工作状态。此外,我们需要在界面切换、游戏状态以及一帧数据传输结束的时候对其置地进行我们的复位操作,在我们的复位时间到达后将其拉高继续开始工作。
状态跳转仿真
此处的仿真我只是模拟进入了游戏状态,从仿真图上看整体的状态跳转没有问题。我们按下两次任意键进入难度系数选择界面,然后按下按键2进入游戏状态。
帧计数器仿真
由上面的仿真图得知,在我们的状态处于START状态时,这时我们的界面处于滚动状态,我们需要通过帧计数来改变我们从ROM中取出数据的地址,仿真的结果和我们设想的一致,在计数器end_cnt有效时,帧计数器加1,仿真通过。
Rden读使能信号仿真
**在我们的rden信号高有效时,**上面的仿真指明了我们的当前的状态为START状态,这时候界面滚动显示我们的START字符,所以此时的数据来源应该是从start的rom中提取。仿真图中,我们的q_r的数据来源也与我们的start_q的数据一致,仿真通过。
下图是我们贪吃蛇控制模块的模块框图:
下面我将对贪吃蛇控制模块以下的几个重点进行讲解该模块的设计思路:
状态机的设定
为了后续更好的操作,我这里设置的状态较多,包括有IDLE、上、下、左、右、win、lose等。我们上电初始后一直处于我们的IDLE状态,等待我们的难度选择信号任一有效便进入游戏状态。
游戏状态中通过按键控制控制蛇头的移动方向,上、下、左、右四个状态下蛇头移动的步长不同来调整转向。在我们的长度大于8时则游戏通关,否则在撞墙、障碍物或自身后游戏失败。
苹果的随机产生
这里和我们原先设想的一致,通过一个计数器一直在0至63之间进行计数,在我们的蛇头位置与我们的苹果位置重合的时候,取出当前的计数值作为我们新苹果的位置。在含有障碍的游戏状态下还需要判定该位置是否与我们的障碍寄存器对应位是否为1(障碍需要亮起),为1的话便是重合了,我们随机加上一个数即可。
蛇身的显示
这里我们只需要对蛇头的移动进行控制即可,后续的蛇身通过一级级的打拍即可。然后在我们产生刷新标志信号后,此时在对我们的WS2812B进行复位操作,这时我们通过for循环对新的一帧该亮的长度的坐标的置为1即可,后续根据64位宽的信号来判断是否亮起。
游戏失败
这里也和我们的设计差不多,主要分三种情况:
撞墙: 在我们的蛇头处于边界的时候,我们才对该情况进行判定,如果位于右边界下一帧依旧向右移动则判定为撞墙,游戏结束。
障碍: 只要我们的蛇头与我们的障碍物位置重合即判定游戏失败。
蛇身:我们只需要对蛇头的下一帧是否等于我们的第五个以后的任一蛇身位置即判定为游戏失败。
游戏成功
这里我们通过当前贪吃蛇的长度进行判定,我们每吃到一个苹果贪吃蛇蛇身就会增加1个长度,直至蛇身大于8即判定游戏成功。
障碍即速度切换
我在初始状态对这两个状态进行了初始设定,通过判断模式选择的情况来对数据进行改变。例如,我们的速度切换,初始设定为慢速,直到我们的难度一或难度三被选中的情况下,改变我们的刷新时间,其余情况保持不变,即可实现切换。
下图是我们的贪吃蛇控制模块的端口列表:
这里我们需要注意的一点就是我们的贪吃蛇模块需要在满足游戏失败或成功时,给出标志信号指示界面模块工作。此外,像素数据及复位刷新信号给到接口模块输出。
贪吃蛇模块的状态时序图:
从上图分析,我们任一选中难度系数进入游戏界面,均会触发我们的贪吃蛇控制模块开始工作。进入后初始为向右移动的状态,通过两个按键进行状态的改变,在我们的长度大于8时跳到win状态并维持一个时钟周期回到IDLE,游戏失败也是一样。
贪吃蛇控制模块部分关键信号
由上面部分关键时序图可以帮助我们对一些细节的信号理清,我们通过比较蛇头与我们的苹果位置判断吃苹果的信号是否有效,在吃苹果过后,我们的蛇身长度加1,直至大于8,产生game_win标志信号。Game_over则是判断我们的蛇头与我们的障碍物是否重合以及边界的处理。
对于速度与障碍则由上可知,我们均对其进行了初始化,在选中一定难度系数过后,我们改变变量的值即可,直至下一个难度被选中。
由上面的仿真分析有,我们的贪吃蛇控制模块在初始状态下为IDLE状态,直到我们的难度系数被选中,我们默认跳转到向右移动的状态,接着继续按下按键1将我们的蛇头移动方向改变为向下,与我们仿真效果一致,仿真通过。
撞墙仿真
从上面的仿真可以看到我们的蛇头比我快了一帧,本应该在23的时候就判断撞墙的,后续在对比显示的数据得到的结论是我的蛇头比显示的数据快了一帧,后面我就将蛇头打了一拍,用打过拍的蛇头数据来判断撞墙、障碍物以及自身的信号。
同样,下面我会分成以下几点对接口模块进行解释:
从前面的方案设计及对WS2812B的简介,我们可以轻松对接口模块的码型进行设计。这里我们设定的周期为1240ns,即需要计数62次。根据不同码型高低电平占比不同,这里我们将计数值分为47与15。我们寄存两个码型信号,以1码为例,则在计数值小于47时均为高电平,其余为低电平。
由于前面我只是显示了单张图片,所以在接口模块这里我只复位了300us,后续其余模块需要在复位后计时一定的刷新周期再开始新数据的传输。
接下来我们继续介绍接口模块,这里我通过一个计数对复位进行设计,外部信号输入一个复位标志信号后,对计数器清零,此时计数值小于300us,设置输出为低电平,直到计数值大于等于我们的300us保持计数值不变。等待,下一个复位信号来临。
周期计数器的工作使能时间点则是我们的复位信号计数至一直保持在300us的阶段,因为这时说明其已经复位结束,可以输出对应数据的0/1码。
下图向我们展示的是接口模块的端口信号列表:
在WS282B接口模块我们通过tx_24x64_done信号来进行复位操作,复位结束,周期计数开始工作,通过判定24位的RGB数据的0/1来决定发送的0/1码型方波。
上面是接口模块的部分时序图,由上图分析可知。我们通过一个300us的计数器数值来驱动我们整个模块的,在其小于T_300us时,我们处于复位不进行任何操作。只有在其维持为300us的数值时,我们的周期计数器开始工作,并产生对应的0/1码的方波,比特计数器从高到低,判断RGB数据的对应位是0或1,将对应方波赋值给send_clk。我们的比特计数器在周期计数器计满62个周期后,逐渐减1,直至为0说明24位的像素数据发送结束。
复位及周期计数器仿真
对上面的仿真图进行观察可以看到我们的周期计数在复位计数器记到最大值我们的周期计数器才开始工作,同时开始产生0/1码型的方波。
比特数据输出仿真
我们的24位像素数据采用的是高位先发的顺序,所以先从23位开始,但是我们这里仿真的数据刚好全为0,那么我们的输出方波信号应该于我们仿真的0码型一致,仿真的效果也确是一致的,仿真通过。
贪吃蛇总体操作演示
难度系数为一的贪吃蛇演示通关
难度系数为三的贪吃蛇演示撞墙
总体项目来看不是很难,代码改着改着发现工程还是有瑕疵,舌头在撞到边界的时候感觉还是有些许延时,不能马上切换,整个工程显得不是那么流畅,由于时间关系这里就不再修改了。
源码: https://github.com/no1jiangjiang/ws2812b_snake