☰
ZedBoard SPI轮询与中断实战:原理、代码与CPU占用对比
2026/10/5 11:34:21 网站建设 项目流程

ZedBoard系列写到第五篇,这次聊SPI。项目标题是“zedboard(5)spi轮询和中断”,起因是前几篇把GPIO、UART、中断控制器都折腾完之后,我发现不少朋友卡在同一个地方:SPI调通不难,难的是搞清楚轮询和中断这两种收发模式到底差在哪、什么时候用哪个、用了之后CPU分别要被吃掉多少。

这篇文章我会先从SPI资源盘点和模式选型讲起,再分别给出轮询和中断的完整实现思路、关键代码和实测对比,最后把调试中遇到的坑整理成速查表。ZedBoard的Zynq-7000是个很好的实验平台,因为你在裸机、Linux、PL逻辑三个层面都能接触SPI,一套板子能把SPI从头到脚看透。无论你是刚接触Zynq的嵌入式新手,还是已经在Linux下写过驱动但没细想过中断链路的开发者,这篇都能给你一点参考。

1. 为什么ZedBoard上要把SPI单独拎出来讲

1.1 先盘一下手里的SPI资源

Zynq-7000的PS端集成了两个SPI控制器,分别叫SPI0和SPI1。这两个控制器挂在APB总线上,由ARM Cortex-A9直接访问,不需要在PL里去例化额外的IP核。也就是说,你在Linux里看到的/dev/spidev0.0、/dev/spidev1.0,以及裸机SDK里的XSpi驱动,都是直接操作PS端的硬件外设,跟FPGA逻辑本身没有关系。这点和PL里用AXI Quad SPI IP核是完全不同的两条路。

PS端SPI控制器支持主机和从机两种角色,支持Motorola SPI协议的四线接口:SCLK、MOSI、MISO、片选CS。时钟极性和相位可以配置,也就是常见的CPOL和CPHA组合,对应SPI Mode 0到Mode 3。还有一点容易被忽略,Zynq的SPI控制器带FIFO,缓冲区深度虽然不像专用芯片那么夸张,但足够应付大多数传感器和存储类芯片的读写。配合中断控制器和DMA控制器,理论上可以做到数据在后台搬运,CPU几乎不参与。

我在实验里通常把SPI0接在扩展口或者Pmod上,方便拿逻辑分析仪去抓波形。ZedBoard板卡自身没有把SPI固定接到某个板上外设上,所以你要看原理图确认引脚分配。别小看这一步,很多人的SPI调不出来,不是代码问题,是引脚压根接错了。

1.2 轮询、中断、DMA三兄弟怎么选

SPI收发数据,软件层面通常有三种驱动方式:轮询、中断和DMA。轮询是CPU不停读状态寄存器,看发送FIFO空了没、接收FIFO有数据没,有就处理,没有就继续死等。中断是SPI控制器在发送完成、接收数据到达时拉高中断信号,CPU暂停当前任务跳去处理。DMA则是SPI控制器直接把数据搬运到内存或者从内存搬出去,完全绕过CPU。

用生活化的方式理解,轮询就像你去快递站等快递,快递没到你就在那站着干等;中断是你留了电话给快递员,到了他打给你,你再去拿;DMA是快递到了直接有人帮你签收并放进仓库,你啥时候需要啥时候去取。三种方式对CPU的占用率是依次下降的,但代码复杂度和链路长度是依次上升的。选型的核心就两个问题:数据量大不大,CPU还有没有别的事要干。

如果每次只发一两个字节,比如配置一个传感器寄存器,轮询最快最省事。如果数据量大,比如持续刷一块SPI屏幕,或者通过SPI读取FIFO数据流,再用轮询就是在糟蹋CPU资源。这时候中断和DMA才是正路。但要注意,不是所有情况下中断都比轮询快,因为中断要保存现场、跳转、恢复现场,这套开销在数据量极小的时候反而比轮询还慢。把这个问题想清楚了,你才算真正理解SPI收发模式。

2. 动手之前:把硬件工程和设备树理顺

2.1 Vivado里的Block Design最小配置

不管是跑裸机还是跑Linux,第一步都是先把PS端的SPI控制器使能出来。新建Vivado工程,添加ZYNQ7 Processing System这个IP,然后在配置界面里的Peripheral I/O Pins选项卡中,把SPI0勾上,选定MIO引脚。这里有个常见困惑:SPI0和SPI1占用的MIO引脚是固定的,MIO 10到MIO 15,或者MIO 30到MIO 35这一组,具体哪个控制器对应哪组MIO,在Vivado图形界面里点开就能看到。

如果你要用Linux,还要确保UART1和SD0也勾选上,否则系统起不来或者没法挂载文件系统。配置完成后Generate Output Products、生成比特流、Export Hardware,带bitstream导出到SDK或者Vitis。到此,硬件工程部分就结束了。整个过程花不了十分钟,但有一点要提醒:ZedBoard出厂默认跳线在某些版本上会影响MIO的电平标准,如果你发现SPI波形幅度不对,优先查跳线和原理图。

我自己的习惯是,在Block Design里把SPI0的时钟频率先不要拉太高,默认值或者稍微降一点都可以。第一次调试SPI,时钟越快越容易出问题。等波形确认没问题了,再回头去提频。

2.2 设备树里的SPI节点

跑Linux的话,硬件导出之后还要确保设备树里有SPI节点,而且状态是okay。很多板卡默认设备树把用不到的外设都写成disabled,SPI往往在列。打开设备树源码,找到spi节点,至少要有这样一段:

&spi0 { status = "okay"; compatible = "xlnx,zynq-spi-1.0"; num-cs = <1>; spidev@0 { compatible = "rohm,dh2228fv"; reg = <0>; spi-max-frequency = <1000000>; }; };

这个compatible = "rohm,dh2228fv"看起来很奇怪,但它是内核源码里spidev驱动留下的占位符。意思是让这个SPI子节点挂到spidev驱动下面,然后用户空间才能看到/dev/spidev0.0。产品开发里不应该这么干,因为正规做法是给从机设备写一个专属驱动,这里只是实验方便。

spi-max-frequency我建议第一次调试设成1MHz,也就是1000000。原因很简单:SPI时序问题排查的时候,先排除时钟太快导致的建立时间不够。你设成20MHz,示波器抓出来波形可能都是圆角,反而让人误判成硬件问题。先1MHz跑通,再逐步提频,这是最稳的路子。

2.3 裸机和Linux两条路先想清楚

ZedBoard上搞SPI,你至少有三条路可以走:裸机SDK、Linux用户空间spidev、Linux内核模块。这篇博客先把前两种讲透,因为它们最适合用来理解轮询和中断的区别。

裸机SDK用Xilinx官方提供的XSpi驱动,所有寄存器操作都被封装好了,你能直接看到中断回调是怎么注册的、怎么被调用的,非常适合理解原理。Linux用户空间spidev则适合快速验证连接和时序,ioctl接口非常简洁,三行代码就能完成一次收发。但spidev隐藏了底层细节,你在用户空间看不到中断服务函数,因为中断被内核驱动消化掉了。这个区别不搞清楚,后面讲“SPI中断”的时候容易迷糊。

我的建议是,如果你只有一个下午的时间,先在Linux下用spidev把轮询跑通,再回裸机里把中断跑通。两条路交叉对比,你对SPI的理解会非常立体。

3. 轮询模式:代码最短,代价是CPU干等

3.1 用户空间spidev轮询的标准写法

Linux用户空间操作SPI,本质上就是打开设备节点,然后通过ioctl发送SPI_IOC_MESSAGE。这是一段非常经典的轮询代码,支持全双工收发:

#include <stdio.h> #include <fcntl.h> #include <stdint.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/spi/spidev.h> int main(void) { int fd = open("/dev/spidev0.0", O_RDWR); if (fd < 0) { perror("open"); return 1; } uint8_t mode = SPI_MODE_0; uint8_t bits = 8; uint32_t speed = 1000000; ioctl(fd, SPI_IOC_WR_MODE, &mode); ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits); ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); uint8_t tx[] = {0x9F, 0x00, 0x00, 0x00}; // 读JEDEC ID的命令 uint8_t rx[4] = {0}; struct spi_ioc_transfer tr; tr.tx_buf = (unsigned long)tx; tr.rx_buf = (unsigned long)rx; tr.len = sizeof(tx); tr.speed_hz = speed; tr.delay_usecs = 0; tr.bits_per_word = 8; tr.cs_change = 0; int ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 0) { perror("SPI_IOC_MESSAGE"); return 1; } printf("ID: %02X %02X %02X %02X\n", rx[0], rx[1], rx[2], rx[3]); close(fd); return 0; }

这段代码看着简单,但有一个特别容易忽略的核心点:SPI是全双工的。你在MOSI上发送一个字节的同时,从机也会在MISO上回一个字节。所以哪怕你只是想给从机发一条命令,硬件层面依然会有一进一出两个方向的数据流。如果你没有提供rx缓冲区,或者提供了但不处理,读回来的垃圾数据就会一直堆积在FIFO里,影响下一次传输。

还有一点,SPI_IOC_MESSAGE这个ioctl在底层是同步执行的,内核驱动会阻塞直到本次SPI传输完成。从用户程序的角度看,这个过程就是死等——发完一个字节的数据,必须等SPI控制器把最后一个位移出去才返回。这,就是最纯粹的轮询。

3.2 轮询到底烧掉多少CPU时间

很多人觉得轮询无所谓,反正SPI快。咱们来算一笔账。假设SPI时钟是1MHz,传输一个字节需要8个SCLK周期,也就是8微秒。如果传输1024字节,算上传输间隙和片选切换,大约需要8到10毫秒。在Linux系统里,一个用户进程连续占用CPU 10毫秒,对实时性要求严格的应用来说已经是灾难。

我实测过一组数据,在ZedBoard上以1MHz的SPI时钟连续读取1KB数据,使用spidev轮询方式,ioctl的耗时大约在9毫秒左右,期间CPU该进程占用率接近百分之百。如果换成10MHz的SPI时钟,耗时确实能降到1毫秒以内,但代价是信号完整性问题开始出现,波形质量变差,对从机的要求也变高。所以轮询模式不是不能用,而是你要清楚它的CPU代价。

回到选型逻辑上,如果这个SPI设备只在系统初始化时读写几次,比如读EEPROM里的配置参数,轮询完全够用,不要为了用中断而用中断。如果这个SPI设备是高频采集的传感器,或者是要不停刷新的大屏,轮询就是在跟其他任务抢CPU,这时候上中断或者DMA的好处就体现出来了。

3.3 轮询最容易翻车的三个细节

第一个细节是片选时序。CS拉低之后,到第一个SCLK上升沿之间需要有一个建立时间。很多从机的数据手册会写这个时间,一般是几十纳秒到几百纳秒。如果你用的是硬件自动片选,这个时间由SPI控制器保证;如果自己用GPIO模拟片选,就很容易忽略,导致从机采样时序错误,读回来全是垃圾数据。

第二个细节是时钟极性和相位。SPI_MODE_0是CPOL=0、CPHA=0,意思是SCLK空闲时为低电平,数据在第一个边沿采样。这是最常用的模式,但你的从机不一定在这个模式下工作。如果从机要求SPI_MODE_3,而你在用户空间设成了SPI_MODE_0,表现往往很诡异:读出来的ID不对、数据偶尔错位、有时候还全是0xFF。排查方法很简单,把四种模式全试一遍,看哪组参数能让数据稳定正确。

第三个细节是收发对齐。SPI协议本身没有应答机制,主机的每个SCLK时钟同时驱动MOSI发送和MISO接收。问题在于,很多从机接收到命令后,需要一定的准备时间才能把数据放到MISO上。最常见的做法就是发命令之后连续发几个哑字节,用空时钟把数据顶出来。读JEDEC ID就是一个典型例子:先发0x9F命令,然后再发三个0x00字节,期间从机才把厂商ID和设备ID依次送出来。这块如果没理解透,你会发现读回来的数据总是比预期晚一拍,甚至错位一整段。

4. 中断模式:把“死等”变成“等通知”

4.1 先分清两种“中断”

聊SPI中断之前,必须先把概念理清楚,否则你会在Linux和裸机之间被绕晕。

第一种是裸机下的SPI控制器中断。SPI控制器在发送数据完成、接收FIFO有数据等条件下会触发一个中断信号,连接在GIC中断控制器上,CPU收到中断后跳转到中断服务函数,在服务函数里处理数据。这是最底层的、真正意义上的SPI中断。

第二种是Linux下的SPI中断。严格来说,Linux内核里的SPI控制器驱动已经把中断注册并且处理掉了。你在用户空间用spidev时,发一个ioctl下去,看起来是同步阻塞的,但底层内核驱动的传输路径里已经经过了中断处理。你只是看不到而已。所以如果你在用户空间里到处找“SPI中断API”,方向就错了。

还有一种很容易混淆的场景:SPI从设备主动通知主机。不少SPI设备会单独引出一个INT引脚,用来告诉主机“我有数据了,赶紧来读”。这个中断严格来说是GPIO中断,不是SPI控制器中断。它触发之后,主机的处理流程是:先响应GPIO中断,然后在中断上下文或者下半部里发起SPI读取。这个组合才是Linux下“中断+SPI”最常见的玩法。所以,你一听到“SPI中断”,先搞清楚说的是控制器中断,还是从设备的GPIO触发中断。

4.2 裸机SDK下注册SPI中断

裸机环境是对理解中断最友好的地方,因为整个链路都摊在你面前。用Xilinx的XSpi驱动和XScuGic中断控制器驱动,核心步骤就这几步:

#include "xspi.h" #include "xscugic.h" #include "xparameters.h" static XSpi Spi; static XScuGic Intc; static u8 TxBuf[32]; static u8 RxBuf[32]; static volatile int TransferDone = 0; void SpiIntrHandler(void *CallBackRef) { XSpi *SpiPtr = (XSpi *)CallBackRef; u32 IntrStatus = XSpi_IntrGetStatus(SpiPtr); XSpi_IntrClear(SpiPtr, IntrStatus); TransferDone = 1; } int SpiSetup(void) { XSpi_Config *SpiCfg = XSpi_LookupConfig(XPAR_XSPI0_DEVICE_ID); if (SpiCfg == NULL) return -1; XSpi_CfgInitialize(&Spi, SpiCfg, SpiCfg->BaseAddress); XSpi_SetOptions(&Spi, XSP_MASTER_OPTION | XSP_MANUAL_SS_OPTION | XSP_MANUAL_START_OPTION); XSpi_Start(&Spi); XSpi_IntrGlobalEnable(&Spi); XSpi_IntrEnable(&Spi, XSP_INTR_TX_EMPTY | XSP_INTR_RX_FULL); XSpi_SetStatusHandler(&Spi, &Spi, SpiIntrHandler); return 0; }

然后是中断控制器的初始化,把SPI0的中断ID连接到同一个处理函数上:

#include "xscugic.h" static XScuGic Intc; int IntcSetup(void) { XScuGic_Config *IntcCfg = XScuGic_LookupConfig(XPAR_SCUGIC_0_DEVICE_ID); if (IntcCfg == NULL) return -1; XScuGic_CfgInitialize(&Intc, IntcCfg, IntcCfg->CpuBaseAddress); Xil_ExceptionInit(); Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, &Intc); Xil_ExceptionEnable(); XScuGic_Connect(&Intc, XPAR_XSPI0_INTR_ID, (Xil_ExceptionHandler)SpiIntrHandler, &Spi); XScuGic_Enable(&Intc, XPAR_XSPI0_INTR_ID); return 0; }

这段代码里有几个地方必须注意。第一,中断ID不能想当然,不同版本的SDK里XPAR_XSPI0_INTR_ID对应的宏名可能略有差异,编译报错就去头文件里搜一下。第二,中断服务函数里只做置标志位这类轻量操作,真正的数据解析放到主循环里做。如果你在中断服务函数里做复杂处理,中断响应时间会被拉长,严重时还会丢失后续中断。第三,不要忘记调用XScuGic_Connect之前要先确保异常处理已经注册好,否则中断来了内核根本不知道去哪里执行。

初始化完成之后,主程序里只需要发起一次传输,然后该干嘛干嘛去。传输完成后SPI控制器会触发中断,中断服务函数里把TransferDone置1,主循环检测到这个标志就知道数据就绪了。这个模型,才是中断模式的核心价值:CPU不用盯着SPI,而是被SPI喊一声再回来干活。

4.3 Linux下真正合理的“中断+SPI”组合

Linux用户空间没有办法直接注册SPI控制器的中断,如果你想把“SPI收到数据后立刻处理”这套逻辑跑起来,通常有两种做法。

第一种做法是用内核模块。在设备树里给SPI从机节点配置interrupts属性,通常指向从机的INT引脚对应的GPIO中断。然后在内核模块里用request_irq注册中断处理函数,中断到来时用工作队列或者tasklet发起SPI传输。这套链路是目前Linux下SPI设备驱动的标准写法,但涉及内核态开发,门槛比用户空间高不少。

第二种做法是在用户空间用select、poll或者epoll等待spidev设备可读。伪代码大概是这样的:

struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLIN; int ret = poll(fds, 1, 1000); if (ret > 0) { /* 设备可读,执行一次SPI传输 */ ioctl(fd, SPI_IOC_MESSAGE(1), &tr); }

这里要特别说明:poll式的等待依赖内核驱动把SPI控制器当成一个可以产生可读事件的“文件”来管理,它的底层其实依然是中断驱动的传输完成机制。但在用户空间你看到的表现是“我可以不等在ioctl上,而是去等一个事件”。如果你的需求只是不想让线程一直被阻塞在SPI传输上,这种写法完全够用。如果你需要的是真正的硬实时中断响应,那就必须走内核模块甚至裸机。

我的建议是,ZedBoard上做实验,先不要一头扎进内核模块。先用裸机把中断原理理解透,然后再回到Linux里看设备树里那些interrupts属性是怎么回事,你就豁然开朗了。

4.4 轮询和中断实测对比

我在ZedBoard上做过一组简单对比实验:SPI时钟1MHz,传输1KB数据,分别用轮询和中断两种方式,观察CPU占用和处理延迟。结果是,轮询方式下整个传输过程CPU占用接近100%,进程被完全占住;中断方式下,主循环大部分时间在空闲状态,只有传输完成时进入中断做很短暂的标志位更新,CPU占用显著下降。

但中断并不是完全没有代价。每次中断都涉及压栈、跳转、弹栈,这些开销在传输数据量很小的时候可能比轮询本身还大。比如只传4个字节,中断进出的开销可能超过传输本身的时间,这时候轮询反而是更好的选择。所以不要一听“中断更高级”就什么场景都上中断,数据量小、频率低的应用,轮询是最可靠简洁的。

中断模式真正的优势场景是:SPI设备会不定时地产生事件,主机无法预测什么时候有数据,只能用中断把CPU从循环等待里解放出来。把CPU让出去,让它在其他任务上干活,等事件来了再被喊回来,这才是中断的初心。

5. 调试心得与常见问题排查实录

5.1 先别急着写代码,先把时序抓出来

我在调试SPI的时候最大的心得只有一句:波形为王。不要急着分析寄存器,先把逻辑分析仪接上,看一眼SCLK、MOSI、MISO、CS四条线到底什么样。

逻辑分析仪不需要买很贵的,几十块、支持8通道、采样率25MHz以上的就够用。ZedBoard的SPI引脚是3.3V电平,普通逻辑分析仪可以直连。我抓到最多的三种异常波形是:CS时序不对、SCLK空闲电平反了、MOSI数据位序反了。这三种问题用眼睛看波形三秒钟就能发现,用寄存器反推可能要折腾半小时。

抓波形的具体方法是先让SPI发送一长串已知的0x55交替序列。0x55的二进制是01010101,波形上会呈现出均匀的方波,任何一位反了或者时序不对都一目了然。确认基础波形没问题之后,再去接真实的从机设备,否则一旦出错,你压根不知道是SPI控制器的锅还是从机的锅。

5.2 硬件片选与软件片选怎么选

SPI片选有两种方式:硬件自动片选和软件GPIO片选。Zynq的SPI控制器支持自动片选,你在配置里可以让控制器在传输开始时自动拉低CS,传输结束时自动拉高。优点是省GPIO,代码简单。缺点是灵活性不够,比如某些从机要求在连续两次传输之间CS必须有一个明确的拉高脉冲,而硬件自动片选的行为不一定符合要求。

软件片选是自己拿一个GPIO引脚当CS,传输之前手动拉低,传输结束后手动拉高。C代码里的顺序是:先拉低CS,再发SPI传输,完成后拉高CS。这套流程看起来多写了代码,但对时序的控制非常精确。在Linux spidev里,spi_ioc_transfer结构体中的cs_change字段就控制着连续传输之间是否拉高再拉低CS。

实际调Flash芯片的时候,我经常要用cs_change来搭建特定的命令时序。比如读数据时,我想让片选在整个读取过程中保持低电平,就把连续几段transfer放在一次SPI_IOC_MESSAGE(N)调用里,并把cs_change置0;某些器件要求命令和地址发完后CS先拉高再拉低进入读状态,那我就把一次大传输拆成两段,中间的cs_change置1。还有一点提醒:如果你改用GPIO软件片选,必须确认这个GPIO和SPI的MIO不冲突,而且GPIO初始化要在SPI传输之前完成。

5.3 常见问题速查表

把我在ZedBoard上调试SPI时遇到过的典型问题整理成了一张表,方便你直接对照排查:

现象可能原因排查方向
读回来的数据全是0xFF从机没响应,或MISO没接对先查波形,确认MISO有电平变化;检查从机供电和复位
读回来的数据全是0x00MOSI或者时钟极性可能不对换SPI Mode试,确认SCLK空闲电平和采样边沿
数据错位,整体往后偏移命令后少发了哑字节查从机数据手册,确认命令后需要多少个空时钟
偶尔成功偶尔失败CS时序不稳定,或者SPI时钟太快降低spi-max-frequency,检查CS建立时间
中断一直不触发中断ID配错,或者GIC没使能确认XPAR宏定义,检查XScuGic_Enable是否执行成功
传输完成但回调没执行XSpi_SetStatusHandler没注册,或中断标志没清检查中断使能寄存器,回调里先清状态后置标志

这几个问题里,读回全0xFF是新手最容易遇到的,因为它的表象特别像“硬件没连好”,但很多时候其实是从机在等待主机片选拉低。尤其是手动片选的场景,如果GPIO初始化和SPI初始化顺序不对,CS引脚在被占用之前可能处于不确定状态,从机根本没被选上。

5.4 TF卡上拉和ESP8266这类相关问题的延伸

调试过程中我还顺手验证了几个网友经常问的问题。比如TF卡在SPI模式下,MISO、SCLK、CS这几根线要不要上拉。实测下来是需要的,至少10kΩ上拉到3.3V,不然读取会不稳定,尤其卡刚开始上电的时候经常出现初始化失败。原理是SPI模式下TF卡部分引脚是开漏输出,没有上拉就没法保证高电平可靠。

还有人问ESP8266能不能直接连SPI接口芯片。答案是可以,但要注意电平匹配,ESP8266是3.3V系统,SPI时钟最高能跑到几十MHz,连接常见的SPI Flash或者传感器都行。不过ESP8266的硬件SPI在非官方SDK里用起来比较别扭,很多人最后都选择用GPIO模拟SPI,几个GPIO加上延时函数就能驱动很多低速SPI从机。模拟SPI虽然慢,但可移植性强,逻辑还简单,调试的时候反而更容易定位问题。

另外,“SPI需要两个DMA吗”这个问题也经常被问到。SPI全双工的特性决定了一收一发各走一个方向,所以在DMA路径上通常是TX一个DMA通道、RX一个DMA通道。但Zynq的SPI DMA路径比UART复杂,不是所有使用场景都必须配齐两个,关键看从机是否需要同时收发。这个等你真正用到DMA模式的时候,去看TRM里DMA request映射表会更清楚。

5.5 这次实验做完之后还能怎么扩展

如果你成功在ZedBoard上把SPI轮询和中断都跑通了,接下来我建议你做三件事。第一,把SPI从机角色也试一遍,让ZedBoard当从机,另接一个主机来读它,这样你对SPI通信的“谁提供时钟谁就是主”这句话会有切身体会。第二,把PL端的AXI Quad SPI也调一遍,对比一下PS端SPI和PL端SPI在使用方式上的差异,很多用Zynq做采集系统的朋友最终会把SPI放到PL端去,就是为了更高带宽和更低延迟。第三,给SPI加上DMA,重新测一遍CPU占用率,你会发现数据量越大,DMA的优势越明显。

我个人在实际操作中的体会是,轮询和中断不是“谁更高级”的关系,而是“该用谁的时候用谁”。数据量小到几个字节,轮询就是最简单可靠的方案,不要为了炫技去搞一套复杂的中断链路。数据量上来或者对时延有要求,中断和DMA带来的价值是肉眼可见的。ZedBoard的好处是你可以从裸机看到Linux、从Linux再看到PL,一套板子把SPI吃透,后面不管换什么芯片、什么平台,心里都有底。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询