【正点原子STM32连载】 第三十四章 PWM DAC实验摘自【正点原子】STM32F103 战舰开发指南V1.2

news/2024/11/25 16:26:14/

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第三十四章 PWM DAC实验

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

34.1 PWM DAC技术的实现原理

DAC工作过程是将源电压按照8位、12位、16位等分辨率进行分割,其输出的电压是最小精度LSB(即1/28、1/212、1/216等)的整数倍,这就是DAC输出的电压。
我们来分析一下PWM波形的特性。PWM信号可以被分解为一个直流分量和一个占空比固定,但是平均幅度为零的方波,如图34.1.1.1所示。
在这里插入图片描述

图34.1.1.1 PWM波形的等效分解
如果使PWM信号的占空比随时间改变,那么其直流分量随之改变,信号滤除交流分量后,将会输出幅度变化的模拟信号。因此通过改变PWM 信号的占空比,可以产生不同的模拟信号。这种技术称之为PWM DAC。
PWM是周期固定,占空比可调的数字信号。在实际电路中,典型的PWM波形,如图34.1.1.2所示。
在这里插入图片描述

图34.1.1.2 实际电路典型PWM波形图
下面根据高数与信号与系统课程的知识我们作一个简单的推导,感兴趣的同学可以查阅对应的知识,如果不感兴趣,直接跳过推导过程,看最后的结论即可。
我们可以把PWM波形用分段函数①表示出来。占空比可以用②的表达式来表示。
在这里插入图片描述

公式⑩正好验证了图34.1.1.1的PWM等效原理。由此我们可知PWM的输出波形为一个与占空比有关的直流等效信号,同时伴有多个不同频率的信号的叠加。如果能把这些频率信号尽可能过滤掉,那么我们通过调整PWM的占空比即可方便实现我们需要的DAC结果,即VDAC=(VH-VL)*p。
分辨率也是DAC一个重要的参数,它可以表示DAC输出的最小精度。存在两个主要误差源影响PWM方式DAC分辨率。首先,PWM信号的占空比只能表示有限的分辨率。这是因为STM32的PWM的占空比是输出比较寄存器CCRx与TIMx_CNT进行比较的结果,而CCRx在STM32F1系列中最多能设置为16位。那么很显然地,用PWM实现的DAC分辨率就与TIMx_CNT有关,即定时器的时钟频率越高则CCRx可以设置的值越多,分辨率相应地越高。但由于定时器最高时钟是72M,这也会导致分辨率越高,DAC的速度越慢。
第二个误差源是PWM信号中不期望的谐波分量产生的峰峰值。前面PWM的频域展开公式⑩说明PWM信号需要通过滤波器才能输出一个纹波较小的直流信号,但实际上对于简单设计的滤波器对交流信号的过滤能力是有限的,所以输出信号还会带有一定的交流成份。
根据公式⑧,将k=1代入我们可以算出PWM的一次谐波幅度:

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

根据公式可知=,当DAC为12位精度时,代入Y=12可知我们设计的滤波器需要衰减74dB以上,当为8位精度时,衰减需要达到50dB。
我们知道一阶RC电路截止频率计算公式为:

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

根据以上公式就能很好地设计一个满足我们需要的滤波器参数了。为了实现低成本的RC电路,我们使用两个一阶RC电路串联起来作为滤波器。
STM32F103的定时器最快的计数频率是72Mhz,8位分辨率的时候,PWM频率为72M/256=281.25Khz。我们需要把交流信号至少衰减50dB左右。
34.2 硬件设计

  1. 例程功能
    我们将设计一个8位的DAC,使用按键(或USMART)控制STM32F1的PWM输出(占空比),从而控制PWM DAC的输出电压。为了得知PWM的输出电压,通过ADC1的通道1采集PWM DAC的输出电压,并在LCD模块上面显示ADC获取到的电压值以及PWM DAC的设定输出电压值等信息。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    2)独立按键
    KEY0 – PE4
    KEY_UP – PA0
    3)PWM输出通道
    TIM1的通道1,对应IO是PA8
  3. 原理图
    根据前面分析的原理,在硬件设计上就比较简单了。PWM可以由STM32定时器输出,我们只需要在外围增加一个滤波电路即可。我们使用的是RC滤波电路,其电路设计如下图所示:
    在这里插入图片描述

图25.2.1 PWM DAC连接原理设计
根据我们的设计,输出8位DAC时,经过一阶滤波后DAC输出的交流信号大概的衰减可以达到117dB,可见我们设计是符合要求的。
这里有个特别需要注意的地方:因为PWM_DAC和OV_VSYNC共用了PA8引脚,所以在做本实验的时候,不能插摄像头模块或OLED模块,否则可能会影响PWM转换结果。
如下图所示,本实验需要用短路帽将PDC和ADC排针连接起来。
在这里插入图片描述

图25.2.2 PCB对应PWM DAC的位置
34.3 程序设计
34.3.1 程序流程图
在这里插入图片描述

图34.3.1.1 PWM DAC实验程序流程图
34.3.2 程序解析

  1. PWM DAC驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。PWM DAC驱动源码包括两个文件:pwmdac.c和pwmdac.h。
    在pwmdac.h中,定义的宏定义如下:
    /*
  • PWMDAC 默认是使用 PA8, 对应的定时器为 TIM1_CH1, 如果你要修改成其他IO输出, 则相应
  • 的定时器及通道也要进行修改. 请根据实际情况进行修改.
 */
#define PWMDAC_GPIO_PORT             	GPIOA
#define PWMDAC_GPIO_PIN             	GPIO_PIN_8
/* PA口时钟使能 */
#define PWMDAC_GPIO_CLK_ENABLE()  	do{ __HAL_RCC_GPIOB_CLK_ENABLE();}while(0)#define PWMDAC_TIMX                 	TIM1
#define PWMDAC_TIMX_CHY            	TIM_CHANNEL_1		/* 通道Y,  1<= Y <=4 */
#define PWMDAC_TIMX_CCRX          	PWMDAC_TIMX->CCR1 	/* 通道Y的输出比较寄存器 */
/* TIM1 时钟使能 */
#define PWMDAC_TIMX_CLK_ENABLE() 	do{ __HAL_RCC_TIM1_CLK_ENABLE();}while(0) 
下面介绍pwmdac.c文件的函数,首先是pwmdac_init函数,其定义如下:
/*** @brief     PWM DAC初始化, 实际上就是初始化定时器* @note*              定时器的时钟来自APB1 / APB2, 当APB1 / APB2 分频时, 定时器频率自动翻倍*              所以, 一般情况下, 我们所有定时器的频率, 都是72Mhz 等于系统时钟频率*              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.*              Ft = 定时器工作频率, 单位: Mhz
* @param      arr: 自动重装值。* @param      psc: 时钟预分频数* @retval     无*/
void pwmdac_init(uint16_t arr, uint16_t psc)
{TIM_OC_InitTypeDef timx_oc_pwmdac = {0};PWMDAC_TIMX_CLK_ENABLE();                      /* PWM DAC 定时器时钟使能 */g_tim1_handle.Instance = TIM1;                /* 定时器1 */g_tim1_handle.Init.Prescaler = psc;          /* 定时器分频 */g_tim1_handle.Init.CounterMode = TIM_COUNTERMODE_UP;  /* 递增计数模式 */
g_tim1_handle.Init.Period = arr;             /* 自动重装载值 */
/* 使能TIMx_ARR进行缓冲 */g_tim1_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;   HAL_TIM_PWM_Init(&g_tim1_handle);            /* 初始化PWM */timx_oc_pwmdac.OCMode = TIM_OCMODE_PWM1;   /* CH1/2 PWM模式1 */timx_oc_pwmdac.Pulse = 0;                     /* 设置比较值,此值用来确定占空比 */
timx_oc_pwmdac.OCPolarity = TIM_OCPOLARITY_HIGH;  /* 输出比较极性为高 */
/* 配置TIM1通道1 */HAL_TIM_PWM_ConfigChannel(&g_tim1_handle, &timx_oc_pwmdac, PWMDAC_TIMX_CHY);HAL_TIM_PWM_Start(&g_tim1_handle, TIM_CHANNEL_1); /* 开启定时器1通道1 */
}

HAL_TIM_PWM_Init初始化TIM1并设置TIM1的ARR和PSC等参数,其次通过调用函数HAL_TIM_PWM_ConfigChannel设置定时器通道使用PWM1模式以及设置比较值等参数,最后通过调用函数HAL_TIM_PWM_Start来使能TIM1以及使能PWM通道TIM1_CH1输出。
HAL_TIM_PWM_Init会调用HAL_TIM_PWM_MspInit函数,用于存放GPIO、NVIC和时钟相关的代码。HAL_TIM_PWM_MspInit函数定义如下:

/*** @brief       定时器底层驱动,时钟使能,引脚配置* @note*               此函数会被HAL_TIM_PWM_Init()调用* @param       htim:定时器句柄* @retval      无*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{GPIO_InitTypeDef gpio_init_struct;if (htim->Instance == TIM1){__HAL_RCC_TIM1_CLK_ENABLE();                  /* 使能定时器1 */__HAL_AFIO_REMAP_TIM1_PARTIAL();             /* TIM1通道引脚部分重映射使能 */PWMDAC_GPIO_CLK_ENABLE();                     /* GPIO 时钟使能 */gpio_init_struct.Pin = PWMDAC_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_AF_PP;gpio_init_struct.Pull = GPIO_PULLUP;gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(PWMDAC_GPIO_PORT, &gpio_init_struct); }
}

下面介绍pwmdac_set_voltage函数,该函数先计算得到比较值,然后通过设置比较值来改变占空比,从而控制PWM DAC输出的电压值,其定义如下:

/*** @brief       设置PWM DAC输出电压* @param       vol : 0~3300,代表0~3.3V* @retval      无*/
void pwmdac_set_voltage(uint16_t vol)
{float temp = vol;temp /= 100;             	/* 缩小100倍, 得到实际电压值 */temp = temp * 256 / 3.3; 	/* 将电压转换成PWM占空比 */__HAL_TIM_SET_COMPARE(&g_tim1_handler, PWMDAC_TIMX_CHY, temp);/*设置新的占空比*/
}
最后在main函数里面编写如下代码: 
extern TIM_HandleTypeDef g_tim1_handler;int main(void)
{uint16_t adcx;float temp;uint8_t t = 0;uint8_t key;
uint16_t pwmval = 0;HAL_Init();                             	/* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9);	/* 设置时钟, 72Mhz */delay_init(72);                        	/* 延时初始化 */usart_init(115200);                   	/* 串口初始化为115200 */usmart_dev.init(72);                  	/* 初始化USMART */led_init();                             	/* 初始化LED */lcd_init();                             	/* 初始化LCD */key_init();                             	/* 初始化按键 */adc3_init();                            	/* 初始化ADC */pwmdac_init(256 - 1, 0);     /* PWM DAC初始化, Fpwm = 72M/256 =281.25Khz */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, "KEY_UP:+  KEY1:-", RED);lcd_show_string(30, 130, 200, 16, 16, "PWM VAL:", BLUE);lcd_show_string(30, 150, 200, 16, 16, "DAC VOL:0.000V", BLUE);lcd_show_string(30, 170, 200, 16, 16, "ADC VOL:0.000V", BLUE);while (1){t++;key = key_scan(0);		/* 按键扫描 */if (key == WKUP_PRES) 	/* PWM占空比调高 */{if (pwmval < 250) 	/* 范围限定 */{pwmval += 10;}
/* 输出新的PWM占空比 */__HAL_TIM_SET_COMPARE(&g_tim1_handler, PWMDAC_TIMX_CHY, pwmval);}else if (key == KEY1_PRES)	/* PWM占空比调低 */{if (pwmval > 10)			/* 范围限定 */{pwmval -= 10;}else{pwmval = 0;}
/* 输出新的PWM占空比 */__HAL_TIM_SET_COMPARE(&g_tim1_handler, PWMDAC_TIMX_CHY, pwmval); }if (t == 10 || key == KEY1_PRES || key == WKUP_PRES) {  /* WKUP / KEY1 按下了, 或者定时时间到了 */
/* PWM DAC 定时器输出比较值 */adcx = __HAL_TIM_GET_COMPARE(&g_tim1_handler,PWMDAC_TIMX_CHY); lcd_show_xnum(94, 130, adcx, 3, 16, 0, BLUE);	/* 显示CCRX寄存器值 */temp = (float)adcx * (3.3 / 256); 				/* 得到DAC电压值 */adcx = temp;lcd_show_xnum(94, 150, temp, 1, 16, 0, BLUE); 	/* 显示电压值整数部分 */temp -= adcx;temp *= 1000;lcd_show_xnum(110, 150, temp, 3, 16, 0X80, BLUE);/* 电压值的小数部分 */adcx = adc3_get_result_average(ADC3_CHY, 10); /* ADC3通道1的转换结果 */temp = (float)adcx * (3.3 / 4096);   /* 得到ADC电压值(adc是12bit的) */adcx = temp;lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE); /* 显示电压值整数部分 */temp -= adcx;temp *= 1000;lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);/* 电压值的小数部分 */LED0_TOGGLE(); /* LED0闪烁 */t = 0;}delay_ms(10);}
}

main函数初始化了LED和LCD用于显示效果,初始化按键和ADC用于修改ADC的占空比,辅助显示ADC。
34.4 下载验证
下载代码后,LED0不停的闪烁,提示程序已经在运行了。此时,可以通过按下KEY_UP按键增大输出电压,按下KEY1按键则电压变小。
在这里插入图片描述

图34.4.1 TFTLCD显示效果图
注意:因为PWM_DAC和OV_VSYNC共用了PA8引脚,所以在做本例程的时候,不能插摄像头模块或OLED模块,否则可能会影响PWM转换结果!!!


http://www.ppmy.cn/news/464101.html

相关文章

华为matebook x和华为matebook 哪款好

华为matebook X采用13英寸&#xff0c;2160X1440分辨率的屏幕&#xff0c;清晰度达到了2K级别&#xff0c;100 华为matebook x更多使用感受和评价&#xff1a;https://www.huawei.com/x 华为matebook x更多使用感受和评价&#xff1a;https://www.huawei.com RGB色域;华为mate…

华为MateBook 14和华为MateBook X Pro 哪个好

华为MateBook 14采用严格的14英寸显示屏&#xff0c;分辨率为2K&#xff08;2,160 x 1,440&#xff09;&#xff0c;相当于每英寸185像素&#xff08;ppi&#xff09;。屏幕亮度高达300尼特&#xff0c;对比度为1,000&#xff1a;1&#xff0c;产生100&#xff05;的sRGB色域。…

计算机行业怎么选笔记本,别再傻傻分不清楚,告诉你华为笔记本电脑的正确选购常识...

华为于2018年正式进军笔记本电脑行业&#xff0c;从此之后便一鸣惊人&#xff0c;推出了多款重量级产品。现在更是成为了笔记本领域的一个重要厂商&#xff0c;旗下笔记本电脑有多个系列&#xff0c;多个版本&#xff0c;我们在选购华为笔记本电脑时会看到很多个版本&#xff0…

华为笔记本电脑安装 Linux 操作系统之Manjaro(手把手教学)

华为笔记本电脑安装 Linux 操作系统之Manjaro&#xff08;手把手教学&#xff09; 一、准备工作 1、器材 华为 MateBook X Pro&#xff08;2018&#xff09;、U盘一个&#xff08;至少8G&#xff0c;最好先格式化&#xff09; 2、软件 Rufus刻录工具、Manjaro操作系统的 is…

联想小新air14和华为matebook 14选哪个好

联想小新air14这款笔记本搭载的英特尔最新10代的处理器&#xff0c;有i5和i7版本&#xff0c;采用mx250的独立显卡&#xff0c;512GB SSD的固态硬盘&#xff0c;内存方面采用12GB的大内存&#xff0c;可以扩展内存至20GB&#xff1b;屏幕外观颜色为灰色&#xff0c;72%NTSC高色…

Java Web程序设计的学习

属于B/S结构、服务器软件&#xff1a;Apache Tomcat、 Web 项目 目录结构&#xff1a; 1.src目录&#xff1a;存放Java源文件 2.WebRoot目录&#xff1a; 存在两个子目录&#xff1a; META-INF目录 WEB-INF目录&#xff1a;&#xff08;lib目录&#xff1a;存放驱动…

浅谈kubernetes部署:UI部署

UI部署 镜像制作 登录私服 以阿里云docker私服举例 sudodockerlogin—usernameregistry.cn-beijing.aliyuncs.com 制作UI和静态页镜像 参考&#xff1a; 《前端镜像制作》 《openresty镜像制作》 修改yaml文件 vi/opt/kubernetes/ui.yaml 修改相应image值为您的镜像目录 部…

盘点一些惊世骇俗的壁纸网站!多年珍藏干货!

前言 自建站至今&#xff0c;阅览壁纸网站无数&#xff0c;收藏列表满满一堆优质的壁纸站。 今天就来一波商业互吹&#xff0c;看看在9102年哪些壁纸站可以清新脱俗。 正文 PS&#xff1a;排名不分先后 一、极简壁纸 官网&#xff1a;bz.zzzmh.cn分类&#xff1a;精选、小姐姐、…