拓冰建站拓冰建站
首页 / 资讯中心 / 正文

FPGA实现SPI通信:从协议原理到Verilog代码与调试实战

大家做FPGA项目基本都绕不开SPI这接口说简单也简单四根线而已但真要在FPGA里稳定跑起来里面坑其实不少。前阵子刚给一个数据采集板卡写完SPI控制器配合ADC读数据顺便驱动了一下外部Flash做日志存储整体走了一遍有些心得正好整理出来。这篇文章我从头捋一遍SPI在FPGA里面的设计思路、关键代码、仿真调试方法再把实际调板时遇到的那些“说多了都是泪”的问题一起聊了希望对正在搞或者准备搞这块的朋友有点帮助。1. 先从SPI这点事说起协议本质和FPGA方案选型1.1 四根线上“唱”的到底是什么戏SPI全称Serial Peripheral Interface是个全双工、同步、串行接口MOTOROLA最早推出来的现在基本是板级通信的默认选项之一。SPI通信原理不复杂一根时钟线SCLK、两根数据线MOSI、MISO、再加一根片选线CS/SS一共四根线就能干活。也说下这四根线SCLK由主机产生决定数据传输节奏一个时钟沿传一位数据MOSIMaster Output Slave Input主机发数据给从机MISOMaster Input Slave Output从机回复数据给主机CS片选信号低有效拉低代表选中某个从机。SPI本质上就是个移位寄存器交互的过程主机在时钟沿把数据推出去同时在另一个时钟沿把从机数据采进来。之所以在FPGA里实现SPI特别合适是因为它逻辑本身不是特别复杂而且FPGA的并行优势能把多路SPI、高分辨率ADC数据采集、自定义协议扩展这些需求都满足得很好比单片机软模拟SPI的方式要稳定得多。1.2 FPGA实现SPI的几种思路选哪个先想清楚FPGA里做SPI通信常见路线有三类直接用厂商IP比如Xilinx有SPI IP核AXI Quad SPI高云、紫光这些国产片子也有对应IP。好处是省事坏处是你得花时间学IP的接口和配置遇到问题反而不好排查而且IP核的资源开销往往比自己写大不少。写状态机控制SCLK用系统时钟分频产生SCLK状态机控制CS拉低、逐位移位、CS拉高。这种方式最通用适合绝大多数学控制和数据采集的场景。用ODDR原语输出SCLKXilinx 7系列以上的片子里用ODDR从时钟域直接产生SCLK可以得到更干净、抖动更小的时钟边沿适合高速SPI场景比如50MHz以上。我做ADC采样用的是状态机控制SCLK的方式因为ADC的SPI时钟也就二三十MHz完全够用。如果你要驱动的是那种支持上百兆SPI时钟的高速DAC或者ADC建议直接上ODDR方案或者用SelectIO接口。1.3 开始之前先把这个想明白主控端是谁写代码之前一定先想清楚你的FPGA在系统里扮演什么角色。是做SPI主机去控制外部器件还是做SPI从机被别人控制大部分情况FPGA扮演的是主机去配置ADC的寄存器、读取转换结果、驱动Flash存储、控制DAC输出。如果你是用ZYNQ或者带软核的方案还可能遇到PS侧通过GPIO模拟SPI控制PL侧逻辑的需求。而如果FPGA做从机比如通过SPI接口做远程升级就是热词里那个“高云基于SPI接口的FPGA远程升级实现”那状态机思路反过来由外部主机发SCLKFPGA被动接收和发送时序约束要注意跨时钟域处理。明确主从角色之后整个代码结构和测试重点就完全不一样了。我下面的内容是围绕FPGA做主机的场景来写的这也是最常用的。2. 动手写FPGA SPI控制器前的关键设计2.1 时钟域与分频策略别一上来就给个系统时钟的div很多FPGA新手写SPI有个习惯直接用系统时钟分频也不管占空比、相位关系结果板子上的设备就是不认。核心原因是SPI时钟必须干净、稳定且和数据的翻转时刻能对齐。我常用的做法是用一个计数器做分频产生一个SCLK使能脉冲而不是直接产生SCLK信号。说起来有点绕我举个具体例子假设系统时钟是50MHz我需要SPI时钟频率5MHz那就是10分频。此时我设计一个计数器从0数到9一共10个周期在计数器值为0或者为5的时候让SCLK翻转这就得到了占空比50%的5MHz时钟。同时这个计数器还天然给了我们一个“半周期”的概念方便在正确的边沿采样和更新数据。这种设计的好处是状态机跟着计数器走每个节拍都在系统时钟域内同步设计非常好写也不容易出现亚稳态问题。如果你直接用系统时钟去分频产生时钟然后再驱动逻辑很容易触发时序约束的问题后面调试会很痛苦。2.2 四种SPI模式CPOL/CPHA这个配置错了通信直接挂这个点必须单独拎出来说。SPI通信原理里面CPOL时钟极性和CPHA时钟相位两个参数决定通信时序的“长相”一共4种模式模式CPOLCPHA数据采样沿数据变化沿Mode 000上升沿下降沿Mode 101下降沿上升沿Mode 210下降沿上升沿Mode 311上升沿下降沿FPGA里面做主机MOSI的数据更新必须和SCLK边沿对齐。很多外设默认是Mode 0CPOL0CPHA0也就是空闲时SCLK为低数据在上升沿被主机采样、在下降沿更新。实际写代码的时候怎么控制这两个沿核心就是利用计数器产生的“半个SCLK周期”的脉冲。在状态机里每当计数器计到中间值半周期点时更新MOSI数据每当计到边界值时内部寄存器采集MISO。这样数据变化沿和采样沿就自然错开了半个周期。我当时调试一颗型号比较老的ADC时就因为没注意这个芯片内部逻辑要求CPHA1白白浪费了一个下午。所以拿到一个不熟悉的器件第一件事就是查数据手册里的时序图把CPOL和CPHA确认清楚再写代码。2.3 输入信号同步FPGA跨时钟域的日常必修课SPI从机返回的MISO信号、片选信号等外部输入对FPGA来说都是异步信号必须打两拍同步不然很容易采到亚稳态轻则数据偶尔出错重则整个状态机跑飞。这个同步逻辑几乎是所有FPGA接口设计的“入场券”reg miso_sync1, miso_sync2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin miso_sync1 1b0; miso_sync2 1b0; end else begin miso_sync1 miso; // 来自引脚 miso_sync2 miso_sync1; end end采MISO的时候用的就是miso_sync2这个信号而不是直接采引脚信号。这个习惯一定要养成后续做ADC、Flash、SD卡的接口都能用上。3. 核心代码拆解一个可复用的SPI主机控制器这部分直接给一段我写过的SPI主机控制器的Verilog代码后面详细解释每个模块的作用。这个控制器的能力是支持任意位宽、支持四种SPI模式、主机产生时钟、可配置分频系数足够覆盖大多数使用场景。3.1 模块端口定义module spi_master #( parameter CLK_FREQ 50_000_000, // 系统时钟频率Hz parameter SCLK_FREQ 5_000_000, // SPI时钟频率Hz parameter DATA_WIDTH 8, // 数据位宽 parameter CPOL 1b0, // 时钟极性 parameter CPHA 1b0 // 时钟相位 )( input wire clk, input wire rst_n, // 用户接口 input wire start, input wire [DATA_WIDTH-1:0] tx_data, output reg [DATA_WIDTH-1:0] rx_data, output reg done, // SPI物理接口 output reg sclk, output reg mosi, input wire miso, output reg cs_n );这里把参数全部参数化CLK_FREQ和SCLK_FREQ用来计算分频系数CPOL和CPHA控制模式DATA_WIDTH可以支持8位、16位、24位甚至32位的数据传输。这么做的好处是后续换器件、换项目、换数据宽度直接改参数就能复用不用大改代码。3.2 分频系数计算与SCLK产生SCLK的产生我用的不是直接翻转而是用一个计数器配合脉冲信号来产生。计算方法是localparam DIV_CNT_MAX (CLK_FREQ / SCLK_FREQ) - 1; // 整个周期的计数范围 localparam HALF_CNT (CLK_FREQ / SCLK_FREQ) / 2 - 1; // 半周期的计数点然后内部用一个计数器reg [15:0] div_cnt; reg sclk_en; // 表示当前处于SCLK的有效区间 always (posedge clk or negedge rst_n) begin if (!rst_n) div_cnt 16d0; else if (state ST_IDLE) div_cnt 16d0; else if (div_cnt DIV_CNT_MAX) div_cnt 16d0; else div_cnt div_cnt 1b1; end实际上的sclk信号产生要根据CPOL来。CPOL0时空闲为低CPOL1时空闲为高。在数据传输状态中就是在半周期点翻转always (posedge clk or negedge rst_n) begin if (!rst_n) sclk CPOL; else if (state ST_IDLE) sclk CPOL; else if (div_cnt HALF_CNT) sclk ~sclk; end这种通过计数器翻转的方式其实就等效于用系统时钟生成了一个占空比接近50%的SCLK而且因为翻转点都是同步于系统时钟的时序十分稳定。3.3 数据发送与接收核心逻辑发数据的过程核心是一个移位寄存器。每到一个数据更新沿把下一位移出去同时在采样沿把MISO收入寄存器reg [DATA_WIDTH-1:0] tx_shift; reg [DATA_WIDTH-1:0] rx_shift; reg [15:0] bit_cnt; // 发送核心在数据变化沿更新MOSI always (posedge clk or negedge rst_n) begin if (!rst_n) begin mosi 1b0; tx_shift {DATA_WIDTH{1b0}}; end else if (state ST_LOAD) begin tx_shift tx_data; mosi tx_data[DATA_WIDTH-1]; end else if ((state ST_TRANS) (div_cnt HALF_CNT) send_en) begin tx_shift {tx_shift[DATA_WIDTH-2:0], 1b0}; mosi tx_shift[DATA_WIDTH-2]; end end接收核心代码// 采样在采样沿把MISO放进移位寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) rx_shift {DATA_WIDTH{1b0}}; else if ((state ST_TRANS) (div_cnt 0) samp_en) rx_shift {rx_shift[DATA_WIDTH-2:0], miso_sync2}; end注意这里的send_en和samp_en是由CPOL/CPHA综合出来的使能信号。以Mode 0为例CPOL0、CPHA0此时采样沿是上升沿也就是div_cnt0附近数据变化沿是下降沿对应div_cntHALF_CNT附近。而Mode 1则相反。为了让代码通用我提前把“数据变化沿”和“数据采样沿”分别抽象成send_en和samp_en通过CPOL/CPHA参数判断之后赋不同值这样整个发送和接收逻辑主体完全不用改。3.4 状态机的完整流程整个控制器我用一个简单的状态机管理核心状态只有四个状态作用ST_IDLE空闲等待start信号拉高ST_LOAD装载发送数据并拉低CSST_TRANS逐位传输数据ST_DONE拉高done信号释放总线状态机代码localparam ST_IDLE 3d0; localparam ST_LOAD 3d1; localparam ST_TRANS 3d2; localparam ST_DONE 3d3; reg [2:0] state; reg [2:0] next_state; always (posedge clk or negedge rst_n) begin if (!rst_n) state ST_IDLE; else state next_state; end always (*) begin next_state state; case (state) ST_IDLE: if (start) next_state ST_LOAD; ST_LOAD: next_state ST_TRANS; ST_TRANS: if (bit_cnt DATA_WIDTH-1 div_cnt DIV_CNT_MAX) next_state ST_DONE; ST_DONE: next_state ST_IDLE; default: next_state ST_IDLE; endcase end这里值得说下ST_LOAD这个状态为什么不能直接从IDLE进TRANS因为CS信号从高到低之后从机需要一点点时间准备有些芯片的CS下降沿就是触发点如果你在CS拉低的同一个时钟周期就开始发数据很多从机根本反应不过来。加一个ST_LOAD状态让CS先拉低然后至少等一个系统时钟周期再进入数据传输这个小细节能解决不少兼容性问题。3.5 地址/命令数据的复合帧怎么处理很多时候SPI操作不是简单的“发8位收8位”比如SPI Flash的读操作要先发一个命令字节0x03再发24位地址之后才能读数据ADC的寄存器配置可能要发“寄存器地址数据”两段。所以我在主机控制器之上又封装了一层“帧控制逻辑”。帧控制逻辑内部维护一个操作序列把需要发送的各个阶段依次调用底层的主机控制器。比如对Flash的读操作帧控制逻辑收到一个读请求之后依次发送命令0x03发送24位地址高字节在前连续读取N字节数据并存入FIFO。这样做的好处是底层SPI主机控制器保持简单只负责“发一组数据收一组数据”而上层的帧控制逻辑负责拼装复杂协议。分层清晰调试的时候也容易定位问题。4. 实例一用FPGA驱动SPI接口ADC4.1 典型ADC如AD7606、ADS1256的SPI配置流程SPI接口的ADC类型其实很多。像AD7606是并行/串行可选的ADS1256是纯SPI接口、24位Δ-Σ ADC还有国产的AD7606兼容芯片也很多。不管哪颗配置流程基本是一致的第一步上电之后先等待电源稳定一般要等几十毫秒芯片手册会给具体值 第二步通过SPI写寄存器配置采样速率、增益、通道选择、输入范围等参数 第三步启动转换等待DRDY数据就绪引脚拉低 第四步通过SPI读取转换结果。FPGA在里面的角色就是那个“始终待命、按时钟节拍做事的管家”。系统时钟一到该配置寄存器就配置寄存器该读数据就读数据绝不拖泥带水。4.2 连续采样模式的时序处理我调试过一颗24位ADC要求连续采样时每次DRDY信号拉低之后必须在一个规定时间内把数据读走否则数据会覆盖。这个场景FPGA实现起来其实很轻松因为可以用状态机监控DRDY引脚一旦检测到下降沿就立刻启动SPI读操作。DRDY信号先打两拍同步进来然后做下降沿检测reg drdy_d, drdy_dd; wire drdy_fall drdy_dd ~drdy_d; always (posedge clk or negedge rst_n) begin if (!rst_n) begin drdy_d 1b1; drdy_dd 1b1; end else begin drdy_d drdy_in_sync; drdy_dd drdy_d; end end检测到drdy_fall之后状态机从IDLE跳到READ_START启动SPI控制器读3字节24位读完之后把数据拼起来。整个过程是硬件并行执行的主CPU可以完全不管。4.3 SPI时钟和ADC转换速率的匹配这块有个很典型的误区以为SPI时钟越高越好。对ADC来说SPI时钟只要能在下一次转换完成之前把数据读完就没问题但太高反而可能引起信号完整性问题特别是当你用了杜邦线、面包板、飞线这种不靠谱的连接方式时。我当时的做法是ADC的DRDY周期是1msSPI时钟设成10MHz读24位数据只需要2.4us绰绰有余。这种情况下完全没必要把SPI拉到更高的速率留点裕量给PCB走线和信号完整性。反过来如果你是做高速采集比如ADC输出速率是10MSPS每位16位那SPI就已经扛不住了得选并行接口或者LVDS接口的ADC——这也是为什么热词里会有“fpga的lvds接收”这个东西。SPI适合中低速精密测量高速采集它有天然上限。5. 实例二FPGA驱动SPI Flash做数据记录5.1 从W25Q128到国产Flash指令集基本通用SPI Flash这个场景在FPGA项目里出现频率非常高热词里“xilinx fpga添加华邦w25q”其实是很多人做启动配置的诉求但更多时候FPGA外挂一颗SPI Flash是为了存参数、存日志、存波形数据。以W25Q128为例这是颗16MB的SPI NOR Flash常见的指令0x06写使能WREN0x03读数据READ0x02页编程PP一页256字节0x20扇区擦除SE一个扇区4KB0xD8块擦除BE一个块64KB0x05读状态寄存器RDSR其中bit0是BUSY位国产的很多SPI Flash比如XMC、GD25系列指令集基本跟W25Q系列兼容设计上可以按同一个逻辑来写只是擦除时间、页编程时间可能会有差异需要看手册确认。5.2 页编程必须遵守的“先擦后写”原则SPI NOR Flash有个物理特性写入只能把1写成0要把0恢复成1只能靠擦除。所以写数据之前必须先擦除目标区域。这是新手的“第一课”也是调板中经历得最多的“神秘故障”来源。我最初调试的时候直接往Flash里写数据写完之后读出来发现全是0xFF怎么查都查不到原因。后来翻手册才发现NOR Flash不带“自动擦除”功能写入前必须保证目标地址处于擦除状态全0xFF。所以现在我的帧控制逻辑里写操作之前一定先走一个“擦除流程”发写使能、发扇区擦除命令、轮询BUSY位直到擦除完成。5.3 状态寄存器轮询BUSY位判断擦除和编程操作都需要时间页编程一般几百微秒到几毫秒扇区擦除可能上百毫秒。这段时间内Flash内部在忙你不能发新的写命令。实现方式就是通过读状态寄存器0x05判断bit0是否为0// 轮询BUSY位直到bit00 // 每次发0x05命令 1字节读 // 如果读到的数据的bit01继续轮询0操作完成这里要注意Flash忙的时候如果继续发操作指令它很可能直接忽略掉造成数据丢失。所以设计状态机时在页编程或者扇区擦除之后一定要强制进入“等待不忙”的状态。5.4 写保护位为啥上电之后写不进去SPI Flash大多有写保护机制。W25Q系列有个状态寄存器其中BP位bit2~bit4是块保护位默认上电之后可能是保护状态直接发WREN0x06和PP0x02是写不进去的。解决办法是上电初始化的时候先发0x01命令写状态寄存器把BP位清零也就是写0x00进去。顺序是先发0x06写使能再发0x010x00写状态寄存器清除保护位。这个坑特别隐蔽因为芯片上电之后读写指令看起来都正常响应但数据就是写不进去读出来永远是0xFF。示波器上看波形也没问题结果就是BP位没清。6. 仿真验证看不见波形别上板6.1 写个Testbench把时序逼出来SPI控制器的仿真很直观但一定要写。我习惯是造一个虚拟的从机模型在Testbench里模拟出一个外部从机设备的行为根据SCLK的边沿采样MOSI数据在相应的边沿把MISO数据推出去。Testbench的主体结构类似这样// 虚拟从机在上升沿采样下降沿输出MISO always (posedge sclk) begin if (!cs_n) begin miso_model rx_shift_reg[7]; // 把预先准备的数据逐位移出 rx_shift_reg {rx_shift_reg[6:0], mosi}; end end然后初始化时给主机配置好发送数据拉高start跑完仿真看波形CS信号是否在传输前拉低、传输后拉高SCLK的频率和占空比是否正常MOSI上发出的数据字节和预期是否一致MISO上采到的数据和虚拟从机发出的是否一致done信号是否在一个完整帧结束之后才拉高。仿真能过滤掉一大半低级错误。如果你的习惯是写完代码直接上板然后对着示波器发呆那我建议你改改仿真花半小时可能帮你省下两天的调板时间。6.2 常见仿真问题初始化没复位、数据延迟一拍仿真里最常见的两个问题一个是复位时序不对代码里用了异步复位但是Testbench里复位信号给得太短导致内部状态没复位干净。建议Testbench里复位至少保持100个时钟周期以上。另一个问题是数据总比预期晚一拍这是SPI设计中最典型的现象。根因通常是主机在某个沿更新数据采样沿采到的是上一个时刻的值。排查方法是在波形窗口里把sclk、mosi、miso、状态机、移位寄存器全部拉出来看重点观察数据变化沿和数据采样沿的相对关系。6.3 Vivado/Quartus里的仿真流程Xilinx全家桶用VivadoIntel用Quartus国产高云、紫光也都有各自的IDE。仿真流程基本是写RTL代码写Testbench跑行为仿真Behavioral Simulation看波形。如果代码里用到厂商原语或IP核行为仿真时还需要加对应仿真库。我用Vivado跑仿真时的小习惯是在Testbench里加一个系统任务在done信号拉高时自动比对数据并打印结果always (posedge done) begin if (rx_data expected_data) $display(SPI Test PASS, data %h, rx_data); else $display(SPI Test FAIL, exp %h, got %h, expected_data, rx_data); end这样仿真跑完直接看控制台输出不用肉眼一个个对波形效率高很多。7. 上板调试那些仿真里不会出现的坑7.1 飞线连接的信号完整性灾难如果只是在开发板上测试飞线连接是常态。但SPI时钟上了10MHz以后飞线带来的寄生电容和串扰就会开始捣乱时钟上升沿变缓数据采样的建立时间不满足偶尔出现数据错误。我遇到过最离谱的是一次AD7606的SPI调试飞线连接时钟8MHz数据总是偶发错误。用示波器看波形感觉也没啥大问题但就是时不时读错。后来把飞线剪短并且把SCLK跟MOSI、MISO的线分开走问题消失了。所以SPI调试建议线能短则短10cm以内最好SCLK尽量单独走不要跟数据线绑在一起SPIGND要连最好在两头都连如果迫不得已要用长线就把SPI时钟降下来比如降到1MHz。7.2 示波器抓波形别只盯着数据线调试SPI最直观的工具就是示波器但很多人抓波形时习惯只看MOSI和SCLK忽略了CS和MISO。实际上CS的时序问题比数据线更隐蔽。有些从机要求CS拉低之后再过一定时间才能开始接收时钟这个时间叫CS setup time如果主机没有满足从机可能直接不响应。我的调试顺序是先抓CS和SCLK的配合关系确认CS拉低到第一个SCLK上升沿的时间足够再抓SCLK和MOSI的对齐关系确认数据变化沿和采样沿符合器件手册要求最后再关注MISO的数据是否正常。7.3 ILA在线逻辑分析仪FPGA调试的灵魂工具有些信号是示波器不太好抓的比如FPGA内部控制器的状态机状态、芯片内部寄存器值、FIFO的空满状态这时候就该上ILAIntegrated Logic Analyzer。Xilinx Vivado里用ILA的思路是这样的把想要观察的信号比如state、bit_cnt、sclk、mosi、rx_data等加进ILA的探针列表设置采样深度和触发条件然后重新综合实现下载到板子上运行之后就能实时看到内部信号波形。我在调试SPI Flash写数据失败的时候就是靠ILA抓到了状态寄存器读出值始终是0x0CBP位被置1这个关键信息一下定位到了写保护问题。如果没有ILA这个故障光靠猜测真的很难查。7.4 常见问题速查表现象可能原因排查方法从机无任何响应CPOL/CPHA配置错误查器件手册时序图确认模式数据错位字节对不上数据宽度/位序配置错误检查高位在前还是低位在前偶发数据错误信号完整性差/采样沿不对降速、短线、看示波器边沿写Flash失败读回0xFF写保护未解除发0x060x01清BP位Flash写不进去未先擦除写前先做扇区/块擦除上电后通信异常CS上电时序不对确认上电到CS拉低间隔8. 扩展场景SPI在FPGA里的更多玩法8.1 多从机SPI总线片选信号的艺术当一颗FPGA要同时管理多颗SPI从机时比如一颗ADC加一颗Flash加一颗DAC最简单的方式是共用SCLK、MOSI、MISO每个从机单独分配一个CS信号。多从机场景要特别注意的是CS信号的切换时机。从一个从机切换到另一个从机时一定要保证当前CS已经拉高、总线处于空闲状态再拉低下一个从机的CS。如果切换太快新从机可能还没准备好而旧从机的输出还在驱动MISO两个从机抢总线数据就乱了。一个简单的保护措施帧控制逻辑里在两个CS之间插入至少一个系统时钟周期的空闲间隔。8.2 无线模块和其他外设SPI协议栈的抽象复用SPI这个接口的外设特别多WiFi模块、以太网控制器有些型号是SPI接口、SD卡、TFT屏幕、RF收发器、温度传感器、陀螺仪……如果你把SPI主机控制器的代码抽象得好换外设只需要改帧控制逻辑和参数配置底层完全复用。我现在的做法是把SPI主机控制器做成一个独立的模块对外只暴露简单的接口——给出待发送数据、拉高启动信号、等done、读回数据。上层无论是连ADC、Flash、传感器还是无线模块都通过一组简单的信号交互。模块复用率非常高至少帮我省掉了好几个项目里重复造轮子的时间。8.3 从SPI到远程升级CAPTURE 重配置热词里有“SPI接口的FPGA远程升级”这个方向。FPGA远程升级的思路是系统运行时先把新的配置文件写入SPI Flash的空闲区域然后触发一次FPGA重配置让FPGA从新的地址加载配置。这个方案在很多产品里都有应用核心就是SPI接口的读写可靠性因为一旦配置文件写坏了产品可能就直接变砖。如果要做远程升级一定要考虑“升级失败回滚”机制。常见做法是保留两个配置镜像区一个运行区、一个升级区。升级时先写升级区校验无误后更新启动标志如果升级区校验失败启动代码自动加载旧配置。这个逻辑用SPI通信FPGA实现配合厂商的重配置原语是可行的。还有那些关于“ZYNQ怎么用纯FPGA逻辑”“PCIE”“高速图像处理”方向其实很多都跟SPI有交集PCIe的配置空间可能通过SPI接口挂一颗EEPROM图像处理系统里的传感器初始化、参数配置很多也是SPI。SPI在FPGA系统里就是个“万金油”接口基本功扎实了后面学什么都不慌。9. 我做这个项目时的几点体会代码写完之后我回头看整个SPI调通的过程有几个“早知道就好了”的体会分享给后来的人。第一SPI是同步接口但它对时序的要求一点也不比异步接口低。很多人写SPI代码完全靠“感觉”来不查手册不写仿真直接上板碰运气。SPI通信原理看着简单真做起来CPOL/CPHA、建立时间、采样窗口一个都不能马虎。建议拿到一颗新器件第一件事就是把时序图看懂、吃透对照着写代码而不是边猜边调。第二分层设计真的很重要。底层的SPI主机控制器、中间的帧控制逻辑、上层的应用逻辑这三层分开之后排查问题会容易非常多。如果你把所有逻辑塞在一个超级大的状态机里出问题的时候你会疯掉。第三仿真和实测要结合缺一不可。仿真能帮你验证逻辑正确性实测能帮你发现信号完整性和器件兼容性问题。我只靠仿真不看实测Flash的保护位问题就永远发现不了我只靠实测不仿真光是找代码逻辑错误的时间就得翻倍。第四有条件的话给SPI的引脚加上下拉或者串阻。上电瞬间FPGA引脚状态不确定如果发现器件在上电瞬间有异常响应可以考虑外部上下拉电阻保证默认电平。这个细节在很多工业环境下的板卡上很关键。如果后续再做SPI相关项目可以考虑往这几个方向扩展SPI的时序约束在Vivado里对SCLK生成做约束、SPI DMA方式配合AXI DMA实现大批量数据高速搬运、多SPI主机的冗余方案等。不过那些都是后话了先把基础搞扎实后面自然就顺了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门