B站视频地址为https://www.bilibili.com/video/BV1sF411t7Tj?share_source=copy_web
任务要求
完成具体项目为任务8 利用姿态传感器制作一个水平仪
-
通过开发板自带的mma7660收集姿态信息
-
通过滤波算法对收集来的姿态信息做滤波处理,消除抖动
-
通过ST7789主控,将收集到的姿态信息绘制到内建的240*240屏幕上
一:环境配置
1、使用arduino IDE:
因为我没有使用micropython的经验,而且过去用过一些arduino设备进行过小东西的制作,所以本次使用了arduino IDE作为开发环境。
如果是纯新手,什么也不懂的,建议使用官方推荐的开发软件,thonny,这个软件页面简洁,基础功能齐全,简单易上手,非常适合初学者(比如我)。安装的教程网上比较多,这里推荐一个硬禾的教学视频https://class.eetree.cn/live_pc/l_60fe7f4fe4b0a27d0e360f74
2、硬禾学堂的基于树莓派RP2040的嵌入式系统学习平台:
硬禾学堂为“2022年寒假在家一起练活动统学习平台”制作了一系列平台,我选用的平台是RP2040 Game kit。
他的原理图如下,具体可以参考https://www.eetree.cn/project/detail/698
二:程序实现:
程序使用arduino IDE编写,其实mcropython非常好,有非常多的例程,而且还是未来的趋势,主要我时间太短了,假期天天在家带孩子,比平时上班更忙。时间实在不允许。实际上这个活动2.27结束,我是2.25才完成程序,2.26写这个和做视频。
1、模块介绍:
1、显示屏的使用:
首先需要下载st7789的库,使用的是一个成品库TFT_eSPI。这里要非常感谢杨半泛大佬,是这位大佬给提供的库,要是从github上直接搜的话,有的接口引脚定义的不太一致,是没法运行的。
使用方式如下:
初始化配置
// 以下初始设置来自TFT_SPI的示例程序,第1、2行引入库文件,第三行是给屏幕命名叫tft
#include "SPI.h"
#include "TFT_eSPI.h"
TFT_eSPI tft = TFT_eSPI();
uint32_t targetTime = 0; //这句用来定义刷新屏幕的间隔
屏幕填充
tft.init(); //ST7889屏幕初始调用
tft.setRotation(0); //0是正上,1是顺时针旋转1x90度
tft.fillScreen(TFT_WHITE); // 设置背景为白色。或许白色看起来好看点
//tft.fillScreen( TFT_BLACK); 这里往下是一些其他可以用的颜色。
//tft.fillScreen(TFT_RED); 这里设个红色,结果背景出的是蓝色,我的天
//tft.fillScreen(TFT_GREEN);
//tft.fillScreen(TFT_BLUE);
//tft.fillScreen(TFT_BLACK);
// tft.fillScreen(TFT_GREY);
//tft.fillScreen(TFT_WHITE); //但是设的白色确实是显示的白色
画线
tft.drawLine(x0, y0, x1,y1, color);
参数:
x0:起点横坐标
y0:起点纵坐标
x1:终点横坐标
y1:终点纵坐标
color:线的颜色
tft.drawCircle(y1, x1, 5, TFT_GREEN);//画个坐标是x1,y1的小绿圈
//上一句如果是用的tft.fillCircle,就是画个实心的球,
写字
int xx=1;
tft.setTextColor(TFT_BLUE, TFT_WHITE);//括号里分别是文字颜色和文字的背景颜色
tft.drawCentreString("X=",10,10,2);// “”里是字符串,10和10是xy的坐标,2是代表文字大小
tft.setTextColor(TFT_BLUE, TFT_WHITE);//同上
tft.drawFloat(xx,1,25,10,2);//跟第3行类似,这里是告诉大家怎么显示一个运算出来或是传感器发送来的数值,1代表小数点后1位。25和10代表xy坐标,2代表字高
2、MMA7660的调用
mma7660是一种比较低级的姿态传感器,精度比较差。角度信息是从-32到31,也就是只有0-64,这是只有6位吗。。。例程只有一个。相对是非常简单的。这个库可以在gitHUB搜到,是seeed做的
初始化
#include <Wire.h>
#include "MMA7660.h"
MMA7660 accelemeter;
int8_t x;
int8_t y;
int8_t z;
float ax,ay,az;
float xa ;
float ya ;
/*
头三行是调用相关的库文件和使能。4567行是用来定义相关参数,xyz代表xyz轴的角度,ax ay az是xyz轴的加速度。实际后者并没有用到。最后的xa ya是用来数值变换的。
*/
setup里这样调用
accelemeter.init(); //mma7660初始调用
采集数据我写了两个小函数,我估计正经是可以写一个函数就可以的。初哥没办法,愚公移山吧。这两个函数分别读xy轴的角度变化,为什么读x结果用的y的数据呢。是因为屏幕方向x的方向对应着传感器的y。所以就这样了。
//读取x坐标
int GetX(){
accelemeter.getXYZ(&x,&y,&z); //利用mma获取小珠的xyz变化
return y;
}
//读取y坐标
int GetY(){
accelemeter.getXYZ(&x,&y,&z); //利用mma获取小珠的xyz变化
return x;
}
4、具体显示
显示内容是这样的,首先擦除小球过去位置的图像,然后画小球现在位置的图像,最后画标尺线,
//这段内容是在loop里的, 画框线和写入xy值,用了两个小函数,让程序可编辑性或是代用性更强一点。
//我怀疑画小球可能也可以用一个小函数,时间有点紧,脑袋有点笨,只能直接放进loop了
if (targetTime <= millis()) {
targetTime = millis() + 15; // Update emter every 35 milliseconds 每35ms刷新一次
//现在要开始画小圈了,先画个白色的,和背景颜色一样。相当于恢复背景颜色
tft.fillCircle(FilterValueXB, FilterValueYB, 10, TFT_WHITE); //画个中间位置的圈
tft.drawCircle(FilterValueXB, 231, 5, TFT_WHITE); //画个水平位置的圈
tft.drawCircle(231, FilterValueYB, 5, TFT_WHITE); //画个垂直位置的圈
FilterValueYB = FilterValueY;//用新的y参数带入y参数,x也是一样原理
FilterValueXB = FilterValueX;
//下面3行是把新的参数带入进去,这回得画带颜色的了
tft.fillCircle(FilterValueXB, FilterValueYB, 10, TFT_GREEN); //画个中间位置的圈
tft.drawCircle(FilterValueXB, 231, 5, TFT_BLACK); //画个水平位置的圈
tft.drawCircle(231, FilterValueYB, 5, TFT_BLACK); //画个垂直位置的圈
drawStaff(); //画框线
drawTxt(); //写入xy的值
画框线和写如xy值的小函数
void drawStaff() //弄一个画标尺的小函数,包括大框中间的十字标尺和水平,垂直标尺
{
tft.drawLine(221, 0, 221, 221, TFT_GREEN);
tft.drawLine(0, 221, 221, 221, TFT_GREEN);
for(int i=-12; i<13; i+=1)
{
tft.drawLine(110+i*8, 106, 110+i*8, 114, TFT_BLUE);//中间大框的横线
tft.drawLine(110+i*8, 222, 110+i*8, 225, TFT_BLUE);//下面水平条的横线1
tft.drawLine(110+i*8, 237, 110+i*8, 240, TFT_BLUE);//下面水平条的横线2
tft.drawLine( 106, 110+i*8, 114, 110+i*8, TFT_BLUE);//中间大框的竖线
tft.drawLine(222, 110+i*8, 225, 110+i*8, TFT_BLUE);//下面水平条的竖线1
tft.drawLine(237, 110+i*8, 240, 110+i*8, TFT_BLUE);//下面水平条的竖线2
}
for(int i=0; i<13; i+=4)
{
tft.drawCircle(110, 110, i*8, TFT_RED); //画个垂直位置的圈
}
}
void drawTxt()
{
int xx=map(xa,32, -31,-90,90);
int yy=map(ya,32, -31,-90,90);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawCentreString("X=",10,10,2);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawFloat(abs(xx),1,25,10,2);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawCentreString("Y=",10,30,2);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawFloat(abs(yy),1,25,30,2);
}
5、滤波算法
这个mma7660,至少我这个真的是抖的太厉害了。从网上找的滤波算法,一共10种,第十一种卡曼滤波我没试,其他都试了一遍,按有关资料来说,用32个平均值来滤波就行了,实际效果不好,还是抖的很厉害。
算法抄自极客工坊 http://www.geek-workshop.com/thread-7694-1-1.ht
我就不转载了,这里把我修改后使用的分享一下,因为我比较笨,还是xy轴各用一次滤波算法,而不是只用一个函数,分别算两次。
//算术平均滤波法
#define FILTER_N 30 //理论上越大越平滑,但是太大会卡,因为mcu算力不够。。。
float FilterX() {
int i;
int filter_sum = 0;
for(i = 0; i < FILTER_N; i++) {
filter_sum += GetX();
delay(5);//原版是1
}
return (float)(filter_sum / FILTER_N);
}
float FilterY() {
int i;
int filter_sum = 0;
for(i = 0; i < FILTER_N; i++) {
filter_sum += GetY();
delay(1);
}
return (float)(filter_sum / FILTER_N);
}
6、整体实现
流程图如下
这一部分我将把全部代码写出来并用注释的方式来介绍代码的实现。里面定义成float的,完全都应该是定义成int的,只不过我想定义成float是不是能平滑点呢,似乎是效果有限。毕竟传感器来的数据只有6位。
/*
* 以下初始设置来自mma7660fc的示例程序,前3行是引入库文件,后面4行是引入小珠坐标的定义字,其实就x和y有用
*/
#include <Wire.h>
#include "MMA7660.h"
MMA7660 accelemeter;
int8_t x;
int8_t y;
int8_t z;
float ax,ay,az;
float xa ;
float ya ;
/*
* 以下初始设置来自TFT_SPI的示例程序,第1、2行引入库文件,第三行是给屏幕命名叫tft
*/
#include "SPI.h"
#include "TFT_eSPI.h"
TFT_eSPI tft = TFT_eSPI();
uint32_t targetTime = 0; //这句用来刷新屏幕的
//定义滤波前后的x值 y值
float FilterValueX;//滤波后的x
float ValueX;
float ValueXB;
float FilterValueY;
float ValueY;
float ValueYB;
int Value=0; //这个可能是没啥用,本来是用在最后滤波的,我怀疑全用一个value是不行的,所以后面滤波那里又把vlaue分别携程valuex和valuey了
//定义时刻2的x值和y值,可能直接用滤波后的当时间2的值也行,回头要测试以下
int FilterValueXB;//x滤波后的值2,也就是值B
int FilterValueYB;
void setup()
{
accelemeter.init(); //mma7660初始调用
Serial.begin(9600);
/*
* 下面是设置屏幕的部分,先画出一部分来,省的完全都是用loop画,节省一些资源
*/
tft.init(); //ST7889屏幕初始调用
tft.setRotation(0); //0是正上,1是顺时针旋转1x90度
tft.fillScreen(TFT_WHITE); // 设置背景为白色。或许白色看起来好看点
//tft.fillScreen( TFT_BLACK); 一些其他可以用的颜色。
//tft.fillScreen(TFT_RED); 这里设个红色,结果背景出的是蓝色,我的天
//tft.fillScreen(TFT_GREEN);
//tft.fillScreen(TFT_BLUE);
//tft.fillScreen(TFT_BLACK);
// tft.fillScreen(TFT_GREY);
//tft.fillScreen(TFT_WHITE); //但是设的白色确实是显示的白色
targetTime = millis(); // Next update time 设置刷新用的,要不这东西不刷新
}
//---------------
void drawStaff() //弄一个画标尺的小函数,包括大框中间的十字标尺和水平,垂直标尺
{
tft.drawLine(221, 0, 221, 221, TFT_GREEN);
tft.drawLine(0, 221, 221, 221, TFT_GREEN);
for(int i=-12; i<13; i+=1)
{
tft.drawLine(110+i*8, 106, 110+i*8, 114, TFT_BLUE);//中间大框的横线
tft.drawLine(110+i*8, 222, 110+i*8, 225, TFT_BLUE);//下面水平条的横线1
tft.drawLine(110+i*8, 237, 110+i*8, 240, TFT_BLUE);//下面水平条的横线2
tft.drawLine( 106, 110+i*8, 114, 110+i*8, TFT_BLUE);//中间大框的竖线
tft.drawLine(222, 110+i*8, 225, 110+i*8, TFT_BLUE);//下面水平条的竖线1
tft.drawLine(237, 110+i*8, 240, 110+i*8, TFT_BLUE);//下面水平条的竖线2
}
for(int i=0; i<13; i+=4)
{
tft.drawCircle(110, 110, i*8, TFT_RED); //画个垂直位置的圈
}
}
//----------------
//----------------
/*
void xyzBall() //弄一个找珠子位置的函数
tft.setTextColor(TFT_BLACK); // Text colour 定义了文字颜色 这有用吗?
tft.drawCircle(y1, x1, 5, TFT_GREEN);//画个坐标是x1,y1的小绿球
}
*/
void drawTxt()
{
int xx=map(xa,32, -31,-90,90);
int yy=map(ya,32, -31,-90,90);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawCentreString("X=",10,10,2);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawFloat(abs(xx),1,25,10,2);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawCentreString("Y=",10,30,2);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.drawFloat(abs(yy),1,25,30,2);
}
void loop() {
//下面一段是数值变换一下。把数值变换成lcd屏的坐标
xa=FilterX();
FilterValueX =map(xa,32/3, -31/3,0,220) ; //满量程32到-31
ValueX = FilterValueX;
ya=FilterY();
FilterValueY =map(ya,-32/3, 31/3,0,220) ; //满量程32到-31
ValueY = FilterValueY;
if (targetTime <= millis()) {
targetTime = millis() + 15; // Update emter every 35 milliseconds 每35ms刷新一次
//现在要开始画小圈了,先画个白色的,和背景颜色一样。相当于恢复背景颜色
tft.fillCircle(FilterValueXB, FilterValueYB, 10, TFT_WHITE); //画个中间位置的圈
tft.drawCircle(FilterValueXB, 231, 5, TFT_WHITE); //画个水平位置的圈
tft.drawCircle(231, FilterValueYB, 5, TFT_WHITE); //画个垂直位置的圈
FilterValueYB = FilterValueY;//用新的y参数带入y参数,x也是一样原理
FilterValueXB = FilterValueX;
//下面3行是把新的参数带入进去,这回得画带颜色的了
tft.fillCircle(FilterValueXB, FilterValueYB, 10, TFT_GREEN); //画个中间位置的圈
tft.drawCircle(FilterValueXB, 231, 5, TFT_BLACK); //画个水平位置的圈
tft.drawCircle(231, FilterValueYB, 5, TFT_BLACK); //画个垂直位置的圈
drawStaff(); //画框线
drawTxt(); //写入xy的值
}
}
//读取x坐标
int GetX(){
accelemeter.getXYZ(&x,&y,&z); //利用mma获取小珠的xyz变化
return y;
}
//读取y坐标
int GetY(){
accelemeter.getXYZ(&x,&y,&z); //利用mma获取小珠的xyz变化
return x;
}
//算术平均滤波法
#define FILTER_N 30 //不是越大越好,太大会卡
float FilterX() {
int i;
int filter_sum = 0;
for(i = 0; i < FILTER_N; i++) {
filter_sum += GetX();
delay(5);//原版是1
}
return (float)(filter_sum / FILTER_N);
}
float FilterY() {
int i;
int filter_sum = 0;
for(i = 0; i < FILTER_N; i++) {
filter_sum += GetY();
delay(1);
}
return (float)(filter_sum / FILTER_N);
}
三:后记
除了选择用arduino IED开发外,最好还是用tonny开发,硬禾官方也提供了非常好的例程和相关的课程。。。。可是,我对python实在是一窍不通,实在是可惜了那么好的例程。。主要是用那个还能移植小游戏玩。还可以选择使用VSCODE开发。官方为VSCODE写好了一个扩展插件Pico-Go。
我觉得不像我有arduino包袱的完全可以从tonny入手,路要比用arduino宽广的多,也能走的更远。毕竟arduino更像是个玩具。
还有RP2040 game kit的硬件。实际我只是用了非常少的一部分功能,它已经留好了I2C SPI口。5个数字io口和2个adc口。还有红外线发射和接受原件。4个可定义按键和一个xy拨盘,真的能用来做很多东西,没准也可以发展成硬禾的wio terminal。 但是这个硬件还需要进一步打磨。要它的稳定性和一致性再好一些。比如这个姿态传感器,至少我的这个,抖的实在是太厉害了,又比如小行星dx发的游戏机固件,我的game kit刷完了就根本没有任何反应。而我这个硬件应该是好的,毕竟刷官方的演示小程序,小球是可以乱飞的。我怀疑还是我这个体质要差一点的原因。也不排除是我在拧外壳的时候,力量没掌握好,导致电路板上有应力,造成哪里损坏了。