↓ Skip to main content
  1. Projects/

把 Tiny386 移植到 ESP32-P4:单片机上的 Windows 95

·472 words·3 mins·
f4bb0
Author
f4bb0
Meow meow meow
Table of Contents

背景
#

tiny386 是一个跑在 ESP32-P4 上的 x86 模拟器,带 VGA 帧缓冲输出,能在单片机上启动 FreeDOS,理论上也能跑 Windows 95。我们手上是 WT99P4C5-S1 开发板加一块 EK79007(1024×600,MIPI DSI)面板,目标是启动系统并点亮屏幕。

说明一下:这篇是移植过程记录,不是可复现的完整教程,我们的分支也不公开——上游后来推进了很多 commit,当时遇到的一部分问题在上游已经解决了,把过时的补丁贴出来没意义。留下的是踩坑的思路和一些通用的结论。

一、Arch 下准备 ESP-IDF
#

大概三种装法:

  • git clone --single-branch --recursive https://github.com/espressif/esp-idf.git:能用,但要下一个多 GB 的树,慢。
  • eim(ESP-IDF Installer Manager):最快最省心。
  • AUR / 系统级静态安装到 /opt/esp-idf:只适合刚好要 6.0.1 的场景。

装好之后每个 shell 都要激活一次:

source "/home/fabbo/.espressif/tools/activate_idf_v6.0.1.sh"
idf.py --version
Tip

不要让 CMake 在 NTFS 分区上跑。构建时会触发 Linux 内核的 iomap BUG,直接 kernel panic。工作目录放 ext4 上。

二、板级移植
#

流程大致是:

  1. 先按 README 给 SDK 打补丁。

  2. 用现成板型跑通一遍,确认链路没问题:

    idf.py -DBOARD=jc4880p443 update-dependencies build

    能出 Successfully created a ESP32-P4 image 就说明 ESP-IDF 那一侧是好的。

  3. 然后新增我们自己的板级文件:esp/main/board_wt99p4c5_s1.h、sdkconfig.wt99p4c5_s1 等。

  4. USB HID 键盘鼠标支持来自社区 PR:hchunhui/tiny386#4(BigFishDev22)。很高兴能看到 BigFishDev22 的 PR 在稍晚被合并。

  5. 在 idf_component.yml 里补上依赖条目。

  6. 删掉 build/ 和旧的 sdkconfig 再重新配置——换了 BOARD 之后残留的 sdkconfig 会带着旧板子的选项:

    idf.py -DBOARD=wt99p4c5_s1 update-dependencies build
    idf.py flash

编译完了就有了 bios.bin、vgabios.bin镜像,再加上 FreeDOS 的 floppy.img ,和 tinyi386.ini 的配置放进 SD 卡。

值得一提的是,build.sh thirdparty 在 Arch 上编不过:脚本要编一个 Win32 的 slirp,TARGET_WINVER=0x501(Windows XP)写死在里面,现代 MinGW 的头文件不吃这个老宏。装了 mingw-w64-gcc 也不行,i686-w64-mingw32-gcc 报 Error 127 之后一路挂。好在 out/bios.bin、out/vgabios.bin 是编译出来了。

三、内存爆炸
#

刷进去后开不了机,串口里是这样的:

E (1413) sdmmc_cmd: allocate_dma_buf: not enough mem, err=0x101
E (1413) diskio_sdmmc: sdmmc_read_blocks failed (0x101)
E (1713) async_fbcpy: esp_async_fbcpy_install(73): no memory for DMA2D descriptors
E (1713) lcd.dsi: esp_lcd_dpi_panel_enable_dma2d(519): install async memcpy 2d failed
ESP_ERROR_CHECK failed: esp_err_t 0x101 (ESP_ERR_NO_MEM) at 0x4002d550
file: "./main/lcd_ek79007.c" line 128

0x101 就是 ESP_ERR_NO_MEM。SD 卡的 DMA buffer 分不出来,LCD 的 DMA2D 描述符也分不出来,最后 ESP_ERROR_CHECK 直接 abort。根因是这块板子上被点亮的组件太多,内部 RAM 和 DMA 可用区被吃干净了。

我们做的削减:

  • 关掉 ES8311 音频编解码、I2S 驱动;
  • 关掉 ESP-Hosted(无线协处理那一套);
  • 试了 CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y,把 BSS 挪到 SPIRAM——是否真的有效存疑,但配合上面几条之后内存压力确实下来了。

削减之后:终于能运行系统了。FreeDOS 从 floppy.img 正常启动,速度还不错;Windows 95 能进桌面,大约 30 秒后崩溃,不过后面通过优化解决了。

首次启动画面
首次在 WT99P4C5-S1 + EK79007 上点亮

四、屏幕驱动
#

是用的现成的 lcd_ek79007 库,其实遇到的都是些细节问题。

旋转问题
#

在 lcd_ek79007.c 里写 MADCTL 寄存器(0x36,置 MV 位 0x20)做硬件级旋转。

镜像问题
#

画面位置对了,但左右颠倒,文字完全没法读。先试标准接口:

E (1585) lcd_panel: esp_lcd_panel_swap_xy(59): invalid panel handle
ESP_ERROR_CHECK failed: esp_err_t 0x102 (ESP_ERR_INVALID_ARG) at 0x400160b4

esp_lcd_panel_swap_xy() 发现 EK79007 这套 DPI 面板驱动不支持。翻数据手册才发现,这颗面板的扫描方向是由两组 GPIO 硬件引脚决定的:

  • UPDN:垂直扫描方向(从上到下 / 从下到上);
  • SHLR:水平扫描方向(从左到右 / 从右到左)。

问题在 lcd_ek79007.c 的 bsp_configure_panel_direction_pins():改成 UPDN = 1、SHLR = 1(都按正向扫描),画面就正常了。

看来MIPI DSI 面板的扫描方向不一定走软件接口,很多是引脚拉死的。

五、优化
#

Note

此后部分的优化 fabbo 就没实际参与了,都是队友的功劳。

分析启动全流程,识别出四个瓶颈:

瓶颈项详情
CPU 频率默认 360MHz,ESP32-P4 支持最高 400MHz
PSRAM 自检上电后遍历全部 10MB PSRAM,耗时 2-3 秒
编译优化-Os(体积优先),模拟器核心计算密集
日志输出INFO 级别,启动过程大量串口日志

通过修改 sdkconfig.defaults 中的 Kconfig 选项:

优化项优化前优化后效果
CPU 频率360MHz400MHz全局性能 +11%
PSRAM 自检启用禁用省去 10MB 内存遍历耗时
编译优化-Os (体积优先)-O3 (速度优先)模拟器核心加速
日志级别INFOWARN减少串口 I/O 开销

整体启动速度提升约 20-30%。进一步分析发现:

  1. Kconfig 层面:ESP32-P4 硬件支持 RISC-V ZCMP 压缩指令扩展(SOC_CPU_HAS_ZC_EXTENSIONS=y),但 Kconfig 中未启用 CONFIG_COMPILER_ENABLE_RISCV_ZCMP。FreeRTOS 内核函数可从 Flash 迁移到 IRAM(CONFIG_FREERTOS_IN_IRAM),减少任务切换时的 cache miss。

  2. VGA 刷新:FULL_UPDATE 宏禁用了增量文本模式刷新,每次全屏重绘所有字符。DOS/文本模式下的应用程序(BIOS 自检、命令行)大部分时间只改变屏幕的一小部分,全量刷新浪费大量 CPU。

  3. I2S 任务延迟:vTaskDelay(8ms) 将 VGA 刷新率限制在 125Hz,而 VGA 自身场消隐周期为 5ms(RETRACE_INTERVAL_US = 5000),存在 3ms 不必要的延迟。

  4. TLB 大小:ESP32 构建使用 tlb_size=256,仅为桌面版的 50%。TLB miss 触发昂贵的 PSRAM 页表遍历,TLB 减半意味着 miss 率翻倍。

  5. 任务栈:4096 字节栈偏小,限制编译器内联优化。

  6. IRAM_ATTR_CPU_EXEC1:cpu_exec1()(模拟器最热函数)被注释掉,运行在 Flash 上。尝试启用后链接器报 sram_low 溢出 40240 字节——函数体过大无法放入 IRAM,且与 FREERTOS_IN_IRAM 冲突。 ESP32-P4 的 IRAM(sram_low)容量有限,在已启用 FREERTOS_IN_IRAM、GDMA_CTRL_FUNC_IN_IRAM、ESP_SYSTEM_IN_IRAM 等系统组件的 IRAM 优化后,没有足够空间容纳 6000 行的 cpu_exec1()。一种折中方案是只将 cpu_exec1 中最热的基本块(如 ALU 指令、内存读写)提取到独立函数并标 IRAM_ATTR,但这需要大规模重构。当前最现实的优化是确保该函数在 Flash 中被 i-cache 充分覆盖。

优化项改动文件效果
RISC-V ZCMPsdkconfig.defaults代码压缩,减少 i-cache 占用
FreeRTOS 入 IRAMsdkconfig.defaults内核函数零等待执行
增量 VGA 刷新board_wt99p4c5_s1.h(注释 FULL_UPDATE)文本模式下减少无效重绘
vTaskDelay 8→5mslcd_ek79007.c刷新率 125→200Hz
tlb_size 256→512i386.cTLB miss 率减半
栈 4096→8192esp_main.c解锁编译器内联
IRAM_ATTR_CPU_EXEC1board_wt99p4c5_s1.h(回退)IRAM 溢出 40KB,不可行
Windows 3.1 运行画面
Windows 3.1 跑起来
Windows 95 桌面
优化之后 Windows 95 能稳在桌面上