概述
CW32L052支持DMA(Direct Memory Access),即直接内存访问,无需CPU干预,实现高速数据传输。数据的传输可以发生在:
•外设和内存之间:例如ADC采集数据到内存,这种传输方式常见于需要将外设采集的数据快速传输到内存进行处理的应用。
•内存和内存之间:例如在两个不同的数组之间传输数据,或者在不同的内存块之间进行数据拷贝。
•外设和外设之间:例如从一个SPI主/从机传输数据到另一个SPI从/主机。
使用DMA能够有效减轻CPU的负担,特别是在大量数据需要高效传输的情况下,可以提高系统的整体性能。
框图
DMA功能框图
特性
使用DMA,最核心的就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是连续传输等等。
4条独立DMA通道:
4个DMA通道的优先级和通道号绑定,通道号越小优先级越高,通道号越大优先级越低。
4种传输模式:
硬件触发BULK传输模式、硬件触发BLOCK传输模式、软件触发BULK传输模式和软件触发BLOCK传输模式。
硬件 | BULK |
BLOCK | |
软件 | BULK |
BLOCK |
BULK传输模式:
适用于小数据块的传输,通常涉及大量的数据点,但每个数据点的大小较小。
与BLOCK模式不同,BULK模式下DMA会更频繁地启动新的传输,因为每个数据点通常被视为单独的传输单元,所以DMA控制器需要在每个数据点传输完成后需要重新配置或者启动DMA。在BULK传输模式下,传输过程不可被打断。
BLOCK传输模式:
适用于大数据块的高速传输,通常用于需要连续传输大量数据的情况。BLOCK模式下,DMA会将数据分成较大的块,并在传输时以这些块为单位进行操作。DMA控制器在一次配置后,连续传输多个数据块,而无需额外的干预或重新配置。每传输完成1个数据块后就要进行一次传输优先级的仲裁,允许CPU或者更高优先级的DMA通道访问当前DMA通道所占用的外设。
•硬件触发和软件触发:
要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求。部分外设支持硬件触发启动DMA传输,如FLASH存储器、UART串口、TIM定时器、ADC数模转换器等被配置为DMA通道的触发源时,
可以产生DMA请求(DMA request),硬件触发启动DMA传输,
而不支持硬件DMA的外设,只能配置为软件触发启动DMA传输。
虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
•DMA中断
DMA通道在传输工程中可产生2个中断标志:传输错误中断标志和传输完成中断标志
不同 DMA 通道的中断各自独立,通过中断标志寄存器 DMA_ISR 可以获取各通道的中断标志。标志对应多个可能的产生原因,具体产生原因需查询 DMA_CSRy.STATUS 状态位,如下表所示
•数据宽度:
数据位宽可以设置为8bit、16bit和32bit,DMA通道的源地址和目的地址的位宽必须完全一致
•数据块数量:
传输的数据块数量可以设置为1 ~ 65535
•数据传输优先级
当CPU和DMA访问不同的外设时,数据的传输可以同时进行;
当CPU和DMA同时访问同一个外设时,CPU的优先级高于DMA。
从外设到内存
通过ADC转换完成标志触发(硬件触发)DMA方式实现外设到内存的DMA传输
核心代码:
#include "main.h" #include "delay.h" #include "gpio.h" #include "cw32l052_lcd.h" #include "cw32l052_adc.h" #include "cw32l052_dma.h" #define NUM0 0x070d //段式LCD数字段码 #define NUM1 0x0600 #define NUM2 0x030e #define NUM3 0x070a #define NUM4 0x0603 #define NUM5 0x050b #define NUM6 0x050f #define NUM7 0x0700 #define NUM8 0x070f #define NUM9 0x070b void ADC_Configuration(void); //ADC配置函数 void DMA_Configuration(void); //DMA配置函数 void LCD_Configuration(void); //LCD配置函数 void LCD_Proc(uint16_t dispdata); //LCD子程序函数 /* **功能说明: **ADC采集数据触发DMA,将采集到的数据(1.2V内核电压基准源)存储在内存value中,并显示在LCD屏上 */ int main(void) { LED_Init(); LCD_Configuration(); ADC_Configuration(); DMA_Configuration(); while (1) { LCD_Proc(value); //显示采集到的ADC PA15_TOG(); Delay_ms(500); } } void ADC_Configuration(void) { ADC_InitTypeDef ADC_InitStruct = {0}; __RCC_ADC_CLK_ENABLE(); __RCC_GPIOA_CLK_ENABLE(); PA00_ANALOG_ENABLE(); //PA00 (AIN0) ADC_InitStruct.ADC_OpMode = ADC_SingleChOneMode; //单通道单次转换模式 ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div128; //PCLK ADC_InitStruct.ADC_SampleTime = ADC_SampTime5Clk; //5个ADC时钟周期 ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA; //VDDA参考电压(3.3V) ADC_InitStruct.ADC_InBufEn = ADC_BufEnable; //开启跟随器 ADC_InitStruct.ADC_TsEn = ADC_TsDisable; //内置温度传感器失能 ADC_InitStruct.ADC_DMASOCEn = ADC_DMASOCEnable; //ADC转换完成触发DMA传输 ADC_InitStruct.ADC_Align = ADC_AlignRight; //ADC转换结果右对齐 ADC_InitStruct.ADC_AccEn = ADC_AccDisable; //转换结果累加不使能 ADC_Init(&ADC_InitStruct); //初始化ADC配置 CW_ADC->CR1_f.DISCARD = FALSE; //ADC转换结果保存策略配置:新数据覆盖未被读取的旧数据 CW_ADC->CR1_f.CHMUX = ADC_Vref1P2Input; //待转换通道配置:1.2V内核电压基准源 ADC_ClearITPendingBit(ADC_IT_EOC); ADC_ITConfig(ADC_IT_EOC, ENABLE); ADC_EnableNvic(ADC_INT_PRIORITY); ADC_Enable(); ADC_SoftwareStartConvCmd(ENABLE); //开始转换 } void ADC_IRQHandler(void) { /* USER CODE BEGIN */ if(ADC_GetITStatus(ADC_IT_EOC) != RESET) { ADC_ClearITPendingBit(ADC_IT_EOC); ADC_SoftwareStartConvCmd(ENABLE); //开始转换 } /* USER CODE END */ } void NVIC_Configuration(void) { __disable_irq(); NVIC_ClearPendingIRQ(DMACH1_IRQn); NVIC_EnableIRQ(DMACH1_IRQn); __enable_irq(); } void DMA_Configuration(void) { DMA_InitTypeDef DMA_InitStruct = {0}; __RCC_DMA_CLK_ENABLE(); DMA_StructInit(&DMA_InitStruct); DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK; //BLOCK模式 DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT; //数据宽度32bit DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix; //源地址固定 DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix; //目标地址固定 DMA_InitStruct.DMA_TransferCnt = 1; //数据块数量1 DMA_InitStruct.DMA_SrcAddress = (uint32_t)&(CW_ADC->RESULT0); //数据源地址 (外设) DMA_InitStruct.DMA_DstAddress = (uint32_t)&value; //传输目标地址 (内存) DMA_InitStruct.TrigMode = DMA_HardTrig; //硬件触发DMA传输 DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_SINGLETRANSCOM; //硬件触发源:ADC单次转换完成标志 DMA_Init(CW_DMACHANNEL1,&DMA_InitStruct); //DMA通道1 DMA_ClearITPendingBit(DMA_IT_ALL); //清除DMA中断标志位 DMA_ITConfig(CW_DMACHANNEL1, DMA_IT_TC, ENABLE); //使能DMA通道1中断 NVIC_Configuration(); DMA_Cmd(CW_DMACHANNEL1, ENABLE); //启动DMA通道1进行传输 } void DMACH1_IRQHandler(void) { /* USER CODE BEGIN */ if( DMA_GetITStatus(DMA_IT_TC1) ) //DMA通道1传输完成标志 { DMA_ClearITPendingBit(DMA_IT_TC1); CW_DMACHANNEL1->CNT = 0x10001; //REPEAT写1,传输数量为1 DMA_Cmd(CW_DMACHANNEL1, ENABLE); } /* USER CODE END */ }
演示:ADC转换结果为1580左右,换算成电压:1580/4096*3.3=1.27V
从内存到内存
通过软件触发DMA方式实现内存(FLASH)到内存(SRAM)的DMA传输
核心代码:
//单片机头文件 #include "main.h" #include "cw32l052_lcd.h" #include "cw32l052_dma.h" //硬件外设 #include "delay.h" #include "gpio.h" //C库 #include#define NUM0 0x070d //段式LCD数字段码 #define NUM1 0x0600 #define NUM2 0x030e #define NUM3 0x070a #define NUM4 0x0603 #define NUM5 0x050b #define NUM6 0x050f #define NUM7 0x0700 #define NUM8 0x070f #define NUM9 0x070b #define DATASIZE 10 uint16_t const srcBuf[DATASIZE] = //源内存(FLASH)数据 { 9999,8888,7777,6666,5555, 4444,3333,2222,1111,0 }; uint16_t dstBuf[DATASIZE]={0}; //目标内存(SRAM)数据 void DMA_Configuration(void); //DMA配置函数 void LCD_Configuration(void); //LCD配置函数 void LCD_Proc(uint16_t dispdata); //LCD子程序函数 uint32_t value=0; //ADC数值 /* **功能说明: **将srcBuf数组中的数据通过DMA传送到dstBuf数组中, **srcBuf数组中的数据通过const关键词存储到FLASH中, **dstBuf数组存储在SRAM程序运行过程中 **传输完成后比较两数组内容,相同则打开LED1和LED1,LCD上循环显示dstBuf数据; **不同则进入死循环,两指示灯闪烁 */ int main(void) { LED_Init(); LCD_Configuration(); DMA_Configuration(); DMA_SWTrigCmd(CW_DMACHANNEL1); //使能通道1软件触发 while(DMA_GetFlagStatus(CW_DMACHANNEL1)!=DMA_CHANNEL_STATUS_TRANSCOMPLETE); //等待传输完成 if(memcmp(srcBuf,dstBuf,DATASIZE)==0) //如果srcBuf和dstBuf相同 { LED1_ON(); //指示灯 LED2_ON(); for(int i=0;i<10;i++) //LCD屏显示dstBuf数据 { LCD_Proc(dstBuf[i]); Delay_ms(500); } } else //如果不相同 { while(1) //进入while死循环 { PA15_TOG(); //指示灯 PC10_TOG(); Delay_ms(500); } } while (1) { } } void DMA_Configuration(void) { DMA_InitTypeDef DMA_InitStruct = {0}; __RCC_DMA_CLK_ENABLE(); DMA_StructInit(&DMA_InitStruct); DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK; //BLOCK模式 DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT; //数据宽度16bit DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Increase; //源地址固定 DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase; //目标地址递增 DMA_InitStruct.DMA_TransferCnt = DATASIZE; //数据块数量 DMA_InitStruct.DMA_SrcAddress = (uint32_t)&srcBuf[0]; //数据源地址 (内存) DMA_InitStruct.DMA_DstAddress = (uint32_t)&dstBuf[0]; //传输目标地址 (内存) DMA_InitStruct.TrigMode = DMA_SWTrig; //软件触发DMA传输 DMA_Init(CW_DMACHANNEL1,&DMA_InitStruct); //DMA通道1 DMA_Cmd(CW_DMACHANNEL1, ENABLE); //启动DMA通道1进行传输 }
演示:LCD屏上显示通过DMA传输的dstBuf的数据
从外设到外设
通过硬件触发DMA方式实现外设(SPI)到外设(SPI)的DMA传输
核心代码:
/*单片机头文件*/ #include "main.h" /*硬件驱动*/ #include "delay.h" #include "gpio.h" #include "cw32l052_dma.h" #include "cw32l052_spi.h" /*C库*/ #include//硬件连接 //SPIY_SCK (PA10) -- SPIX_SCK (PB13) //SPIY_MISO (PA11) -- SPIX_MISO (PB14) //SPIY_MOSI (PA12) -- SPIX_MOSI (PB15) //SPI2相关定义(Master) #define SPIX CW_SPI2 #define SPIX_GPIO CW_GPIOB #define SPIX_SCK_PIN GPIO_PIN_13 #define SPIX_MISO_PIN GPIO_PIN_14 #define SPIX_MOSI_PIN GPIO_PIN_15 #define SPIX_AF_SCK PB13_AFx_SPI2SCK() #define SPIX_AF_MISO PB14_AFx_SPI2MISO() #define SPIX_AF_MOSI PB15_AFx_SPI2MOSI() #define SPIX_RX_DMACHANNEL CW_DMACHANNEL1 #define SPIX_TX_DMACHANNEL CW_DMACHANNEL2 #define SPIX_DMA_RXTRIGSOURCE DMA_HardTrig_SPI2_RXBufferNE #define SPIX_DMA_TXTRIGSOURCE DMA_HardTrig_SPI2_TXBufferE //SPI1相关定义(Slave) #define SPIY CW_SPI1 #define SPIY_GPIO CW_GPIOA #define SPIY_SCK_PIN GPIO_PIN_10 #define SPIY_MISO_PIN GPIO_PIN_11 #define SPIY_MOSI_PIN GPIO_PIN_12 #define SPIY_AF_SCK PA10_AFx_SPI1SCK() #define SPIY_AF_MISO PA11_AFx_SPI1MISO() #define SPIY_AF_MOSI PA12_AFx_SPI1MOSI() //数组长度 #define BUFFERSIZE ARRAY_SZ(TxBuffer1) //发送内容1 uint8_t TxBuffer1[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23 }; //发送内容2 uint8_t TxBuffer2[] = {0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83 }; uint8_t RxBuffer1[BUFFERSIZE]; //接收数组1 uint8_t RxBuffer2[BUFFERSIZE]; //接收数组2 uint8_t TxCounter = 0; //发送计数 uint8_t RxCounter = 0; //接收计数 uint8_t TransferStatus1 = 1; //DMA传输状态标志1 uint8_t TransferStatus2 = 1; //DMA传输状态标志2 void DMA_Configuration(void); //DMA配置函数 void SPI_Configuration(void); //SPI配置函数 void SPI_GPIO_Configuration(void); //SPI相关GPIO口配置 /* **功能说明: **主机SPIY发送TxBuffer1中的数据,从机SPIX通过DMA接收数据并存储到RxBuffer1 **主机SPIY发送无效数据,启动SPI通信,同时SPIX从机通过DMA发送TxBuffer2中的数据,SIPY接收数据并存储到RxBuffer2 **单独比较TxBuffer1与RxBuffer1、TxBuffer2与RxBuffer2中的内容,比较结果通过LED灯指示 */ int main(void) { LED_Init(); //初始化LED指示灯 SPI_GPIO_Configuration(); //配置PI相关GPIO口 DMA_Configuration(); //配置DMA传输 SPI_Configuration(); //配置SPI传输 SPI_DMACmd(SPIX, SPI_DMAReq_Rx, ENABLE); //使能SPIX DMA RX SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Reset);//位选CS选中从机SPIX,起始信号 while(TxCounter < BUFFERSIZE) { while(SPI_GetFlagStatus(SPIY,SPI_FLAG_TXE) == RESET);//等待发送缓冲空(为空后硬件自动置1) SPI_SendData(SPIY,TxBuffer1[TxCounter++]); //发送TxBuffer1中的数据,通过数据寄存器DR把数据填充到发送缓冲区中 } while(DMA_GetFlagStatus(SPIX_RX_DMACHANNEL) != DMA_CHANNEL_STATUS_TRANSCOMPLETE);//等待DMA接收完成 SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Set); //释放从机SPIX,结束信号 TransferStatus1 = memcmp(TxBuffer1, RxBuffer1, BUFFERSIZE); //对比两数组数据 if(TransferStatus1==0) //如果数据相同 { LED1_ON(); //LED1指示 } else { LED1_OFF(); } TxCounter = 0; SPI_ReceiveData(SPIY);//读DR以清除RXNE(接收非空)标志位 SPI_DMACmd(SPIX, SPI_DMAReq_Rx, DISABLE);//失能SPIX DMA RX SPI_FlushSendBuff(SPIX);//清空发送缓冲区和移位寄存器 SPI_DMACmd(SPIX, SPI_DMAReq_Tx, ENABLE);//使能SPIX DMA TX SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Reset); while(TxCounter < BUFFERSIZE) { while(SPI_GetFlagStatus(SPIY, SPI_FLAG_TXE) == RESET){;} //主机发送数据以启动SPI通信 SPI_SendData(SPIY, TxBuffer1[TxCounter++]); while(SPI_GetFlagStatus(SPIY, SPI_FLAG_RXNE) == RESET){;} RxBuffer2[RxCounter++] = SPI_ReceiveData(SPIY); //获取接收缓冲区中的内容 } while(SPI_GetFlagStatus(SPIY,SPI_FLAG_BUSY) == SET); //检查数据是否已经全部通过SPI发送完毕 SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Set); //释放 TransferStatus2 = memcmp(TxBuffer2, RxBuffer2, BUFFERSIZE); //检查 if(TransferStatus2 == 0) { LED2_ON(); } else { LED2_OFF(); } while (1) { } } void SPI_GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; //打开GPIO时钟 __RCC_GPIOA_CLK_ENABLE(); __RCC_GPIOB_CLK_ENABLE(); //SPI SCK MOSI MISO 复用 SPIY_AF_SCK; SPIY_AF_MISO; SPIY_AF_MOSI; SPIX_AF_SCK; SPIX_AF_MISO; SPIX_AF_MOSI; //推挽输出 GPIO_InitStructure.Pins = SPIY_SCK_PIN | SPIY_MOSI_PIN; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_Init(SPIY_GPIO, &GPIO_InitStructure); GPIO_InitStructure.Pins = SPIX_MISO_PIN; GPIO_Init(SPIX_GPIO, &GPIO_InitStructure); //浮空输入 GPIO_InitStructure.Pins = SPIX_SCK_PIN | SPIX_MOSI_PIN; GPIO_InitStructure.Mode = GPIO_MODE_INPUT; GPIO_Init(SPIX_GPIO, &GPIO_InitStructure); GPIO_InitStructure.Pins = SPIY_MISO_PIN; GPIO_Init(SPIY_GPIO, &GPIO_InitStructure); } void SPI_Configuration(void) { SPI_InitTypeDef SPI_InitStructure = {0}; __RCC_SPI1_CLK_ENABLE(); __RCC_SPI2_CLK_ENABLE(); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工模式 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机模式 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据位宽8bit SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟极性 SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,奇数边缘采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //配置NSS引脚(片选信号线)的使用模式,软件控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //波特率:PCLK8分频 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //MSB先行模式 SPI_InitStructure.SPI_Speed = SPI_Speed_Low; //低速 SPI_Init(SPIY,&SPI_InitStructure); SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; //从机模式 SPI_Init(SPIX,&SPI_InitStructure); SPI_Cmd(SPIX,ENABLE); SPI_Cmd(SPIY,ENABLE); } void DMA_Configuration(void) { DMA_InitTypeDef DMA_InitStructure = {0}; __RCC_DMA_CLK_ENABLE(); //DMA TX DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK; //BLOCK模式 DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT; DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Increase; DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Fix; DMA_InitStructure.TrigMode = DMA_HardTrig; DMA_InitStructure.HardTrigSource = SPIX_DMA_TXTRIGSOURCE; DMA_InitStructure.DMA_TransferCnt = BUFFERSIZE; DMA_InitStructure.DMA_SrcAddress = (uint32_t)&TxBuffer2[0]; DMA_InitStructure.DMA_DstAddress = (uint32_t)&SPIX->DR; //数据寄存器 DMA_Init(SPIX_TX_DMACHANNEL,&DMA_InitStructure); DMA_Cmd(SPIX_TX_DMACHANNEL,ENABLE); //DMA RX DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK; DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT; DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Fix; DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Increase; DMA_InitStructure.TrigMode = DMA_HardTrig; DMA_InitStructure.HardTrigSource = SPIX_DMA_RXTRIGSOURCE; DMA_InitStructure.DMA_TransferCnt = BUFFERSIZE; DMA_InitStructure.DMA_SrcAddress = (uint32_t)&SPIX->DR; DMA_InitStructure.DMA_DstAddress = (uint32_t)&RxBuffer1[0]; DMA_Init(SPIX_RX_DMACHANNEL,&DMA_InitStructure); DMA_Cmd(SPIX_RX_DMACHANNEL,ENABLE); }
审核编辑:刘清
-
单片机
+关注
关注
6035文章
44553浏览量
634718 -
数据传输
+关注
关注
9文章
1882浏览量
64563 -
LCD屏
+关注
关注
0文章
122浏览量
15422 -
dma
+关注
关注
3文章
560浏览量
100553 -
推挽输出
+关注
关注
0文章
42浏览量
6513
原文标题:CW32L052单片机DMA直接内存访问
文章出处:【微信号:武汉芯源半导体,微信公众号:武汉芯源半导体】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论