介绍了DMA的基础概念以及存储器到存储器、存储器到外设两种DMA的使用方式。
DMA(Direct Memory Access)翻译过来就是直接存储器访问,顾名思义就是能够直接访问存储器的一个外设。传输数据是单片机的一个重要工作任务,比如各种传感器传输给单片机的数据,以及处理完成的数据要传输给上位机。如果这些任务全部交给CPU核心,那么可能太占用资源,导致CPU来不及处理其它的任务。DMA是STM32里独立于CPU的外设,专门用于传输数据,这样我们可以把传输数据的任务交给DMA完成,计算等其他任务交给CPU,最大化发挥单片机的性能。
根据数据的位置,DMA有三种工作方式,即外设到存储器、存储器到外设以及存储器到存储器。外设到存储器就比如传感器采样后通过ADC将数据传入存储器;存储器到外设就比如通过串口向上位机发送数据;而存储器到存储器可以用于FLASH向SRAM复制数据。
其中有关外设的工作方式需要外设向DMA控制器发出请求,DMA做出应答后才能进行数据传输。STM32F1系列芯片中有DMA1与DMA2两个DMA控制器,分别有7个通道与5个通道,每个通道对应着不同的外设请求,外设都要通过专门的通道发出请求才能完成数据的传输。
以下介绍在使用HAL库时如何设置存储器到存储器DMA传输、存储器到外设DMA传输 。
通过STM32CubeIDE(或者STM32CubeMX),我们可以十分快速的完成DMA基础配置。选择MEMTOMEM(memory to memory),普通传输模式,数据源与目的地地址均设为增量模式,数据单位为word(32位)。
完成初始化配置后,我们尝试通过DMA将FLASH中的数据传输到SRAM中,首先定义数据源数组SRC_Buffer、目的地数组DST_Buffer以及数据长度BUFFER_SIZE,数据类型为uint32_t,通过const关键字将数据源数组定义在FLASH中。
#define BUFFER_SIZE 32
const uint32_t SRC_Buffer[BUFFER_SIZE] = {
0x01020304,0x05060708,0x090A0B0C,0X0D0E0F10,
0x11020304,0x15060708,0x190A0B0C,0X1D0E0F10,
0x21020304,0x25060708,0x290A0B0C,0X2D0E0F10,
0x31020304,0x35060708,0x390A0B0C,0X3D0E0F10,
0x41020304,0x45060708,0x490A0B0C,0X4D0E0F10,
0x51020304,0x55060708,0x590A0B0C,0X5D0E0F10,
0x61020304,0x65060708,0x690A0B0C,0X6D0E0F10,
0x71020304,0x75060708,0x790A0B0C,0X7D0E0F10,
};
uint32_t DST_Buffer[BUFFER_SIZE];
根据之前的设置,CubeIDE程序已经在dma.c中帮我们初始化了以下代码:
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma1_channel1 on DMA1_Channel1 */
hdma_memtomem_dma1_channel1.Instance = DMA1_Channel1;
hdma_memtomem_dma1_channel1.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma1_channel1.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_memtomem_dma1_channel1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_memtomem_dma1_channel1.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma1_channel1.Init.Priority = DMA_PRIORITY_MEDIUM;
if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
{
Error_Handler();
}
}
可以看到我们选择的通道、模式以及传输数据大小都设置到了结构体hdma_memtomem_dma1_channel1中,接下来通过函数HAL_DMA_Start我们就可以完成DMA数据传输。这里我们要设置HAL状态变量供HAL_DMA_Start返回传输状态。
HAL_StatusTypeDef DMA_status = HAL_ERROR;
DMA_status = HAL_DMA_Start(&hdma_memtomem_dma1_channel1, (uint32_t)SRC_Buffer, (uint32_t)DST_Buffer, BUFFER_SIZE);
传输完成后,可以通过如下函数比较二者数据的一致性
TransferStatus = Buffer_cmp(SRC_Buffer, DST_Buffer, BUFFER_SIZE);
uint8_t Buffer_cmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength)
{
while(BufferLength--){
if(*pBuffer != *pBuffer1){
return 0;
}
pBuffer++;
pBuffer1++;
}
return 1;
}
存储器到外设DMA传输的典型应用是单片机通过串口向上位机(电脑)传输数据,在上位机上可以通过串口助手显示传上来的数据。我们采用USART1_TX串口1的发送功能完成这次传输。首先在CubeIDE中配置DMA基本设置:
根据DMA1各通道对应的外设请求,选择DMA1的通道4完成串口1的发送。发送设置为循环模式,外设地址非增量、存储器地址增量,传输数据单位长度为1字节。
DMA初始化代码如下,注意该段代码在usart.c文件中,不再是dma.c。
/* USART1 DMA Init */
/* USART1_TX Init */
hdma_usart1_tx.Instance = DMA1_Channel4;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
{
Error_Handler();
}
代码初始化完成后,设置待传输数据长度以及数组,然后在数组里填入待发送的数据,这里我们选择全部发送字符“A”。
#define SENDBUFF_SIZE 10
uint8_t SendBuff[SENDBUFF_SIZE];
for(uint16_t i=0; i
然后通过函数HAL_UART_Transmit_DMA便可完成数据传输。
HAL_UART_Transmit_DMA(&huart1, (uint8_t *)SendBuff, SENDBUFF_SIZE);
由于采用了循环传输模式,数据量非常大,但都是通过DMA完成的,CPU还处于空闲状态,依旧可以完成其他的工作。
介绍了DMA基础概念以及存储器到存储器、存储器到外设两种DMA使用方式(HAL库、轮询使用)