常年以来,我仍然在尝试用最少的组件打造一台能运行Linux的微型计算机。我也尝试过极限简化和各类有趣的形态设计,而此次我想做一次新的尝试:用最简单的形式,即只用三个8引脚芯片,来组装出一台可运行Linux的迷你笔记本套件。

设计一台“最小化”的极简笔记本

(1)初步构思

曾几何时,人们可以买到DIY套件,之后自己在家中亲手组装出一台能与市售计算机匹敌的设备——可现在,这样的时代已经一去不返。

现今的计算机由成百上千颗复杂的小型芯片构成,这种芯片除了没有公开的数据指南,还通过复杂的电源传输拓扑结构供给数百瓦的电力。而现代操作系统对硬件的要求也更加严苛:GB级别的RAM、TB级别的储存空间、始终在线的网路联接……简直像是为了“更好地监控你”而生。

这么问题来了:假如想在家能够动手组装一台现代计算机,可能吗?我觉得,只要能运行DebianLinux、能用vi编辑器、能用gcc编译器并能执行make命令,就早已够“现代”了——于是这成为了我的目标。

基于我之前的探求,我晓得这似乎并不须要太高的配置:8MB显存+1MIPS(百万条指令每秒)的处理能力就足够了。储存方面更简单,SD卡早能够轻松满足容量需求。尽管如今的笔记本大多没有并口了,但对于嵌入式系统来说,并口仍是最简单的插口方法,用USB转并口即可取代传统并口。

为此,最终我设定的目标如下:起码8MB的RAM、至少1MIPS的处理能力、SD卡储存、USB插口(用于并口通讯)。

在硬件设计方面,我希望能设计出一种在家也能轻松点焊组装的计算机,让点焊经验几乎为零、仅拥有一把RadioShack45W电烙铁的人也能做到。整机要精巧、可爱且低成本。为了增加点焊难度,我决定只使用8引脚芯片,这本身也是一次趣味挑战。因为每颗芯片起码要保留电源和相线引脚,剩下最多只能用6个引脚来实现功能。这一限制对整个电路设计影响特别大,也导致了好多局限。

在外观上,我决定将其设计为一个精巧的方形电路板,在底部边沿设置一个USB-C插口,如右图所示。这就是最终能正常工作的版本,也确实是我亲手用RadioShack45W电烙铁点焊完成的!

最小化极简电脑_8引脚芯片_串口驱动linux

(2)零件选择

事实上,能支持USB通讯的8引脚芯片几乎没有,不过勉强说的话,应当有“一种半”。

串口驱动linux_最小化极简电脑_8引脚芯片

第一种是真正可用的解决方案:PL2303GL。这是一款十分精巧、实用的USB转并口桥接芯片,无需任何外部元元件linux就该这么学,能够额外输出100mA的3.3V稳压电流,十分便捷!它的表现完全符合预期,我个人十分喜欢。Prolific官方也为几乎所有主流和冷门操作系统都提供了驱动支持。惟一稍为麻烦一点的是,在macOS上,这种驱动程序须要从AppStore安装,但整个过程也还算简单。须要一提的是,它的前代机型PL2303SA虽然也可以用,但因为它早已停止生产(EOL),为此我并不推荐。

那所谓的“半个芯片”又是哪些意思呢?这就要讲到V-USB项目了——它让ATTINYx5系列芯片也可以实现USB通讯功能。似乎只能支持低速(Low-Speed)USB,而且会占用大量CPU资源,但它的确可以工作。问题在于:现有的USB转并口合同基本都要用BULK传输端点,而USB规范中明令严禁在低速设备中使用BULK端点。假如我们要完全遵循USB标准,那就得用中断端点(InterruptEndpoint)自己设计一个通讯合同,并为所有主流操作系统编撰驱动程序——这个工作量实在太大linux 命令,我也没哪些兴趣。所幸,所有主流操作系统实际上并未严格执行这一规则,即使是低速USB设备用上了BULK端点,也仍然能正常辨识和通讯。所以串口驱动linux,我们可以直接用V-USB模拟并口设备(ACM插口),基本上都能用。

至于RAM的选择,当然没哪些好迟疑的,SOIC-8封装的PSRAM就是最合适的解决方案。目前常见的供应商包括ISSI、APMEMORY和Vilsion,她们已然吹了一年多说自己要出16MB的PSRAM芯片了,但到现今都没实现承诺,所以很可能是在画饼。好在8MB的芯片货源充足、价格实惠,在各大电商平台花几欧元才能买到。为此,我最终决定直接用8MB的RAM来打造这台Linux小笔记本。

最后一个问题是:该选用哪款微控制器?通过参数筛选功能,我最终把眼神放在了STM3232GG0系列。按理说,STM芯片应当作为最后的选择,由于它们从不认真发布完整确切的勘误指南。STM3232GG030030第一个被淘汰,由于其中一个引脚被硬设为RESET,只剩5个I/O太局限了。STM3232GG031031031JJ44MM6看上去挺好,这是一款比较新的芯片,其实STM此次有点靠谱,把往年的各类坑都填了?再说了,这个项目计划中也不会用到太多片上外设,恐怕会没问题?32KB闪存、8KBRAM——这两个数字秒杀所有其他选项!Cortex-M0+核心也让它成为性能方面最强的候选之一。官方数据显示,这款芯片位宽为64MHz,稍加努力能达到80MHz,再下点狠手,甚至能挪到150MHz。假定我能避免勘误表中各类已知/未知的bug,这毫无疑惑是最强选择——虽然我不太喜欢STM,但也只能这样了。

硬件设计

(1)控制台

UART的引脚几乎难以与其他功能复用。企图将UART的RX引脚复用会带来数据遗失的风险——当“其他功能”正在运行时,若果此时恰好有并口数据到来,都会被错过。而将UART的TX引脚复用也同样不靠谱:无论“其他功能”切换得有多快,只要形成低电平脉冲,PC就可能错判这是一位并口字符。假如是短暂的低电平,PC一般会将其辨识为0xFF。

理论上来说,可以通过启用奇偶校准来掩藏这种干扰,但这并不是可靠方案。更毕竟,2025年了谁就会用奇偶校准呢?说究竟,UART引脚因为欠缺更高层的合同、片选讯号或独立时钟,基本不适宜与其他功能复用。

所以,这6个引脚中,早已被占用掉2个了。我也只能无奈接受这个现实……

(2)RAM

所有的SPIPSRAM芯片都支持QSPI模式,以提升传输速率。但遗憾的是,QSPI须要6个引脚,而现今只剩下4个了。好在多数PSRAM也支持双线SPI模式,这些模式下MOSI和MISO会同时传输,一次时钟周期内传送2位数据,是普通SPI速度的两倍。更棒的是,它不须要比普通SPI多占任何引脚,而且可以与其他设备共享SPI总线——因为在未被选中(deselected)时,这种设备不会驱动MISO,也不会尝试读取MOSI。

不过,STM3232GG031031并不原生支持dual-SPI。假如想用,只能通过软件模拟实现。但问题来了:软件实现dual-SPI的速率是否能赶上硬件SPI模块?硬件SPI模块可以以CPU时钟一半的速度运行,并能联接DMA单元实现持续的数据传输。

要想用纯CPU模拟实现同样的吞吐,须要保证每次传输周期只消耗4个指令周期,这几乎是CPU的极限操作,再快就不可能了。既然最快也只是“勉强追平”硬件SPI,那又不必折腾?推论:RAM还是用普通SPI访问就好。不过,这下就直接用掉了最后剩下的4个引脚了。唉……

(3)SD卡

最小化极简电脑_串口驱动linux_8引脚芯片

所以,情况显得棘手了:没有引脚剩余,但还要连一个SD卡。

SD卡可以使用SPI通讯,只需再提供一个引脚用于片选讯号即可,但早已没有多余的引脚了。我考虑了几种方案,最简单的做法是,在RAM的nCS上接一个反相器(inverter),并将其输出作为SD卡的nCS讯号。我把这个思路被做了原型测试,发觉疗效还不错。但有两个问题:首先,有些SD卡不能接受那个“被选中但没有数据发送”的情况。假如两次RAM访问之间没有写入数据,对SD卡来说如同是这些异常操作,存在兼容性隐患。其次是反相器须要一个额外的IC或起码一个二极管,这会降低BOM复杂度。对于想DIY这块板子的初学者来说,板子上的组件越多,组装就越困难。因而这个方案被贴上了“最坏方案”标签,暂时搁置,还得继续找寻更好的解决方式。

因为这个设备的数据产出速度不高,UART的码率虽然可以设得很低。于是可以考虑:是否能把UART的TX引脚加个低通混频器,之后同时用作SD卡的nCS?只要SD命令足够短、时钟频度足够高,选通讯号的时间窗口就可以被混频器“放过去”。这个技巧在理论上可行,但实际上十分脆弱。我进一步估算发觉,假如想符合SD合同初始化要求,UART的码率必须低至300bps或更低,而尽管这么,一旦SD卡响应速率稍慢,系统就容易崩溃,由于SD合同中明晰严禁在读取响应过程中取消片选讯号。所以,这个方案甚至比第一个还差。

正当我打算回到最初那种“最坏方案”时,又忽然冒出一个更疯狂的看法:RAM是否介意被选中后又立刻取消,而不执行任何命令或数据传输?实验表明:不介意,这项测试在所有SPIRAM芯片上都通过了!

为何?由于SD卡不仅支持SPI,还支持SDIO合同。SDIO不用4根双向线,而是用1根时钟线(CLK)和2根单向线(CMD和DAT)。假如是4-bit模式,会额外加3根DAT,但本项目中只用1-bit模式即可。虽然SDIO合同在公开SD规范中并没有详尽说明,但可以通过观察推理下来——这个方案似乎在引脚上节约不了多少,但带来了新的可能性组合。

于是问题弄成:是否能把SDIO的3根讯号线与RAM的引脚复用?经过反复推演,总算找到“可行映射”:RAM的nCS→SD的CLK;RAM的CLK→SD的CMD;RAM的MOSI→SD的DAT。剖析各自访问行为后发觉:当访问RAM时,SD卡听到的是CLK拉低,而当RAM被取消片选,CLK就拉高。RAM的SPI设置为Mode3,CLK空闲态为高电平,所以每次访问RAM,对SD来说都像是CMD线上发送了一个“1”位。这恰好对应SDIO合同中命令间隙的空闲状态,是安全的。

同样,SD卡在命令之间不会读或写DAT线,所以RAM的MOSI讯号不会被错判。反过来看,当访问SD卡时,须要切换CLK和写入CMD、DAT,对RAM来说就是快速选中再取消——RAM也能接受这个行为。完美!

须要注意的是,这个方案的前提是SD卡事务必须“一次性完成”,过程中不能访问RAM。这就意味着不能用多块读写(multi-block),考虑到目前这些引脚紧张的现况,这是可以接受的。

好了,这就是一个潜在可行的解决方案!接出来是实验验证,结果是——成功!其实了,因为STM3232GG031031没有对应的硬件模块,因而SDIO的访问是完全手工bit-bang实现的。最终我写的汇编代码达到了每个14个CPU周期的传输效率,总的来说表现还不错。

(4)再回到控制台

现今所有的I/O插口理论上都能塞入6个引脚里了,是时侯即将分配每位引脚的功能了。

部份引脚的功能早已确定:RAM要用标准SPI,对应的那几根线就直接保留给它。SD卡与RAM共享那些线,也无需额外分配。还剩下引脚7和引脚8,这两个引脚恰好是SWD调试插口,在开发初期用于调试十分便捷。另外通过排除法,它们也也须要作为串行端口。引脚8作为A14可以充当USART2.TX,通过启用USART的“引脚交换”功能,可以将其转换为USART2.RX。由于假如没有硬件协助的话,UART接收是很麻烦的,所以引脚7就留给TX。这个引脚并不支持USART的任何取代功能,但没关系,我们可以用bit-bang的形式手工实现UART发送。

有趣的是,之前在考虑共享引脚方案时,我还想着让UART尽可能慢;而如今,为了自动实现UART,码率又得尽可能快——因为发送期间CPU必须专注“盯着发”,不能被打断。每发送一个字符(以115200bps估算)大概只需87毫秒。理论上我们也可以通过定时中断逐位发送每位字符,但中断带来的时间晃动可能导致串口误码。庆幸,大多数情况下设备并不会频繁输出,所以如今这个方案早已挺好了。UART发送bit-bang实现疗效良好,引脚分配也已完成,接出来就可以步入软件开发阶段了。

但你可能会问,初次烧写如何办?在这些“非常规引脚布局”下,STM32官方的引导加载程序(bootloader)如何可能支持?确实不支持,所以我在板子上设计了4个可点焊尾纤桥(solderbridge),通过尾纤配置可以切换并口的联接方法:在“开发模式”下,bootloader可用,但RAM和SD卡都没法工作;在“正式模式”下,ROMbootloader失效,但项目可以正常启动。庆幸,本项目带有自定义bootloader,所以在初次烧写以后,之后就无需依赖ROMbootloader了。

串口驱动linux_最小化极简电脑_8引脚芯片

关于软件的故事

(1)模拟器

最小化极简电脑_串口驱动linux_8引脚芯片

早在项目开始前,我就早已写好了一个可以启动Linux的MIPS模拟器,整个代码用ARMv6M汇编语言编撰,要在新项目中复用这部份代码并不难。

为了进一步提高性能,我还写了一个MIPS到ARMv6M的JIT(即时编译器),运行疗效也不错。但不幸的是,这个JIT编译器的容积太大,编译后的代码有46KB,而我在这个项目中可用的翻译缓存只有6KB,因而性能提高疗效并不显著。最终,我选择将这个JIT暂时搁置,留待日后再用。

在此次项目中,STM3232GG031031芯片的32KB闪存被界定为两个区域:8KB分配给引导程序(bootloader),24KB用于主程序(mainapplication)。不仅一些必要的优化和适配,主模拟器代码基本保留了原貌。

(2)引导加载程序(bootloader)

这么,为何须要一个引导程序呢?缘由似乎很简单:板子上早已没有多余的引脚用于调试;项目还处于开发阶段,须要一种方法来升级固件、修复bug或添加新功能。最直接有效的方案就是设计一个支持SD卡、能够辨识FAT文件系统、并在测量到新版本时手动升级固件的bootloader。

之所以bootloader的大小会达到8KB(实际上是6.5KB,但因为闪存块大小为2KB,所以向下取整到了8KB),是由于它必须包含完整的SDIO驱动、FAT文件系统驱动、闪存写入代码以及大量的日志记录以排查更新时的各类问题。其实,它还内嵌了一个基于bit-bang实现的UART发送模块。bootloader会检测主程序镜像中偏斜地址16处的数值,这是主程序的版本号。只有当更新文件中的版本号低于当前主程序,而且通过了一些基本校准,才能执行更新。至于bootloader自身的版本号,则被记录在偏斜地址8处,仅用于显示启动信息。符合条件的固件更新文件名为FIRMWARE.BIN,一旦通过校准,都会被应用。

bootloader本身在芯片复位后运行,其默认频度为16MHz。而主程序运行时的频度是可调的,便捷用户尝试开核。但频繁地自动更改代码、重新编译并烧写固件,太费力了。这个问题可以通过一个小窍门轻松解决:既然bootloader早已挂载了FAT文件系统用于检测更新,这么顺便也可以扫描是否存在以CLOCK开头命名的文件或文件夹。假如有,它前面的数字都会被解析为主程序的运行频度(单位MHz)。假如这个值不在合理范围(32–200MHz)内,或则找不到相关文件,则默认设为132MHz。

(3)SD卡分区与启动过程

和我之前几个基于MIPS模拟器的项目一样,本项目的启动流程也借鉴了PC启动过程的设计。系统首先读取SD卡的第一个磁道(sector),将其加载到显存前几个字节中,之后跳转执行;这个一级引导代码会继续找寻一个分区类型为0xBB的分区,并将其完整加载至显存地址0x80001000,之后再度跳转;此时,二级引导程序开始运行,它具备日志和并口输出功能;它会扫描所有分区,找到被标记为“active”的那种,尝试将其挂载为FAT16文件系统;假如该分区中存在名为VMLINUX的文件,它会将其作为ELF文件加载,并跳转至其入口地址;假如该文件是一个有效的Linux内核,还会步入Linux系统启动流程。

传递给内核的启动参数(commandline)被硬编码在bootloader中,不支持动态更改。它指定内核将/dev/pvd3作为根文件系统,并以/sbin/uMIPSinit作为init程序,同时它就会尝试将/dev/pvd1挂载为/boot目录。

仔细阅读上文可以发觉,尽管系统要求根文件系统必须在第3个分区(pvd3),但其它分区次序似乎并没有强制要求——这是刻意为之的设计。对于这个项目,FAT分区是第一个,bootloader分区是第二个,Linux根文件系统是第三个。为何呢?当插入具有多个分区的SD卡时,Windows和macOS会挂载第一个分区,而Linux会挂载所有分区,这意味着:

bootloader在开始执行任何操作之前,首先会静静地等待6秒钟。这段延后是在重新配置引脚前进行的,目的是为用户留出一段时间联接调试器(SWD),由于开发板上预留了一个4针的调试插口。超过6秒后,bootloader会重新配置引脚,此时SWD功能不再可用,调试器难以再联接。据悉,作为一种后备机制,bootloader都会通过设置optionbytes选项字节,实现如下行为:假如将BOOT0引脚(第8引脚)拉高,芯片将从ROM启动;

禁用芯片的RESET引脚(在本项目中作为普通GPIO使用)和BOR(低电流复位,在本项目中没有用)。一切就绪后,bootloader会尝试初始化与SD卡的通讯,检测更新文件,最后即将启动系统。

性能怎样?

8引脚芯片_最小化极简电脑_串口驱动linux

STM32G031官方标称的最高运行频度是64MHz,那为何这儿还要讨论让它挪到150MHz呢?缘由是:只要适当应用一些“黑科技”,STM3232GG031031实际上开核能力十分强。STM3232GG031031的CPU核心电流由内部稳压器供电,而这个电流可以通过PWR->CR1寄存器进行调整。ST官方文档中提及了两种电流设置:VOS2(对应1.0V的Vcore),在这些设置下芯片只能挪到16MHz;VOS1(对应于1.2V的Vcore),这些情况下芯片只能挪到64MHz。

实际测试表明,在VOS1模式下,STM3232GG031031可以稳定运行在75MHz左右,这早已算是一次不错的开核了,但还不算震撼。但是,初期文档(以及同系列芯片的资料)中还提及了VOS0模式,对应1.35V的Vcore。假如我们强行尝试开启这个电流模式,会如何?结果让人惊喜——真的能用,但是开核潜力急剧增强:大多数芯片在136MHz时仍能稳定运行,某些体质优良的芯片甚至能冲上180MHz!其实,Flash的访问速率并不会随着频度提高而同步推进,因而必须正确设置Flash的等待周期(waitstate),尽管这会影响一些速率提高,但综合来看还是值得的。

在显存达到148MHz时,这颗STM32模拟运行的MIPSCPU,大致等效于1.65MHz的MIPSR3000(禁用FPU)。它算不上性能怪兽,但能在大概一分钟内启动,但是vi、make、objdump和gcc等工具都能正常工作——这是一个完整的Debian系统,你甚至可以通过/boot目录导出.deb包并安装,一切都能跑上去。

最后组装!

(1)获取零件

你可以选择自己订购零件,并把PCB板送到自己喜欢的打板厂打样钎焊。另外,我们也正在找寻合作厂商来打包成套件开售,假如你有相关线索可以联系我——这可能是绝佳的DIY礼物!

8引脚芯片_串口驱动linux_最小化极简电脑

(2)初步点焊

接出来是大家最关心的部份:怎么自己动手焊出这块板子。你手上这块板子,虽然我早已尽量把它设计成容易组装的。我们推荐的点焊次序如下:

(3)二次点焊

这个阶段要给STM32刷入bootloader。因此,可以下载STM提供的烧写工具(Windows用户可使用官方flasher,其他系统可以用开源的stm32flash工具)。打算一根USB-C线串口驱动linux,用两根细导线桥接电路板上标记为R101和R201的位置,此时确保SD卡未插入且仍未点焊RAM芯片。之后,将板子通过USB-C接入笔记本,系统应辨识到一个虚拟并口,使用烧写工具写入BOOTLOADER.BIN文件(路径和并口名称视系统而定)。

烧写完成后,移除用于桥接R101和R201的点焊导线,改为联接R102和R202,这也是并口引脚的最终正确配置。最后,点焊RAM芯片(APS6408或VTI7064,位置为U2),此芯片的引脚1也有一个小凹陷标示,将其对齐PCB上的小圆点并点焊即可。至此,硬件部份组装完成!

烧写主固件并完成首次启动

我们须要使用c盘镜像写入工具,将提供的系统镜像写入一张容量不大于1GB的SD卡中,具体如下:

(1)Windows:使用

Win32DiskImager

(2)macOS:使用系统自带的“磁盘工具”(DiskUtility);

(3)Linux:使用dd命令。

这个系统镜像里早已包含了完整的启动流程:包括第一阶段的MIPS启动程序、第二阶段的MIPS启动程序、包含Linux内核与固件的分区,以及一个Debian根文件系统(rootfs)。

镜像写入完成后,弹出并重新插入SD卡,笔记本会辨识并挂载FAT文件系统分区。此时,请将下载包中的主固件FIRMWARE.BIN拷贝进SD卡的FAT分区中。这个步骤的作用是让启动引导程序在第一次启动时手动辨识并烧写该固件。假如你没有重新编译固件,虽然这个步骤可以跳过,由于镜像本身早已包含了这个文件。不过虽然重复操作也不会有任何副作用,可以放心执行——如此,一切便打算就绪!

插入SD卡,再度将USB-C数据线联接至笔记本,打开你喜欢的并口终端软件,并将其配置为115,200bps,8N1格式。几秒钟后,你将见到并口终端开始复印启动信息,这是多个启动阶段依次执行的过程。第一次启动时,STM32的熔断器(fuse)将被写入配置,此时可能须要你在并口信息停止时重新拔插一次USB-C插口。因为熔断器是非易失性的,因而只需执行一次即可。大概20秒后,你将见到Linux内核启动的复印信息,整个启动过程大概须要1分钟,最终你将见到一shell提示符。考虑到系统仅有8MB的显存,因而建议你在登陆后先执行swapon/swapfile命令启用交换空间,启用过程大概须要几十秒,完成后你就可以运行更多命令和程序了!

最后,感兴趣的开发者可以自行下载这个压缩包:

,上面包含了该项目所需的全部内容,期盼诸位的DIY之旅!

Tagged:
Author

这篇优质的内容由TA贡献而来

刘遄

《Linux就该这么学》书籍作者,RHCA认证架构师,教育学(计算机专业硕士)。

发表回复