1. SPI通用收发模块设计背景与核心特性
在嵌入式系统和FPGA开发中,SPI(Serial Peripheral Interface)总线是最常用的外设接口之一。传统SPI控制器实现通常存在配置不灵活、扩展性差等问题。这个基于Verilog实现的SPI通用收发模块,通过AXI4-Lite总线接口提供了高度可配置的SPI主机功能,特别适合需要同时管理多个SPI设备的应用场景。
模块的核心优势体现在三个方面:
- 多总线支持:单个控制器可管理32个独立SPI总线,每个总线支持32个从设备,总计可扩展至1024个SPI设备
- 全参数化配置:通过寄存器可动态调整所有关键参数,包括时钟极性、相位、数据格式等
- 标准化接口:AXI4-Lite接口使其能无缝集成到Xilinx FPGA的处理器系统中
2. 模块架构与接口设计
2.1 整体架构框图
模块采用分层设计架构,主要包含以下功能单元:
- AXI4-Lite接口桥:处理总线协议转换
- 控制寄存器组:存储配置参数和状态信息
- SPI引擎:实现SPI协议的状态机
- 数据缓冲区:提供指令和数据的双缓冲机制
code复制[AXI Master] ←AXI4-Lite→ [SPI Controller] ←SPI→ [Slave Devices]
↑
Configuration
& Status
2.2 AXI4-Lite接口实现细节
AXI4-Lite接口采用标准实现方案,包含以下关键信号组:
- 写地址通道(awaddr, awvalid, awready)
- 写数据通道(wdata, wstrb, wvalid, wready)
- 写响应通道(bresp, bvalid, bready)
- 读地址通道(araddr, arvalid, arready)
- 读数据通道(rdata, rresp, rvalid, rready)
典型寄存器访问时序示例:
verilog复制// 寄存器写操作示例
always @(posedge axi_aclk) begin
if (axi_awvalid && axi_awready) begin
case (axi_awaddr[7:0])
8'h00: config_reg <= axi_wdata;
8'h04: instruction_reg <= axi_wdata;
// 其他寄存器地址...
endcase
end
end
3. SPI协议实现关键技术
3.1 时钟模式配置
模块完整支持SPI四种工作模式组合:
- Mode 0: CPOL=0, CPHA=0
- Mode 1: CPOL=0, CPHA=1
- Mode 2: CPOL=1, CPHA=0
- Mode 3: CPOL=1, CPHA=1
时钟生成逻辑关键代码:
verilog复制// SCLK生成逻辑
always @(posedge clk or posedge reset) begin
if (reset) begin
sclk <= CPOL; // 复位时根据极性设置初始状态
end else if (enable) begin
if (CPHA) begin
// 相位为1时的时钟边沿处理
sclk <= ~sclk;
end else begin
// 相位为0时的时钟边沿处理
sclk <= ~sclk;
end
end
end
3.2 数据传输格式处理
模块支持MSB-first和LSB-first两种传输格式,通过移位寄存器实现:
verilog复制// 数据移位处理
always @(posedge clk) begin
if (shift_enable) begin
if (MSB_first) begin
// MSB优先传输
sdo <= tx_buffer[31];
tx_buffer <= {tx_buffer[30:0], 1'b0};
end else begin
// LSB优先传输
sdo <= tx_buffer[0];
tx_buffer <= {1'b0, tx_buffer[31:1]};
end
end
end
4. 多总线管理机制
4.1 总线选择逻辑
32个SPI总线的选择通过5位总线选择信号实现:
verilog复制// 片选信号解码器
always @(*) begin
for (int i=0; i<32; i++) begin
cs_n[i] = (bus_select == i) ? active_low_cs : 1'b1;
end
end
4.2 从设备地址映射
每个总线支持32个从设备,采用两级地址映射:
- 高5位:总线选择
- 低5位:从设备选择
地址映射示例:
verilog复制wire [4:0] bus_sel = axi_awaddr[9:5];
wire [4:0] slave_sel = axi_awaddr[4:0];
5. 工作模式与数据缓冲区
5.1 单次传输模式
单次传输模式下支持:
- 32位指令(Instruction)
- 64位数据(Data)
传输状态机设计:
verilog复制localparam IDLE = 2'b00;
localparam SEND_CMD = 2'b01;
localparam SEND_DATA = 2'b10;
always @(posedge clk) begin
case (state)
IDLE: if (start) state <= SEND_CMD;
SEND_CMD: if (cmd_done) state <= SEND_DATA;
SEND_DATA: if (data_done) state <= IDLE;
endcase
end
5.2 连续传输模式
连续传输模式采用双缓冲机制:
- 当前缓冲区正在发送时,可准备下一帧数据
- 通过状态寄存器指示缓冲区状态
缓冲区管理逻辑:
verilog复制// 双缓冲切换逻辑
always @(posedge clk) begin
if (buffer_switch) begin
active_buffer <= ~active_buffer;
ready_buffer <= ~ready_buffer;
end
end
6. 性能优化与实测结果
6.1 时序约束与优化
在KC705开发板上实现50MHz SCLK的关键约束:
tcl复制create_clock -period 20.000 -name spi_clk [get_ports sclk]
set_input_delay -clock spi_clk 2.000 [get_ports {sdi sdio}]
set_output_delay -clock spi_clk 3.000 [get_ports {sdo cs_n*}]
6.2 资源占用统计
典型实现资源消耗(XC7K325T):
- LUT: 423
- FF: 587
- BRAM: 1 (36Kb)
7. 集成与调试经验
7.1 MicroBlaze系统集成
Vivado Block Design配置要点:
- 添加MicroBlaze处理器核
- 连接AXI Interconnect
- 添加SPI控制器IP
- 分配地址空间
SDK软件接口示例:
c复制#define SPI_CTRL_BASE XPAR_SPI_CONTROLLER_0_BASEADDR
void spi_send_command(uint32_t bus, uint32_t slave, uint32_t cmd, uint64_t data) {
// 设置总线/从机选择
Xil_Out32(SPI_CTRL_BASE + 0x00, (bus << 5) | slave);
// 写入指令
Xil_Out32(SPI_CTRL_BASE + 0x04, cmd);
// 写入数据(分两次写入)
Xil_Out32(SPI_CTRL_BASE + 0x08, (uint32_t)(data >> 32));
Xil_Out32(SPI_CTRL_BASE + 0x0C, (uint32_t)data);
// 触发传输
Xil_Out32(SPI_CTRL_BASE + 0x10, 0x01);
}
7.2 常见问题排查
-
时钟无输出问题检查:
- 确认AXI配置寄存器已正确写入
- 检查reset信号是否已释放
- 验证时钟分频系数是否合理
-
数据错位问题:
- 确认CPOL/CPHA设置与从设备匹配
- 检查MSB/LSB配置
- 验证时序约束是否满足
-
片选信号异常:
- 确认总线选择寄存器值正确
- 检查从设备地址映射
- 验证CS信号极性配置
8. 扩展应用与优化建议
-
多主设备共享方案:
- 添加仲裁逻辑
- 实现总线请求/授权机制
-
更高时钟频率实现:
- 采用ODDR原语输出SCLK
- 使用IDELAYCTRL调整输入数据时序
-
DMA集成方案:
- 添加AXI Stream接口
- 实现描述符机制管理传输
实际项目中,这个SPI控制器在多个传感器采集系统中表现稳定。特别是在需要同时读取多个SPI温度传感器的场景中,通过合理配置总线切换时序,实现了所有传感器的周期轮询,采样率达到1kHz以上。一个特别有用的技巧是在连续传输模式下,利用双缓冲机制实现数据无缝传输,避免了传统方案中的总线空闲等待时间。