直接寄存器访问(DMA)

it2023-05-28  69

DMA 即 Direct Memory Access. DMA传输方式无须CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。

1. DMA基础知识

STM32 最多有2个DMA控制器(DMA2仅存在大容量产品中),DMA有7个通道,DMA2有5个通道。每个通道专门用来管理来自一个或多个外设对存储器访问的请求。还有一个仲裁器协调各个DMA请求的优先权。

STM32的DMA有以下一些特性:

每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。在7个请求间的优先权可以通过软件编程来配置,假如在优先权相等时,由硬件决定。独立的源和目标数据区的传输宽度(字节,半字,全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。支持循环的缓冲器管理。每个通道都有3个事件标志(DMA半传输,DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。存储器和存储器间的传输。外设和存储器,存储器和外设的传输。闪存,SRAM, 外设的SRAM,APB1,APB2,和AHB外设均可作为访问的源和目标。可编程的数据传输数目,最大为65536. 外设通过逻辑或输入到DMA通道,意味着每个通道只能同时接收一个请求。

2. 用寄存器方式操作DMA

假设为DMA1的通道4,配置步骤如下:

设置外设地址。 外设地址通过DMA1_CPAR4设置,只要在这个寄存器里面写入&USART1_DR的值就可以。该地址将作为DMA传输的目标地址。设置存储器地址。存储器地址通过DMA1_CMAR4来设置,假设要把数组SendBuf作为存储器,那么在该寄存器内写入&SendBuf就可以。该地址将作为DMA传输的源地址。设置传输数据量。通过DMA_CNDTR4寄存器,这里面写入此次要传输的数据量,即SendBuf的大小。该寄存器的数值将在DMA启动后自减,每次新的DMA传输,都重新向该寄存器写入要传输的数据量。设置通道4的配置信息。设置存储器和外设的数据位宽为8,存储器增量模式。优先级可以随便设置,因为只有一个通道开启。使能DMA1通道4,启动传输。以上配置都完成后,就使能DMA1_CCR4的最低位开启DMA传输,注意要设置USART1的使能DMA传输位,通过USART1->CR3的第7位设置。

3. 用库函数的方式操作DMA

总共5步去配置DMA.

使能DMA时钟。初始化DMA通道4的参数。使能串口DMA发送。使能DMA1通道4,启动传输。查询DMA传输状态。

1. 使能DMA时钟

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

2. 初始化DMA通道4的参数

DMA的初始化是通过配置函数:

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)

第1个参数指定通道,第二个参数用来初始化DMA。 设置具体如下:

DMA_InitTypeDef DMA_InitStructure; //DMA外设ADC基地址 DMA_InitStructure.DMA_PeriperalBaseAddr = &USART->DR; //DMA内存基地址 DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //从内存读取发送到外设 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripherialDST; //DMA通道的DMA缓存的大小 DMA_InitStructure.DMA_BufferSize = 64; //外设地址不变 DMA_InitStructure.DMA_PeripherialInc = DMA_PeripherialInc_Disable; //内存地址递增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //8位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //工作在正常缓存模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA通道x拥有中优先级 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //非内存到内存传输 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //根据指定的参数初始化 DMA_Init(DMA_CHx, &DMA_InitStructure);

3. 使能串口DMA发送

进行DMA配置后,就要开启串口的DMA发送功能,使用的函数是:

USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);

如果是接受的话,第2个参数修改为USART_DMAReq_Rx即可。

4. 使能DMA1通道4, 启动传输

使能串口DMA发送之后,接着就要使能DMA传输通道。

DMA_Cmd(DMA_CHx, ENABLE);

5. 查询DMA传输状态

用户要查询DMA传输通道的状态,使用的函数是:

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

查询 DMA 通道4 是否完成,方法是:

DMA_GetFlagStatus(DMA2_FLAG_TC4);

获取当前剩余数据量大小的函数:

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

例如 要获取DMA通道4, 还有多少个数据没有传输:

DMA_GetCurrDataCounter(DMA1_Channel4);

4. DMA操作实例

该例子是利用DMA把数据(数组)从内存转移到外设(串口)。外设工作的时候,除了转移数据,实质是不需要内核干预的,而数据转移的工作现在交给了DMA, 所以在串口发送数据的时候,内核同时还可以进行其他操作,比如点亮LED灯。

#include "stm32f10x.h" #include "usart1.h" #include "led.h" extern uint8_t SendBuff[SENDBUFF_SIZE]; static void Delay(__IO u32 nCount); int main(void) { USART1_Config(); USART1_DMA_Config(); LED_GPIO_Config(); printf("\r\n usart1 DMA TX Test \r\n"); { uint16_t i; /*填充将要发送的数据*/ for(i=0; i<SENDBUFF_SIZE; i++) { SendBuff[i] = 'A'; } } /*USART1 向DMA 发出TX请求*/ USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); /*此时CPU是空闲的可以干其他事情*/ //同时控制LED for(;;) { LED1(ON); Delay(0xFFFFF); LED1(OFF); Delay(0xFFFFF); } } static void Delay(__IO uint32_t nCount)//简单的延时函数 { for(;nCount != 0; nCount--); }

DMA传输代码如下:

#include "usart1.h" uint8_t SendBuff[SENDBUFF_SIZE]; void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; /*配置USART1时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE); /*配置USART1 PA.9(Tx)脚为复用推挽输出*/、 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); /*配置USART1的PA.10(Rx)脚位浮空输入*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATTING; GPIO_Init(GPIOA,&GPIO_InitStructure); /*USART1 模式配置*/ USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); } void USART1_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; /*开启DMA时钟*/ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /*设置DMA源:串口数据寄存器地址*/ DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; /*内存地址(要传输的变量的指针)*/ DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; /*方向:从内存到外设*/ DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; /*传输大小 DMA_BufferSize = SENDBUFF_SIZE*/ DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; /*外设地址不增*/ DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /*内存地址自增*/ DMA_InitStructure.DMA_MemoryInc = DMA_Memory_Enable; /*外设数据单位*/ DMA_InitStructure.DMA_PeriphaeralDataSize = DMA_PeripheralDataSize_Byte; /*内存数据单位*/ DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; /*DMA模式:不断循环*/ DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /*优先级:中*/ DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; /*禁止内存到内存之间的传递*/ DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /*配置DMA1的4通道*/ DMA_Init(DMA1_Channel4, &DMA_InitStructure); /*使能DMA*/ DMA_Cmd(DMA1_Channel4, ENABLE); } //重定向c库函数printf到USART1 int fputc(int ch, FILE *f) { /*发送一个字节数据到USART1*/ USART_SendData(USART1, (uint8_t)ch); /*等待发送完毕*/ while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return(ch); } //重定向c库函数scanf到USART1 int fgetc(FILE *f) { /*等待串口1输入数据*/ while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); return (int)USART_RecieveDat(USART1); }
最新回复(0)