STM32F407 的 DAC(Digital-to-analog converters,数模转换器)功能。我们通过学习 DAC,分别是 DAC 输出``、DAC 输出三角波和 DAC 输出

正弦波。

DAC 简介

STM32F407 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC。DAC 可以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合使用。DAC 工作在 12 位模式时,数据可以设置成左对齐或右对齐。DAC 模块有 2 个输出通道,每个通道都有单独的转换器。在双 DAC 模式下,2 个通道可以独立地进行转换,也可以同时进行转换并同步地更新 2个通道的输出。DAC 可以通过引脚输入参考电压 Vref+以获得更精确的转换结果。

STM32 的 DAC 模块主要特点:

  • 两个 DAC 转换器:各对应一个输出通道
  • 12 位模式下数据采用左对齐或右对齐
  • 同步更新功能
  • DMA 下溢错误检测
  • 噪声\三角波形生成
  • DAC 双通道单独或同时转换
  • 每个通道都有 DMA 功能

DAC 通道框图

VDDA 和 VSSA 为 DAC 模块模拟部分的供电,而 Vref+则是 DAC 模块的参考电压输入引脚。DAC_OUTx 就是 DAC 的两个输出通道了(对应 PA4 或者 PA5 引脚)。

DAC的这些输入/输出引脚信息

  • DAC 输出是受 DORx(x=1/2,下同)寄存器直接控制的,但是我们不能直接往 DORx 寄存器写入数据,而是通过 DHRx 间接的传给 DORx 寄存器,实现对DAC 输出的控制。
  • STM32F407 的 DAC 支持 8/12 位模式,8 位模式的时候是固定的右对齐的,而 12 位模式又可以设置左对齐/右对齐。DAC 单通道模式下的数据寄存器对齐方式,总共有 3 种情况:

  • 8 位数据右对齐:用户将数据写入 DAC_DHR8Rx[7:0]位(实际存入 DHRx[11:4]位)。
  • 12 位数据左对齐:用户将数据写入 DAC_DHR12Lx[15:4]位(实际存入 DHRx[11:0]位)。
  • 12 位数据右对齐:用户将数据写入 DAC_DHR12Rx[11:0]位(实际存入 DHRx[11:0]位)。

使用单通道模式下的 DAC 通道 1,采用 12 位右对齐格式,所以采用第③种情况。另外 DAC 还具有双通道转换功能。

对于 DAC 双通道(可用时),也有三种可能的方式,如下图所示:

  • 8 位数据右对齐:用户将 DAC 通道 1 的数据写入 DAC_DHR8RD[7:0]位(实际存入DHR1 [11:4]位),将 DAC 通道 2 的数据写入 DAC_DHR8RD[15:8]位(实际存入 DHR2 [11:4]位)。
  • 12 位数据左对齐:用户将 DAC 通道 1 的数据写入 DAC_DHR12LD [15:4]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12LD [31:20]位(实际存入DHR2[11:0]位)。
  • 12 位数据右对齐:用户将 DAC 通道 1 的数据写入 DAC_DHR12RD [11:0]位(实际存入DHR1[11:0]位),将DAC通道2的数据写入DAC_DHR12RD [27:16]位(实际存入DHR2[11:0]位)。

DAC 可以通过软件或者硬件触发转换,通过配置 TENx 控制位来决定。如果没有选中硬件触发(寄存器 DAC_CR1 的 TENx 位置 0),存入寄存器 DAC_DHRx 的数据会在 1 个 APB1时钟周期后自动传至寄存器 DAC_DORx。如果选中硬件触发(寄存器 DAC_CR1 的 TENx 位置 1),数据传输在触发发生以后 3 个 APB1 时钟周期后完成。一旦数据从 DAC_DHRx 寄存器装入 DAC_DORx 寄存器,在经过时间 tSETTLING 之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。

当 DAC 的参考电压为 VREF+的时候,DAC 的输出电压是线性的从 0~VREF+,12 位模式下 DAC 输出电压与 VREF +以及 DORx 的计算公式:

如果使用硬件触发(TEN=1),可通过外部事件(定时计数器、外部中断线)触发 DAC 转换。由 TSELx[2:0]控制位来决定选择 8 个触发事件中的一个来触发转换。这 8 个触发事件如下表所示:

DAC 寄存器

==DACx 控制寄存器(DACx_CR)==

  • DAC_CR 的低 16 位用于控制通道 1,而高 16 位用于控制通道 2,我们这里仅列出本实验需要设置的一些位:
  • EN1 位:用于 DAC 通道 1 的使能,我们要用到 DAC 通道 1 的输出,该位必须设置为 1。
  • BOFF1 位:用于 DAC 输出缓存控制,本教程的三个 DAC 实验我们都不使用输出缓存,即该位设置为 1。
  • TEN1 位:用于 DAC 通道 1 的触发使能,我们设置该位为 0,不使用触发。写入 DHR1的值会在 1 个 APB1 周期后传送到 DOR1,然后输出到 PA4 口上。
  • TSEL1[2:0]位,用于选择 DAC 通道 1 的触发方式,这里我们没有用到外部触发,所以这几位设置为 0 即可。
  • WAVE1[1:0]位,用于控制 DAC 通道 1 的噪声/波形输出功能,我们这里没用到波形发生器,所以默认设置为 00,不使能噪声/波形输出。
  • MAMP[3:0]位,是屏蔽/幅值选择器,用来在噪声生成模式下选择屏蔽位,在三角波生成模式下选择波形的幅值。本实验没有用到波形发生器,所以设置为 0 即可。
  • DMAEN1 位,用于 DAC 通道 1 的 DMA 使能,本实验没有用到 DMA 功能,所以设置为0。

==DACx 通道 1 12 位右对齐数据保持寄存器(DACx_DHR12R1)==

DAC 输出

DAC 的 HAL 库驱动

DAC 在 HAL 库中的驱动代码在 stm32f4xx_hal_dac.cstm32f4xx_hal_dac_ex.c 文件(及其头文件)中。

==HAL_DAC_Init 函数==

HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef *hdac);
  • 形参 1 是 DAC_HandleTypeDef 结构体类型指针变量
typedef struct
{
 DAC_TypeDef *Instance; /* DAC 寄存器基地址 */
 __IO HAL_DAC_StateTypeDef State; /* DAC 工作状态 */
 HAL_LockTypeDef Lock; /* DAC 锁定对象 */
 DMA_HandleTypeDef *DMA_Handle1; /* 通道 1 的 DMA 处理句柄指针 */
 DMA_HandleTypeDef *DMA_Handle2; /* 通道 2 的 DMA 处理句柄指针 */
 __IO uint32_t ErrorCode; /* DAC 错误代码 */
} DAC_HandleTypeDef;

==HAL_DAC_ConfigChannel 函数==

HAL_StatusTypeDef HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac, DAC_ChannelConfTypeDef *sConfig, uint32_t Channel);
  • 形参 1 是DAC_HandleTypeDef 结构体类型指针变量。
  • 形参 2 是 DAC_ChannelConfTypeDef 结构体类型指针变量,其定义如下:
typedef struct
{
 uint32_t DAC_Trigger; /* DAC 触发源的选择 */
 uint32_t DAC_OutputBuffer; /* 输出缓冲*/
} DAC_ChannelConfTypeDef;

==HAL_DAC_Start 函数==

HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel);
  • 形参1 是 DAC_HandleTypeDef 结构体类型指针变量。
  • 形参 2 用于选择要启动的通道,可选择 DAC_CHANNEL_1 或者 DAC_CHANNEL_2。

==HAL_DAC_SetValue==

HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data);
  • 形参 1 是 DAC_HandleTypeDef 结构体类型指针变量。
  • 形参 2 用于选择要输出的通道,可选择 DAC_CHANNEL_1 或者 DAC_CHANNEL_2。
  • 形参 3 用于指定数据对齐方式。
  • 形参 4 设置要加载到选定数据保存寄存器中的数据。

DAC 输出配置步骤

开启DAC 和输出通道的 GPIO 时钟,配置该 IO 口的模拟输出功能

首先开启 DAC 和 GPIO 的时钟,然后配置 GPIO 为模拟模式。

IO 口模拟输出功能是通过函数 HAL_GPIO_Init 来配置的。

__HAL_RCC_DAC_CLK_ENABLE (); /* 使能 DAC1 时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启 GPIOA 时钟 */

初始化 DAC

通过 HAL_DAC_Init 函数来设置需要初始化的 DAC。该函数并没有设置任何 DAC 相关寄存器,也就是说没有对 DAC 进行任何配置,它只是 HAL 库提供用来在软件上初始化 DAC。

配置 DAC 通道并启动 DA 转换器

在 HAL 库中,通过 HAL_DAC_ConfigChannel 函数来设置配置 DAC 的通道,根据需求设置触发类型以及输出缓冲。配置好 DAC 通道之后,通过 HAL_DAC_Start 函数启动 DA 转换器。

设置_DAC 的输出值*_

通过 HAL_DAC_SetValue 函数设置 DAC 的输出值。

/**
* @brief DAC 初始化函数
* @param outx: 要初始化的通道. 1,通道 1; 2,通道 2
* @retval 无
*/
void dac_init(uint8_t outx)
{
    GPIO_InitTypeDef gpio_init_struct;
    DAC_ChannelConfTypeDef dac_ch_conf;

    __HAL_RCC_DAC_CLK_ENABLE(); /* 使能 DAC1 的时钟 */
    /* 使能 DAC OUT1/2 的 IO 口时钟(都在 PA 口,PA4/PA5) */
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /* STM32 单片机, 总是 PA4=DAC1_OUT1, PA5=DAC1_OUT2 */
    gpio_init_struct.Pin = (outx==1)? GPIO_PIN_4 : GPIO_PIN_5; 
    gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    g_dac_handle.Instance = DAC;
    HAL_DAC_Init(&g_dac_handle); /* 初始化 DAC */
    dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE; /* 不使用触发功能 */
    dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE; /* 关闭输出缓冲 */

    switch(outx)
    {
        case 1: /* DAC 通道 1 配置 */
        HAL_DAC_ConfigChannel(&g_dac_handle,&dac_ch_conf,DAC_CHANNEL_1); 
        HAL_DAC_Start(&g_dac_handle,DAC_CHANNEL_1); /* 开启 DAC 通道 1 */
        break;
        case 2: /* DAC 通道 2 配置 */
        HAL_DAC_ConfigChannel(&g_dac_handle,&dac_ch_conf,DAC_CHANNEL_2); 
        HAL_DAC_Start(&g_dac_handle,DAC_CHANNEL_2); /* 开启 DAC 通道 2 */
        break;
        default:break;
    }
}

该函数主要调用 HAL_DAC_Init 和 HAL_DAC_ConfigChannel 函数初始化 DAC,并调用HAL_DAC_Start 函数使能 DAC 通道。HAL_DAC_Init 函数会调用 HAL_DAC_MspInit 回调函数,该函数用于存放 DAC 和对应通道的 IO 时钟使能和初始化 IO 等代码。

==dac_init 函数==

/**
* @brief 设置通道 1/2 输出电压
* @param outx: 1,通道 1; 2,通道 2
* @param vol : 0~3300,代表 0~3.3V
* @retval 无
*/
void dac_set_voltage(uint8_t outx, uint16_t vol)
{
    double temp = vol;
    temp /= 1000;
    temp = temp * 4096 / 3.3;
    if (temp >= 4096)temp = 4095; /* 如果值大于等于 4096, 则取 4095 */
    if (outx == 1) /* 通道 1 */
    {
        /* 12 位右对齐数据格式设置 DAC 值 */
        HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,temp); 
    }
    else /* 通道 2 */
    {
          /* 12 位右对齐数据格式设置 DAC 值 */
            HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_2,DAC_ALIGN_12B_R,temp);
    }
}

DAC 输出三角波配置步骤

开启 DACx 和输出通道的 GPIO 时钟,配置该 IO 口的模拟输出功能

首先开启 DACx 和 GPIO 的时钟,然后配置 GPIO 为模拟模式。

__HAL_RCC_DAC_CLK_ENABLE (); /* 使能 DAC1 时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启 GPIOA 时钟 */

IO 口模拟输出功能是通过函数 HAL_GPIO_Init 来配置的。

初始化 DACx

通过 HAL_DAC_Init 函数来设置需要初始化的 DAC。该函数并没有设置任何 DAC 相关寄存器,也就是说没有对 DAC 进行任何配置,它只是 HAL 库提供用来在软件上初始化 DAC。

配置 DAC 通道并启动 DA 转换器

在 HAL 库中,通过 HAL_DAC_ConfigChannel 函数来设置配置 DAC 的通道,根据需求设置触发类型以及输出缓冲。

配置好 DAC 通道之后,通过 HAL_DAC_Start 函数启动 DA 转换器。

设置 DAC 的输出值

通过 HAL_DAC_SetValue 函数设置 DAC 的输出值。这里我们根据三角波的特性,创建了dac_triangular_wave 函数用于控制输出三角波。

DAC 驱动源码包括两个文件:dac.c 和 dac.h。

/**
* @brief 设置 DAC_OUT1 输出三角波
* @note 输出频率 ≈ 1000 / (dt * samples) Khz, 不过在 dt 较小的时候,比如小于 5us
时, 由于 delay_us 本身就不准了(调用函数,计算等都需要时间,延时很小的时候,这些
时间会影响到延时), 频率会偏小.
*
* @param maxval : 最大值(0 < maxval < 4096), (maxval + 1)必须大于等于
samples/2
* @param dt : 每个采样点的延时时间(单位: us)
* @param samples: 采样点的个数, samples 必须小于等于(maxval + 1) * 2 , 
且 maxval 不能等于 0
* @param n : 输出波形个数,0~65535
*
* @retval 无
*/
void dac_triangular_wave(uint16_t maxval, uint16_t dt, uint16_t samples,uint16_t n)
{
    uint16_t i, j;
    float incval; /* 递增量 */
    float Curval; /* 当前值 */

    if(samples > ((maxval + 1) * 2))return ; /* 数据不合法 */

    incval = (maxval + 1) / (samples / 2); /* 计算递增量 */

    for(j = 0; j < n; j++)
    {
        Curval = 0; /* 先输出 0 */
        HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,Curval);
        for(i = 0; i < (samples / 2); i++) /* 输出上升沿 */
        {
            Curval += incval; /* 新的输出值 */
            HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,
            Curval);
            delay_us(dt);
        }
        for(i = 0; i < (samples / 2); i++) /* 输出下降沿 */
        {
            Curval -= incval; /* 新的输出值 */
            HAL_DAC_SetValue(&g_dac_handle,DAC_CHANNEL_1,DAC_ALIGN_12B_R,
            Curval);
            delay_us(dt);
        }
    }
}

该函数用于设置 DAC 通道 1 输出三角波,输出频率 ≈ 1000 / (dt * samples) Khz,使用 HAL_DAC_SetValue 函数来设置 DAC 的输

出值,这样得到的三角波在示波器上可以看到。如果有跳动现象(不平稳),是正常的。

DAC 输出正弦波实验

让 DAC 输出正弦波。实验将用定时器 2 的更新事件来触发DAC 进行转换,输出正弦波,并以 DMA 的方式传输数据。

DAC 的 HAL 库驱动

下面将介绍本用到且没有介绍过的 HAL 库 API 函数。

==HAL_DAC_Start_DMA==

启动 DAC 使用 DMA 方式传输函数

HAL_StatusTypeDef HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, uint32_t Alignment);
  • 形参 DAC_HandleTypeDef 结构体类型指针变量。
  • 形参 用于选择要启动的通道,可选择 DAC_CHANNEL_1 或者 DAC_CHANNEL_2。
  • 形参3是使用 DAC 输出数据缓冲区的指针。
  • 形参 4是 DAC 输出数据的长度。
  • 形参 5 是指定 DAC 通道的数据对齐方式,有:DAC_ALIGN_8B_R(8 位右对齐)、DAC_ALIGN_12B_L(12 位左对齐)和 DAC_ALIGN_12B_R(12 位右对齐)三种方式。

==HAL_TIMEx_MasterConfigSynchronization==

  • 形参 1 是 TIM_HandleTypeDef 结构体类型指针变量。
  • 形参 2 是 TIM_MasterConfigTypeDef 结构体类型指针变量,用于配置定时器工作在主/从模式,以及触发输出(TRGO 和 TRGO2)的选择。

DAC 输出正弦波配置步骤

开启 DACx、DMA 和输出通道的 GPIO 时钟,配置该 IO 口的模拟输出功能

首先开启 DACx、DMA 和 GPIO 的时钟,然后配置 GPIO 为模拟模式。

IO 口模拟输出功能是通过函数 HAL_GPIO_Init 来配置的。

__HAL_RCC_DAC_CLK_ENABLE (); /* 使能 DAC 时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启 GPIOA 时钟 */
__HAL_RCC_DMA1_CLK_ENABLE (); /* 开启 DMA1 时钟 */

配置 DMA 并关联 DAC

  • 通过 HAL_DMA_Init 函数初始化 DMA,包括配置通道,外设地址,存储器地址,传输数据量等。
  • HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接 DMA 和外设句柄。这个宏定义为__HAL_LINKDMA。

初始化 DACx

通过 HAL_DAC_Init 函数来设置需要初始化的 DAC。该函数并没有设置任何 DAC 相关寄存器,也就是说没有对 DAC 进行任何配置,它只是 HAL 库提供用来在软件上初始化 DAC。

配置定时器控制触发 DAC

  • 通过 HAL_TIM_Base_Init 函数设置定时器溢出频率。
  • 通过 HAL_TIMEx_MasterConfigSynchronization 函数配置定时器溢出事件用做触发器。
  • 通过 HAL_TIM_Base_Start 函数启动计数。

产生正弦波序列

通过 dac_creat_sin_buf 函数产生一组正弦波序列,其本质上就是正弦波曲线上的点。

配置 DAC 通道并开启 DMA 传输

通过 HAL_DAC_ConfigChannel 函数来设置配置 DAC 的通道,根据需求设置触发类型以及输出缓冲等。

再通过 HAL_DAC_Start_DMA 函数启动 DMA 传输以及 DAC 输出。

DAC 驱动源码包括两个文件:dac.c 和 dac.h。

uint16_t g_dac_sin_buf[4096]; /* 正弦波序列数据缓冲区 */
/**
* @brief 产生正弦波函序列
* @note 需保证: maxval > samples/2
*
* @param maxval : 峰值(0 < maxval < 2048)
* @param samples: 采样点的个数
*
* @retval 无
*/
void dac_creat_sin_buf(uint16_t maxval, uint16_t samples)
{
    uint8_t i;
    float outdata = 0;

    if( maxval <= (samples/2) )return ; /* 数据不合法 */
    /* 
    * 正弦波最小正周期为 2π,约等于 2 * 3.1415926
    * 曲线上相邻的两个点在 x 轴上的间隔 = 2 * 3.1415926 / 采样点数量
    * DAC 无法输出负电压,所以需要将曲线向上偏移一个峰值的量,让整个曲线都落在正数区域
    */
    float inc = (2 * 3.1415926) / samples; /* 计算相邻两个点的 x 轴间隔 */
    for (i = 0; i < samples; i++) /* 连续打 samples 个点 */
    {
        /*
        * 正弦波函数解析式:y = Asin(wx + φ)+ b
        * 计算每个点的 y 值,将峰值放大 maxval 倍,并将曲线向上偏移 maxval 到正数区域
        */
        outdata = maxval * sin(inc * i) + maxval;

        if (outdata > 4095)
        {
            outdata = 4095; /* y 值上限限定 */
        }
        g_dac_sin_buf[i] = outdata;
    }
}

该函数用于产生正弦波序列,即正弦波曲线上的各个采样点,各个点最终控制的是DAC_DHR12R1 寄存器的值。在 DAC 输出频率固定、波形周期一定的情况下,采样点越多,实际的曲线越接近于正弦波,但是波形的频率也会越小。

==DAC DMA 初始化函数==

/**
* @brief DAC DMA 初始化函数
* @param outx: 要初始化的通道.1,通道 1; 2,通道 2
* @param cndtr: DMA 通道单次传输数据量(采样点数量)
* @retval 无
*/
void dac_init(uint8_t outx,uint16_t cndtr)
{
    GPIO_InitTypeDef gpio_init_struct;
    DAC_ChannelConfTypeDef DACCH1_Config;

    __HAL_RCC_DAC_CLK_ENABLE(); /* 使能 DAC 的时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能 DAC OUT1/2 的 IO 口时钟 */
    __HAL_RCC_DMA1_CLK_ENABLE();

    /* STM32 单片机, 总是 PA4=DAC1_OUT1, PA5=DAC1_OUT2 */
    gpio_init_struct.Pin = (outx==1)? GPIO_PIN_4 : GPIO_PIN_5; 
    gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    gpio_init_struct.Pull = GPIO_PULLUP; 
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    g_dma_dac_handle.Instance = DMA1_Stream5; /* DMA1 数据流 5 */
    g_dma_dac_handle.Init.Channel = DMA_CHANNEL_7; /* 通道 7 */
    g_dma_dac_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;/* 从存储器到外设 */
    g_dma_dac_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
    g_dma_dac_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */
    /* 外设数据长度:16 位 */
    g_dma_dac_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; 
    /* 存储器数据长度:16 位 */
    g_dma_dac_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; 
    g_dma_dac_handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
    g_dma_dac_handle.Init.Priority = DMA_PRIORITY_LOW; /* 中等优先级 */
    g_dma_dac_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 不开启 FIFO */
    HAL_DMA_Init(&g_dma_dac_handle); /* 初始化 DMA */
    /* 把 DAC 和 DMA 关联 */
    __HAL_LINKDMA(&g_dac_dma_handle,DMA_Handle1,g_dma_dac_handle); 

    g_dac_dma_handle.Instance = DAC;
    HAL_DAC_Init(&g_dac_dma_handle); /* 初始化 DAC */
    /* 使用定时器 2 的 TRGO 事件触发 DAC 转换 */
    DACCH1_Config.DAC_Trigger = DAC_TRIGGER_T2_TRGO;
    /* DAC1 输出缓冲关闭 */
    DACCH1_Config.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;

    dac_creat_sin_buf(2048,cndtr); /* 产生正弦波序列,即画点 */

     switch(outx)
     {
        case 1:
        HAL_DAC_ConfigChannel(&g_dac_dma_handle,&DACCH1_Config,
        DAC_CHANNEL_1); /* DAC 通道 1 配置 */
        HAL_DAC_Start_DMA(&g_dac_dma_handle,DAC_CHANNEL_1,
        (uint32_t *)g_dac_sin_buf,cndtr,DAC_ALIGN_12B_R); /* 开启 DAC 通道 1 */
        break;
        case 2:
        HAL_DAC_ConfigChannel(&g_dac_dma_handle,&DACCH1_Config
        DAC_CHANNEL_2); /* DAC 通道 2 配置 */
        HAL_DAC_Start_DMA(&g_dac_dma_handle,DAC_CHANNEL_2,
        (uint32_t *)g_dac_sin_buf,cndtr,DAC_ALIGN_12B_R); /* 开启 DAC 通道 2 */
        break;
        default:break;
    } 
}

该函数用于初始化 DAC 用 DMA 的方式输出正弦波,我们采用定时器 2 触发 DAC 进行转换输出。这里调用了 dac_creat_sin_buf 函数来产生正弦波序列,采样点的个数通过入口参数 cndtr 传进来。

==定时器相关的 gtim.c 文件==

/**
* @brief 通用定时器 TIMX 初始化函数
* @param arr: 自动重装值。
* @param psc: 时钟预分频数
* @retval 无
*/
void gtim_timx_int_init(uint16_t arr, uint16_t psc)
{
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    GTIM_TIMX_INT_CLK_ENABLE(); /* 使能 TIMx 时钟 */
    g_timx_handle.Instance = GTIM_TIMX_INT; /* 通用定时器 x */
    g_timx_handle.Init.Prescaler = psc; /* 预分频系数 */
    g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
    g_timx_handle.Init.Period = arr; /* 自动装载值 */
    HAL_TIM_Base_Init(&g_timx_handle);
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; /* 更新事件用于触发 */
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&g_timx_handle, &sMasterConfig);
    HAL_TIM_Base_Start(&g_timx_handle); /* 使能定时器 x */
}