1 项目需求
目标:制作一个可切换功能的计算器
具体要求:
用LCD做界面,用4向摇杆和两个按键做输入,用姿态传感器切换功能,制作一款具有四种计算功能、通过姿态传感器可以切换功能的计算器,比如下图中的三个功能,再增加一个程序员常用到的制式转换器。
制作者可以自己定义4种不同的功能,旋转板子可以切换到不同功能的界面,用四向摇杆和按键搭配进行计算。
2 完成的功能及达到的性能
2.1 按键界面显示
点击运行,经过开屏动画后屏幕默认出现以下界面。
右上角黑框用于显示数字和字符;
黄色小点作用类似于鼠标,受摇杆控制;
白色字符均可以按下,显示在右上输出框。
2.2 切换界面
陀螺仪用于读取板卡姿态,以控制计算器的界面切换。
事先摆好姿态,再按下B键,计算机切换到相应界面,并不再随姿态变化;
四种界面如下:
普通模式:左前角在下
制式转换模式:右后角在下
幂与指数模式:左后角在下
三角函数模式:右前角在下
2.3 按键与摇杆输入
摇杆控制光标移动;
按下A键,对光标所在的按键进行操作;
“=”键按下,计算结果并输出到右上角输出框;
“ac”键按下,清空输出框;
其它键按下,输入字符到输出框里。
2.4 输入与计算
由于使用了python内置库中的eval()函数,其功能是执行字符串表达式并返回字符串的值,因此在输入上与普通计算机有所不同:
普通四则运算与普通计算机一致;
计算幂函数和指数函数时请参考以下格式,输入“5**2”表示求5的平方;
计算三角函数和其他函数时引入了内置math库,输入参考以下案例:
三角函数输入示例:
按下cos出现'math.cos(',
再按数字出现'math.cos(pi',
再按括号出现'math.cos(pi)',此时按’=’方可准确计算,注意为弧度制。
关于进制转换,参考以下案例:
选择想要的进制转换,例如db为dec->bin
按下db1,'bin('
按下数字,'bin(5'
按下db2,'bin(5)',此时按’=’方可准确计算。
3 实现思路
通过ADC读取电位计上的电压分压,确定摇杆x、y轴的位移,进而移动光标;
通过I2C协议获取陀螺仪重力感应传感器参数,并转化为x、y方向的偏移坐标,进而用板卡姿态决定计算界面的显示;
将界面上的字符str_display与实际的字符内容str_value联系起来,按下A键即输入到输出框。
4 实现过程
4.1 程序流程图
4.2 界面显示
通过spi协议,导入ST7789固件,在LCD屏上绘制图形界面。
class LCDdisplay:
def __init__(self):
print('display the mode')
def mode1(self):
display.fill_rect(0, 49, 240, 191, 0x0000)
display.fill_rect(0, 0, 47, 50, 0x0000)
display.hline(0, 192, 240, 0xFFFF)
display.hline(0, 144, 240, 0xFFFF)
display.hline(0, 96, 240, 0xFFFF)
display.hline(0, 48, 240, 0xFFFF)
display.vline(60, 0, 240, 0xFFFF)
display.vline(120, 48, 192, 0xFFFF)
display.vline(180, 48, 192, 0xFFFF)
display.text(font2, "=", 25, 200)
display.text(font2, "0", 85, 200)
display.text(font2, ".", 145, 200)
display.text(font2, "+", 205, 200)
…………
4.3 界面切换
获取MMA7660重力感应传感器参数:通过I2C协议获取陀螺仪重力感应传感器参数,并转化为x、y方向的偏移坐标,进而用板卡姿态决定计算界面的显示;
#陀螺仪驱动
i2c1 = I2C(1, scl=Pin(11), sda=Pin(10))
attitude = MMA7660(i2c1)
attitude.on(True)
#姿态接收
attitude_receive = bytearray(3)
r = [0 for x in range(3)]
#返回二补数
def twos_compliment(n, nbits):
sign_bit = 1 << nbits - 1
sign = 1 if n & sign_bit == 0 else -1
val = n & ~sign_bit if sign > 0 else sign * ((sign_bit << 1) - n)
return val
…………
attitude.getSample(attitude_receive)
#转换为陀螺仪偏移坐标
for i in range(3):
r[i] = twos_compliment(attitude_receive[i], 6)
x_bias = -(r[1]*4)
y_bias = r[0]*4-20
utime.sleep(0.5)
if x_bias>0 and y_bias>0:
#x>0,y>0,mode1
my_display.mode1()
mode_num = 1
…………
4.4 光标移动
通过ADC读取电位计上的电压分压,确定摇杆x、y轴的位移,进而移动光标。
#定义光标对象
class Cursor:
def __init__(self,x_coord,y_coord):
self.x_coord = x_coord
self.y_coord = y_coord
#使用前给x_coord和y_coord赋初值
def cursor_move(self):
xValue = xAxis.read_u16()
yValue = yAxis.read_u16()
if xValue <= 600 and self.x_coord>60:
self.x_coord = self.x_coord-60
elif xValue >= 60000 and self.x_coord<179:
self.x_coord = self.x_coord+60
if yValue <= 600 and ((self.x_coord<60 and self.y_coord>48) or (self.x_coord>60 and self.y_coord>96)):
self.y_coord = self.y_coord-48
elif yValue >= 60000 and self.y_coord<192:
self.y_coord = self.y_coord+48
…………
#光标的初始坐标与绘制
display.fill_rect(85, 152, 5, 5, 0xFFE0)
#光标移动
my_cursor.cursor_move()
if xBefore == my_cursor.x_coord and yBefore == my_cursor.y_coord:
display.fill_rect(my_cursor.x_coord, my_cursor.y_coord, 5, 5, 0xFFE0)
else:
#将原光标涂黑去除,绘制新光标
display.fill_rect(xBefore, yBefore, 5, 5, 0x0000)
display.fill_rect(my_cursor.x_coord, my_cursor.y_coord, 5, 5, 0xFFE0)
4.5 计算实现
对输入的字符串“formula”用内置的eval()函数计算并返回结果,并在输出框中显示。
solution = eval(self.formula)
display.fill_rect(62, 0, 175, 47, 0x0000)
display.text(font2, str(solution), 62, 0)
5 遇到的主要难题
5.1 按钮的坐标
按钮坐标、光标坐标、界面显示需要一一对应,即一个坐标值对应着一个字符,同时对应着字符的功能。为了将显示的字符与字符的实际内容对应,我创建了字典dict,以字符坐标为key,以实际内容为value,一一对应;但是还有光标坐标的问题,于是我利用字符格子的规则排列,给光标坐标赋初值,并且每次移动固定的数值,光标坐标就能始终与字符坐标对应。因为用了这种处理,我只能将每个界面的板式做的一样。
5.2 字符输入与计算的问题
普通计算机上的字符输入非常自由,可以在任意位置输入,也可以删去字符,数学函数的使用也很便捷;但是,首先我使用了eval()函数对字符串做统一处理,算式中会出现math.cos(pi)之类的字样;其次,插入字符和删除字符设计麻烦的字符串操作,因此没有做成。
5.3 字符显示的问题
输入的字符串过长时,输出框不能完全显示,我的想法是计算字符串长度与输出框长度比较,再缩小字符,未能实现;另外,在绘制光标时,我将其绘制在了不会遮挡字符的黑色背景上,每次移动时只需要重绘黑色背景和绘制新光标,因此光标的移动有很大局限性,而且不能连续移动。
6 未来的计划建议
主要是解决以上遇到的难题,比如:
自己编写数学函数,从而简化输入的字符,也更符合常规;
添加普通计算器会有的移动光标和删去字符的功能;
改善字符串过长二不能完整显示的问题;
做一个自由移动的光标,等等。