☰
PYNQ-Z2 入门实战:从镜像烧写到 Jupyter 例程的完整指南
2026/10/29 3:03:14 网站建设 项目流程

简介:《PYNQ-Z2入门指导手册_v2.01》面向希望借助Python快速上手Xilinx Zynq APSoC开发板的嵌入式初学者与教学人员,重点解决不熟悉可编程逻辑设计却想调用FPGA硬件加速能力的问题。手册从PYNQ开源框架与Jupyter Notebook交互环境讲起,依次覆盖软硬件准备、板卡接口与固件配置、浏览器在线编程连接,并给出按键控制LED、录音及音频处理、动态实时面部识别三个由浅入深的实践例程,帮助读者建立从环境搭建到硬件I/O、音频与图像处理的完整认知。资源包共1个PDF文件,大小约1.68MB,单文件结构便于随身查阅与打印。目前已有2698人学习,适合作为课程实验、自学入门与项目原型验证的参考起点。

1. 拿到 PYNQ-Z2 别急着点灯:这份手册真正省掉的是哪几步

板子到手,很多人第一反应是插电、连网线、打开浏览器,然后在http://pynq:9090这个地址上卡半小时——不是打不开,就是打开了不知道下一步干什么。PYNQ-Z2 入门指导手册 v2.01 解决的正是这个断点:它把「烧镜像 → 设跳线 → 连 Jupyter → 跑例程」这条链路按顺序铺开,中间不留需要你自己猜的环节。PYNQ 框架的核心价值在于,它把 Zynq APSoC 的可编程逻辑封装成硬件库(Overlay),你用 Python 调base.leds、base.audio、base.video就像调普通软件库,不需要先写 Verilog 才能让板子动起来。这份手册适合两类人:一是刚接触 Zynq 平台、想用 Python 快速验证硬件功能的嵌入式开发者;二是手上有一块 PYNQ-Z2、需要一套能照着跑通的起步流程,而不是从官方 Wiki 里自己拼信息的人。

2. 从镜像到 Jupyter:启动链路里每一步在做什么

2.1 镜像烧写与启动介质的选择逻辑

PYNQ-Z2 的启动介质是 Micro SD 卡,板载 QSPI Flash 里没有预置可用的 PYNQ 镜像,所以第一步必须把镜像写到卡上。手册要求最少 8GB 容量,这个数字不是随便定的——PYNQ 镜像解压后接近 4GB,加上 Jupyter Notebook 示例、Overlay 的.bit文件和后续你自己存的音频、视频数据,8GB 是能正常周转的下限。烧写工具分两路:Windows 下用 win32DiskImager,选好 Image File 和 Device 后点 Write;Linux/macOS 下用dd。这里有个容易被忽略的点:dd的of参数必须指向整块卡设备(如/dev/sdb),不是分区(如/dev/sdb1),写错分区会导致板子起不来。

# Linux/macOS 下烧写 PYNQ 镜像到 SD 卡 # 先用 lsblk 确认卡设备名,假设为 /dev/sdb sudo dd if=pynq_z2_v2.01.img of=/dev/sdb bs=4M status=progress conv=fsync

bs=4M是块大小,影响写入速度,4M 在大多数卡上比较均衡;conv=fsync保证数据真正落盘再返回,避免拔卡时缓存没写完导致镜像损坏。烧完后把卡插入板子底部的 Micro SD 插槽,这一步才算完。

2.2 跳线与供电:两个跳线帽决定板子能不能起来

硬件设置里有两组跳线必须确认。第一组是 Boot 跳线,要拨到 SD 位置,否则板子会去找别的启动源,表现就是上电后 LED 不按预期闪、串口无输出。第二组是电源跳线,拨到 USB 位置时由 Micro USB 供电,拨到 REG 位置时从外部 12V 电源调节器取电。用 USB 供电做入门实验够用,但如果你后面要接 HDMI 显示器加 USB 摄像头跑面部识别,USB 口的电流余量会比较紧,常见做法是换 REG 加 12V 适配器。

上电后的启动判断有个明确信号:大约一分钟后,两个蓝色 LED 和四个红色 LED 同时闪动,随后蓝色灭、四个红色常亮,说明系统启动完毕。如果超过两分钟还是只有电源灯亮,先查 SD 卡是否插到位、镜像是否烧写完整,再查 Boot 跳线。

2.3 网络连接的两种拓扑与对应地址

手册给了两条路:接路由器/交换机走 DHCP,或者直连电脑走静态 IP。走路由器时,板子自动获取地址,浏览器访问http://pynq:9090,用户名和密码都是xilinx。这里依赖的是主机名解析,第一次连接时电脑要花几秒解析pynq这个主机名,如果解析失败,改用板子的实际 IP 访问。直连电脑时,需要先给电脑网卡配一个静态 IP,然后访问http://192.168.2.99:9090,这是板子默认的静态地址。

# 直连模式下,在电脑上确认网卡配置(以 Linux 为例) ip addr add 192.168.2.100/24 dev eth0 ip link set eth0 up # 然后浏览器访问 http://192.168.2.99:9090

电脑侧地址只要和192.168.2.99同网段且不冲突即可,192.168.2.100只是示例。直连模式下板子没有外网访问能力,装不了新软件包,这一点手册写得很直白,但实际用的时候还是有人在这里翻车——想pip install一个包,结果一直超时,以为是镜像问题,其实是网络拓扑选错了。

2.4 主机名修改与 Samba 文件通道

默认主机名pynq在同一网络里有多块板子时会冲突,手册建议尽早改。在 Jupyter 主页开New >> Terminal,执行sudo /home/xilinx/scripts/hostname.sh NEW_HOST_NAME,然后sudo shutdown -r now重启生效。改完之后访问地址也要跟着换成新主机名。

文件传输走 Samba,Windows 下在资源管理器输入\\pynq\xilinx(DHCP)或\\192.168.2.99\xilinx(静态),Linux 下用smb://pynq/xilinx,凭据同样是xilinx/xilinx。这个通道在传音频文件、放自己的.bitOverlay 时很实用,比在 Jupyter 里用上传按钮一个个点要快。

3. 三个例程拆开看:从 GPIO 到音频再到视频的递进

3.1 按键控制 LED:Overlay 加载与 GPIO 读写的骨架

实验一打开的是base>board下的board_btn_leds.ipynb。核心代码先加载BaseOverlay("base.bit"),这个.bit文件就是硬件库,加载后base.leds、base.buttons、base.rgbleds这些对象才可用。按键 0 切 RGB LED 颜色,按键 1 和 2 分别做两个方向的流水灯,按键 3 退出循环。

from time import sleep from pynq.overlays.base import BaseOverlay base = BaseOverlay("base.bit") Delay1 = 0.3 # RGB 颜色切换的间隔 Delay2 = 0.1 # 流水灯的步进间隔 color = 0 rgbled_position = [4, 5] # 板载两颗 RGB LED 的索引 for led in base.leds: led.on() while (base.buttons[3].read() == 0): if (base.buttons[0].read() == 1): color = (color + 1) % 8 for led in rgbled_position: base.rgbleds[led].write(color) sleep(Delay1) elif (base.buttons[1].read() == 1): for led in base.leds: led.off() sleep(Delay2) for led in base.leds: led.toggle() sleep(Delay2) elif (base.buttons[2].read() == 1): for led in reversed(base.leds): led.off() sleep(Delay2) for led in reversed(base.leds): led.toggle() sleep(Delay2) print('End of this demo ...') for led in base.leds: led.off() for led in rgbled_position: base.rgbleds[led].off()

base.buttons[n].read()返回 0 或 1,是轮询式读取,不是中断。base.rgbleds[n].write(color)的color取值 0 到 7,对应 8 种颜色组合,% 8保证循环。Delay1和Delay2这两个参数直接决定手感:Delay1太小颜色跳太快看不清,太大按键响应显得迟钝;Delay2影响流水灯速度,0.1 秒是比较舒服的默认值。这段代码的价值不在功能本身,而在于它展示了 PYNQ 编程的基本骨架——加载 Overlay、拿外设对象、读写、清理。后面两个实验都是这个骨架的变体。

3.2 录音与音频处理:bypass、record、save 的时序关系

实验二在base>audio下,audio_playback.ipynb。音频对象从base.audio拿,关键调用是select_line_in()、bypass(seconds=5)、record(5)、save()、load()、play()。这里有个时序上的坑:bypass(seconds=5)是把输入直通到输出 5 秒,用来确认线路通不通;record(5)才是录 5 秒到缓冲区;save()把缓冲区写文件。手册示例里save那行是注释掉的,如果你不取消注释,后面load一个不存在的文件就会报错。

from pynq.overlays.base import BaseOverlay base = BaseOverlay("base.bit") pAudio = base.audio pAudio.select_line_in() # 选择 Line_in 作为输入 pAudio.bypass(seconds=5) # 直通 5 秒,确认输入输出链路正常 pAudio.record(5) # 录制 5 秒到缓冲区 pAudio.save("recording_1.wav") # 落盘,手册里这行默认注释,需要手动打开 pAudio.load("/home/xilinx/jupyter_notebooks/base/audio/recording_0.wav") pAudio.play()

后面的 PCM 绘图部分用wave读文件头拿num_frames、num_channels、sample_rate、sample_width,再用numpy把字节流重组成int32帧,最后用matplotlib画时域、频域和频谱图。sample_width决定字节重组的形状,temp_buffer那个三维数组的构造是这段代码里最绕的地方,改采样位深时这里要跟着调。频域图用fft取前半段,np.linspace(0.0, sample_rate/2, len(yf))生成频率轴,这是标准的单边谱画法。

3.3 实时面部识别:摄像头、HDMI 输出与 Haar 级联的配合

实验三需要 USB 摄像头接 USB HOST,HDMI OUT 接显示器,打开base>video下的opencv_face_detect_webcam.ipynb。流程是:初始化 HDMI 输出为 640x480、24 位、PIXEL_BGR,用cv2.VideoCapture(0)打开摄像头并设分辨率,然后在while 1里读帧、写 HDMI、跑 Haar 检测、画框。

from pynq.overlays.base import BaseOverlay from pynq.lib.video import * import cv2 import numpy as np base = BaseOverlay("base.bit") hdmi_out = base.video.hdmi_out Mode = VideoMode(640, 480, 24) hdmi_out.configure(Mode, PIXEL_BGR) hdmi_out.start() videoIn = cv2.VideoCapture(0) videoIn.set(cv2.CAP_PROP_FRAME_WIDTH, 640) videoIn.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) print("Capture device is open: " + str(videoIn.isOpened())) face_cascade = cv2.CascadeClassifier( '/home/xilinx/jupyter_notebooks/base/video/data/haarcascade_frontalface_default.xml') eye_cascade = cv2.CascadeClassifier( '/home/xilinx/jupyter_notebooks/base/video/data/haarcascade_eye.xml') while 1: ret, frame_vga = videoIn.read() if not ret: raise RuntimeError("Failed to read from camera.") gray = cv2.cvtColor(frame_vga, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.3, 5) for (x, y, w, h) in faces: cv2.rectangle(frame_vga, (x, y), (x + w, y + h), (255, 0, 0), 2) roi_gray = gray[y:y + h, x:x + w] roi_color = frame_vga[y:y + h, x:x + w] eyes = eye_cascade.detectMultiScale(roi_gray) for (ex, ey, ew, eh) in eyes: cv2.rectangle(roi_color, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 2) outframe = hdmi_out.newframe() outframe[0:480, 0:640, :] = frame_vga[0:480, 0:640, :] hdmi_out.writeframe(outframe)

detectMultiScale(gray, 1.3, 5)里1.3是每次缩放的步长,5是邻域阈值,调大5会减少误检但可能漏脸,调小则相反。VideoMode(640, 480, 24)的第三个参数是位深,和PIXEL_BGR配套。这段代码在 PYNQ-Z2 的 ARM 核上跑 Haar 检测,帧率不会很高,属于能演示但不算流畅的水平,想提帧率得把检测部分下沉到 PL 侧做硬件加速,那就超出这份入门手册的范围了。

4. 避坑与排查:五个真实会卡住你的地方

4.1 浏览器打不开 pynq:9090

现象是地址栏一直转圈或提示无法访问。原因通常是主机名解析失败,或者电脑和板子不在同一网段。解决:先用串口确认板子拿到了什么 IP,ifconfig看一眼;能 ping 通 IP 但主机名不通,就直接用 IP 访问;直连模式下检查电脑网卡是否配了192.168.2.x段地址。

4.2 上电后 LED 不按预期闪

现象是只有电源灯亮,蓝红 LED 不闪。原因多半是 Boot 跳线没拨到 SD,或者 SD 卡镜像烧写不完整。解决:断电重查跳线位置,把卡拔下来在电脑上重新烧一遍,烧写时确认of指向整卡设备。如果换卡能起,说明原卡有坏块或烧写中断。

4.3 音频例程报文件不存在

现象是pAudio.load(...)抛异常。原因是手册示例里save那行是注释状态,recording_1.wav根本没生成,而load加载的是另一个路径的recording_0.wav。解决:先取消save的注释跑一遍生成文件,或者确认load的路径下确实有预置的 wav 文件,路径拼写逐字符核对。

4.4 摄像头读帧失败

现象是videoIn.isOpened()返回 False,或循环里ret为假。原因是 USB 摄像头没被识别,或者供电不足。解决:换一个 USB 口,尽量用带外部供电的 Hub;在终端ls /dev/video*确认设备节点存在;HDMI 输出初始化要在摄像头打开之前完成,顺序反了也可能出问题。

4.5 改了主机名之后连不上

现象是改完 hostname 重启,原地址访问不了。原因是访问地址没跟着换。解决:用新主机名访问,或者用串口ifconfig查当前 IP 直连;如果新主机名也解析不了,退回 IP 访问,主机名解析依赖网络环境,不是所有路由器都及时更新。

5. 把例程改成自己的验证脚本:一个可复用的检查习惯

跑通三个例程只是起点,真正省时间的是把「板子状态自检」固化成一段脚本,每次换卡、换网络、换供电方式之后先跑一遍,确认 Overlay 能加载、外设对象能拿到、网络和文件通道正常,再去碰具体业务逻辑。我一般会在 Jupyter 里新建一个selfcheck.ipynb,内容大致如下:

from pynq.overlays.base import BaseOverlay import os # 1. 加载 Overlay,确认 .bit 可读 base = BaseOverlay("base.bit") print("Overlay loaded:", base) # 2. 外设对象可达性检查 print("LEDs:", len(base.leds)) print("Buttons:", len(base.buttons)) print("RGB LEDs:", len(base.rgbleds)) print("Audio:", base.audio is not None) print("Video:", base.video is not None) # 3. 文件通道检查:确认示例目录可读 sample_dir = "/home/xilinx/jupyter_notebooks/base" print("Sample dir exists:", os.path.isdir(sample_dir)) print("Audio sample:", os.path.exists( os.path.join(sample_dir, "audio/recording_0.wav")))

这段脚本不碰具体硬件动作,只做可达性判断,跑完看输出就能定位问题出在 Overlay 层、外设层还是文件层。参数上没什么可调的,重点是把它当成固定流程:每次环境变动后先跑,比出了问题再逐个例程试要快得多。

再往上一层,如果你要加载自己综合出来的 Overlay,把BaseOverlay("base.bit")换成对应的.bit路径,然后确认新 Overlay 暴露的 IP 名称和驱动是否和你的 Python 侧调用对得上。常见做法是先用base.ip_dict打印一遍 IP 清单,看名字和地址,再决定怎么调。这一步没有捷径,名字对不上就是AttributeError,翻车过一次之后就会养成先打印ip_dict的习惯。

几个实操边界值得提前知道:PYNQ-Z2 的 ARM 核跑 Python 做图像处理,帧率和分辨率是硬约束,640x480 的 Haar 检测能演示,再往上就吃力;音频例程的bypass和record是阻塞式调用,seconds参数设大了 Notebook 会一直显示[*],别以为卡死了;HDMI 输出配置的VideoMode要和显示器的支持模式匹配,配错了黑屏但代码不报错,这种静默失败最费时间。

从那以后我每次拿到一块新板子或者换一张 SD 卡,都强制先跑一遍自检脚本再动业务代码,省下来的排查时间远比写脚本的几分钟多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询