ChibiOS STM32F1 DMA 驱动分析
STM32F1和STM32F4的DMA架构存在很大的不同,这里介绍ChibiOS对于STM32F1系列DMA的驱动实现。
首先我们需要找到对于STM32F1系列的寄存器定义:os\hal\ports\STM32\STM32F1xx\stm32_registry.h
以及:os\common\ext\ST\STM32F1xx\stm32f103xb.h
找到其中对于DMA部分的描述:
/* DMA attributes.*/
#define STM32_ADVANCED_DMA FALSE
#define STM32_DMA_SUPPORTS_DMAMUX FALSE
#define STM32_DMA_SUPPORTS_CSELR FALSE
#define STM32_DMA1_NUM_CHANNELS 7
#define STM32_DMA1_CH1_HANDLER Vector6C
#define STM32_DMA1_CH2_HANDLER Vector70
#define STM32_DMA1_CH3_HANDLER Vector74
#define STM32_DMA1_CH4_HANDLER Vector78
#define STM32_DMA1_CH5_HANDLER Vector7C
#define STM32_DMA1_CH6_HANDLER Vector80
#define STM32_DMA1_CH7_HANDLER Vector84
#define STM32_DMA1_CH1_NUMBER 11
#define STM32_DMA1_CH2_NUMBER 12
#define STM32_DMA1_CH3_NUMBER 13
#define STM32_DMA1_CH4_NUMBER 14
#define STM32_DMA1_CH5_NUMBER 15
#define STM32_DMA1_CH6_NUMBER 16
#define STM32_DMA1_CH7_NUMBER 17
#define STM32_DMA2_NUM_CHANNELS 0
从上可见,STM32F1的DMA功能相对来说还是比较少的。
ChibiOS对于STM32 DMA一个STREAM的描述结构体:
/**
* @brief STM32 DMA stream descriptor structure.
*/
typedef struct {
DMA_TypeDef *dma; /**< @brief Associated DMA. */
DMA_Channel_TypeDef *channel; /**< @brief Associated DMA channel. */
uint32_t cmask; /**< @brief Mask of streams sharing
the same ISR. */
#if (STM32_DMA_SUPPORTS_CSELR == TRUE) || defined(__DOXYGEN__)
volatile uint32_t *cselr; /**< @brief Associated CSELR reg. */
#elif STM32_DMA_SUPPORTS_DMAMUX == TRUE
DMAMUX_Channel_TypeDef *mux; /**< @brief Associated DMA mux. */
#else
uint8_t dummy; /**< @brief Filler. */
#endif
uint8_t shift; /**< @brief Bit offset in ISR, IFCR
and CSELR registers. */
uint8_t selfindex; /**< @brief Index to self in array. */
uint8_t vector; /**< @brief Associated IRQ vector. */
} stm32_dma_stream_t;
其中的DMA_TypeDef和DMA_Channel_TypeDef是STM32标准库中对于DMA寄存器的直接描述,定义如下:
/**
* @brief DMA Controller
*/
typedef struct
{
__IO uint32_t CCR;
__IO uint32_t CNDTR;
__IO uint32_t CPAR;
__IO uint32_t CMAR;
} DMA_Channel_TypeDef;
typedef struct
{
__IO uint32_t ISR;
__IO uint32_t IFCR;
} DMA_TypeDef;
对于STM32F1和STM32F4 DMA的对比:https://www.cnblogs.com/helesheng/p/18167026

对于DMA1含有7个通道,DMA2含有5个通道。

根据前面的定义:
#define STM32_DMA1_NUM_CHANNELS 7
#define STM32_DMA2_NUM_CHANNELS 0
/**
* @brief Total number of DMA streams.
* @details This is the total number of streams among all the DMA units.
*/
#define STM32_DMA_STREAMS (STM32_DMA1_NUM_CHANNELS + \
STM32_DMA2_NUM_CHANNELS)
对于STM32F1只有7个流可供使用。
随后在stm32_dma.c中定义了一个全局数组,用于管理DMA1 DMA2的所有通道:
/**
* @brief DMA streams descriptors.
* @details This table keeps the association between an unique stream
* identifier and the involved physical registers.
* @note Don't use this array directly, use the appropriate wrapper macros
* instead: @p STM32_DMA1_STREAM1, @p STM32_DMA1_STREAM2 etc.
*/
const stm32_dma_stream_t _stm32_dma_streams[STM32_DMA_STREAMS] = {
#if STM32_DMA1_NUM_CHANNELS > 0
{DMA1, DMA1_Channel1, STM32_DMA1_CH1_CMASK, DMA1_CH1_VARIANT, 0, 0, STM32_DMA1_CH1_NUMBER},
#endif
#if STM32_DMA1_NUM_CHANNELS > 1
{DMA1, DMA1_Channel2, STM32_DMA1_CH2_CMASK, DMA1_CH2_VARIANT, 4, 1, STM32_DMA1_CH2_NUMBER},
#endif
#if STM32_DMA1_NUM_CHANNELS > 2
{DMA1, DMA1_Channel3, STM32_DMA1_CH3_CMASK, DMA1_CH3_VARIANT, 8, 2, STM32_DMA1_CH3_NUMBER},
#endif
#if STM32_DMA1_NUM_CHANNELS > 3
{DMA1, DMA1_Channel4, STM32_DMA1_CH4_CMASK, DMA1_CH4_VARIANT, 12, 3, STM32_DMA1_CH4_NUMBER},
#endif
#if STM32_DMA1_NUM_CHANNELS > 4
{DMA1, DMA1_Channel5, STM32_DMA1_CH5_CMASK, DMA1_CH5_VARIANT, 16, 4, STM32_DMA1_CH5_NUMBER},
#endif
#if STM32_DMA1_NUM_CHANNELS > 5
{DMA1, DMA1_Channel6, STM32_DMA1_CH6_CMASK, DMA1_CH6_VARIANT, 20, 5, STM32_DMA1_CH6_NUMBER},
#endif
#if STM32_DMA1_NUM_CHANNELS > 6
{DMA1, DMA1_Channel7, STM32_DMA1_CH7_CMASK, DMA1_CH7_VARIANT, 24, 6, STM32_DMA1_CH7_NUMBER},
#endif
#if STM32_DMA1_NUM_CHANNELS > 7
{DMA1, DMA1_Channel8, STM32_DMA1_CH8_CMASK, DMA1_CH8_VARIANT, 28, 7, STM32_DMA1_CH8_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 0
{DMA2, DMA2_Channel1, STM32_DMA2_CH1_CMASK, DMA2_CH1_VARIANT, 0, 0 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH1_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 1
{DMA2, DMA2_Channel2, STM32_DMA2_CH2_CMASK, DMA2_CH2_VARIANT, 4, 1 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH2_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 2
{DMA2, DMA2_Channel3, STM32_DMA2_CH3_CMASK, DMA2_CH3_VARIANT, 8, 2 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH3_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 3
{DMA2, DMA2_Channel4, STM32_DMA2_CH4_CMASK, DMA2_CH4_VARIANT, 12, 3 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH4_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 4
{DMA2, DMA2_Channel5, STM32_DMA2_CH5_CMASK, DMA2_CH5_VARIANT, 16, 4 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH5_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 5
{DMA2, DMA2_Channel6, STM32_DMA2_CH6_CMASK, DMA2_CH6_VARIANT, 20, 5 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH6_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 6
{DMA2, DMA2_Channel7, STM32_DMA2_CH7_CMASK, DMA2_CH7_VARIANT, 24, 6 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH7_NUMBER},
#endif
#if STM32_DMA2_NUM_CHANNELS > 7
{DMA2, DMA2_Channel8, STM32_DMA2_CH8_CMASK, DMA2_CH8_VARIANT, 28, 7 + STM32_DMA1_NUM_CHANNELS, STM32_DMA2_CH8_NUMBER},
#endif
};
例如对于DMA1通道1,定义数组元素为:
{DMA1, DMA1_Channel1, STM32_DMA1_CH1_CMASK, DMA1_CH1_VARIANT, 0, 0, STM32_DMA1_CH1_NUMBER},
其中前两项对应实际的寄存器指针,可以实现寄存器的直接操作。
后面的内容涉及到ChibiOS对于不同通道的管理,后续会说明。
我们关注对于DMA的初始化函数:
/**
* @brief STM32 DMA helper initialization.
*
* @init
*/
void dmaInit(void) {
int i;
dma.allocated_mask = 0U;
dma.isr_mask = 0U;
for (i = 0; i < STM32_DMA_STREAMS; i++) {
_stm32_dma_streams[i].channel->CCR = STM32_DMA_CCR_RESET_VALUE;
dma.streams[i].func = NULL;
}
DMA1->IFCR = 0xFFFFFFFFU;
#if STM32_DMA2_NUM_CHANNELS > 0
DMA2->IFCR = 0xFFFFFFFFU;
#endif
}
此函数主要是对各个变量进行复位,其中复位了各个通道的CCR寄存器为STM32_DMA_CCR_RESET_VALUE,这个宏的实际值为0.
并且清除了对应的系统回调函数,随后置位所有DMA控制器的IFCR寄存器。
下面重点关注这些寄存器的作用:
首先是每个通道的CCR寄存器:
实际上,每个DMA通道含有四个独立的寄存器:CCR CNDTR CPAR CMAR。

CCR寄存器位说明:
MEM2MEM表示是否为存储器到存储器模式,置位使能此模式。
PL[1:0]表示这个通道的优先级,2bit,范围0-3,数值越大表示优先级越高。
MSIZE[1:0]表示存储器传输数据宽度: 0-2分别表示8 16 32bits
PSIZE[1:0]表示外设传输数据宽度:0-2分别表示8 16 32bits
MINC表示存储器指针自增模式
PINC表示外设指针自增模式
CIRC表示DMA循环传输模式
DIR表示数据传输方向,0表示从外设读取数据,1表示从存储器读取数据。
TEIE表示传输错误中断使能
HTIE表示传输一半完成中断使能
TCIE表示传输传输完成中断使能
EN表示通道使能
所以这个寄存器所有数值设置为0表示通道不使用,为默认状态,数据手册给出的Reset value: 0x0000 0000。
将DMA控制器的IFCR寄存器全部置位表示清除对应的全部中断:

这个寄存器是一个只写寄存器,向对应为写1表示清除对应的中断。
综上,这个DMA初始化函数dmaInit()就是关闭所有DMA通道,设置为默认值,并且清除之前可能存在的所有中断。
随后我们关注一些辅助宏:
首先是对于DMA一个通道外设地址的设置:
/**
* @brief Associates a peripheral data register to a DMA stream.
* @note This function can be invoked in both ISR or thread context.
* @pre The stream must have been allocated using @p dmaStreamAlloc().
* @post After use the stream can be released using @p dmaStreamRelease().
*
* @param[in] dmastp pointer to a stm32_dma_stream_t structure
* @param[in] addr value to be written in the CPAR register
*
* @special
*/
#define dmaStreamSetPeripheral(dmastp, addr) { \
(dmastp)->channel->CPAR = (uint32_t)(addr); \
}
也就是直接设置这个通道的CPAR寄存器:

这个寄存器的含义很简单,就是这个DMA通道的外设地址。
对应的,也有对于内存地址的设置函数:
/**
* @brief Associates a memory destination to a DMA stream.
* @note This function can be invoked in both ISR or thread context.
* @pre The stream must have been allocated using @p dmaStreamAlloc().
* @post After use the stream can be released using @p dmaStreamRelease().
*
* @param[in] dmastp pointer to a stm32_dma_stream_t structure
* @param[in] addr value to be written in the CMAR register
*
* @special
*/
#define dmaStreamSetMemory0(dmastp, addr) { \
(dmastp)->channel->CMAR = (uint32_t)(addr); \
}
通过设置这个通道的CMAR寄存器实现:

之后是对于这个通道传输数量的配置宏:
/**
* @brief Sets the number of transfers to be performed.
* @note This function can be invoked in both ISR or thread context.
* @pre The stream must have been allocated using @p dmaStreamAlloc().
* @post After use the stream can be released using @p dmaStreamRelease().
*
* @param[in] dmastp pointer to a stm32_dma_stream_t structure
* @param[in] size value to be written in the CNDTR register
*
* @special
*/
#define dmaStreamSetTransactionSize(dmastp, size) { \
(dmastp)->channel->CNDTR = (uint32_t)(size); \
}
通过设置这个通道的CNDTR寄存器实现对于传输数量的配置,这是一个16位寄存器,所以单次最大传输数量为65536个传输单元(8bit 16bit 32bit)

在DMA传输过程中,我们可以通过读取这个寄存器实现对于剩余传输数量的获取,同样提供了如下宏:
/**
* @brief Returns the number of transfers to be performed.
* @note This function can be invoked in both ISR or thread context.
* @pre The stream must have been allocated using @p dmaStreamAlloc().
* @post After use the stream can be released using @p dmaStreamRelease().
*
* @param[in] dmastp pointer to a stm32_dma_stream_t structure
* @return The number of transfers to be performed.
*
* @special
*/
#define dmaStreamGetTransactionSize(dmastp) ((size_t)((dmastp)->channel->CNDTR))
浙公网安备 33010602011771号