棱镜屏做的桌面 3D 玩物
故事之前
这些年看过非常多的创意项目,如魔镜,如悬空钟等等,都心动过,看着手头有了人生第二块 MCU,免费的 Air001,便想做个小玩具可以摆弄摆弄,于是盯上了小电视项目。项目很简单,主要区别还是体现在内容不同而已,我并不想做个别人家有的电视,因为你可以看到晒作品的,内容基本上都是千篇一律的动图展示。而我想做的,是一个属于自己专有内容的电视。
便是事故
最初我的想法是,做个迷宫类游戏,能在透明空间里穿梭,后面发现,我引个 C++ 的标准库,如字符串标准库,就报空间不足,一看 Air001 的空间只有 32K,Ardunio 平台就占了 1/3,游戏写起来估计不是很好的体验,毕竟你做个玩具,享受过程也是很重要的。
于是我换了想法,想做成动态视觉的内容,由陀螺仪来控制游戏常用的无限远近景应该有不错的效果。结果买的国产屏幕,分辨率是 240x240,但是 Air001 的 2K 内存,很难支撑我想要 6 层 240x240x16 bits (675K)计算,我各种压缩点来算依旧算不过来。当然过程主要不快的体验,就是买的那个屏幕,驱动是 ST7789,但是第三方库用不了,所以得自己写驱动,且最让人崩溃的是屏幕的质量,莫名其妙就漏液了。
最后,随着想法和屏幕一起坏掉,便重新利用之前的 SBC 和大屏幕,SBC 来采集应用显示然后输出到 SPI 大屏幕,然后 Air001 驱动陀螺仪来控制被 SBC 采集的显示内容。过程最折腾的,大概就是 SBC 的 SPI 屏幕驱动,这时候才能知道树莓派和国产派的差距,少了很多硬件级别的开源项目支持不说,资料差的真不是一点两点,如基本的 NanoPC T4 的 I2C 和 SPI 对应的 GPIO 都没有标出来,你让别人都不知道怎么帮你开发了。你买国产嵌入式 Linux 设备,大抵你也只能当桌面 Linux 应用使用设备或者跑分设备,根本没法像树莓派一样当嵌入式 Linux 开发设备。
准备
- 树莓派/国产派
- 微控制器
- 陀螺仪
- 棱镜
杜邦线或者焊台面包板等看自己习惯和需要准备。
控制
我使用的陀螺仪是 MP6050,网上有比较多资料,基本是自己写驱动,也可以直接使用 Arduino 的第三方库,Electronic Cats 写给 Arduino 的 DMP 实例直接可以用,其中 Air001 在使用 IIC 的时候需要改为:
Wire.begin(SDA, SCL);
然后输出也需要改:
//#define OUTPUT_READABLE_YAWPITCHROLL
#define OUTPUT_TEAPOT
烧录后,是串口对接数据的。
显示
大屏幕的驱动是 ILI9341,这个内核的 fbtft 模块已经有该驱动,虽然是在 staging 里头的。这里题外话说下,虽然这里也有 ST7789 的驱动,但是现在很多国产 7PIN 的 ST7789 屏幕是驱动不了的,主要是因为它初始化得先下拉 SCK,没有软件重置指令,而且每次传输都得先下拉 SCK,然后上下拉 RST,所以跟之前的 4PIN/8PIN 驱动不兼容。
首先是 SBC 的 SPI 功能,很多 SBC 都是默认没有开启的,不管是官方镜像还是 Armbian,我是以 Armbian 来弄的,但是这里不重复之前已经写过 Armbian 编译的东西。NanoPC T4 的 SPI 和 UART4 是冲突的,所以需要:
&spi1 {
status = "okay";
pinctrl-names = "default", "sleep";
pinctrl-1 = <&spi1_gpio>;
};
&uart4 {
status = "disabled";
};
在使用 fbtft 的驱动之前,我们可以先测试 SBC 的 SPI 能不能用,这个需要 spidev 的支持:
&spi1 {
status = "okay";
pinctrl-names = "default", "sleep";
pinctrl-1 = <&spi1_gpio>;
spidev0: spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <10000000>;
status = "okay";
};
};
这里注意 spidev 的 compatible,以前可以是 spidev,现在可不行了,可以看 StackOverflow 的解释。
编译系统后的 spidev 模块如果没有自动加载就:
echo spidev >> /etc/modules-load.d/modules.conf
modprobe spidev
这时候应该就可以看到 /dev/spidevX.X
了。内核的 spidev_test.c
可以测试,但是想测试 SPI 和屏幕的话,可以使用 BB-CP 作者的 SPI_LCD 项目,编辑 spi_lcd.c
加入 NanoPC T4 的 GPIO 支持:
#define USE_GENERIC
#define USE_NANOPIPCT4
#ifdef USE_NANOPIPCT4
static int iGenericPins[] = {
-1,
-1, -1, -1, -1, -1, -1, 32, 145, -1, 144,
33, 50, 35, -1, 36, 54, -1, 55, 39, -1,
40, 56, 41, 42, -1, 149, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
#endif // USE_NANOPIPCT4
这里你可以看到好多 -1
,因为你根本不知道 NanoPC T4 的 GPIO 的 PIN 口,没资料可以查,SPI1 的 GPIO 也是根据代码找的。
这里要说下 Rockchip 的 GPIO 计算规则,为 Bank、Group 和 Pin 组成,命名为大概为 GPIO{$Bank}_{$Group}{$Pin}
,其中 Bank 和 Pin 是数字,Group 是 A-Z,对应 0-26,计算规则是 $Bank * 32 + $Group * 8 + $Pin
,NanoPC T4 官网标注的 11 PIN 口对应的是 GPIO1_A1
,所以他的 GPIO 值是 33。测试可以:
echo 33 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio33/direction
echo 1 > /sys/class/gpio/gpio33/value
之后 make
会安装 SPI_LCD,我们这里修改 main.c
:
#define LCD LCD_ILI9341
int width=240, height=320;
将里头的调用改为我们的接线:
rc = spilcdInit(LCD, 0, 0, 31250000, 13, 11, 15);
这里的 13、11,15 是 NanoPC T4 板子上的编号,对应的值是我们之前 iGenericPins
数组定的 GPIO 值。于是我们 13 连 DC,11 连 RST,15 连 BLK,然后就是 SPI 的两根线连接,SCK 对应 SPI 的时钟线,MOSI 对应 SDA,当然 NanoPC T4 默认是 UART 而不是 SPI,所以花印是 TX 和 RX,根据数据传输规则,我们很容易知道 MISO 连 RX,MOSI 连 TX。连线后执行:
make -f make_sample
./lcd
屏幕就会出现各种测试输出。虽然说 BB-FB 是替换树莓派 fbtft + fb-ili9341 的,但是年久失修,作者也不打算处理分辨率的问题,所以 BB-FB 基本是没法用的,所以接下来我们主要还是使用的是 fbtft 来驱动屏幕。fbtft 默认是用的是 spidev0,所以与我们之前开启的 spidev 冲突,这里修改下,一并改了 NanoPC 的 i2c 报错问题:
&spi1 {
status = "okay";
pinctrl-names = "default", "sleep";
pinctrl-1 = <&spi1_gpio>;
pinctrl-0 = <&spi0_cs1>;
cs-gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>;
spidev0: spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <10000000>;
status = "disabled";
};
ili9341v@1 {
status = "okay";
compatible = "ilitek,ili9341";
pinctrl-1 = <&ili9341_pins>;
reg = <0>;
spi-max-frequency = <32000000>;
rotate = <180>;
rgb;
fps = <30>;
buswidth = <8>;
reset-gpios = <&gpio1 RK_PA1 GPIO_ACTIVE_HIGH>;
dc-gpios = <&gpio1 RK_PA3 GPIO_ACTIVE_LOW>;
led-gpios = <&gpio1 RK_PA4 GPIO_ACTIVE_LOW>;
debug = <0>;
};
};
&uart4 {
status = "disabled";
};
&pinctrl {
fusb30x {
fusb0_int: fusb0-int {
rockchip,pins = <1 2 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
spi1 {
spi1_gpio: spi1-gpio {
rockchip,pins =
<1 7 RK_FUNC_GPIO &pcfg_output_low>,
<1 8 RK_FUNC_GPIO &pcfg_output_low>,
<1 9 RK_FUNC_GPIO &pcfg_output_low>,
<1 10 RK_FUNC_GPIO &pcfg_output_low>;
};
spi0_cs1: spi0_cs1 {
rockchip,pins =
<1 10 RK_FUNC_GPIO &pcfg_output_low>;
};
ili9341_pins: ili9341_pins {
rockchip,pins =
<1 1 RK_FUNC_GPIO &pcfg_output_high>,
<1 3 RK_FUNC_GPIO &pcfg_output_low>,
<1 4 RK_FUNC_GPIO &pcfg_output_low>;
};
};
};
&i2c4 {
status = "okay";
i2c-scl-rising-time-ns = <160>;
i2c-scl-falling-time-ns = <30>;
clock-frequency = <400000>;
fusb0: typec-portc@22 {
compatible = "fairchild,fusb302";
reg = <0x22>;
pinctrl-names = "default";
pinctrl-0 = <&fusb0_int>;
int-n-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
vbus-5v-gpios = <&gpio4 26 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
这里的 GPIO 口就配置跟我们测试时使用的一样,然后重新编译系统,开机会自动加载 fbtft 和 fb_ili9341 模块,在 /dev
可以找到 fbX
设备。
应用
最终我们是要将想要的应用投放出去,这里我们投放的是能够被陀螺仪控制的飞机模型。安装 Processing 后,需要安装 toxiclibs 库:
cd $processing_sketch/libraries
wget https://github.com/postspectacular/toxiclibs/releases/download/0021/toxiclibs-complete-0021.zip
unzip toxiclibs-complete-0021.zip
运行 Electronic Cats 给的 Arduino 实例里的 processing 代码,就可以直接控制飞机了。然后用 ffmpeg 将他们按 rgb565 编码给之前我们 /dev/fbX
设备:
ffmpeg -video_size 280x280 -framerate 5 -f x11grab -i :0.0+0,200 -vf scale=160:-2 -pix_fmt rgb565le -f fbdev /dev/fb1
最后要说的是,这里展示的是飞机模型,但是我们完全可以投射其他应用,操控方式也可以利用 WiFi 蓝牙,微控制器能控制的设备,只要你愿意的话,可以有很多眼前一新的创意实现。