第二十七章 PWM DAC实验
[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板
2.软件平台:MDK5.1
3.固件库版本:V1.4.0
[/mw_shl_code]
上一章,我们介绍了STM32F4自带DAC模块的使用,但有时候,可能两个DAC不够用,此时,我们可以通过PWM+RC滤波来实一个PWM DAC。本章我们将向大家介绍如何使用STM32F4的PWM来设计一个DAC。我们将使用按键(或USMART)控制STM32F4的PWM输出,从而控制PWM
DAC的输出电压,通过ADC1的通道5采集PWM DAC的输出电压,并在LCD模块上面显示ADC获取到的电压值以及PWM DAC的设定输出电压值等信息。本章将分为如下几个部分:
27.1 PWM DAC简介
27.2 硬件设计
27.3 软件设计
27.4 下载验证
27.1
PWM DAC简介
有时候,STM32F4自带的2路DAC可能不够用,需要多路DAC,外扩DAC成本又会高不少。此时,我们可以利用STM32F4的PWM+简单的RC滤波来实现DAC输出,从而节省成本。
在精度要求不是很高的时候,PWM+RC滤波的DAC输出方式,是一种非常廉价的解决方案。
PWM本质上其实就是是一种周期一定,而高低电平占空比可调的方波。实际电路的典型PWM波形,如图27.1.1所示:
图27.1.1 实际电路典型PWM波形
图27.1.1的PWM波形可以用分段函数表示为式①:
其中:T是单片机中计数脉冲的基本周期,也就是STM32F4定时器的计数频率的倒数。N是PWM波一个周期的计数脉冲个数,也就是STM32F4的ARR-1的值。n是PWM波一个周期中高电平的计数脉冲个数,也就是STM32F4的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位。而STM32F4的定时器大部分都是16位的(TIM2和TIM5是32位),可以很容易得到更高的分辨率,分辨率越高,速度就越慢。不过我们在本章要设计的DAC分辨率为8位。
在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。
探索者STM32F4开发板的PWM DAC输出采用二阶RC滤波,该部分原理图如图27.1.2所示:
图27.1.2 PWM DAC二阶RC滤波原理图
二阶RC滤波截止频率计算公式为:
f=1/2πRC
以上公式要求R28*C37=R29*C38=RC。根据这个公式,我们计算出图27.1.2的截止频率为:33.8Khz超过了26.14Khz,这个和我们前面提到的要求有点出入,原因是该电路我们还需要用作PWM DAC音频输出,而音频信号带宽是22.05Khz,为了让音频信号能够通过该低通滤波,同时为了标准化参数选取,所以确定了这样的参数。实测精度在0.5LSB左右。
PWM DAC的原理部分,就为大家介绍到这里。
27.2 硬件设计
本章用到的硬件资源有:
1) 指示灯DS0
2) KEY_UP和KEY1按键
3) 串口
4) TFTLCD模块
5) ADC
6) PWM DAC
本章,我们使用STM32F4的TIM9_CH2(PA3)输出PWM,经过二阶RC滤波后,转换为直流输出,实现PWM DAC。同上一章一样,我们通过ADC1的通道5(PA5)读取PWM DAC的输出,并在LCD模块上显示相关数值,通过按键和USMART控制PWM DAC的输出值。我们需要用到ADC采集DAC的输出电压,所以需要在硬件上将PWM DAC和ADC短接起来,PWM
DAC部分原理图如图27.2.1所示:
图27.2.1 PWM DAC原理图
从上图可知PWM_DAC的连接关系,但是这里有个特别需要注意的地方:因为PWM_DAC和USART2_RX共用了PA3引脚,所以在做本例程的时候,必须拔了P9上面PA3(RX)的跳线帽(左侧跳线帽),否则会影响PWM转换结果!!!
在硬件上,我们还需要用跳线帽短接多功能端口的PDC和ADC,如图27.2.2所示:
图27.2.2 硬件连接示意图
27.3 软件设计
打开本章的实验工程可以看到,我们本章并没有增加其他新的库函数文件支持。主要是使用了adc和定时器相关的库函数支持。因为我们是使用定时器产生PWM信号作为PWM DAC的输入信号经过二阶RC滤波从而产生一定幅度模拟信号,所以我们需要添加定时器相关的库函数支持。在HARDWARE分组下,我们新建了pwmdac.c源文件和对应的头文件用来初始化定时器9的PWM。接下来我们看看pwmdac.c源文件内容:
void TIM9_CH2_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef
GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9,ENABLE); //TIM9时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,
ENABLE); //使能PA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //GPIOA3
GPIO_InitStructure.GPIO_Mode
= GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed
= GPIO_Speed_100MHz; //速度100MHz
GPIO_InitStructure.GPIO_OType
= GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd
= GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOA,&GPIO_InitStructure);
//初始化PA3
GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_TIM9);
//PA3复用位定时器9 AF3
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
//向上计数模式
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM9,&TIM_TimeBaseStructure);//初始化定时器9
//初始化TIM14 Channel1 PWM模式
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
}
该函数用来初始化TIM9_CH2的PWM输出(PA3),其原理同之前介绍的PWM输出一模一样,只是换过一个定时器而已。这里就不细说了。
pwmdac.h头文件内容主要是函数申明,这里不做过多讲解。
接下来我们看看主函数内容:
int main(void)
{
u16
adcx, pwmval=0;
float
temp;
u8 t=0,key;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
LCD_Init(); //LCD初始化
Adc_Init();
//adc初始化
KEY_Init();
//按键初始化
TIM9_CH2_PWM_Init(255,0);//TIM4
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,"2014/5/6");
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)
{
if(pwmval<250)pwmval+=10;
TIM_SetCompare2(TIM9,pwmval); //输出
}else
if(key==2)
{
if(pwmval>10)pwmval-=10;
else
pwmval=0;
TIM_SetCompare2(TIM9,pwmval); //输出
}
if(t==10||key==2||key==4)
//WKUP/KEY1按下了,或者定时时间到了
{
adcx=TIM_GetCapture2(TIM9);;
LCD_ShowxNum(94,150,adcx,3,16,0); //显示DAC寄存器值
temp=(float)adcx*(3.3/256);; //得到DAC电压值
adcx=temp;
LCD_ShowxNum(94,170,temp,1,16,0); //显示电压值整数部分
temp-=adcx; temp*=1000;
LCD_ShowxNum(110,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(94,190,temp,1,16,0); //显示电压值整数部分
temp-=adcx; temp*=1000;
LCD_ShowxNum(110,190,temp,3,16,0x80); //显示电压值的小数部分
t=0;
LED0=!LED0;
}
delay_ms(10);
}
}
此部分代码,同上一章的基本一样,先对需要用到的模块进行初始化,然后显示一些提示信息,本章我们通过KEY_UP和KEY1(也就是上下键)来实现对PWM脉宽的控制,经过RC滤波,最终实现对DAC输出幅值的控制。按下KEY_UP增加,按KEY1减小。同时在LCD上面显示TIM4_CCR1寄存器的值、PWM DAC设计输出电压以及ADC采集到的实际输出电压。同时DS0闪烁,提示程序运行状况。
27.4 下载验证
在代码编译成功之后,我们通过下载代码到ALIENTEK探索者STM32F4开发板上,可以看到LCD显示如图27.4.1所示:
图27.4.1 PWM DAC实验测试图
同时伴随DS0的不停闪烁,提示程序在运行。此时,我们通过按KEY_UP按键,可以看到输出电压增大,按KEY1则变小。特别提醒:此时PA3不能接其他任何外设,如果没有拔了P9排针上面PA3的跳线帽,那么PWM DAC将有很大误差!