« 上一篇下一篇 »

STM32——库函数分析:DAC&DMA相关

今天我们来学习STM32的DAC模块与DMA控制器的配合使用。

一、DAC配置

STM32中的数字/模拟转换模块(DAC)是12位数字输入,0 ~ VREF+范围电压输出的DAC。其可以配置成8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC有2个输出通道,每个通道都有单独的转换器,可以工作在双DAC模式。在此模式下,可以同步的更新2个通道的输出,这2个通道的转换可以同时进行,也可以分别进行。

/**
  * @brief  使能DAC的时钟,初始化GPIO
  * @param  无
  * @retval 无
  */
static void DAC_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  DAC_InitTypeDef  DAC_InitStructure;
  /* 使能GPIOA时钟 */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 使能DAC时钟 */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  /* DAC的GPIO配置,模拟输入 */
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_4 | GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 为了避免寄生的干扰与额外的功耗,管脚PA4/PA5在使能之前应当设置为模拟输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;//使用TIM2作为触发源
  DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;//不使用波形发生器
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;//不使用DAC输出缓冲
  /* 配置DAC 通道1 */
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);
  /* 配置DAC 通道2 */
  DAC_Init(DAC_Channel_2, &DAC_InitStructure);
  /* 使能通道1 由PA4输出 */
  DAC_Cmd(DAC_Channel_1, ENABLE);
  
  /* 使能通道2 由PA5输出 */
  DAC_Cmd(DAC_Channel_2, ENABLE);
  
    /* 使能DAC1的DMA请求 */
    DAC_DMACmd(DAC_Channel_1, ENABLE);
  /* 使能DAC2的DMA请求 */
  DAC_DMACmd(DAC_Channel_2, ENABLE);
}

STM32F103的DAC输出在PA4/5两个引脚,为了避免寄生的干扰与额外的功耗,在使能PA4/5的DAC复用功能之前,我们要把这两个引脚配置成“模拟输入”模式。

  DAC_InitTypeDef  DAC_InitStructure;

首先定义一个结构体DAC_InitStructure,其类型为DAC_InitTypeDef,其成员有4个,分别为:DAC触发器、波形生成、噪声或三角波幅度、输出缓冲。

/* 使能DAC时钟 */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
DAC挂载于APB1总线上,使能其时钟。
  DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;//使用TIM2作为触发源
  DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;//不使用波形发生器
  DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;//不使用DAC输出缓冲
  /* 配置DAC 通道1 */
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);

DAC_Trigger_T2_TRGO、DAC_WaveGeneration_None、DAC_OutputBuffer_Disable分别被定义为:

#define DAC_Trigger_T2_TRGO                ((uint32_t)0x00000024) /*!< TIM2 TRGO selected as external conversion trigger for DAC channel */
#define DAC_WaveGeneration_None            ((uint32_t)0x00000000)
#define DAC_OutputBuffer_Disable           ((uint32_t)0x00000002)

DAC_Channel_1被定义为

#define DAC_Channel_1                      ((uint32_t)0x00000000)

执行流来到DAC初始化库函数

  /* 配置DAC 通道1 */
  DAC_Init(DAC_Channel_1, &DAC_InitStructure);

DAC_Init();

/**
  * @brief  Initializes the DAC peripheral according to the specified 
  *         parameters in the DAC_InitStruct.
  * @param  DAC_Channel: the selected DAC channel. 
  *   This parameter can be one of the following values:
  *     @arg DAC_Channel_1: DAC Channel1 selected
  *     @arg DAC_Channel_2: DAC Channel2 selected
  * @param  DAC_InitStruct: pointer to a DAC_InitTypeDef structure that
  *        contains the configuration information for the specified DAC channel.
  * @retval None
  */
void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct)
{
  uint32_t tmpreg1 = 0, tmpreg2 = 0;
  /* Check the DAC parameters */
  assert_param(IS_DAC_TRIGGER(DAC_InitStruct->DAC_Trigger));
  assert_param(IS_DAC_GENERATE_WAVE(DAC_InitStruct->DAC_WaveGeneration));
  assert_param(IS_DAC_LFSR_UNMASK_TRIANGLE_AMPLITUDE(DAC_InitStruct->DAC_LFSRUnmask_TriangleAmplitude));
  assert_param(IS_DAC_OUTPUT_BUFFER_STATE(DAC_InitStruct->DAC_OutputBuffer));
/*---------------------------- DAC CR Configuration --------------------------*/
  /* Get the DAC CR value */
  tmpreg1 = DAC->CR;
  /* Clear BOFFx, TENx, TSELx, WAVEx and MAMPx bits */
  tmpreg1 &= ~(CR_CLEAR_MASK << DAC_Channel);
  /* Configure for the selected DAC channel: buffer output, trigger, wave generation,
     mask/amplitude for wave generation */
  /* Set TSELx and TENx bits according to DAC_Trigger value */
  /* Set WAVEx bits according to DAC_WaveGeneration value */
  /* Set MAMPx bits according to DAC_LFSRUnmask_TriangleAmplitude value */ 
  /* Set BOFFx bit according to DAC_OutputBuffer value */   
  tmpreg2 = (DAC_InitStruct->DAC_Trigger | DAC_InitStruct->DAC_WaveGeneration |
             DAC_InitStruct->DAC_LFSRUnmask_TriangleAmplitude | DAC_InitStruct->DAC_OutputBuffer);
  /* Calculate CR register value depending on DAC_Channel */
  tmpreg1 |= tmpreg2 << DAC_Channel;
  /* Write to DAC CR */
  DAC->CR = tmpreg1;
}

上面为函数定义,我们按照代码顺序一条条来对其进行分析。

  tmpreg1 = DAC->CR;

tmpreg1 = 0;因为这个时候还没有配置CR寄存器,所以其值为0。

  tmpreg1 &= ~(CR_CLEAR_MASK << DAC_Channel);

因为0与上任何数都为0,所以tmpreg1仍然是0。

  tmpreg2 = (DAC_InitStruct->DAC_Trigger | DAC_InitStruct->DAC_WaveGeneration |
             DAC_InitStruct->DAC_LFSRUnmask_TriangleAmplitude | DAC_InitStruct->DAC_OutputBuffer);

即tmpreg2 = (0x0000 0024 | 0x0000 0000 | 0 | 0x0000 0002) = 0x0000 0026;

  tmpreg1 |= tmpreg2 << DAC_Channel;

即tmpreg1 = 0 | (0x0000 0026 << 0x0000 0000) = 0x0000 0026;

  DAC->CR = tmpreg1;

DAC->CR = 0x0000 0026,根据DAC控制寄存器描述

可知,此时DAC被配置为:波形生成不使能、定时器2更新事件被选为触发输出、DAC通道1输出缓冲不使能、使能DAC通道1触发器

  /* 使能通道1 由PA4输出 */
  DAC_Cmd(DAC_Channel_1, ENABLE);

使能DAC通道1。

    DAC_DMACmd(DAC_Channel_1, ENABLE);

根据DAC_DMACmd()函数定义

void DAC_DMACmd(uint32_t DAC_Channel, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_DAC_CHANNEL(DAC_Channel));
  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    /* Enable the selected DAC channel DMA request */
    DAC->CR |= (DAC_CR_DMAEN1 << DAC_Channel);
  }
  else
  {
    /* Disable the selected DAC channel DMA request */
    DAC->CR &= ~(DAC_CR_DMAEN1 << DAC_Channel);
  }
}

可得DAC->CR |= (DAC_CR_DMAEN1 << 0x0000 0000),DAC_CR_DMAEN1定义如下

#define  DAC_CR_DMAEN1                       ((uint32_t)0x00001000)        /*!< DAC channel1 DMA enable */

所以DAC->CR |= (0x0000 1000 << 0x0000 0000) 

DAC->CR |= 0x0000 1000,即DAC->CR寄存器的Bit12置1

由寄存器的描述

可知,此时DAC被配置为:DAC通道1的DMA模式被使能

二、定时器配置

/**
  * @brief  配置TIM
  * @param  无
  * @retval 无
  */
static void DAC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
/* 使能TIM2时钟,TIM2CLK 为72M */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  /* TIM2基本定时器配置 */
 // TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); 
  TIM_TimeBaseStructure.TIM_Period = 20;       //定时周期 20  
  TIM_TimeBaseStructure.TIM_Prescaler = 0x0;       //预分频器为0
  TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;    //时钟分频因子
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  /* 配置TIM2触发源 */
  TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
/* 使能TIM2 */
  TIM_Cmd(TIM2, ENABLE);
}

有关定时器的配置之前已经有专门的文章描述过了,所以此处只做简单说明,具体描述请参照之前博文。

定时器2被配置为:开启定时器2时钟、设置时间周期值为20、预分频器值为0、分频因子为0、向上计数模式、并进行初始化

  TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
/**
  * @brief  Selects the TIMx Trigger Output Mode.
  * @param  TIMx: where x can be 1, 2, 3, 4, 5, 6, 7, 8, 9, 12 or 15 to select the TIM peripheral.
  * @param  TIM_TRGOSource: specifies the Trigger Output source.
  *   This paramter can be one of the following values:
  *
  *  - For all TIMx
  *     @arg TIM_TRGOSource_Reset:  The UG bit in the TIM_EGR register is used as the trigger output (TRGO).
  *     @arg TIM_TRGOSource_Enable: The Counter Enable CEN is used as the trigger output (TRGO).
  *     @arg TIM_TRGOSource_Update: The update event is selected as the trigger output (TRGO).
  *
  *  - For all TIMx except TIM6 and TIM7
  *     @arg TIM_TRGOSource_OC1: The trigger output sends a positive pulse when the CC1IF flag
  *                              is to be set, as soon as a capture or compare match occurs (TRGO).
  *     @arg TIM_TRGOSource_OC1Ref: OC1REF signal is used as the trigger output (TRGO).
  *     @arg TIM_TRGOSource_OC2Ref: OC2REF signal is used as the trigger output (TRGO).
  *     @arg TIM_TRGOSource_OC3Ref: OC3REF signal is used as the trigger output (TRGO).
  *     @arg TIM_TRGOSource_OC4Ref: OC4REF signal is used as the trigger output (TRGO).
  *
  * @retval None
  */
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource)
{
  /* Check the parameters */
  assert_param(IS_TIM_LIST7_PERIPH(TIMx));
  assert_param(IS_TIM_TRGO_SOURCE(TIM_TRGOSource));
  /* Reset the MMS Bits */
  TIMx->CR2 &= (uint16_t)~((uint16_t)TIM_CR2_MMS);
  /* Select the TRGO source */
  TIMx->CR2 |=  TIM_TRGOSource;
}

由上述触发源选择函数定义

TIM2->CR2 = 0 & (uint16_t)~((uint16_t)TIM_CR2_MMS) = 0;

由定时器2的控制寄存器2的描述

可知,定时器2被配置为:复位MMS位

TIM2->CR2 |= TIM_TRGOSource;

TIM_TRGOSource_Update被定义为

#define TIM_TRGOSource_Update              ((uint16_t)0x0020)

所以TIM2->CR2 = 0 | 0x0020 = 0x0020;

由定时器2的CR2寄存器描述可知

定时器2被配置为:定时器2的更新事件被选为触发输入(TRGO)

三、DMA配置

STM32的DMA(直接存储器存取)用来提供外设与存储器或者存储器与存储器之间的高速数据传输。无须CPU任何干预,通过DMA,数据可以快速移动,这就节省了CPU的资源来做其他事情。STM32F103有两个DMA控制器,一共有12个通道(DMA1有7个,DMA2有5个),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器用于协调各个DMA请求的优先权。

/**
  * @brief  配置DMA
  * @param  无
  * @retval 无
  */
static void DAC_DMA_Config(void)
{
DMA_InitTypeDef  DMA_InitStructure;
/* 使能DMA2时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
/* 配置DMA2 */
  DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_Address;//外设数据地址
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit ;//内存数据地址 DualSine12bit
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//数据传输设置外设为目的地
  DMA_InitStructure.DMA_BufferSize = 32;//缓存大小为32位
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设数据地址固定
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存数据地址自增
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;//外设数据以字为单位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;//内存数据以字为单位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//循环模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高DMA通道优先级
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//非内存至内存模式
  DMA_Init(DMA2_Channel4, &DMA_InitStructure);
  /* 使能DMA2-14通道 */
  DMA_Cmd(DMA2_Channel4, ENABLE);
  
  DMA_Init(DMA2_Channel3, &DMA_InitStructure);
  DMA_Cmd(DMA2_Channel3, ENABLE);
}
DMA_InitTypeDef  DMA_InitStructure;

首先定义一个结构体DMA_InitStructure,类型为DMA_InitTypeDef,成员为:DMA外设基地址、DMA内存基地址、DMA方向、DMA数据传输数量、DMA外设地址自增选择、DMA内存地址自增选择、DMA外设数据大小、DMA内存数据大小、DMA模式。

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

使能DMA2时钟,2个DMA控制器全部挂载于AHB总线上

相关配置参数的定义汇总如下

#define DAC_DHR12RD_Address      0x40007420 //0x40007420

这个地址可查询用户手册得知,为DAC基地址加上对应寄存器偏移地址。

uint32_t DualSine12bit[32];

DualSine12bit为一个32个元素的数组,其地址做为内存基地址。

#define DMA_DIR_PeripheralDST              ((uint32_t)0x00000010)
#define DMA_PeripheralInc_Disable          ((uint32_t)0x00000000)
#define DMA_MemoryInc_Enable               ((uint32_t)0x00000080)
#define DMA_PeripheralDataSize_Word        ((uint32_t)0x00000200)
#define DMA_MemoryDataSize_Word            ((uint32_t)0x00000800)
#define DMA_Mode_Circular                  ((uint32_t)0x00000020)
#define DMA_Priority_High                  ((uint32_t)0x00002000)
#define DMA_M2M_Disable                    ((uint32_t)0x00000000)

下面分析DMA初始化库函数

DMA_Init();

/**
  * @brief  Initializes the DMAy Channelx according to the specified
  *         parameters in the DMA_InitStruct.
  * @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_InitStruct: pointer to a DMA_InitTypeDef structure that
  *         contains the configuration information for the specified DMA Channel.
  * @retval None
  */
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
{
  uint32_t tmpreg = 0;
  /* Check the parameters */
  assert_param(IS_DMA_ALL_PERIPH(DMAy_Channelx));
  assert_param(IS_DMA_DIR(DMA_InitStruct->DMA_DIR));
  assert_param(IS_DMA_BUFFER_SIZE(DMA_InitStruct->DMA_BufferSize));
  assert_param(IS_DMA_PERIPHERAL_INC_STATE(DMA_InitStruct->DMA_PeripheralInc));
  assert_param(IS_DMA_MEMORY_INC_STATE(DMA_InitStruct->DMA_MemoryInc));   
  assert_param(IS_DMA_PERIPHERAL_DATA_SIZE(DMA_InitStruct->DMA_PeripheralDataSize));
  assert_param(IS_DMA_MEMORY_DATA_SIZE(DMA_InitStruct->DMA_MemoryDataSize));
  assert_param(IS_DMA_MODE(DMA_InitStruct->DMA_Mode));
  assert_param(IS_DMA_PRIORITY(DMA_InitStruct->DMA_Priority));
  assert_param(IS_DMA_M2M_STATE(DMA_InitStruct->DMA_M2M));
/*--------------------------- DMAy Channelx CCR Configuration -----------------*/
  /* Get the DMAy_Channelx CCR value */
  tmpreg = DMAy_Channelx->CCR;
  /* Clear MEM2MEM, PL, MSIZE, PSIZE, MINC, PINC, CIRC and DIR bits */
  tmpreg &= CCR_CLEAR_Mask;
  /* Configure DMAy Channelx: data transfer, data size, priority level and mode */
  /* Set DIR bit according to DMA_DIR value */
  /* Set CIRC bit according to DMA_Mode value */
  /* Set PINC bit according to DMA_PeripheralInc value */
  /* Set MINC bit according to DMA_MemoryInc value */
  /* Set PSIZE bits according to DMA_PeripheralDataSize value */
  /* Set MSIZE bits according to DMA_MemoryDataSize value */
  /* Set PL bits according to DMA_Priority value */
  /* Set the MEM2MEM bit according to DMA_M2M value */
  tmpreg |= DMA_InitStruct->DMA_DIR | DMA_InitStruct->DMA_Mode |
            DMA_InitStruct->DMA_PeripheralInc | DMA_InitStruct->DMA_MemoryInc |
            DMA_InitStruct->DMA_PeripheralDataSize | DMA_InitStruct->DMA_MemoryDataSize |
            DMA_InitStruct->DMA_Priority | DMA_InitStruct->DMA_M2M;
  /* Write to DMAy Channelx CCR */
  DMAy_Channelx->CCR = tmpreg;
/*--------------------------- DMAy Channelx CNDTR Configuration ---------------*/
  /* Write to DMAy Channelx CNDTR */
  DMAy_Channelx->CNDTR = DMA_InitStruct->DMA_BufferSize;
/*--------------------------- DMAy Channelx CPAR Configuration ----------------*/
  /* Write to DMAy Channelx CPAR */
  DMAy_Channelx->CPAR = DMA_InitStruct->DMA_PeripheralBaseAddr;
/*--------------------------- DMAy Channelx CMAR Configuration ----------------*/
  /* Write to DMAy Channelx CMAR */
  DMAy_Channelx->CMAR = DMA_InitStruct->DMA_MemoryBaseAddr;
}

函数定义如上。

  tmpreg = DMAy_Channelx->CCR;

即tmpreg = 0;

  tmpreg &= CCR_CLEAR_Mask;

即tmpreg = 0 & CCR_CLEAR_Mask = 0;

  tmpreg |= DMA_InitStruct->DMA_DIR | DMA_InitStruct->DMA_Mode |
            DMA_InitStruct->DMA_PeripheralInc | DMA_InitStruct->DMA_MemoryInc |
            DMA_InitStruct->DMA_PeripheralDataSize | DMA_InitStruct->DMA_MemoryDataSize |
            DMA_InitStruct->DMA_Priority | DMA_InitStruct->DMA_M2M;

即tmpreg = 0 | 0x0000 0010 | 0x0000 0020 | 0x0000 0000 | 0x0000 0080 | 0x0000 0200 | 0x0000 0800 | 0x0000 2000 | 0x0000 0000 = 0x0000 2AB0;

  DMAy_Channelx->CCR = tmpreg;

DMA2_Channel4->CCR = 0x0000 2AB0;根据DMA2的CCR寄存器描述

可知,DMA2被配置为:非存储器到存储器模式、通道优先级为高、存储器数据宽度为32位、外设数据宽度为32位、执行存储器地址增量操作、不执行外设地址增量操作、执行循环操作、从存储器读数据

  DMAy_Channelx->CNDTR = DMA_InitStruct->DMA_BufferSize;

DMA2_Channel4->CNDTR = 32;由DMA2的CNDTR寄存器描述

可知,DMA2被配置为:数据传输数量为32

  DMAy_Channelx->CPAR = DMA_InitStruct->DMA_PeripheralBaseAddr;

DMA2_Channel4->CPAR = 0x40007420;由DMA2的CPAR寄存器描述

可知,DMA2被配置为:0x40007420(DAC_DHR12RD寄存器)为数据传输的目标地址,且操作自动地与字地址对齐

  DMAy_Channelx->CMAR = DMA_InitStruct->DMA_MemoryBaseAddr;

DMA2_Channel4->CMAR = (uint32_t)&DualSine12bit;由DMA2的CMAR(内存地址寄存器)描述

可知,DMA2被配置为:DualSine12bit[]数组做为数据传输来源,且操作自动地与字地址对齐

配置完毕,这样就可以以DMA的方式来使用STM32的DAC功能,通过改变代码中DMA数据源的数据就可以改变输出的电压值。


原创作品