基于RP2040的单板HDMI信号输出
写在前面
这篇blog是对我之前制作的picoConsole项目进行的复盘。这个项目制作于2023年,最初的目标是用RP2040制作一个单板游戏机。当时还没有RP2350芯片,而RP2040的PIO控制器等模块数量有限,甚至无法同时完成音视频的输出,因此没能继续下去。如果有机会,我会尝试用RP2350扩展SRAM的方式制作一个更高阶的版本,从而离最初定下的目标更近一些。
基础架构
硬件设计基于PicoDVI,这个项目随后由(adafruit)[https://github.com/adafruit/PicoDVI]进行fork并加入了Adafruit_GFX的支持,从而可以使用更通用的绘图API。这个项目利用RP2040的PIO控制器输出最高640x480的标准TMDS信号,从而可以在任意支持HDMI/DVI的显示器上输出画面。
HDMI使用四组差分信号对传输TMDS信号(时钟一组,RGB信号各一组),为了让RP2040能够输出差分信号,PicoDVI占用了一个PIO控制器,从而让两个IO端口输出相反的电平信号。因此,在做引脚定义时,每个差分对只需要定义一个引脚,该引脚的下一个引脚会自动被占用。定义方式如下:
1
2
3
4
5
6
7
static const struct dvi_serialiser_cfg pico_console_cfg = {
.pio = pio0, // PIO控制器
.sm_tmds = {0, 1, 2},
.pins_tmds = {24, 26, 28}, // 像素对
.pins_clk = 22, // 时钟对
.invert_diffpairs = true
};
在实际显示中,PicoDVI可以选择是否开启buffer。开启buffer后,两个显示缓冲区会以“显示一帧、渲染一帧”的方式交替启用,从而避免画面撕裂。但这也会直接占用一半内存,导致在运行前将各sprite加载进内存不太现实,因此需要在画面渲染时不断从外存中读入。
RP2040原生只支持一个QSPI控制器,这个控制器被用于连接最大16MB的ROM,其余外设只能通过SPI连接,因此在数据传输上面临极大的瓶颈。一开始我考虑使用SD卡存储数据,调用时直接从SD卡读入数据并填充,但实践后发现SPI模式传输速率极低,而如果想要以非原生的方式使用DSPI或QSPI,就势必需要调用此前TMDS信号已经占用的PIO控制器。因此,一旦面临大面积bitmap填充,数据传输瓶颈会直接让帧率跌至个位数。
一开始,我尝试用外接SRAM的形式缓解。SRAM与SD卡的差别在于,SD卡支持的时钟频率有限,而SRAM可以运行在更高的频率上。这虽然能在一定程度上提升数据的吞吐量,但提升极其有限。最终,我选择将图像全部以const的形式写入头文件中。在ARM处理器中,由于外存和内存被视为连续的统一空间,因此只需要引用对应图像的指针就可以直接将图像从外存读入显示缓冲区。
压力测试——播放《Bad Apple》
测试视频
所谓的压力测试,就是测试单片机的IO和填充性能,因此我选择播放一段视频来极限压榨IO。虽然PicoDVI使用640x480的分辨率输出信号,但缓冲区的渲染分辨率实际只有320x240,因此只需要播放320p的视频即可。
视频处理与压缩
《Bad Apple》全长大约3分40秒,如果完全以RGB565的Bitmap存储,文件体积将会是完全无法承载的。因此,想要将视频完整地放入ROM中,就需要对视频进行压缩。
首先可以想到的就是用黑白像素替换原视频中的彩色像素。可以粗略计算一下,如果每个像素选取一个二进制位代表黑白,那么一帧画面就需要320*240/8=9600字节进行存储,在20fps的规模下,视频全长约需要40MB的存储空间,这几乎是ROM的两倍还多,因此,仍然需要对黑白画面进行进一步压缩。
最终,我针对《Bad Apple》黑白剪影画面的独特风格,设计了一种跳变算法,即在单一行中,用数字分别代表连续黑(白)像素的个数。例如,对于一个4x4的画面,如果画面全黑,即可记为{2, 2},如果全白,即可记为{0, 2, 0, 2},换行隐含在像素个数中,不需要单独记录。在320x240的分辨率下,一行共有320个像素,因此这个数字最大为320,需要2字节记录。
在这个无损压缩算法中,最坏的情况便是黑白像素交替出现,这会导致一帧画面需要整整15.3KB存储,相较于普通的二进制位翻了整整16倍,但这种极端情况在《Bad Apple》中并不多见,反倒是由于剪影画面的特性,连续的黑(白)像素才是主流,在这种情况下,一帧画面的平均压缩率甚至可以高达50%。最终,在这种算法下,完整的《Bad Apple》以及逻辑代码占用了ROM 94%的空间,从而使上文中的演示视频成为了可能。
在RP2350中,树莓派基金会为其引入了第二个QSPI控制器,原生支持以QSPI的速率外接第二颗SPI Flash或PSRAM,最大16MB(128Mb),完美解决了上述问题,这也是我最期待的改进方向之一。在第二颗Flash的支持下,RP2350甚至有可能以原生帧率播放完整的《Bad Apple》,甚至是更长的视频。
未来规划
如果可以,我的最终目标是在实现仅使用一颗MCU就完成一款游戏从渲染到画面输出的全过程,这也是我将这个开源项目命名为picoConsole的原因。考虑到树莓派基金会已经推出了RP2350,可以实现更大的数据吞吐量,实现这个目标的芯片已经准备就绪,接下来我会基于这颗芯片重新打造一块核心板,并测试内嵌音频输出的可能性。