ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H7R7开发指南 V1.1 》第三十五章 PWM DAC实验

《STM32H7R7开发指南 V1.1 》第三十五章 PWM DAC实验

原创 嵌入式/内核开发 作者:正点原子 时间:2025-09-02 11:07:21 0 删除 编辑

第三十五章 PWM DAC实验


       虽然STM32H7R7L8H6H没有内部DAC,通常情况下,可以采用专用的D/A芯片来实现,但是这样就会带来成本的增加。不过STM32所有的芯片都有PWM输出,并且PWM输出通道很多,资源丰富。因此,我们可以使用PWM+简单的RC滤波来实现DAC的输出从而节省成本。

       本章我们将向大家介绍如何使用STM32H7R7的PWM来设计一个DAC。我们将使用按键控制STM32H7R7的PWM输出,从而控制PWMDAC的输出电压,通过ADC1的通道9采集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所示。


图35.1.1.1 PWM波形的等效


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


图35.1.1.2 PWM波形的时域表示


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

       我们可以把PWM波形用分段函数①表示出来。占空比可以用②的表达式来表示。



       PWM是一个周期信号,我们令周期为NT,由傅里叶变换的知识可知任意周期信号都可按频域展开,我们把分段表达式按频域展开。得到如③的表达式,④⑤⑥为展开系数:



       我们知道PWM的幅度为V H-V L,占空比为p,代入公式③~⑥求对应的积分,可以求出f(t)的展开系数分别如下:



       根据展开得到的频率参数,由此可得出PWM信号及其占空比在时域上的表达式。



       公式⑩正好验证了图35.1.1.1的PWM等效原理。由此我们可知PWM的输出波形为一个与占空比有关的直流等效信号,同时伴有多个不同频率的信号的叠加。如果能把这些频率信号尽可能过滤掉,那么我们通过调整PWM的占空比即可方便实现我们需要的DAC结果,即V DAC=(V H-V L)*p。

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

       第二个误差源是PWM信号中不期望的谐波分量产生的峰峰值。前面PWM的频域展开公式⑩说明PWM信号需要通过滤波器才能输出一个纹波较小的直流信号,但实际上对于简单设计的滤波器对交流信号的过滤能力是有限的,所以输出信号还会带有一定的交流成分。

       根据公式⑧,将k=1代入我们可以算出PWM的一次谐波幅度:



       当sin(πp-π)=1时滤波器需要达到衰减峰值,可知PWM占空比为50%时,一次谐波的幅度最大。为了减少这个基波的影响,我们希望滤波器在这个最大幅度下也能把基波的交流影响衰减到1/2LSB以下,即后外围滤波器至少需要满足以下条件才能避免DAC输出干扰过大:



       根据公式1112可知= ,当DAC为12位精度时,代入Y=12可知我们设计的滤波器需要衰减74dB以上,当为8位精度时,衰减需要达到50dB。

       我们知道一阶RC电路截止频率计算公式为:



       把电容等效成一个电阻,对于一阶分压时电压的等效衰减的表达式可以是:



       根据以上公式就能很好地设计一个满足我们需要的滤波器参数了。为了实现低成本的RC电路,我们使用两个一阶RC电路串联起来作为滤波器。

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


        35.2 硬件设计


       1. 例程功能

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


       2. 硬件资源


       1)LED灯

              LED0 – PD14


       2)独立按键

              KEY1 – PE8

              KEY_UP – PC13


       3)PWM输出通道

              我们的例程中使用TIM15的通道2,对应IO是PA3


       4)设计一个对PWM输出的DAC信号进行滤波的滤波电路;


       5)ADC

              ADC1 通道9 – PB0


       3. 原理图

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


图35.2.1 PWM DAC连接原理设计


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


图35.2.2 PCB对应PWM DAC的位置


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


        35.3 程序设计 

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


       35.3.1 程序解析


       1.PWM DAC驱动代码

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

       为方便修改,我们在pwmdac.h中使用宏定义PWM的输出控制外设,它们列出如下:

/* PWM DAC定义 */
#define PWMDAC_TIMX                         TIM15
#define PWMDAC_TIMX_CLK_ENABLE()            
                            do{ __HAL_RCC_TIM15_CLK_ENABLE();} while (0)
#define PWMDAC_TIMX_CHY                     TIM_CHANNEL_2
#define PWMDAC_TIMX_CHY_GPIO_PORT           GPIOA
#define PWMDAC_TIMX_CHY_GPIO_PIN            GPIO_PIN_3
#define PWMDAC_TIMX_CHY_GPIO_AF             GPIO_AF4_TIM15
#define PWMDAC_TIMX_CHY_GPIO_CLK_ENABLE()   
                            do{ __HAL_RCC_GPIOA_CLK_ENABLE();} while (0)

       我们使用pwmdac_init来进行定时器15及其PWM输出通道的设置的输出使能,下面我们具体来看一下该函数的实现:

/**
 * @brief   初始化PWM DAC
 * @param   arr: 自动重装载值
 * @param   psc: 预分频系数
 * @retval  无
 */
void pwmdac_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_struct = {0};
    
    /* 初始化TIM PWM */
    g_pwmdac_timx_handle.Instance = PWMDAC_TIMX;
    g_pwmdac_timx_handle.Init.Prescaler = psc;
    g_pwmdac_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    g_pwmdac_timx_handle.Init.Period = arr;
    g_pwmdac_timx_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
g_pwmdac_timx_handle.Init.AutoReloadPreload = 
TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_PWM_Init(&g_pwmdac_timx_handle);
    
    /* 配置TIM PWM通道 */
    timx_oc_pwm_struct.OCMode = TIM_OCMODE_PWM1;
    timx_oc_pwm_struct.Pulse = (arr + 1) >> 1;
    timx_oc_pwm_struct.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&g_pwmdac_timx_handle, &timx_oc_pwm_struct, 
PWMDAC_TIMX_CHY);
    
    /* 开启TIM PWM输出 */
    HAL_TIM_PWM_Start(&g_pwmdac_timx_handle, PWMDAC_TIMX_CHY);
}

       我们需要通过重设占空比来调整DAC的输出有效值,为了方便设置电压,需要把目标电压作为我们的形参,我们把参数放大成整型而不使用浮点数来加快运算。根据需要的比例来调整输出比较值来达到重设占空比的目的,所以设计函数如下:

/**
 * @brief   设置PWMDAC输出电压
 * @param   voltage: 输出电压(单位:mV)
 * @retval  无
 */
void pwmdac_set_voltage(uint16_t voltage)
{
    uint16_t value;
    
value = (voltage * __HAL_TIM_GET_AUTORELOAD(&g_pwmdac_timx_handle)) 
/ VDD_VALUE;
    __HAL_TIM_SET_COMPARE(&g_pwmdac_timx_handle, PWMDAC_TIMX_CHY, value);
}

       本部分用定时器输出PWM的知识可以参考我们定时器PWM关联的章节,我们需要关心的就是PWM的精度设计的部分的知识。


       2. main.c代码

       在main.c文件代码如下:

int main(void)
{ 
uint8_t t = 0;
    uint8_t key;
    uint8_t pwmdac_value;
    uint16_t pwmdac_voltage = 100;
    uint32_t adc_value;
    uint32_t adc_voltage;
    
    sys_mpu_config();                   /* 配置MPU */
    sys_cache_enable();                 /* 使能Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
    delay_init(600);                    /* 初始化延时 */
    usart_init(115200);                 /* 初始化串口 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    hyperram_init();                    /* 初始化HyperRAM */
    lcd_init();                         /* 初始化LCD */
    adc_init();                         /* 初始化ADC */
    pwmdac_init(256 - 1, 1 - 1);        /* 初始化PWMDAC */
    
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "PWM DAC TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    
    lcd_show_string(30, 110, 200, 16, 16, "WK_UP:+ KEY1:-", RED);
    
    lcd_show_string(30, 130, 200, 16, 16, "PWM DAC VAL:", BLUE);
    lcd_show_string(30, 150, 200, 16, 16, "PWM DAC VOL: 0.000V", BLUE);
    lcd_show_string(30, 170, 200, 16, 16, "ADC1_CH9_VOL: 0.000V", BLUE);
    
    pwmdac_set_voltage(pwmdac_voltage); /* 设置PWMDAC输出电压 */
    
    while (1)
    {
        key = key_scan(0);
        if (key == WKUP_PRES)
        {
            /* 加大PWMDAC输出 */
            if (pwmdac_voltage < VDD_VALUE)
            {
                pwmdac_voltage += 100;
                pwmdac_set_voltage(pwmdac_voltage);
            }
        }
        else if (key == KEY1_PRES)
        {
            /* 减小PWMDAC输出 */
            if (pwmdac_voltage > 100)
            {
                pwmdac_voltage -= 100;
                pwmdac_set_voltage(pwmdac_voltage);
            }
        }
        
        if (++t == 20)
        {
            t = 0;
            
            /* 显示PWMDAC输出数字量 */
            pwmdac_value = (pwmdac_voltage * 256 - 1) / VDD_VALUE;
            lcd_show_xnum(126, 130, pwmdac_value, 3, 16, 0, BLUE);
            
            /* 显示PWMDAC输出模拟量 */
            lcd_show_xnum(134, 150, pwmdac_voltage / 1000, 1, 16, 0, BLUE);
            lcd_show_xnum(150, 150, pwmdac_voltage % 1000, 3, 16, 0x80, BLUE);
            
            /* 显示ADC采集模拟量 */
            adc_value = adc_get_result_average(ADC_ADCX_CHY, 10);
            adc_voltage = (adc_value * VREFINT_CAL_VREF) / 4095;
            lcd_show_xnum(142, 170, adc_voltage / 1000, 1, 16, 0, BLUE);
            lcd_show_xnum(158, 170, adc_voltage % 1000, 3, 16, 0x80, BLUE);
            
            LED0_TOGGLE();
        }
        
        delay_ms(10);
    }
}

       main函数初始化了LED和LCD用于显示效果,初始化按键和ADC用于修改ADC的占空比,辅助显示ADC。


        35 .4 下载验证

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


图35.4.1 现象效果图


       PWM 实现DAC的知识我们就讲解到这里,大家可以试试其它精度或者更换定时器输出通道测试。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3094748/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764723