目录
1. 为什么需要使用PWM DAC
2. PWM DAC简介
3. 硬件设计
4. 软件设计
4.1 main.c
4.2 PWMDAC.c
4.3 PWMDAC.h
1. 为什么需要使用PWM DAC
虽然STM32F4自带DAC模块,但是在有些时候,可能出现两个DAC不够用的情况(STM32F4只有两个DAC),更有甚者,STM32的某些型号是没有板载DAC模块的;这里可能会有很多学习过51单片机的读者发问,为什么不像51一样采用专用的 D/A芯片 来实现,其实这样是可以的,但是会带来成本的增加。
STM32的所有芯片都有PWM输出功能,并且STM32F4有19个PWM输出通道,16个通用通道,资源丰富。因此,我们可以使用 PWM+RC滤波来实现一个PWM DAC 。
2. PWM DAC简介
有时候,STM32F4自带的 2 路DAC可能不够用,需要多路DAC,外扩DAC的成本又比较高。此时,我们可以利用STM32F4的 PWM+简单的RC滤波来实现DAC输出 ,从而节省成本。在精度要求不是很高的时候,PWM+RC滤波的DAC输出方式,是一种非常廉价的解决方案。
PWM称作脉冲宽度调制。其本质就是一种周期一定,而高低电平占空比可调的方波。
其中:T 是单片机中计数脉冲的基本周期,也就是STM32F4定时器的计数频率的倒数。
N是PWM波一个周期的计数脉冲个数,也就是STM32F4的ARR-1的值。
n是PWM波一个周期中高电平的计数脉冲个数,也就是STM32F4的CCRx的值。
和分别是PWM波的高低电平电压值,k为谐波次数,t为时间。
将分段函数展开为傅里叶级数得到:
从②式可以看出,式中第1个方括弧为直流分量,第2项为1次谐波分量,第3项为大于1次的高次谐波分量。如果我们希望只要直流分量,不要1次谐波分量和多次谐波分量的话,需要借助滤波器滤掉谐波。
式②中的直流分量与 n 成线性关系,并随着n从0到N,直流分量从VL到VL+VH之间变化。这正是电压输出的DAC所需要的。因此,如果能把式②中除直流分量外的谐波过滤掉,则可以得到从PWM波到电压输出DAC的转换,即:PWM波可以通过一个低通滤波器进行解调。式②中的第2项的幅度和相角与n有关,频率为1/(NT),其实就是PWM的输出频率。该频率是设计低通滤波器的依据。如果能把1次谐波很好过滤掉,则高次谐波就应该基本不存在了。
低通滤波器 是容许低于截止频率的信号通过,但高于截止频率的信号不能通过的电子滤波装置。换句话说,他将信号的频谱分离为将要通过的频率分量和将被阻塞的频率分量,且这部分被阻塞的频率分量是高频分量。而这个通过和阻塞的临界值被称为截止频率。
截止频率 从频域响应的角度讲,当保持输入信号的幅度不变,改变频率使输出信号降至最大值的0.707倍,即用频响特性来表述即为-3dB点处即为截止频率,它是用来说明频率特性指标的一个特殊频率。
通过对上面公式的分析,可以得到PWM DAC的分辨率为:
分辨率=log2(N)
假设n的最小变化为1,当N=256的时候,分辨率就是8位。STM32F4的定时器大部分都是16位的(TIM2和TIM5是32位的),可以很容易得到更高的分辨率,分辨率越高,速度就越慢。
在8位分辨条件下,我们一般要求 1 次谐波对输出电压的影响不超过1个位的精度,也就是3.3/256=0.01289V。假设VH为3.3V,VL为0V,那么一次谐波的最大值是2*3.3/π=2.1V,这变相的要求我们的RC滤波电路提供至少-20lg(2.1/0.01289)=-44dB的衰减。
STM32F4的定时器最快的计数频率是168Mhz,某些定时器只能到84M;以84M频率为例介绍,8位分辨率的时候,PWM频率为84M/256=328.125Khz。如果是1阶RC滤波,则要求截止频率为2.07Khz,如果是2阶RC滤波,则要求截止频率为26.14Khz。
二阶RC滤波截止频率计算公式:
f=1/2π RC
公式要求R20*C34=R21*C35=RC。
根据计算得截止频率为33.8Khz,超过了26.14Khz,和二阶RC滤波的截止频率有出入的原因是该电路我们还需要用作PWM DAC音频输出,音频信号带宽是22.05Khz,为了让音频信号能够通过该低通滤波,同时为了标准化参数选取,所以确定了这样的参数。实测精度在0.5LSB左右。
3. 硬件设计
本次实验,我们使用STM32F4的TIM9_CH2(PA3)输出PWM,经过二阶RC滤波后,转换为直流输出,实现PWM DAC。
注意:
本次实验需要用 ADC 采集 DAC 产生的模拟信号,也就是输出电压值,所以在硬件上需要将 PWM DAC 和 ADC 短接。
特别的:
上图中功能 PWM DAC 和 USART2_RX 共用 PA3 引脚,所以在本实验中,必须拔了 P9 上面 PA3(RX)跳线帽,否则会影响转换结果。
4. 软件设计
本次实验通过ADC通道5(PA5)读取PWM DAC输出,并在LCD模块上显示相关数值,通过按键控制PWM DAC的输出值。
4.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "usmart.h"
#include "ADC.h"
#include "KEY.h"
#include "DAC.h"
#include "PWMDAC.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
int main(void)
{
u16 adcx,pwmval=0;
float temp;
u8 t=0,key;
delay_init(168);
uart_init(115200);
LED_Init();
LCD_Init();
Adc_Init();
Key_Init();
TIM9_CH2_PWM_Init(255,0);//TIM9 PWM初始化 Fpwm=168M/256=656.25Khz
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");
LCD_ShowString(30,70,200,16,16,"PWM DAC Test");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2023/20/23");
LCD_ShowString(30,130,200,16,16,"WK_UP:+ KEY1:-");
POINT_COLOR=BLUE;
LCD_ShowString(30,150,200,16,16,"DAC VAL");
LCD_ShowString(30,170,200,16,16,"DAC VOL:0.000V");
LCD_ShowString(30,190,200,16,16,"ADC VOL:0.000V");
TIM_SetCompare2(TIM9,pwmval); //初始值
while(1)
{
t++;
key=KEY_Scan(0);
if(key==4)// 表示KEY_UP按键按下
{
if(pwmval<250) //pwmval介于0~250之间
pwmval=pwmval+10;
TIM_SetCompare2(TIM9,pwmval); //输出
}
else if(key==2) //表示KEY1按下
{
if(pwmval>10)
pwmval=pwmval-10;
else
pwmval=0;
TIM_SetCompare2(TIM9,pwmval); //输出
}
if(t==10||key==2||key==4) //有按键按下或者定时时间到了
{
adcx=TIM_GetCapture2(TIM9); //获取DAC寄存器的数值
LCD_ShowxNum(30+8*8,150,adcx,3,16,0); //显示DAC寄存器值
temp=(float)adcx*(3.3/256); //通过公式转化得到DAC电压值
adcx=temp; //电压值一定是介于0~3.3V,adcx设置的为整型,所以将temp赋值给adcx,adcx得到temp的整数部分
LCD_ShowxNum(30+8*8,170,temp,1,16,0);//显示整数部分
temp=temp-adcx;
temp=temp*1000;
LCD_ShowxNum(30+8*10,170,temp,3,16,0x80); //显示小数部分
adcx=Get_Adc_Average(ADC_Channel_5,20); //得到ADC转换值
temp=(float)adcx*(3.3/4096); //通过公式计算得到ADC电压值
adcx=temp; //同理得到电压值的整数部分
LCD_ShowxNum(30+8*8,190,temp,1,16,0); //显示电压值整数部分
temp=temp-adcx;
temp=temp*1000;
LCD_ShowxNum(30+8*10,190,temp,3,16,0x80); //显示电压值的小数部分
t=0;
LED0=!LED0;
}
delay_ms(10);
}
}
4.2 PWMDAC.c
#include "stm32f4xx.h"
#include "PWMDAC.h"
#include "Timer.h"
//arr:自动重装载值 psc:预分频值
void TIM9_CH2_PWM_Init(u16 arr,u16 psc)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9,ENABLE);// TIM9时钟使能 PA3引脚复用功能TIM9_CH2
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟,因为要用到PA3引脚
//初始化GPIOA PA3
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//因为PA3引脚复用了很多功能,所以规定每次只能使用引脚的一个功能,所以GPIO模式为复用时,需要调用引脚复用函数
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_TIM9);//将PA3引脚复用为定时器9
//初始化定时器9
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
TIM_TimeBaseInitStructure.TIM_Period=arr;
TIM_TimeBaseInitStructure.TIM_Prescaler=psc;
TIM_TimeBaseInit(TIM9,&TIM_TimeBaseInitStructure);
//初始化TIM9 Channel2 PWM模式
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出比较使能
TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性高
TIM_OCInitStructure.TIM_Pulse=0;
TIM_OC2Init(TIM9,&TIM_OCInitStructure); //初始化外设TIM9 OC2
TIM_OC2PreloadConfig(TIM9,TIM_OCPreload_Enable); //使能预装载寄存器
TIM_ARRPreloadConfig(TIM9,ENABLE); //ARPE使能
TIM_Cmd(TIM9,ENABLE);//使能TIM9
}
4.3 PWMDAC.h
#ifndef _PWMDAC__H_
#define _PWMDAC__H_
void TIM9_CH2_PWM_Init(u16 arr,u16 psc);
#endif