简介:DE1-Soc硬件原理图及开发手册面向使用DE1-Soc开发板的嵌入式工程师、FPGA学习者与高校实验教学人员,可用于解决开发板硬件结构不熟、板上外设与FPGA配置无从下手的入门障碍。整包仅含1个PDF文件,约7.1MB,即Terasic官方用户手册,按章节编排,便于随查随用。内容从开发套件清单与系统光盘的使用入手,逐章讲解板卡布局、组件与块图结构,再落到FPGA配置模式设置、Cyclone V SoC FPGA配置流程、板载LED、按键、开关等状态与复位元素、时钟电路,以及7段数码管、GPIO扩展排针、24位音频编解码、I2C多路复用、VGA与红外收发等外设。已有1323人学习下载。读者可据原理图理清ARM+FPGA异构平台的引脚定义、供电与接口时序,为硬件调试、外设驱动编写和课程实验提供查证依据,适合作为案头工具书反复翻阅。
1. DE1-SoC 硬件原理图及开发手册:一块异构板卡的整机说明书
第一次给板子上电,七段数码管全亮、风扇在转、串口却是一片空白,这种场面在 SoC 板卡上出现的频率高得离谱。手边这份《DE1-SoC 硬件原理图及开发手册》解决的正是这类问题:它把一块 ARM + FPGA 异构开发板从包装清单、板卡布局、框图、配置模式、时钟复位、FPGA 侧外设、HPS 侧外设,一路讲到 System Builder、示例工程和 EPCS 固化流程,配套原理图能把每一个网络标号落到具体引脚上。对做嵌入式硬件的人来说,它不是教程,是板级黑匣子的说明书;对只写过单片机、刚转到 ARM + 可编程逻辑平台的工程师,它更像一张必须先看懂再动手的地图。板子不启动、JTAG 扫不到、固化完冷启动无效,这些问题的答案大多藏在手册第 3 章和原理图的那几页里。
2. 把原理图读成约束文件:从框图、时钟域到 40 针扩展口的引脚映射
2.1 按供电域切分原理图,比按页码顺序读快得多
板卡手册第 2 章给的是俯视图、仰视图、框图和一长串硬件特性清单:FPGA 侧有 64MB 16 位数据总线的 SDRAM、4 个按键、10 个拨码开关、10 个红色 LED、6 个数码管、4 路 50MHz 时钟源、24 位音频 CODEC、VGA DAC、TV 解码器、PS/2、红外收发、两个 40 针扩展口和一路 SPI 接口的 ADC;HPS 侧是 800MHz 双核 Cortex-A9、1GB 32 位 DDR3、千兆以太网 PHY、两路 USB Host、Micro SD 卡座、I2C 加速度计、UART 转 USB、冷热复位按键和一个 LTC 2x7 扩展座。
这些信息是"有什么",原理图回答的是"接在哪"。真正翻开原理图,几十页密密麻麻,从头读到尾基本等于白读。我一般的顺序是先找电源树:12V DC 输入进来,经过几级转换分出 FPGA 内核域、FPGA 各 IO Bank 域、HPS 域、DDR3 域、外设域。DDR3 按规范是 1.5V 供电、0.75V 参考,这一域跟 FPGA 侧的 SDRAM 完全独立,两者不要混着看。按供电域分组之后,每个域里有哪些器件、哪些去耦电容、哪些使能信号,一目了然。
这样切的好处很直接:上电异常有九成出在供电域或者复位域,而不是逻辑本身。拿着一份按域整理的清单,你只需要在几个测试点上量电压、看使能,比对着几十页原理图逐页翻找快得多。框图那一页也别跳过,它标的是模块之间的数据流向——哪条走 FPGA fabric,哪条走 HPS 硬核,哪条走片内互联,后面写约束、划分工程时全靠这张图定位。
2.2 时钟和复位先整理成一张表
手册的 3.5 节讲时钟电路,3.4 节讲复位。这两块内容看起来零碎,但一旦出问题就是全局性的,所以我的习惯是先整理成表,再动其他。
| 信号类别 | 来源 | 作用域 | 常见误用 |
|---|---|---|---|
| 50MHz 时钟源(4 路) | 板载时钟发生器 | FPGA fabric、PLL 参考 | 直接当全局时钟用却不加 PLL 约束 |
| HPS 冷复位 | 板上复位按键 | HPS 与 DDR3 控制器 | 与热复位混用,导致 DDR 重新训练 |
| HPS 热复位 | 板上复位按键 | 仅 HPS 逻辑 | 以为能复位 FPGA 侧逻辑 |
| FPGA 配置复位 | 配置完成后释放 | FPGA 用户逻辑 | 用户逻辑没等配置完成就开始驱动 IO |
把这张表列出来,调试时就能快速排除一大类"看起来像逻辑 bug、实际上是复位时序问题"的现象。热复位不会重新训练 DDR3,冷复位会;这个差别在排查"偶发死机"时非常关键。
2.3 从网络表生成 Quartus 引脚约束
原理图读完,下一步是把引脚落到工程里。手工一个个抄进 .qsf 是最容易出错的一步,尤其是 40 针扩展口和数码管段选这种成组的信号。常见做法是从原理图工具导出网络表或引脚表 CSV,用脚本批量生成约束。下面这段脚本处理的就是这件事:
import csv import re # 输入 CSV 约定三列:net_name(网络标号), fpga_pin(器件引脚号), io_std(电平标准) # 输出:可直接 source 进工程的 .tcl 约束片段 def gen_constraints(csv_path, tcl_path, prefix="", default_io="3.3-V LVTTL"): rows = [] with open(csv_path, newline="", encoding="utf-8") as f: reader = csv.DictReader(f) for r in reader: net = r["net_name"].strip() pin = r["fpga_pin"].strip().upper() # 只接受形如 PIN_AB12 的合法引脚,过滤掉电源、地、NC 网络 if not re.fullmatch(r"PIN_[A-Z]{1,2}\d{1,2}", pin): continue if net.upper().startswith(("GND", "VCC", "NC")): continue rows.append((net, pin, r.get("io_std") or default_io)) with open(tcl_path, "w", encoding="utf-8") as out: out.write("# 由原理图网络表自动生成,勿手改\n") for net, pin, io in rows: port = f"{prefix}{net}" out.write(f'set_location_assignment {pin} -to {port}\n') out.write(f'set_instance_assignment -name IO_STANDARD "{io}" -to {port}\n') print(f"生成 {len(rows)} 条约束 -> {tcl_path}") if __name__ == "__main__": # prefix 用于给端口加统一前缀,避免和 IP 核端口重名 gen_constraints("board_pinout.csv", "board_pinout.tcl", prefix="")脚本的核心逻辑就三步:读表、过滤、按 Quartus 的 Tcl 语法输出。过滤那一步最重要——原理图导出的表里一定混着电源、地、未连接网络,如果不过滤,生成的约束会报一堆找不到端口的错误。prefix参数是给端口加前缀用的,当工程里例化了多个 IP 核时,顶层端口名往往和网络标号对不上,加个前缀能省掉大量重命名工作。default_io兜底成 3.3V LVTTL,遇到 HPS 侧或者特殊 Bank 需要手动改成对应标准,改的时候直接改 CSV 那一列,别去改生成的 tcl,否则下次重新生成就丢了。
生成之后在 Quartus 的 Tcl Console 里source board_pinout.tcl,再打开 Pin Planner 核对一遍。我一般会随机抽五六个信号,对着原理图确认引脚号,尤其是 40 针扩展口的两排针脚,顺序写反是高频翻车点。
2.4 40 针扩展口的两个物理约束
手册 3.6.3 节写得很清楚,两个 40 针扩展头带二极管保护。这意味着两件事:第一,外部器件的电平不要超过 FPGA IO Bank 的供电电压,串电阻或者加电平转换是常规操作;第二,二极管保护会带来压降和一定的结电容,跑高速并行总线时信号质量会打折,接摄像头、并口屏这类设备要留裕量。
另一个容易被忽略的是驱动能力。40 针口直接驱动 LED 或者继电器模块,短期能亮,长期 IO 发热、边沿变缓,最后表现为"偶尔采错"。稳妥的做法是外部加一级三极管或者缓冲器,FPGA 只出逻辑电平。这些内容手册不会专门写一節提醒你,但它把"二极管保护"这四个字放进特性列表里,本身就是提示。
3. FPGA 配置链路:JTAG 下载、SOF 转 JIC 与 EPCS128 固化的完整路径
3.1 三种配置模式与 MSEL 的关系
这块板子的 FPGA 配置有三条路:JTAG 直接下载到 SRAM、通过板载 USB-Blaster II 走 JTAG、以及从上电就自动加载的串行配置器件 EPCS128。手册 3.1 节专门讲配置模式的设置,3.2 节讲 Cyclone V SoC FPGA 的配置过程。MSEL 拨码决定上电后从哪儿取配置数据,JTAG 模式只影响调试阶段,不影响上电行为。
实际项目里最常见的误解是:以为 JTAG 下载成功就代表固化成功。两者完全无关。JTAG 把比特流写进 FPGA 的易失配置存储器,断电即失效;固化是把比特流写进 EPCS128,上电由配置控制器读出来灌进 FPGA。调试阶段用 JTAG 图快,交付阶段必须固化,这是两条独立的链路。
还有一点:SoC 器件的配置顺序和纯 FPGA 不同,HPS 部分要先于 FPGA fabric 完成初始化。手册在讲配置流程时按顺序列了步骤,跳步的后果通常是 FPGA 侧看着正常、HPS 侧起不来,或者反过来。
3.2 JTAG 下载:命令行比 GUI 更适合反复迭代
图形界面点几下就能下载,但一天改二十版的时候,命令行才是效率来源。Quartus 自带的编程工具支持直接跑脚本:
# 1) 确认板卡上电、USB-Blaster II 被识别 quartus_pgm -l # 2) 通过 JTAG 把 sof 下载进 FPGA(易失,断电丢失) quartus_pgm -c "USB-Blaster II" -m jtag -o "p;output_files/top.sof" # 3) 需要连 HPS 一起调试时,先加载预加载器再下载 sof # 具体文件依工程而定,顺序不能反 quartus_pgm -c "USB-Blaster II" -m jtag -o "p;output_files/preloader.sof"-l是列出当前识别到的下载器,板子没上电或者 USB 线只供电不传数据时,这一步就会返回空列表,别急着怀疑驱动。-c指定下载器名字,装了多个版本工具链的机器上,名字可能带后缀,按-l的输出照抄。-o后面的格式是操作;文件路径,p表示 program。
下载完不要立刻下结论。看板子上的状态元素:配置完成指示灯、用户 LED 的初始状态。手册 3.3 节列了板上所有状态元素,把这几个灯的含义记住,比看 Quartus 的进度条靠谱。
3.3 SOF 转 JIC 再写进 EPCS128
固化分两步:先把 .sof 转成 .jic,再用编程工具写进 EPCS128。手册第 8 章把这两步拆成 8.2 和 8.3 两节,中间还夹了一节 8.4 擦除器件,顺序不要搞混。
# 第一步:SOF -> JIC,-c 指定压缩,-o 指定输出 quartus_cpf -c -o bitstream_compression=on \ output_files/top.sof output_files/top.jic # 第二步:把 JIC 写进 EPCS128 quartus_pgm -c "USB-Blaster II" -m jtag \ -o "epv;output_files/top.jic" # 需要重烧前先擦除,避免残留配置干扰 quartus_pgm -c "USB-Blaster II" -m jtag -o "ebv;EPCS128"-c在 cpf 命令里是"转换"的意思,和 pgm 里的-c指定下载器不是一回事,这一点坑过不少人。-o bitstream_compression=on打开比特流压缩,EPCS128 容量有限,工程大时不压缩可能塞不下。第二步的epv是 EPCS 器件编程的操作码,ebv是擦除,两个操作码别写反。
手册 8.5 节还提到 Nios II 从 EPCS 启动的配置要求,如果工程里带软核,除了比特流本身,启动代码的复位向量也要指向 EPCS 映射的地址区间。这一步漏了的表现是:固化成功、配置指示灯亮、CPU 不跑。
3.4 固化之后的验证顺序
写完之后别直接拔电。我的验证顺序是三段:先按热复位,看用户逻辑是否重新初始化;再断电冷启动,看配置指示灯是否在预期时间内点亮;最后接串口,看有没有启动打印。三段里任何一段异常,问题范围就能缩到很小的区间——热复位异常多半是用户逻辑的复位设计问题,冷启动异常多半是 MSEL 或者 JIC 本身的问题,串口无打印则是 HPS 侧的事。
手册第 8 章末尾讲了从 EPCS 启动的注意事项,配合原理图上 EPCS 那几页的连线一起看,能确认配置时钟、片选、数据线的走向。有些板子在这些线上串了电阻,用示波器量波形时探头位置要选在器件侧而不是连接器侧。
4. HPS 侧的 ARM 怎么碰 FPGA:地址映射、/dev/mem 与设备树三处改动
4.1 先把资源分工搞清楚
这块板子最容易让人迷糊的地方,是同一类外设分属两个世界。手册第 3 章把外设分成 3.6「连接到 FPGA 的外设」和 3.7「连接到 HPS 的外设」两大块,这个划分不是排版习惯,是物理事实:
| 资源 | 归属 | 访问方式 |
|---|---|---|
| 64MB SDRAM | FPGA 侧 | Verilog/Nios II 直接寻址 |
| 1GB DDR3 | HPS 侧 | Linux 内存管理,用户态不可直接碰 |
| 按键、拨码、LED、数码管 | FPGA 侧 | 用户逻辑读写 |
| 千兆以太网、USB Host、SD 卡 | HPS 侧 | Linux 驱动 |
| 加速度计 | HPS 侧(I2C + 中断) | I2C 子系统 |
| 音频 CODEC、VGA、TV 解码、PS/2、红外 | FPGA 侧 | 需自己写控制器 |
| ADC(8 通道 12 位 500KSPS) | FPGA 侧(SPI) | SPI 控制器 + 状态机 |
看这张表就知道为什么新手容易走弯路:想在 Linux 里点个 LED,那盏 LED 却挂在 FPGA 侧;想读加速度计,代码却写在了 Verilog 里。手册把两侧外设分章讲,就是在暗示这条边界。
4.2 用 /dev/mem 打通轻量桥
HPS 要访问 FPGA 侧外设,走的是片内互联上的桥接。常见做法是在 Linux 下打开/dev/mem,把桥接窗口映射到用户态,然后按寄存器偏移读写。下面这段代码演示点灯的基本骨架:
#include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <stdint.h> /* 轻量桥基址与窗口大小以设备树中的 h2f_lw 节点为准, 不同工程可能不同,移植前务必核对 */ #define LW_BRIDGE_BASE 0xFF200000 #define LW_BRIDGE_SPAN 0x00200000 #define LED_PIO_OFFSET 0x00000010 /* 相对基址的偏移,按自己的地址映射改 */ int main(void) { int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("open /dev/mem"); 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; } volatile uint32_t *led = (uint32_t *)((char *)base + LED_PIO_OFFSET); for (int i = 0; i < 10; i++) { *led = (1u << i); /* 逐个点亮,具体位宽看 PIO 配置 */ usleep(200000); } *led = 0; munmap(base, LW_BRIDGE_SPAN); close(fd); return 0; }几个参数必须解释清楚。LW_BRIDGE_BASE是桥接窗口的物理基址,LW_BRIDGE_SPAN是映射长度,这两个值不能凭记忆写,要从设备树里对应的节点读出来。LED_PIO_OFFSET是 PIO 在窗口里的偏移,由 Platform Designer 里分配的地址决定,改一次硬件就要同步改一次。O_SYNC不能省,否则写操作可能停留在缓存里,表现为"程序跑完了灯没动"。volatile也不能省,编译器优化会把连续写同一地址的循环优化掉。
这段代码跑起来的前提是:FPGA 侧已经有一个配置好的 PIO,且地址映射和软件里写的一致。硬件软件两边对不上,最典型的现象就是段错误或者写进去毫无反应。
4.3 设备树和启动参数里要动的三处
只改应用层不够,系统层面还有三处要同步。第一处是设备树里 HPS 到 FPGA 的桥接节点,要确保状态是启用,窗口地址和长度与硬件一致。第二处是 UART 的引脚复用,串口不通时先查这里,而不是先怀疑线。第三处是启动参数里的控制台设备名和波特率,板载 UART 转 USB 的默认波特率是固定的,改了之后串口终端要同步改。
/* 片段示意:节点名与属性以实际工程的 dts 为准 */ &hps_0_h2f_lw_bridge { status = "okay"; ranges; /* 地址转换表,缺失会导致映射失败 */ }; &uart0 { status = "okay"; };设备树改完必须重新编译并替换,只把源文件丢进去不生效。修改之后最省事的验证方式是启动后cat /proc/device-tree/...看节点是否存在,比反复重启猜要快。手册第 6 章和第 7 章的示例工程里,HPS 访问 LED、数码管、加速度计、I2C 多路复用器的代码都可以直接对照,把地址和偏移抄过来改成自己的即可。
5. 避坑与排查:板子不启动、JTAG 掉线、固化失效的五条记录
5.1 上电后数码管全亮、程序不跑
现象:给板子上电,六个数码管全部点亮,风扇转,但按键没有任何响应,串口没有输出。
原因:数码管全亮通常是上电默认态,说明 FPGA 根本没被配置,或者配置了但用户逻辑的复位没有释放。前者的可能性远高于后者。检查 MSEL 拨码是否在正确的配置模式,再确认 EPCS 里到底有没有内容。
解决:先用 JTAG 直接下载 .sof,如果下载后功能正常,问题就锁定在固化链路上;如果 JTAG 下载后依然全亮,去看用户逻辑的复位信号是不是被某个未初始化的状态卡住了。手册 3.4 节列了板上所有复位元素,逐个对照。
5.2 JTAG 报扫描不到器件链
现象:编程工具报无法扫描器件链,或者扫描到的器件 ID 和预期不符。
原因:三种情况最常见——USB 线只供电不传数据、板子没上电、以及上一次下载异常退出导致下载器状态卡死。还有一种隐蔽情况:工程里选错了器件型号,扫描能过但下载校验失败。
解决:先换一根确认能传数据的线,再确认 12V 电源指示灯亮,然后拔插下载器重新枚举。器件型号在工程设置里核对一遍,5CSEMA5F31 这个型号的后缀(速度等级、温度等级)一个字符都不能错。
5.3 JIC 写入成功,冷启动仍然无效
现象:编程工具提示写入成功、校验通过,断电再上电,FPGA 依然不配置。
原因:多半是 MSEL 没切到从配置器件启动的模式,或者转换 JIC 时选错了器件型号。还有一种情况是 EPCS 里残留了旧内容,新写入的数据没有覆盖到正确的起始地址。
解决:先擦除 EPCS 再重写,然后确认 MSEL 拨码位置。手册 8.4 节专门讲了擦除操作,这一步不要省。如果擦除后重写仍然无效,把 JIC 转换命令的器件参数重新核对一遍。
5.4 SD 卡做了镜像,串口一片空白
现象:卡按流程烧好插上去,上电后串口终端没有任何输出。
原因:按出现频率排——串口线接的是 FPGA 侧那个口而不是 HPS 侧、终端波特率不对、卡的分区格式不被引导程序识别、以及设备树里 UART 节点没启用。
解决:先确认物理接口,HPS 的 UART 走的是 Mini-B 那个 USB 口,和 FPGA 编程用的 Type-B 是两个完全不同的口,插错了自然没输出。然后核对终端参数。最后才怀疑镜像本身,用读卡器在电脑上确认分区结构。
5.5 加速度计读不到数据
现象:I2C 总线上扫描不到设备,或者能扫到但读出来的值恒定不变。
原因:这条总线上挂了一个 I2C 多路复用器,通道没选对就访问不到后面的从设备。手册 3.6.5 节和 6.4 节的示例都提到这个结构。另一个原因是中断引脚没配置,轮询能读到数据但中断方式收不到。
解决:先写多路复用器的通道选择寄存器,把目标通道打开,再去访问从设备地址。用i2cdetect这类工具逐通道扫一遍,确认设备挂在哪个通道上,比盲猜快得多。
6. 用 System Builder 打底:一条从原理图到可运行工程的自检链
手册第 4 章讲 System Builder,很多人把这一章当"自动生成工具说明"跳过,其实它最有价值的用法是当自检基准。我的做法是:先用 System Builder 生成一个只含时钟、复位、基本 PIO 的最小工程,跑通下载和固化,确认板子、线、工具链、MSEL 全部正常,再往上叠自己的逻辑。这样后面出的任何问题,都能确定不是环境问题。
具体流程是四步。第一步,在 System Builder 里按板卡型号生成骨架,它会自动带上引脚约束和基本 IP。第二步,把生成的 .qsf 和自己从原理图导出的约束做一次比对,下面这段脚本就是干这个的:
import re def load_pins(path): pins = {} for line in open(path, encoding="utf-8"): m = re.search(r'set_location_assignment\s+(PIN_\w+)\s+-to\s+(\S+)', line) if m: pins[m.group(2)] = m.group(1) # 端口名 -> 引脚 return pins auto = load_pins("system_builder/top.qsf") manual = load_pins("board_pinout.tcl") for port, pin in manual.items(): if port in auto and auto[port] != pin: print(f"冲突: {port} 自动={auto[port]} 原理图={pin}") print(f"自动 {len(auto)} 条, 原理图 {len(manual)} 条, 冲突已列出")比对结果里只关心同名端口的不同引脚,两边都有的端口如果引脚一致就说明约束对上了,不一致就必须人工裁决——以原理图为准还是以工具生成为准,取决于你手上的板卡版本。第三步,下载最小工程,看状态元素是否符合预期。第四步,固化并冷启动一次,走完整链路。
这套流程我在几个项目里都用过,最省时间的地方在于:它把"板子坏没坏"和"我的逻辑对不对"这两个问题彻底分开了。有一次连着两天怀疑自己写的时序有问题,最后用最小工程一跑,发现是拨码开关被人碰过,MSEL 位置变了,血泪经验。从那以后我每次拿到一块不熟悉的板子,都强制先走一遍最小工程、比对约束、固化冷启动这三步,再开始写第一行业务代码。手册第 4 章的 Design Flow 图值得打印出来贴在显示器边上,配合第 5、6、7 章的 FPGA 示例、HPS 示例和双端联合示例一起看,基本能把这块板子的边界摸清。希望帮到你。
本文还有配套的精品资源,点击获取