尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

FPGA实现SD卡WAV音频播放器:从SPI驱动到FAT32解析全流程详解

FPGA实现SD卡WAV音频播放器:从SPI驱动到FAT32解析全流程详解 1. 项目概述与核心价值最近在整理手头的FPGA项目发现很多朋友对如何用FPGA实现一个“能出声”的系统特别感兴趣尤其是涉及到从存储介质读取音频文件并播放的场景。今天就来拆解一个非常经典且实用的例程基于FPGA的SD卡WAV音频播放器。这个项目可以说是FPGA在数字信号处理、接口协议和系统控制方面的一个综合性练兵场它绝不仅仅是让喇叭响起来那么简单。想象一下你有一个FPGA开发板、一张SD卡和一个音频编解码芯片比如WM8731如何让FPGA充当大脑从SD卡里读取一首存储在WAV格式下的音乐然后流畅地播放出来这个过程涉及到SD卡底层的SPI或SDIO协议通信、FAT32文件系统的解析、WAV文件格式的解读、音频数据的缓存管理以及最终通过I2S或I2C协议驱动音频DAC。完成这个项目你对FPGA如何作为一个“片上系统”来协调多个外设和复杂时序会有更深的理解这些技能在工业控制、嵌入式媒体播放终端等领域非常实用。这个例程我把它分为上下两篇来聊上篇我们聚焦在最前端的部分如何让FPGA正确地从SD卡中定位并读取到WAV文件的数据。这是整个系统的基石如果数据都读不出来后面的播放就无从谈起了。下篇则会深入音频数据的传输、时钟同步和DAC驱动。无论你是FPGA的初学者还是想巩固一下存储和文件系统相关知识的开发者相信这个详细的拆解都能给你带来不少干货。2. 系统整体架构与设计思路在动手写代码之前我们必须先搞清楚整个系统是怎么运转的。一个基于FPGA的SD卡音频播放器其核心任务可以抽象为一个数据流管道从物理存储介质SD卡到最终的声音模拟信号。为了实现这个管道我们需要设计一个清晰、稳定的硬件逻辑架构。2.1 核心模块划分整个FPGA工程可以划分为几个关键的功能模块它们之间通过明确的接口进行通信这样做的好处是逻辑清晰便于调试和维护。SD卡控制器模块 (SD Card Controller)这是与SD卡物理接口对话的“翻译官”。SD卡支持多种模式对于大多数FPGA项目为了简化硬件连接和逻辑设计我们通常优先使用SPI模式。该模块需要实现SD卡SPI模式下的初始化、命令发送与响应解析、单块/多块数据读取等功能。它的输出是原始的扇区Sector数据流每个扇区通常是512字节。FAT32文件系统解析模块 (FAT32 Parser)SD卡被格式化为FAT32文件系统后文件并不是连续存放的。这个模块的作用就是理解FAT32的“地图”。它需要读取引导扇区MBR/DBR来获取文件系统关键参数如每簇扇区数、FAT表起始位置然后根据文件名例如“TEST.WAV”在根目录区找到对应的目录项从中获取文件的首簇号。接着通过查询文件分配表FAT像串珠子一样找到文件占用的所有簇并计算出对应的物理扇区地址交给SD卡控制器去读取。WAV文件头解析与数据提取模块 (WAV Parser)从SD卡读出的数据首先是WAV文件的头部。WAV是一种简单的容器格式头部包含了音频数据的“身份证信息”。这个模块需要解析出关键的参数音频编码格式通常是PCM、声道数1为单声道2为立体声、采样率如44.1kHz、位深度如16位以及数据块的大小。解析完成后模块需要跳过文件头将后续纯粹的音频样本数据流输出。音频数据流缓存与时钟域同步模块 (Audio Buffer FIFO)这是保证播放流畅不卡顿的关键。SD卡的读取速度是不均匀的而音频DAC如WM8731要求数据以极其精确和稳定的时钟速率由采样率决定如44.1kHz供给。因此必须设立一个缓冲区通常用FPGA内部的Block RAM实现一个FIFO。SD卡读取侧以较快的、不固定的速率向FIFO写入数据而音频输出侧则以固定的采样率时钟从FIFO中读取数据。FIFO的深度需要精心设计以平衡突发读取和防止上溢/下溢。I2S音频传输控制器模块 (I2S Controller)这是将数字音频样本流转换为标准I2S协议信号的模块。I2S协议主要包含三个信号位时钟BCLK、左右声道时钟LRCK和串行数据SDATA。该模块根据WAV文件头解析出的采样率生成或锁相产生对应的BCLK和LRCK并将FIFO中读出的左右声道样本数据按照I2S的时序规范一位一位地输出到SDATA线上送给WM8731这类音频编解码芯片。I2C配置模块 (I2C Configuration)WM8731芯片在上电后需要初始化其内部寄存器以设置工作模式、采样率、输入输出音量、功耗等。这个配置过程通常通过I2C总线完成。因此FPGA还需要实现一个I2C主控制器在系统启动后向WM8731写入一系列预设的配置值。顶层控制与状态机模块 (Top-level FSM)这是系统的大脑负责协调以上所有模块有序工作。它通常是一个状态机状态包括初始化SD卡 - 初始化WM8731 - 查找WAV文件 - 解析WAV头 - 预填充音频FIFO - 启动音频播放同时持续读取SD卡数据填充FIFO - 文件结束处理。这个状态机还需要处理各种错误情况如SD卡未找到、文件格式错误、FIFO空/满等。2.2 关键设计考量与选型为什么选择SPI模式而不是SDIO对于大多数学习和中等性能需求的应用SPI模式有显著优势接口简单仅需3根线MOSI, MISO, SCLK加片选线FPGA逻辑实现复杂度低几乎所有的FPGA开发板都能直接连接。SDIO模式虽然速度更快但协议复杂需要4位或8位数据总线更适合高性能嵌入式处理器在纯FPGA逻辑中实现代价较高。为什么是FAT32和WAVFAT32是SD卡最通用、兼容性最好的文件系统在Windows、Mac、Linux上都能直接读写方便用户准备测试文件。WAV是一种未压缩的脉冲编码调制PCM格式结构简单解析容易无需复杂的解码算法如MP3、AAC需要专门的解码器非常适合用FPGA硬件直接处理能够直观地体现从数字到模拟的完整链路。注意在开始编码前务必确认你的FPGA开发板原理图。SD卡座的引脚是否直接连接到了FPGA的通用IO上WM8731的I2S和I2C引脚连接是否正确电平标准通常是3.3V是否匹配这些硬件细节是项目成功的先决条件。3. SD卡SPI模式驱动实现详解SD卡驱动是整个数据链的源头其稳定性和正确性至关重要。SPI模式虽然相对简单但初始化流程和命令时序有严格的规范。3.1 硬件连接与上电初始化SD卡在SPI模式下我们主要使用以下引脚CS (Card Select)片选信号低电平有效。SCLK (Serial Clock)SPI时钟由FPGA产生。MOSI (Master Out Slave In)FPGA向SD卡发送命令和数据的线路。MISO (Master In Slave Out)SD卡向FPGA返回响应和数据的线路。上电后SD卡处于SD总线模式。要切换到SPI模式必须遵循一个特定的“邀请”流程在至少74个SCLK周期内保持CS为高不选中MOSI为高这是SD卡的“空闲态”。拉低CS选中SD卡。发送CMD0 (GO_IDLE_STATE)命令其参数为0x00000000CRC校验在SPI模式下通常可以固定为0x95或忽略取决于后续命令。这个命令的目的是让SD卡复位并进入SPI模式。发送CMD8 (SEND_IF_COND)命令用于检查SD卡版本V2.0和电压兼容性。参数中需要包含你支持的电压信息如0x000001AA表示2.7-3.6V检查模式。循环发送CMD55 (APP_CMD)后紧跟ACMD41 (SD_SEND_OP_COND)命令直到ACMD41的响应表明卡初始化完成。这个过程是告诉SD卡“主机已准备好请卡也准备好”。需要持续尝试直到收到0x00的响应表示初始化完成。// 示例CMD0命令发送的Verilog代码片段 localparam CMD0 8‘h40 | 8’d0; // 命令索引0 起始位为01 所以是 0100_0000 0x40 reg [47:0] cmd_packet; cmd_packet {CMD0, 32‘h00000000, 8’h95}; // 组合命令包命令参数CRC // 通过SPI接口在SCLK的下降沿将cmd_packet从高位到低位依次移出到MOSI3.2 数据读取流程与关键状态机成功初始化后就可以读取数据了。读取的基本单位是扇区512字节。使用CMD17 (READ_SINGLE_BLOCK)命令。发送读命令构造CMD17命令包其中参数为要读取的扇区地址LBA地址。对于容量小于等于2GB的SD卡地址单位是字节对于SDHC2GB-32GB地址单位是扇区。我们的代码通常需要适配SDHC/SDXC卡所以使用扇区地址更通用。等待数据令牌命令发送后SD卡会先返回一个响应字节通常期望0x00然后开始发送数据。数据块以一个特定的起始令牌Start Block Token0xFE开始。接收数据检测到0xFE后紧接着的512个字节就是扇区数据。FPGA需要连续读取这512个字节。接收CRC校验数据之后是2个字节的CRC校验码。在SPI模式下为了简化我们常常选择忽略CRC校验直接接收并丢弃即可。后续处理读取完成后需要继续提供SCLK直到CS拉高结束本次传输。实现这个流程需要一个稳健的状态机。状态可能包括IDLE,SEND_CMD,WAIT_RESP,WAIT_TOKEN,RECEIVE_DATA,RECEIVE_CRC,COMPLETE。实操心得在WAIT_TOKEN状态一定要有超时机制。不能无限等待0xFE。如果等待了足够多的SCLK周期比如10000个还没收到就应该跳转到错误处理状态并重试或上报。这是提高驱动鲁棒性的关键。3.3 提高读取性能的注意事项单扇区读取对于播放音频可能勉强够用但为了更流畅尤其是高采样率立体声音频可以采用CMD18 (READ_MULTIPLE_BLOCK)进行多扇区连续读取。这需要SD卡支持并且在读取结束后需要发送CMD12 (STOP_TRANSMISSION)来终止传输。另一个重要技巧是时钟速度。SD卡在初始化阶段SPI时钟必须低于400kHz。初始化完成后可以通过命令切换到更高的时钟速度如12.5MHz, 25MHz。在FPGA中可以通过切换时钟分频器来实现。更快的时钟意味着更高的数据吞吐率能更轻松地满足音频流的实时需求。// 示例时钟速度切换的思路 reg [7:0] clk_divider; always (posedge sys_clk) begin if (sd_init_done) begin clk_divider 8‘d2; // 系统时钟100MHz 分频后得到50MHz SCLK 注意SD卡SPI模式上限通常为25MHz或50MHz需查卡规格 end else begin clk_divider 8’d200; // 初始化阶段低速如100MHz/200 500kHz end end // 用clk_divider生成使能信号进而生成SCLK4. FAT32文件系统解析实战拿到了扇区读取能力我们还需要在SD卡的“文件迷宫”里找到特定的WAV文件。FAT32是微软设计的一种文件系统理解其结构是自主导航的关键。4.1 关键数据结构解析首先我们需要读取主引导记录MBR的第一个扇区LBA 0。MBR的末尾是分区表我们找到第一个有效分区通常是0x0C类型表示FAT32 LBA记录其起始扇区号LBA Begin。然后跳转到该分区的起始扇区即DOS引导记录DBR。DBR里包含了我们需要的所有“地图参数”每扇区字节数Bytes Per Sector几乎总是512。每簇扇区数Sectors Per Cluster这是关键文件存储的基本单位是簇而不是扇区。可能是1, 2, 4, 8...等。保留扇区数Reserved Sectors CountDBR本身及其后面的一些保留区域。FAT表份数Number of FATs通常是2一份主FAT一份备份。根目录最大条目数对于FAT32此值无效FAT32的根目录是簇链的一部分不再是固定区域。每个FAT表大小的扇区数Sectors Per FATFAT表本身有多大。根目录起始簇号Root Cluster NumberFAT32中根目录也是一个文件有起始簇号。有了这些信息我们就可以计算出关键区域的起始扇区FAT1起始扇区 分区起始LBA 保留扇区数数据区起始扇区 FAT1起始扇区 (FAT表份数 * 每个FAT表大小的扇区数)某个簇号Cluster Number对应的第一个扇区地址公式为扇区地址 数据区起始扇区 (簇号 - 2) * 每簇扇区数因为簇号0和1是保留的4.2 定位与解析WAV文件FAT32的目录本质上是一种特殊的文件其内容是由32字节一条的“目录项”组成。我们要在根目录起始簇号已知中查找我们的目标文件例如“TEST.WAV”。读取根目录簇根据根目录起始簇号利用上述公式计算出对应的扇区地址用SD卡控制器读取整个簇的数据可能是多个连续扇区。遍历目录项在读取到的数据缓冲区中以32字节为步进进行解析。目录项的结构包括文件名8字节扩展名3字节注意是短文件名格式不足部分用空格填充。“TEST.WAV”会被存储为“TEST WAV”。属性1字节0x20表示归档文件普通文件。首簇号高16位/低16位各2字节这是文件内容开始的簇号需要组合成一个32位的值。文件大小4字节单位是字节。匹配文件名将缓冲区中的文件名、扩展名与目标字符串进行比较。找到匹配的条目后提取其首簇号和文件大小。追踪簇链文件可能不是连续存储的。通过首簇号去FAT表中查找该簇号对应的表项。FAT32的表项是32位的但高4位通常保留。表项的值指示了文件的下一个簇号。如果值在0x00000002到0x0FFFFFEF之间表示下一个簇号如果是0x0FFFFFFF则表示这是文件的最后一个簇EOF。通过循环查询FAT表我们可以得到文件占用的所有簇的列表。转换为扇区地址列表将得到的簇号列表通过扇区地址 数据区起始扇区 (簇号 - 2) * 每簇扇区数公式转换为最终的物理扇区地址列表。SD卡控制器将依据这个列表依次读取数据。避坑指南FAT32支持长文件名会使用多个连续的目录项来存储一个文件的Unicode长名并在最后跟一个短文件名目录项。我们的简单解析器可以只处理短文件名项。在遍历目录时如果遇到目录项的第一个字节是0xE5表示该条目已被删除如果是0x00表示此后没有有效条目了可以停止搜索。这能有效提高搜索效率。5. WAV文件格式解析与数据提取从SD卡读出的首先是WAV文件的头部。WAV是RIFF文件格式的一种其结构非常规整像是一个容器里面装着描述信息和实际数据。5.1 WAV文件头结构详解一个典型的PCM格式WAV文件头长度为44字节其结构如下偏移量从文件开始计算0-3字节RIFF固定标识表示这是一个RIFF格式文件。4-7字节文件总大小减8从下一个地址开始到文件末尾的总字节数。8-11字节WAVE固定标识表示这是WAVE类型。12-15字节fmtfmt块的标识注意最后有个空格。16-19字节fmt块大小对于PCM通常是16。20-21字节音频格式1表示PCM线性量化这是我们需要的。22-23字节声道数1为单声道2为立体声。24-27字节采样率每秒采样次数如441000x0000AC44。28-31字节字节率每秒数据字节数 采样率 * 声道数 * (位深度/8)。32-33字节块对齐每个采样帧的字节数 声道数 * (位深度/8)。34-35字节位深度每个采样点的位数如16。36-39字节data数据块的标识。40-43字节数据块大小音频数据的总字节数。5.2 FPGA中的解析逻辑实现在FPGA中我们通常用一个状态机或顺序逻辑来解析这44字节的头信息。缓冲头部数据当FAT32解析器开始读取文件时前44个字节可能跨越扇区边界需要被缓存到一个专门的寄存器组或小型RAM中。校验标识依次检查RIFF、WAVE、fmt、data这四个标识是否正确。任何一步出错都应视为文件格式错误停止播放并报错。提取关键参数从固定的偏移地址读取数据并赋值给对应的寄存器audio_format确认是否为1PCM。num_channels存储声道数。sample_rate存储采样率这个值将用于生成I2S的位时钟BCLK和左右声道时钟LRCK。bits_per_sample存储位深度通常是16。data_size存储音频数据的总字节数用于计算播放时长和判断文件结束。计算衍生参数根据上述参数可以计算出bytes_per_framenum_channels* (bits_per_sample/ 8)。对于16位立体声就是 2 * 2 4字节。I2S所需的bclk_freqsample_rate*bits_per_sample*num_channels。对于44.1kHz 16位立体声就是 44100 * 16 * 2 1.4112 MHz。定位数据起始点解析完成后我们就知道从文件开始的第44字节或根据data块标识后的data_size字段计算出的偏移量之后就是纯粹的PCM音频数据了。后续SD卡读取到的数据从这一点开始就可以直接送入音频数据FIFO。注意事项WAV文件头有时会大于44字节例如包含一些扩展信息fact块、列表信息等。一个健壮的解析器应该通过查找data块标识来动态定位数据开始位置而不是硬编码偏移44字节。具体方法是在找到fmt块并读取其大小后跳过该块然后继续读取后续的块标识和块大小直到找到data标识。这样能兼容更多种类的WAV文件。6. 音频数据流缓冲与FIFO设计这是连接“不匀速”的SD卡读取和“匀速”的音频输出的桥梁是保证播放音质连续、无爆音的关键模块。6.1 FIFO的必要性与参数计算SD卡读取受限于SPI时钟、命令响应时间、寻道时间虽然连续读取时影响小等因素其数据供给是“突发式”的即一段时间内高速送来一个扇区512字节的数据然后可能有一小段空闲。而I2S输出要求每个采样周期例如对于44.1kHz是22.676us都必须有新的样本数据就绪否则就会重复输出旧数据或静音导致卡顿或爆音。FIFO的作用就是平滑数据流。SD卡侧在数据就绪时快速写入FIFOI2S侧则以恒定的采样率时钟从FIFO中读取数据。只要FIFO的深度足够大就能吸收掉SD卡读取的延迟波动保证I2S侧永不“断粮”。如何确定FIFO的深度这是一个权衡。深度太小容易下溢读空深度太大浪费FPGA的Block RAM资源且会增加从按下播放键到实际出声的初始延迟。 一个实用的估算方法是确定SD卡读取的最大延迟时间T_read_max。这包括发送读命令、等待响应、传输一个数据块比如4个扇区2048字节的总时间。可以保守估计例如2ms。确定音频数据的消耗速率。对于16位立体声44.1kHz消耗速率为 44100 * 4字节/秒 ≈ 176.4 KB/s。在最大延迟时间内I2S侧会消耗掉的数据量 消耗速率 * T_read_max 176.4 KB/s * 0.002s ≈ 353字节。因此FIFO的深度至少需要能容纳一次突发读取的数据量2048字节并且最好留有额外余量以应对延迟波动。一个常见的设计是深度为4096字节即8个扇区或更深。深度最好是2的幂次便于地址管理。6.2 双时钟域FIFO的实现与同步由于SD卡控制器和I2S控制器很可能运行在不同的时钟域例如SD卡SPI时钟是25MHz而I2S主时钟可能是11.2896MHz或由PLL生成这个FIFO必须是异步FIFO。在FPGA中我们可以使用厂商提供的IP核如Xilinx的FIFO Generator Intel的RAM-based FIFO来快速生成一个异步FIFO。配置时需要注意写时钟域连接SD卡数据读取有效的时钟可能是SPI时钟分频后的数据有效时钟。读时钟域连接I2S模块的位时钟BCLK或主时钟。数据宽度通常设置为16位或32位。如果音频数据是16位单声道16位宽即可如果是16位立体声一次读取需要左右声道各16位共32位因此FIFO数据宽度设为32位更高效。深度根据上述计算设定如1024个32位字即4096字节。标志位需要用到“空empty”和“满full”标志。写逻辑在!full时写入读逻辑在!empty时读取。几乎满almost_full和几乎空almost_empty标志也很有用可以用于提前预警实现更流畅的控制。实操心得FIFO的初始填充与流控在状态机中不能等到FIFO空了才去读SD卡。一个良好的策略是在开始播放前先读取一定量的数据例如填充FIFO深度的一半进行初始预填充。播放开始后监控FIFO的almost_empty标志例如设定为深度1/4。当触发almost_empty时状态机发起一次多扇区读取请求向FIFO写入一批数据。同时监控almost_full标志避免写入过多导致溢出。这种“水位线”控制策略能有效平衡读写速度差实现平滑播放。7. 常见问题与调试技巧实录即使逻辑设计看起来完美第一次调试也难免遇到问题。下面分享一些在这个项目中常见的“坑”和解决方法。7.1 SD卡初始化失败现象发送CMD0后收不到任何响应或一直返回0xFF。排查硬件连接用示波器或逻辑分析仪检查CS、SCLK、MOSI、MISO四根线。确认CS在上电后是否有至少74个时钟的高电平SCLK在初始化阶段是否低于400kHzMOSI线上是否有正确的命令波形上电时序确保FPGA配置完成、逻辑开始运行后SD卡已稳定上电。可以尝试在FPGA代码中增加一个上电延时例如等待1毫秒再开始初始化序列。命令CRC对于CMD0尝试发送标准的CRC 0x95。有些卡在SPI模式下忽略CRC有些则要求正确。可以都试试。卡类型确认你的代码是否同时支持SDSC标准容量 2GB和SDHC高容量 2GB。CMD8和ACMD41的流程对SDHC卡是必须的。7.2 能初始化但读不到数据现象CMD17发送后能收到0x00响应但永远等不到数据起始令牌0xFE。排查地址格式确认你发送给CMD17的地址是扇区地址LBA而不是字节地址。对于SDHC卡CMD17的参数就是扇区号。一个常见的错误是对SDHC卡使用了字节地址。时钟相位/极性SPI模式有四种模式CPOL, CPHA。SD卡在SPI模式下固定为模式0 (CPOL0, CPHA0)。即时钟空闲时为低电平数据在时钟上升沿采样或下降沿变化。务必确认你的SPI控制器时序与此一致。等待时间发送读命令后SD卡需要准备数据这段时间称为“忙期”。在等待0xFE之前需要持续读取MISO线直到读到的不是0xFF。这个非0xFF的字节就是响应。响应之后可能还有一段忙期才会出现0xFE。确保你的状态机有足够的等待状态。7.3 播放声音卡顿或杂音现象声音能出来但是不连续有“噼啪”声或断断续续。排查FIFO下溢这是最常见的原因。使用SignalTap或ChipScope等嵌入式逻辑分析仪抓取FIFO的空标志、读写使能和数据计数。查看播放过程中FIFO是否被读空。如果是说明SD卡读取速度跟不上消耗速度或者FIFO深度不足。尝试增加FIFO深度或者优化SD卡读取为多扇区连续读CMD18。时钟精度I2S的位时钟BCLK和主时钟MCLK如果WM8731需要必须非常精确。例如44.1kHz的采样率对应的BCLK必须是精确的1.4112MHz。如果FPGA使用普通的时钟分频产生可能会有累积误差。最好使用FPGA内部的PLL或MMCM通过锁相环来生成精确的音频时钟。数据对齐错误确认从FIFO读出的32位数据是如何拆分成左右声道16位样本的。I2S协议有左对齐、右对齐、I2S标准格式。WM8731通常配置为I2S格式即LRCK变化后的第二个BCLK上升沿开始传输最高位MSB。确保你的I2S发送模块时序和数据拆分与此匹配。电源噪声模拟音频部分对电源噪声非常敏感。检查开发板上音频编解码芯片的模拟电源滤波是否良好。在靠近芯片的电源引脚处增加去耦电容。7.4 FAT32文件找不到现象SD卡初始化成功可以读扇区但遍历根目录找不到文件。排查SD卡格式化确认SD卡是用FAT32格式化的并且是主引导记录MBR分区表而不是GUID分区表GPT。有些大容量卡默认用GPT可能需要重新格式化为MBR。目录项解析将读出的根目录扇区数据通过UART打印到电脑上用十六进制查看。手动核对目录项的结构。特别注意文件名和扩展名是空格填充的并且是大写。检查你代码中字符串比较的逻辑。根目录位置对于FAT32根目录是簇链的一部分其起始簇号存储在DBR中。确保你正确读取了根目录起始簇号而不是使用FAT16中固定的根目录扇区偏移。长文件名干扰如果你在电脑上创建的文件名是“test.wav”系统可能会同时生成一个长文件名条目和一个短文件名条目如“TEST~1.WAV”。你的解析器可能只匹配短文件名。尝试在代码中搜索短文件名或者直接在电脑上将文件重命名为全大写、8.3格式的“TEST.WAV”。调试是一个系统工程从硬件到软件从协议到时序。最有效的工具是逻辑分析仪可以同时抓取SPI总线、I2S总线、关键状态信号和FIFO标志位将复杂的交互过程可视化从而快速定位问题所在。在FPGA内部插入一些调试用的计数器或状态寄存器通过LED或UART输出也是低成本且高效的调试手段。
返回列表