简介:DE1-Soc硬件原理图及开发手册面向使用DE1-Soc开发板的嵌入式工程师与学习者,聚焦ARM+FPGA异构平台的硬件认知与开发入门。手册从开发套件包装清单、系统光盘使用与帮助获取讲起,逐步深入到开发板布局、组件与块图,帮助读者理解处理器、内存与外设的连接关系;随后重点讲解FPGA配置模式设置、Cyclone V SoC FPGA配置方法,以及LED、按键、开关、复位、时钟电路和GPIO扩展、音频编解码、VGA、TV解码、红外收发等外围接口的使用。资源包为1个PDF文件,约7.1MB,内容完整、结构清晰,适合作为案头速查与系统学习资料。目前已有1318人学习下载,无论是搭建实验环境、查阅原理图细节,还是开展嵌入式项目设计,都能从中获得扎实的硬件参考与操作指引。
1. DE1-SoC 硬件原理图与开发手册:从引脚到外设,一份能直接上手的拆解路径
拿到一块 DE1-SoC 开发板,很多人第一反应是插上电源、连上 USB Blaster,然后打开 Quartus 跑一个点灯工程。灯亮了,觉得“跑通了”,但一旦要接一个自定义外设、改一个时钟频率、或者把 HPS 和 FPGA 之间的数据通路调通,就发现完全不知道从哪下手。问题不在工具,在于没有把硬件原理图和开发手册这两份文档真正读进去。DE1-SoC 的硬件原理图告诉你每个引脚连到哪里、每个电源域怎么分配、每个外设的地址映射是什么;开发手册则告诉你这些资源怎么用、时序怎么约束、HPS 和 FPGA 怎么协同。这两份文档合在一起,才是这块板子的完整“黑匣子说明书”。这篇文章面向的是已经拿到板子、准备做实际项目的开发者,不管你是要做一个视频采集系统、一个电机控制回路,还是一个 HPS+FPGA 异构计算的原型,读完之后你应该能自己从原理图里找到关键约束,从手册里找到配置方法,而不是每次遇到问题就去翻论坛碰运气。
2. 从原理图里读出引脚约束与电源域:选型前必须确认的三件事
2.1 引脚分配不是查表,是理解 Bank 和 I/O 标准
DE1-SoC 的 FPGA 是 Cyclone V SE 系列,引脚按 Bank 分组,每个 Bank 有独立的 I/O 电压标准。原理图上每个连接器、每个外设的引脚都会标注它属于哪个 Bank、参考电压是多少。很多人做引脚约束时直接抄别人的 .qsf 文件,结果发现某个 GPIO 怎么都驱动不起来,或者电平不对。常见原因是那个引脚所在的 Bank 的 VCCIO 电压和你的外设不匹配。比如你接一个 3.3V 的传感器,但那个 Bank 的 VCCIO 被板上的跳线设成了 2.5V,那输入阈值就不对,读回来全是随机值。
我一般会先做一件事:把原理图里所有用到的 Bank 的 VCCIO 供电来源找出来。DE1-SoC 上有些 Bank 的 VCCIO 是固定 3.3V,有些是通过跳线选择 3.3V 或 2.5V,还有些是直接由 FPGA 的 1.1V 核心电压供电,只能做低速信号。这个信息在原理图的电源部分和 FPGA 的 Bank 引脚标注里都能找到。确认之后,再去看你要用的外设引脚落在哪个 Bank,然后决定是否需要改跳线或者加电平转换。
下面是一个典型的引脚约束片段,展示如何在 .qsf 里指定引脚位置和 I/O 标准:
# 假设我们要把 LED0 分配到 PIN_A15,属于 Bank 3B,VCCIO=3.3V set_location_assignment PIN_A15 -to led[0] # 指定 I/O 标准为 3.3V LVTTL,注意必须和 Bank 的 VCCIO 一致 set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to led[0] # 如果是差分信号,还需要指定负端引脚 # set_location_assignment PIN_A16 -to led_diff_n[0] # set_instance_assignment -name IO_STANDARD "LVDS" -to led_diff[0]这段约束的逻辑是:先确定物理引脚编号,再确定电气标准。参数说明:PIN_A15是原理图上标注的 FPGA 引脚号,不是开发板丝印上的编号;IO_STANDARD必须和该 Bank 的 VCCIO 电压一致,否则 Quartus 在编译时会报错或者警告,但有时候只是警告,下载后行为异常,这就是坑。如果你不确定某个引脚属于哪个 Bank,可以在 Quartus 的 Pin Planner 里看,或者直接翻原理图的 FPGA 页,每个引脚旁边都有 Bank 编号。
2.2 时钟网络:别把普通 I/O 当全局时钟用
DE1-SoC 上有多个时钟源:50MHz 晶振、24MHz 晶振、以及从 HPS 过来的时钟。原理图上会标明哪些引脚是专用时钟输入引脚(CLKIN),哪些是普通 I/O。专用时钟引脚连接到 FPGA 的全局时钟网络,抖动小、偏斜小,适合做系统主时钟。如果你把一个普通 I/O 引脚配置成时钟输入,Quartus 会把它布到局部时钟网络上,导致时序很难收敛,或者在不同温度下行为不一致。
我见过一个案例:某开发者用普通 I/O 接了一个外部 25MHz 有源晶振,做视频像素时钟,结果图像偶尔出现撕裂。后来查原理图发现,那个引脚虽然物理上靠近 CLKIN,但并不是专用时钟引脚,走的是局部布线,偏斜太大。换成专用 CLKIN 引脚后问题消失。所以选型阶段就要确认:你的外部时钟源必须接到专用时钟引脚上。DE1-SoC 的专用时钟引脚在原理图上会标注为CLKIN_*或者FPGA_CLK_*,一般分布在 Bank 的特定位置。
2.3 HPS 侧的外设地址映射:别在 FPGA 里重复造轮子
DE1-SoC 的 HPS 侧有千兆以太网、USB、SD 卡、UART 等外设,这些在原理图上都有明确的引脚连接和地址映射。很多初学者会在 FPGA 里用软核实现一个 UART 或者 SPI,然后通过 PIO 和 HPS 通信,绕了一大圈。其实 HPS 本身就有这些外设,直接通过 Linux 驱动就能用。原理图的作用是告诉你:HPS 的哪些引脚已经固定连接到了板载外设,你不需要在 FPGA 侧再分配引脚。
比如 HPS 的 UART0 已经连接到板上的 USB-UART 芯片,你在 Linux 里直接操作/dev/ttyS0就能收发数据。如果你非要在 FPGA 里再做一个 UART,那就需要占用额外的引脚,而且还要写 Avalon-MM 接口和 HPS 通信,复杂度高很多。所以第一步应该是:把原理图上 HPS 侧的外设连接关系理清楚,能用 HPS 原生外设的就不要用 FPGA 模拟。
下面是一个在 Linux 下查看 HPS 外设地址映射的常用命令,可以帮助你确认哪些外设已经可用:
# 查看 HPS 的 UART 设备 ls /dev/ttyS* # 查看 I2C 总线 i2cdetect -l # 查看 GPIO 映射 cat /sys/kernel/debug/gpio # 查看 HPS 外设的物理地址映射(需要 root) cat /proc/iomem | grep -E "ff2|ffc"这些命令的输出能帮你快速判断 HPS 侧的资源占用情况。参数说明:/proc/iomem里的地址范围对应 HPS 的寄存器映射,ff2xxxxx一般是外设控制器,ffcxxxxx是系统管理器。如果你在 FPGA 侧要访问 HPS 的内存,需要用到 FPGA-to-HPS 桥或者 HPS-to-FPGA 桥,这些桥的地址窗口在开发手册里有详细说明。
3. 开发手册里的时序约束与 HPS-FPGA 桥接:从手册到可运行工程
3.1 时序约束不是可选项,是必选项
Cyclone V 的时序模型在开发手册里有详细描述,包括建立时间、保持时间、时钟到输出延迟等。很多人做 FPGA 设计时只写功能代码,不写 SDC 约束,Quartus 默认会按一个很宽松的时钟模型去布局布线,结果功能仿真通过,上板就挂。DE1-SoC 的开发手册里给出了推荐的 SDC 模板,包括主时钟定义、输入输出延迟、虚假路径等。
我一般会先根据原理图确定所有时钟源的频率和引脚,然后在 SDC 里逐个定义。比如 50MHz 主时钟:
# 定义 50MHz 主时钟,周期 20ns create_clock -name clk_50 -period 20.000 [get_ports {CLOCK_50}] # 定义衍生时钟,比如通过 PLL 产生的 100MHz derive_pll_clocks # 定义输入延迟,假设外部器件在时钟上升沿后 5ns 输出数据 set_input_delay -clock clk_50 -max 5.000 [get_ports {data_in[*]}] set_input_delay -clock clk_50 -min 2.000 [get_ports {data_in[*]}] # 定义输出延迟 set_output_delay -clock clk_50 -max 3.000 [get_ports {data_out[*]}] # 设置异步路径为虚假路径 set_false_path -from [get_ports {reset_n}]这段 SDC 的逻辑是:先定义时钟,再定义输入输出延迟,最后处理异步信号。参数说明:-period 20.000对应 50MHz,单位是纳秒;set_input_delay的 max/min 值需要根据外部器件的时序手册来定,不能随便写。如果你不确定外部器件的时序,保守做法是设一个较大的值,让工具去满足,但这样可能会浪费面积。开发手册里通常会给出常见外设的推荐值,比如 SDRAM 的输入输出延迟。
3.2 HPS-FPGA 桥接:地址窗口和带宽要匹配
DE1-SoC 的 HPS 和 FPGA 之间有三组桥:FPGA-to-HPS、HPS-to-FPGA、轻量级 HPS-to-FPGA。开发手册里会给出每组桥的地址范围、数据位宽和最大频率。很多人做异构计算时,把大量数据从 HPS 搬到 FPGA,结果带宽不够,成了瓶颈。比如你用 HPS 的 DDR 存图像,然后通过 HPS-to-FPGA 桥搬到 FPGA 做处理,如果桥的位宽是 32 位、频率 100MHz,理论带宽 400MB/s,实际可能只有一半。如果图像是 1080p@60fps,每帧 3MB,每秒 180MB,看起来够,但加上读写开销和仲裁延迟,可能就卡了。
我一般会先算一下数据吞吐量,再决定用哪组桥。轻量级桥适合寄存器配置,带宽小但延迟低;全功能桥适合大数据搬运。开发手册里有每座桥的时序图,可以帮你估算实际带宽。另外,HPS 侧的 DMA 控制器可以直接和 FPGA 桥对接,减少 CPU 干预,这个在手册的 DMA 章节有说明。
下面是一个在 Linux 下通过 mmap 访问 FPGA 侧寄存器的例子,用的是轻量级桥:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #define LW_BRIDGE_BASE 0xFF200000 // 轻量级桥基地址,参考开发手册 #define LW_BRIDGE_SPAN 0x00200000 // 映射窗口大小 int main() { int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("open"); return -1; } // 映射轻量级桥地址空间 void *base = mmap(NULL, LW_BRIDGE_SPAN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, LW_BRIDGE_BASE); if (base == MAP_FAILED) { perror("mmap"); close(fd); return -1; } // 假设 FPGA 侧有一个 32 位寄存器在偏移 0x100 处 volatile unsigned int *reg = (unsigned int *)(base + 0x100); // 写数据 *reg = 0xDEADBEEF; // 读回验证 unsigned int val = *reg; printf("Read back: 0x%08X\n", val); munmap(base, LW_BRIDGE_SPAN); close(fd); return 0; }这段代码的逻辑是:打开/dev/mem,用 mmap 把轻量级桥的物理地址映射到用户空间,然后直接读写。参数说明:LW_BRIDGE_BASE是 0xFF200000,这是 Cyclone V 的轻量级桥基地址,开发手册里有;LW_BRIDGE_SPAN是映射窗口大小,一般 2MB 够用。注意:/dev/mem需要 root 权限,而且如果内核配置了 STRICT_DEVMEM,可能会限制访问范围。更安全的做法是写一个内核驱动,但调试阶段用 mmap 最快。
3.3 从手册到工程:一个最小可运行系统的搭建步骤
把原理图和手册的知识串起来,搭建一个最小系统:FPGA 侧做一个 PWM 控制器,HPS 侧通过轻量级桥配置 PWM 频率和占空比,然后驱动一个 LED 或者电机。步骤如下:
第一步,在原理图上找到 LED 或电机驱动接口的引脚,确认 Bank 和 I/O 标准。第二步,在 Quartus 里创建工程,分配引脚,写 PWM 的 Verilog 代码。第三步,在 Qsys 里添加 HPS 和轻量级桥,把 PWM 寄存器映射到桥的地址空间。第四步,生成设备树和头文件,在 Linux 里用 mmap 访问。第五步,写一个简单的测试程序,改变占空比,观察 LED 亮度或电机转速。
这个流程里最容易翻车的地方是地址对齐。轻量级桥的寄存器必须是 32 位对齐,如果你在 Qsys 里分配的偏移不是 4 的倍数,访问会出错。另外,设备树里的reg属性要和 Qsys 里分配的地址一致,否则 mmap 的偏移对不上。我一般会在 Qsys 里生成地址映射后,直接复制到设备树里,避免手写出错。
4. 避坑与排查:DE1-SoC 原理图和手册里最容易忽略的五个细节
4.1 现象:FPGA 配置成功但外设不工作,原因:引脚被 HPS 占用
DE1-SoC 的很多引脚是 HPS 和 FPGA 复用的,比如某些 GPIO、I2C、SPI。如果你在 FPGA 侧分配了一个引脚,但这个引脚在 HPS 侧已经被默认功能占用,那 FPGA 的输出会被 HPS 覆盖,或者根本驱动不了。原理图上会用不同的颜色或标注区分“HPS 专用”和“FPGA 可用”。解决方法是:在 HPS 侧的设备树里禁用对应的外设,释放引脚给 FPGA。比如你要用 FPGA 控制 I2C,就要在设备树里把 HPS 的 I2C 控制器状态改成disabled。
4.2 现象:时序报告显示建立时间违例,原因:跨时钟域没做同步
开发手册里反复强调跨时钟域信号必须做同步处理,但很多人还是直接把一个时钟域的信号拉到另一个时钟域。Quartus 的时序分析会报建立时间违例,但如果你没看报告,直接下载,可能功能偶尔正常,偶尔出错。解决方法是:对所有跨时钟域信号加两级触发器同步,或者用异步 FIFO。SDC 里要对这些路径设置set_false_path或者set_clock_groups,让工具不要分析,但前提是你已经做了同步。
4.3 现象:HPS 启动后无法访问 FPGA 寄存器,原因:桥的时钟没使能
HPS-to-FPGA 桥需要时钟才能工作,这个时钟可以来自 FPGA 侧,也可以来自 HPS 侧。开发手册里说明,如果桥的时钟没有使能,访问会返回总线错误。我遇到过一种情况:在 Qsys 里连接了桥,但忘记在 HPS 的时钟配置里使能对应的桥时钟,结果 Linux 启动后 mmap 成功,但读写寄存器时进程直接挂掉。解决方法是:在 Qsys 的 HPS 配置里,确认FPGA-to-HPS和HPS-to-FPGA的时钟使能位都打开,并且在设备树里添加对应的clocks和clock-names属性。
4.4 现象:SD 卡启动失败,原因:设备树里的引脚配置和原理图不一致
DE1-SoC 的 SD 卡引脚在原理图上有明确的上下拉要求,设备树里的pinctrl节点必须和原理图一致。如果上下拉配错,SD 卡在高速模式下会识别失败。解决方法是:对照原理图的 SD 卡部分,检查pinctrl里的bias-pull-up或bias-pull-down设置,以及驱动强度。另外,SD 卡的电源域也要确认,有些板子 SD 卡电源是可控的,需要在设备树里加 regulator 节点。
4.5 现象:Quartus 编译报错“引脚不存在”,原因:原理图版本和器件型号不匹配
DE1-SoC 有多个版本,不同版本的 FPGA 器件型号可能不同,引脚定义也有差异。如果你拿到的原理图是旧版本的,而 Quartus 工程里选的器件是新版本的,就会报引脚不存在。解决方法是:在 Quartus 里确认器件型号和板子上的 FPGA 丝印一致,然后对照对应版本的原理解图。开发手册的首页通常会写明适用的板子版本和器件型号,不要混用。
5. 用 SignalTap 和 System Console 验证硬件通路:一个可复现的调试习惯
5.1 用 SignalTap 抓取 FPGA 内部信号
SignalTap 是 Quartus 自带的嵌入式逻辑分析仪,可以把内部信号抓到 JTAG 上观察。我一般会在关键路径上例化 SignalTap,比如 HPS-to-FPGA 桥的读写使能、PWM 的计数器、跨时钟域同步器的输出。设置时注意采样深度和时钟域,采样时钟要用被观测信号所在的时钟域,否则抓到的数据没有意义。SignalTap 会占用额外的逻辑资源和 RAM,调试完成后记得在工程里禁用,否则会影响时序和面积。
# 在 Qsys 里添加 SignalTap 需要先在 Quartus 里创建 .stp 文件 # 然后在 .qsf 里添加以下约束,指定采样时钟 set_instance_assignment -name SIGNALTAP_CLOCK clk_50 -to auto_stp_external_stp_clock # 采样深度设为 1024,根据 RAM 资源调整 set_instance_assignment -name SIGNALTAP_SAMPLE_DEPTH 1024 -to auto_stp_external_stp_clock这段约束的逻辑是:指定 SignalTap 的采样时钟和深度。参数说明:clk_50是采样时钟,必须是设计里的时钟信号;采样深度越大,占用的 RAM 越多,一般 1024 或 2048 够用。注意:SignalTap 的采样时钟频率不能超过 JTAG 的带宽限制,否则数据传不出来。
5.2 用 System Console 读写 Avalon 寄存器
System Console 是 Quartus 里的一个 Tcl 控制台,可以通过 JTAG 直接读写 Avalon-MM 寄存器,不需要 Linux 启动。这在调试 HPS-FPGA 桥的时候特别有用,因为你可以绕过 Linux,直接验证 FPGA 侧的寄存器逻辑。比如:
# 打开 System Console,连接到板子 # 假设有一个 Avalon-MM 从设备,基地址 0x1000 set master [lindex [get_service_paths master] 0] open_service master $master # 写寄存器 master_write_32 $master 0x1000 0x12345678 # 读寄存器 set val [master_read_32 $master 0x1000 1] puts "Read: $val" # 关闭 close_service master $master这段 Tcl 的逻辑是:获取 master 服务路径,打开服务,然后读写 32 位寄存器。参数说明:0x1000是 Avalon-MM 地址,相对于桥的基地址;master_write_32和master_read_32是 System Console 的内置命令。注意:使用前要确保 FPGA 已经配置,并且 JTAG 连接正常。这个方法的好处是快,不需要编译 Linux 内核或者写驱动,适合早期调试。
5.3 一个可复现的调试习惯:先通桥,再通外设
我自己的习惯是:拿到一块新板子或者新工程,先不急着接外设,而是先用 System Console 确认 HPS-FPGA 桥能读写,再用 SignalTap 确认 FPGA 内部逻辑在跑,最后才接外设。这个顺序能帮你快速定位问题是在桥、在逻辑、还是在外设。很多人的翻车经历是:外设不工作,就怀疑外设,结果查了半天发现是桥的地址映射错了。先通桥,再通外设,能省很多后悔药。
另外,原理图和开发手册要对照着看。原理图告诉你“连到哪里”,手册告诉你“怎么用”。比如原理图上某个引脚标了HPS_GPIO,手册里会告诉你这个 GPIO 在 Linux 里对应哪个编号,怎么导出、怎么控制。两者结合,才能把硬件资源用起来。希望帮到你。
本文还有配套的精品资源,点击获取