8引脚 Linux

仅用3个8引脚芯片,轻松构建交互式高速 Linux 电脑

简介

我一直在尝试构建能够运行 Linux 的极简电脑。我曾涉猎[极端](https://dmitry.gr/</?r=05.Projects&proj=35. Linux4004>)、[低端](https://dmitry.gr/</?r=05.Projects&proj=07. Linux on 8bit>)以及[有趣的形态](https://dmitry.gr/</?r=05.Projects&proj=33. LinuxCard>)。我觉得有必要进行另一个有趣的实验:一个简单易组装的套件电脑,仅使用8引脚芯片。

目录

  1. 一个极简电脑 1. 初步想法 2. 零件选择
  2. 硬件设计 1. 控制台 2. RAM 3. SD 卡 4. 再次谈论控制台
  3. 软件故事 1. 模拟器 2. 引导加载程序 3. 卡片分区和启动过程
  4. 性能
  5. 组装 1. 获取 2. 初始组装 3. 二次组装 4. 刷写主固件和首次启动
  6. 下载和使用 1. 文件 2. 使用方法
  7. 评论...

一个极简电脑

An empty 8-pin Linux PCB

初步想法

曾经有一段时间,人们可以订购套件并在家组装电脑。它几乎可以完成当时商店购买的电脑所能做的一切。那个时代早已过去。现代电脑由数百个庞大而复杂的芯片组成,没有公开的数据手册,需要数百瓦的功率通过复杂的供电拓扑结构为其供电。雪上加霜的是,现代操作系统需要千兆字节的 RAM、兆兆字节的存储空间,并且始终需要互联网连接才能正确地监视你。但是,如果有人试图将现代电脑装入一个可以轻松在家组装的套件中呢? 被认为是“现代电脑”的最低要求是什么? 我认为能够运行 Debian Linux、vi、gcc 和 make 就足够了,所以这成为了目标。感谢我之前的尝试,我知道这可以在 8MB 的 RAM 和 1 MIPS 的 CPU 中完成。存储很简单:SD 卡可以轻松提供任何大小的存储空间。串行端口仍然是轻松连接嵌入式系统的实际方法,但由于计算机不再具有串行端口,因此 USB-serial 取代了它。因此,我有了目标:至少 8MB 的 RAM、至少 1 MIPS 的 CPU、SD 卡、USB。 在硬件方面,我想设计一些可以由几乎没有焊接经验和 RadioShack 45W 烙铁的人组装的东西,要小巧、可爱且便宜。引脚较少的芯片更容易焊接,因此我决定 仅使用具有 8 个引脚的芯片,作为一个有趣的挑战。由于所有芯片至少需要电源和接地引脚,这会将每个芯片限制为不超过 6 个有用的引脚。这个决定最终影响了许多设计的特性和限制。在物理上,我决定将其制成一个小型圆形板,顶部带有板边缘 USB-C 连接器,如您在此图像中看到的那样。这就是最终的工作设计,由我使用以下说明以及 RadioShack 45W 烙铁实际组装而成。

零件选择

实际上,没有太多 8 引脚芯片可以进行 USB 通信。存在一个半选项。第一个是非常可爱的 PL2303GL,它是一个 USB-to-serial 桥接器,完全不需要外部组件,甚至提供 100mA 的 3.3V 稳压输出。超级方便!它的工作方式与承诺的完全一样。我是粉丝! Prolific 甚至可以轻松获得所有主要和次要操作系统的驱动程序。可悲的是,在 macOS 上,它们需要从 AppStore 安装,但这是一个简单的过程。应该注意的是,它的前身 PL2303SA 也可以考虑,但它已停产,因此不值得一提。那么,“半个芯片”是什么意思? 嗯,多亏了 V-USBATTINYx5 系列芯片也可以进行 USB 通信。它只是 USB 低速,并且占用大量 CPU 时间,但它可以工作。问题是所有现有的 USB-serial 协议都需要批量端点,并且 USB 规范禁止 USB 低速使用 BULK 端点。为了保持符合规范,这将需要使用中断端点设计我自己的通信协议,并为所有主要操作系统编写驱动程序。不好玩。幸运的是,所有 主要操作系统都没有强制执行这种 USB 低速上没有批量 EP 的规定。它_只是有效_,并且实施 ACM 以充当串行端口有效。所以,这就是用于 USB 连接的两种选择。 关于要使用的 RAM 实际上没有任何问题。SOIC-8 PSRAM 是解决方案。ISSIAPMEMORYVilsion 生产它们。他们所有人都在承诺推出 16MB 芯片已经一年了,但没有一个交付,因此假设他们都在胡说八道可能是公平的。但是,8MB 部件可以在常用的零售商处轻松购买,价格为几美元,因此决定使其与 8MB 的 RAM 一起使用。 最后一个问题是:使用哪个微控制器? 参数搜索 提出了几个系列选项:

硬件设计

An empty 8-pin Linux PCB

控制台

UART 引脚不能与任何东西组合。任何将 UART RX 与某些东西组合的尝试都会创建在“某些东西”发生时丢失一些传入数据的可能性。将 UART TX 与某些东西组合,无论“某些东西”有多快,也是有问题的。任何低脉冲都会被 PC 视为一个字符。对于短暂的低电平,它将被视为 0xFF 字节。理论上,可以启用奇偶校验并使用它来隐藏它,但这并非万无一失。另外,谁在 2024 年使用奇偶校验? 实际上,UART 引脚,缺少更高级别的协议、芯片选择或单独的时钟,基本上无法与任何东西组合。这会占用 6 个引脚中的两个。唉......

RAM

所有 SPI PSRAM 都支持 QSPI 模式以提高速度。可悲的是,QSPI 需要 6 个引脚,只剩下 4 个引脚。好的,它们中的大多数还支持双 SPI,其中 MISO 和 MOSI 同时用于单向发送每个时钟两个数据位。正如你所想的那样,这是正常 SPI 的两倍速度。作为额外的功劳,它不需要比正常 SPI 更多的引脚,并且可以与共享 SPI 总线的其他设备兼容,因为当取消选择时,它们不会期望读取 MOSI 或驱动 MISO。 STM32G031 不支持双 SPI。当然,它可以进行位操作。但是,它能足够快地击败硬件 SPI 单元吗? 硬件 SPI 单元可以在 CPU 时钟的一半下运行,并与 DMA 单元配对以进行不间断的传输/接收。要通过 CPU 匹配此配置,需要使用不超过每个时钟 4 个周期进行位操作的双 SPI。考虑到一些准备步骤,这几乎是不可能的。比这更快地执行它是不可能的。由于能做的最好的事情是匹配硬件 SPI 单元,为什么还要费心呢? RAM 将使用普通的 SPI 进行访问。这会占用剩余四个引脚中的四个。该死......

SD 卡

所以,情况是这样的:没有剩余引脚,并且仍然需要连接 SD 卡。SD 卡使用 SPI 通信,因此给它一个芯片选择信号就足够了,但是没有剩余引脚。考虑了几个选项。最简单的想法是在 RAM 的 nCS 上包含一个反相器,并将其用作 SD 卡的 nCS。这进行了原型设计,并且效果还不错。这种方法的问题是双重的。首先,一些卡不喜欢在没有向其发送任何数据字节的情况下选择和取消选择,但这正是两次 RAM 访问对它们来说的样子。没有多少卡有这个问题,所以这不是一个障碍。第二个问题是,反相器要么是一个额外的 IC,要么是一个额外的晶体管。板上的组件越多,新手就越难组装。这个解决方案被搁置,标记为“最坏情况”。寻找更好的解决方案一直在进行。 此设备不会很快产生数据,因此可以安全地以非常慢的速度运行 UART。然后,可以将 UART TX 引脚在到达 USB-to-serial 芯片的途中进行低通滤波。只要命令很短,并且时钟速率足够高,以至于选择时间低于低通滤波器允许通过的时间,那么同一个引脚可以用于 SD 卡的 nCS。此解决方案可行,但脆弱。一旦计算出允许按照规范进行卡初始化所需的波特率,就会变得更加糟糕。UART 需要以 300 bps 或更慢的速率运行才能使此工作正常进行。即使这样,如果卡决定很慢,它也是脆弱的,因为 SD 规范的 SPI 章节从技术上讲不允许在等待它读取数据时取消选择卡。不,这甚至比第一个选项更糟糕。 在放弃第一个选项之前,还有一个更疯狂的选项需要考虑。SPI RAM 会介意在没有命令或数据的情况下被选择然后取消选择吗? 实验提供了答案:不。此测试通过了所有 SPI RAM 芯片。为什么? 好吧,除了 SPI 之外,SD 卡还使用 SDIO 协议通信。该协议不使用四个单向电线,而是使用一个单向 CLK 信号和两个双向信号:CMD 和 DAT。四位宽版本使用三个更多的数据引脚,但这里无关紧要。与 SPI 不同,此协议在公共 SD 文档中没有完全记录,但是不难理解。使用更少的引脚很好,但是没有 没有 空闲引脚,并且此方法需要三个,因此这并不是立即有用。在尝试使其 有用 之前,必须首先使其完全起作用。经过一段时间后,这完成了,并且产生了 SDIO 1 位协议的有效实现。它成功地初始化并访问了所有尝试过的 SD 卡。很酷。 那么,哪些引脚可以与 SDIO 的三个引脚组合? 经过大量思考后,解决方案很明显。RAM 的 nCS 可以是 SD 卡的 CLK。RAM 的 CLK 可以是 SD 卡的 CMD。RAM 的 MOSI 可以是 SD 卡的 DAT。尝试找出与每个设备的所有可能交互,以及它对另一个设备的影响,以说服自己它将安全地工作。当选择 RAM 时,SD 卡将看到其 CLK 下降;当取消选择 RAM 时,SD 卡将看到其 CLK 上升。RAM 的 SPI 配置为模式 3,这意味着其 CLK 在高电平空闲。反过来,这意味着每次 RAM 访问,无论多短,SD 卡都会将其视为发送给它的单个位,即 1 位。这是命令之间 SDIO 的 CMD 线的空闲状态,这是安全的。SD 卡在命令之间不驱动或读取 DAT 线,因此 RAM 的 MOSI 的移动将被忽略。到目前为止还不错。访问 SD 卡需要切换其 CLK 线,同时读取或写入其 CMD 和 DAT 线。对于 RAM,这将看起来像它被选择和取消选择,但两者之间没有任何事情发生。这是安全的。很酷! 应该注意的是,只有在一次完成整个 SD 事务而不进行任何干预 RAM 访问的情况下,此操作才能正常工作。这意味着不能使用多块读取或写入。考虑到引脚不足,这是可以接受的。嗯,这是一个可能有效的解决方案! 进行了实验验证。成功! 当然,STM32G031 没有合适的引脚排列来为此使用任何硬件单元,因此 SD 访问完全是位操作的。这是可以接受的,并且我进行此操作的汇编代码实现了大约每个位 14 个 CPU 周期的吞吐量。总的来说,还不错。

再次谈论控制台

现在已经清楚地确定所有 I/O 至少在理论上可以安装到 6 个引脚中,现在是时候将东西分配给引脚了。其中一些是微不足道的:RAM 需要真正的 SPI,因此那些是引脚。SD 卡共享 RAM 的引脚,因此也很清楚。剩下的是引脚 7 和 8。这些引脚构成了 SWD 接口,这对于早期调试来说很方便。此外,通过消除过程,它们需要是串行端口。引脚 8,作为 A14 可以充当 USART2.TX,并且启用了 USART 的“引脚交换”功能,可以将其转换为 USART2.RX。这很好,因为如果没有硬件帮助,UART 接收会很痛苦。这使引脚 7 成为我的 TX 引脚。该引脚没有与任何 USART 相关的替代功能。嗯,幸运的是,位操作 UART 传输并不难。具有讽刺意味的是 - 早期的方法要求尽可能慢的 UART 波特率。位操作 UART 调用尽可能快的波特率,因为在传输过程中整个世界都需要停止。以 115,200bps 的速度发送每个字符将需要 87 微秒。理论上,可以使用非常慢的波特率,并使用计时器驱动的中断来发送每个字符的位,但是中断定时的抖动可能会导致问题。无论如何,大多数时候板没有输出任何东西,因此就目前而言还可以。位操作 UART 传输效果很好。已分配引脚。该开始进行软件开发了。 但是,你可能会说,初始闪存怎么办? 当然,STM 的引导加载程序将不支持使用这些奇怪的引脚进行初始闪存? 确实是这样。这就是为什么板具有四个焊桥,可以配置串行端口的接线。在一种配置中,引导加载程序将工作,但 RAM 和 SD 卡将不工作,并且无法工作;在另一种配置中,ROM 引导加载程序不再有用,但该项目将启动。幸运的是,该项目包括其自身的引导加载程序,因此在初始闪存之后不需要 ROM 引导加载程序。

软件故事

模拟器

An empty 8-pin Linux PCB已经拥有一个可以启动 Linux 的 MIPS 模拟器,用 ARMv6M 汇编编写。重用它将是微不足道的。为了寻找更高的速度,我实际上编写了一个 MIPS-to-ARMv6M JIT,效果很好。可悲的是,它太大了(编译为 46KB 的代码),并且仅使用 6KB 的翻译缓存,在此项目中使用没有获得太多速度提升。它被搁置了,直到下一次。 STM32G31 中的 32KB 闪存分为 8KB 区域用于引导加载程序,24KB 区域用于主代码。其余的只是润色。主模拟器代码保持不变。

引导加载程序

为什么需要引导加载程序? 嗯,没有剩余引脚用于调试,需要找到一种更新固件以修复错误或添加功能的方法。最简单的解决方案是引导加载程序,该引导加载程序可以使用 SD 卡,了解 FAT 文件系统,并在找到更新时从中应用更新。这正是选择的解决方案。引导加载程序大小惊人地为 8KB(实际上为 6.5KB,但闪存块为 2KB,因此将其四舍五入)的原因是,它必须包含完整的 SDIO 驱动程序、FAT 文件系统驱动程序、闪存写入代码以及大量日志记录,以帮助调试出现的任何问题。当然,它还包括位操作 UART 传输代码的副本。应用程序映像中偏移量为 16 的字被认为是版本号,并且只有当其版本高于当前应用程序的版本并通过一些基本检查时,才会应用更新。引导加载程序中偏移量为 8 的字节是引导加载程序版本。这仅用于在主应用程序的启动文本中显示。如果固件更新通过所有测试,则会应用该更新,并将其称为 FIRMWARE.BIN。 引导加载程序本身在芯片重置后运行,因此它以 16MHz 运行。主应用程序可以以各种 CPU 速度运行,以允许每个人尝试超频。但是,需要有一种故障保护方法来更改速度,并且每次手动重新编译映像似乎太麻烦了。这很容易解决。引导加载程序已经安装了卡上的 FAT 文件系统,以查找固件更新。在执行此操作时,它还会查找任何名称以 CLOCK 开头的文件或目录。如果找到,则使用名称后面的数字作为主应用程序的时钟速率。如果该数字超出合理范围(32-200MHz),或者未找到此类文件,则使用 132 MHz。是的,132MHz。稍后会详细介绍。

卡片分区和启动过程

与我之前的 MIPS 模拟项目相同,启动过程让人联想到 PC 启动过程。该卡的第一个扇区被读入 RAM 的第一个字节并跳转到。然后,它可能会加载下一阶段。它通过查找类型为 0xBB 的分区并将其加载到地址 0x80001000 的 ram 中,然后跳转到该分区来执行此操作。在那里,存在着第二阶段引导加载程序。它足够大,可以包含日志记录和控制台输出。它将查找标记为活动的分区,尝试将其挂载为 FAT16,并在其中找到一个名为 VMLINUX 的文件。如果找到该文件,则将其作为 ELF 文件加载,并跳转到其入口点。如果这是一个有效的 Linux 内核,它将启动。传递给内核的命令行嵌入在引导加载程序中,因为预计不会更改。它指示内核将 /dev/pvd3 挂载为根,并将 /sbin/uMIPSinit 用作 init。它将尝试将 /dev/pvd1 挂载为 /boot 仔细阅读以上段落会发现,虽然 rootfs 需要是第三个分区,但没有指定或重要的其他顺序。这是故意的。对于此项目,FAT 分区是第一个,引导加载程序分区是第二个,rootfs 是第三个。为什么? 当插入具有多个分区的 SD 卡时,Windows 和 macOS 将挂载第一个分区。Linux 将全部挂载它们。这意味着 (1) FAT 分区可以用于轻松地将文件移入和移出此计算机,因为它们对于启动的 Linux 也是可见的,位于 /boot 中,并且 (2) 从主机 和/或 从启动的 Linux 本身更改时钟速度/超频很容易。 引导加载程序要做的第一件事是简单地等待六秒钟而不做任何事情。这在重新配置引脚之前完成。延迟允许一些时间附加 SWD 调试器(板上有一个 4 针接头)。在此延迟之后,重新配置引脚,并且没有更多引脚分配给 SWD,因此无法再附加调试器。作为次要备份,引导加载程序配置选项字节,以允许通过升高 BOOT0 引脚(引脚 8)强制芯片从 ROM 启动。它还对选项字节进行编程,以禁用重置引脚(它用作 GPIO),并禁用 BOR(此处无用)。然后,它将尝试使用 SD 卡初始化通信并检查更新,然后启动。

性能

STM32G031 指定在 64MHz 下运行,那么为什么所有关于在 150MHz 下运行的讨论呢? 嗯,通过正确应用黑魔法,STM32G031 可以很好地超频。CPU 内核从内部调节器运行,其电压使用 PWR->CR1 寄存器进行调整。STM 记录了两个设置:VOS2(对应于 Vcore 的 1.0V),芯片只能运行到 16MHz;以及 VOS1(对应于 Vcore 的 1.2V),芯片只能运行到 64MHz。实际上,在 VOS1 时,芯片在超过约 75MHz 时无法很好地运行。一个令人尊敬的超频,但不是那么令人兴奋。但是......较旧的文档(以及类似芯片的文档)也提到了 VOS0,对应于 Vcore 的 1.35V。如果有人尝试会怎样? 嗯,你知道吗? 它起作用了,并且芯片变得 LOT 更容易超频。现在,芯片的大多数实例在 136MHz 下运行良好,有些可以达到 180MHz。你需要正确管理闪存等待状态,因为闪存内存不会神奇地变得更快。额外的等待状态确实会影响速度提升,但仍然值得。 在 148MHz 的主机 CPU 速度下,模拟的 MIPS CPU 大约相当于一个禁用了 FPU 的 1.65MHz MIPS R3000。它不是速度野兽,但它确实在一分钟左右启动,并且 vi、make、objdump 和 gcc 等工具都可以工作。这是一个完整的 Debian 系统