LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

棱镜屏做的桌面 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 蓝牙,微控制器能控制的设备,只要你愿意的话,可以有很多眼前一新的创意实现。