大数跨境
0
0

如何使用树莓派RP2040实现“鼠标”功能?

如何使用树莓派RP2040实现“鼠标”功能? DF创客社区
2022-03-08
1
导读:来自华北电力大学的同学分享项目。
【点击上方「蓝字」关注DF创客社区,一起成为技术颜控】

来源:硬禾学堂

今天我们来欣赏一位来自华北电力大学的【xiaoyu555】同学做的“鼠标”。

项目介绍

利用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、当位移到冰墩墩的文字并按下B键进入新页面

3、当鼠标位移到TEXT并且按下B键后会进入TEXT说明的界面

4、当鼠标位移到最下方数字的右边并且按下B键时,鼠标速度会对应增加。

5、当鼠标位移到最下方数字的左边并且按下B键时,鼠标速度会对应减小。

6、当按下START按键后,进入对PC屏幕光标的操控,摇杆控制光标移动,B键充当鼠标左键功能,A键充当鼠标右键功能,并且当长按B键并且移动摇杆时,能够进行多选。

实现过程

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, 240240,
                        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(0if angle is None else angle

            # 除非有给速度参数否则随便给一个
            self.velocity_x = random.uniform(
                0.500.99)*6-3 + 0.75 if v_x is None else v_x
            self.velocity_y = random.uniform(
                0.500.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, 00)
            
            #更新鼠标的移动速度
        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(0if angle is None else angle

            # 除非有给速度参数否则随便给一个
            self.velocity_x = random.uniform(
                0.500.99)*6-3 + 0.75 if v_x is None else v_x
            self.velocity_y = random.uniform(
                0.500.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, 00)

解释:

  • 每次绘制时只需要传入想要的鼠标的颜色,然后进行绘制就行。

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"00, st7789c.SLOW)
        enterflag=1   #每次进入图像后置1,不让再次刷新图像,返回后再置0,使得下一次得以进入图像。
                    #这样子可以避免影响返回键A的判断
    elif(faceflag==2 and enterflag==0):  #text界面
        display.jpg("text.jpg"00, 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"00, st7789c.SLOW)

解决方法:定义一个变量,当进入一次界面后,便将其置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"00, st7789c.SLOW)
        enterflag=1   #每次进入图像后置1,不让再次刷新图像,返回后再置0,使得下一次得以进入图像。
                      #这样子可以避免影响返回键A的判断

资料获取

作者已将项目分享在电子森林和百度网盘,大家可自行获取,如有参考请备注参考来源: 电子森林链接:https://www.eetree.cn/project/detail/912

百度网盘链接:https://pan.baidu.com/s/1w96ahikxAOgv7YJZrb5abQ?pwd=0000 提取码:0000

最后祝泥萌女神节快乐!

来源:科技学堂
转载请注明出处


硬件军火库


DF硬件军火库


点击了解详情👆


大家有什么想法,欢迎在下方留言!



 往期项目回顾 



树莓派基础系列教程

树莓派是什么?能吃吗?

开源!教你做最精致的Pi!

用树莓派和 ESP32 做一台掌上电脑

心情记录仪——基于无线射频模块的计数系统

用ESP8266魔改一个《黑客帝国》“代码雨”机箱

如何用ESP32-CAM和3D打印件做一个Strider摄像机器人

用APC220模块和Arduino制作一辆远距离遥控履带车

2022第一弹——用Arduino和电磁继电器做一个“电磁炮”


点击阅读👆

【声明】内容源于网络
0
0
DF创客社区
我们是专注于创新和开源硬件开发的公司——DFRobot成立的创客社区,无论你是资深创客还是小白,这里都有你的一席之地。一个人玩自己的项目,你只是寂寞宅;一群人看你玩项目,你就是技术牛!快来分享你的项目吧!
内容 1282
粉丝 0
DF创客社区 我们是专注于创新和开源硬件开发的公司——DFRobot成立的创客社区,无论你是资深创客还是小白,这里都有你的一席之地。一个人玩自己的项目,你只是寂寞宅;一群人看你玩项目,你就是技术牛!快来分享你的项目吧!
总阅读2.1k
粉丝0
内容1.3k