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

深入解析DMA技术:从原理到TMS320F2837xS实战应用

1. 项目概述与DMA的核心价值

在嵌入式系统开发,尤其是实时控制和高性能数据采集领域,我们常常面临一个核心矛盾:CPU的算力是宝贵的,但大量时间却被“搬砖”——也就是在内存和外设之间搬运数据——这类简单重复的I/O操作所消耗。想象一下,你是一位工厂里最顶尖的工程师,却不得不花大量时间亲自去仓库取零件、再把成品搬回仓库,这无疑是巨大的人才浪费。直接内存访问(DMA)技术,就是为了解决这个矛盾而生的“自动化物流系统”。

DMA的本质,是在芯片内部集成一个专司数据搬运的“协处理器”。当外设(如ADC模数转换器)产生了一批新数据,或者内存中有一批数据需要发送给通信接口(如SPI)时,不再是CPU停下手中的计算任务,通过执行load/store指令来一个字节一个字节地搬运,而是由DMA控制器接管这个任务。CPU只需要在初始化时告诉DMA:“从A地址开始,搬N个数据到B地址,搬完了告诉我一声”,就可以继续去执行核心算法或响应其他事件。DMA会在后台默默完成所有数据传输,整个过程无需CPU干预,从而将CPU彻底解放出来。

以我手头常用的TI C2000系列微控制器,特别是TMS320F2837xS这款高性能实时MCU为例,其DMA模块的设计堪称典范。它不仅仅是一个简单的数据搬运工,更是一个高度可编程、具备数据预处理能力的智能引擎。它支持多达6个独立通道,每个通道都可以由不同的事件(如ADC转换完成、定时器溢出、外部中断)触发;它能在传输过程中对数据进行“正交重排”,把杂乱的数据流整理成便于CPU处理的规整块;它还支持“乒乓缓冲”这种高级数据流管理技术,实现数据搬运和处理的完美流水线化。理解并用好DMA,是从嵌入式新手迈向资深工程师的关键一步,它能让你设计的系统在性能、实时性和能效上产生质的飞跃。

2. DMA架构与核心工作机制深度解析

要驾驭DMA,不能只停留在“配置寄存器让它动起来”的层面,必须深入理解其内部架构和工作原理。这就像开车,知道踩油门能走只是第一步,了解发动机、变速箱和传动系统如何协同工作,才能应对复杂路况,发挥车辆的全部性能。

2.1 整体架构与总线仲裁

TMS320F2837xS的DMA模块是一个独立于CPU的子系统,拥有自己的32位地址总线和32位数据读写总线。这意味着DMA可以和CPU并行工作,访问共享的内存和外设资源。其架构核心是一个精巧的状态机和紧密耦合的地址控制逻辑。

一个关键且容易忽略的细节是“公共外设架构”。芯片上的许多外设(如ePWM、SPI)的寄存器,可以被CPU、DMA以及另一个协处理器CLA访问。这就引入了资源冲突的可能性。为了解决这个问题,芯片内部有一个仲裁机制。例如,当DMA和CPU在同一周期试图访问同一个外设时,DMA拥有更高的优先级,CPU的访问会被“阻塞”一个周期,直到DMA操作完成。这种设计保证了DMA传输的确定性和低延迟,但对于CPU端的实时任务规划提出了要求——你需要避免让CPU频繁访问那些可能被DMA占用的外设寄存器。

注意:这里有一个重要的实践细节。手册中特别警告,如果CPU正在对一个内存位置进行“读-修改-写”操作(例如,对一个控制寄存器进行位操作),而DMA恰好在这条指令的“读”和“写”之间向同一位置写入数据,那么DMA的这次写入可能会丢失。因此,在软件设计时,应避免CPU和DMA对同一内存地址进行写操作,或者使用信号量等机制进行保护。

2.2 事件触发机制:让数据动起来的“发令枪”

DMA是一个“事件驱动”的机器,它不会自己周期性地启动传输,必须等待一个触发信号。这个设计非常符合嵌入式系统响应外部事件的本质。TMS320F2837xS为每个DMA通道提供了极其丰富的触发源选择,通过DMACHSRCSELxCHx.MODE.PERINTSEL寄存器进行配置。

触发源列表几乎涵盖了所有可能产生数据的外设:

  • ADC模块:四个ADC模块(A, B, C, D)各自的序列转换完成中断(INT1-4)或事件(EVT),这是数据采集应用最常用的触发源。
  • ePWM模块:12个ePWM模块的SOCA/SOCB(启动转换)信号,可以实现与PWM波形严格同步的精确采样。
  • 外部中断(XINT):5个外部中断引脚,可以将GPIO上的外部事件(如传感器信号)直接作为DMA触发。
  • 定时器中断(TINT):3个CPU定时器中断,可以将DMA配置为定时启动的“软周期”传输。
  • 通信外设:SPI的发送/接收FIFO就绪信号,McBSP的多通道缓冲串行口事件等。
  • 软件触发:通过设置CONTROL.CHx[PERINTFRC]位,可以随时用软件强制触发一次DMA传输,用于初始化或测试。

触发逻辑内部有一个锁存器(Latch)。当外设产生一个触发事件,PERINTFLG标志位会被置起,表示该通道有一个传输请求“待处理”。DMA的状态机根据通道优先级,依次处理这些待处理的请求。一旦某个通道的“突发传输”开始,其PERINTFLG标志位就会被自动清除,以准备接收下一个触发事件。这个机制是理解DMA流控的基础。

3. DMA传输的精密控制:地址指针与状态机

DMA传输的控制逻辑,本质上是由两个嵌套循环构成的状态机:内层的“突发循环”和外层的“传输循环”。理解这两个循环以及相关的地址指针、步进和包装机制,是进行复杂数据传输编程的关键。

3.1 核心概念:突发与传输

  • 突发(Burst):这是DMA响应一次触发事件所执行的最小传输单元。一次突发传输的数据量由BURST_SIZE寄存器定义。例如,BURST_SIZE = 15表示一次突发传输16个数据单元(因为实际传输数是BURST_SIZE + 1)。这个数据单元可以是16位或32位,由MODE.DATASIZE位决定。突发传输是原子性的,一旦开始,就会连续完成指定数量的数据传输,期间不会被同优先级的其他通道打断。
  • 传输(Transfer):一个完整的DMA传输任务由多次突发构成。总突发次数由TRANSFER_SIZE寄存器定义。完成整个传输后,DMA可以产生一个中断通知CPU。因此,一个完整的DMA传输总共搬运的数据量是:(BURST_SIZE + 1) * (TRANSFER_SIZE + 1)个数据单元。

3.2 地址指针的“双缓冲”与步进控制

DMA的地址管理非常灵活,它有两套指针:“影子寄存器”和“活动寄存器”。

  • 影子寄存器(Shadow Registers):如SRC_ADDR_SHADOWDST_ADDR_SHADOW。这些寄存器由软件在初始化时配置,代表了传输任务的“蓝图”。在传输过程中,软件可以随时修改它们,而不会影响正在进行的传输。
  • 活动寄存器(Active Registers):如SRC_ADDR_ACTIVEDST_ADDR_ACTIVE。这些寄存器由硬件在每次传输开始时,从对应的影子寄存器加载,并在传输过程中实时更新,指向当前正在读写的内存地址。

地址指针的移动由三组“步进”寄存器控制,它们都是有符号数,支持地址递增或递减:

  1. 突发步进(BURST_STEP):在一次突发传输内部,每完成一个数据的搬运后,源地址和目的地址增加的偏移量。例如,从ADC结果寄存器(地址连续)搬运数据到内存数组,通常将SRC_BURST_STEP设为0(因为总是读同一个ADC结果寄存器地址),DST_BURST_STEP设为2(如果数据宽度是16位,地址+2指向下一���内存字)。
  2. 传输步进(TRANSFER_STEP):在两次突发传输之间,即完成一次突发后、等待下一个触发事件前,源地址和目的地址增加的偏移量。这常用于处理非连续的数据块。
  3. 包装步进(WRAP_STEP):这是实现“乒乓缓冲”或“环形缓冲区”的关键。当使能地址包装功能后,每完成一定数量的突发(由WRAP_SIZE定义),活动地址指针不会简单地加上TRANSFER_STEP,而是会被重置为BEG_ADDR(开始地址),同时这个BEG_ADDR自身会加上WRAP_STEP。这实现了指针在多个缓冲区之间“跳跃”。

3.3 关键工作模式:OneShot与Continuous

  • OneShot模式:此模式默认禁用。当ONESHOT=0时,每次触发事件只引起一次突发传输。这是最常用的模式,确保了单个通道不会独占DMA总线。当ONESHOT=1时,一次触发事件将导致整个传输循环(所有突发)被连续执行完毕。这非常危险,因为一次触发可能占用极长的总线时间,阻塞其他所有通道,仅在对实时性要求极高、且数据块较小的特定场景下谨慎使用。
  • 连续模式:此模式默认禁用。当CONTINUOUS=0时,完成整个传输循环(TRANSFER_COUNT减到0)后,该DMA通道会自动禁用,需要软件重新设置RUN位才能开始下一次传输。当CONTINUOUS=1时,传输完成后通道保持使能状态,自动等待下一轮触发,适用于需要持续不断进行数据搬运的场景。

3.4 数据传输的“流水线”与吞吐量计算

DMA内部采用三级流水线操作:发送源地址 -> 读取源数据 -> 写入目的数据。在理想无冲突情况下,传输一个数据单元(16位或32位)需要3个时钟周期。但实际吞吐量还需考虑以下因素:

  • 每个突发传输开始时有1个周期的固定开销。
  • 如果被高优先级通道(如通道1)打断,恢复时会有1个周期的延迟。
  • 与CPU或CLA访问冲突时,会产生额外的等待周期。
  • 最关键的一点:32位传输的效率是16位的两倍。因为硬件总线是32位的,传输一个32位字和传输一个16位字的时间几乎相同。

我们来算一笔账:假设需要从GS0 RAM搬运128个16位数据到GS3 RAM。

  • 方案A(16位模式):配置为8次突发,每次突发16个数据。耗时 =8 bursts * [(3 cycles/word * 16 words) + 1 cycle overhead] = 392 cycles
  • 方案B(32位模式):将数据视为64个32位字。同样8次突发,每次突发8个32位字(等效16个16位数据)。耗时 =8 bursts * [(3 cycles/word * 8 words) + 1 cycle overhead] = 200 cycles

效率提升接近一倍!因此,在内存对齐允许的情况下,应优先使用32位数据宽度进行DMA传输。

4. 通道优先级与仲裁策略

当多个DMA通道同时有数据传输请求时,需要一套公平且高效的仲裁规则。TMS320F2837xS的DMA支持两种优先级模式,理解它们对设计多任务数据流系统至关重要。

4.1 轮询模式

这是默认模式,所有6个通道(1-6)优先级平等。DMA内部维护一个轮询指针,按照CH1 -> CH2 -> CH3 -> CH4 -> CH5 -> CH6 -> CH1...的顺序依次服务。

这里有一个非常关键且反直觉的细节:轮询的顺序是固定的,与触发到达的先后无关,只与当前服务的通道和轮询顺序有关。举个例子:

  1. 假设CH1、CH4、CH5三个通道使能。
  2. 当前DMA正在服务CH4的一次突发传输。
  3. 在CH4传输完成前,CH1和CH5的触发事件同时到达。
  4. CH4传输完成后,DMA的轮询指针会指向列表中的下一个已使能通道,即CH5。因此,即使CH1先触发,也会先服务CH5
  5. CH5完成后,指针指向CH1,服务CH1。

这种机制保证了所有通道的长期公平性,避免了某个高频率触发通道饿死其他通道。但它要求工程师在设计时,必须考虑最坏情况下的通道服务延迟。

4.2 通道1高优先级模式

在此模式下,通道1拥有绝对优先权,可以打断正在进行的其他通道(2-6)的传输。通道2-6之间仍采用轮询模式。

工作流程如下:

  1. DMA正在服务CH4的传输(可能一次突发还没完成)。
  2. CH1的触发事件到达。
  3. DMA会在完成当前正在进行的这一个数据字的传输后,立即暂停CH4,转而去服务CH1的整个突发传输。
  4. CH1的突发传输全部完成后,DMA再回到CH4被暂停的地方,继续完成剩余的传输。

这种模式专为对延迟极其敏感、数据率高的外设设计,例如高速ADC。手册特别警告:通道1的高优先级模式和OneShot模式不能同时启用。因为OneShot模式下通道1一次传输的数据量可能非常大,如果它还能打断别人,将导致其他通道被长时间阻塞,违背了实时系统的设计原则。

5. 高级应用:乒乓缓冲与数据重排

DMA不仅仅是搬运工,更是数据预处理的高手。其“乒乓缓冲”和“正交数据重排”功能,能极大优化CPU的数据处理流程。

5.1 乒乓缓冲实现零等待数据处理

乒乓缓冲是一种经典的双缓冲区技术。它需要两个大小相同的内存缓冲区:Buffer A和Buffer B。

  1. 阶段一:DMA被配置为连续模式,并将目的地址指向Buffer A。当外设(如ADC)产生数据时,DMA自动将数据填入Buffer A。
  2. 阶段二:当Buffer A填满时,DMA产生一个中断(通过设置CHINTMODE为传输结束中断)。CPU收到中断后,开始处理Buffer A中的数据。
  3. 关键点:在CPU处理Buffer A的同时,DMA并没有闲着。通过配置地址包装功能,在Buffer A满的瞬间,DMA的目的地址指针会自动“包装”到Buffer B的起始地址。因此,新的外设数据会源源不断地填入Buffer B,而不会覆盖正在被CPU处理的Buffer A。
  4. 阶段三:Buffer B填满,DMA再次产生中断,并将目的地址指针包装回Buffer A。此时CPU应已处理完Buffer A,可以转而处理Buffer B,而DMA则向Buffer A写入新数据。

如此循环往复,实现了数据采集和处理的完全并行,CPU永远在处理“上一帧”完整数据,DMA永远在填充“下一帧”数据,两者互不干扰,系统吞吐量达到最大。

配置要点:

  • 设置CONTINUOUS = 1,使通道在传输完成后自动重新使能。
  • 配置TRANSFER_SIZEBURST_SIZE,使得一次传输正好填满一个缓冲区。
  • 配置DST_WRAP_SIZE,使其等于TRANSFER_SIZE(或略小),并设置DST_WRAP_STEP为两个缓冲区之间的地址偏移量。这样,每次传输完成(即填满一个缓冲区)后,目的地址会自动跳转到另一个缓冲区。
  • CHINTMODE设置为在传输结束时产生中断,以通知CPU处理数据。

5.2 数据重排:化乱为整的魔法

许多外设,特别是高速ADC,其采样数据在内存中的存储顺序可能并不符合CPU高效处理的需求。例如,一个多通道ADC交替采样两个通道(CH1, CH2),得到的数据流可能是:CH1_S1, CH2_S1, CH1_S2, CH2_S2, CH1_S3, CH2_S3...。如果CPU想分别处理所有CH1的数据和所有CH2的数据,就需要在内存中重新排序,这又会消耗CPU周期。

DMA的数据重排功能可以在传输过程中自动完成这个工作。它通过巧妙地配置源/目的地址的BURST_STEPTRANSFER_STEP来实现。

示例:将交错的ADC数据分离���两个独立的数组。假设ADC结果寄存器基地址为AdcResult,我们想将100个点的CH1和CH2数据分别存放到数组Ch1_Data[100]Ch2_Data[100]中。

  • 思路:将一次“传输”定义为处理一对数据(CH1和CH2各一个)。一次“突发”只搬运一个数据。
  • 配置
    • BURST_SIZE = 0(每次突发搬1个数据)
    • TRANSFER_SIZE = 99(总共100对数据,即100次突发)
    • SRC_BURST_STEP = 4(假设每个ADC结果寄存器是16位,地址偏移为2。交错存储,所以从CH1结果跳到下一个CH1结果,地址需要跳过一个CH2,因此步进为4)
    • SRC_TRANSFER_STEP = -198 * 2(这是一个技巧。完成一次传输(即搬完一对数据)后,需要将源地址回退到起始位置附近,以便开始搬下一对数据。具体计算需要根据内存布局来调整)
    • DST_BURST_STEP = 2(目的地址每次增加一个数据单元的长度)
    • DST_TRANSFER_STEP = 200(完成一对数据的搬运后,目的地址需要跳到另一个数组的起始位置,两个数组各100个数据,所以偏移是200个数据单元地址)

通过这样的配置,DMA就能自动将交错的数据流,整理成两个连续的数据块。这背后的硬件逻辑就是利用了之前提到的“传输循环”和“突发循环”中对地址指针的不同步进控制。掌握这个技巧,能解决很多实际项目中的数据格式化难题。

6. 实战配置指南与常见问题排查

理论最终要服务于实践。下面我将以一个典型的ADC多通道采样并通过DMA存储的案例,拆解配置步骤,并分享几个我踩过的“坑”。

6.1 典型配置流程:ADC+DMA数据采集

目标:配置ADC序列采样4个通道,每次序列转换完成触发DMA,将4个通道的结果搬运到长度为100的循环缓冲区中。

  1. 初始化ADC:配置ADC工作模式、采样通道、触发源(例如ePWM的SOC)和序列长度(4个转换)。
  2. 配置DMA通道
    • 选择触发源:在DMACHSRCSELx寄存器中,选择对应ADC模块的序列完成中断作为触发源(例如ADCAINT1)。
    • 配置通道模式MODE.CHx[PERINTSEL]设置为该通道号。DATASIZE根据ADC结果寄存器宽度选择(通常16位)。ONESHOTCONTINUOUS根据需求设置,本例中CONTINUOUS=1以实现循环缓冲。
    • 配置传输尺寸
      • BURST_SIZE = 3(一次突发搬运4个数据,对应4个ADC通道)。
      • TRANSFER_SIZE = 99(总共搬运100次突发,即100组4通道数据)。
    • 配置地址指针与步进
      • 源地址SRC_ADDR_SHADOW:设置为ADC结果寄存器的基地址。
      • SRC_BURST_STEP = 2(ADC结果寄存器通常是连续地址,每个结果占16位,地址步进为2)。
      • SRC_TRANSFER_STEP = 0(每次突发后,源地址应回到起始的ADC结果寄存器地址)。
      • 目的地址DST_ADDR_SHADOW:设置为循环缓冲区在内存中的起始地址(如&AdcBuffer[0])。
      • DST_BURST_STEP = 2(数据在内存中连续存放)。
      • DST_TRANSFER_STEP = 8(每次突发搬运4个16位数据,地址增加8。这样100次突发后,正好写满400个数据单元的缓冲区)。
    • 配置包装实现循环缓冲
      • DST_WRAP_SIZE = 99(与TRANSFER_SIZE相等,表示每次传输完成时触发包装)。
      • DST_WRAP_STEP = -800(100次突发 * 每次地址增加8 = 总地址增量800。包装步进设为-800,使得目的地址指针在缓冲区满后,跳回缓冲区起始地址)。
      • DST_BEG_ADDR_SHADOW设置为与DST_ADDR_SHADOW相同的值。
    • 配置中断:使能通道中断(CHINTE=1),并设置CHINTMODE=1在传输完成(即缓冲区满)时产生中断,通知CPU处理数据。
  3. 启动传输:设置CONTROL.CHx[RUN] = 1,使能DMA通道。随后ADC在ePWM触发下开始转换,每次序列完成即触发DMA搬运4个数据。

6.2 常见问题与排查技巧实录

问题1:DMA似乎没有启动,数据没有搬运。

  • 检查清单
    1. 触发源是否正确:确认DMACHSRCSELxPERINTSEL配置匹配。最稳妥的方法是先用软件触发(PERINTFRC)测试DMA配置本身是否正确。
    2. 外设中断是否产生:确认ADC/ePWM等触发源本身工作正常,能产生中断事件。可以先用CPU中断来验证。
    3. DMA通道是否使能:确认CONTROL.CHx[RUN]位已置1。
    4. 内存映射是否正确:确保源地址(外设寄存器)和目的地址(内存)是可访问的,并且没有越界。特别是目的地址是否在有效的RAM区域内。
    5. 优先级与阻塞:检查是否被更高优先级的通道(如通道1高优先级模式)长期阻塞,或者CPU正在访问冲突的外设导致DMA被持续仲裁等待。

问题2:数据搬运错位,或者只搬运了一部分。

  • 检查清单
    1. 数据宽度:确认DATASIZE设置与外设数据宽度和内存访问预期是否一致。16位外设数据用32位模式访问会导致错位。
    2. 步进计算:这是最容易出错的地方。反复核对BURST_STEPTRANSFER_STEPWRAP_STEP的值。记住,这些步进值都是针对16位地址单元的。如果你操作的是32位数据(4字节),地址步进应该是2,而不是1。
    3. 缓冲区溢出:检查TRANSFER_SIZEBURST_SIZE的乘积是否超过了目的缓冲区的容量。同时,检查包装逻辑是否正确,避免写穿缓冲区。
    4. Overrun标志:检查CONTROL.CHx[OVRFLG]是否被置位。如果置位,说明DMA处理速度跟不上外设触发速度,有触发事件丢失。需要增大DMA带宽(如改用32位传输)、提高DMA优先级或降低外设数据率。

问题3:系统运行一段时间后出现数据错误或死机。

  • 检查清单
    1. 内存对齐:确保源地址和目的地址符合数据宽度的对齐要求。例如,32位访问通常要求地址是4字节对齐的。
    2. 共享资源冲突:检查是否有其他主设备(CPU、CLA)与DMA访问同一块内存或外设,且存在写冲突。避免对DMA正在写入的内存区域进行写操作。
    3. 中断服务程序:如果DMA传输完成中断服务程序执行时间过长,可能导致错过后续的DMA事件或系统其他关键任务。优化ISR,或考虑使用乒乓缓冲结合DMA半满中断来分散处理压力。
    4. 电源与时钟:在低功耗模式下,DMA模块的时钟可能被关闭。确保在操作DMA前,其所在的外设时钟域已使能。

一个独家避坑技巧:在调试复杂的DMA传输,特别是涉及地址包装和步进时,我习惯先用一个简单的、已知的数据模式进行测试。例如,在源内存区域填充递增数列(0x0001, 0x0002, 0x0003...),然后运行DMA,再检查目的区域的数据。通过观察实际搬运过去的数据序列,可以非常直观地判断出是步进计算错误、数据宽度错误还是包装逻辑错误。这比单纯看寄存器配置要高效得多。

http://www.cnnetsun.cn/news/3587540.html

相关文章:

  • TMS320F2837xS Flash配置优化:从等待状态到ECC的嵌入式系统性能提升指南
  • 深度解析 AP-0316:集 AI降噪 + AEC回消 + USB + 波束拾音于一体的“全能语音处理模组“
  • TuneUp JS高级技巧:如何用screen.js实现复杂UI元素定位
  • DeepResearch(上):方法论、OpenAI Deep Research、SurfSence、MindSearch、morphik、Firesearch
  • 深入解析USB控制器寄存器:RNDIS/CDC模式配置与自动请求机制实战
  • 深入解析TI AM335x USB DMA:RNDIS与CDC模式配置差异与调试实践
  • 2026论文工具避坑排行榜[特殊字符]实测打分!PaperXie凭实力封神✅
  • 科研绘图封神榜[特殊字符]告别Visio/Origin!PaperXie一键出学术图✅
  • 美育赏析是低分刚需,轻松填满日常美育档案
  • Python百天实战指南:从零到精通的数据科学与算法进阶
  • HugeGraph【部署】Linux单机部署
  • 【AI写作生产力跃迁指南】:20年内容架构师亲授观点提炼×逻辑强化×风格定制三重实战法
  • 问卷调查设计:从基础到高级的数据收集技巧
  • DM642 EVM视频处理系统迁移:FVID驱动与内存优化实战
  • TI MCU PMM电源管理模块寄存器级编程与低功耗设计实战
  • C2000 ERAD模块实战:硬件级系统监控与实时诊断技术解析
  • Windows server安装nginx详细教程,包教包会
  • TC3_Check function定位出错代码
  • 从Bing Copilot到私有化部署:全球TOP10 AI搜索架构拆解(含开源vs商用、云原生vs边缘部署的12项硬指标对比)
  • 5步实现Windows自动化部署:unattend-generator让IT运维效率提升300%
  • 中国电子多项AI创新成果入选国资央企人工智能典型案例
  • 【Python课程设计/毕业设计】基于Python的便民智慧医疗问诊预约管理系统设计 数字化医疗预约与辅助诊疗信息系统【附源码、数据库、万字文档】
  • 【Python课程设计/毕业设计】基于 Python Web 的医院门诊预约系统 高校 / 社区医院预约挂号服务系统设计 智能门诊预约与医生排班管理系统【附源码、数据库、万字文档】
  • 上海温州老旧房改造:上门出方案的几家服务商解析
  • P1049 [NOIP 2001 普及组] 装箱问题
  • Ajax异步请求解析,打通前后端,看这一篇就够了
  • AIGC率能不能降到个位数?讲清原理实测降到合格
  • namae背后的技术:React组件设计与多平台API集成原理
  • 基于TI DM642与RF-5框架的MPEG-2实时编解码系统设计与调优
  • 网络热词“那什么的交互“的传播与沟通密码