STM32 DAC学习日记
文章目录
1. DAC简介
STM32F103 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC。DAC 可以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合使用。DAC 工作在 12 位模式时,数据可以设置成左对齐或右对齐。DAC 模块有 2 个输出通道,每个通道都有单独的转换器。在双 DAC 模式下,2 个通道可以独立地进行转换,也可以同时进行转换并同步地更新 2 个通道的输出。DAC 可以通过引脚输入参考电压 Vref+以获得更精确的转换结果。
STM32 的 DAC 模块主要特点有:
① 2 个 DAC 转换器:每个转换器对应 1 个输出通道
② 8 位或者 12 位单调输出
③ 12 位模式下数据左对齐或者右对齐
④ 同步更新功能
⑤ 噪声\三角波形生成
⑥ 双 DAC 双通道同时或者分别转换
⑦ 每个通道都有 DMA 功能
2.DAC工作原理
2.1 参考电压/模拟部分电压
图中 VDDA 和 VSSA 为 DAC 模块模拟部分的供电,而 VREF+则是 DAC 模块的参考电压。DAC_OUTx 就是 DAC 的两个输出通道了(对应 PA4 或者 PA5 引脚)。ADC 的这些输入/输出引脚信息如下表所示:
2.3 DAC数据格式
从图 33.1.1 可以看出,DAC 输出是受 DORx(x=1/2,下同)寄存器直接控制的,但是我们不能直接往 DORx 寄存器写入数据,而是通过 DHRx 间接的传给 DORx 寄存器,实现对 DAC输出的控制。
前面我们提到,STM32F103 的 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]位)。
2.4 触发源
DAC 可以通过自动触发、软件触发、外部事件触发,通过配置 TENx 控制位来决定。
如果没有选中硬件触发(寄存器 DAC_CR 的 TENx 位置 0),存入寄存器 DAC_DHRx 的数据会在1个APB1时钟周期后自动传至寄存器DAC_DORx。如果选中硬件触发(寄存器DAC_CR的 TENx 位置 1),数据传输在触发发生以后 3个 APB1时钟周期后完成。一旦数据从DAC_DHRx寄存器装入 DAC_DORx 寄存器,在经过时间 tSETTLING之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从《《STM32F103ZET6.pdf》数据手册查到tSETTLING的典型值为 3us,最大是 4us,所以 DAC 的转换速度最快是 333K 左右。
如果使用硬件触发(TENx=1),可通过外部事件(定时计数器、外部中断线)触发 DAC 转换。由 TSELx[2:0]控制位来决定选择 8 个触发事件中的一个来触发转换。触发事件如下表所示:
2.5 DMA请求
每个 DAC 通道都有 DMA 功能,两个 DMA 通道分别用于处理两个 DAC 通道的 DMA 请求。如果 DMAENx 位置 1 时,如果发生外部触发(而不是软件触发),就会产生一个 DMA 请求,然后 DAC_DHRx 寄存器的数据被转移到 DAC_NORx 寄存器。
2.6 DAC输出电压
3.DAC输出实验
3.1 实验简要
-
功能描述:通过DAC1通道1(PA4)输出预设电压,然后由ADC1通道1 (PA1) 采集,最后显示ADC转换的数字量及换算后的电压值
-
关闭通道1触发(即自动):TEN1位置0
-
关闭输出缓冲:BOFF1位置1
-
使用12位右对齐模式:将数字量写入DAC_DHR12R1寄存器
DAC_CR寄存器框图
3.2 相关HAL函数介绍
函数 | 主要寄存器 | 主要功能 |
---|---|---|
HAL_DAC_Init() | 无 | 配置DAC工作状态(HAL库内部使用) |
HAL_DAC_MspInit() | 无 | 存放NVIC、CLOCK、GPIO初始化代码 |
HAL_DAC_ConfigChannel() | CR | 配置DAC相应通道的相关参数 |
HAL_DAC_Start() | CR、SWTRIGR | 启动D/A转换 |
HAL_DAC_SetValue() | DHR12Rx | 设置输出数字量 |
HAL_DAC_GetValue() | DORx | 读取通道输出数字量 |
3.3 关键结构体介绍
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;
typedef struct
{
uint32_t DAC_Trigger; /* DAC 触发源的选择 */
uint32_t DAC_OutputBuffer; /* 启用或者禁用 DAC 通道输出缓冲区 */
} DAC_ChannelConfTypeDef;
3.4 例程代码
dac.c
#include "./BSP/DAC/dac.h"
DAC_HandleTypeDef g_dac_handle;
/* DAC初始化函数 */
void dac_init(void)
{
DAC_ChannelConfTypeDef dac_ch_conf;
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; /* DAC输出缓冲关闭 */
HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1); /* 配置DAC通道1 */
HAL_DAC_Start(&g_dac_handle, DAC_CHANNEL_1); /* 开启DAC通道1 */
}
/* DAC MSP初始化函数 */
void HAL_DAC_MspInit(DAC_HandleTypeDef *hdac)
{
if (hdac->Instance == DAC)
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_DAC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
gpio_init_struct.Pin = GPIO_PIN_4;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
}
}
/* 设置通道输出电压 */
void dac_set_voltage(uint16_t vol)
{
double temp = vol;
temp /= 1000;
temp = temp * 4096 / 3.3;
if (temp >= 4096)temp = 4095; /* 如果值大于等于4096, 则取4095 */
HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp); /* 12位右对齐数据格式设置DAC值 */
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/DAC/dac.h"
#include "./BSP/ADC/adc.h"
#include "./USMART/usmart.h"
int main(void)
{
uint16_t adcx;
float temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
usmart_init(72); /* 我这里用了USMART调试电压,也可以不用 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
adc_init(); /* 初始化ADC */
dac_init(); /* 初始化DAC1_OUT1通道 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
dac_set_voltage(2000);
while (1)
{
adcx = adc_get_result();
lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE); /* 显示ADCC采样后的原始值 */
temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
LED0_TOGGLE();
delay_ms(250);
}
}