前言
在做项目时我使用的板子是STM32G0x0系列的,做到有关DMA模块就感觉和以前的F1系列使用上不一样,F1的DMA通道是固定的,每个通道都只能设置固定的外设模块,但G0X0系列的DMA就没有该限制,再使用CUBEMX开发时发现任何模块你都可以使用任意DMA通道来传输,而做到这个功能的就是DMAMUX模块。我这里就不解释这个东西的原理了,我看了几遍也没怎么理解这个东西,我只说说这个东西是怎么配合DMA使用的。
正文
/* SPI1_TX Init */
hdma_spi1_tx.Instance = DMA1_Channel3;
hdma_spi1_tx.Init.Request = DMA_REQUEST_SPI1_TX; //为SPI1_TX配置DMA通道
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_tx.Init.Mode = DMA_NORMAL;
hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
{
Error_Handler();
}
我的项目是用SPI+DMA的方式通信,上面是用CUBEMX生成的代码,把SPI1的发送接到DMA通道上。其他的参数都是配置DMA模式的,对本文意义不大,主要是探讨DMA通道的搭建。
进入初始化函数后首先是一堆的参数检查函数,但点开是下图这样的,没什么用,估计是要开启什么选项才会开启参数检查吧。
然后是下图DMA2部分的内容,我没有了解,不过全是灰灰的一片,估计没有这个宏定义,可能是其他型号芯片的内容。用VScode这一点就是方便啊,直接帮我忽略掉无用的代码。
遇到了第一行有用的代码,直接直译就是根据根据前面选的通道几,把这个值相应的设置为几。我选的是DMA1_Channel3,对照着宏的值计算,写的简约点就是ChannelIndex = (3-1)<<2;
#define DMA1_Channel1 ((DMA_Channel_TypeDef *) DMA1_Channel1_BASE) //0x40020008
#define DMA1_Channel2 ((DMA_Channel_TypeDef *) DMA1_Channel2_BASE) //0x4002001C
#define DMA1_Channel3 ((DMA_Channel_TypeDef *) DMA1_Channel3_BASE) //0x40020030
#define DMA1_Channel4 ((DMA_Channel_TypeDef *) DMA1_Channel4_BASE) //0x40020044
#define DMA1_Channel5 ((DMA_Channel_TypeDef *) DMA1_Channel5_BASE) //0x40020058
hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2U;
下面这部分就是设置开始配置的DMA各个参数了,然后写到相应DMA通道的CCR寄存器。
/* Clear PL, MSIZE, PSIZE, MINC, PINC, CIRC, DIR and MEM2MEM bits */
CLEAR_BIT(hdma->Instance->CCR, (DMA_CCR_PL | DMA_CCR_MSIZE | DMA_CCR_PSIZE | \
DMA_CCR_MINC | DMA_CCR_PINC | DMA_CCR_CIRC | \
DMA_CCR_DIR | DMA_CCR_MEM2MEM));
/* Set the DMA Channel configuration */
SET_BIT(hdma->Instance->CCR, (hdma->Init.Direction | \
hdma->Init.PeriphInc | hdma->Init.MemInc | \
hdma->Init.PeriphDataAlignment | hdma->Init.MemDataAlignment | \
hdma->Init.Mode | hdma->Init.Priority));
看注释了解,这个函数初始化了一些需要用到的参数。
/* Initialize parameters for DMAMUX channel :
DMAmuxChannel, DMAmuxChannelStatus and DMAmuxChannelStatusMask
*/
DMA_CalcDMAMUXChannelBaseAndMask(hdma);
进入后发现这个函数主要是更新了几个参数。第一个DMAmuxChannel,这个参数貌似就是说DMAMUX也有五个通道,分别依次连接了DMA的五个通道。参照着宏定义来看,唯一的变量就是ChannelIndex,这个是不是我们之前算的 (3-1)<<2 啊,这里又给右移回去了,所以DMAmuxChannel = 0x04 * x (x = 0 to 4),x就是DMA通道1~5.
channel_number 的值就是0~4,对应DMA通道1~5。
DMAmuxChannelStatus 这个变量我不是很理解,通道状态等于DMAMUX1_ChannelStatus,这是个固定值,就是0x40020880这个地址的值,也就是下面第三张图的寄存器。
DMAmuxChannelStatusMask 最后这个就不用解释了。
#define DMAMUX1 ((DMAMUX_Channel_TypeDef *) DMAMUX1_BASE) //0x40020800
#define DMAMUX1_Channel0 ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel0_BASE)//DMAMUX1+0
#define DMAMUX1_Channel1 ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel1_BASE)//DMAMUX1+4
#define DMAMUX1_Channel2 ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel2_BASE)//DMAMUX1+8
#define DMAMUX1_Channel3 ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel3_BASE)//DMAMUX1+C
#define DMAMUX1_Channel4 ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel4_BASE)//DMAMUX1+10
/* Associate a DMA Channel to a DMAMUX channel */
hdma->DMAmuxChannel = (DMAMUX_Channel_TypeDef *)(uint32_t)((uint32_t)DMAMUX1_Channel0 + ((hdma->ChannelIndex >> 2U) * ((uint32_t)DMAMUX1_Channel1 - (uint32_t)DMAMUX1_Channel0)));
/* Prepare channel_number used for DMAmuxChannelStatusMask computation */
channel_number = (((uint32_t)hdma->Instance & 0xFFU) - 8U) / 20U;
#endif
/* Initialize the field DMAmuxChannelStatus to DMAMUX1_ChannelStatus base */
hdma->DMAmuxChannelStatus = DMAMUX1_ChannelStatus;
/* Initialize the field DMAmuxChannelStatusMask with the corresponding index of the DMAMUX channel selected for the current ChannelIndex */
hdma->DMAmuxChannelStatusMask = 1UL << (channel_number & 0x1FU);
下面这个就是最重要的部分了,将DMAMUX的通道的一头连接到外设上。这里我们就搞完了DMAMUX通道的两头了,一头连接外设,一头连接DMA。
Request 的值一开始配置的时候设置好的,为SPI的TX请求。这部分的宏太多只截取了一部分。
CCR就是下面官方图的CXCR,这个X就是每个DMAMUX通道都有这个寄存器的意思。
/* Set peripheral request to DMAMUX channel */
hdma->DMAmuxChannel->CCR = (hdma->Init.Request & DMAMUX_CxCR_DMAREQ_ID);
总结
至此就结束了我对DMAMUX的理解,总之就是这部分被归到了DMA部分,属于是从原来的DMA--外设,到现在的DMA--DMAMUX--外设。最后DMA初始化还有一部分,如果当你将DMA请求设置为下面几个宏,就会还有一部分设置。这部分我没用过不是很了解,看文档感觉像是为了一些没有DMA功能的东西准备的。
#define DMA_REQUEST_GENERATOR0 LL_DMAMUX_REQ_GENERATOR0
#define DMA_REQUEST_GENERATOR1 LL_DMAMUX_REQ_GENERATOR1
#define DMA_REQUEST_GENERATOR2 LL_DMAMUX_REQ_GENERATOR2
#define DMA_REQUEST_GENERATOR3 LL_DMAMUX_REQ_GENERATOR3