本次项目任务还是选择做个水平仪(项目5 - 制作一个水平仪),之前在RP2040GameKit上已经实现过,积累了一定经验,这次在这块ssd1306上能否实现同样优良的显示效果呢,让我继续往下看。
技术线路图
(1)屏幕驱动
为了得到高性能的显示效果,选择了众多图形库里面兼容性最高的LovyanGFX图形库,这个库本来是使用在LCD上的,但是使用过程中发现它也支持ssd1306这个经典的黑白屏。那么意味在一块ssd1306上也可以享受到LCD上同样高级显示图形函数。这个屏幕的驱动图形驱动文件单独生成了个一个驱动文件,在Arduino 同时安装LovyanGFX这个支持库,即可体验到这个自带的例程。例如运行了其中一个鹦鹉的动画,效果跟LCD上显示速度一样快,没有卡顿的的感觉,因为这块板上的ssd1306也是用SPI外设类驱动的,相比用I2C驱动的ssd1306获得更好速度。 基于得到优秀的图形库的加持,使得后面的工作水到渠成,所以在此建议大家积极上手这库图形驱动库。
cfg.freq_write = 300000000; // 发送时的SPI时钟(最大80 MHz,80 MHz四舍五入为整数)
cfg.freq_read = 16000000; // 接收时的SPI时钟
DMA通道(0=不使用DMA)
cfg.pin_sclk = 10; // 设置 SPI 的 sclk 引脚编号
cfg.pin_mosi = 11; // 设定 SPI 的 mosi 引脚编号
cfg.pin_miso = -1; // 设置SPI的MISO引脚编号(-1=disable)
cfg.pin_dc = 9; // 设置SPI的D/C引脚编号(-1 = disable)
(2)PICO板卡支持
之前都是使用官方板块支持,后来发现有个更优秀的第三方板卡支持库,它极大的丰富拓展了PICO的功能,其中一个很牛的特性就是可以实现核心主频从133MHz超频到240MHz,这样就可以达到与esp32相同主频,同时还支持了FreeRTOS实时多任务的性能。但也同时带来一些兼容性的问题,可能会跟其他的库类没法共用,另外,在vscode上使用也比较困难,因为要下载支持的文件都是放github上的,至今还没在vscode上建好编程环境,只能在Arduino上默默的耕耘,幸好本项目的代码量不大。本次使用的pico板卡上集成了由I2C外设MM7760的姿态传感器,如果使用官方的支持库是在内部修改I2C的引脚设定,这样在系统内部改了其他PICO板卡使用的时候就又需要修改回来,而使用了这个个第三方库可以原地直接指定I2C的两个引脚,使用起来非常方便。
具体使用方法参见:https://github.com/earlephilhower/arduino-pico 。
#include <Wire.h>
Wire.setSDA(14);
Wire.setSCL(15);
Wire.begin();
之前用官方的板卡支持是需要改引脚脚本的,现在可以直接指定
(3)图形显示机理
为了实现水平仪的小球的实施移动效果而又不会有卡顿和晃眼的效果,这里使用了sprite(个人理解为图层)的功能,它的原理是将整幅图形放在sprite这内存上,然后等待所有需要绘制的图形绘制完成后整幅图像同时输出有效地解决了卡顿晃眼的问题。这好用的方法的缺点就是有点耗内存,如果屏幕比较大就会消耗更多的内容,由于我们的ssd1306的分辨率只有128*64,所以内存使用上不存在任何问题,可以大胆放心使用。
(4)姿态传感器数据采集
前期实现发现如果不进行滤波处理,姿态传统器的数据不太温度,水平仪的小球会出现周期性的抖动,这里使用一阶滞后滤波,其原理是最新采集的数据只能占据已有数据的一部分(40%),减少了新数据波动产生作用,对系统资源也没有很大的占用。
static int8_t x,y,z;
static int old_x, old_y;
accelemeter.getXYZ(&x, &y, &z);
// x = constrain(x, -21, 21);
// y = constrain(y, -21, 21);
/*滤波*/
int new_x = old_x * 0.6 + x * 0.4;
x = old_x = new_x;
int new_y = old_y * 0.6 + y * 0.4;
y = old_y = new_y;
/*计算x,y轴的角度,实现量角器的功能;*/
double rad_x = acos(x / 21.00); //21是x轴最大读数,量程。
double rad_y = acos(y / 21.00);
int angleX = abs(rad_x * 180 / PI) - 90;
int angleY = abs(rad_y * 180 / PI) - 90;
/*计算水泡中心位置核心参数*/
int center_x = map((int)x, 20, -20, 64, 0 );
int center_y = map((int)y, -20, 20, 32 , 128 - 32 );
spr.fillScreen(0);
spr.setCursor(5, 20);
spr.print(angleX);
spr.setCursor(5, 50);
spr.print(angleY);
spr.fillCircle(center_y, center_x, 10, TFT_WHITE);
spr.drawCircle(lcd.width() >> 1, lcd.height() >> 1, 10, TFT_WHITE);
spr.drawCircle(lcd.width() >> 1, lcd.height() >> 1, 20, TFT_WHITE);
spr.drawCircle(lcd.width() >> 1, lcd.height() >> 1, 32, TFT_WHITE);
spr.pushSprite(0, 0);
}
(5)彩灯协同显示
这次实现一个更炫酷的功能就是,除了用水平仪上的小球能够表征水平状态,另外包围屏幕的12颗灯珠也利用起来,就是同步指向水平仪的方向。 这里需要使用到极坐标的知识,我们把屏幕的中心看作极点,利用小球与极点的坐标计算出极坐标的极角和极径。用极角指映射到对应点亮灯珠,用极径作为灯珠的亮度指标,如果小球已经居中了,所有灯珠都熄灭了,这样的解决方案非常完美,具体现实请阅读代码。
float x_ = center_y - 64.0; // 计算位点到到极点的距离
float y_ = center_x - 32.0; // 计算位点到到极点的距离
float r = sqrt(x_ * x_ + y_ * y_); // 计算极径
float theta = atan2(y_,x_ )*180/PI+180; // 计算极角,还原到0-360便于理解。
使用 atan2这个函数可以完美解决atan在x值接近0时不稳定的像,具体细节大家可以去百度一下,这函数的取值范围是-Pi - Pi.
int led_point2 = map(theta, 0, 360, 9, 21); // 为什么映射都(9-21)?因为0度的时候正好是9点钟的位置,后面超过12点的灯珠我们用取余的方法,让它们又回到了正常的位置,这是没有实验现象辅助会有点难理解。
pixels.clear(); // Set all pixel colors to 'off'
pixels.setPixelColor(led_point2 % 12, pixels.Color(r, 0, 0)); // 用极径r作为灯珠的亮度指标
pixels.show(); // Send the updated pixel colors to the hardware.
(不要问我怎么那么厉害/dodge)
总结
本项目的代码量虽然不多,但较好的完成了指定任务,其中不乏一些创新点,例如在rp2040上采用LovyanGFX图形库驱动ssd1306 oled屏;采用极坐标方法映射水平仪和灯珠的位点,都是一些比较有趣的尝试。这里还是真诚的推荐大家使用Arduino来开发rp2040一些小型项目,Arduino生态庞大,库类选择多,相对于Micropython可以获得更加优秀和稳定的性能,但Micropython的性能还在上升的通道上,我们保持关注,另外人工智能正在深刻的改变我们的生活方式,我应积极拥抱新科技,不至于沦落为古董(股东?)。
感谢朋友的陪伴,让我们勇敢地继续前行。
代码地址:
链接:https://pan.baidu.com/s/1SsXPF4qHi5Fv6PrZBNWkdw?pwd=dnfd
提取码:dnfd