参考文章
https://blog.csdn.net/as480133937/article/details/104927922
DMA简介
DMA,全称Direct Memory Access,即直接存储器访问。DMAC 即 DMA 控制器,提供了一种硬件的数据传输方式,无需 CPU 的介入,可以处理外设和存储器之间或者存储器和存储器之间的传输数据。因无 CPU 介入,从而使 CPU 可以专注在处理其他系统功能上。由此可以减轻CPU的负担,使得更多的CPU资源分配到其他的任务处理上面去。
主要配置参数
了解DMA是什么,以及为什么要使用DMA后,我们之后就是要知道如何去配置DMA,以及需要配置哪些参数。下面是一些主要需要配置的参数。
DMA的传输通道
不同单片机有不同的通道可供使用,结合实际资源参数进行选择。
目标外设和源外设号(有些可能是直接配置地址)
DMA的传输方式
- 外设到内存
- 内存到外设
- 内存到内存
- 外设到外设
数据传输宽度
这个可以根据实际需求配置成一个字节、半个字、全字这三种方式
- DMA_SRC_WIDTH_BYTE
- DMA_SRC_WIDTH_HALF_WORD
- DMA_SRC_WIDTH_WORD
模式设置
- 模式可以设置为正常模式(开启之后仅只传输一次)
- 循环模式(会一直循环传输数据)
地址生成方式
所谓地址生成方式,就是看你使不使用,地址递增或者固定地址。也就是
固定模式和递增模式。
- 在固定模式中,地址一直固定为初始化的基地址(DMACCxSrcAddr、 DMACCxDestAddr)。
- 在递增模式中,下一次传输数据的地址是当前地址加 1(或者 2, 4),这个值取决于数据
- 传输宽度。
具体配置代码
void DMA_SPITransmit_Init(void)
{
DMA_SPIT_Handle.Instance = DMA_Channel1;
DMA_SPIT_Handle.Init.Data_Flow = DMA_DATA_FLOW_M2P;
DMA_SPIT_Handle.Init.Request_ID = REQ3_SPI2_SEND;
DMA_SPIT_Handle.Init.Mode = DMA_NORMAL;
DMA_SPIT_Handle.Init.Source_Inc = DMA_SOURCE_ADDR_INCREASE_ENABLE;
DMA_SPIT_Handle.Init.Desination_Inc = DMA_DST_ADDR_INCREASE_DISABLE;
DMA_SPIT_Handle.Init.Source_Width = DMA_SRC_WIDTH_BYTE;
DMA_SPIT_Handle.Init.Desination_Width = DMA_DST_WIDTH_BYTE;
/*-----------------------------------------------------------------------------------*/
/* Note:If user dons not apply interrupt, Set DMA_ITC_Callback?¢DMA_IE_Callback NULL */
/*-----------------------------------------------------------------------------------*/
DMA_SPIT_Handle.DMA_ITC_Callback = NULL;
DMA_SPIT_Handle.DMA_IE_Callback = NULL;
HAL_DMA_Init(&DMA_SPIT_Handle);
__HAL_LINK_DMA(SPI_Handle, HDMA_Tx, DMA_SPIT_Handle);
}
我这里使用的是SPI+DMA配置方式,单片机还是航芯的单片机,选择了寄存器地址(目标地址)不变,源地址(数据地址)递增,这样配置满足我们需求。然后传输完成回调函数和传输出错回调函数暂时没有配置。
DMA中断
每个 DMA 通道都有一个专用的中断。中断事件有两种类型:传输完成和传输错误。 每一个中断事件在状态寄存器和清除寄存器中有专用的标志位。通过检测两种中断标志位可以获取我们DMA的传输情况。
__weak void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
uint32_t lu32_Channel_Index;
/* Get DMA Channel number */
lu32_Channel_Index = ((uint32_t)(hdma->Instance) - (uint32_t)(DMA_Channel0)) / 0x20;
/* Channel has been interrupted */
if (DMA->INT_STATUS & (1 << lu32_Channel_Index))
{
/* Transfer complete interrupt */
if (DMA->INT_TC_STATUS & (1 << lu32_Channel_Index))
{
DMA->INT_TC_CLR |= (1 << lu32_Channel_Index);
if (NULL != hdma->DMA_ITC_Callback)
{
hdma->DMA_ITC_Callback();
}
}
/* Transfer error interrupt */
if (DMA->INT_ERR_STATUS & (1 << lu32_Channel_Index))
{
DMA->INT_ERR_CLR |= (1 << lu32_Channel_Index);
if (NULL != hdma->DMA_IE_Callback)
{
hdma->DMA_IE_Callback();
}
}
}
}
上面就是在触发中断后在中断函数中处理函数的中断程序,主要逻辑就是先获取哪个中断触发,然后判断触发源(传输完成或者传输错误)。
总结
DMA配置使用起来参数不多也比较简单,但是要真正使用并理解还是需要时间去消化。像之前不使用DMA不知道有啥作用,只模糊地知道可以帮CPU减负,导致很多情况下都乱用DMA。