手把手用Verilog实现SPI主从通信:基于Xilinx Artix-7的FPGA实战教程
手把手用Verilog实现SPI主从通信:基于Xilinx Artix-7的FPGA实战教程
在嵌入式系统开发中,SPI(Serial Peripheral Interface)因其高速、全双工和硬件简单的特性,成为连接Flash存储器、传感器等外设的首选协议。本文将带你从零开始,用Verilog在Xilinx Artix-7 FPGA上实现SPI主设备控制W25Q128 Flash和从设备连接MPU6050加速度计的完整流程。不同于使用现成IP核,我们将深入时序细节,通过仿真和逻辑分析仪验证每个信号跳变,真正掌握同步串行通信的核心原理。
1. SPI协议核心与硬件准备
SPI协议通过四根信号线实现全双工通信:
- SCK:主设备提供的同步时钟
- MOSI:主设备输出,从设备输入
- MISO:从设备输出,主设备输入
- SS:从设备片选(低电平有效)
以W25Q128 Flash为例,其关键时序参数如下:
| 参数 | 典型值 | 说明 |
|---|---|---|
| SCK频率 | 50MHz | 最大支持时钟速率 |
| 建立时间(tSU) | 3ns | 数据在SCK上升沿前稳定时间 |
| 保持时间(tH) | 2ns | 数据在SCK上升沿后保持时间 |
硬件连接需注意:
// Artix-7引脚分配示例 set_property PACKAGE_PIN F12 [get_ports {spi_clk}] // SCK set_property PACKAGE_PIN D10 [get_ports {spi_mosi}] // MOSI set_property PACKAGE_PIN E11 [get_ports {spi_miso}] // MISO set_property PACKAGE_PIN G13 [get_ports {spi_cs_n}] // SS提示:实际布线时需确保SCK与数据线等长,避免时序偏移超过1/4时钟周期
2. SPI主设备Verilog实现
主设备状态机设计采用三段式结构,确保时序清晰:
2.1 时钟生成与相位控制
SPI模式0(CPOL=0, CPHA=0)的时钟生成逻辑:
always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin spi_clk <= 1'b0; clk_cnt <= 0; end else if(trans_en) begin clk_cnt <= (clk_cnt == CLK_DIV-1) ? 0 : clk_cnt + 1; spi_clk <= (clk_cnt < CLK_DIV/2) ? 1'b0 : 1'b1; end end2.2 数据移位与采样
并行转串行发送逻辑:
always @(posedge sys_clk or negedge rst_n) begin if(!rst_n) begin shift_reg <= 8'h00; bit_cnt <= 0; end else if(trans_start) begin shift_reg <= tx_data; // 加载并行数据 bit_cnt <= 7; // 8位计数器 end else if(clk_cnt == CLK_DIV-1) begin shift_reg <= {shift_reg[6:0], 1'b0}; // 左移 bit_cnt <= bit_cnt - 1; end end assign spi_mosi = shift_reg[7]; // 输出最高位2.3 Flash操作指令实现
W25Q128的页编程(PP)指令时序:
- 拉低SS,发送0x02指令码
- 发送24位地址(A23-A0)
- 发送最多256字节数据
- 拉高SS结束传输
对应Verilog实现:
case(state) IDLE: if(cmd_valid) begin tx_data <= 8'h02; // PP指令 state <= ADDR_H; end ADDR_H: begin tx_data <= addr[23:16]; state <= ADDR_M; end // ... 其他状态转移 endcase3. SPI从设备适配MPU6050
MPU6050作为I2C/SPI双模器件,需配置为SPI模式:
// 初始化序列 initial begin spi_write(0x6B, 8'h00); // 退出睡眠模式 spi_write(0x6A, 8'h10); // 禁用I2C,启用SPI end task spi_write; input [7:0] reg_addr; input [7:0] reg_data; begin ss_n = 0; spi_xfer(reg_addr); spi_xfer(reg_data); ss_n = 1; end endtask加速度计数据读取的时序要点:
- 寄存器地址最高位置1表示读操作
- 连续读取时自动递增地址
- 数据返回有1字节延迟
4. 验证与调试技巧
4.1 Modelsim功能仿真
建立测试平台验证读写时序:
initial begin // 发送Flash读ID指令(0x9F) spi_master_tb(8'h9F); #100; // 模拟Flash返回ID: 0xEF 0x40 0x18 force uut.spi_miso = 1'b1; // 第一位MSB=1 #200; release uut.spi_miso; end4.2 逻辑分析仪抓包
使用Saleae Logic解析SPI信号:
- 设置解码器为SPI模式
- 配置时钟极性/相位匹配硬件
- 触发条件设为SS下降沿
- 导出数据为CSV进行后期分析
典型问题排查流程:
- 无响应:检查SS信号是否有效
- 数据错位:确认CPOL/CPHA设置
- CRC错误:降低SCK频率测试
5. 性能优化进阶
5.1 时钟域交叉处理
当SPI时钟与系统时钟不同源时:
// 双触发器同步器 always @(posedge sys_clk) begin spi_miso_sync <= {spi_miso_sync[0], spi_miso}; end5.2 DMA传输优化
大数据量传输时采用DMA控制器:
- 配置源/目的地址和传输长度
- 启动DMA后自动搬运数据
- 通过中断通知完成
dma_ctrl u_dma( .clk (sys_clk), .mem_addr (32'h4000_0000), .spi_addr (24'h00_1234), .length (1024), .start (dma_start), .done (dma_done) );在完成Flash和加速度计的联合调试后,发现MPU6050对SCK噪声敏感,通过以下改进稳定通信:
- 在SCK线上串联33Ω电阻
- 缩短FPGA与传感器间走线至<5cm
- 在电源引脚添加0.1μF去耦电容
