单片机
返回首页

PWM与ADC实验——自定义精度的DAC输出实验

2022-05-30 来源:eefocus

PWM为什么可以作DAC来使用?

虽然大容量的 STM32F103 具有内部 DAC,但是更多的型号是没有 DAC 的,不过 STM32


所有的芯片都有 PWM 输出,因此,我们可以用 PWM+简单的 RC 滤波来实现 DAC 输出,


从而节省成本。


PWM 本质上其实就是是一种周期一定,而高低电平占空比可调的方波。实际电路的典


型 PWM 波形,如图 26.1.1 所示:

图 26.1.1 的 PWM 波形可以用分段函数表示为式①:


其中:T 是单片机中计数脉冲的基本周期,也就是 STM32 定时器的计数频率的倒数。


N 是 PWM 波一个周期的计数脉冲个数,也就是 STM32 的 ARR-1 的值。n 是 PWM 波一个周期中高电平的计数脉冲个数,也就是 STM32 的 CCRx 的值。VH 和 VL 分别是 PWM 波的高低电平电压值,k 为谐波次数,t 为时间。我们将①式展开成傅里叶级数,得到公式②:

 

从②式可以看出,式中第 1 个方括弧为直流分量,第 2 项为 1 次谐波分量,第 3 项为大


于 1 次的高次谐波分量。式②中的直流分量与 n 成线性关系,并随着 n 从 0 到 N,直流分量从 VL 到 VL+VH 之间变化。这正是电压输出的 DAC 所需要的。


因此,如果能把式②中除直流分量外的谐波过滤掉,则可以得到从 PWM 波到电压输出 DAC 的转换,即:PWM 波可以通过一个低通滤波器进行解调。式②中的第 2 项的幅度和相角与 n 有关,频率为 1/(NT),其实就是 PWM 的输出频率。该频率是设计低通滤波器的依据。如果能把 1 次谐波很好过滤掉,则高次谐波就应该基本不存在了。


通过上面的了解,我们可以得到 PWM DAC 的分辨率,计算公式如下:分辨率=log2(N)


这里假设 n 的最小变化为 1,当 N=256 的时候,分辨率就是 8 位。而 STM32 的定时器


都是 16 位的,可以很容易得到更高的分辨率,分辨率越高,速度就越慢。不过我们在本章


要设计的 DAC 分辨率为 8 位。也就是 3.3/256=0.01289V。假设 VH 为 3.3V,VL 为 0V,那么一次谐波的最大值是 2*3.3/π=2.1V,这就要求我们的 RC 滤波电路提供至少-20lg(2.1/0.01289)=-44dB 的衰减。


STM32 的定时器最快的计数频率是 72Mhz,8 为分辨率的时候,PWM 频率为72M/256=281.25Khz。如果是 1 阶 RC 滤波,则要求截止频率为 1.77Khz,如果为 2 阶 RC 滤波,则要求截止频率为 22.34Khz。


战舰 STM32 开发板的 PWM DAC 输出采用二阶 RC 滤波,该部分原理图如图 26.1.2 所


示:

二阶 RC 滤波截止频率计算公式为:


f=1/2πRC


以上公式要求 R1=R2=R,C2=C2=C。根据这个公式,我们计算出图 26.1.2 的截止频率


为:33.8Khz 超过了 22.34Khz,这个和我们前面提到的要求有点出入,原因是该电路我们还


需要用作 PWM DAC 音频输出,而音频信号带宽是 22.05Khz,为了让音频信号能够通过该


低通滤波,同时为了标准化参数选取,所以确定了这样的参数。实测精度在 0.5LSB 以内。


为什么用PWM来输出模拟量?

当我们用DAC输出的模拟量精度不够时,我们可以采用PWM输出DAC的方式,因为我们从PWM输出DAC原理得知,PWM经过傅里叶变换后分解为一个常量和一系列谐波,当我么滤掉谐波后,我们得到的是在全周期上都存在的直流电压量,即

引脚简介

image.png

 

 

代码示例

Main.c

#include 'pwm.h'  

#include 'adc.h'  

#include 'usart.h'  

#include 'delay.h'  

#include 'stm32f10x.h'  

  

int main()  

{  

    float temp = 0;  

      

    delay_init();  

    uart_init(115200);  

    PWM_InitConfig(5000,72);  // 初始化TIM1_CH1

    ADC_InitConfig();  // 初始化ADC1_CH4

      

    while(1)  

    {  

        TIM_SetCompare4(TIM1,2000);  // 设置PWM比较值

        temp = ADC_GetAverageAnalogValue();  // 获取5次读取ADC值的平均值

        printf('ADC:%f',temp);  // 使用USART1进行输出

        printf('n');  

    }  

 


Pwm.c

#include 'pwm.h'  

#include 'sys.h'  

#include 'stm32f10x.h'  

  

void PWM_InitConfig(u32 ARR,u32 PR)  

{  

    GPIO_InitTypeDef GPIO_InitStructure;  

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;  

    TIM_OCInitTypeDef TIM_OCInitStructure;  

      

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_TIM1,ENABLE);  

      

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 切记:一定要是有复用推挽输出

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;  

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  

    GPIO_Init(GPIOA,&GPIO_InitStructure);  

      

    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;  

    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;  

    TIM_TimeBaseInitStructure.TIM_Period = ARR;  

    TIM_TimeBaseInitStructure.TIM_Prescaler = PR;  

    TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);  

      

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  // 采用PWM1模式出输出

    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  // 高电平有效

    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  

    TIM_OC1Init(TIM1,&TIM_OCInitStructure);  

      

    TIM_CtrlPWMOutputs(TIM1,ENABLE);  // TIM1高级定时器的PWM主输出模式使能

      

    TIM_Cmd(TIM1,ENABLE);  // TIM1使能

}  

 


Pwm.h

#ifndef _PWM_H  

#define _PWM_H  

  

#include 'sys.h'  

  

void PWM_InitConfig(u32 ARR,u32 PR);  

  

#endif  

 


Adc.c

#include 'adc.h'  

#include 'sys.h'  

#include 'stm32f10x.h'  

  

void ADC_InitConfig()  

{  

    GPIO_InitTypeDef GPIO_InitStructure;  

    ADC_InitTypeDef ADC_InitStructure;  

      

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA,ENABLE);  

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);  

      

    ADC_DeInit(ADC1);  

      

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;  

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  

    GPIO_Init(GPIOA,&GPIO_InitStructure);  

      

    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  

    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  

    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  

    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  

    ADC_InitStructure.ADC_NbrOfChannel = 1;  

    ADC_InitStructure.ADC_ScanConvMode = DISABLE;  

    ADC_Init(ADC1,&ADC_InitStructure);  

      

    ADC_Cmd(ADC1,ENABLE);  

      

    ADC_ResetCalibration(ADC1);  // 开始ADC1校准功能复位

    while(ADC_GetResetCalibrationStatus(ADC1) == SET);  // 轮询等待结束

      

    ADC_GetCalibrationStatus(ADC1);  // 使能ADC1校准功能

    while(ADC_GetCalibrationStatus(ADC1) == SET);  // 轮询等待结束

}  

  

float ADC_GetSingleAnalogValue()  

{  

    float temp = 0;  

      

    ADC_RegularChannelConfig(ADC1,ADC_Channel_4,1,ADC_SampleTime_28Cycles5);  // 配置ADC1_CH4规则通道的采样周期与采样优先级

      

    ADC_SoftwareStartConvCmd(ADC1,ENABLE);  // 开始AD转换

    while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);  // 轮询等待结束

      

    temp = ADC_GetConversionValue(ADC1);  // 获得采样值

    temp /= 4096.0;  

    temp *= 3.3;  

      

    return temp;  

}  

  

float ADC_GetAverageAnalogValue()  

{  

    float temp = 0;  

    u8 times = 0;  

    for(;times<5;times++)  

    {  

        temp += ADC_GetSingleAnalogValue();  

    }  

    temp /= 5;  

    return temp;  

}  

 


Adc.h

#ifndef _ADC_H  

#define _ADC_H  

  

void ADC_InitConfig();  // ADC初始化

float ADC_GetSingleAnalogValue();  // 对AD转换值采样一次

float ADC_GetAverageAnalogValue();  // 对AD转换值采样多次取平均值

  

#endif  

 

代码运行结果

 

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 简洁的过零调功器电路设计与分析

  • 永不缺相启动运行的电动机控制电路

  • IGBT模块通过控制门极阻断过电流

  • 开关电源的基本组成及工作原理

  • 运算放大器IC741的基本工作原理及在电路中的实现方式

  • 基于CA3193的热电偶放大器电路

    相关电子头条文章