[TOC]

STM32F103C8T6是没有DAC的,所以这个可以先了解学习。

STM32F103 DAC 介绍

DAC数字/模拟转换模块,故名思议,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与 ADC 相反。在常见的数字信号系统中,大部分传感器信号被化成电压信号,而 ADC 把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由DAC 输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。

STM32 具有片上 DAC 外设,它的分辨率可配置为 8 位或 12 位的数字输入信号,具有两个 DAC输出通道,这两个通道互不影响,每个通道都可以使用 DMA 功能,都具有出错检测能力,可外部触发。

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

STM32F10x系列芯片ADC通道和引脚对应关系

STM32 的 DAC 模块主要特点有

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

单个 DAC 通道的框图如下图所示

整个 DAC 模块围绕框图下方的“数字至模拟转换器 x”展开,它的左边分别是参考电源的引脚:_`V_DDA、*VSSA Vref+,其中 STM32 的 DAC 规定了它的参考电压:math:*V_{ref +}* 输入范围为 2.4——3.3V`。“数字至模拟转换器 x”的输入为 DAC 的数据寄存器“DORx”的数字编码,经过它转换得的模拟信号由图中右侧的“DAC_OUTx”输出。而数据寄存器“DORx”又受“控制逻辑”支配,它可以控制数据寄存器加入一些伪噪声信号或配置产生三角波信号。图中的左上角为 DAC 的触发源,DAC 根据触发源的信号来进行 DAC 转换,其作用就相当于 DAC 转换器的开关,它可以配置的触发源为外部中断源触发、定时器触发或软件控制触发。

与 ADC 外设类似,DAC 也使用 VREF+ 引脚作为参考电压,在设计原理图的时候一般把 VSSA 接地,把 VREF+ 和 VDDA 接 3.3V,可得到 DAC 的输出电压范围为:0~3.3V

  • 图中 VDDA 和 VSSA 为 DAC 模块模拟部分的供电
  • 而 Vref+则是 DAC 模块的参考电压

DAC_OUTx就是 DAC 的输出通道了(对应 PA4 或者 PA5 引脚)。从上面图可以看出,DAC 输出是受 DORx 寄存器直接控制的,但是我们不能直接往 DORx寄存器写入数据,而是通过 DHRx 间接的传给 DORx 寄存器,实现对 DAC 输出的控制。STM32 的 DAC 支持 8/12 位模式,8 位模式的时候是固定的右对齐的,而 12 位模式。

可以设置左对齐/右对齐。单 DAC 通道 x,总共有 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_CR1 的 TENx 位置’0’),存入寄存器 DAC_DHRx的数据会在一个 APB1 时钟周期后自动传至寄存器 DAC_DORx。如果选中硬件触发(寄存器DAC_CR1 的 TENx 位置’1’),数据传输在触发发生以后 3 个 APB1 时钟周期后完成。 一旦数据从 DAC_DHRx 寄存器装入 DAC_DORx 寄存器,在经过时间 之后,输出即有效,这段时间的长短依电源电压和模拟输出负载的不同会有所变化。我们可以从 STM32F103ZET6的数据手册查到 的典型值为 3us,最大是4us。所以 DAC 的转换速度最快是 250K 左右。

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

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

DACx 输出电压=Vref*(DORx/4095)

寄存器

DAC控制寄存器 DAC_CR

DAC_CR 的低 16 位用于控制通道 1,而高 16 位用于控制通道 2,我们这里仅列出比较重要的最低 8 位的详细描述。

  • 通过查看 DAC 通道 1 使能位(EN1),来控制 DAC 通道 1 使能,我们再一次实验用的就是 DAC 通道 1,所以该位设置为 1。
  • 关闭 DAC 通道 1 输出缓存控制位(BOFF1),这里 STM32 的 DAC 输出缓存做的有些不好,如果使能的话,虽然输出能力强一点,但是输出没法到 0,这是个很严重的问题。所以我们通过不使用输出缓存,设置该位为 1。

  • DAC 通道 1 触发使能位(TEN1),该位用来控制是否使用触发,我们不使用触发,设置该位为 0。

  • DAC 通道 1 触发选择位(TSEL1[2:0]),没用到外部触发,设置这几个位为 0。
  • DAC 通道 1 噪声/三角波生成使能位(WAVE1[1:0]),这里同样没用到波形发生器,设置为 0 即可。
  • DAC 通道 1 屏蔽/幅值选择器(MAMP[3:0]),这些位仅在使用了波形发生器的时候有用,我们实验没有用到波形发生器,设置为 0 。
  • 最后是一个DAC 通道 1 DMA 使能位(DMAEN1),没有用到 DMA 功能,是设置为 0。

通道 2 的情况和通道 1 一模一样。在 DAC_CR 设置好之后,DAC 就可以正常工作了,我们仅需要再设置 DAC 的数据保持寄存器的值,就可以在 DAC 输出通道得到你想要的电压了(对应 IO 口设置为模拟输入)。我们实验用的是 DAC 通道 1 的 12 位右对齐数据保持寄存器:DAC_DHR12R1。

该寄存器用来设置 DAC 输出,通过写入 12 位数据到该寄存器,就可以在 DAC 输出通道 1(PA4)。

经常使用库函数

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);

==void ADC_Init(ADC_TypeDef_ ADCx, ADC_InitTypeDef_ ADC_InitStruct)==

typedef struct
{
  uint32_t ADC_Mode;//ADC模式:配置ADC_CR1寄存器的位[19:16]  :DUALMODE[3:0]位
  FunctionalState ADC_ScanConvMode; //是否使用扫描模式。ADC_CR1位8:SCAN位 
  FunctionalState ADC_ContinuousConvMode; //单次转换OR连续转换:ADC_CR2的位1:CONT
  uint32_t ADC_ExternalTrigConv;  //触发方式:ADC_CR2的位[19:17] :EXTSEL[2:0]                
  uint32_t ADC_DataAlign;   //对齐方式:左对齐还是右对齐:ADC_CR2的位11:ALIGN         
  uint8_t ADC_NbrOfChannel;//规则通道序列长度:ADC_SQR1的位[23:20]: L[3:0]       
}ADC_InitTypeDef;

==ADC使能函数 ADC_Cmd()==

void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);

==ADC使能软件转换函数 ADC_SoftwareStartConvCmd==

void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx,FunctionalState NewState)

ADC1的通道1(PA1)进行单次转化

==①开启PA口时钟和ADC1时钟,设置PA1为模拟输入==

GPIO_Init();           
APB2PeriphClockCmd();

==② 复位ADC1,同时设置ADC1分频因子==

RCC_ADCCLKConfig(RCC_PCLK2_Div6);      
ADC_DeInit(ADC1);

==③ 初始化ADC1参数,设置ADC1的工作模式以及规则序列的相关信息==

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

==④ 使能ADC并校准==

ADC_Cmd(ADC1, ENABLE);

==⑤ 配置规则通道参数==

  ADC_RegularChannelConfig();

==⑥开启软件转换==

ADC_SoftwareStartConvCmd(ADC1);

==⑦等待转换完成,读取ADC值==

 ADC_GetConversionValue(ADC1);

实验配置函数

开启 PA 口时钟,设置 PA4 为模拟输入

STM32F103ZET6 的 DAC 通道 1 在 PA4 上,使能 PORTA 的时钟,然后设置 PA4 为模拟输入。使能 DACx 通道之后,相应的 GPIO 引脚(PA4 或者 PA5)会自动与 DAC 的模拟输出相连,设置为输入,是为了避免额外的干扰。

==使能 GPIOA 时钟==

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE ); //使能PORTA 时钟

==设置 PA1 为模拟输入只需要设置初始化==

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入

使能 DAC1 时钟

外设要使用必须先开启相应的时钟。STM32 的 DAC 模块时钟是由 APB1提供的,所以我们调用函数 RCC_APB1PeriphClockCmd()设置 DAC 模块的时钟使能。

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE ); //使能 DAC 通道时

初始化 DAC,设置 DAC 的工作模式

该部分设置全部通过 DAC_CR 设置实现,包括:DAC 通道 1 使能、DAC 通道 1 输出缓存关闭、不使用触发、不使用波形发生器等设置。这里 DMA 初始化是通过函数 DAC_Init .

void DAC_Init(uint32_t DAC_Channel, DAC_InitTypeDef* DAC_InitStruct)

==DAC_InitTypeDef 的定义==

typedef struct
{
    uint32_t DAC_Trigger; 
    uint32_t DAC_WaveGeneration; 
    uint32_t DAC_LFSRUnmask_TriangleAmplitude; 
    uint32_t DAC_OutputBuffer; 
}DAC_InitTypeDef;
  • DAC_Trigger:用来设置是否使用触发功能,前面已经讲解过这个的含义,这里我们不是用触发功能,所以值为 DAC_Trigger_None。
  • DAC_WaveGeneratio: 用来设置是否使用波形发生,这里我们前面同样讲解过不使用。所以值为 DAC_WaveGeneration_None。
  • DAC_LFSRUnmask_TriangleAmplitude: 用来设置屏蔽/幅值选择器,这个变量只在使用波形发生器的时候才有用,这里我们设置为 0 即可,值为DAC_LFSRUnmask_Bit0。
  • DAC_OutputBuffer :是用来设置输出缓存控制位,前面讲解过,我们不使用输出缓存,所以值为 DAC_OutputBuffer_Disable。到此四个参数设置完毕。

==Code==

DAC_InitTypeDef DAC_InitType;
DAC_InitType.DAC_Trigger=DAC_Trigger_None; //不使用触发功能 TEN1=0
DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;//不使用波形发生
DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; //DAC1 输出缓存关闭
DAC_Init(DAC_Channel_1,&DAC_InitType); //初始化 DAC 通道 1

使能 DAC 转换通道

DAC_Cmd(DAC_Channel_1, ENABLE); //使能 DAC1

设置 DAC 的输出值

DAC 就可以开始工作了,我们使用 12 位右对齐数据格式,设置 DHR12R1,就可以在 DAC 输出引脚(PA4)得到不同的电压值了。

DAC_SetChannel1Data(DAC_Align_12b_R, 0);
  • 第一个设置对齐方式,可以为 12 位右对齐 DAC_Align_12b_R,12 位左对齐DAC_Align_12b_L 以及 8 位右对齐 DAC_Align_8b_R 方式。
  • 第二个就是 DAC 的输入值了,这个很好理解,初始化设置为 0。

==读出 DAC 的数值函数==

DAC_GetDataOutputValue(DAC_Channel_1);

Code

//DAC 通道 1 输出初始化
void Dac1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    DAC_InitTypeDef DAC_InitType;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE ); //①使能 PA 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE ); //②使能 DAC 时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; // 端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure); //①初始化 GPIOA
    GPIO_SetBits(GPIOA,GPIO_Pin_4) ; //PA.4 输出高
    DAC_InitType.DAC_Trigger=DAC_Trigger_None; //不使用触发功能
    DAC_InitType.DAC_WaveGeneration=DAC_WaveGeneration_None;//不使用波形发生
    DAC_InitType.DAC_LFSRUnmask_TriangleAmplitude=DAC_LFSRUnmask_Bit0;
    DAC_InitType.DAC_OutputBuffer=DAC_OutputBuffer_Disable ; //DAC1 输出缓存关
    DAC_Init(DAC_Channel_1,&DAC_InitType); //③初始化 DAC 通道 1
    DAC_Cmd(DAC_Channel_1, ENABLE); //④使能 DAC1
    DAC_SetChannel1Data(DAC_Align_12b_R, 0); //⑤12 位右对齐,设置 DAC 初始值
}
//设置通道 1 输出电压
//vol:0~3300,代表 0~3.3V
void Dac1_Set_Vol(u16 vol)
{
    float temp=vol;
    temp/=1000;
    temp=temp*4096/3.3;
    DAC_SetChannel1Data(DAC_Align_12b_R,temp);// 12 位右对齐设置 DAC 值
}