OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2624|回复: 0

《STM32F407 探索者开发指南》第三十五章 PWM DAC实验

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4895
金钱
4895
注册时间
2019-5-8
在线时间
1248 小时
发表于 2023-8-12 10:02:39 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2023-8-11 16:20 编辑

第三十五章 PWM DAC实验

1)实验平台:正点原子探索者STM32F407开发板

2) 章节摘自【正点原子】STM32F407开发指南 V1.1


4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)STM32技术交流QQ群:151941872

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

前面的章节,我们介绍了STM32F407自带DAC模块的使用,虽然STM32F407ZGT6具有内部DAC,但是也仅仅只有两条DAC通道,而STM32还有其他的很多型号是没有DAC的。通常情况下,采用专用的D/A芯片来实现,但是这样就会带来成本的增加。不过STM32所有的芯片都有PWM输出,并且PWM输出通道很多,资源丰富。因此,我们可以使用PWM+简单的RC滤波来实现DAC的输出从而节省成本。
本章我们将向大家介绍如何使用STM32F407的PWM来设计一个DAC。我们将使用按键(或USMART)控制STM32F407的PWM输出,从而控制PWMDAC的输出电压,通过ADC1的通道5采集PWM DAC的输出电压,并在LCD模块上面显示ADC获取到的电压值以及PWM DAC的设定输出电压值等信息本章分为如下几个小节:
35.1 PWM DAC技术的实现原理
35.2 硬件设计
35.3 程序设计
35.4 下载验证

35.1 PWM DAC技术的实现原理
从前面的学习我们知道DAC是根据我们的源电压,按指定的8位、12位、16位等精度对源电压进行分割,其输出按最小精度LSB(即1/28、1/212、1/216等)的倍数输出,即得到我们需要的DAC电压。最后,我们得到的DAC的电压为直流有效信号。

我们来分析一下PWM波形的特性。从电做功的角度,可以把一个PWM波等效成一个“总有效值为0”的交流波形和一个直流的电信号的叠加,直流部分的特性可根据占空比的改变而改变,这符合DAC的特性,如图35.1.1.1所示。                              
image002.png
图35.1.1.1 PWM波形的等效

上面的等效原理我们从图形上就很容易等效出来,下面我们简单介绍一下这个现象的数学原理,我们知道对于一个典型的PWM波型,它的输出波形和时间的关系如图35.1.1.2所示。
image004.png
图35.1.1.2 PWM波形的时域表示

对于上面的PWM波形,下面根据高数与信号与系统课程的知识我们作一个简单的推导,感兴趣的同学可以查阅对应的知识,如果不感兴趣,直接跳过推导过程看最后的结论即可。

我们可以把PWM波形用分段函数①表示出来。占空比可以用②的表达式来表示。
QQ截图20230811160630.png
PWM是一个周期信号,我们令周期为NT,由傅里叶变换的知识可知任意周期信号都可按频域展开,我们把分段表达式按频域展开。得到如③的表达式,④⑤⑥为展开系数:
QQ截图20230811160644.png
我们知道PWM的幅度为VH-VL,占空比为p,代入公式③~⑥求对应的积分,可以求出f(t)的展开系数分别如下:
QQ截图20230811160650.png
根据展开得到的频率参数,由此可得出PWM信号及其占空比在时域上的表达式。
QQ截图20230811160700.png
公式⑩正好验证了图35.1.1.1的PWM等效原理。由此我们可知PWM的输出波形为一个与占空比有关的直流等效信号,同时伴有多个不同频率的信号的叠加。如果能把这些频率信号尽可能过滤掉,那么我们通过调整PWM的占空比即可方便实现我们需要的DAC结果,即VDAC=(VH-VL)*p

分辨率也是DAC一个重要的参数,它可以表示DAC输出的最小精度。存在两个主要误差源影响PWM方式DAC分辨率。首先,PWM信号的占空比只能表示有限的分辨率。这是因为STM32的PWM的占空比是输出比较寄存器CCRx与TIMx_CNT进行比较的结果,而CCRx在STM32F4系列中大部分是16位的(TIM2和TIM5是32位的)。那么很显然地,用PWM实现的DAC分辨率就与TIMx_CNT有关,即定时器的时钟频率越高则CCRx可以设置的值越多,分辨率相应地越高。定时器最高时钟是168Mhz,而某些定时器只能到84Mhz,定时器的频率越高,DAC的速度越慢。

第二个误差源是PWM信号中不期望的谐波分量产生的峰峰值。前面PWM的频域展开公式⑩说明PWM信号需要通过滤波器才能输出一个纹波较小的直流信号,但实际上对于简单设计的滤波器对交流信号的过滤能力是有限的,所以输出信号还会带有一定的交流成份。
根据公式⑧,将k=1代入我们可以算出PWM的一次谐波幅度:
QQ截图20230811160711.png
当sin(πp-π)=1时滤波器需要达到衰减峰值,可知PWM占空比为50%时,一次谐波的幅度最大。为了减少这个基波的影响,我们希望滤波器在这个最大幅度下也能把基波的交流影响衰减到1/2LSB以下,即后外围滤波器至少需要满足以下条件才能避免DAC输出干扰过大:
QQ截图20230811160718.png
根据公式1112可知 QQ截图20230811161029.png ,当DAC为12位精度时,代入Y=12可知我们设计的滤波器需要衰减74dB以上,当为8位精度时,衰减需要达到50dB。
我们知道一阶RC电路截止频率计算公式为:
QQ截图20230811160727.png
把电容等效成一个电阻,对于一阶分压时电压的等效衰减的表达式可以是:
QQ截图20230811160733.png
根据以上公式就能很好地设计一个满足我们需要的滤波器参数了。为了实现低成本的RC电路,我们使用两个一阶RC电路串联起来作为滤波器。

我们以84Mhz的频率为例,8位分辨率的时候,PWM频率为84M/256=328125Hz。如果是1阶RC滤波,则要求截止频率为2.07Khz,如果是2阶RC滤波,则要求截止频率为26.14Khz。

35.2 硬件设计
1. 例程功能
我们将设计一个8位的DAC,使用按键(或USMART)控制STM32F407的PWM输出(占空比),从而控制PWM DAC的输出电压。为了得知PWM的输出电压,通过ADC1的通道1采集PWM DAC的输出电压,并在LCD模块上面显示ADC获取到的电压值以及PWM DAC的设定输出电压值等信息。

2. 硬件资源
1)LED灯
LED0 – PF9
2)独立按键
KEY0 – PE4
KEY_UP – PA0
3)PWM输出通道
我们的例程中使用TIM9的通道2,对应IO是PA3
4)设计一个对PWM输出的DAC信号进行滤波的滤波电路
5)ADC
    ADC1 通道5 – PA5

3. 原理图
根据前面分析的原理,在硬件设计上就比较简单了,PWM可以由STM32定时器输出,我们只需要在外围增加一个滤波电路即可。我们使用的是二阶RC滤波电路,所以电路设计如下:     
image037.png
图35.2.1 PWM DAC连接原理设计

根据我们的设计,输出8位DAC时,经过二阶滤波后DAC输出的交流信号大概的衰减可以达到50dB,可见我们设计是符合要求的。     
image039.png
图35.2.2 PCB对应PWM DAC的位置

我们把PWM的输出引到排针P11上,它在开发板上与ADC相邻。我们需要使用ADC功能,来测量我们的PWM输出的DAC值,所以用跳线把它们两个连接到一起即可。

35.3 程序设计
前面学习了用PWM实现DAC的原理,下面我们将结合之前学习的知识,实战一下PWM DAC的实现。

35.3.1 程序流程图
QQ截图20230811161943.png
图35.3.1.1 PWM DAC实验程序流程图

35.3.2 程序解析
1. PWMDAC驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWM DAC驱动源码包括两个文件:pwmdac.c和pwmdac.h。

为方便修改,我们在pwmdac.h中使用宏定义PWM的输出控制外设,它们列出如下:
  1. /*
  2. *PWMDAC 默认是使用 PA3, 对应的定时器为 TIM9_CH2, 如果你要修改成其他IO输出, 则相应
  3. * 的定时器及通道也要进行修改. 请根据实际情况进行修改.
  4. */
  5. #define PWMDAC_GPIO_PORT              GPIOA
  6. #define PWMDAC_GPIO_PIN              GPIO_PIN_3
  7. /* PA口时钟使能 */
  8. #define PWMDAC_GPIO_CLK_ENABLE()   do{__HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
  9. #define PWMDAC_GPIO_AFTIMX           GPIO_AF3_TIM9
  10. #define PWMDAC_TIMX                   TIM9
  11. #define PWMDAC_TIMX_CHY              TIM_CHANNEL_2       /* 通道Y,  1<= Y <= 4 */
  12. #define PWMDAC_TIMX_CCRX             PWMDAC_TIMX->CCR2  /* 通道Y的输出比较寄存器 */
  13. /* TIM9 时钟使能 */
  14. #define PWMDAC_TIMX_CLK_ENABLE()   do{ __HAL_RCC_TIM9_CLK_ENABLE(); }while(0)
复制代码
我们使用pwmdac_init来进行定时器9及其PWM输出通道的设置的输出使能,下面我们具体来看一下该函数的实现:
  1. /**
  2. * @brief      PWM DAC初始化, 实际上就是初始化定时器
  3. * @note
  4. *              定时器的时钟来自APB1 / APB2, 当APB1 / APB2 分频时, 定时器频率自动翻倍
  5. *              所以, 一般情况下, 我们所有定时器的频率, 都是84Mhz 等于系统时钟频率
  6. *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc +1)) / Ft us.
  7. *              Ft = 定时器工作频率, 单位: Mhz
  8. *
  9. * @param       arr: 自动重装值
  10. * @param       psc: 时钟预分频数
  11. * @retval      无
  12. */
  13. void pwmdac_init(uint16_t arr, uint16_t psc)
  14. {
  15.    g_tim9_handler.Instance = PWMDAC_TIMX;                    /* 定时器9 */
  16.    g_tim9_handler.Init.Prescaler = psc;                      /* 定时器分频 */
  17.    g_tim9_handler.Init.CounterMode = TIM_COUNTERMODE_UP;  /* 向上计数模式 */
  18.    g_tim9_handler.Init.Period = arr;                          /* 自动重装载值 */
  19.    HAL_TIM_PWM_Init(&g_tim9_handler);                        /* 初始化PWM */
  20.    g_tim9_ch2handler.OCMode = TIM_OCMODE_PWM1;              /* CH1/2 PWM模式1 */
  21.     /* 设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50% */
  22.    g_tim9_ch2handler.Pulse = arr / 2;
  23.    g_tim9_ch2handler.OCPolarity = TIM_OCPOLARITY_HIGH;    /* 输出比较极性为高 */
  24.    HAL_TIM_PWM_ConfigChannel(&g_tim9_handler, &g_tim9_ch2handler,
  25.                                 PWMDAC_TIMX_CHY);            /* 配置TIM9通道2 */
  26.    HAL_TIM_PWM_Start(&g_tim9_handler, PWMDAC_TIMX_CHY);   /* 开启PWM通道2 */
  27. }
复制代码
调用时HAL_TIM_PWM_Init会调用回调接口HAL_TIM_PWM_MspInit,同样地,我们把用到的DAC引脚的初始化在回调函数里完成,代码如下:
  1. /**
  2. * @brief       定时器底层驱动,时钟使能,引脚配置
  3. * @note
  4. *              此函数会被HAL_TIM_PWM_Init()调用
  5. * @param       htim:定时器句柄
  6. * @retval      无
  7. */
  8. voidHAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
  9. {
  10.    GPIO_InitTypeDef gpio_init_struct;
  11.     if (htim->Instance == PWMDAC_TIMX)
  12.     {
  13.        PWMDAC_TIMX_CLK_ENABLE();    /* 使能定时器 */
  14.        PWMDAC_GPIO_CLK_ENABLE();    /* PWM DAC GPIO 时钟使能 */
  15.        gpio_init_struct.Pin = PWMDAC_GPIO_PIN;
  16.        gpio_init_struct.Mode = GPIO_MODE_AF_PP;
  17.        gpio_init_struct.Pull = GPIO_PULLUP;
  18.        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  19.        gpio_init_struct.Alternate = PWMDAC_GPIO_AFTIMX;
  20.         /* TIMX PWM CHY 引脚模式设置 */
  21.         HAL_GPIO_Init(PWMDAC_GPIO_PORT, &gpio_init_struct);
  22.     }
  23. }
复制代码
我们需要通过重设占空比来调整DAC的输出有效值,为了方便设置电压,需要把目标电压作为我们的形参,我们把参数放大100倍设成整型而不使用浮点数来加快运算。根据需要的比例来调整输出比较值来达到重设占空比的目的,所以设计函数如下:
  1. /**
  2. * @brief       设置PWM DAC输出电压
  3. * @param       vol : 0~3300,代表0~3.3V
  4. * @retval      无
  5. */
  6. void pwmdac_set_voltage(uint16_t vol)
  7. {
  8.   float temp = vol;
  9.   temp /= 100;                /* 缩小100倍, 得到实际电压值 */
  10.   temp = temp * 256 / 3.3;  /* 将电压转换成PWM占空比 */
  11. __HAL_TIM_SET_COMPARE(&g_tim9_handler, PWMDAC_TIMX_CHY, temp);/*设置新的占空比*/
  12. }
复制代码
本部分用定时器输出PWM的知识可以参考我们定时器PWM关联的章节,我们需要关心的就是PWM的精度设计的部分的知识。

2. main.c代码
在main.c文件代码如下:
  1. extern TIM_HandleTypeDefg_tim9_handler;
  2. int main(void)
  3. {
  4.     uint16_t adcx;
  5.     float temp;
  6.     uint8_t t = 0;
  7.     uint8_t key;
  8.     uint16_t pwmval= 0;
  9.     HAL_Init();                                 /* 初始化HAL库 */
  10.    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟, 168Mhz */
  11.    delay_init(168);                            /* 延时初始化 */
  12.    usart_init(115200);                        /* 串口初始化为115200 */
  13.    usmart_dev.init(84);                       /* 初始化USMART */
  14.     led_init();                                  /* 初始化LED */
  15.     lcd_init();                                 /* 初始化LCD */
  16.     key_init();                                 /* 初始化按键 */
  17.     adc_init();                                 /* 初始化ADC */
  18.     pwmdac_init(256 - 1, 0);   /* PWM DAC初始化, Fpwm = 84Mhz/256 = 328.125Khz */
  19.     lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
  20.     lcd_show_string(30,  70, 200, 16, 16, "PWM DACTEST", RED);
  21.     lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  22.     lcd_show_string(30, 110, 200, 16, 16, "KEY_UP:+ KEY1:-", RED);
  23.     lcd_show_string(30, 130, 200, 16, 16, "PWM VAL:", BLUE);
  24.     lcd_show_string(30, 150, 200, 16, 16, "DAC VOL:0.000V", BLUE);
  25.     lcd_show_string(30, 170, 200, 16, 16, "ADC VOL:0.000V", BLUE);
  26.     __HAL_TIM_SET_COMPARE(&g_tim9_handler, PWMDAC_TIMX_CHY, pwmval);
  27.     while (1)
  28.     {
  29.         t++;
  30.         key = key_scan(0);       /* 按键扫描 */
  31.         if (key == WKUP_PRES)    /* PWM占空比调高 */
  32.         {
  33.             if (pwmval < 250)    /* 范围限定 */
  34.             {
  35.                pwmval += 10;
  36.             }
  37. /* 输出新的PWM占空比 */
  38.            __HAL_TIM_SET_COMPARE(&g_tim9_handler, PWMDAC_TIMX_CHY, pwmval);
  39.         }
  40.         else if (key == KEY1_PRES)  /* PWM占空比调低 */
  41.         {
  42.             if (pwmval > 10)         /* 范围限定 */
  43.             {
  44.                pwmval -= 10;
  45.             }
  46.             else
  47.             {
  48.                pwmval = 0;
  49.             }
  50. /* 输出新的PWM占空比 */
  51.             __HAL_TIM_SET_COMPARE(&g_tim9_handler, PWMDAC_TIMX_CHY, pwmval);
  52.         }
  53.         if (t == 10 || key == KEY1_PRES || key == WKUP_PRES)
  54.         {  /* WKUP / KEY1 按下了, 或者定时时间到了 */
  55. /* PWM DAC 定时器输出比较值 */
  56.             adcx =__HAL_TIM_GET_COMPARE(&g_tim9_handler,PWMDAC_TIMX_CHY);
  57.            lcd_show_xnum(94, 130, adcx, 3, 16, 0, BLUE);   /* 显示CCRX寄存器值 */
  58.             temp = (float)adcx * (3.3 / 256);                /* 得到DAC电压值 */
  59.             adcx = temp;
  60.            lcd_show_xnum(94, 150, temp, 1, 16, 0, BLUE);    /* 显示电压值整数部分 */
  61.             temp -= adcx;
  62.             temp *= 1000;
  63.            lcd_show_xnum(110, 150, temp, 3, 16, 0X80, BLUE);/* 电压值的小数部分 */
  64.             adcx =adc3_get_result_average(ADC3_CHY, 10); /* ADC3通道1的转换结果 */
  65.             temp = (float)adcx * (3.3 / 4096);   /* 得到ADC电压值(adc是12bit的) */
  66.             adcx = temp;
  67.            lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE);    /* 显示电压值整数部分 */
  68.             temp -= adcx;
  69.             temp *= 1000;
  70.            lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);/* 电压值的小数部分 */
  71.            LED0_TOGGLE(); /* LED0闪烁 */
  72.             t = 0;
  73.         }
  74.         delay_ms(10);
  75.     }
  76. }
复制代码
main函数初始化了LED和LCD用于显示效果,初始化按键和ADC用于修改ADC的占空比,辅助显示ADC。

35.4 下载验证
下载代码后,LED0不停的闪烁,提示程序已经在运行了。我们按下KEY_UP/KEY1调整PWM的占空比,可以看到输出电压随PWM VAL发生变化,也可以把PA3和PWM DAC的最终输出接到示波器观察滤波后的区别。     
image043.png
图35.4.1 TFTLCD显示效果图

注意:因为PWM_DAC和USART2共用了PA3引脚,所以在本例程不能使用串口2的接收功能,否则可能会影响PWM转换结果!!!

PWM 实现DAC的知识我们就讲解到这里,大家可以试试其它精度或者更换定时器输出通道测试。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 08:06

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表