基于树莓派rp2040实现能控制LCD和电脑界面的“鼠标”
一.项目介绍:
- 利用rp2040的四向摇杆和按键设计一款“鼠标”,该鼠标可以对240*240的LCD屏进行菜单选择和参数控制,同时,可以利用板子上的摇杆和按钮实现控制PC屏上的光标移动和点击操作,行使电脑鼠标的功能。
二.实现的功能:
- 绘制了一个“鼠标”图案,并且不断移动这个鼠标,当B键按下时,如果鼠标图案在相应的位置,那么会加载出新的界面,如果按下A键,就可以返回最初始的页面。
- 当按下START按键时,会进入控制PC屏幕光标的操作,通过移动rp2040上的四向摇杆,PC屏幕光标会进行相应的移动,若按下B键,即相当于鼠标左键的功能,按下A键时即相当于鼠标右键的功能,从而实现了用rp2040来充当电脑鼠标的目的
三.设计思路:
- 第一步绘制鼠标图案,通过st7789c的polygon函数绘画多边形从而绘制出鼠标的形状。
- 第二步实现鼠标的移动,通过刷新鼠标的位置,在新的位置绘制另一个鼠标图案,并且在这之前先消除掉原来位置鼠标的图案,使其融为背景色,从而最终实现鼠标移动的效果。
- 第三步实现利用“鼠标”来进行菜单选择和参数控制。通过判断鼠标图案的所属位置,并且当按钮按下时实现对菜单的选择,这时要实现只需要定义一个变量,当相应位置按下按键B时来改变变量,并且当变量变化时执行LCD界面的变换。同理,为了当A按键时按下回到起始页面,也只要当A按键按下时使变量变为初始值即可。
- 第四步实现通过USB端口来控制PC屏幕上的光标移动和点击操作。这里需要利用到python的usb_hid库,通过该库进行人机的交互,利用里面的函数对屏幕光标进行XY轴位置的变化,和光标左右键点击的判断。当四向摇杆被操控时,利用函数来对光标操作,同理,AB键按下时也执行对应的操作,B键为鼠标左键操作,A键为鼠标右键操作。
- 上述对LCD屏的鼠标图案和PC屏幕光标的操作完成后,实现按钮对两个模式进行转换,当START按下时用变量进行对操作模式的转换。
四.实现的过程:
1.程序流程图:
2.定义引脚:
orange=0xfd20
xAxis = ADC(Pin(29)) #x轴
yAxis = ADC(Pin(28)) #Y轴
Bbutton = Pin(5,Pin.IN, Pin.PULL_UP) #B按键
Abutton = Pin(6,Pin.IN, Pin.PULL_UP) #A按键
buttonStart = Pin(7,Pin.IN, Pin.PULL_UP)#START按键
3.利用st7789c驱动LCD屏幕,并且实例化一个对象方便后期调用:
背景色是白色
#调用ST7789C来驱动LCD屏幕
spi0=machine.SPI(0,baudrate=4000000, phase=1, polarity=1,sck=machine.Pin(game_kit.lcd_sck, machine.Pin.OUT),
mosi=machine.Pin(game_kit.lcd_sda, machine.Pin.OUT))
print(spi0)
display = st7789c.ST7789(spi0, 240, 240,
reset=machine.Pin(game_kit.lcd_rst, machine.Pin.OUT),
dc=machine.Pin(game_kit.lcd_dc, machine.Pin.OUT),
rotation=0)#实例化了一个ST7789
display.init() #st7789初始化
display.fill(st7789c.WHITE)
4.定义一个rpMouse类来实现对鼠标图案的初始化,鼠标位置的移动,鼠标位置的绘画,还有更新鼠标位置,使用时定义对象使用函数即可
4.1总代码:
#设置边界
width =240
height=240
#rp2040鼠标定义
class rpMouse():
'''
Poly class to keep track of a polygon based sprite
'''
#初始化
def __init__(
self,
# list (x,y) tuples of convex polygon, must be closed
polygon,
x=None, # 鼠标X坐标
y=None, # 鼠标Y坐标
v_x=None, # X轴的移动速度
v_y=None, # Y轴的移动速度
angle=None, # 鼠标图案的移动速度
max_velocity=25, # 鼠标图案移动的最大速度
min_velocity=10 # 鼠标图案移动的最小速度
): #这边是传入的参数,对其进行一个初始化赋值
self.polygon = polygon #赋值元组
# 如果没有初始化起始位置便随便给个位置
self.x = random.randint(0, width) if x is None else x
self.y = random.randint(0, width) if y is None else y
# 如果角度有给就设置
self.angle = float(0) if angle is None else angle
# 除非有给速度参数否则随便给一个
self.velocity_x = random.uniform(
0.50, 0.99)*6-3 + 0.75 if v_x is None else v_x
self.velocity_y = random.uniform(
0.50, 0.99)*6-3 + 0.75 if v_y is None else v_y
self.max_velocity = max_velocity #设置最大值
self.min_velocity = min_velocity #设置最小值
#移动
def moveX(self,moveflag):
self.draw(st7789c.WHITE) #使旧的鼠标图案融于背景色,达到消失的效果
if(moveflag==1) :#向左
self.x -= int(self.velocity_x*0.2) #每次以速度移动
if(self.x<0): #如果到最底端,就更新位置到最上端
self.x=240
elif(moveflag==2) : #向右
self.x += int(self.velocity_x*0.2) #每次以速度移动
self.x %= width #不会超过边界
self.draw(orange) #使之移动
def moveY(self,moveflag):
self.draw(st7789c.WHITE)
if moveflag==1:#向上
self.y -= int(self.velocity_y*0.2) #每次以速度移动,乘上倍数,看起来流畅
if(self.y<=0):
self.y=240
if moveflag==2:
self.y += int(self.velocity_y*0.2) #每次以速度移动,乘上倍数,看起来流畅
self.y %= height
self.draw(orange) #使之移动
#画鼠标
def draw(self, color):
display.polygon(self.polygon, self.x, self.y, color, self.angle, 0, 0)
#更新鼠标的移动速度
def update(self,vflag): #1为加速,2为减速
if vflag==2 :
self.velocity_x -= 1
self.velocity_y -= 1
elif vflag==1:
self.velocity_x += 1
self.velocity_y += 1
if self.velocity_x > self.max_velocity: #使速度不超过最大值
self.velocity_x = self.max_velocity
elif self.velocity_x < self.min_velocity:
self.velocity_x = self.min_velocity
#定义一个rp2040鼠标
mouse_poly=[(-7,-7),(7,0),(-7,7),(-3,0),(-7,-7)] #鼠标图像元组
mouse =rpMouse(mouse_poly,x=120,y=120,v_x=15,v_y=15,angle=180.0) #类对象
mouse.draw(orange)
4.2初始化:
#初始化
def __init__(
self,
# list (x,y) tuples of convex polygon, must be closed
polygon,
x=None, # 鼠标X坐标
y=None, # 鼠标Y坐标
v_x=None, # X轴的移动速度
v_y=None, # Y轴的移动速度
angle=None, # 鼠标图案的移动速度
max_velocity=25, # 鼠标图案移动的最大速度
min_velocity=10 # 鼠标图案移动的最小速度
): #这边是传入的参数,对其进行一个初始化赋值
self.polygon = polygon #赋值元组
# 如果没有初始化起始位置便随便给个位置
self.x = random.randint(0, width) if x is None else x
self.y = random.randint(0, width) if y is None else y
# 如果角度有给就设置
self.angle = float(0) if angle is None else angle
# 除非有给速度参数否则随便给一个
self.velocity_x = random.uniform(
0.50, 0.99)*6-3 + 0.75 if v_x is None else v_x
self.velocity_y = random.uniform(
0.50, 0.99)*6-3 + 0.75 if v_y is None else v_y
self.max_velocity = max_velocity #设置最大值
self.min_velocity = min_velocity #设置最小值
解释:
- polygon是绘制鼠标位置的元组,里面的元素是鼠标图案各个顶点与中心坐标(x,y)的相对位置,而且元素的顺序要按照绘画各个点的顺序来,最后一个元素要回到第一个点。
- 定义完后,当想绘制鼠标时,要先进行初始化,将鼠标的基础信息传入,后面才得以绘制。
4.3绘制鼠标的函数:
#画鼠标
def draw(self, color):
display.polygon(self.polygon, self.x, self.y, color, self.angle, 0, 0)
解释:
- 每次绘制时只需要传入想要的鼠标的颜色,然后进行绘制就行。
4.4移动鼠标的函数:
#移动
def moveX(self,moveflag):
self.draw(st7789c.WHITE) #使旧的鼠标图案融于背景色,达到消失的效果
if(moveflag==1) :#向左
self.x -= int(self.velocity_x*0.2) #每次以速度移动
if(self.x<0): #如果到最底端,就更新位置到最上端
self.x=240
elif(moveflag==2) : #向右
self.x += int(self.velocity_x*0.2) #每次以速度移动
self.x %= width #不会超过边界
self.draw(orange) #使之移动
def moveY(self,moveflag):
self.draw(st7789c.WHITE)
if moveflag==1:#向上
self.y -= int(self.velocity_y*0.2) #每次以速度移动,乘上倍数,看起来流畅
if(self.y<=0):
self.y=240
if moveflag==2:
self.y += int(self.velocity_y*0.2) #每次以速度移动,乘上倍数,看起来流畅
self.y %= height
self.draw(orange) #使之移动
解释:以在x轴移动为例,y轴是同样的道理
- 首先先使原来的鼠标融于背景色中达到消失的效果,然后判断是要正半轴还是负半轴移动,最后更新rpMouse对象的X,Y的位置,然后在新位置绘画另一个鼠标。
5.定时器扫描按键:
#定时器检索按键,并且给显示对应画面
def timercb(n):
global rporcomflag #判断是操控rp2040的鼠标还是PC屏幕的光标,0为RP2040的,1是PC屏幕光标
global faceflag #判断是进入初始页面还是其他页面的变量
if(Bbutton.value()==0 and rporcomflag==1): #相当于电脑鼠标左键按下
hid_mouse.press(hid_mouse.BUTTON_LEFT)
if(Bbutton.value()==1 and rporcomflag==1): #相当于电脑鼠标左键松开
hid_mouse.release(hid_mouse.BUTTON_LEFT)
if(Abutton.value()==0 and rporcomflag==1): #相当于电脑右键按下一次
hid_mouse.click(hid_mouse.BUTTON_RIGHT)
if(Bbutton.value()==0 and rporcomflag==0): #如果是rp2040按下B键,为确定键
if(mouse.y>70 and mouse.y<98 and mouse.x>=84 and mouse.x<=163): #移动到冰墩墩的位置
faceflag=1 #冰墩墩
elif(mouse.y>98 and mouse.y<128 and mouse.x>=84 and mouse.x<=163):
faceflag=2 #TEXT
elif(mouse.y>220 and mouse.y<240 and mouse.x>=110 and mouse.x<=240):
mouse.update(1) #更新鼠标的速度
utime.sleep(0.05)
elif(mouse.y>220 and mouse.y<240 and mouse.x>=0 and mouse.x<=100):
mouse.update(2) #更新鼠标的速度
utime.sleep(0.05)
tim=Timer(period=100, mode=Timer.PERIODIC, callback=lambda t:timercb(1))
解释:
- 利用定时器不断地扫描按键,减少主循环中对摇杆判断的影响。
- 利用rporcomflag来判断是操控哪个,从而判断B,A键按下时的效果。
- 当操控RP2040鼠标图案时,判断鼠标位置,从而当B键按下时,进入不同的页面。
6.主循环,扫描摇杆进行对应的操作:
#定义hid鼠标
hid_mouse=Mouse()
rporcomflag=0
enterflag=0 #每次进入图像后置1,不让再次刷新图像,返回后再置0,使得下一次得以进入图像。
#这样子可以避免影响返回键A的判断
while True:
if (rporcomflag==1): #1为电脑鼠标控制
xValue = xAxis.read_u16() #读取摇杆位移
yValue = yAxis.read_u16()
if buttonStart.value()==0: #当按下START时,使rporcomflag置0
rporcomflag-=1
utime.sleep(0.2)
if xValue <=600: #当摇杆向左
hid_mouse.move(-1,0) #进行对应的位移
elif xValue >= 60000: #当摇杆向右
hid_mouse.move(1,0)
if yValue <= 600: #当摇杆向上
hid_mouse.move(0,-1)
elif yValue >= 60000: #当摇杆向下
hid_mouse.move(0,1)
elif rporcomflag==0 and faceflag==0: #0为rp2040鼠标控制
xValue = xAxis.read_u16()
yValue = yAxis.read_u16()
if buttonStart.value()==0:
rporcomflag+=1
utime.sleep(0.2)
if xValue <= 600:#左
mouse.moveX(1)
utime.sleep(0.00835)
elif xValue >= 60000:
mouse.moveX(2)
utime.sleep(0.0083)
if yValue <= 600:
mouse.moveY(1)
utime.sleep(0.0083)
elif yValue >= 60000:
mouse.moveY(2)
utime.sleep(0.0083)
interface1() #每次移动后要再进行一次页面的刷新,防止鼠标图像清空时使背景色消失
mouse.draw(orange)
if(Abutton.value()==0 and rporcomflag==0):#返回操作
display.fill(st7789c.WHITE)
interface1()
faceflag=0
enterflag=0
elif(faceflag==1 and enterflag==0): # 冰墩墩界面
display.jpg("bingdundun.jpg", 0, 0, st7789c.SLOW)
enterflag=1 #每次进入图像后置1,不让再次刷新图像,返回后再置0,使得下一次得以进入图像。
#这样子可以避免影响返回键A的判断
elif(faceflag==2 and enterflag==0): #text界面
display.jpg("text.jpg", 0, 0, st7789c.SLOW)
enterflag=1
解释:
- 利用循环不断地扫描摇杆,并且判断是操作rp2040鼠标图案还是屏幕光标,当摇杆移动时,对rp2040和屏幕光标进行移动,屏幕光标的移动只需要利用硬禾学堂官方库中的hid的move进行对鼠标的移动即可。rp2040在鼠标移动后还需要再进行背景的刷新,避免背景被鼠标刷新时局部刷新掉。
五.遇见的难题和解决方法:
1.问题一
1.1问题:LCD屏鼠标闪烁频率过高:当鼠标进行移动时,鼠标会不停的进行闪烁,此时鼠标图案的效果太差。
1.2原因:当进行一个新位置鼠标的绘画,此时再移动时,这个新位置鼠标会被擦除,这时由于擦除旧位置鼠标和绘制新位置鼠标的间隔太短,新的鼠标显示完成后就马上被擦除,这时就会出现闪烁的现象。
1.3解决方法:在每次位移函数后,进行一个延时,从而增加鼠标的存在时长,从而减轻闪烁的问题,经试验,延时的时长在08秒到0.085秒之间效果最好。
2:问题二
2.1问题:当进入另一个界面时会出现返回键(A键)的检索不灵敏,会出现得按好多次的问题。
2.2原因:当页面进入新的界面后,由于这时faceflag没有进行更改,所以会不断的进入这个if的分支语句里面,从而不断刷新页面,这时会对树莓派加深负担,从而影响对A按键的判断。
if(Abutton.value()==0 and rporcomflag==0):
display.fill(st7789c.WHITE)
interface1()
faceflag=0
elif(faceflag==1): # 冰墩墩界面
display.jpg("bingdundun.jpg", 0, 0, st7789c.SLOW)
2.3解决方法:定义一个变量,当进入一次界面后,便将其置1,并且只有当其为0时才能再次进入这个界面,这时就能控制其进入一次,并且,当按下返回键时,就会将该变量重新置为0,变成可进入的状态,从而解决问题。
if(Abutton.value()==0 and rporcomflag==0):
display.fill(st7789c.WHITE)
interface1()
faceflag=0
enterflag=0
elif(faceflag==1 and enterflag==0): # 冰墩墩界面
display.jpg("bingdundun.jpg", 0, 0, st7789c.SLOW)
enterflag=1 #每次进入图像后置1,不让再次刷新图像,返回后再置0,使得下一次得以进入图像。
#这样子可以避免影响返回键A的判断
六.成品展示:
1.初始界面:
2.当位移到冰墩墩的文字并按下B键进入新页面
3.当鼠标位移到TEXT并且按下B键后会进入TEXT说明的界面
4.当鼠标位移到最下方数字的右边并且按下B键时,鼠标速度会对应增加。
5.当鼠标位移到最下方数字的左边并且按下B键时,鼠标速度会对应减小。
6.当按下START按键后,进入对PC屏幕光标的操控,摇杆控制光标移动,B键充当鼠标左键功能,A键充当鼠标右键功能,并且当长按B键并且移动摇杆时,能够进行多选。