目录
概述
用 CH549 在 LCD 屏上显示表情,用电位器可以调节它的心情。
赶时间的同学可以只看材料、接线和总结。
另外,东为此项目制作了视频
材料
名称 | 数量 |
CH549DB | 1 |
面包板 | 1 |
电位器 | 1 |
LCD 屏 | 1 |
公-母杜邦线 | 7 |
公-公杜邦线 | 2 |
使用 ILI9225主控 分辨率 176*220 的 LCD;
也可以更改 LCD 库的配置后使用 ST7789 主控的 LCD。见总结。
接线

程序
流程图

初始化
我们用到了 SPI 接口的 LCD 和 CH549 的 ADC ,需要在 main()
函数中初始化它们
CfgFsys(); //单片机主频配置
SPIMasterModeSet(3); //SPI 主机模式设置
SPI_CK_SET(2); //设置 SPI 分频
LCD_init(); //初始化LCD
LCD_setDir(3); //屏幕显示方向 目前(库版本0.1.1)仅对 ILI9225 有效
ADC_ExInit( 3 ); //初始化ADC
程序开始运行时,屏幕上可能残留有一些图案,我们要给整个屏幕填充底色来擦除它们。
先使用 LCD_setColor()
函数设置颜色,再用 LCD_fill()
函数填充
LCD_setColor(WHITE);
LCD_fill(0, 0, LCD_parameter.width-1, LCD_parameter.height-1); //屏幕填充白色
画脸
我们只用嘴巴来表现心情变化,所以脸和眼睛画一次就可以了。
脸和眼睛都是实心圆,需要先用 LCD_setColor()
设置颜色,然后用 LCD_fillCircle()
填充。
圆的大小和位置怎么确定呢?
慢慢调就行。
我希望脸在屏幕正中,四周有一点留白。所以它的中心坐标为
(\frac {1}{2}宽,\frac {1}{2}高)
直径为
\frac {4}{5}高
直径以高为参考是考虑到这次用的屏幕是横屏的、高比宽小。参考要以最小的为准,否则就容易画出界。
代码中的圆心坐标和半径没有用坐标值来表示,而是用液晶屏的宽高来计算。这样即使屏幕换了,画脸的代码大概率还能用。
画眼睛的方法与画脸类似,不过参考变成了脸的半径和位置,已经与屏幕的尺寸无关了。
/*画脸函数*/
void showFace(void)
{
UINT16 rFace =LCD_parameter.height *2 / 5; //脸对应的圆的半径
UINT16 xFace = (LCD_parameter.width) / 2; //脸对应圆的圆心x轴坐标
UINT16 yFace = (LCD_parameter.height) / 2; //y轴坐标
LCD_setColor(FACE_COLOR); //设置脸颜色
LCD_fillCircle(xFace, yFace, rFace); //画脸(x坐标,y坐标,半径)
UINT16 rEye = rFace / 7 ; //眼睛对应圆的半径
UINT16 xEyeOffset = rFace/3; //眼睛x轴偏移
UINT16 yEye = yFace + rFace/4; //眼睛y轴坐标
LCD_setColor(EYE_COLOR); //眼睛颜色
LCD_fillCircle(xFace+xEyeOffset, yEye, rEye); //画右眼
LCD_fillCircle(xFace-xEyeOffset, yEye, rEye); //画左眼
}

嘴巴
嘴巴是这个表情的灵魂。嘴巴怎么画决定了表情是笑是哭还是面无表情或者是诡异惊悚。
嘴角上扬可以表示开心,嘴角向下可以表示难过。
我们猜想可以用嘴角上扬或者嘴角向下的程度来表现开心或难过的程度。
那么如何控制嘴角的弧度呢?
嘴巴的函数
我们知道二次函数 y = ax2 + bx + h 的曲线在顶点附近是弧形的。
而 a
控制着曲线的弧度:a>0, 曲线上扬;a<0, 曲线向下弯;a 绝对值越大,曲线越弯曲。
二次函数的特性非常适合做嘴。
二次函数的 a,图片来自搜狗百科
但使用 y = ax2 + bx + h 的解析式里没有直接表现曲线的顶点位置,不利于控制。
所以我们改用顶点式 y = a ( x - b )2 + h, 式中 b 为顶点 x 坐标,h为顶点 y 坐标。
二次函数的顶点式
我们先来构造这个嘴巴的函数:
int mouthCurve(int x,int mood) {
float a = MOUTHARC*mood; //嘴巴弧度
int h = LCD_parameter.height*(MOUTH_Y-MOUTH_AMPLITUDE*mood); //嘴巴顶点y
x -= MOUTH_X ; //嘴巴顶点x
return (a * x * x + h);
}
函数的输入参数除 x
以外,还有 表示心情的 mood
。mood
用于计算曲线的 a
和 h
。
为了保持嘴巴在合适的位置,h
需要跟随嘴巴弧度变动。
函数中 MOUTH_X
,MOUTH_Y
是预定义的嘴巴坐标(独立于脸的位置以便实现歪嘴);
MOUTHARC
和 MOUTH_AMPLITUDE
分别是控制嘴巴弧度和顶点上下移动幅度的预定义参数,反复试验得出。
画嘴巴
有了嘴巴函数,就可以画嘴了:
for (x = MOUTH_X - ABS(mood); x <= MOUTH_X + ABS(mood); x++)
{
int y =mouthCurve(x,mood);
LCD_fill(x,y,x,y+3);
}
这实际是对 MOUTH_X - ABS(mood)
开始,到MOUTH_X + ABS(mood)
间的 x
值,求出嘴巴的 y 坐标;然后在该位置向上画一条长 4 个像素的竖线 (LCD_fill(x,y,x,y+3)
),使嘴巴的线粗为4。
此处画的是一个对称的嘴,所以循环从 x = MOUTH_X - ABS(mood)
开始,到MOUTH_X + ABS(mood)
结束,嘴巴宽度为 2*mood
。作者觉得嘴巴的宽度也会影响心情,所以让它跟 mood
一起改变。
mood
从 ADC 采样值算出:
mood=(getADC(ADC_CH)-ADCMAX/2) * ADC2MOOD;
由于本次使用的液晶没有引出刷新同步信号,我们刷新屏幕的时候容易出现裂帧的现象。
这个时候快速刷新会使内容闪烁,所以我们需要减少不必要的刷新。
每当心情改变的时候,我们就要擦除旧的嘴巴,画上新的嘴巴;所以我们要记住上一次的心情。
综上,画嘴巴的函数如下:
int lastMood= 0; // 记录上次的心情
while (1) {
int mood;
mood=(getADC(ADC_CH)-ADCMAX/2) * ADC2MOOD;
// 画嘴巴
if(ABS(mood - lastMood)>1){ // 心情波动超过 1 时才刷新
int x;
// 擦除以前的嘴
LCD_setColor(FACE_COLOR);
for (x = MOUTH_X - ABS(lastMood); x <= MOUTH_X + ABS(lastMood); x++)
{
int y =mouthCurve(x,lastMood);
LCD_fill(x,y,x,y+3);
}
lastMood = mood; // 更新心情
// 根据心情画现在的嘴
LCD_setColor(MOUTH_COLOR);
for (x = MOUTH_X - ABS(mood); x <= MOUTH_X + ABS(mood); x++)
{
int y =mouthCurve(x,mood);
LCD_fill(x,y,x,y+3);
}
mDelaymS(50);
}
}
总结
有点不开心的效果
本文中的项目工程放在 Gitee 仓库上:
EmoticonAdjustable
外壳的 3D 模型也将放入仓库中
如需使用 ST7789 主控的 LCD 屏幕(分辨率240*240),需将 CH549_LCD.h
的
//实际使用的 LCD 型号与分辨率
#define LCD_MODEL (LCD_ILI9225)
#define LCD_W 176
#define LCD_H 220
改为
//实际使用的 LCD 型号与分辨率
#define LCD_MODEL (LCD_ST7789)
#define LCD_W 240
#define LCD_H 240
此外,下列引脚如果 LCD 有就必须接,没有就不接
//LCD 的 SPI 接口定义
#define LCD_BLK (P3_2) //Backlight 背光控制
#define LCD_RES (P3_0) //Reset 复位
#define LCD_CS (P3_3)
发自 ST7789 的高兴
