DMA(data memory access)直接存储器访问,和串口USART、GPIO一样,也是一个外设。
把数据从一个地方搬到另一个地方,而不占用CPU。像串口发送数据,数据是一个一个的发。CPU首先把数据从内存(数组)拿到CPU内部的寄存器(CPU内部有一组暂存数据寄存器R1、R2、R3等),然后再发到串口的数据寄存器USART_DR,这些过程一直需要占用CPU。而当我们使用DMA时,如果CPU给DMA一个命令(把数据从内存发到串口),这时DMA就会实现这个功能,整个过程CPU是空闲的,可以去做其他事。比如,点亮一个LED,显示液晶屏LCD等。
DMA支持3种模式:
- Memory to Memory (这里的Memory可以是Flash或内部SRAM);
- Memory to Peripheral,这个最典型的应用是串口发送;
- Peripheral to Memory,这个最典型的应用就是ADC。ADC采集的数据是保存在数据寄存器DR,然后要读到内存;
DMA1有7个通道,DMA2有5个通道。
开始DMA时,以内存—串口为例。首先串口向CPU发送请求,告诉DMA,把数据从内存搬到发送数据寄存器中。这个请求由其中的一方来产生(发送方),通道为传输数据的管道,
有一个比较有意思的问题,就是数据对齐。我们通常要求发送方和接收方的数据长度要一致(数据对齐),但有些时候我们又会使用到不一致的情况(比如8位数据发送到定时器16位CCR寄存器),这时候该怎么办?我看到基本上很少有这种问题的详细解释与实验验证,但其实在中文参考手册中是有相关描述的。可以看到,对于DMA对齐的数据,直接操作没有问题。而当DMA数据不对其,比如8位传到16位时,如 DMA读到的8位数据为B0,实际写入的会是00B0,高8位用0补齐。当16位转8位数据时,如DMA读到B1B0,在写入时会把高8位丢弃,只写入B0位。这跟C语言的强制类型转换一致。
为了验证这个DMA数据不对齐配置是否正确,使用stm32CubeMX进行了一次测试(8位数据从内存传到16位CCR寄存器),配置如下,实验结果正确。
DMA每个通道具体对应的外设,可以看到,连接在同一通道上的外设,DMA请求同时只能有有一个生效。同理,DMA发送数据时,每个通道只能用作其中的一种。例如:DMA1通道1,任何时刻,只能作为ADC1 TIM2_CH3 TIM4_CH1中的一个数据管道。这里说的只是M-P和P-M,当使用M-M时,所有的通道都可以随意使用。
当有多个DMA请求一起来,就需要仲裁器来仲裁,到底哪个DMA请求先执行,这个在中文参考手册中有很详细的说明,具体如下:
那么我们就知道了,当多个DMA请求一起来时,先比较软件阶段,这里由DMA_CCRx寄存器的PL位分成4个等级,根据软件等级可以确定发送顺序。如果情况更加复杂,如有7路DMA请求,此时4个等级明显不够用,一定会出现等级相同的情况。此时,则比较硬件通道编号,编号越小等级越高。
标准固件库中的dma初始化结构体:
/**
* @brief DMA Init structure definition
*/
typedef struct
{
// 外设基地址
uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */
// 存储器基地址
uint32_t DMA_MemoryBaseAddr; /*!< Specifies the memory base address for DMAy Channelx. */
// DMA传输方向(M-P P-M)
uint32_t DMA_DIR; /*!< Specifies if the peripheral is the source or destination.
This parameter can be a value of @ref DMA_data_transfer_direction */
// DMA传输的数据量(注意,不是字节数,如4个uint16_t,就是4,不是8)
uint32_t DMA_BufferSize; /*!< Specifies the buffer size, in data unit, of the specified Channel.
The data unit is equal to the configuration set in DMA_PeripheralDataSize
or DMA_MemoryDataSize members depending in the transfer direction. */
// 外设地址递增
uint32_t DMA_PeripheralInc; /*!< Specifies whether the Peripheral address register is incremented or not.
This parameter can be a value of @ref DMA_peripheral_incremented_mode */
// 存储器地址递增
uint32_t DMA_MemoryInc; /*!< Specifies whether the memory address register is incremented or not.
This parameter can be a value of @ref DMA_memory_incremented_mode */
// DMA外设数据宽度(字节,半字,字),通常要与存储器数据宽度对齐(也可以不对齐)
// 如果数据宽度不对齐,按前面说的C语言规则处理
uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
This parameter can be a value of @ref DMA_peripheral_data_size */
// 存储器数据宽度
uint32_t DMA_MemoryDataSize; /*!< Specifies the Memory data width.
This parameter can be a value of @ref DMA_memory_data_size */
// DMA模式(普通和循环模式)
uint32_t DMA_Mode; /*!< Specifies the operation mode of the DMAy Channelx.
This parameter can be a value of @ref DMA_circular_normal_mode.
@note: The circular buffer mode cannot be used if the memory-to-memory
data transfer is configured on the selected Channel */
// DMA优先级(最高/高/中/低)
uint32_t DMA_Priority; /*!< Specifies the software priority for the DMAy Channelx.
This parameter can be a value of @ref DMA_priority_level */
// 是否使能存储器到存储器模式
uint32_t DMA_M2M; /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;
DMA可以设置三种中断,传输完成中断、传输过半中断、传输错误中断。
/**
* @brief Enables or disables the specified DMAy Channelx interrupts.
* @param DMAy_Channelx: where y can be 1 or 2 to select the DMA and
* x can be 1 to 7 for DMA1 and 1 to 5 for DMA2 to select the DMA Channel.
* @param DMA_IT: specifies the DMA interrupts sources to be enabled
* or disabled.
* This parameter can be any combination of the following values:
* @arg DMA_IT_TC: Transfer complete interrupt mask
* @arg DMA_IT_HT: Half transfer interrupt mask
* @arg DMA_IT_TE: Transfer error interrupt mask
* @param NewState: new state of the specified DMA interrupts.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
assert_param(IS_DMA_CONFIG_IT(DMA_IT));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
/* Enable the selected DMA interrupts */
DMAy_Channelx->CCR |= DMA_IT;
}
else
{
/* Disable the selected DMA interrupts */
DMAy_Channelx->CCR &= ~DMA_IT;
}
}