STM32 DAC学习日记

文章目录

  • STM32 DAC学习日记
  • 1. DAC简介
  • 2.DAC工作原理
  • 2.1 参考电压/模拟部分电压
  • 2.3 DAC数据格式
  • 2.4 触发源
  • 2.5 DMA请求
  • 2.6 DAC输出电压
  • 3.DAC输出实验
  • 3.1 实验简要
  • 3.2 相关HAL函数介绍
  • 3.3 关键结构体介绍
  • 3.4 例程代码
  • 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 功能

    image-20241031214706101

    2.DAC工作原理

    image-20241031215508337

    2.1 参考电压/模拟部分电压

    图中 VDDA 和 VSSA 为 DAC 模块模拟部分的供电,而 VREF+则是 DAC 模块的参考电压。DAC_OUTx 就是 DAC 的两个输出通道了(对应 PA4 或者 PA5 引脚)。ADC 的这些输入/输出引脚信息如下表所示:

    image-20241031215614735

    2.3 DAC数据格式

    从图 33.1.1 可以看出,DAC 输出是受 DORx(x=1/2,下同)寄存器直接控制的,但是我们不能直接往 DORx 寄存器写入数据,而是通过 DHRx 间接的传给 DORx 寄存器,实现对 DAC输出的控制。

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

    image-20241031215746101

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

    image-20241031215817914

    ① 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 左右。

    image-20241031220039703

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

    image-20241031220424679

    image-20241031220216613

    2.5 DMA请求

    image-20241031220302608

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

    2.6 DAC输出电压

    image-20241031220334130

    3.DAC输出实验

    3.1 实验简要

    1. 功能描述:通过DAC1通道1(PA4)输出预设电压,然后由ADC1通道1 (PA1) 采集,最后显示ADC转换的数字量及换算后的电压值

    2. 关闭通道1触发(即自动):TEN1位置0

    3. 关闭输出缓冲:BOFF1位置1

    4. 使用12位右对齐模式:将数字量写入DAC_DHR12R1寄存器

    DAC_CR寄存器框图

    image-20241031220910575

    image-20241031220828384

    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);
        }
    }
    

    作者:dianfu233

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 DAC学习日记

    发表回复