基于树莓派RP2040-GameKit制作水平仪
基于GameKit板载的mma7660传感器制作的水平仪,实现了气泡与文字双重显示,大体标定合格
标签
嵌入式系统
ST7789
mma7660
RP2040
2022寒假在家练
水平仪
小栋栋
更新2022-03-03
722

1.任务要求

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

以上要求基本都已满足。

2.环境配置

试用的固件为

lecture · EETree-git/RP2040_Game_Kit - 码云 - 开源中国 (gitee.com)里面的固件

这个固件自带了一个ST7789的显示库,本项目就基于这个显示库完成图形开发,只要按住B键将固件拖进去就好了

用于开发的IDE是本次课程推荐的IDE,thonny

Fjv_MPDJ7AliMFTbFGWV3MvecJP5

 

3.硬件系统

Fh_sRDYdgqYASKaDn3Rp-RRoeLlg

本例只是用了MMA7660和ST7789显示屏部分,所以只截图了相关的电路,可以看到MMA7660通过IIC总线挂载在RP2040上面,SDA SCL分别是IO10和IO11,ST7789的IO:RST、DC、SCK、SDA则分别挂载在IO2、3、4、5几个IO上,由于固件自带ST7789专用驱动,所以只需要了解ST7789的IO即可。

4.程序实现

二话不说,先上流程图和代码,稍后讲解

Frhs_4h5G3Q-jGXiaUsqumcogAQH

import time
import random
from breakout_colourlcd240x240 import BreakoutColourLCD240x240

from machine import Pin, I2C
import vga2_8x8
import vga1_16x32

width = BreakoutColourLCD240x240.WIDTH
height = BreakoutColourLCD240x240.HEIGHT

display_buffer = bytearray(width * height * 2)  # 2-bytes per pixel (RGB565)
display = BreakoutColourLCD240x240(display_buffer)

display.set_backlight(1.0)

i2c = I2C(1, scl=Pin(11), sda=Pin(10), freq=400_000)

i2c.writeto_mem(76, 0x07, '\x01')
time.sleep(0.21)
class Ball:
    def __init__(self, x, y, r, dx, dy, pen):
        self.x = x
        self.y = y
        self.r = r
        self.dx = dx
        self.dy = dy
        self.pen = pen

def write8x8(char,x,y):
    char=char*8
    display.set_pen(255,255,255)
    for i in range(8):
        a=vga2_8x8.FONT[char+i]
        for c in range(8):
            d=[128,64,32,16,8,4,2,1]
            if a&d[c]==d[c]:
                display.pixel(x+c,y+i)

def write16x32(char,x,y):
    char=(char-0x20)*64

    display.set_pen(255,255,255)
    for i in range(32):
        a=vga1_16x32.FONT[char+i*2]

        for c in range(8):
            d=[128,64,32,16,8,4,2,1]
            if a&d[c]==d[c]:
                display.pixel(x+c,y+i)
        a=vga1_16x32.FONT[char+i*2+1]

        for c in range(8):
            d=[128,64,32,16,8,4,2,1]
            if a&d[c]==d[c]:
                display.pixel(x+8+c,y+i)


balls = []
for i in range(0, 25):
    r = random.randint(0, 10) + 3
    balls.append(
        Ball(
            random.randint(r, r + (width - 2 * r)),
            random.randint(r, r + (height - 2 * r)),
            r,
            (14 - r) / 2,
            (14 - r) / 2,
            display.create_pen(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
        )
    )
    
currentx=[]
currenty=[]
sumx=0
sumy=0
a=0
while True:
    mma=i2c.readfrom(76, 4)
    if mma[0]>31:
        x=abs(mma[0]-63)*-1
    else:
        x=mma[0]
        
    if mma[1]>31:
        y=abs(mma[1]-63)*-1
    else:
        y=mma[1]
    
    if mma[2]>31:
        z=abs(mma[2]-63)*-1
    else:
        z=mma[2]

    '''print('x=')
    print(x)
    print('y=')
    print(y)'''
    if a<14:
        a+=1
        currentx.append(x)
        currenty.append(y)
    else:
        currentx.append(x)
        currenty.append(y)
        currentx.pop(0)
        currenty.pop(0)
        for i in currentx:
            sumx+=i
        for z in currenty:
            sumy+=z
        nowx=sumx/14
        nowy=sumy/14
        sumx=0
        sumy=0
        display.set_pen(0,255,0)
        display.clear()
        display.set_pen(250,0,250)
        display.circle(120, 120, 120)
        display.set_pen(0,255,0)
        display.circle(120, 120, 118)
        display.set_pen(250,0,250)
        display.circle(120, 120, 60)
        display.set_pen(0,255,0)
        display.circle(120, 120, 58)
        display.set_pen(0,255,250)
        #display.circle(int(120-nowy*3.75),int(120+nowx*3.75),10)
        display.circle(int(120-nowy*5.45),int(120+nowx*5.45),10)
        display.set_pen(250,0,250)
        display.pixel_span(0,120,240)
        display.pixel_span(0,121,240)
        for i in range(240):
            display.pixel(120,i)
            display.pixel(121,i)
        write16x32(88,2,2)
        write16x32(58,18,2)
        write16x32(58,18,34)
        write16x32(89,2,34)
        if 0<=int(nowx*4):
            if nowx*4<10:
                write16x32(int(abs(nowx*4))-48,34,2)
            else:
                write16x32(int(abs(nowx*4)/10)-48,34,2)
                write16x32(int(abs(nowx*4)%10)-48,34+16,2)
        else:
            write16x32(45,34,2)
            if abs(nowx*4)<10:
                write16x32(int(abs(nowx*4))-48,34+16,2)
            else:
                write16x32(int(abs(nowx*4)/10)-48,34+16,2)
                write16x32(int(abs(nowx*4)%10)-48,34+32,2)
        if 0<=int(nowy*4):
            if nowy*4<10:
                write16x32(int(abs(nowy*4))-48,34,34)
            else:
                write16x32(int(abs(nowy*4)/10)-48,34,34)
                write16x32(int(abs(nowy*4)%10)-48,34+16,34)
        else:
            write16x32(45,34,34)
            if abs(nowy*4)<10:
                write16x32(int(abs(nowy*4))-48,34+16,34)
            else:
                write16x32(int(abs(nowy*4)/10)-48,34+16,34)
                write16x32(int(abs(nowy*4)%10)-48,34+32,34)
        display.update()

下面开始分段讲代码实现

先说说屏幕是怎么显示的把:

因为图形库已经由固件自带,所以只需要完成基本图形绘制即可完成示例水平仪形状的绘制,简要学习了自带的方法,本项目用到的主要有

set_pen设置画笔颜色
circle画实心圆
pixel_span画水平线
pixel画点
 
 

,但是自带的方法里面没有画空心圆,只有画实心圆的方法,所以,画两个同心圆的线框还是通过不同大小的实心同心圆叠加产生的效果,按照图层叠加,使用clear方法填充完背景色后,首先画半径为120的紫色大圆,再画半径118的背景色小圆,再画半径60的紫色小圆,最后画半径58的背景色小圆(代码118-125行),画完同心圆就应该画正交线了,为了美观,正交线也是两个像素的宽度,但不知为啥,这个显示库只提供了水平线的方法,没提供竖直线的方法,我只好写了个For循环使用pixel方法手工画了一次130-134行(也可能是人家有这些方法我没发现,看见群里其他小伙伴做的那么炫,我可羡慕了)

 

以上内容实现了基础的屏幕显示,可更重要的还有个数据来源啊,本来想偷懒的,可翻遍了gayhub也没找到一个薅来就能用的MMA7660 micro python库,只要亲自下场看datasheet了,不过幸亏这玩意简单,就十个寄存器,前三个就是x、y、z的坐标,剩下的寄存器一块来看下程序里都用到了啥FtLCqmeELfOrCT_pJw3zxooUdFeb

按照之前的硬件图,设置相应的iIC接口
i2c = I2C(1, scl=Pin(11), sda=Pin(10), freq=400_000)
这个传感器的IIC地址为76也就是HEX的4C
 
由于这个传感器上电就是Standby模式,这个模式只能用来配置寄存器不能读取数据,所以,需要将MMA7660的模式配置到Active模式,就可以正常使用了
也就是给0x07寄存器的第0位写个1,至于其他的采样率中断等设置,本例没用到,就没具体设置了。
 
虽然现在数据是有了,但是很不稳定,小球在屏幕上乱跳,所以,又去学习了各种滤波算法,群里小伙伴推荐到kalman filter由于数学水平不够实在是没理解,就去寻找了其他的滤波方式,最终经过不同的实验,在以下文章找到了一个合适的滤波算法,十大滤波算法程序大全(Arduino精编无错版) - Arduino - 极客工坊 - Powered by Discuz! (geek-workshop.com)
使用了其中的滑动平均滤波算法,设置了14个数据为一组,每次将新采集的数值放到列表,而将最老的一个数据踢出去,先进先出,这样就解决了数据平滑稳定的问题,不过由于这个算法吃ram,加上后面要讲的刷写字符,所以,整个水平仪看起来比较迟钝。
 
做到这呢,基本上是实现了气泡在里面滚动,其中有句代码,是我自己按照实际标定的,并不是理论值,就是这个,
display.circle(int(120-nowy*5.45),int(120+nowx*5.45),10)
按照mma7660的读数-32到31,这64个数值映射到0-240的屏幕区间上,应该是X3.75的系数,但是我通过实践发现读出来的数值最大才是22,所以只好手动标定为5.45,好让他映射到整个屏幕。(这一点理论和实际不符,如果有什么错误,还请各位大佬指正)
 
做完这些,就要做文字显示了,可惜这个图形库里自带的字体丑的很,我不愿意用,只好自己写了个将板子自带个vga1_16x32字体刷写到屏幕上的方法(代码40-56行),这个方法就是用pixel像素按照16x32的尺寸先行后列去填充,一个大循环读取两个字节填充一行,然后32个大循环填充整个字符,在我的实践看来,这个操作是很费时间的,不知道是否还有更快的方法,还请大佬斧正。

好了,关于此项目的开发,基本就是这些了,谢谢各位看官老爷的阅读,小弟才疏学浅,日后还需要向各位大佬多加学习!

附件下载
shuipingyi.zip
团队介绍
已经工作五年的老油条,某车企调车的,所以学校那一栏就空着了,如果有小伙伴一块来组队玩更好
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号