38. DAC
一、DAC简介
STM32F407 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC。DAC 可以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合使用。DAC 工作在 12 位模式时,数据可以设置成左对齐或右对齐。DAC 模块有 2 个输出通道,每个通道都有单独的转换器。在双 DAC 模式下,2 个通道可以独立地进行转换,也可以同时进行转换并同步地更新 2 个通道的输出。DAC 可以通过引脚输入参考电压 Vref+(通 ADC 共用)以获得更精确的转换结果。
二、DAC框图
DAC 模块主要特点有:
- 2 个 DAC 转换器:每个转换器对应 1 个输出通道
- 8 位或者 12 位单调输出
- 12 位模式下数据左对齐或者右对齐
- 同步更新功能
- 噪声波形生成
- 三角波形生成
- 双 DAC 双通道同时或者分别转换
- 每个通道都有 DMA 功能
三、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 双通道(可用时),也有三种可能的方式,如下图所示:
- 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 寄存器,在经过时间 \(t_{SETTLING}\) 之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从《STM32F407ZGT6.pdf》数据手册查到 \(t_{SETTLING}\) 的典型值为 3us,最大是 6us,所以 DAC 的转换速度最快是 333K 左右。
不使用硬件触发(TEN=0),其转换的时间框图如下图所示:
如果使用硬件触发(TEN=1),可通过外部事件(定时计数器、外部中断线)触发 DAC 转换。由 TSELx[2:0] 控制位来决定选择 8 个触发事件中的一个来触发转换。这 8 个触发事件如下表所示:
五、DMA请求
每个 DAC 通道都有 DMA 功能,两个 DMA 通道分别用于处理两个 DAC 通道的 DMA 请求。如果 DMAENx 位置 1 时,如果发生外部触发(而不是软件触发),就会产生一个 DMA 请求,然后 DAC_DHRx 寄存器的数据被转移到 DAC_NORx 寄存器。
六、DAC输出电压
当 DAC 的参考电压为 \(V_{REF+}\) 的时候,DAC 的输出电压是线性的从 0 ~ \(V_{REF+}\),N 位模式下 DAC 输出电压与 \(V_{REF+}\) 以及 DORx 的计算公式如下:
七、DAC常用的寄存器
7.1、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软件触发寄存器
7.3、 DAC1通道12位右对齐数据保持寄存器
7.4、DAC1通道12位左对齐数据保持寄存器
7.5、DAC1通道8位右对齐数据保持寄存器
7.6、DAC2通道12位右对齐数据保持寄存器
7.7、DAC2通道12位左对齐数据保持寄存器
7.8、DAC2通道8位右对齐数据保持寄存器
7.9、双DAC12位右对齐数据保持寄存器
7.10、双DAC12位左对齐数据保持寄存器
7.11、双DAC8位右对齐数据保持寄存器
7.12、DAC1通道数据输出寄存器
7.13、DAC2通道数据输出寄存器
7.14、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);
形参 hdac 是 ADC_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);
形参 hdac 是 DAC_HandleTypeDef 结构体类型指针变量。
形参 sConfig 是 DAC_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);
形参 hdac 是 ADC_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);
形参 hdac 是 DAC_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);
形参 hdac 是 DAC_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;
}