利利普杯C题硬件
电赛超声相控阵系统:FPGA 引脚分配与外设连接设计
在做超声阵列定向音频传输系统时,很多人一开始会把注意力放在波束形成算法、40 kHz PWM、上位机控制界面或者声压测试结果上。但真正调试硬件时,最容易出问题的往往是一个看起来很“基础”的部分:引脚分配。
本项目使用 Mizar Z7 作为主控板,核心是 Zynq-7000 SoC。FPGA 侧负责产生 25 路相位和幅度可控的 40 kHz PWM,再通过 74HC595D 串并转换扩展输出,最后由 UCC27624DR 驱动 5×5 超声换能器阵列。系统还预留了接收板 ADC 接口,用于麦克风采样和后续闭环控制。
这篇文章主要记录本项目中 FPGA 引脚分配、74HC595 扩展、阵列通道映射、ADC 接口和调试经验,适合作为同类电赛或 FPGA 外设扩展项目的参考。
1. 为什么不能直接用 25 个 FPGA IO?
5×5 超声阵列共有 25 个换能器,如果每个阵元都使用 FPGA 一个 IO 直接输出 PWM,逻辑上最简单:
FPGA_CH1 -> 阵元 1
FPGA_CH2 -> 阵元 2
...
FPGA_CH25 -> 阵元 25
但这样做有几个问题:
- 占用 FPGA IO 数量较多;
- 连接线数量多,布线和焊接复杂;
- 阵列板与主控板之间接口臃肿;
- 后续增加通道数量时扩展性差。
因此本项目采用 74HC595D 串并转换扩展 IO 的方式。FPGA 只需要输出三根控制线:
DATA 串行数据
CLK 移位时钟
LATCH 锁存时钟
通过 4 片 74HC595D 级联,可以扩展出 32 路并行输出,其中使用前 25 路控制 5×5 阵列,其余通道预留。
这种结构虽然增加了移位逻辑,但大大减少了 FPGA 与驱动板之间的连接线数量,非常适合本项目这种“多路同步、低速更新、高速载波输出”的场景。
2. 系统引脚整体结构
整个系统的引脚可以分为四类:
┌────────────────────────────┐
│ Mizar Z7 FPGA │
│ Zynq PL 逻辑 │
└─────────────┬──────────────┘
│
├── 74HC595 控制接口
│ ├── DATA
│ ├── CLK
│ └── LATCH
│
├── ADC 接收接口
│ ├── ADC_CS
│ ├── ADC_CLK
│ └── ADC_DATA
│
├── 调试接口
│ ├── carrier/debug
│ └── LED/status
│
└── UART/BLE 通信
├── RX
└── TX
其中最关键的是驱动板接口。实际系统中,FPGA 并不是直接输出 25 路 PWM 到换能器,而是先在 FPGA 内部生成 25 路通道状态,然后按顺序打包为 32 bit,并通过 74HC595D 移位输出。
3. Mizar Z7 GPIO 选择原则
Mizar Z7 提供两组 2×20 的 40Pin GPIO 扩展口,适合外接自制驱动板。使用时需要注意下面几点。
3.1 优先选择 3.3V IO
74HC595D、ADC 接口和常见串口模块都可以工作在 3.3V 逻辑电平,因此 FPGA 侧 IO 尽量选择 3.3V 电平的 GPIO。这样可以减少电平转换芯片,调试也更方便。
如果使用可调电压 Bank,一定要确认当前 Bank 电压是否为 3.3V。否则可能出现以下问题:
- FPGA 输出高电平不足,74HC595 识别不稳定;
- 外设输出电平过高,反向损伤 FPGA IO;
- 仿真正确但实物无法通信。
3.2 控制线尽量放在同一扩展口
驱动板的 DATA、CLK、LATCH 三根线建议放在同一个 GPIO 扩展口附近,原因是:
- 走线更短;
- 接线不容易出错;
- 地线参考更稳定;
- 示波器调试时更方便。
本项目中 74HC595 的控制线可以按如下逻辑规划:
| 信号名 | 方向 | 功能 | 建议说明 |
|---|---|---|---|
| DATA | FPGA → 74HC595 | 串行数据输入 | 每个时钟写入 1 bit |
| CLK | FPGA → 74HC595 | 移位时钟 | 上升沿移位 |
| LATCH | FPGA → 74HC595 | 锁存时钟 | 一帧 32 bit 移完后更新输出 |
| GND | 公共地 | 地参考 | 必须与驱动板共地 |
| FPGA_3V3 | 电源/参考 | 逻辑电平供电或参考 | 视实际驱动板供电方式确定 |
4. 74HC595D 级联输出设计
74HC595 是一个 8 位串入并出移位寄存器。单片只能输出 8 路,4 片级联后可以输出 32 路。
本项目使用 4 片 74HC595D:
FPGA DATA ──> 74HC595 #1 ──> 74HC595 #2 ──> 74HC595 #3 ──> 74HC595 #4
FPGA CLK ──> 四片共用 SRCLK
FPGA LATCH ──> 四片共用 RCLK
输出关系可以理解为:
Q0 ~ Q7 -> 第 1 片 74HC595
Q8 ~ Q15 -> 第 2 片 74HC595
Q16 ~ Q23 -> 第 3 片 74HC595
Q24 ~ Q31 -> 第 4 片 74HC595
其中 Q0 到 Q24 用于 25 个换能器通道,Q25 到 Q31 预留。
5. FPGA 到 74HC595 的时序
74HC595 的控制时序非常简单,但必须保证顺序正确。
一次完整更新流程如下:
1. LATCH 拉低
2. 按顺序输出 32 bit 数据
3. 每输出 1 bit,CLK 产生一个上升沿
4. 32 bit 全部移入后,LATCH 产生一个上升沿
5. 74HC595 并行输出同步更新
伪代码如下:
for (i = 31; i >= 0; i = i - 1) begin
DATA <= shift_data[i];
CLK <= 1'b1;
CLK <= 1'b0;
end
LATCH <= 1'b1;
LATCH <= 1'b0;
实际 FPGA 代码中需要使用状态机,而不是这种软件式写法。推荐状态机分为:
IDLE 空闲
LOAD 装载 32 bit 通道状态
SHIFT 移位输出
LATCH 锁存更新
DONE 完成
这样可以保证 DATA、CLK、LATCH 的时序稳定。
6. 为什么不能随便改变移位顺序?
74HC595 的移位顺序会直接影响通道编号。
例如 FPGA 内部生成的通道状态为:
channel_bits[0] -> CH1
channel_bits[1] -> CH2
...
channel_bits[24] -> CH25
但经过 74HC595 级联后,最先移入的数据可能最终出现在最后一片的 QH,也可能出现在第一片的 QA,具体取决于代码中是先发高位还是先发低位。
因此,一定要做一张明确的映射表:
| FPGA 逻辑通道 | 74HC595 输出位 | 物理阵元 |
|---|---|---|
| CH1 | Q0 | 第 1 行第 1 列 |
| CH2 | Q1 | 第 1 行第 2 列 |
| CH3 | Q2 | 第 1 行第 3 列 |
| … | … | … |
| CH25 | Q24 | 第 5 行第 5 列 |
调试时可以只点亮或输出某一路 PWM,用示波器或简单 LED 检查实际通道是否对应正确。
7. 5×5 阵列的蛇形映射
在 PCB 布线时,为了减少走线交叉,阵列通道往往不会严格按“从左到右、从上到下”的自然顺序排列。本项目采用蛇形映射。
逻辑上可以理解为:
第 1 行:CH01 CH02 CH03 CH04 CH05
第 2 行:CH10 CH09 CH08 CH07 CH06
第 3 行:CH11 CH12 CH13 CH14 CH15
第 4 行:CH20 CH19 CH18 CH17 CH16
第 5 行:CH21 CH22 CH23 CH24 CH25
这样布线更方便,但软件计算波束时必须使用真实物理坐标,而不能直接按照通道号推算位置。
推荐在上位机或 FPGA 中建立一个映射表:
const channelMap = [
[1, 2, 3, 4, 5],
[10, 9, 8, 7, 6],
[11, 12, 13, 14, 15],
[20, 19, 18, 17, 16],
[21, 22, 23, 24, 25]
];
在计算相位时,使用阵元的几何位置;在下发数据时,再根据映射表转换为实际通道号。
8. 通道相位和幅度如何落到引脚上?
上位机计算得到的是每个阵元的相位字节和幅度字节:
phase[25] 0 ~ 255
amplitude[25] 0 ~ 255
FPGA 接收后并不会直接把这些字节输出到引脚,而是先生成每一路 PWM:
phase[i] -> PWM 相位偏移
amplitude[i] -> PWM 高电平宽度
然后在每个 40 kHz 载波周期内,FPGA 判断当前计数器是否落在某一路 PWM 的高电平窗口内。如果是,该通道输出 1;否则输出 0。
25 路通道状态会被打包:
shift_data[0] = pwm_ch1;
shift_data[1] = pwm_ch2;
...
shift_data[24] = pwm_ch25;
shift_data[31:25] = 7'b0;
然后通过 DATA、CLK、LATCH 更新到 74HC595D。
因此,真正连接到外部的 FPGA 引脚只有几根,但 FPGA 内部仍然实现了 25 路独立 PWM。
9. UCC27624DR 直驱换能器的连接说明
驱动板初版在 UCC27624DR 后面预留了 MOSFET 开关级,但在实测过程中发现,对于本项目的 5×5 阵列和测试功率要求,直接使用 UCC27624DR 的 OUTA/OUTB 驱动超声换能器即可满足需求。
最终实物中拆除或旁路了 MOSFET 级,连接关系简化为:
74HC595 输出 -> UCC27624DR 输入
UCC27624 OUT -> 超声换能器通道
这样做有几个优点:
- 少一级开关器件,延迟更小;
- 减少 MOSFET 栅极振铃和寄生影响;
- 焊接和排查更简单;
- 与实际测试样机一致。
当然,这种做法是否适合其他项目,需要根据换能器数量、驱动电压、电流能力和芯片散热情况重新评估。
10. ADC 接收板引脚设计
除了发射端,本项目还预留了接收板,用于麦克风采样和自解调音频验证。接收板主要包括:
麦克风输入
LMV358 放大
半电源偏置
串行 ADC
FPGA 接口
ADC 接口可以抽象为三线 SPI 类接口:
| 信号名 | 方向 | 功能 |
|---|---|---|
| ADC_CS | FPGA → ADC | 片选 |
| ADC_CLK | FPGA → ADC | 采样/移位时钟 |
| ADC_DATA | ADC → FPGA | 串行采样数据 |
部分原理图中还出现 FPGA_CS、FPGA_CLK、FPGA_DATA 等命名,这是为了在接收板和 FPGA 接口之间做信号对应。实际写代码时建议统一命名为:
output adc_cs;
output adc_clk;
input adc_data;
这样更符合 FPGA 顶层模块的方向定义。
11. 引脚约束文件 XDC 写法示例
实际工程中,需要在 XDC 文件中把 Verilog 顶层端口绑定到 FPGA 物理管脚。
下面给出一个模板,具体 PACKAGE_PIN 需要根据实际接线修改:
## 74HC595 control
set_property PACKAGE_PIN <PIN_DATA> [get_ports shift_data]
set_property IOSTANDARD LVCMOS33 [get_ports shift_data]
set_property PACKAGE_PIN <PIN_CLK> [get_ports shift_clk]
set_property IOSTANDARD LVCMOS33 [get_ports shift_clk]
set_property PACKAGE_PIN <PIN_LATCH> [get_ports shift_latch]
set_property IOSTANDARD LVCMOS33 [get_ports shift_latch]
## ADC interface
set_property PACKAGE_PIN <PIN_ADC_CS> [get_ports adc_cs]
set_property IOSTANDARD LVCMOS33 [get_ports adc_cs]
set_property PACKAGE_PIN <PIN_ADC_CLK> [get_ports adc_clk]
set_property IOSTANDARD LVCMOS33 [get_ports adc_clk]
set_property PACKAGE_PIN <PIN_ADC_DATA> [get_ports adc_data]
set_property IOSTANDARD LVCMOS33 [get_ports adc_data]
## Debug output
set_property PACKAGE_PIN <PIN_CARRIER> [get_ports carrier_debug]
set_property IOSTANDARD LVCMOS33 [get_ports carrier_debug]
如果使用 Mizar Z7 的 JP1/JP2 扩展口,需要根据开发板手册找到对应 GPIO 的 FPGA Pin No.,再写入 XDC。
12. 顶层 Verilog 端口建议
为了方便维护,顶层模块不要直接写一堆无意义的端口名,而应该按功能命名:
module top_ultrasonic_array (
input wire clk_50m,
input wire rst_n,
// 74HC595 interface
output wire shift_data,
output wire shift_clk,
output wire shift_latch,
// ADC interface
output wire adc_cs,
output wire adc_clk,
input wire adc_data,
// UART interface
input wire uart_rx,
output wire uart_tx,
// Debug
output wire carrier_debug
);
这样做的好处是:
- 看顶层端口就知道系统结构;
- XDC 约束更清晰;
- 后续换引脚不影响内部逻辑;
- 写报告和答辩时更容易解释。
13. UART/BLE 通信引脚
上位机通过串口或 BLE 透传向 FPGA 发送任务参数,包括:
任务编号
25 路幅度
25 路相位
应用命令
状态查询
从 FPGA 角度看,BLE 透传模块和 USB-UART 本质上都是串口设备,只需要 RX/TX 两根信号:
| 信号名 | 方向 | 功能 |
|---|---|---|
| UART_RX | 上位机/BLE → FPGA | 接收控制帧 |
| UART_TX | FPGA → 上位机/BLE | 返回状态或应答 |
调试时建议先使用 USB-UART,因为它稳定、方便抓取日志;BLE 透传可以在基础通信稳定后再接入。
14. 调试引脚一定要预留
很多时候,FPGA 内部逻辑没有问题,但外部波形并不符合预期。如果没有调试引脚,就只能靠猜。
本项目建议至少预留以下调试信号:
| 调试信号 | 用途 |
|---|---|
| carrier_debug | 观察 40 kHz 载波频率是否正确 |
| shift_clk_debug | 观察 74HC595 移位时钟 |
| latch_debug | 观察锁存时刻 |
| selected_ch_debug | 观察某一路 PWM |
| uart_rx_flag | 判断是否收到上位机数据 |
其中最重要的是 carrier_debug。只要示波器能看到稳定的 40 kHz,就说明时钟分频、载波计数器和基础 PWM 逻辑基本正常。
15. 常见问题与排查方法
问题 1:74HC595 没有输出
优先检查:
DATA 是否有变化
CLK 是否有跳变
LATCH 是否在 32 bit 后产生上升沿
OE 是否使能
SRCLR 是否保持非清零状态
74HC595 是否有 3.3V 供电
FPGA 和驱动板是否共地
问题 2:通道顺序错乱
常见原因是移位顺序和实际通道顺序不一致。
解决方法:
- 每次只打开一个通道;
- 用示波器逐个测量 CH 输出;
- 记录逻辑 CH 与物理阵元位置;
- 在上位机或 FPGA 中建立映射表。
问题 3:波束方向和设定方向相反
常见原因包括:
X/Y 坐标定义反了
阵列正反面方向理解错了
蛇形映射没有处理
相位补偿符号写反
建议先测试:
全零相位:中心方向声压最大
θx = +12°:声束向一侧偏转
θx = -12°:声束向相反方向偏转
如果正负方向相反,只需要在相位计算中调整符号,不一定是硬件问题。
问题 4:ADC 数据异常
检查顺序:
麦克风偏置是否正常
LMV358 输出是否在 ADC 输入范围内
ADC_CS 时序是否正确
ADC_CLK 频率是否过高
ADC_DATA 是否需要电平转换
FPGA 输入是否设置为 LVCMOS33
16. 本项目的引脚设计经验总结
这个超声相控阵系统的引脚设计可以总结为一句话:
用尽可能少的 FPGA 外部引脚,换取尽可能多的可控阵元通道。
具体做法是:
- 使用 Mizar Z7 的 PL GPIO 作为外设接口;
- 用 3 根线控制 4 片 74HC595D;
- 通过 32 路并行输出控制 25 路阵元;
- 用 UCC27624DR OUT 直接驱动换能器;
- 用蛇形映射解决 PCB 布线与几何坐标不一致的问题;
- ADC、UART、调试信号分开规划,避免后期混乱;
- XDC 文件中所有信号按功能命名,方便维护。
对于电赛项目来说,引脚设计不是简单的“能接上就行”,而是直接影响系统调试效率、波束控制精度和后期扩展能力。尤其是多通道阵列类项目,一定要在画板前先确定好:
逻辑通道编号
物理阵元位置
74HC595 输出顺序
FPGA 顶层端口
XDC 管脚约束
上位机映射表
只要这几张表保持一致,后面的 PWM、相位控制、聚焦算法和上位机下发都会顺畅很多。