38. DAC

一、DAC简介

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

二、DAC框图

DAC通道框图

  DAC 模块主要特点有:

  • 2 个 DAC 转换器:每个转换器对应 1 个输出通道
  • 8 位或者 12 位单调输出
  • 12 位模式下数据左对齐或者右对齐
  • 同步更新功能
  • 噪声波形生成
  • 三角波形生成
  • 双 DAC 双通道同时或者分别转换
  • 每个通道都有 DMA 功能

三、DAC数据格式

  STM32F407 的 DAC 支持 8/12 位模式,8 位模式的时候是固定的右对齐的,而 12 位模式又可以设置左对齐/右对齐。DAC 单通道模式下的数据寄存器对齐方式,总共有 3种情况,如下图所示:

DAC单通道模式下的数据寄存器对齐方式

  • 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 双通道(可用时),也有三种可能的方式,如下图所示:

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触发方式

  一旦数据从 DAC_DHRx 寄存器装入 DAC_DORx 寄存器,在经过时间 \(t_{SETTLING}\) 之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从《STM32F407ZGT6.pdf》数据手册查到 \(t_{SETTLING}\) 的典型值为 3us,最大是 6us,所以 DAC 的转换速度最快是 333K 左右。

  不使用硬件触发(TEN=0),其转换的时间框图如下图所示:

DAC模块转换时间框图

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

DAC触发选择

五、DMA请求

  每个 DAC 通道都有 DMA 功能,两个 DMA 通道分别用于处理两个 DAC 通道的 DMA 请求。如果 DMAENx 位置 1 时,如果发生外部触发(而不是软件触发),就会产生一个 DMA 请求,然后 DAC_DHRx 寄存器的数据被转移到 DAC_NORx 寄存器。

DMA请求

六、DAC输出电压

  当 DAC 的参考电压为 \(V_{REF+}\) 的时候,DAC 的输出电压是线性的从 0 ~ \(V_{REF+}\),N 位模式下 DAC 输出电压与 \(V_{REF+}\) 以及 DORx 的计算公式如下:

\[DACx(输出电压) = \frac{DORx}{2^{N}} * V_{REF+} \]

七、DAC常用的寄存器

7.1、DAC控制寄存器

DAC控制寄存器

  位 0 EN1 位:用于 DAC 通道 1 的使能,我们需要用到 DAC 通道 1 的输出,该位必须设置为 1。

  位 1BOFF1 位:用于 DAC 输出缓存控制,这里 STM32 的 DAC 输出缓存做的有些不好,如果使能的话,虽然输出能力强一点,但是输出没法到 0,这是个很严重的问题。所以该位我们设置为 0。

  位 2TEN1 位:用于 DAC 通道 1 的触发使能,我们设置该位为 0,不使用硬件触发。写入 DHR1的值会在 1 个 APB1 周期后传送到 DOR1,然后输出到 PA4 口上。

  TSEL1[5:3]位,用于选择 DAC 通道 1 的触发方式,这里我们没有用到外部触发,所以这几位设置为 0 即可。

  WAVE1[7:6]位,用于控制 DAC 通道 1 的噪声/波形输出功能,默认设置为 00,不使能噪声/波形输出。

  MAMP[11:8]位,用于在噪声生成模式下选择屏蔽位,在三角波生成模式下选择波形值

  位 12 DMAEN1 位,用于控制 DAC 通道 1 的 DMA 使能,本实验不使能,设置该位为 0 即可。

7.2、DAC软件触发寄存器

DAC软件触发寄存器

7.3、 DAC1通道12位右对齐数据保持寄存器

DAC1通道12位右对齐数据保持寄存器

7.4、DAC1通道12位左对齐数据保持寄存器

DAC1通道12位左对齐数据保持寄存器

7.5、DAC1通道8位右对齐数据保持寄存器

DAC1通道8位右对齐数据保持寄存器

7.6、DAC2通道12位右对齐数据保持寄存器

DAC2通道12位右对齐数据保持寄存器

7.7、DAC2通道12位左对齐数据保持寄存器

DAC2通道12位左对齐数据保持寄存器

7.8、DAC2通道8位右对齐数据保持寄存器

DAC2通道8位右对齐数据保持寄存器

7.9、双DAC12位右对齐数据保持寄存器

双DAC12位右对齐数据保持寄存器

7.10、双DAC12位左对齐数据保持寄存器

双DAC12位左对齐数据保持寄存器

7.11、双DAC8位右对齐数据保持寄存器

双DAC8位右对齐数据保持寄存器

7.12、DAC1通道数据输出寄存器

DAC1通道数据输出寄存器

7.13、DAC2通道数据输出寄存器

DAC2通道数据输出寄存器

7.14、DAC状态寄存器

DAC状态寄存器

八、DAC对应的通道引脚

DAC_OUT1 DAC_OUT2
PA4 PA5

九、DAC输出配置步骤

9.1、使能DAC对应的GPIO时钟

  使用 DAC 时钟:

#define __HAL_RCC_DAC_CLK_ENABLE()    do { \
                                      __IO uint32_t tmpreg = 0x00U; \
                                      SET_BIT(RCC->APB1ENR, RCC_APB1ENR_DACEN);\
                                      /* Delay after an RCC peripheral clock enabling */ \
                                      tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_DACEN);\
                                      UNUSED(tmpreg); \
                                      } while(0U)

  使能对应的 GPIO 时钟:

#define __HAL_RCC_GPIOA_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)

9.2、配置DAC工作参数

  DAC 的初始化函数,其声明如下:

HAL_StatusTypeDef HAL_DAC_Init(DAC_HandleTypeDef *hdac);

  形参 hdacADC_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;

  Instance指向 DAC 寄存器基地址。实际上这个基地址 HAL 库已经定义好了,可以选择范围如下:

#define DAC1                ((DAC_TypeDef *) DAC_BASE)
#define DAC                 ((DAC_TypeDef *) DAC_BASE)

  从该结构体看到该函数并没有设置任何 DAC 相关寄存器,即没有对 DAC 进行任何配置,它只是 HAL 库提供用来在软件上初始化 DAC,为后面 HAL 库操作 DAC 做好准备。从该结构体看到该函数并没有设置任何 DAC 相关寄存器,即没有对 DAC 进行任何配置,它只是 HAL 库提供用来在软件上初始化 DAC,为后面 HAL 库操作 DAC 做好准备。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

typedef enum 
{
    HAL_OK = 0x00U,             // 成功
    HAL_ERROR = 0x01U,          // 错误
    HAL_BUSY = 0x02U,           // 忙碌
    HAL_TIMEOUT = 0x03U         // 超时
} HAL_StatusTypeDef;

9.3、DAC底层初始化

  HAL 库中,提供 HAL_GPIO_Init() 函数用于配置 GPIO 功能模式,初始化 GPIO。该函数的声明如下:

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);

  该函数的第一个形参 GPIOx 用来指定端口号,可选值如下:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)

  第二个参数是 GPIO_InitTypeDef 类型的结构体变量,用来设置 GPIO 的工作模式,其定义如下:

typedef struct
{
  uint32_t Pin;         // 引脚号
  uint32_t Mode;        // 模式设置
  uint32_t Pull;        // 上下拉设置
  uint32_t Speed;       // 速度设置
  uint32_t Alternate;   // 复用功能设置
}GPIO_InitTypeDef;

  成员 Pin 表示 引脚号,范围:GPIO_PIN_0 到 GPIO_PIN_15。

#define GPIO_PIN_4                 ((uint16_t)0x0010)  /* Pin 4 selected    */
#define GPIO_PIN_5                 ((uint16_t)0x0020)  /* Pin 5 selected    */

  成员 Mode 是 GPIO 的 模式选择,有以下选择项:

#define  GPIO_MODE_ANALOG                       MODE_ANALOG             // 模式功能

  成员 Pull 用于 配置上下拉电阻,有以下选择项:

#define  GPIO_NOPULL        0x00000000U     // 无上下拉
#define  GPIO_PULLUP        0x00000001U     // 上拉
#define  GPIO_PULLDOWN      0x00000002U     // 下拉

9.4、配置DAC通道

HAL_StatusTypeDef HAL_DAC_ConfigChannel(DAC_HandleTypeDef *hdac,  const DAC_ChannelConfTypeDef *sConfig, uint32_t Channel);

  形参 hdacDAC_HandleTypeDef 结构体类型指针变量。

  形参 sConfigDAC_ChannelConfTypeDef 结构体类型指针变量,定义如下:

typedef struct
{
  uint32_t DAC_Trigger;                     // DAC触发源选择
  uint32_t DAC_OutputBuffer;                // 启用或者禁止DAC通道缓冲区
} DAC_ChannelConfTypeDef;

  成员 DAC_Trigger 用来 选择 DAC 触发源,可选值如下:

#define DAC_TRIGGER_NONE                0x00000000UL                                                     /*!< Conversion is automatic once the DAC1_DHRxxxx register has been loaded, and not by external trigger */
#define DAC_TRIGGER_T2_TRGO             (DAC_CR_TSEL1_2                                   | DAC_CR_TEN1) /*!< TIM2 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T4_TRGO             (DAC_CR_TSEL1_2                  | DAC_CR_TSEL1_0 | DAC_CR_TEN1) /*!< TIM4 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T5_TRGO             (                 DAC_CR_TSEL1_1 | DAC_CR_TSEL1_0 | DAC_CR_TEN1) /*!< TIM3 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T6_TRGO             (                                                   DAC_CR_TEN1) /*!< Conversion started by software trigger for DAC channel */
#define DAC_TRIGGER_T7_TRGO             (                 DAC_CR_TSEL1_1                  | DAC_CR_TEN1) /*!< TIM7 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_T8_TRGO             (                                  DAC_CR_TSEL1_0 | DAC_CR_TEN1) /*!< TIM8 TRGO selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_EXT_IT9             (DAC_CR_TSEL1_2 | DAC_CR_TSEL1_1                  | DAC_CR_TEN1) /*!< EXTI Line9 event selected as external conversion trigger for DAC channel */
#define DAC_TRIGGER_SOFTWARE            (DAC_CR_TSEL1                                     | DAC_CR_TEN1) /*!< Conversion started by software trigger for DAC channel */

  成员 DAC_OutputBuffer 用来 启用或者禁止 DAC 通道缓冲区,可选值如下:

#define DAC_OUTPUTBUFFER_ENABLE            0x00000000U
#define DAC_OUTPUTBUFFER_DISABLE           (DAC_CR_BOFF1)

  形参 Channel用于选择要配置的通道,可选择 DAC_CHANNEL_1 或者 DAC_CHANNEL_2。

#define DAC_CHANNEL_1                      0x00000000U
#define DAC_CHANNEL_2                      0x00000010U

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

9.5、启动DA转换器

  使能启动 DAC 转换通道函数,其声明如下:

HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel);

  形参 hdacADC_HandleTypeDef 结构体类型指针变量。

  形参 Channel用于选择要配置的通道,可选择 DAC_CHANNEL_1 或者 DAC_CHANNEL_2。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

9.6、设置输出数字量

  配置 DAC 的通道输出值。

HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data);

  形参 hdacDAC_HandleTypeDef 结构体类型指针变量。

  形参 Channel 用于 选择要输出的通道,可选择 DAC_CHANNEL_1 或者 DAC_CHANNEL_2

  形参 Alignment 用于 指定数据对齐方式

#define DAC_ALIGN_12B_R                    0x00000000U
#define DAC_ALIGN_12B_L                    0x00000004U
#define DAC_ALIGN_8B_R                     0x00000008U

  形参 Data 设置要加载到选定数据保存寄存器中的 数据

9.7、读取通道输出数字量

  获取所选 DAC 通道的输出的数字量。

uint32_t HAL_DAC_GetValue(const DAC_HandleTypeDef *hdac, uint32_t Channel);

  形参 hdacDAC_HandleTypeDef 结构体类型指针变量。

  形参 Channel 用于 选择要输出的通道,可选择 DAC_CHANNEL_1 或者 DAC_CHANNEL_2

  该函数返回读取 DAC 通道输出的数字量。

十、程序源码

  DAC 初始化函数:

DAC_HandleTypeDef g_dac_handle;

/**
 * @brief DAC初始化
 * 
 */
void DAC_Init(void)
{
    g_dac_handle.Instance = DAC;                                                // DAC寄存器基地址
    HAL_DAC_Init(&g_dac_handle);
}

  DAC 底层初始化函数:

/**
 * @brief DAC通道配置函数
 * 
 * @param hdac DAC句柄
 * @param DAC_Trigger 触发选择,可选值:[DAC_TRIGGER_NONE, DAC_TRIGGER_Tx_TRGO(x可取[2, 4, 5, 5, 6, 7, 8]), DAC_TRIGGER_EXT_IT9, DAC_TRIGGER_SOFTWARE]
 * @param DAC_OutputBuffer 输出缓冲,可选值:[DAC_OUTPUTBUFFER_ENABLE, DAC_OUTPUTBUFFER_DISABLE]
 * @param channel 通道,可选值:[DAC_CHANNEL_1, DAC_CHANNEL_2]
 */
void DAC_ConfigChannel(DAC_HandleTypeDef *hdac, uint32_t DAC_Trigger, uint32_t DAC_OutputBuffer, uint32_t channel)
{
    DAC_ChannelConfTypeDef DAC_ChannelConfStruct = {0};

    DAC_ChannelConfStruct.DAC_Trigger = DAC_Trigger;
    DAC_ChannelConfStruct.DAC_OutputBuffer = DAC_OutputBuffer;
    HAL_DAC_ConfigChannel(hdac,&DAC_ChannelConfStruct, channel);
}

  DAC 通道配置函数:

/**
 * @brief DAC通道配置函数
 * 
 * @param hdac DAC句柄
 * @param DAC_Trigger 触发选择,可选值:[DAC_TRIGGER_NONE, DAC_TRIGGER_Tx_TRGO(x可取[2, 4, 5, 5, 6, 7, 8]), DAC_TRIGGER_EXT_IT9, DAC_TRIGGER_SOFTWARE]
 * @param DAC_OutputBuffer 输出缓冲,可选值:[DAC_OUTPUTBUFFER_ENABLE, DAC_OUTPUTBUFFER_DISABLE]
 * @param channel 通道,可选值:[DAC_CHANNEL_1, DAC_CHANNEL_2]
 */
void DAC_ConfigChannel(DAC_HandleTypeDef *hdac, uint32_t trigger, uint32_t outputBuffer, uint32_t channel)
{
    DAC_ChannelConfTypeDef DAC_ChannelConfStruct = {0};

    DAC_ChannelConfStruct.DAC_Trigger = trigger;
    DAC_ChannelConfStruct.DAC_OutputBuffer = outputBuffer;
    HAL_DAC_ConfigChannel(hdac, &DAC_ChannelConfStruct, channel);
}

  DAC 设置输出电压函数

/**
 * @brief DAC设置输出电压函数
 * 
 * @param hdac DAC句柄
 * @param Channel 通道,可选值:[DAC_CHANNEL_1, DAC_CHANNEL_2]
 * @param voltage 电压值
 */
void DAC_SetVoltage(DAC_HandleTypeDef *hdac, uint32_t channel, double voltage)
{
    voltage = voltage * 4096 / 3.3;
    voltage = (voltage > 4095) ? 4095 : voltage;
    HAL_DAC_SetValue(hdac, channel, DAC_ALIGN_12B_R, voltage);
}

  DAC 输出三角波函数。

/**
 * @brief DAC输出三角波函数
 * 
 * @param hdac DAC句柄
 * @param Channel 通道,可选值:[DAC_CHANNEL_1, DAC_CHANNEL_2]
 * @param maxVoltage 最大电压值
 * @param sampleInterval 采样点间隔
 * @param samples 采样点个数
 * @param n 波形个数
 */
void DAC_OutputTriangularWave(DAC_HandleTypeDef *hdac, uint32_t channel, double maxVoltage, uint16_t sampleInterval, uint16_t samples, uint16_t n)
{
    uint16_t i = 0, j = 0;
    double increment = 0, currentValue = 0;
    int maxValue = maxVoltage * 4096 / 3.3;
  
    if((maxValue + 1) <= samples)                                               // 数据不合法
    {   
        return;
    }

    increment = (maxValue + 1) / (samples / 2);                                 // 计算递增量

    for(j = 0; j < n; j++)
    {
        HAL_DAC_SetValue(hdac, channel, DAC_ALIGN_12B_R, currentValue);
        for(i = 0; i < (samples / 2); i++)                                      // 输出上升沿
        {
            currentValue += increment;                                          // 新的输出值
            HAL_DAC_SetValue(hdac, channel, DAC_ALIGN_12B_R, currentValue);
            Delay_us(sampleInterval);
        }
        for(i = 0; i < (samples / 2); i++)                                      // 输出下降沿
        {
            currentValue -= increment;                                          // 新的输出值
            HAL_DAC_SetValue(hdac, channel, DAC_ALIGN_12B_R, currentValue);
            Delay_us(sampleInterval);
        }
    }
}

  main() 函数:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    DAC_Init();
    DAC_ConfigChannel(&g_dac_handle, DAC_TRIGGER_NONE, DAC_OUTPUTBUFFER_DISABLE, DAC_CHANNEL_1);
    DAC_SetVoltage(&g_dac_handle, DAC_CHANNEL_1, 2.5);

    HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1);

    while (1)
    {
      
    }
  
    return 0;
}
posted @ 2024-01-03 21:37  星光樱梦  阅读(21)  评论(0编辑  收藏  举报