Skip to content

xddcore/Zero_Linux_Board

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

基础版正面1

Zero Linux Board - 一个Linux小电脑。

如果你喜欢此项目,欢迎为本项目点一个Star🌟

核心板视频介绍【自制】圆梦Linux小电脑

开源仓库介绍【自制|开源】小白也可以轻松复现的Linux小电脑

透明探索版便携式小电脑:【自制】技术宅up耗时两个月,自制掌上触屏小电脑【硬核】【透明探索版】

介绍

本项目是xddcore同学在2023年6月2日启动的一个Linux开发板项目。本项目旨在纪念学生时代的结束(从初二至今9年的电子编程折腾生涯)。同时,设计一块板子来满足一些需要Linux生态但是又不需要很强性能的场景。

特性

  1. 紧凑的设计。PCB尺寸49mm*49mm*1.6mm,带壳尺寸59mm*59mm*27mm
  2. 差不多性能。内置全志F1C200S(ARM9@400Mhz+)和RP2040(双Cortex M0+处理器核心,最高133MHz)。
  3. 方便的开发方式。仅一个Type-C接口可完成对F1C200S和RP2040的开发。另外还可通过F1C200S直接对RP2040进行编程。
  4. 充分的外设USB Hub(USB2.0*2)2.4 GHz WIFI3W扬声器锂电池充放电管理MPU6050等。
  5. 扩展板支持。RP2040GPIO全引出(除2个ADC引脚外),F1C200S部分核心外设引出。在后期将支持ISP屏幕/墨水屏➕摄像头➕麦克风一体化扩展板
  6. 透明探索版。外壳主体采用12块2mm透明亚克力拼装而成。

关于量产拼单活动

xddcore zero Linux开发板软硬件全开源,大家可以选择自行复现。为了响应大家的购买需求,特地开启量产拼单活动。本次量产拼单旨在集合大家的生产需求来拼单生产。这将显著显著降低大家制作开发板的成本,节约制作时间,并避免自行焊接带来问题。

活动记录
230806:已成功拼单45块Zero Linux Board核心板,并全部成功交付。
230817: 第二批量产拼单活动筹备进行中。本次将会拼单掌上小电脑套件(含核心板)。

活动参与方式

添加QQ群481227232,并填写支付问卷。


0. 目录结构

  1. Hardware: 所有硬件相关资料

1. 复现指南

1.1 硬件

1.1.1 核心板PCB

  1. PCB尺寸:49mm*49mm,板厚1.6mm
  2. PCB层数:4层
  3. 电源线宽度10mil,信号线宽度6mil
  4. WIFI天线50Ω阻抗匹配: 下单时选用嘉立创JLC0461H-7628层压结构,经过计算,天线走线宽度为13.75mil
  5. 最小孔径/外径选择: 下单时选用嘉立创0.25mm(外径0.35/0.4)
  6. 大量封装采用0402,手焊推荐开钢网(节省成本可开小钢片)。

硬件迭代

版本号 说明
V1.0 完成初步设计
V1.1 优化布线,引出IO。Bug预警:已知原有TP5400升压电路10uH 0402电感功率较小,升压时会导致电感烧毁,请替换为额定电流大于1.5A的功率电感(TP5400最高输出1.5A(Vbat=3.8v))。
V1.1-量产版本 优化布线,修复Bug。已稳定生产。

1.1.2 扩展板PCB

名称 进度 说明
MINIPC 扩展板 已成功发布 2.8寸ISP电容触摸屏,摄像头,麦克风,自定义按钮,全彩LED
桌面机器人扩展板 已绘制完成,等待测试 稚晖君ElectronBot桌面机器人的Linux版本

MINI_PC.JPG

1.1.3 外壳

亚克力外壳

平衡美观和实用,预计推出两种亚克力外壳形式(得益于n*2mm亚克力外壳方案带来Z轴方向的积木特性):

  1. 透明探索款:收藏陈列。全封闭外壳,避免进灰,搭配上蓝色阻焊层&1u沉金&JLC高清丝印,主打一个精致和帅。
  2. 基础款:一般开发应用。Type-C&TF Card接口,内部含电池槽&扬声器槽,主控散热片支持&顶板散热孔。

Note:如果把亚克力外壳的图纸(.dwg文件)发到淘宝进行生产,某些厂家图纸尺寸单位可能识别成mm,导致外壳大小仅有5.9mm*5.9mm。这时候需要提醒厂家外壳图纸单位为cm,外壳实际尺寸为5.9cm*5.9cm。

关于亚克力外壳组装的说明(非常重要):
在首次亚克力外壳设计中,采用了12块2mm厚度的亚克力。经过实际组装测试,亚克力高度缺少2.8mm,为了成功的组装采用如下方法修补:

方法一: 使用4颗2.8mm高度的M2防滑螺母垫高处理(M2防滑螺母安装在由下往上数的第4块亚克力上方)。效果图如下:
Acrylic

方法二: 在生产亚克力时,多生产一块3mm厚度的亚克力。最后共计生产12块2mm厚度亚克力和1块3mm厚度亚克力
12块2mm厚度亚克力dwg生产文件:点我下载生产文件
1块3mm厚度亚克力dwg生产文件:点我下载生产文件

3D打印外壳
  1. MINI PC外壳:尺寸90mm * 70mm * 34mm。内置2.8寸ISP电容触摸屏,麦克风,iphone扬声器等,可玩性极高。

MINI PC外壳螺丝要求:

  1. 2 * M3螺丝长度小于16.1mm大于12.1mm经实际测试,推荐16mm
  2. 2 * M3螺丝长度小于29.1mm大于25.1mm经实际测试,推荐29mm
  3. 2 * M2螺丝长度小于4mm大于2mm经实际测试,推荐4mm

1.1.4 其他硬件

  1. 4 *(M2螺丝+M2防滑螺母)。螺丝长度需大于27mm,M2防滑螺母厚度2.8mm。
  2. 锂电池尺寸需小于50mm*50mm*8mm,锂电池接口为XH2.54。锂电池接口极性如下图:

battery

  1. F1C200SESP8266EX散热片尺寸需小于10mm*10mm*12mm

1.1.5 Type-C连接方式

连接方式 功能
正插 连接USB(具体取决于拨码开关配置)
反插 连接F1C200S的串口0(USB-TTL),用于输出串口终端

1.1.6 Type-C拨码开关配置

功能(1-4Bit) 1 Bit 2 Bit 3 Bit 4 Bit
F1C200S的USB与USB HUBType—C断开连接 OFF OFF OFF OFF
F1C200S的USB与USB HUB的上行端口连接 OFF ON OFF ON
F1C200S的USB与Type—C连接 ON OFF ON OFF
功能(5-8Bit) 5 Bit 6 Bit 7 Bit 8 Bit
RP2040的USB与USB HUBType—C断开连接 OFF OFF OFF OFF
RP2040的USB与USB HUB的下行端口连接 OFF ON OFF ON
RP2040的USB与Type—C连接 ON OFF ON OFF

如何跃过USB HUB进行通信 操作:
将F1C200s和RP2040的拨码开关都跳到连接Type-c,然后Type-c反面供电(连接F1C200s的串口)。此时F1c200s和RP2040直接连接。

1.2 软件

在软件章节开始前,想说明本项目的RAM&Flash资源情况如下:

F1C200S RP2040
RAM 64MB 264KB
Flash 取决于SD卡容量(典型值32GB) 16MB(SPI Flash)

1.2.1 驱动支持情况:

驱动名称 支持情况
状态指示LED
WIFI网卡(ESP8266EX) ✅(WIFI速度下载806KB/s,上传729KB/s。以上结果由speedtest得出,仅供参考)
SD卡(SDIO)
视频硬解码 ❌(开发中)
音频codec
MPU6050(IIO Device)
2.8寸ISP电容触摸屏
USB OTG/Host/Device
USB HUB(键盘,U盘,摄像头)
F1C200S与RP2040的UART CDC通信;控制RP2040进入DFU模式,并对RP2040编程

1.2.2 快速验证

为了方便大家快速验证本开源项目,我提供以下SD卡镜像文件,大家可以以类似树莓派的玩法来游玩本开源项目。

镜像名称 下载链接
xddcore_zero_debian_v5.10.186_230715.img.gz 点我查看
xddcore_zero_debian_v5.10.186_230810_Tiny.img(最新) 点我查看

开发板工具包路径(相关软件会放置在里面):

/xddcore_toolbox

1.2.3 U-Boot

  1. 获取U-Boot源码
git clone https://github.com/xddcore/u-boot.git -b zero-v2023.07-rc4
  1. 安装交叉编译工具链
sudo apt install gcc-arm-linux-gnueabi
  1. 拉取配置文件
make xddcore_zero_defconfig

点我查看性能测试结果

超频配置(CPU 408Mhz->720Mhz,DDR 156Mhz) 点我查看超频配置

make xddcore_zero_overclock_defconfig
  1. 编译U-Boot
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j4
  1. 写入U-Boot至SD卡(假如为/dev/sdb)

Note: SD卡在写入前请格式化为NTFS,数据无价,格式化前请做好数据备份。

sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/sdb bs=1024 seek=8
  1. 验证烧写结果,通过gparted软件查看。
sudo apt install gparted
  1. 将SD卡插入Zero核心板背面的SD卡自弹插座中

  2. 连接Type-C线缆(注意反面插入为USB-TTL功能)

  3. 使用终端软件连接串口(波特率115200)

xddcore同学在此使用的是Termius,大家可以选择自己喜欢的终端软件

U-Boot

在此,我们可以看到U-boot成功在我们的板子上跑起来了。

1.2.4 Linux主线(5.10.186)

  1. 获取Linux内核源码

长期支持版本: 6.1.36(因为内核逐渐向设备树方向发展,API换了不少(也删了不少),啥驱动都得自己折腾)

git clone https://github.com/xddcore/linux.git -b zero-v6.1.36

长期支持版本: 5.10.186(驱动丰富)

git clone https://github.com/xddcore/linux.git -b zero-v5.10.186-codec

在此我们使用v5.10.186内核进行接下来的开发。

  1. 拉取内核配置
make ARCH=arm xddcore_zero_defconfig 
  1. 编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j4

4.将arch/arm/boot/zImage和dtb移入SD卡的FAT16分区

1.2.5 使用Buildroot创建一个简易的rootfs来调试内核

  1. 拉取Buildroot源码
git clone https://github.com/xddcore/buildroot.git -b zero-v2023.05
  1. 拉取配置
make xddcore_zero_defconfig 
  1. 使busybox支持`depmod``
sudo make busybox-menuconfig
Linux Module Utilities-> 选中depmod模块
  1. ssh支持
sudo make menuconfig
Networking applications->Openssh,dhcpcd选中

Networking applications->
wireguard tools, 
wireless-regdb,
wireless tools(install shared library), 
wpa_supplicant,
wpan-tools,
xinetd,
xl2tp选中
  1. 编译
sudo make busybox

sudo make -j4
  1. 将rootfs放入EXT4分区

  2. 将sd卡插入Zero

  3. 串口终端波特率115200,用户名root,密码123456.至此根文件系统收工。

如果启动的时候,console有如下报错:

can't open /dev/console: Permission denied 权限不足,是因为移动rootfs的时候rootfs被虚拟机用户所属,

解决方案:把所有文件所有者切回root

chown root * -R

1.2.6 使用debian来作为rootfs(apt install很舒服)

  1. 安装工具
sudo apt install qemu-user-static -y
sudo apt install debootstrap -y
  1. 构建rootfs
sudo debootstrap --foreign --verbose --arch=armel  buster rootfs 

#如果梯子不太好用,也可以试试华为云的镜像
sudo debootstrap --foreign --verbose --arch=armel  buster rootfs http://mirrors.huaweicloud.com/debian/
  1. 挂载

此步坑点 apple m1 pro的pd虚拟机不支持arm 32bit(qemu),换x86/x64即可解决。

cd rootfs
sudo mount --bind /dev dev/
sudo mount --bind /sys sys/
sudo mount --bind /proc proc/
sudo mount --bind /dev/pts dev/pts/
cd ..
sudo cp /usr/bin/qemu-arm-static rootfs/usr/bin/
sudo chmod +x rootfs/usr/bin/qemu-arm-static
sudo LC_ALL=C LANGUAGE=C LANG=C chroot rootfs /debootstrap/debootstrap --second-stage --verbose
sudo LC_ALL=C LANGUAGE=C LANG=C chroot rootfs
  1. 预先安装一些软件(实现Zero板子的自力更生)
#网络
apt-get install wpasupplicant #安装WIFI配置相关的组件
apt-get install net-tools     #安装网络基础组件、如使用ifconfig等
apt-get install udhcpc        #当wifi连接成功后,需要用这个组件去获取IP地址


## 其他组件
apt-get install wireless-tools 
apt install sudo vim openssh-server htop
apt install pciutils usbutils acpi
  1. 配置账号
passwd root
123456
  1. 修改主机名
HOSTNAME=xddcore_zero(替换为你自己的主机名)
echo $HOSTNAME > /etc/hostname
sed -i '/localhost/s/$/\t'"$HOSTNAME"'/g' /etc/hosts
  1. 允许root用户通过ssh登陆(只有一个root用户,所以这步非常重要,不然root无法通过ssh登陆)
vim /etc/ssh/sshd_config

在里面添加

PermitRootLogin yes
  1. 退出rootfs,打包rootfs
exit  #退出chroot
rm rootfs/usr/bin/qemu-arm-static

cd rootfs
sudo umount   dev/pts/
sudo umount   dev/
sudo umount   sys/
sudo umount   proc/
sudo umount   dev/pts/

#打包文件(此时pwd在rootfs里面)
tar cvf ../rootfs.tar .    
  1. 将rootfs解压到sd卡的ext4(rootfs)中

  2. 启动后,通过串口命令行手动配网

  3. 安装WiFi驱动

insmod esp8089-spi.ko
  1. 启动wlan0无线网卡
ifconfig wlan0 up
  1. 新建wpa_supplicant配置文件(只需要进行一次)
vim /etc/wpa_supplicant/wpa_supplicant.conf

假如我有一个ssid为GL-MT1300-52d的无密码Wi-Fi。
填入以下内容:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=CN

network={
  ssid="GL-MT1300-52d"
  key_mgmt=NONE
}


有密码的WIFI
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=CN

network={
    ssid="MyWifiNetwork"
    psk="MyPassword"
    key_mgmt=WPA-PSK #WPA2-PSK
    pairwise=CCMP
    group=CCMP
}

  1. 连接WIFI
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf 
  1. 使用dhcp获取ip地址
udhcpc -i wlan0
  1. 此时,板子成功上网了,使用ssh连接,Enjoy it!

  2. 如果修改配置文件后,需要重启服务

pkill wpa_supplicant

sudo systemctl restart wpa_supplicant

1.2.7 debian-番外篇

  1. SWAP分区 64MB内存对于一台Linux小电脑来说,非常小。为了防止内存溢出,此处在sd卡中建立swap分区。 使用以下命令创建512MB Swap分区
dd if=/dev/zero of=/swap1 bs=1M count=512

mkswap /swap1

swapon /swap1

#永久性Swap分区
vim /etc/fstab
在内添加:
/swap1 swap swap defaults 0 0
  1. 开机自动配网

每次都串口cmd手动一个个输入命令,累死个人 注意:此步骤需要串口cmd先手动登陆root用户,成功登陆之后,会自动运行配网脚本。

0.创建/xddcore_toolbox/文件夹,将esp8089-spi.ko放入。

1.编辑~/.bashrc文件

vim ~/.bashrc

2.在~/.bashrc文件末尾写入如下内容。

insmod /xddcore_toolbox/esp8089-spi.ko
ifconfig wlan0 up
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
udhcpc -i wlan0

这里大家肯定有一个疑问,有没有不用登陆root用户,也可以自动配网的方式呢?答案是肯定的,操作步骤如下:

1.创建/etc/init.d/net.sh脚本

vim /etc/init.d/net.sh

2.文件中填入如下内容

#!/bin/bash
 
### BEGIN INIT INFO
# Provides:          skeleton
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Should-Start:      $portmap
# Should-Stop:       $portmap
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example initscript
### END INIT INFO
 
insmod /xddcore_toolbox/esp8089-spi.ko
ifconfig wlan0 up
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf
udhcpc -i wlan0
 
exit 0

2.给脚本设置可运行权限

chmod 777 net.sh

3.设置脚本自动启动

Note: 数字越大越晚执行,99代表最后执行。符合我们的需求。

update-rc.d net.sh defaults 99

4.如果未来不需要的时候,去除自动运行

update-rc.d net.sh remove

1.2.8 系统性能测试

1.安装sysbench

wget https://github.com/akopytov/sysbench/archive/refs/tags/1.0.20.tar.gz
tar -zxvf 1.0.20.tar.gz

2.编译安装

apt install libtool m4 automake pkg-config
cd sysbench-1.0.20/
./autogen.sh
# Add --with-pgsql to build with PostgreSQL support
./configure --without-mysql
make -j
make install

3.安装完成后,查看sysbench版本

sysbench --version

4.性能测试

sysbench cpu --cpu-max-prime=200 run

1.3 驱动开发

1.3.1 Linux音频

1.在debian下,查看声卡

cat /proc/asound/cards
  1. 安装alsa-utils
apt install alsa-utils
  1. 设置默认声卡
vim /etc/asound.conf

添加如下内容:
defaults.ctl.card 1
defaults.pcm.card 1
defaults.timer.card 1
  1. 安装音频/视频播放软件
apt install mplayer 
  1. 启动运放(PE10拉高 NUM=138)
echo 138 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio138/direction
echo 1 > /sys/class/gpio/gpio138/value
echo 138 > /sys/class/gpio/unexport
  1. 播放歌曲
mplayer /xddcore_toolbox/you.mp3 
  1. 网易云音乐命令行版本
apt install mpg123
apt install python3-pip
pip3 install NetEase-MusicBox
#这一步需要比较长的时间,耐心等待
#运行
musicbox

1.3.2 ESP8266EX WIFI驱动

以下驱动代码在buildroot生成的rootfs中调试开发。

性能参数:

WIFI速度下载806KB/s,上传729KB/s。以上结果由speedtest得出,仅供参考 wifi_speedtest

WIFI稳定性: 路由器:
WIFI_Stability_Router 手机热点: WIFI_Stability_SOFTAP 用来测试的手机比较老了,等后面换一台再试试

  1. 拉取代码
https://github.com/xddcore/ESP8089-SPI.git -b zero-esp8266ex
  1. 编译代码
#KBUILD=换成自己下载的内核路径(我提供的内核:https://github.com/xddcore/linux/tree/zero-v5.10.186-codec)

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- KBUILD=/home/parallels/xddcore/For_My_Board/linux
  1. 此时在代码根目录下会生成esp8089-spi.ko文件,将驱动文件拷贝至sd卡的rootfs中。

  2. SD卡插回开发板。

  3. 在开发板的命令行下执行以下命令,加载驱动

insmod esp8089-spi.ko

此时应该会出现如下log: wifi_driver_insmod

wlan0会自动启动

  1. 编辑 /etc/wpa_supplicant.conf 替换为自己的SSID和密码。
vi /etc/wpa_supplicant.conf

wifi_driver_conf

  1. 使用wpa_supplicant连接WIFI
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf 
  1. 此时你可以运行一些简单的wpa_cli来检查工作状态
wpa_cli -i wlan0 scan #扫描附近的WIFI

wpa_cli -i wlan0 scan_results #展示扫描结果

wpa_cli -i wlan0 status #查看网卡状态
  1. 运行结果如下:
root@xddcore-zero:~# wpa_cli -i wlan0 scan #扫描附近的WIFI
OK
root@xddcore-zero:~# 
root@xddcore-zero:~# wpa_cli -i wlan0 scan_results #展示扫描结果
bssid / frequency / signal level / flags / ssid
34:8f:27:5e:13:a8       2432    -80     [WPA2-EAP+FT/EAP-CCMP][ESS]     ASK4 Wireless (802.1x)
34:8f:27:9e:13:a8       2432    -81     [WPA2-PSK-CCMP][ESS]    Glasgow West End Staff
94:83:c4:0f:35:2f       2457    -56     [ESS]   GL-MT1300-52d
34:8f:27:1e:13:a8       2432    -81     [ESS]   ASK4 Wireless
root@xddcore-zero:~# 
root@xddcore-zero:~# wpa_cli -i wlan0 status #查看网卡状态
bssid=94:83:c4:0f:35:2f
freq=2457
ssid=GL-MT1300-52d
id=0
mode=station
pairwise_cipher=NONE
group_cipher=NONE
key_mgmt=NONE
wpa_state=COMPLETED
ip_address=192.168.8.193
p2p_device_address=18:fe:34:cd:30:ca
address=18:fe:34:cd:30:ca
uuid=8f4984fd-97c3-52a9-ac66-48aefaaa4498
  1. 对于ssh之类的应用来说,你可能需要一个IP地址
udhcpc -i wlan0

最后获取IP如下图: wifi_driver_ip

  1. 至此,网卡驱动折腾完毕

1.3.3 MPU6050驱动

  1. 安装IIO设备工具包
apt install libiio-utils
  1. 测试MPU6050
#读取所有IIO设备信息
iio-info
#读取MPU6050信息
iio_readdev -b 256 -s 1 iio:device0
#应该会返回乱码,因为以二进制返回
  1. 查看当前采样率
cat /sys/bus/iio/devices/iio:device0/sampling_frequency
  1. 查看可以设置的采样率
cat /sys/bus/iio/devices/iio:device0/sampling_frequency_available
  1. 设置采样率
echo 500 > /sys/bus/iio/devices/iio:device0/sampling_frequency
  1. 通过命令行读取加速度和角速度数据
while true; do
  x_accel=$(awk '{print $1*0.000598}' /sys/bus/iio/devices/iio:device0/in_accel_x_raw)
  y_accel=$(awk '{print $1*0.000598}' /sys/bus/iio/devices/iio:device0/in_accel_y_raw)
  z_accel=$(awk '{print $1*0.000598}' /sys/bus/iio/devices/iio:device0/in_accel_z_raw)
  x_anglvel=$(awk '{print $1*0.001064724}' /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw)
  y_anglvel=$(awk '{print $1*0.001064724}' /sys/bus/iio/devices/iio:device0/in_anglvel_y_raw)
  z_anglvel=$(awk '{print $1*0.001064724}' /sys/bus/iio/devices/iio:device0/in_anglvel_z_raw)

  echo "X: $x_accel m/s², Y: $y_accel m/s², Z: $z_accel m/s²"
  echo "X: $x_anglvel °/s, Y: $y_anglvel °/s, Z: $z_anglvel °/s"
  echo " "
  sleep 1
done
  1. 通过互补滤波计算X轴和Y轴角度

Note: Bash的数学运算效率不是很高,所以响应速度比较慢。以下代码仅作为测试。

#!/bin/bash

# 初始角度
angle_x=0
angle_y=0

# 滤波因子,可以根据实际情况进行调整
alpha=0.98

while true; do
  x_accel=$(awk '{print $1*0.000598}' /sys/bus/iio/devices/iio:device0/in_accel_x_raw)
  y_accel=$(awk '{print $1*0.000598}' /sys/bus/iio/devices/iio:device0/in_accel_y_raw)
  z_accel=$(awk '{print $1*0.000598}' /sys/bus/iio/devices/iio:device0/in_accel_z_raw)
  x_anglvel=$(awk '{print $1*0.001064724}' /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw)
  y_anglvel=$(awk '{print $1*0.001064724}' /sys/bus/iio/devices/iio:device0/in_anglvel_y_raw)
  z_anglvel=$(awk '{print $1*0.001064724}' /sys/bus/iio/devices/iio:device0/in_anglvel_z_raw)

  # 根据角速度计算角度增量,考虑到陀螺仪给出的是角速度,我们需要乘以时间间隔
  angle_x=$(echo "$angle_x + $x_anglvel * 0.01" | bc)
  angle_y=$(echo "$angle_y + $y_anglvel * 0.01" | bc)

  # 根据加速度计算角度,需要将加速度转换为角度
  accel_angle_x=$(echo "a( $y_accel / sqrt( $x_accel*$x_accel + $z_accel*$z_accel ) ) * 180 / 3.14159265" | bc -l)
  accel_angle_y=$(echo "a( $x_accel / sqrt( $y_accel*$y_accel + $z_accel*$z_accel ) ) * 180 / 3.14159265" | bc -l)

  # 使用互补滤波计算角度
  angle_x=$(echo "$alpha * $angle_x + (1 - $alpha) * $accel_angle_x" | bc)
  angle_y=$(echo "$alpha * $angle_y + (1 - $alpha) * $accel_angle_y" | bc)

  echo "X: $x_accel m/s², Y: $y_accel m/s², Z: $z_accel m/s²"
  echo "X: $x_anglvel °/s, Y: $y_anglvel °/s, Z: $z_anglvel °/s"
  echo "Angle X: $angle_x °, Angle Y: $angle_y °"
  echo " "
  sleep 0.01
done
  1. 利用c语言实现一个高效的互补滤波(以下代码写入mpu6050.c文件)
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <time.h>

#define ALPHA 0.98
#define DT 0.001  // 1ms
#define PRINT_INTERVAL 0.1  // 100ms
#define GYRO_SCALE_FACTOR 0.001064724
#define ACCEL_SCALE_FACTOR 0.000598

double get_value(char *filename) {
    double value;
    FILE *fp = fopen(filename, "r");
    fscanf(fp, "%lf", &value);
    fclose(fp);
    return value;
}

int main() {
    double angle_x = 0;
    double angle_y = 0;
    int print_counter = 0;
    int print_interval_counts = PRINT_INTERVAL / DT;

    struct timespec delay = {0, DT * 1e9}; // Delay for nanosleep

    while (1) {
        double x_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_x_raw") * ACCEL_SCALE_FACTOR;
        double y_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_y_raw") * ACCEL_SCALE_FACTOR;
        double z_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_z_raw") * ACCEL_SCALE_FACTOR;
        double x_anglvel = get_value("/sys/bus/iio/devices/iio:device0/in_anglvel_x_raw") * GYRO_SCALE_FACTOR;
        double y_anglvel = get_value("/sys/bus/iio/devices/iio:device0/in_anglvel_y_raw") * GYRO_SCALE_FACTOR;
        double z_anglvel = get_value("/sys/bus/iio/devices/iio:device0/in_anglvel_z_raw") * GYRO_SCALE_FACTOR;

        angle_x += x_anglvel * DT;
        angle_y += y_anglvel * DT;

        double accel_angle_x = atan2(y_accel, sqrt(pow(x_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;
        double accel_angle_y = atan2(x_accel, sqrt(pow(y_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;

        angle_x = ALPHA * angle_x + (1 - ALPHA) * accel_angle_x;
        angle_y = ALPHA * angle_y + (1 - ALPHA) * accel_angle_y;

        if (++print_counter >= print_interval_counts) {
            printf("X: %lf m/s², Y: %lf m/s², Z: %lf m/s²\n", x_accel, y_accel, z_accel);
            printf("X: %lf °/s, Y: %lf °/s, Z: %lf °/s\n", x_anglvel, y_anglvel, z_anglvel);
            printf("Angle X: %lf °, Angle Y: %lf °\n\n", angle_x, angle_y);
            print_counter = 0;
        }

        nanosleep(&delay, NULL);
    }

    return 0;
}

  1. 卡尔曼滤波(性能优于互补滤波)(以下代码写入mpu6050.c文件)
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <time.h>

#define DT 0.001  // 1ms
#define PRINT_INTERVAL 0.1  // 100ms
#define GYRO_SCALE_FACTOR 0.001064724
#define ACCEL_SCALE_FACTOR 0.000598

double get_value(char *filename) {
    double value;
    FILE *fp = fopen(filename, "r");
    fscanf(fp, "%lf", &value);
    fclose(fp);
    return value;
}

// 卡尔曼滤波器的结构
typedef struct {
    double q;  // 过程噪声协方差
    double r;  // 测量噪声协方差
    double x;  // 最优化估算值
    double p;  // 估算误差协方差
    double k;  // 卡尔曼增益
} kalman_state;

// 卡尔曼滤波器的初始化
void kalman_init(kalman_state *state, double q, double r, double p, double initial_value) {
    state->q = q;
    state->r = r;
    state->p = p;
    state->x = initial_value;
}

// 卡尔曼滤波器的更新
void kalman_update(kalman_state *state, double measurement) {
    // 预测
    state->p += state->q;

    // 更新
    state->k = state->p / (state->p + state->r);
    state->x += state->k * (measurement - state->x);
    state->p = (1 - state->k) * state->p;
}

int main() {
    struct timespec delay = {0, DT * 1e9}; // Delay for nanosleep
    int print_counter = 0;
    int print_interval_counts = PRINT_INTERVAL / DT;

    kalman_state state_x;
    kalman_state state_y;
    kalman_init(&state_x, 0.125, 32, 1023, 0);  // You may need to adjust these values based on your sensor's characteristics
    kalman_init(&state_y, 0.125, 32, 1023, 0);  // You may need to adjust these values based on your sensor's characteristics

    while (1) {
        double x_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_x_raw") * ACCEL_SCALE_FACTOR;
        double y_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_y_raw") * ACCEL_SCALE_FACTOR;
        double z_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_z_raw") * ACCEL_SCALE_FACTOR;
        double x_anglvel = get_value("/sys/bus/iio/devices/iio:device0/in_anglvel_x_raw") * GYRO_SCALE_FACTOR;
        double y_anglvel = get_value("/sys/bus/iio/devices/iio:device0/in_anglvel_y_raw") * GYRO_SCALE_FACTOR;
        double z_anglvel = get_value("/sys/bus/iio/devices/iio:device0/in_anglvel_z_raw") * GYRO_SCALE_FACTOR;

        double accel_angle_x = atan2(y_accel, sqrt(pow(x_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;
        double accel_angle_y = atan2(x_accel, sqrt(pow(y_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;

        kalman_update(&state_x, accel_angle_x);
        kalman_update(&state_y, accel_angle_y);

        if (++print_counter >= print_interval_counts) {
            printf("X: %lf m/s², Y: %lf m/s², Z: %lf m/s²\n", x_accel, y_accel, z_accel);
            printf("X: %lf °/s, Y: %lf °/s, Z: %lf °/s\n", x_anglvel, y_anglvel, z_anglvel);
            printf("Angle X: %lf °, Angle Y: %lf °\n\n", state_x.x, state_y.x);
            print_counter = 0;
            print_counter = 0;
        }

        nanosleep(&delay, NULL);
    }

    return 0;
}

  1. 编译
gcc -o mpu6050 mpu6050.c -lm
  1. 运行
./mpu6050
  1. 耐心大作战|一个利用X轴角度和Y轴角度设计的小游戏

在这个游戏中,你将看到一个由"."组成的20x20的网格。玩家(标记为"P")通过调整设备的角度来在屏幕上移动P。目标位置(标记为"T")在网格的一个固定位置。当玩家到达目标位置时,游戏结束,并显示"Congratulations, you won!"的消息。

新手模式(以下代码写入mpu6050_game.c文件)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <time.h>

#define DT 0.001  // 1ms
#define PRINT_INTERVAL 0.05  // 50ms
#define GYRO_SCALE_FACTOR 0.001064724
#define ACCEL_SCALE_FACTOR 0.000598
#define BOARD_SIZE 20

int TARGET_X=0,TARGET_Y=0;

double get_value(char *filename) {
    double value;
    FILE *fp = fopen(filename, "r");
    fscanf(fp, "%lf", &value);
    fclose(fp);
    return value;
}

typedef struct {
    double q;  
    double r;  
    double x;  
    double p;  
    double k;  
} kalman_state;

void kalman_init(kalman_state *state, double q, double r, double p, double initial_value) {
    state->q = q;
    state->r = r;
    state->p = p;
    state->x = initial_value;
}

void kalman_update(kalman_state *state, double measurement) {
    state->p += state->q;
    state->k = state->p / (state->p + state->r);
    state->x += state->k * (measurement - state->x);
    state->p = (1 - state->k) * state->p;
}

void print_board(int player_x, int player_y) {
    for(int i=0; i<BOARD_SIZE; i++) {
        for(int j=0; j<BOARD_SIZE; j++) {
            if(i == player_x && j == player_y) {
                printf("P ");
            } else if(i == TARGET_X && j == TARGET_Y) {
                printf("T ");
            } else {
                printf(". ");
            }
        }
        printf("\n");
    }
}

int main() {
    srand(time(NULL));
    TARGET_X = rand() % BOARD_SIZE;
    TARGET_Y = rand() % BOARD_SIZE;
    struct timespec delay = {0, DT * 1e9}; 
    int print_counter = 0;
    int print_interval_counts = PRINT_INTERVAL / DT;

    kalman_state state_x;
    kalman_state state_y;
    kalman_init(&state_x, 0.125, 32, 1023, 0);
    kalman_init(&state_y, 0.125, 32, 1023, 0); 

    int player_x = BOARD_SIZE / 2;
    int player_y = BOARD_SIZE / 2;

    while (1) {
        double x_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_x_raw") * ACCEL_SCALE_FACTOR;
        double y_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_y_raw") * ACCEL_SCALE_FACTOR;
        double z_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_z_raw") * ACCEL_SCALE_FACTOR;

        double accel_angle_x = atan2(y_accel, sqrt(pow(x_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;
        double accel_angle_y = atan2(x_accel, sqrt(pow(y_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;

        kalman_update(&state_x, accel_angle_x);
        kalman_update(&state_y, accel_angle_y);

        // 根据角度调整角色的位置
        if(state_x.x > 15 && player_x > 0) {
            player_x--;
        } else if(state_x.x < -15 && player_x < BOARD_SIZE - 1) {
            player_x++;
        }

        if(state_y.x > 15 && player_y < BOARD_SIZE - 1) {
            player_y++;
        } else if(state_y.x < -15 && player_y > 0) {
            player_y--;
        }

        // 清屏并打印游戏版面
        system("clear");
        print_board(player_x, player_y);

        // 检查角色是否已经到达目标
        if(player_x == TARGET_X && player_y == TARGET_Y) {
            printf("Congratulations, you won!\n");
            break;
        }

        nanosleep(&delay, NULL);
    }

    return 0;
}

高难度模式(以下代码写入mpu6050_game.c文件)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <time.h>

#define DT 0.001  // 1ms
#define PRINT_INTERVAL 0.05  // 50ms
#define GYRO_SCALE_FACTOR 0.001064724
#define ACCEL_SCALE_FACTOR 0.000598
#define BOARD_SIZE 20

int TARGET_X=0,TARGET_Y=0;

char last_board[BOARD_SIZE][BOARD_SIZE];

double get_value(char *filename) {
    double value;
    FILE *fp = fopen(filename, "r");
    fscanf(fp, "%lf", &value);
    fclose(fp);
    return value;
}

typedef struct {
    double q;  
    double r;  
    double x;  
    double p;  
    double k;  
} kalman_state;

void kalman_init(kalman_state *state, double q, double r, double p, double initial_value) {
    state->q = q;
    state->r = r;
    state->p = p;
    state->x = initial_value;
}

void kalman_update(kalman_state *state, double measurement) {
    state->p += state->q;
    state->k = state->p / (state->p + state->r);
    state->x += state->k * (measurement - state->x);
    state->p = (1 - state->k) * state->p;
}

void print_board(int player_x, int player_y) {
    for(int i=0; i<BOARD_SIZE; i++) {
        for(int j=0; j<BOARD_SIZE; j++) {
            char new_char;
            if(i == player_x && j == player_y) {
                new_char = 'P';
            } else if(i == TARGET_X && j == TARGET_Y) {
                new_char = 'T';
            } else {
                new_char = '.';
            }
            if(last_board[i][j] != new_char) {
                printf("\033[%d;%dH%c", i+1, j*2+1, new_char);
                fflush(stdout); // 确保字符被立即打印
                last_board[i][j] = new_char;
            }
        }
    }
}

int main() {
    srand(time(NULL));
    system("clear");
    TARGET_X = rand() % BOARD_SIZE;
    TARGET_Y = rand() % BOARD_SIZE;
    struct timespec delay = {0, DT * 1e9}; 
    int print_counter = 0;
    int print_interval_counts = PRINT_INTERVAL / DT;

    kalman_state state_x;
    kalman_state state_y;
    kalman_init(&state_x, 0.125, 32, 1023, 0);
    kalman_init(&state_y, 0.125, 32, 1023, 0); 

    int player_x = BOARD_SIZE / 2;
    int player_y = BOARD_SIZE / 2;

    while (1) {
        double x_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_x_raw") * ACCEL_SCALE_FACTOR;
        double y_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_y_raw") * ACCEL_SCALE_FACTOR;
        double z_accel = get_value("/sys/bus/iio/devices/iio:device0/in_accel_z_raw") * ACCEL_SCALE_FACTOR;

        double accel_angle_x = atan2(y_accel, sqrt(pow(x_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;
        double accel_angle_y = atan2(x_accel, sqrt(pow(y_accel, 2) + pow(z_accel, 2))) * 180 / M_PI;

        kalman_update(&state_x, accel_angle_x);
        kalman_update(&state_y, accel_angle_y);

        // 根据角度调整角色的位置
        if(state_x.x > 15 && player_x > 0) {
            player_x--;
        } else if(state_x.x < -15 && player_x < BOARD_SIZE - 1) {
            player_x++;
        }

        if(state_y.x > 15 && player_y < BOARD_SIZE - 1) {
            player_y++;
        } else if(state_y.x < -15 && player_y > 0) {
            player_y--;
        }

        // 打印游戏版面
        print_board(player_x, player_y);

        // 检查角色是否已经到达目标
        if(player_x == TARGET_X && player_y == TARGET_Y) {
            printf("\033[%d;%dH", BOARD_SIZE + 1, 1); // 将光标移动到版面下方
            printf("Congratulations, you won!\n");
            break;
        }

        nanosleep(&delay, NULL);
    }

    return 0;
}

  1. 编译
gcc -o mpu6050_game mpu6050_game.c -lm
  1. 运行
./mpu6050_game

1.3.4 2.8寸ISP电容触摸屏驱动&LVGL

  1. 下载交叉编译工具链
#!/bin/sh

HOST=arm-linux-gnueabi
SCRIPT_PATH=$(pwd)

#修改源码包解压后的名称
MAJOR_NAME=gcc-arm-linux-gnueabi

#修改需要下载的源码版本前缀和后缀
OPENSRC_VER_PREFIX=7.2
OPENSRC_VER_SUFFIX=.1

PACKAGE_NAME=${MAJOR_NAME}-${OPENSRC_VER_PREFIX}${OPENSRC_VER_SUFFIX}

#定义压缩包名称
COMPRESS_PACKAGE=gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabi.tar.xz

#定义编译后安装--生成的文件,文件夹位置路径
INSTALL_PATH=/opt/${PACKAGE_NAME}

#无需修改--下载地址
DOWNLOAD_LINK=https://releases.linaro.org/components/toolchain/binaries/7.2-2017.11/arm-linux-gnueabi/${COMPRESS_PACKAGE}

#下载源码包
do_download_src () {
   echo "start download ${COMPRESS_PACKAGE}..."
   if [ ! -f "${COMPRESS_PACKAGE}" ];then
      if [ ! -d "${PACKAGE_NAME}" ];then
        wget -c ${DOWNLOAD_LINK}
      fi
   fi
   echo "\033[1;33mdone...\033[0m"
}

#解压源码包
do_tar_package () {
   echo "start unpacking the ${PACKAGE_NAME} package ..."

   mkdir -p ${INSTALL_PATH}

   if [ ! -d "${PACKAGE_NAME}" ];then
      tar -xf ${COMPRESS_PACKAGE} -C ${INSTALL_PATH} --strip-components=1
   fi
   echo "done..."
}

#删除下载的文件
do_delete_file () {
   cd ${SCRIPT_PATH}
   if [ -f "${PACKAGE_NAME}" ];then
      sudo rm -f ${PACKAGE_NAME}
   fi
}

do_download_src
do_tar_package
# do_delete_file

exit $?
  1. 拉取源码 所有控件展示的demo
git clone https://github.com/xddcore/lv_port_linux_frame_buffer -b zero-lvgl

心率,血压demo

git clone https://github.com/xddcore/lv_port_linux_frame_buffer -b zero_lvgl_heartrate
  1. 拉取子模块
cd lv_port_linux_frame_buffer/
git submodule update --init --recursive
  1. 生成Makefile,然后编译源码
#ls看一下,如有/build,则请先执行rm -rf build/
mkdir build
cd build
cmake ..
make
  1. 运行
./lvgl_fb

一些优化体验的指令

echo -e "\033[?25l" > /dev/tty1 #关闭光标
echo -e "\033[?25h" > /dev/tty1 #开启光标

1.3.5 树莓派RP2040 USB驱动

RP2040的RUN(PE8,NUM=136)引脚以及BOOT(PE7,NUM=135)引脚受到F1C200S的控制,因此F1C200S可以方便的对RP2040进行编程。

  1. 操作RP2040进入DFU模式
echo 136 > /sys/class/gpio/export
echo 135 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio136/direction
echo out > /sys/class/gpio/gpio135/direction
echo 0 > /sys/class/gpio/gpio136/value
echo 0 > /sys/class/gpio/gpio135/value
sleep 1
echo 1 > /sys/class/gpio/gpio136/value
sleep 1
echo 1 > /sys/class/gpio/gpio135/value
echo 136 > /sys/class/gpio/unexport
echo 135 > /sys/class/gpio/unexport
  1. 将F1C200s USB模式切换为Host
echo host > /sys/devices/platform/soc/1c13000.usb/musb-hdrc.1.auto/mode

#补充一下如果要换成device模式的话,执行以下代码:
echo peripheral > /sys/devices/platform/soc/1c13000.usb/musb-hdrc.1.auto/mode
  1. RP2040已进入DFU模式(此时在Linux上表现为一个存储设备)
fdisk -l

rp2040_fdisk-l

  1. 挂载RP2040
#此处根据实际情况修改可能为sda1或sdb1
mount -t vfat /dev/sda1 /media
  1. 下载MicroPython固件
wget https://micropython.org/download/rp2-pico/rp2-pico-latest.uf2

Bug修复
Bug现象:重启后固件丢失,以及flash size无法正确返回的问题。
SPI Flash型号:W25Q128JV
解决办法:
根据数据手册,Clock frequency for Read Data instruction (03h) fR D.C. 50 MHz,最大读取数据指令时钟为50Mhz。MicroPython配置的PICO_FLASH_SPI_CLKDIV=4时,时钟约为60Mhz,将会导致SPI Flash无法工作。我们在编译程序时,需要手动降低至40Mhz(添加编译选项 PICO_FLASH_SPI_CLKDIV= 6,BOOT2_S_CFLAGS ?= -DPICO_FLASH_SPI_CLKDIV= 6)。
参考:

adafruit/circuitpython#4377
raspberrypi/pico-sdk#1304 (两种fixup思路)
https://github.com/adafruit/circuitpython/commit/aec03a409f66cdb90e1b51c7ffb827750eadd830

  1. 下载CircuitPython固件
wget https://downloads.circuitpython.org/bin/raspberry_pi_pico/en_US/adafruit-circuitpython-raspberry_pi_pico-en_US-8.2.0.uf2
  1. Pico-SDK
git clone https://github.com/raspberrypi/pico-sdk

Note: Pico-SDK的SPI Flash时钟部分和MicroPython具有相同Bug,参考上述方法进行修复。

  1. 烧录固件至RP2040
#micropython
cp /xddcore_toolbox/rp2-pico-latest.uf2 /media/

#circuitpython
cp /xddcore_toolbox/adafruit-circuitpython-raspberry_pi_pico-en_US-8.2.0.uf2 /media/
  1. 成功烧录后,你将看到如下提示:
[  164.934750] usb 1-1: USB disconnect, device number 2
[  164.948943] blk_update_request: I/O error, dev sda, sector 260 op 0x1:(WRITE) flags 0x0 phys_seg 1 prio class 0
[  164.959264] Buffer I/O error on dev sda1, logical block 259, lost async page write
[  165.499616] usb 1-1: new full-speed USB device number 3 using musb-hdrc
[  165.692558] cdc_acm 1-1:1.0: ttyACM0: USB ACM device
  1. 装个MINICOM(与MicroPython进行交互)
apt install minicom
  1. 查看USB串口设备

USB 串行设备通常显示为 /dev/ttyUSBx 或 /dev/ttyACMx,其中 x 是一个数字。

 ls /dev/tty*
  1. 连接RP2040
minicom -o -D /dev/ttyACM0

rp2040_micropython circuitpython 11. 运行如下测试代码

代码作用:查看SPI FLASH大小

import os

stat = os.statvfs('/')
flash_size_bytes = stat[0] * stat[2]

flash_size_MB = flash_size_bytes / (1024 * 1024)
print('SPI Flash size:', flash_size_MB, 'MB')

返回结果:

Adafruit CircuitPython 8.2.0 on 2023-07-05; Raspberry Pi Pico with rp2040
>>> import os

stat = os.statvfs('/')
flash_size_bytes = stat[0] * stat[2]

flash_size_MB = flash_size_bytes / (1024 * 1024)
print('SPI Flash size:', flash_size_MB, 'MB')

SPI Flash size: 14.9688 MB
>>> 
  1. 退出MINICOM
ctrl A Q
  1. 重启RP2040(如果需要)
echo 136 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio136/direction
echo 0 > /sys/class/gpio/gpio136/value
sleep 1
echo 1 > /sys/class/gpio/gpio136/value
sleep 1
echo 136 > /sys/class/gpio/unexport
  1. 操作RP2040进入DFU模式
echo 136 > /sys/class/gpio/export
echo 135 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio136/direction
echo out > /sys/class/gpio/gpio135/direction
echo 1 > /sys/class/gpio/gpio136/value
echo 1 > /sys/class/gpio/gpio135/value
echo 136 > /sys/class/gpio/unexport
echo 135 > /sys/class/gpio/unexport

1.3.6 语音识别与交互驱动

离线文字转语音espeak

apt install espeak

1.4 玩法推荐

以下所有DEMO,若运行报错,可尝试git pull拉取最新代码。

1.4.1 在ISP屏幕上显示系统信息

neofetch --off > /dev/tty1

1.4.2 在ISP屏幕上显示图片

  1. 安装fbi
apt install fbi
  1. 显示图片

这里,-a:这个选项会让 fbi 自动缩放图像,使其适应屏幕的大小。 -noverbose:这个选项告诉 fbi 在启动时不要显示任何冗长的信息或警告。 -d /dev/fb0 指定了帧缓冲设备,-T 1 指定了使用哪个虚拟终端,image.jpg 是要显示的图像的路径。

fbi -a -noverbose -d /dev/fb0 -T 1 image.jpg

如果要关闭可以键盘escq,或者直接杀死进程。 . 3.使用ffmpeg显示图片

#demo1(无缩放)
ffmpeg -i input.jpg -c:v rawvideo -pix_fmt rgb565le -f fbdev /dev/fb0

#demo2(自动缩放)
ffmpeg -i ../ILSVRC2012_val_00000003.JPEG -vf "scale='min(320,iw)':'min(240,ih)'" -c:v rawvideo -pix_fmt rgb565le -f fbdev /dev/fb0

1.4.3 在ISP屏幕上视频播放

ffmpeg -re -i video2_h264.mp4 -c:v rawvideo -pix_fmt rgb565le -f fbdev /dev/fb0

1.4.4 USB摄像头

注意,由于F1C200S USB EndPoint不足,所以无法驱动USB摄像头。

  1. 查看摄像头支持的输出格式
v4l2-ctl --list-formats
v4l2-ctl --list-formats-ext
root@xddcore-zero:/xddcore_toolbox/v4l2_cameara# v4l2-ctl --list-formats
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'MJPG' (Motion-JPEG, compressed)
        [1]: 'YUYV' (YUYV 4:2:2)
  1. 捕获图像
fswebcam -d /dev/video0 -r 320x240 test.jpeg
ffmpeg -f v4l2 -s 640x480 -i /dev/video0 output.jpg

1.4.5 本地音乐播放

音频播放请先通过以下命令打开运放:

echo 138 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio138/direction
echo 1 > /sys/class/gpio/gpio138/value
echo 138 > /sys/class/gpio/unexport
mplayer xxx.mp3

或

aplayer xxx.mp3

1.4.6 网易云音乐播放音乐播放

musicbox

1.4.7 6轴陀螺仪

#demo1
/xddcore_toolbox/mpu6050

#demo2
/xddcore_toolbox/mpu6050_game

1.4.8 LVGL

#demo1
/xddcore_toolbox/zero_lvgl_port/demo/lvgl_fb_example

#demo2
/xddcore_toolbox/zero_lvgl_port/demo/lvgl_fb_heartrate

1.4.9 RP2040编程

见上述RP2040驱动章节

需要注意:在发布镜像中,由于F1C200S USB Endpoint不足,且开启了USB HID设备,并且USB HUB会占用一定的EndPoint。所以(以Circuit Python为例):

  1. 如果要对RP2040的disk进行操作,改写Code.py文件。则需要将拨码开关配置为1010 1010(越过USB Hub进行通信)。
  2. 如果要对RP2040的disk进行操作,且需要依赖USB串口的交互模式。则需要在Linux Kernel Menuconfig中关闭USB HID驱动(或将USB HID驱动编译为模块)后,自行编译内核。
  3. 如果要对RP2040的disk进行操作,且需要依赖USB串口的交互模式,且想快速完成rp2040编程的话。则需要将拨码开关配置为0101 1010(F1C200S连接板载USB Hub,RP2040通过Type-C连接电脑)。

1.4.10 智能助手(Chatgpt-3.5)

  1. 编辑文件,并填入自己的OPENAI API密钥
vim /xddcore_toolbox/Hello-Pinecone/Hello-Pinecone.py
  1. 运行DEMO
python3 /xddcore_toolbox/Hello-Pinecone/Hello-Pinecone.py

1.4.11 登陆月球模拟器

详情见https://github.com/xddcore/TinyLander-Linux-fb

运行DEMO:

/xddcore_toolbox/TinyLander-Linux-fb/TinyLander

如遇屏幕闪烁,请git pull重新拉取最新代码后,重新编译运行。

1.4.12 NES模拟器

游戏ROM下载地址:https://www.emulatorgames.net/roms/nintendo/

  1. 安装依赖
apt install alsa-utils
apt install libasound2-dev
apt install zlib1g-dev
  1. 拉取InfoNES源码
git clone https://github.com/xddcore/arm-NES-linux.git -b zero-InfoNES
  1. 编译
cd arm-NES-linux/linux
make

测试游戏包下载:https://github.com/xddcore/arm-NES-linux/releases/tag/NES_Game_Pack

  1. 下载测试游戏
wget https://github.com/xddcore/arm-NES-linux/releases/download/NES_Test_Game/Super_Mario_Bros_3.nes
  1. 运行游戏
/xddcore_toolbox/arm-NES-linux/linux/InfoNES ./Super_Mario_Bros_3.nes

1.4.13 OpenNNA2.0神经网络框架

1.拉取源码

git clone https://github.com/xddcore/OpenNNA2.0.git

2.编译

cd OpenNNA2.0/platform/zero_linux_board/
#ls看一下,如有/build,则请先执行rm -rf build/
mkdir build
cd build
cmake ..
make

3.运行

#结果显示在cmd
ffmpeg -i ../ILSVRC2012_val_00000003.JPEG -vf "scale=w=320:h=240" -c:v rawvideo -pix_fmt rgb565le -f fbdev /dev/fb0
./core
#结果显示在isp屏幕
ffmpeg -i ../ILSVRC2012_val_00000003.JPEG -vf "scale=w=320:h=240" -c:v rawvideo -pix_fmt rgb565le -f fbdev /dev/fb0
./core > /dev/tty1

1.4.14 搭建个人博客(Typecho)

运行博客四件套(Nginx+php+SQlite3+typecho+rathole),怎么轻量化怎么来

  1. 安装nginx
apt install nginx

此步骤后会遇到报错Nginx Error: [::1]:80 failed: Address family not supported by protocol [Solved]

原因及解决方法:

 该错误消息表明该服务正在尝试在 IPv6 地址“ [::]:80 ”上启动,但由于地址系列不受支持而失败。这意味着该机器没有设置 IPv6 地址。您需要做的就是简单地编辑 Ngnix 配置文件来监听 IPv4 地址,如下所示。
  1. 打开/etc/nginx/sites-enabled/default
vim /etc/nginx/sites-enabled/default
  1. 注释以下行:
listen [::]:80 default_server;
  1. 重新运行安装
apt install nginx
  1. 检查nginx运行状态
systemctl status nginx
  1. 浏览器输入IP地址,再次检查Nginx是否运行正常

  2. 安装PHP(内含mysql,sqlite等插件)

apt install php7.3-cli php7.3-fpm php7.3-curl php7.3-mysql php7.3-gd php7.3-xml php7.3-mbstring php-sqlite3
  1. 安装SQlite3
apt install sqlite3 php-sqlite3
  1. 修改/etc/nginx/sites-enabled/default(如下图)
vim /etc/nginx/sites-enabled/default

Nginx配置修改 5. 重启nginx

service nginx restart
  1. 下载Typecho(地址:https://typecho.org/download)
cd /var/www/html/
wget https://github.com/typecho/typecho/releases/latest/download/typecho.zip
unzip typecho.zip
rm typecho.zip
  1. 将上传目录修改为可写入
pwd=/var/www/html
chmod -R 777 ./
  1. 访问开发板的IP地址
ifconfig
  1. 按照提示安装Typecho 注意:数据库驱动请选择 pdo sqlite驱动
    后台:http://开发板IP地址/admin/

  2. Enjoy it!

1.4.15 内网穿透(Rathole)

对于没有公网IP的场景,我们还需要一个内网穿透软件。最终选择Rathole(相比frpc70MB的内存占用来说,Rathole的内存占用仅10MB左右,非常适合部署在zero linux board中)。

Rathole基于rust开发,官方并没有提供基于armv5tejl的二进制构建文件。所以我们需要手动交叉编译。(万幸,armv5tejl在rust的第二层支持列表中)

所有服务运行起来后的系统资源信息: Typeho+Rathole资源占用

以下为在交叉编译环境中的操作

  1. 安装rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  1. 安装交叉编译工具链
sudo apt-get install -qq gcc-arm-linux-gnueabi
sudo apt-get install gcc-arm-linux-gnueabi libc6-armel-cross libc6-dev-armel-cross
 sudo apt install pkg-config
 sudo apt install libssl-dev
 sudo apt-get install libudev-dev
 sudo apt install librust-openssl-sys-dev
 sudo apt install perl

export OPENSSL_DIR="usr/lib/ssl"
export OPENSSL_INCLUDE_DIR="/usr/include/openssl"
export OPENSSL_LIB_DIR="/usr/lib/x86_64-linux-gnu"
  1. 添加目标
rustup target add armv5te-unknown-linux-gnueabi​​

  1. 配置cargo
$ mkdir -p ~/.cargo
$ vim ~/.cargo/config
#添加以下内容:
[target.armv5te-unknown-linux-gnueabi​​]
linker = "arm-linux-gnueabi-gcc"

  1. 拉取rathole源码
git clone https://github.com/rapiz1/rathole.git
cd rathole/
git checkout tags/v0.4.8
  1. 编译源码
cargo build --release --target armv5te-unknown-linux-gnueabi \
  --no-default-features --features noise,client,server,hot-reload \
  --config target.armv5te-unknown-linux-gnueabi.linker=\"rust-lld\"

  cargo build --release --target armv5te-unknown-linux-gnueabi \
  --no-default-features --features noise,client,server,hot-reload \
  --config target.armv5te-unknown-linux-gnueabi.linker=\"arm-linux-gnueabi-gcc\"
  1. 编译好的二进制文件在rathole/target/armv5te-unknown-linux-gnueabi/release目录下

服务器部署

  1. 下载构建好的二进制文件
wget https://github.com/rapiz1/rathole/releases/download/v0.4.8/rathole-x86_64-unknown-linux-gnu.zip
  1. 创建并编辑配置文件
vim server.toml

#填入以下内容
# server.toml
[server]
bind_addr = "0.0.0.0:2333" # `2333` 配置了服务端监听客户端连接的端口
[server.services.my_blog]
token = "123456" # 用于验证的 token
bind_addr = "0.0.0.0:80" # `5202` 配置了将 `my_blog` 暴露给互联网的端口
  1. 运行rothole
./rothole server.toml

客户端部署(即Zero Linux Board)

  1. 下载构建好的二进制文件
wget https://github.com/xddcore/Zero_Linux_Board/releases/download/Rathole_Armv5tejl/rathole
  1. 创建并编辑配置文件
vim client.toml

#填入以下内容
# client.toml
[client]
remote_addr = "myserver.com:2333" # 服务器的地址。端口必须与 `server.bind_addr` 中的端口相同。
[client.services.my_nas_ssh]
token = "use_a_secret_that_only_you_know" # 必须与服务器相同以通过验证
local_addr = "127.0.0.1:22" # 需要被转发的服务的地址
  1. 运行rothole
./rothole client.toml

1.5 一些图片

PCB_v10 Front_Board_Basic_Shell_2 board1 board2 board3 board4 board5