单片机上运行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里)
要注意自己的开发版的flash跟psram的连接方式以及容量,例如8m、16m等,以及是opi还是qspi,可以按照里面的模板改
分区表给了2个配置8m跟16m,如果是用其他容量的flash例如4m,要自己也增加一个
点击下方的✔开始编译
编译完成后,在项目文件夹下的'.pio/xxx(开发板的名字)/‘下面会发现生成的引导,分区表跟固件
烧录参考官方文档: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
4.把分区表挪到根目录,并改名为 partitions.csv (这一步骤可选,注意:只要根目录有这个文件,不管Arduino设置里分区表怎么选,都还是用这个)
5.Arduino里的配置
需要改的地方:
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接线参考:
平台: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
电路图参考:
平台:RP2040
jeason1997/pico-rv32ima: 树莓派RP2040上运行RiscV模拟器跑linux(mini-rv32ima)
TODO
平台:ESP32-P4
Epiczhul/esp32p4-rv32ima: Run Linux on ESP32-P4 (32MB psram) with this RISC-V emulator
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)
补充,原理: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 2 RP2350B RP2350-Linux迷你开发板PSRAM
这个项目比较简单,思路清晰,但功能应该不多,原理是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
