基于树莓派RP2040实现水平仪
本项目使用树莓派pico开发板,结合MMA7660重力传感器,制作了一款水平仪。
标签
嵌入式系统
测试
显示
传感器
raspberry pi pico
2022寒假在家练
esp32小白
更新2022-03-03
金陵科技学院
1362

内容介绍

开发板介绍

本次使用的开发板是树莓派新推出的RP2040 pico 开发板,核心具有双核Arm Cortex M+内核,工作频率可高达133MHz,具有264KB的内存,和高度灵活的可编程IO可用于高速数字接口。

功能实现

项目8 - 利用姿态传感器制作一个水平仪

在LCD屏上设计一款水平仪,通过一个滚动的小球或气泡,来显示当前板子的倾斜度,当板子处于水平位置的时候,小球停在屏幕的正中间,倾斜板子,小球偏移,并能够显示偏移的角度(二维信息)

按键操作

初始状态是水平仪的页面,按下按键B会进入结束页面,页面显示“End !”。再按下A键回到水平仪界面。

代码实现框图

Fg6glFgOrlMF5D1yPLY4vH0xIlqN

设置

本次使用arduino平台开发树莓派pico,使用万能的编辑器vscode,在板卡管理器中输入pico,选择Raspberry Pi Pico (Arduino Mbed OS RP2040) Boards 选项。

Fhw9rBZ_oN5UL7dm3VVI4GNgHsMq

注意

板卡上MMA7660传感器的I2C总线引脚和arduino的rp2040 pico的默认引脚不一致,需稍加改动,操作如下。

由于板卡上MMA7660连接的SDA、SCL分别是10、11脚,

FmQFcjeQSpCsVcOi8o_mDHEMuhcN

故应转到路径

C:\Users\NSwor\AppData\Local\Arduino15\packages\arduino\hardware\mbed_rp2040\2.6.1\variants\RASPBERRY_PI_PICO

下的 pins_arduino.h 文件修改对应引脚。SDA-10  SCL-11

FlxKK2s2YT7T3uc2bvTTuTgdGFzs

烧录

按下B键再按下reset键,再松开reset键和B键,进入boot模式,将uf2文件拖入树莓派U盘中,即可执行。

代码分析

在setup()函数中进行串口初始化,MMA7660重力传感器芯片初始化,初始化按键引脚(设置上拉电阻输入)并定义一些Sprite初始化函数

Serial.begin(250000);
accelemeter.init();
// 初始化按键引脚
pinMode(button_A,INPUT_PULLUP);
pinMode(button_B,INPUT_PULLUP);

tft_init();

spr1_init();

spr_init();

spr2_init();

spr3_init();

板载屏幕为中景园的ST7789屏幕,使用TFT_eSPI库来驱动,由于使用C语言更容易获得更快的刷屏速度,所以我放弃了使用python开发,转为arduino开发。我上传的TFT_eSPI库文件中已经集成好了ST7789屏幕对应的驱动,需要在 User_Setup.h 文件和 User_Setup_Select.h 文件中手动打开 #define ST7789_DRIVER 和 #include <User_Setups/Setup_RP2040_ST7789.h> 注释,注释掉其他型号驱动。

FoPy_rj3v-3oeYRdXyjJmHdBTKsBFnPpQIqkBXiB9mg2ycGOZxhj0Rqp

在TFT_eSPI库中,如果使用全屏刷屏的方式还是会比较慢,但是如果使用Sprite方式刷屏,则会流畅很多。具体操作为,先定义一个TFT_eSPI类型的变量tft,然后再定义几个TFT_eSprite类型的变量用来访问tft。

TFT_eSPI    tft = TFT_eSPI();

TFT_eSprite spr = TFT_eSprite(&tft);
TFT_eSprite spr1 = TFT_eSprite(&tft);
TFT_eSprite spr2 = TFT_eSprite(&tft);
TFT_eSprite spr3 = TFT_eSprite(&tft);

使用Sprite移动的原理是,将多个Sprite区域中的内容push到屏幕上,然后移动Sprite区域,或是定义一个全屏的大Sprite,其他内容用新的小Sprite push到大Sprite上,最后将大Sprite push到屏幕上。缺点是消耗内存比较大,当使用的Sprite很多或很大时,可能造成内存不足。

spr.pushToSprite(&spr1, x, y, TFT_BLACK);
spr2.pushToSprite(&spr1, x2+10, 200, TFT_BLACK);
spr3.pushToSprite(&spr1, 200, y2+10, TFT_BLACK);
spr1.pushSprite(0, 0, TFT_TRANSPARENT);

为了使水平仪显示获得更好的流畅度,可以从MMA7660采样数据入手,这里采用一阶滞后滤波对x,y,z三轴数据进行滤波,增加画面稳定性。

// 一阶滞后滤波
x0=0.8*x0+0.2*x1;x1=x0;
y0=0.8*y0+0.2*y1;y1=y0;
z0=0.8*z0+0.2*z1;y1=z0;

气泡小球不能跑出屏幕范围,因此要对小球的运动范围做出限制。这里使用圆的定义(如下)来限定范围。

// 到定点距离为定长的点的轨迹

if(((85-x+dy)*(85-x+dy)+(y+dx-85)*(y+dx-85))<8000){
    x=x-dy;
    y=y+dx;
    x2=x;
    y2=y;
}

if(((85-x)*(85-x)+(y-85)*(y-85))<8000){
    spr.pushToSprite(&spr1, x, y, TFT_BLACK);
    spr2.pushToSprite(&spr1, x2+10, 200, TFT_BLACK);
    spr3.pushToSprite(&spr1, 200, y2+10, TFT_BLACK);
    spr1.pushSprite(0, 0, TFT_TRANSPARENT);
}

定义整型变量flag来标记显示的内容。flag默认为0,当按键A按下时flag = 0,显示水平仪界面;当按键B按下时flag = 1,显示结束页面,水平仪结束。在loop循环中检测按键状态。

if(digitalRead(button_A) == 0){
    flag = 0;
}
if(digitalRead(button_B) == 0){
    flag = 1;
    tft.fillScreen(TFT_WHITE);
}

显示角度。在屏幕顶端显示三轴方向的角度。

spr1.drawRoundRect(0,0,80,30,6,TFT_WHITE);
spr1.drawFloat((x0/22.0)*90.0, 2, 70, 17, 4);

spr1.drawRoundRect(80,0,80,30,6,TFT_WHITE);
spr1.drawFloat((y0/23.0)*90.0, 2, 150, 17, 4);

spr1.drawRoundRect(160,0,80,30,6,TFT_WHITE);
spr1.drawFloat((z0/22.0)*90.0, 2, 230, 17, 4);

结果展示

FhmpYZxJT_wpA3Gx1dxuwuCHA0g_FpyKr24R6mIKHvwzJK2l3jk32faL

附:

   关于这个板卡,我还实现了其他功能。

项目1 - 能控制LCD和电脑界面的“鼠标”

使用Thonny IDE进行micropython开发。ADC读取四向摇杆的值,判断值在不同的区间定义鼠标移动的不同速度,提升鼠标控制的流畅度。

# 定义光标移动函数
def ymove(speed):
    global x, y
    if speed > 60000:# down
        m.move(0, 10)
    elif (speed > 40000)&(speed < 60000):
        m.move(0,6)
    elif (speed > 34000)&(speed < 40000):
        m.move(0, 2)
    elif (speed > 20000)&(speed < 29000):# up
        m.move(0,-2)
    elif (speed > 3000)&(speed < 20000):
        m.move(0, -6)
    elif (speed > 0)&(speed < 3000):
        m.move(0,-10)

点击与长按,我想到了一个算法可以把二者完美结合起来。

def m_pressLEFT():
    m.press(m.BUTTON_LEFT)
    while True:
        if (xAxis.read_u16() >= 0) & (xAxis.read_u16() <= 68000):
            ymove(xAxis.read_u16())
        if (yAxis.read_u16() >= 0) & (yAxis.read_u16() <= 68000):
            xmove(yAxis.read_u16())
        if buttonB.value() == 1:
            m.release(m.BUTTON_LEFT)
            break
def m_pressRIGHT():
    m.press(m.BUTTON_RIGHT)
    while True:
        if (xAxis.read_u16() >= 0) & (xAxis.read_u16() <= 68000):
            ymove(xAxis.read_u16())
        if (yAxis.read_u16() >= 0) & (yAxis.read_u16() <= 68000):
            xmove(yAxis.read_u16())
        if buttonA.value() == 1:
            m.release(m.BUTTON_RIGHT)
            break

使用micropython的多线程时,我遇到了内存问题和屏幕调用冲突,在老师的指导下,我学会了可以使用内存垃圾收集、锁定线程内存和使用互斥锁防冲突等方法实现。

gc.threshold(20_000)
gc.enable()
lock = _thread.allocate_lock()
_thread.stack_size(20 * 1024)

def blit_buffer(a, b, c, d, e):
    lock.acquire()
    display.blit_buffer(a, b, c, d, e)
    lock.release()

gc.collect()

 

另外我也在尝试做复古游戏移植,实现一款90版的坦克大战,由于学业繁忙,暂时只完成了一半,后续会跟进这个项目。

FrrNYBZxUYkEfn3d2tUnkrqfr1s5

附件下载

arrow.zip
鼠标项目,先烧录micropython固件uf2,再thonny上传图片和库文件。最后运行main.py
TFT_eSPI.zip
屏幕驱动库文件,稍微有点大所以没打包在主文件夹里。
spirit_level.zip
水平仪主文件,里面的 spirit_level.ino.elf.uf2 文件可以直接烧录查看结果。

团队介绍

一名金陵科技学院信息工程专业的大一学生~
团队成员
esp32小白

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号