当前位置: 首页 > news >正文

从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
http://www.jsqmd.com/news/697339/

相关文章:

  • MySQL 调优
  • Nintendo Switch大气层系统终极指南:如何在5分钟内完成专业级自制系统部署?
  • 2026年山东断桥铝门窗与系统阳光房选购完全指南:泰安峰睿门窗定制方案深度评测 - 企业名录优选推荐
  • 网易云音乐NCM格式终极解密:3分钟掌握免费转换技巧,彻底解放你的音乐库
  • 如何构建航班价格自动化监控系统以应对动态定价挑战?
  • Hotkey Detective:深入解析Windows热键冲突检测的技术实现与实战应用
  • AUTOSAR BswM模块深度解析:从“模式仲裁”到“动作列表”,如何像搭积木一样设计汽车ECU的大脑?
  • 2026年山东断桥铝门窗与系统阳光房选购避坑指南:找到官方直达渠道的正确姿势 - 企业名录优选推荐
  • 5分钟为Windows添加无限虚拟显示器:终极配置指南
  • 软件/游戏存档路径计算工具补充unity游戏引擎适配
  • 如何高效使用Mermaid在线编辑器:5个实用技巧全解析
  • 如何快速解决Windows热键冲突:Hotkey Detective完全指南
  • 拒绝“AI贴图感”!亲测全网,这才是平面设计师找的AI海报设计工具首选
  • BarrageGrab:全平台直播弹幕抓取的终极解决方案
  • Docker拉取Milvus 2.0镜像慢到怀疑人生?试试这个组合加速方案(阿里云镜像+手动替换)
  • 2026年山东断桥铝门窗与系统阳光房选购完全指南 - 企业名录优选推荐
  • 别再用平台了!手把手教你用纯QT C++从零搭建游戏框架(附超级玛丽源码解析)
  • 2026年毕业论文AI检测日趋严格?收藏降AI工具助你高效通过 - 降AI实验室
  • Qt Creator集成clang-format:告别团队协作中的代码风格之争
  • MT5 Zero-Shot中文增强效果深度测评:与BERT-wwm、ChatGLM对比分析
  • Windows Cleaner:告别C盘爆红,让你的Windows系统重获新生
  • 做题记录(Chemistry)
  • 原神帧率解锁终极指南:如何轻松突破60FPS限制实现高刷新率体验
  • 2026年山东断桥铝门窗与系统阳光房选购完全指南:泰安峰睿门窗官方对接 - 企业名录优选推荐
  • 即时编译器:解释执行与热点代码编译的切换
  • 终极解决方案:3步轻松重置Navicat试用期,告别14天限制
  • 免费解锁专业直播画面:StreamFX 终极指南
  • 京东E卡闲置不用怎么办?这几个方法帮你解决 - 抖抖收
  • uv与conda
  • 告别环境配置烦恼:用Docker容器在Mac上轻松搞定Go CGO交叉编译(以K8s为例)