1)实验平台:alientek 阿波罗
STM32F767
开发板2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子
第二十八章 PWM DAC 实验
上一章,我们介绍了 STM32F767 自带 DAC 模块的使用,但有时候,可能两个 DAC 不
够用,此时,我们可以通过 PWM+RC 滤波来实一个 PWM DAC。本章我们将向大家介绍如
何使用 STM32F767 的 PWM 来设计一个 DAC。我们将使用按键(或 USMART)控制
STM32F767 的 PWM 输出,从而控制 PWM DAC 的输出电压,通过 ADC1 的通道 5 采集 PWM
DAC 的输出电压,并在 LCD 模块上面显示 ADC 获取到的电压值以及 PWM DAC 的设定输
出电压值等信息。本章将分为如下几个部分:
28.1 PWM DAC 简介
28.2 硬件设计
28.3 软件设计
28.4 下载验证
28.1 PWM DAC 简介
有时候,STM32F767 自带的 2 路 DAC 可能不够用,需要多路 DAC,外扩 DAC 成本又
会高不少。此时,我们可以利用 STM32F767 的 PWM+简单的 RC 滤波来实现 DAC 输出,
从而节省成本。 在精度要求不是很高的时候,PWM+RC 滤波的 DAC 输出方式,是一种非
常廉价的解决方案。
PWM 本质上其实就是是一种周期一定,而高低电平占空比可调的方波。实际
威廉希尔官方网站
的典
型 PWM 波形,如图 28.1.1 所示:
图 28.1.1 实际威廉希尔官方网站
典型 PWM 波形
图 28.1.1 的 PWM 波形可以用分段函数表示为式①:
其中:T 是
单片机中计数脉冲的基本周期,也就是 STM32F767 定时器的计数频率的倒
数。N 是 PWM 波一个周期的计数脉冲个数,也就是 STM32F767 的 ARR-1 的值。n 是 PWM
波一个周期中高电平的计数脉冲个数,也就是 STM32F767 的 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 位。而 STM32F767 的定
时器大部分都是 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 的衰减。
STM32F767 的定时器最快的计数频率是 216Mhz,某些定时器只能到 108M,所以我们
以 108M 频率为例介绍,8 位分辨率的时候,PWM 频率为 108M/256=421.875Khz。如果是 1
阶 RC 滤波,则要求截止频率 2.66Khz,如果为 2 阶 RC 滤波,则要求截止频率为 33.62Khz。
阿波罗 STM32F767 开发板的 PWM DAC 输出采用二阶 RC 滤波,该部分原理图如图
28.1.2 所示:
图 28.1.2 PWM DAC 二阶 RC 滤波原理图
二阶 RC 滤波截止频率计算公式为:
f=1/2πRC
以上公式要求 R28*C37=R29*C38=RC。根据这个公式,我们计算出图 28.1.2 的截止频
率为:33.8Khz,和 33.62Khz 非常接近,满足设计要求。
PWM DAC 的原理部分,就为大家介绍到这里。
28.2 硬件设计
本章用到的硬件资源有:
1) 指示灯 DS0
2) KEY_UP 和 KEY1 按键
3) 串口
4) LCD 模块
5) ADC
6) PWM DAC
本章,我们使用 STM32F767 的 TIM9_CH2(PA3)输出 PWM,经过二阶 RC 滤波后,转
换为直流输出,实现 PWM DAC。同上一章一样,我们通过 ADC1 的通道 5(PA5)读取 PWM
DAC 的输出,并在 LCD 模块上显示相关数值,通过按键和 USMART 控制 PWM DAC 的输
出值。我们需要用到 ADC 采集 DAC 的输出电压,所以需要在硬件上将 PWM DAC 和 ADC
短接起来,PWM DAC 部分原理图如图 28.2.1 所示:
图 28.2.1 PWM DAC 原理图
从上图可知 PWM_DAC 的连接关系,但是这里有个
特别需要注意的地方:因为
PWM_DAC 和 USART2_RX 共用了 PA3 引脚,所以在做本例程的时候,必须拔了 P8 上面
PA3(RX)的跳线帽(左侧跳线帽),否则会影响 PWM 转换结果!!!
在硬件上,我们还需要用跳线帽短接多功能端口的 PDC 和 ADC,如图 28.2.2 所示:
图 28.2.2 硬件连接示意图
28.3 软件设计
打开本章的实验工程可以看到,我们本章并没有增加其他新的库函数文件支持。主要是
使用了 adc 和定时器相关的库函数支持。因为我们是使用定时器产生 PWM 信号作为 PWM
DAC 的输入信号经过二阶 RC 滤波从而产生一定幅度模拟信号,所以我们需要添加定时器
相关的库函数支持。在 HARDWARE 分组下,我们新建了 pwmdac.c 源文件和对应的头文件
用来初始化定时器 9 的 PWM。接下来我们看看 pwmdac.c 源文件内容:
TIM_HandleTypeDef TIM9_Handler; //定时器 9 PWM 句柄
TIM_OC_InitTypeDef TIM9_CH2Handler; //定时器 9 通道 2 句柄
//PWM DAC 初始化(也就是 TIM9 通道 2 初始化)
//PWM 输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM9_CH2_PWM_Init(u16 arr,u16 psc)
{
TIM9_Handler.Instance=TIM9; //定时器 9
TIM9_Handler.Init.Prescaler=psc; //定时器分频系数
TIM9_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式
TIM9_Handler.Init.Period=arr; //自动重装载值
TIM9_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&TIM9_Handler); //初始化 PWM
TIM9_CH2Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1
TIM9_CH2Handler.Pulse=arr/2; //设置比较值用来确定占空比为 50%
TIM9_CH2Handler.OCPolarity=TIM_OCPOLARITY_HIGH; //输出比较极性为高
HAL_TIM_PWM_ConfigChannel(&TIM9_Handler,&TIM9_CH2Handler,
TIM_CHANNEL_2);//配置 TIM9 通道 2
HAL_TIM_PWM_Start(&TIM9_Handler,TIM_CHANNEL_2);//开启 PWM 通道 2
}
//定时器底层驱动,时钟使能,引脚配置
//此函数会被 HAL_TIM_PWM_Init()调用
//htim:定时器句柄
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_TIM9_CLK_ENABLE();
//使能定时器 9
__HAL_RCC_GPIOA_CLK_ENABLE();
//开启 GPIOA 时钟
GPIO_Initure.Pin=GPIO_PIN_3;
//PA3
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推完输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
GPIO_Initure.Alternate= GPIO_AF3_TIM9;
//PA3 复用为 TIM9_CH2
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
}
//设置 TIM 通道 2 的占空比
//TIM_TypeDef:定时器
//compare:比较值
void TIM_SetTIM9Compare2(u32 compare)
{
TIM9->CCR2=compare;
}
该文件有三个函数,和 PWM 输出实验几乎是一模一样的,只不过定时器由 TIM3 换为
了 TIM9,这里就不细说了。
接下来我们看看主函数内容:
//设置输出电压
//vol:0~330,代表 0~3.3V
void PWM_DAC_Set(u16 vol)
{
double temp=vol;
temp/=100;
temp=temp*256/3.3;
TIM_SetTIM9Compare2(temp);
}
int main(void)
{
u16 adcx;
float temp;
u8 t=0;
u16 pwmval=0;
u8 key;
Cache_Enable(); //打开 L1-Cache
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz
delay_init(216); //延时初始化
uart_init(115200);
//串口初始化
usmart_dev.init(108);
//初始化 USMART
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
SDRAM_Init(); //初始化 SDRAM
LCD_Init(); //LCD 初始化
MY_ADC_Init();
//初始化 ADC1
TIM9_CH2_PWM_Init(255,1); /TIM9 PWM 初始化, Fpwm=90M/256=351.562Khz.
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Apollo STM32F4/F7");
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,"2016/1/13");
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_SetTIM9Compare2(pwmval);
//初始值为 0
while(1)
{
t++;
key=KEY_Scan(0);
if(key==WKUP_PRES)
{
if(pwmval<250)pwmval+=10;
TIM_SetTIM9Compare2(pwmval);
//输出
}else if(key==KEY1_PRES)
{
if(pwmval>10)pwmval-=10;
else pwmval=0;
TIM_SetTIM9Compare2(pwmval);
//输出
}
if(t==10||key==KEY1_PRES||key==WKUP_PRES)
//WKUP/KEY1 按下了,或者定时时间到了
{
adcx=HAL_TIM_ReadCapturedValue(&TIM9_Handler,TIM_CHANNEL_2);
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); // 得到转换值
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_Toggle;
}
delay_ms(10);
}
}
此部分代码,同上一章的基本一样,先对需要用到的模块进行初始化,然后显示一些提
示信息,本章我们通过 KEY_UP 和 KEY1(也就是上下键)来实现对 PWM 脉宽的控制,经
过 RC 滤波,最终实现对 DAC 输出幅值的控制。按下 KEY_UP 增加,按 KEY1 减小。同时
在 LCD 上面显示 TIM4_CCR1 寄存器的值、PWM DAC 设计输出电压以及 ADC 采集到的实
际输出电压。同时 DS0 闪烁,提示程序运行状况。
28.4 下载验证
在代码编译成功之后,我们通过下载代码到 ALIENTEK 阿波罗 STM32 开发板上,可以
看到 LCD 显示如图 28.4.1 所示:
图 28.4.1 PWM DAC 实验测试图
同时伴随 DS0 的不停闪烁,提示程序在运行。此时,我们通过按 KEY_UP 按键,可以
看到输出电压增大,按 KEY1 则变小。
特别提醒:此时 PA3 不能接其他任何外设,如果没
有拔了 P8 排针上面 PA3 的跳线帽,那么 PWM DAC 将有很大误差!!!!