使用树莓派RP2040嵌入式系统学习平台设计鼠标
该项目基于树莓派RP2040的嵌入式系统学习平台,使用micropython编写,最终呈现效果是模拟我们平常使用的鼠标
标签
树莓派
嵌入式系统
2022寒假在家练
回龙观电工职业技术学校刘旋
更新2022-03-03
华北电力大学
860

项目总结报告

一:项目描述:

项目简介:该项目基于树莓派RP2040的嵌入式系统学习平台,使用micropython编写,最终呈现效果是模拟我们平常使用的鼠标。

项目需求:

  1. 利用板上的四向摇杆和按键设计一款“鼠标”
  2. 在240*240的LCD屏幕内可以通过该鼠标进行菜单选择和参数控制(在屏幕上要有上图中图形化的箭头形状)
  3. 通过USB端口可以控制PC屏幕上的光标移动和点击操作,行使电脑鼠标的功能

最终实现的功能有:

1.利用板上的四向摇杆控制鼠标移动方向和速度,通过按下A和B两个按键来模拟PC鼠标的单击、双击和长按

2.在LCD上显示交互界面,可显示鼠标图案,并可通过其进行菜单选择,可显示当前状态和鼠标移动方向和距离

3.通过USB端口向PC端传递信息,控制PC屏幕上鼠标的移动和点击等,行使电脑鼠标的功能

4.鼠标点击、长按、滑动等状态都会在LED上显示不同的灯效

 

设计思路:

  1. 在LCD屏幕上显示交互界面,先是简要介绍,然后是速度参数选择,最后显示运行界面
  2. 通过ADC采集摇杆电位,将其转化成鼠标移动的方向和速度,传输到PC端
  3. 按键B模拟鼠标左键,按键A模拟鼠标右键,按键A、B同时按下时进入滑动模式,通过摇杆上下左右移动模拟纵向滚轮和横向滚轮

                                                                    FnAVfc-Iecewm_7OtOrVy9vmENBn

介绍界面

Fi17jeTRXBfsK0PzmuoSth2pmVqS

菜单选择界面

FhajUlSWjzz7vBU9Jbwk47JrzaW9

运行界面

Fu0dNc9YvYHUeVrjctg96jBJkg8v

硬件介绍:此项目完全使用基于树莓派RP2040的嵌入式系统学习平台,未外接器件,使用到板载的RP2040 MCU,其具有双核Arm Cortex M0+内核和264KB内存,并支持MicroPython、C、C++编程;使用到了学习板自带的240*240分辨率的彩色IPS LCD屏幕,其接口为SPI,控制器为ST7789;除此之外,还使用到了四向摇杆、按键A和B,USB Type C连接器(用于供电和程序下载)。通过PWM控制GPIO4接的STA LED亮度,使用ADC读取摇杆电位。

 

项目的开发中遇到的困难:

  1. 在判断摇杆移动方向时,我开始用IF将摇杆方向简单的判断为上下左右左上左下右上右下八个方向,后面发现鼠标移动太僵硬了,就改成了按偏移比例给x,y方向上的速度赋值
  2. 我开始用中断来判断A、B键是否长按,后面发现用中断就不能一边长按一边移动鼠标,我就改成了定时器判断(这里有一个卡了很久的Bug,就是运行后一直有个定时器缺少必要参数的错误信息,后来把定时器初始化移到While循坏外面才不报错)
  3. 之前长按A或B键时移动鼠标会出现卡顿的问题,我尝试注释了多处代码后猜测可能是在LCD屏幕上显示文字频率过高的原因,但是如果加sleep函数就会导致ADC低速采集,鼠标也会不流畅,我就引入了Flag变量结合定时器来替代sleep函数(当时我把Flag重新赋值的语句写在了回调函数里,结果因为回调函数里flag是局部变量又找Bug找了一段时间)
  4. 在运行界面时我本来想移动鼠标图片的,但是清理鼠标痕迹会导致PC屏幕上鼠标运行卡顿,我先手试了圆形和矩形,发现圆形移动闪烁过快,就采用了矩形移动来模拟鼠标

 

在这次项目中我提升的技能:

1. 学习并巩固了MicroPython编程知识,学会了导入模块、在模块中自己编写函数、信号传输等

2. 学会了如何实现Arm Cortex M0+嵌入式系统总线访问 ,熟练掌握SPI调用方法

3. 学会了如何用st7789驱动学习板上带的240 * 240 LCD屏幕,学会了图形化信息显示方法,熟练掌握了绘制屏幕的方法,并能够自己编写绘图函数

4. 学会了按键输入控制、用ADC模拟信号的采集和转换,用PWM实现脉冲宽度的调制,控制学习板上的STA LED亮度随时间变化,即呼吸灯

二. 代码介绍(用到的hid,st7789和fonts是官方给的):

首先导入需要使用的模块

from machine import Pin, ADC, Timer, PWM
import utime
from hid import Mouse
import usb_hid
import st7789 as st7789
from fonts import vga1_16x32 as font
import framebuf

使用st7789驱动屏幕,对spi总线进行初始化

st7789_res = 0
st7789_dc  = 1
disp_width = 240
disp_height = 240
spi_sck=machine.Pin(2)
spi_tx=machine.Pin(3)
spi0=machine.SPI(0,baudrate=4000000, phase=1, polarity=1, sck=spi_sck, mosi=spi_tx)
display = st7789.ST7789(spi0, disp_width, disp_width,
                          reset=machine.Pin(st7789_res, machine.Pin.OUT),
                          dc=machine.Pin(st7789_dc, machine.Pin.OUT),
                          xstart=0, ystart=0, rotation=0)

偏移量的初始化

dx = 0
dy = 0
dv = 0
dh = 0

引脚4的初始化,给它可以调PWM的功能

led_pwm = PWM(Pin(4))
led_pwm.freq(50)

调节占空比的变量的初始化

duty = 0
direction = 1

实例化鼠标类

m = Mouse()

按键A,B的初始化,PULL_UP是设置为上拉,按键A和B没被按下时检测到高电平

buttonA = Pin(6,Pin.IN,Pin.PULL_UP) #A
buttonB = Pin(5,Pin.IN,Pin.PULL_UP) #B

四向摇杆引脚初始化,给它ADC的功能

xAxis = ADC(Pin(29)) #joystick 
yAxis = ADC(Pin(28)) #joystick

flag是为了判断板子LCD上显示的模式是不是已经刷新过,如果高频率刷的话会导致鼠标移动卡顿,加了flag就可以让板子刷新频率慢下来

speed = 0
flag = 0

简要的介绍界面

display.fill(st7789.WHITE)#把板子全填充为白色
display.text(font, "Hello!", 10, 20, st7789.BLACK, st7789.WHITE)#黑底白字
display.text(font, "It's an analog", 6, 60, st7789.BLACK, st7789.WHITE)
display.text(font, "mouse control", 10, 100, st7789.BLACK, st7789.WHITE)
display.text(font, "program by LX", 10, 143, st7789.BLACK, st7789.WHITE)
display.text(font, "from NCEPU", 10, 190, st7789.BLACK, st7789.WHITE)
utime.sleep_ms(3000)

读取文件地址

mouseimage_file = "mouse.bin"

进行菜单选择,选择鼠标DPI

display.fill(st7789.WHITE)
display.text(font, "dpi_high", 0, 35, st7789.BLACK, st7789.WHITE)
display.text(font, "dpi_middle", 0, 105, st7789.BLACK, st7789.WHITE)
display.text(font, "dpi_low", 0, 175, st7789.BLACK, st7789.WHITE)

while True:
    y_value = yAxis.read_u16()
    if y_value >= 30000 and y_value <=35000:
        dy = 0
    else:
        dy = int(80 * (y_value - 32400) / 32400)
    #打开图片文件
    mouse_image = open(mouseimage_file, 'rb')
    #读取像素字节
    buf = mouse_image.read(1152)
    #在板子的局部显示鼠标图片
    display.blit_buffer(buf, 200, 108+dy, 24, 24)
    utime.sleep_ms(20)
    #用白色矩形刷掉上次鼠标痕迹
    display.fill_rect(200,0,40,240,st7789.WHITE)
    #坐标判断鼠标位置,达到菜单选择的效果
    if buttonA.value() == 0 or buttonB.value() == 0:
        if dy >= 50:
            speed = 15
            break
        if dy <= -50:
            speed = 25
            break
        speed = 20
        break    

清屏,进入运行界面,初始化模式为点击模式

display.fill(st7789.WHITE)
display.text(font, "mode:click", 10, 10, st7789.BLUE, st7789.WHITE)

定时器的回调函数

def ButtonA_CALLBACK(tim1):
    if buttonA.value() == 1:
        m.release(m.BUTTON_RIGHT) #向PC发送松开的信号
        global flag
        flag = 0
        #模式回到点击
        display.text(font, "click  ", 90, 10, st7789.BLUE, st7789.WHITE)
        led_pwm.duty_u16(0) #熄灭灯
        tim1.deinit()   #关闭定时器
    
def ButtonB_CALLBACK(tim2):
    if buttonB.value() == 1:
        m.release(m.BUTTON_LEFT)
        global flag
        flag = 0
        display.text(font, "click  ", 90, 10, st7789.BLUE, st7789.WHITE)
        led_pwm.duty_u16(0)
        tim2.deinit()

 进入运行界面

#定时器实例化
tim1 = Timer()
tim2 = Timer()

#主要运行代码
while True:
    #用ADC采集读取四向摇杆位置,并转化成PC鼠标移动距离dx、dy
    x_value = xAxis.read_u16()
    y_value = yAxis.read_u16()
    if x_value >= 30000 and x_value <=35000:
        dx = 0
    else:
        dx = int(speed * (x_value - 32400) / 32400) 
    if y_value >= 30000 and y_value <=35000:
        dy = 0
    else:
        dy = int(speed * (y_value - 32400) / 32400)
    display.fill_rect(110+dx*3,120+dy*3,20,20,st7789.BLACK) #板子LCD显示矩形模拟鼠标移动
    m.move(dx,dy,0,0) #移动PC鼠标
    utime.sleep_ms(20)
    display.fill_rect(110+dx*3,120+dy*3,20,20,st7789.WHITE) #消除矩形痕迹
    dx=0
    dy=0
    if buttonA.value() == 0:
        if buttonB.value() == 0:
            display.text(font, "slide  ", 90, 10, st7789.BLUE, st7789.WHITE)
            #进入滑动模式
            while buttonA.value() == 0 and buttonB.value() == 0:
                x_value = xAxis.read_u16()
                y_value = yAxis.read_u16()
                if x_value >= 30000 and x_value <=35000:
                    dh = 0
                else:
                    dh = int(speed * (x_value - 32400) / 32400)
                if y_value >= 30000 and y_value <=35000:
                    dv = 0
                else:
                    dv = int(speed * (y_value - 32400) / 32400)
                display.fill_rect(110+dh*3,120+dv*3,20,20,st7789.BLACK)
                m.move(0, 0, -dv, dh) #移动滚轮
                utime.sleep_ms(20)
                display.fill_rect(110+dh*3,120+dv*3,20,20,st7789.WHITE)
                dv = 0
                dh = 0
                #控制LED占空比
                duty += direction
                if duty > 255:
                    duty = 255
                    direction = -1
                elif duty < 0:
                    duty = 0
                    direction = 1
                led_pwm.duty_u16(duty*duty)
            led_pwm.duty_u16(0)
            display.text(font, "click  ", 90, 10, st7789.BLUE, st7789.WHITE)
            continue
        m.press(m.BUTTON_RIGHT) #向PC发送按键按下信号
        led_pwm.duty_u16(256*256-1) #占空比拉满,对应长按
        #to judge whether user is holding buttonA
        if flag == 0:
            display.text(font, "hold ", 90, 10, st7789.BLUE, st7789.WHITE)
            flag = 1
        #定时器初始化,第一个参数是模式选择,我用的模式是循环定时,周期是100ms,每次结束(或开始?俺也不记得了)就执行回调函数
        tim1.init(mode=Timer.PERIODIC, period=100,callback=ButtonA_CALLBACK)
        
    if buttonB.value() == 0:
        if buttonA.value() == 0:
            display.text(font, "slide  ", 90, 10, st7789.BLUE, st7789.WHITE)
            #slide the mouse
            while buttonA.value() == 0 and buttonB.value() == 0:
                x_value = xAxis.read_u16()
                y_value = yAxis.read_u16()
                if x_value >= 30000 and x_value <=35000:
                    dh = 0
                else:
                    dh = int(speed * (x_value - 32400) / 32400)
                if y_value >= 30000 and y_value <=35000:
                    dv = 0
                else:
                    dv = int(speed * (y_value - 32400) / 32400)
                display.fill_rect(110+dh*3,120+dv*3,20,20,st7789.BLACK)
                m.move(0, 0, -dv, dh)
                utime.sleep_ms(20)
                display.fill_rect(110+dh*3,120+dv*3,20,20,st7789.WHITE)
                dv = 0
                dh = 0
                duty += direction
                if duty > 255:
                    duty = 255
                    direction = -1
                elif duty < 0:
                    duty = 0
                    direction = 1
                led_pwm.duty_u16(duty*duty)
            led_pwm.duty_u16(0)
            display.text(font, "click  ", 90, 10, st7789.BLUE, st7789.WHITE)
            continue
        m.press(m.BUTTON_LEFT)
        led_pwm.duty_u16(256*256-1)
        #to judge whether user is holding buttonB
        if flag == 0:
            display.text(font, "hold  ", 90, 10, st7789.BLUE, st7789.WHITE)
            flag = 1
        tim2.init(mode=Timer.PERIODIC, period=100, callback=ButtonB_CALLBACK)
   

 

 

 

    

  

 

 

附件下载
工程文件.zip
团队介绍
由刘旋1人组成
团队成员
回龙观电工职业技术学校刘旋
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号