前言
本学期(大二上)学习的“数字逻辑电路与系统”课程要求基于 DE1-SoC 完成一个 FPGA 设计项目,课题自拟。学期伊始我们本着“借鉴”的态度在网上一番寻找,发现音乐播放器有很多参考资料,故决定做用 DE1-SoC 实现一个简单的音乐播放器。
需要注意的是,这个项目有些脱离了练习 Verilog 的初衷,我们项目的很大一部分是在 SoC 上集成的 ARM 处理器上实现的。
代码仓库:Nativu5/DE1-SoC-MyPlayer: A Music Player on DE1-SoC Board. (github.com)
开发板简介
DE1-SoC 是 Terasic 设计生产的开发板,集成了 Cyclone V FPGA 芯片和 ARM Cortex-A9 处理器,具有丰富的外设,例如 WM8731 音频芯片、千兆网口、SD卡槽等等。
值得注意的是,这个开发板没有集成蜂鸣器,音频的播放需要通过 WM8731 芯片完成。另外 SD 卡槽位于 HPS 部分,FPGA 部分不能直接访问,为了降低 FPGA 开发难度我们直接使用了 HPS。
我们的开发环境是 Windows 10 + WSL + Quartus Prime 18.0 + SoC EDS Standard,同时在 SD 卡上烧录了 Ubuntu Desktop 14.04 LTS 系统,具体的环境配置参见:FPGA 开发笔记:DE1-SoC 启动 Linux 并配置开发环境 - Nativus' Space (naiv.fun)
DE1-SoC 开发板的官方手册和原装光碟资料请参考:Terasic - DE Boards - Cyclone - DE1-SoC Board(Resources)
DE1-SoC 开发板的额外设计 Demo 请参考:Terasic - DE Boards - Cyclone - DE1-SoC Board(Demo)
设计思路
由于 DE1-SoC 没有蜂鸣器,音频的播放需要通过 WM8731 芯片完成。与蜂鸣器只具有一种音色相比,WM8731 芯片能够实现音频的模/数与数/模转换,更高程度的还原音频。同时,直接使用 Verilog 语言对硬件进行设计难以实现复杂的功能(我们很菜),若内置音乐,则音乐的丰富程度会受到限制,所以我们使用 SD 卡来存储音乐文件,允许用户自由增添音乐。而调用 SD 卡又需要使用 HPS,故项目的硬件部分由 HPS 和 FPGA 组成,软件部分由 Linux 上的 C 语言播放器程序和 FFmpeg 音视频转换工具组成。
硬件部分,DE1-SoC 中集成了一个基于 ARM 的硬件处理系统(HPS),使得 DE1-SoC 可以运行 Linux 系统,同时因为 HPS 的操作系统装载在 SD 卡上,通过 Linux 系统就可以直接访问 SD 卡的存储。HPS-to-FPGA 桥能够使 HPS 控制 FPGA 部分的外设,包括 LED 灯、 WM8731 音频芯片等。我们只需在 Linux 上运行程序控制 FPGA 上的外设,将需要播放的歌曲处理后通过 HPS-FPGA 桥传输给 WM8731 即可实现播放。
软件部分,Intel SoC FPGA Embedded Development Suite 可以根据硬件设计和 Qsys 中指定的内存映射地址产生头文件。基于头文件中宏定义的内存映射地址,我们使用 C 语言编写 Audio Control 模块,通过 I2C 协议配置 WM8731 芯片。此外还需要调用 FFmpeg 将各类音乐转换为 PCM 数据,并通过 I2S 协议发送到 WM8731 芯片最终实现播放效果。
设计框图如下:
具体开发流程,我们基于 GSDR 进行开发,过程中综合应用 IP 核等资源,节省开发时间和精力。
项目实现
我们基于黄金系统设计参考(Golden System Reference Design, GSRD)进行本项目的开发。
GSRD是一套由 Intel 和开发板厂商以及开源社区共同维护的设计参考模板,提供了现成的管脚绑定和顶层模块设计。基于该参考模板进行开发能够减少开发工作量,提高设计效率。
DE1-SoC 的设计参考可以在附赠光盘中找到,光盘资料链接在上方开发板简介部分。
硬件设计
通过查阅资料发现,实现 HPS 与 FPGA 间的通信和控制需要使用 Qsys 工具。在 Qsys 中添加IP核控制开发板上的组件,通过连线建立数据通路,就能够对 HPS 和 FPGA 部分进行开发。其中,HPS 能够访问 HPS-to-FPGA 总线连接的组件。Qsys 设计图如下所示。
其中:
- clk_0 模块:FPGA 上的 50MHz 时钟,为整个系统提供时钟源。
- hps_0 模块:即硬核处理器模块,是整个系统的核心,负责将软件上的指令分配到硬件上。
- *_pio 模块:即 FPGA 部分的各个模块,他们都有一个物理地址,映射到 Linux 上供使用。
- pll 模块:调整时钟频率的 IP 核,可以输入主时钟的频率,然后其输出作为音频芯片的频率。
- audio_IF 模块:一个 FIFO 缓冲器,对输入的音频数据进行缓存。
- oc_i2c 模块:一个 I2C 控制器,通过该模块对音频芯片进行配置。
配置 Qsys 时,可以预留地址供软件使用。这里预留的这些地址是真实存在于总线上的,称为物理地址。与之相对的是物理地址在 Linux 操作系统中的映射,称为虚拟地址。软件开发时只需操作虚拟地址就可以操控相应的硬件。
Qsys 设计完成后,可以自动 Generate HDL,我们在项目顶层模块里加入 Qsys 中增加的内容,并依照实际需求进行连线,将 Qsys 和开发板上的具体资源相对应。
此外,完成Qsys的设计后可以直接使用Intel SoC FPGA Embedded Development Suite生成头文件供软件开发使用。到此硬件部分基本实现完毕。
软件设计
软件部分主要由 Linux 系统和播放器程序、FFmpeg 转换软件组成。
首先准备板上的运行环境:
sudo apt update
sudo apt upgrade
sudo apt install ffmpeg # 更新所有软件并安装 ffmpeg
FFmpeg 是一个成熟的音视频转换工具。我们的播放器软件通过调用 FFmpeg 将各种不同格式的音乐转换为 32K 采样率、16Bits 采样深度、双声道的 PCM 格式数据,便于统一配置音频模块。具体的音频格式细节可以查询 WM8731 芯片的手册和代码中的注释了解。
然后开始编写我们自己的播放器程序。播放器分为三大模块,作用分别如下:
- audio_control.c:实现了 I2C 协议,通过对虚拟地址进行读写来操作硬件上的音频模块。
- pcm.c:调用 FFmpeg 软件实现对多种音乐格式的转换,同时能够将转换后的PCM音乐数据发送到硬件上的音频模块,实现播放。
- main.c:主函数,实现虚拟内存的映射和管理。
具体各模块的函数设计请查看源代码及注释。编译的方法是,使用 SoC EDS Shell 切换到软件源代码所在目录下。
make clean # 清理上次编译产生的文件
make # 编译
然后将编译生成的 MyPlayer
下载到开发板上,更改可执行权限,运行即可测试。
软件的命令行操作为:
./MyPlayer filename
filename
可以是任何 FFmpeg 支持的音乐格式。播放器运行后会在目录下生成转换后的 .pcm
文件,下次播放时无需转换格式。
后记
首先分析一下这个项目的优缺点。
优点是,用到了 HPS,播放效果尚可,用户可以自己拷贝歌曲播放。
缺点是:
- 播放时有个 BUG ,特定频率的歌曲会有杂音,我怀疑是时序的问题,从软件看没有什么问题...
(我太菜了解决不来) - 硬件上的功能有点单薄了,很多操作都需要从命令行做,没有和按键之类的联系到一起。
最初我们小组的计划是实现一个蜂鸣器发音的“播放器”,当时甚至因为设计功能过于简单,称其为“八音盒”。实际上手时发现我们的开发板和网上资料相去甚远,难度的上升令我们猝不及防。笔者一度想要摆烂(去淘宝买个外接蜂鸣器,这样就可以白嫖网上代码了),终于还是和两位队友相互鼓励完成了这样一个对于初学者不太友好的项目。中间涉及了很多知识,远非一门“数字逻辑电路与系统”所包涵的。
这个项目的实现参考了许多开源的代码:
- Terasic - DE Boards - Cyclone - DE1-SoC Board 附赠光盘里的学习资料是我们最好的老师,其中录音机相关代码让我们了解了 Nios II(虽然最终没有用上),VIP Demo 更是给我们提供了非常宝贵的设计思路和部分代码
(没有这个 Demo 恐怕我们不可能这么短时间内完工)。 - bsteinsbo/DE1-SoC-Sound (github.com) 一个 Linux 驱动,能够让板载 Linux 直接调用 WM8731 芯片播放声音。很有研究价值,但是在我们时间比较紧且大家都不了解 Linux 内核的情况下,没有尝试。
- ffxx283/WM8731_Audio: FPGA Control WM8731 Audio codec (github.com) 一个现成的 WM8731 控制模块,实现了 I2C 和 I2S。我们曾在这个基础上实现了按键发出不同频率声音的功能,但是因为功能过于单一而放弃
(真 · 八音盒),转为使用 HPS。
最后还是要感谢开源社区的贡献者以及 Terasic 丰富的文档和 Demo 资源。
其实项目的最后,笔者用 React 整了个前端播放器,Golang 写了个后端用来接收控制指令和播放歌曲,还计划改用 CGO 来着,但是时间比较紧,写完的部分没有 Debug 完成就项目汇报了,于是搁置……