在单片机上运行Linux

单片机上运行Linux,有诸多困难,例如:

  • 没有mmu,好在新版本的Linux内核已经合并了uClinux,支持无mmu运行。NOMMU Linux 并非 “完美兼容所有 Linux 应用”—— 它不支持多进程(仅支持多线程,所有线程共享同一地址空间),也不支持虚拟内存(无法用 swap、内存分页),因此更适合 “单应用 + 多任务” 的场景(如物联网网关、轻量控制终端),而非复杂多进程场景。
  • 内存不够,只有小到可怜的几百甚至几kb,好在可以外接psram来扩大内存(esp32s3、rp2350这种高端点的才支持硬件qspi连psram,其他的只能通过spi强行访问psram)
  • cpu架构不支持,单片机的cpu一般比较简单,不支持Linux所需的某些cpu特性,例如乘法器,原子操作等,亦或者该cpu没有Linux驱动兼容。好在可以运行一个满足Linux特性的cpu内核模拟器(例如mini-rv32ima),虽然速度会慢很多,但也确实能运行了。不过有人对esp32s3的xtensa处理器做了Linux支持,rp2350的cpu也满足riscv-ima,这两者都可以原生运行Linux,而非模拟器。

 

方案一:mini-rv32ima虚拟机

主要原理是利用cnlohr大佬的mini-rv32ima,在EPS32上运行一个riscv32ima模拟器,在模拟器里运行无需MMU版的主线Linux 6.X

tvlad1234 大佬在mini-rv32ima的基础上,将它修改为平台无关的代码 tiny-rv32ima ,只需要完成平台相关的接口代码,就能很方便的把mini-rv32ima移植到各种平台

平台:ESP32-S3

参考:用单片机 ESP32-S3 跑 RISC-V 模拟器运行 Linux,这次启动只要 8 秒 | Architecting Life

这位大佬已经把mini-rv32ima移植到esp32-s3的PlatformIO里的Arduino平台上,代码看着好像很简单,就几个脚本

他好像是根据 tvlad1234 的 linux-ch32v003 项目修改移植到esp32的

1.PlatformIO

git clone https://github.com/jeason1997/esp32s3-rv32ima.git

vscode安装platformio,建议直接在微软的CodeSpaces里操作,安装快很多

通过platformio打开esp32s3-rv32ima这个项目

在菜单栏底部,选择对应的模板(模板配置在platformio.ini里)

image

要注意自己的开发版的flash跟psram的连接方式以及容量,例如8m、16m等,以及是opi还是qspi,可以按照里面的模板改

分区表给了2个配置8m跟16m,如果是用其他容量的flash例如4m,要自己也增加一个

image

 点击下方的✔开始编译

image

编译完成后,在项目文件夹下的'.pio/xxx(开发板的名字)/‘下面会发现生成的引导,分区表跟固件

image

烧录参考官方文档:Flashing Firmware - ESP32-S3 - — esptool latest documentation

esp的程序会生成3个文件,一个引导文件bootloader.bin烧录到0x0处,一个分区表烧录到0x8000处,主程序固件烧录到0x10000处

像NuttX,Arduino,MicroPython等,它们都是在生成ESP32的固件的时候把它们合并到一个bin里了,所以直接烧录到0x0就行,也可自己合并,esptool有合并文件的命令

这个工程不用额外烧录镜像文件,他已经把内核Image跟dtb文件转成C数组写入到主程序里

esptool --chip esp32s3 --port /dev/ttyUSB0 --baud 921600 write_flash -z \
0x0 .pio/build/esp32s3/bootloader.bin \
0x8000 .pio/build/esp32s3/partitions.bin \
0x10000 .pio/build/esp32s3/firmware.bin

 

2.移植到Arduno工程:

上面那个工程本来就是arduino的,不过是基于PlatformIO的,我把它移到Arudino官方IDE下:jeason1997/esp32s3-rv32ima-arduino

移植过程:

1.把src目录提取出来即可,然后把main.cpp改为xx.ino(跟文件夹同名),才能被arduino识别

2.把emulator文件夹内的脚本都挪到外面,Arduino好像识别不了文件夹里的代码

3.把mini-rv32ima.c里的 #include <esp32/spiram.h> 改为 #include "esp32-hal-psram.h",否则编译会报错找不到头文件

4.把分区表挪到根目录,并改名为 partitions.csv (这一步骤可选,注意:只要根目录有这个文件,不管Arduino设置里分区表怎么选,都还是用这个

image

5.Arduino里的配置

image

需要改的地方:

Flash Mode:选QIO 120MHz(DIO:2脚数据线 较慢 兼容性好,QIO:4脚数据线 中等,OPI:8脚数据线 快速 但我板子上的不是这种Flash)

Flash Size:改为对应的容量

PSRAM:一般集成在芯片里的是8脚数据线的高速SPI,选 OPI 模式,如果是自己焊接连在外面的,例如8个引脚的ESP-PSRAM,这种就是4跟数据线的,选 QSPI 模式

Partition Scheme:可以选里面对应容量的分区模板,也可以选Custom,选Custom的话,要确保根目录有 partitions.csv 分区表来实现自己分区(注意:只要根目录有这个文件,不管Arduino里分区表怎么选,都还是用这个

分区表参考乐鑫官网文档:分区表 - ESP32 - — ESP-IDF 编程指南 latest 文档

partitions.csv

# 下面选择自己对应的Flash大小,取消注释

# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,

# 4MB Flash partition table (total 0x400000 bytes)
# app0, app, factory, 0x10000, 0x3F0000,

# 8MB Flash partition table (total 0x800000 bytes)
app0, app, factory, 0x10000, 0x7F0000,

# 16MB Flash partition table (total 0x1000000 bytes)
# app0, app, factory, 0x10000, 0xFF0000,

# 32MB Flash partition table (total 0x2000000 bytes)
# 注意,ESP32虽然允许最大32MB Flash,但程序分区不能超过16MB
# app0, app, factory, 0x10000, 0xFF0000,
# app1, app, test, 0x1000000, 0xFF0000,

 

平台:ESP32-C3

注:该方案暂未测试通过,开机运行后卡在进入内核的阶段,初步测试结果是因为PSRAM未能正确读写导致的,可能是硬件上连接不稳定或者芯片损坏导致的。待测试

上面的工程暂时是针对S3配置的,还没测试过是否允许在C3上运行,理论上应该是支持的,但这里有另一份 xhackerustc 写的支持在C3上运行的:https://github.com/jeason1997/esp32c3-rv32ima.git

What's missing if we want to run linux on ESP32C3

  • A single RV32-IMC cpu core, well, this can be solved by patching the linux kernel to remove the 'A' extension usage
  • No MMU, well, this can be solved by using NOMMU
  • No enough memory, only 4MB flash and 400KB sram, well, this can be solved by adding one SPI PSRAM chip. However,
  • I can't make ESP32C3 directly executing code on PSRAM work as ESP32S3 does. If anyone knows the howto, kindly tell me, I really appreciate it;) Then we can directly run linux on ESP32C3!

So far, the idea solution is to use a RISC-V IMA emulator, and use the PSRAM as the emulator's main system memory.

原作者的话,esp32c3是rv32-imc架构的,而linux的运行至少需要ima扩展(c可选),理论上可以运行移除a扩展(原子操作)的linux版本(就是修改起来会比较麻烦)

但esp32c3不支持硬件psram(esp官方说可以通过软件调用,但效率很低),现方案是直接通过spi来访问psram的,速度会慢些。

他最终的解决方案是用mini-rv32ima模拟器,在c3上运行这个模拟器,然后用psram作为这个模拟器的系统内存(通过软件操作调用内存)

编译项目参考:利用CodeSpaces快捷编译Esp32项目 

这个项目的作者实现了用于 RISC-V 模拟器的简单二级缓存设计,主要用于提升对外部 PSRAM 的访问效率,让模拟器运行速度加快许多,tiny-rv32ima 的作者 tvlad1234 参考他的代码也把Cache功能加入到 tiny-rv32ima 里

PSRAM接线参考:

image

 

平台:CH32V003

jeason1997/ch32v003-rv32ima: CH32V003上运行RiscV模拟器跑linux(mini-rv32ima)

Win系统下编译:
1.安装 msys2
2.安装 riscv 编译器
    pacman -Sy mingw64/mingw-w64-x86_64-riscv64-unknown-elf-gcc
3.拉取工程目录并编译
    git clone https://github.com/jeason1997/ch32v003-rv32ima.git
    git submodule update --init
    make
4.打开 MonuRiver“工具”菜单中的“WCH Link 本地烧录工具” 在 WCH-LinuxUtitlity 软件中,打开“文件”菜单中的“打开目标文件”, 选择工程目录下编译好程序文: linux-ch32v003.hex,按"Alt+F4"烧录程序并自动重启ch32v003 
5.如果没问题的话,终端会开始运行,显示初始化硬件信息
    SD init OK
    看到这句话后证明PSRAM跟SD卡都初始化成功,在终端里按下任意键就开始加载内核文件了
    大概5分钟左右才能进入系统,比较慢
SD init OK
System hasn't been cleanly shutdown
Loading kernel image
Starting RISC-V VM

[    0.000000] Linux version 6.6.18tiny-rv32ima (vlad@turboencabulator) (riscv32-buildroot-linux-uclibc-gcc.br_real (Buildroot -gad36342) 13.3.0, GNU ld (GNU Binutils) 2.41) #1 Wed Aug 27 12:46:35 EEST 2025
[    0.000000] Machine model: riscv-minimal-nommu,qemu
[    0.000000] Zone ranges:
[    0.000000]   Normal   [mem 0x0000000080000000-0x00000000807fefff]
[    0.000000] Movable zone start for each node

电路图参考:

image

 

平台:RP2040

jeason1997/pico-rv32ima: 树莓派RP2040上运行RiscV模拟器跑linux(mini-rv32ima)

TODO

 

 

方案二:原生运行

平台:EPS32-S3

主要参考jcmvbkbc大佬的方案,他实现了Linux对xtensa的支持:http://wiki.osll.ru/doku.php/etc:users:jcmvbkbc:linux-xtensa:esp32s3

一键编译脚本:https://github.com/jcmvbkbc/esp32-linux-build

这帖子里有详细的设置教程:ESP32-S3 Linux Kernel Setup Guide

以及另一位老哥的一些记录(有点乱):https://github.com/ESP32DE/Boot-Linux-ESP32S3-Playground

这有个姐们把编译弄到docker里:Use Docker to Compile Linux for ESP32-S3  PDF教程:pdf  Dockerfile文件:Dockerfile

jcmvbkbc大佬的其他杰作:

  为xtensa移植的内核:https://github.com/jcmvbkbc/linux-xtensa

  buildroot(包括了上面等内核):https://github.com/jcmvbkbc/buildroot

  跨平台构建工具,用来构建buildroot等:https://github.com/jcmvbkbc/crosstool-NG

  为xtensa做的qemu支持(可以在PC上模拟esp32?):https://github.com/OSLL/qemu-xtensa

构建也很简单:

# 如果是在CodeSpaces里构建等,参考这篇文章:利用CodeSpaces快捷编译Esp32项目
git clone https://github.com/jcmvbkbc/esp32-linux-build
cd esp32-linux-build
./rebuild-esp32s3-linux-wifi.sh(支持wifi的版本)

# 备注:默认的配置是ESP32S3-N8R8的配置的,但是用支持wifi的版本,编译出来的根文件系统貌似超出范围了,烧录写入会报错提示空间不足(可能需要手动区裁剪下根文件系统或者内核)
# 解决办法是换成16M Flash的S3开发板,然后将 rebuild-esp32s3-linux-wifi.sh 里的 ESP_HOSTED_CONFIG=sdkconfig.defaults.esp32s3 改为 esp32s3.16n16r
# 虽然用的是N16R16的配置,我的S3板子是N16R8的,但可以直接使用不影响,也可以手动去 sdkconfig.defaults.esp32s3.16n16r 里把PSRAM改为8M的

# 然后就会自动构建了,甚至会自动烧录
# 构建完成后生成的文件路径:
内核:build/build-buildroot-esp32s3_devkit_c1_8m/images/xipImage
根文件系统:build/build-buildroot-esp32s3_devkit_c1_8m/images/rootfs.cramfs
配置系统:build/build-buildroot-esp32s3_devkit_c1_8m/images/etc.jffs2

ESP引导文件:build/esp-hosted/esp_hosted_ng/esp/esp_driver/network_adapter/build/bootloader/bootloader.bin
ESP分区表:build/esp-hosted/esp_hosted_ng/esp/esp_driver/network_adapter/build/partition_table/partition-table.bin
ESP主程序:build/esp-hosted/esp_hosted_ng/esp/esp_driver/network_adapter/build/network_adapter.bin

# 手动烧录
# 16MB分区表参考如下
## Label          type  ST       Offset      Length
nvs,              data, nvs,     0x0000a000, 0x00005000
phy_init,         data, phy,     0x0000f000, 0x00001000
factory,          app,  factory, 0x00010000, 0x000a0000
etc,              0x40, 0x1,     0x000b0000, 0x00070000
linux,            0x40, 0x0,     0x00120000, 0x004e0000
rootfs,           0x40, 0x1,     0x00600000, 0x009f0000

# 将EPS相关固件烧录到板子上
esptool --chip esp32s3 --port COM5 write_flash -z 0x0 bootloader.bin
esptool --chip esp32s3 --port COM5 write_flash -z 0x8000 partition-table.bin
esptool --chip esp32s3 --port COM5 write_flash -z 0x10000 network_adapter.bin

# 向分区中写入Linux相关的文件
parttool.py -b 2000000 write_partition --partition-name linux  --input xipImage
parttool.py -b 2000000 write_partition --partition-name rootfs --input rootfs.cramfs
parttool.py -b 2000000 write_partition --partition-name etc --input etc.jffs2

这个Linux比上面的版本都完善,不但是原生运行非模拟器,运行速度很快,甚至还支持WI-FI网络(ESP官方的HOST项目,核心0刷WIFI固件,核心1跑Linux,核心0把WIFI分享给核心1),还能SSH、VI编辑文本等等,看Github的Issues评论据说好像还支持USB-OGT(更新:经过测试lsusb提示找不到usb驱动目录)?

不过根文件系统cramfs是只读的,etc的jffs2分区才是可读写的。

备注:连接WIFI,用 vi 修改 /etc/wpa_supplicant.conf 配置文件即可,在里面填入wifi账号密码,重启就行了(提示,esp32的wifi连接信息是存在nvs分区的,如果之前连过,刷固件的时候没重写这个分区,那连接信息还在)

Win下通过ESP提供的下载工具直接烧写固件跟Linux文件(16M)

image

补充,原理:linux_boot: esp32 version based on IDF-5.1.2 · jcmvbkbc/esp-idf@e2bbcd4,,看作者的提交,作者将Linux启动的boot写在esp32端,无非就是将初始化硬件,将内核加载到PSRAM运行之类的

 

 

平台:RP2350

Mr-Bossman (Jesse Taube) 的 https://github.com/jeason1997/pi-pico2-linux

这个项目比较简单,思路清晰,但功能应该不多,原理是pico的主程序作为bootloader,用于将flash里的内核加载到psram里运行

作者提供里两种构建思路

一种是常规的构建流程

# 安装依赖
sudo apt install -y --no-install-recommends ca-certificates patch git make binutils gcc g++ file wget cpio unzip rsync bc bzip2 g++ cmake python3

# 下载交叉编译工具链
wget https://github.com/raspberrypi/pico-sdk-tools/releases/download/v2.0.0-5/riscv-toolchain-14-x86_64-lin.tar.gz && \
mkdir -p ~/toolchain && \
tar -vxzf riscv-toolchain-14-x86_64-lin.tar.gz -C ~/toolchain && \
rm riscv-toolchain-14-x86_64-lin.tar.gz

git clone https://github.com/Mr-Bossman/pi-pico2-linux

cd pi-pico2-linux

git submodule update --init

# 配置buildroot
make -C buildroot BR2_EXTERNAL=$PWD/ raspberrypi-pico2_defconfig

# 构建Linux,并调用"support/scripts/genimage.sh"将生成的内核、设备树、根文件系统打包为一个可以烧写的lash-image.bin
make -C buildroot
# 构建完毕后的镜像文件位于:buildroot/output/images/flash-image.bin

# 编译pico主程序,用于引导Linux
PICO_TOOLCHAIN_PATH=~/toolchain/ PICO_SDK_FETCH_FROM_GIT=1 make -C psram-bootloader
# 构建完毕后的程序位于:psram-bootloader/build/psram-bootloader.uf2

# 将Linux系统烧录到pico上
make -C psram-bootloader flash-kernel

也可直接用作者已经构建好的文件:https://github.com/Mr-Bossman/pi-pico2-linux/releases(注:作者的开发板是 SparkFun,PSRAM的CS引脚是GPIO19,我的板子是GPIO0,如果不一样则无法正常运行)

另一种是通过docker

docker build -t pi-pico2-linux .
docker run -v $(pwd):/root/ -it --entrypoint /bin/bash pi-pico2-linux
docker run pi-pico2-linux

拉取下来进入容器,把里面构建好的文件拷贝出来即可,或者自己修改然后重新构建

我的开发板用的是 RP2350A Linux开发板 - 立创开源硬件平台这个工程的,调试串口是UART0: TX:GP16, RX:GP17

注:Mr-Bossman 用的是 sparkfun-pico 这块板子,它的PSRAM CS引脚是gpio19,输出串口是UART0(gpio0,gpio1),而我用上面那块板子的PSRAM CS引脚是gpio0,跟他的冲突了,所以他的工程提供的镜像我无法正常运行。我暂时还不知道怎么修改这个项目的Linux的默认输出串口,直接用 RP2350A Linux开发板 - 立创开源硬件平台 里提供的镜像跟 psram-bootloader ,可以正常运行。

 

 

编译Linux:

感谢 tvlad1234 和他的 linux-ch32v003 项目,已经编写好一个去除 MMU 依赖,以及裁剪过内核的 Linux 编译脚本,可以一键编译出一个可以在 mini-rv32ima 上运行的 Linux 镜像。

jeason1997/buildroot-tiny-rv32ima: Buildroot for tiny-rv32ima

直接 make 即可

 

另外也可以看 cnlohr 的项目,直接 make 即可,会自动构建 buildroot,详情看 Makefile

mini-rv32ima/Makefile at master · cnlohr/mini-rv32ima

 

posted @ 2025-10-01 17:10  JeasonBoy  阅读(137)  评论(0)    收藏  举报