tag 标签: dma

相关帖子
相关博文
  • 热度 3
    2024-4-23 13:49
    433 次阅读|
    0 个评论
    直接存储器访问(DMA)控制器,可以在内存和/或外设之间传输数据,而不需要CPU参与每次传输。合理利用DMA控制器,可以减轻CPU的负担。本文通过介绍DMA结构与工作原理,以及两种模式(兵乓模式与多数据包缓冲传输模式),来看看使用DMA如何提高MCU效率。 DMA结构与工作原理 先进的DMA控制器,如 STMicroelectronics 的STM32F4系列中包含的控制器,可以通过灵活的数据流分配和传输管理功能进一步减轻CPU的负担。 如图左侧所示,来自8个不同的通道DMA请求,并到仲裁器上,从而建立优先级(编号较低的输入通道,具有较高的优先级)。然后激活最高优先级的传输,传输到图中右侧的两个AHB 主设备(存储器端口和外设接口),提高了外设到存储器传输的效率。这可能是DMA在基于CPU的设计中最常见的情况。 图 1 STM32F4系列DMA控制器(图片来源于STMicroelectronics) 为每个路径分配单独的FIFO,如图1中间所示,允许针对每个外设接口的特性调整FIFO特性。例如,FIFO的阈值级别(请求传输的深度)可以单独设置为FIFO大小的¼,½或¾。这允许低速通道等待,直到FIFO几乎满了才进行传输,以最小化开销。更快的通道会更早地启动传输,可能只有一半大小,以避免FIFO溢出。 我们来通过一个实例,来看看DMA怎么工作的。 实例:“使用 STM32 来控制 NeoPixels LED” 硬件部分采用STM32 开发板,与 NeoPixel LED、灯带、矩阵等相连接。 RGB NeoPixels实际上是WS2812智能控制LED。下面是WS2812 LED的3字节数据协议的结构,分别代表绿红蓝三个信息。 图 2 WS2812 LED的3字节数据协议的结构 使用计时器来PWM控制波形,然后配置DMA使CPU高效并且易于实施。 图 3 WS2812 LED的0和1位的计时图 在软件中,配置DMA, 选择了“TIM2_CH3/UP”, 将方向改为“内存到外设”, 同时,将优先级改为“非常高”,最后保存.ioc 文件,以生成项目代码。 图 4 配置DMA流,以便有效更新PWM信号的占空比 DMA的两种模式 合理使用两种DMA模式(兵乓模式与多数据包缓冲传输模式),可以帮助提高MCU效率。 USB外设是一个很好的外设示例,早期的USB实现的最大吞吐量只有1.5 Mb/秒。随着更高性能的标准版本的出现。比如要接近12 Mbit/s全速USB标准的理论最大值。我们来看看,数据传输方面DMA如何帮助提高MCU效率! 我们以Microchip的ATXMEGA16D4-MH举例。 兵乓模式: 之前通常使用单个存储器缓冲区进行外设数据传输。如果数据缓冲区已满,MCU将响应NAK(否定确认)消息。接收到NAK后,主机将等待并稍后重试传输。它将继续重试,直到MCU能够成功接收数据。 ATXMEGA16D4-MH使用乒乓模式来消除这个问题。乒乓模式使用两个存储单元(memory banks)进行数据传输。当一个存储单元满时,主机可以将数据传输到另一个存储单元。在两个存储单元之间交替传输可以避免复审,并提高整体数据带宽。 图 5 乒乓模式提高了效率(图片来源于Microchip) 此外,如上图所示,以乒乓模式还使MCU有更多时间来处理数据。如图所示,没有乒乓,CPU只能处理传输之间的数据。使用乒乓模式,CPU可以在传输周期的一部分时间内处理数据,并降低NAK被要求“赶上”数据处理要求的可能性。 多数据包缓冲传输模式 另一个很有用的模式,可以让MCU的数据传输更高效。这个特性叫做“多数据包缓冲传输模式”。如果你要通过USB端口传送的数据包,超过了全速USB的BULK传输模式所允许的最大值(64字节),那么就可以用上这个模式。以前,你需要在主机上把数据包分成小块,然后在接收端把它们合并,这会增加中央处理器(CPU)的负担。不过现在,多数据包缓冲功能加入了USB设备,它会在数据包超过USB标准大小时自动帮你分割和合并数据。重要的是,这个模式还能减少中断的次数,因为只有在整个传输结束后才需要中断CPU。这意味着,CPU可以处理其他任务,或者进入休眠模式,直到整个传输完成并且准备好处理。 总结: 合理利用DMA控制器,可以减轻减轻CPU的负担,事半功倍。结合“乒乓缓冲”和“多传输模式”,你可以把传输的带宽从基准BULK传输模式的5.6 Mb/s提升到8.7 Mbits/s,这是一个不小的提升。更重要的是,在使用这两个功能的情况下,CPU的负担从基准的46%降低到只有9%。这两个功能的结合,不仅在性能上有所改进,而且还能节省能源。 来源:digikey 作者: Alan Yang
  • 热度 6
    2020-9-24 12:06
    5736 次阅读|
    1 个评论
    STM32 串口 DMA方式接收数据及数据处理方法
    通常我们都使用中断方式接收串口数据。用DMA方式接收,效率会更高。 先在CUBE中设置一下。 使用 UART DMA 接收功能,并使用 Circular 模式。收到的数据将循环放在缓冲区里。 #define BUFFERSIZE 128 uint8_t Uart1RxBuf ; // 软件中定义一个接收缓冲区 uint16_t curchr,lastchr; // 指向当前未处理过的字符和最后一个字符。 HAL_UART_Receive_DMA(&huart1,Uart1RxBuf,BUFFERSIZE); // 程序初始化时,需要启动一次 UART DMA 接收。 /* 需要定时查询是否收到数据。 */ NDTR; // NDTR 指示存储空间的剩余长度 if(curchr != lastchr) { LEDRUNON(); // 指示收到一个数据包 SerialParse(Uart1RxBuf,&curchr,lastchr); } 找 SerialParse 寻找自定协议中的数据包的头和尾。 因为数据是循环接收的,有可能一个数据包会分布在缓冲区尾部和头部。索引时,需要取模运算。 (data == 'E')&&(data == 'R')&&(data == 'R') 好处就是,串口一直接收着,不会丢掉任何数据,只要合理的缓冲区长度,并及时处理数据。能达到很高的速度且不丢包。
  • 热度 2
    2016-4-9 13:07
    2800 次阅读|
    0 个评论
    由于硬件上采用了mpu6050模块,这模块是自带姿态解算和卡尔曼滤波功能的,所以省了不少事儿。直接通过串口读模块发送的数据,然后按照模块固定的协议把数据解析出来,就能得到三轴角速度,三轴加速度,三轴角度。其实mpu6050内部只集成了陀螺仪测角速度,加速度计测加速度,没有直接测量角度的传感器,但是角度可以通过角速度积分得到,并且通过重力加速度在各轴上的分量进行校正,通过数据融合的算法(比如卡尔曼滤波),就可以比较准确的得到角度啦。当然这个过程都在模块内部完成,我们只是应用的话不需要太关注(以后做四轴的话应该得好好研究一下姿态解算和数据融合方面的东西)。 好了,废话不多说。先说代码思路,然后就上代码。Stm32F405的usart3连接模块,接收数据,通过DMA直接存到一个11字节的数组GYRO_buffer 里面,当数组存满了,也就是接受到了完整的一帧数据(一帧数据包括可能是角度数据、加速度数据或者是角速度数据,要根据帧头的第二个字节加以区分,具体的数据帧格式见附件里面的模块资料),然后触发DMA中断,在中断中对数据进行解析,得到最终数据,存储到一个GYRO结构体变量中,这个变量被设置为全局变量,可以在其他的函数中使用该变量的值,做姿态的控制。 代码: #include "main.h"   GYRO gyro = {1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0}; unsigned char GYRO_buffer ;   void usart3_configuration(void) {          //一定注意配置顺序,先除能再使能等等          USART_InitTypeDef usart3;          GPIO_InitTypeDef  gpio;          NVIC_InitTypeDef  nvic;          DMA_InitTypeDef   dma;                     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);          RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);          RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);                   /*配置GPIO*/          GPIO_PinAFConfig(GPIOC,GPIO_PinSource10,GPIO_AF_USART3);          GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_USART3);          gpio.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;          gpio.GPIO_Mode = GPIO_Mode_AF;          gpio.GPIO_OType = GPIO_OType_PP;          gpio.GPIO_Speed = GPIO_Speed_100MHz;          gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;          GPIO_Init(GPIOC,gpio);                   /*配置DMA*/                  DMA_Cmd(DMA1_Stream1,DISABLE);          while(DMA_GetCmdStatus(DMA1_Stream1) != DISABLE);//等待DMA可配置          DMA_DeInit(DMA1_Stream1);          dma.DMA_Channel= DMA_Channel_4;          dma.DMA_PeripheralBaseAddr = (uint32_t)(USART3-DR);          dma.DMA_Memory0BaseAddr = (uint32_t)GYRO_buffer;          dma.DMA_DIR = DMA_DIR_PeripheralToMemory;          dma.DMA_BufferSize = 11;          dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;          dma.DMA_MemoryInc = DMA_MemoryInc_Enable;          dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;          dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;          dma.DMA_Mode = DMA_Mode_Circular;          dma.DMA_Priority = DMA_Priority_VeryHigh;          dma.DMA_FIFOMode = DMA_FIFOMode_Disable;          dma.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;          dma.DMA_MemoryBurst = DMA_MemoryBurst_Single;          dma.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;          DMA_Init(DMA1_Stream1,dma);          DMA_Cmd(DMA1_Stream1,ENABLE);                   /*配置NVIC*/          nvic.NVIC_IRQChannel = DMA1_Stream1_IRQn;          nvic.NVIC_IRQChannelPreemptionPriority = 1;          nvic.NVIC_IRQChannelSubPriority = 1;          nvic.NVIC_IRQChannelCmd = ENABLE;          NVIC_Init(nvic);                   DMA_ITConfig(DMA1_Stream1,DMA_IT_TC,ENABLE);                   /*配置usart3*/          usart3.USART_BaudRate = 115200;          usart3.USART_WordLength = USART_WordLength_8b;          usart3.USART_StopBits = USART_StopBits_1;          usart3.USART_Parity = USART_Parity_No;          usart3.USART_Mode = USART_Mode_Tx|USART_Mode_Rx;          usart3.USART_HardwareFlowControl = USART_HardwareFlowControl_None;          USART_Init(USART3,usart3);/*usart3初始化*/          //USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);/*配置usart3中断:读数据寄存器非空则中断*/          USART_Cmd(USART3,ENABLE);/*使能usart3*/          USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE);          }   void DMA1_Stream1_IRQHandler(void) {          if(DMA_GetITStatus(DMA1_Stream1, DMA_IT_TCIF1))          {                    DMA_ClearFlag(DMA1_Stream1, DMA_FLAG_TCIF1);                    DMA_ClearITPendingBit(DMA1_Stream1, DMA_IT_TCIF1);                    if(GYRO_buffer == 0x55)                    {                             switch(GYRO_buffer )                             {                                      case 0x51:                                                gyro.AX = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*16;//g                                                gyro.AY = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*16;                                                gyro.AZ = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*16;                                                gyro.Temperature = ((short)(GYRO_buffer 8 | GYRO_buffer )) /340.0f+36.53f;                                                led_red_on();                                                led_green_off();                                                break;                                      case 0x52:                                                gyro.GX = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*2000;//°/s                                                gyro.GY = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*2000;                                               gyro.GZ = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*2000;                                                gyro.Temperature = ((short)(GYRO_buffer 8 | GYRO_buffer )) /340.0f+36.53f;                                                led_red_off();                                                led_green_on();                                                break;                                      case 0x53:                                                gyro.PITCH = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*180;//度                                                gyro.ROLL = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*180;                                                gyro.YAW = ((short)(GYRO_buffer 8 | GYRO_buffer ))/32768.0*180;                                                gyro.Temperature = ((short)(GYRO_buffer 8 | GYRO_buffer )) /340.0f+36.53f;                                                led_red_on();                                                led_green_on();                                                break;                             }                    }          } } 其中GYRO结构体的结构是这样的: typedef struct {          float AX;          float AY;          float AZ;          float GX;          float GY;          float GZ;          float PITCH;          float ROLL;          float YAW;          float Temperature; }GYRO;   我开始在调试的过程中一直进不了DMA中断,后来发现是DMA配置的问题。下面把DMA配置部分的代码单独调出来说一下          /*配置DMA*/                  DMA_Cmd(DMA1_Stream1,DISABLE);          while(DMA_GetCmdStatus(DMA1_Stream1) != DISABLE);//等待DMA可配置          DMA_DeInit(DMA1_Stream1);          dma.DMA_Channel= DMA_Channel_4;          dma.DMA_PeripheralBaseAddr = (uint32_t)(USART3-DR);          dma.DMA_Memory0BaseAddr = (uint32_t)GYRO_buffer;          dma.DMA_DIR = DMA_DIR_PeripheralToMemory;          dma.DMA_BufferSize = 11;          dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;          dma.DMA_MemoryInc = DMA_MemoryInc_Enable;          dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;          dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;          dma.DMA_Mode = DMA_Mode_Circular;          dma.DMA_Priority = DMA_Priority_VeryHigh;          dma.DMA_FIFOMode = DMA_FIFOMode_Disable;          dma.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;          dma.DMA_MemoryBurst = DMA_MemoryBurst_Single;          dma.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;          DMA_Init(DMA1_Stream1,dma);          DMA_Cmd(DMA1_Stream1,ENABLE); 配置的时候首先用DMA_Cmd(DMA1_Stream1,DISABLE);除能DMA,因为DAM在运行过程中是不能配置的。然后用while(DMA_GetCmdStatus(DMA1_Stream1) != DISABLE);语句等待DMA被除能(可能是因为除能需要一定的时间吧,现在也不是很清楚为啥),确定DMA除能后,开始配置DMA,配置完成后,用DMA_Cmd(DMA1_Stream1,ENABLE);重新使能DMA。然后在使能usart3之后,还要加上USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE);语句,使能usart3的DAM功能。          关于stm32的DMA功能具体的配置,网上有很多资料,就不详细说啦。这里只把我看了网上的资料后仍然没有解决,最终基本靠自己摸索解决的问题写出来,供大家参考。
  • 热度 22
    2014-9-18 17:23
    2381 次阅读|
    0 个评论
    WIZnet W5500 支持高达 80MHz SPI 时钟,所以用户可用 MCU来提供一个最大传输速率的高速以太网SPI通讯。本文中,我将展示如何用STM32 MCU来让W5500达到最大传输速率。 当使用来自STMicro的Cortex M3/M4产线的32位处理器,以太网传输速率可以在使用SPI通讯模式事产生最大变化。我将比较使用SPI标准模式和SPI DMA模式的不同传输速率。 组成     MCU : Nucleo STM32F401RE 以太网控制器 : WIZnet WIZ550io(内嵌 W5500) 引脚连接 MCU与WIZnet WIZ550io之间的引脚连接,请参见下表。 首先,连接电源线。 其次,连接SPI信号。连接SCS 引脚到GPIOA_Pin12,因为我将用软件方法处理它。 第三,连接 RSTn 引脚到 GPIOA_Pin11 来复位WIZ550io. 最后, 用GPIOA_pin1这个引脚连接到W550io的RDY引脚上完成初始化. RSTn 引脚和 RDY 引脚的连接并不至关重要,但是连上更稳定. 怎样实现SPI协议  SPI 协议控制W5500和在SPI标准模式及SPI DMA模式是相同的。然而,这两种模式之间的不同是,在SPI总线的数据之间的空闲时间。用于W5500的SPI协议在WIZnet ioLibrary中W5500.c中提供,具有如下功能。 WIZCHIP_READ(uint32_t AddrSel) WIZCHIP_WRITE(uint32_t AddrSel, uint8_t wb) WIZCHIP_READ_BUF(uint32_t AddrSel, uint8_t* pBuf, uint16_t len) WIZCHIP_WRITE_BUF(uint32_t AddrSel, uint8_t* pBuf, uint16_t len) 当SPI DMA 模式未使用时, 内部函数调用标准SPI 读/写函数,比如下面的WIZCHIP_READ_BUF() 功能。 #if !defined (SPI_DMA) WIZCHIP.IF.SPI._write_byte((AddrSel 0x00FF0000) 16); WIZCHIP.IF.SPI._write_byte((AddrSel 0x0000FF00) 8); WIZCHIP.IF.SPI._write_byte((AddrSel 0x000000FF) 0); for(i = 0; i len; i++) pBuf = WIZCHIP.IF.SPI._read_byte(); 当使用 SPI DMA 模式时,准备命令数据,由地址和操作码组成,称作 SPI_DMA_READ() 函数。 #else spi_data = (AddrSel 0x00FF0000) 16; spi_data = (AddrSel 0x0000FF00) 8; spi_data = (AddrSel 0x000000FF) 0; SPI_DMA_READ(spi_data, pBuf, len); #endif 正如在“如何在STM32F2xx or STM32F4xx中使用SPI DMA来完成全双攻通信”, SPI_DMA_READ() 和 SPI_DMA_WRITE() 是由使用SPI DMA模式的代码组成的。 标准 SPI 模式  SPI DMA 模式的性能比较  下面相关数据的源代码是用于loopback测试的例程。可以看到在标准SPI模式和SPI DMA模式中存在多大的性能差异。 标准SPI模式 在 spi_handler.h 中 #ifndef SPI_DMA //#define SPI_DMA #endif 如果你把“#define SPI_DMA” 行打上注释,你将在标准SPI模式下操作。 如果你在Nucleo板上编译之后下载了二进制文件,并用WIZnet提供的AX1.exe上做了loopback的测试,随后传输和接收就如下图所示分别能达到 1.6Mbps,我们可知SPI全速传输速率高达3.2Mbps。 这里, SPI 时钟是 24MHz,你可以清晰的看到在SPI数据间的空闲时间。   SPI DMA 模式 #ifndef SPI_DMA #define SPI_DMA #endif 如上, 移去 “#define SPI_DMA”行注释. 接下来, 如果你下载二进制文件到 Nucleo, 然后你就能看到 SPI DMA 模式的传输速率。传输和接收的loopback测试性能可分别达到  4.3Mbps,并且 SPI全速传输速率超过 8Mbps。如果你使用自己的板子,并且有一个高速的外部时钟来代替Nucleo 板, 随之你就得到了更快的传输速率。 这里, 你可以看到无任何空闲时间的连续SPI通讯,如下图。 by James YS Kim
  • 热度 22
    2013-1-22 16:36
    1455 次阅读|
    0 个评论
      以往的大部分以太网扩展板使用的是WIZnet W5100芯片,我有个一WIZnet WIZ820io模块(使用W5200芯片),W5200有32KB的Buffer缓存(W5100的为16KB),在SPI模式下能运行速率能最大达到33.3Mbps(在W5100上为0.3Mbps)。我在hardware/arduino/sam/libraries/Ethernet/utility/中更新了W5100.cpp和W5100.h以兼容W5200(对于Ethernet.h也有略微改动)。这些更新文件和测试结果(wizperf.txt)能在以下网页获取 https://github.com/manitou48/DUEZoo 简单测试的结果是:从DUE发送出去的1000字节的UDP数据包会包含8字节的响应时延和一定的接收损失率。WIZnet的芯片在每次发送和接收时,会将2048字节的数据送入缓存/每个SOCKET。UDP协议不能保证可靠传输。与UDP协议相比,TCP协议下的数据传输通常较慢,但却能保证可靠传输,并能根据带宽自动调整传输速率。 结果包括在UNO和maple开发板上的测试。在maple和DUE上可以进行SPI+DMA模式下的测试。WIZnet为W5200在SPI模式下给出了33.3Mbps的参考速率。我在SPI时钟频率为28MHz时得到了可靠的结果,但是在42MHz下出错。 在使用DMA模式,且SPI时钟频率为28MHz的情况下,能达到将近16Mbps的速率,大约是只使用SPI传输模式的三倍。文件“w5100.cpp.dma1”是“w5100.cpp”的修正版本,能支持W5200以及DMA+SPI传输模式。详见以下网页中的“wizperf.txt”:https://github.com/manitou48/DUEZoo 更多信息,欢迎了解WIZnet开源硬件合作伙伴:http://blog.iwiznet.cn/?page_id=1494
相关资源