news 2026/4/25 9:56:01

从EEPROM到液晶屏:一个FPGA工程师的SPI实战踩坑记录(附Verilog代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从EEPROM到液晶屏:一个FPGA工程师的SPI实战踩坑记录(附Verilog代码)

从EEPROM到液晶屏:一个FPGA工程师的SPI实战踩坑记录(附Verilog代码)

当FPGA项目需要同时与多个SPI外设通信时,工程师往往会面临时钟速率、数据格式和连接方式的复杂权衡。本文将分享我在驱动EEPROM、DSP协处理器和液晶屏三个典型SPI设备时遇到的真实挑战,以及如何通过Verilog代码实现稳定可靠的通信。

1. 多SPI设备的系统架构设计

在嵌入式系统中,SPI总线因其简单高效而广受欢迎。但当需要同时连接EEPROM(存储配置)、DSP(数据处理)和液晶屏(显示输出)时,系统设计就变得复杂起来。这三种设备对SPI通信有着截然不同的需求:

  • EEPROM:通常需要较低的时钟频率(1-10MHz),数据格式多为8位或16位
  • DSP协处理器:可能需要更高的时钟速率(10-50MHz),数据位宽可达32位
  • 液晶屏:对时序要求严格,可能需要特定的CPOL/CPHA配置

1.1 连接方式的选择

SPI设备有两种主要连接方式,各有优缺点:

连接方式优点缺点适用场景
多NSS独立控制每个设备需要更多GPIO引脚设备间无数据依赖
菊花链节省引脚资源设备间存在串行延迟数据需要顺序处理

在我的项目中,EEPROM和DSP之间存在数据依赖(配置需要先加载到DSP),而液晶屏是独立更新的。因此采用了混合连接方式:

// SPI主控制器接口定义 module spi_master ( input wire clk, input wire reset, output reg [1:0] nss, // nss[0]用于EEPROM+DSP链,nss[1]用于液晶屏 output reg sck, output reg mosi, input wire miso );

2. SPI时序参数的实战配置

不同SPI设备对时序参数的要求可能大相径庭,这是多设备驱动中最容易出问题的地方。

2.1 时钟极性与相位配置

CPOL和CPHA的组合决定了数据采样边沿,常见的四种模式:

  1. Mode 0(CPOL=0, CPHA=0):时钟空闲低电平,数据在上升沿采样
  2. Mode 1(CPOL=0, CPHA=1):时钟空闲低电平,数据在下降沿采样
  3. Mode 2(CPOL=1, CPHA=0):时钟空闲高电平,数据在下降沿采样
  4. Mode 3(CPOL=1, CPHA=1):时钟空闲高电平,数据在上升沿采样

在我的项目中:

  • EEPROM使用Mode 0
  • DSP使用Mode 1
  • 液晶屏使用Mode 3

这要求在Verilog状态机中实现动态模式切换:

// 时钟生成逻辑 always @(posedge clk or posedge reset) begin if (reset) begin sck <= (current_mode[1]) ? 1'b1 : 1'b0; // 根据CPOL设置初始电平 end else begin case (state) IDLE: sck <= (current_mode[1]) ? 1'b1 : 1'b0; TRANSFER: sck <= ~sck; // 在传输状态翻转时钟 endcase end end

2.2 时钟分频策略

三个设备需要不同的时钟速率:

  • EEPROM: 2MHz (主时钟100MHz的50分频)
  • DSP: 10MHz (10分频)
  • 液晶屏: 5MHz (20分频)

实现时采用了可配置的分频计数器:

reg [7:0] clock_divider; reg [7:0] clock_counter; always @(posedge clk or posedge reset) begin if (reset) begin clock_counter <= 0; sck <= 0; end else if (clock_counter >= clock_divider) begin clock_counter <= 0; sck <= ~sck; end else begin clock_counter <= clock_counter + 1; end end

3. 数据传输调度与冲突解决

当多个SPI设备需要同时服务时,合理的调度策略至关重要。我采用了基于优先级的轮询机制:

  1. 高优先级:液晶屏刷新(需要定期更新)
  2. 中优先级:DSP数据处理
  3. 低优先级:EEPROM配置读取

3.1 状态机设计

typedef enum { IDLE, LCD_INIT, LCD_TRANSFER, DSP_TRANSFER, EEPROM_READ, EEPROM_WRITE } spi_state_t; spi_state_t state, next_state; // 状态转移逻辑 always @(*) begin case (state) IDLE: begin if (lcd_update_req) next_state = LCD_INIT; else if (dsp_data_ready) next_state = DSP_TRANSFER; else if (eeprom_read_req) next_state = EEPROM_READ; else next_state = IDLE; end // 其他状态转移... endcase end

3.2 数据缓冲区管理

为每个设备设置了独立的FIFO缓冲区,防止数据丢失:

缓冲区深度位宽用途
LCD_FIFO6416存储显示数据
DSP_FIFO3232处理中间数据
EEPROM_FIFO168配置数据缓存

4. 常见问题与调试技巧

在实际调试过程中,遇到了几个典型问题,这里分享解决方案。

4.1 建立/保持时间违例

当SCK频率过高时,MISO数据可能无法满足从设备的建立保持时间要求。解决方法:

  1. 在FPGA输入端插入IDELAY原语,调整数据采样点
  2. 降低SCK频率
  3. 在SCK边沿后延迟采样(适用于CPHA=1模式)
// 延迟采样实现示例 reg miso_delayed; always @(posedge clk) begin if (sck_rising_edge) begin miso_delayed <= #2 miso; // 插入2ns延迟 end end

4.2 多设备干扰问题

当切换NSS信号时,其他设备可能产生干扰。解决方案:

  1. 在NSS切换期间插入至少1个SCK周期的空闲时间
  2. 确保MOSI在NSS无效时为高阻态
  3. 为每个设备添加独立的输入滤波器
// NSS切换时的保护逻辑 task change_nss; input [1:0] new_nss; begin mosi <= 1'bz; // 置为高阻 #10; // 等待10ns nss <= new_nss; #10; // 等待10ns end endtask

4.3 菊花链中的数据错位

在EEPROM→DSP的菊花链中,发现数据位错位问题。原因是:

  • EEPROM的MSB在前,而DSP期望LSB在前
  • 解决方案是在FPGA中实现位序转换:
function [7:0] reverse_bits; input [7:0] data; begin reverse_bits = {data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]}; end endfunction

5. 完整SPI控制器实现要点

最后分享完整的SPI控制器设计中的关键部分:

5.1 顶层模块接口

module spi_controller ( input wire clk_100mhz, input wire reset_n, // 设备接口 output wire [1:0] nss, output wire sck, output wire mosi, input wire miso, // 配置接口 input wire [1:0] device_select, input wire [7:0] clock_div, input wire [1:0] spi_mode, // 数据接口 input wire [31:0] tx_data, output wire [31:0] rx_data, input wire tx_valid, output wire tx_ready, output wire rx_valid );

5.2 核心状态机

always @(posedge clk_100mhz or negedge reset_n) begin if (!reset_n) begin state <= IDLE; bit_counter <= 0; end else begin case (state) IDLE: begin if (tx_valid) begin shift_reg <= tx_data; state <= START; end end START: begin nss <= device_select; state <= TRANSFER; end TRANSFER: begin if (bit_counter < data_width) begin mosi <= shift_reg[data_width-1]; shift_reg <= {shift_reg[data_width-2:0], miso}; bit_counter <= bit_counter + 1; end else begin state <= STOP; end end STOP: begin nss <= 2'b11; rx_data <= shift_reg; rx_valid <= 1'b1; state <= IDLE; end endcase end end

5.3 时钟生成逻辑

reg [7:0] clk_counter; reg sck_internal; always @(posedge clk_100mhz or negedge reset_n) begin if (!reset_n) begin clk_counter <= 0; sck_internal <= spi_mode[1]; // CPOL end else if (state == TRANSFER) begin if (clk_counter >= clock_div) begin clk_counter <= 0; sck_internal <= ~sck_internal; end else begin clk_counter <= clk_counter + 1; end end else begin sck_internal <= spi_mode[1]; // 空闲状态 clk_counter <= 0; end end assign sck = sck_internal;

在项目最终实现中,这个SPI控制器成功实现了:

  • 同时驱动三个不同特性的SPI设备
  • 动态配置时钟频率(1-25MHz)
  • 支持四种SPI模式
  • 数据传输速率达到15Mbps
  • 资源占用不到500个LUT
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 9:54:10

CentOS 7搭建TeamSpeak服务器避坑指南:解决证书错误、bzip2缺失等常见问题

CentOS 7实战&#xff1a;TeamSpeak语音服务器部署全流程与疑难解析 在游戏公会、远程团队协作等场景中&#xff0c;稳定高效的语音通信系统至关重要。TeamSpeak作为老牌专业语音解决方案&#xff0c;以其低延迟、高音质和灵活的权限管理著称。本文将带您从零开始在CentOS 7系统…

作者头像 李华
网站建设 2026/4/25 9:51:19

告别虚拟机!在Windows 11上用Docker Desktop一键部署SRS 5.0流媒体服务器

在Windows 11上零配置部署SRS 5.0&#xff1a;Docker Desktop全流程指南 对于流媒体开发者而言&#xff0c;快速搭建本地测试环境是刚需。传统方案要么需要配置复杂的虚拟机&#xff0c;要么面临性能损耗和资源占用问题。现在&#xff0c;借助Windows 11的WSL 2和Docker Deskto…

作者头像 李华
网站建设 2026/4/25 9:51:18

3步搭建你的免费音乐聚合系统:MusicFreePlugins完全指南

3步搭建你的免费音乐聚合系统&#xff1a;MusicFreePlugins完全指南 【免费下载链接】MusicFreePlugins MusicFree播放插件 项目地址: https://gitcode.com/gh_mirrors/mu/MusicFreePlugins 你是否厌倦了在不同音乐平台间来回切换&#xff1f;是否因为VIP会员墙和版权限…

作者头像 李华