本文主要是介绍实验4——STM32的PWM和DAC练习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 实验要求
- 一、用STM32F103输出一路PWM波形
- (1)PWM简介
- (2)STM32F1 PWM介绍
- (3)编程实现
- (4)计算拟合周期
- (5)最后运行结果
- 二、用STM32F103的DAC功能输出一个周期2khz的正弦波(循环)
- (1)DAC简介
- (2)使用Adobe Audition生成一个2KHz的正弦波
- 三、用STM32F103的DAC功能将一段数字音频歌曲数据转换为模拟音频波形输出(循环)
实验要求
阅读学习野火开发板资料(零死角玩转stm32-中级篇、零死角玩转 STM32F103—指南者)和网上资源,熟悉 脉冲宽度调制(PWM)和数模/模数转换原理。完成以下实验:
1.用STM32F103输出一路PWM波形,建议采用定时器方法。野火和网上大多数资源采用此方法,有完整源码。用示波器观察输出波形。
2.用STM32F103的DAC功能完成以下波形输出,用示波器观察波形,并用蜂鸣器或手机耳机收听输出声音效果、感受歌曲的音质差异。
1)输出一个周期2khz的正弦波(循环)。此波形驱动作用至蜂鸣器或喇叭,会呈现一个“滴…”的单音;
2)将一段数字音频歌曲数据转换为模拟音频波形输出(循环)。
提示:首先用音频制作工具制作一段数字化的2khz正弦波wav文件、转换一首你喜欢的歌曲片段(或者自己唱录一句,如“我还是从前那个少年miya”)为wav文件。制作时须指定采样频率、量化位数和通道数,以及时间长度。MCU资源有限,建议采样8khz,量化16bit,单通道,时长仅仅5~10秒。音频wav数据可以用类似汉字字模的保存方式,直接copy到Keil代码中数组中,不必使用SD卡上的wav文件(野火开发板是读取SD卡上的wav文件)。
一、用STM32F103输出一路PWM波形
(1)PWM简介
PWM是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控 制的一种非常有效的技术,其控制简单、灵活和动态响应好等优点而成为电力电子技术最广泛应用的控制方式,其应用领域包括测量,通信, 功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习PWM具有十分重要的现实意义。 其实我们也可以这样理解,PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个 具体模拟信号的电平进行编码。PWM 信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去 的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。
(2)STM32F1 PWM介绍
STM32F1除了基本定时器TIM6和TIM7,其他定时器都可以产生PWM输出 。其中高级定时器 TIM1 和 TIM8 可以同时产生多达7 路的 PWM 输出 。而通用定时器也能同时产生多达 4路的 PWM 输出,这些在定时器中断章节中已经介绍过。PWM的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号 ,信号频率是由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR的值决定。
(3)编程实现
这次实验中我们采用的是野火指南者STM32F103开发板,在PWM波形输出中我们采用的是野火官方资料提供的"TIM——单色呼吸灯"例程
硬件相关宏定义文件bsp_breathing.h
:
#ifndef __PWM_BREATHING_H
#define __PWM_BREATHING_H#include "stm32f10x.h"/*PWM表中的点数*/
extern uint16_t POINT_NUM ;
//控制输出波形的频率
extern __IO uint16_t period_class ;#define RED_LIGHT 1
#define GREEN_LIGHT 2
#define BLUE_LIGHT 3/*要使用什么颜色的呼吸灯,可选RED_LIGHT、GREEN_LIGHT、BLUE_LIGHT*/
#define LIGHT_COLOR RED_LIGHT/********************定时器通道**************************/
#if LIGHT_COLOR == RED_LIGHT
/************红灯***************/#define BRE_TIMx TIM3#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd#define BRE_TIM_CLK RCC_APB1Periph_TIM3#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO)//红灯的引脚需要重映射#define BRE_GPIO_REMAP_FUN() GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); #define BRE_TIM_LED_PORT GPIOB#define BRE_TIM_LED_PIN GPIO_Pin_5#define BRE_TIM_OCxInit TIM_OC2Init //通道选择,1~4#define BRE_TIM_OCxPreloadConfig TIM_OC2PreloadConfig #define BRE_CCRx CCR2#define BRE_TIMx_IRQn TIM3_IRQn //中断#define BRE_TIMx_IRQHandler TIM3_IRQHandler#elif LIGHT_COLOR == GREEN_LIGHT
/************绿灯***************/#define BRE_TIMx TIM3#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd#define BRE_TIM_CLK RCC_APB1Periph_TIM3#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)//绿灯不需要重映射#define BRE_GPIO_REMAP_FUN() #define BRE_TIM_LED_PORT GPIOB#define BRE_TIM_LED_PIN GPIO_Pin_0#define BRE_TIM_OCxInit TIM_OC3Init //通道选择,1~4#define BRE_TIM_OCxPreloadConfig TIM_OC3PreloadConfig #define BRE_CCRx CCR3#define BRE_TIMx_IRQn TIM3_IRQn //中断#define BRE_TIMx_IRQHandler TIM3_IRQHandler#elif LIGHT_COLOR == BLUE_LIGHT
/************蓝灯***************/#define BRE_TIMx TIM3#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd#define BRE_TIM_CLK RCC_APB1Periph_TIM3#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)//蓝灯不需要重映射#define BRE_GPIO_REMAP_FUN() #define BRE_TIM_LED_PORT GPIOB#define BRE_TIM_LED_PIN GPIO_Pin_1#define BRE_TIM_OCxInit TIM_OC4Init //通道选择,1~4#define BRE_TIM_OCxPreloadConfig TIM_OC4PreloadConfig #define BRE_CCRx CCR4#define BRE_TIMx_IRQn TIM3_IRQn //中断#define BRE_TIMx_IRQHandler TIM3_IRQHandler#endifvoid TIMx_Breathing_Init (void);#endif /* __PWM_BREATHING_H */
在此宏定义文件中定义了三组LED的宏,我们可以通过修改代码中的 #define LIGHT_COLOR RED_LIGHT
语句,来切换使用红、绿、蓝三种颜色的呼吸灯。每组宏定义中都定义了定时器编号、定时器时钟使能库函数、引脚是否重映射操作、GPIO 端口和引脚号、通道对应的比较寄存器名以及中断通道和中断服务函数名等。
GPIO初始化在bsp_breathing.c
文件中:
static void TIMx_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;/* clock enable */RCC_APB2PeriphClockCmd(BRE_TIM_GPIO_CLK, ENABLE); BRE_TIM_GPIO_APBxClock_FUN ( BRE_TIM_GPIO_CLK, ENABLE );BRE_GPIO_REMAP_FUN(); /* 配置呼吸灯所用到的引脚 */GPIO_InitStructure.GPIO_Pin = BRE_TIM_LED_PIN ;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init( BRE_TIM_LED_PORT, &GPIO_InitStructure );
}
PWM表在bsp_breathing.c
文件中:
uint16_t indexWave[] = {
1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4,
4, 5, 5, 6, 7, 8, 9, 10, 11, 13,
15, 17, 19, 22, 25, 28, 32, 36,
41, 47, 53, 61, 69, 79, 89, 102,
116, 131, 149, 170, 193, 219, 250,
284, 323, 367, 417, 474, 539, 613,
697, 792, 901, 1024, 1024, 901, 792,
697, 613, 539, 474, 417, 367, 323,
284, 250, 219, 193, 170, 149, 131,
116, 102, 89, 79, 69, 61, 53, 47, 41,
36, 32, 28, 25, 22, 19, 17, 15, 13,
11, 10, 9, 8, 7, 6, 5, 5, 4, 4, 3, 3,
2, 2, 2, 2, 1, 1, 1, 1};
PWM表是一个周期内比较寄存器CCRx的变化值,即脉冲宽度的变化值
定时器 PWM配置也在bsp_breathing.c
文件中:
static void NVIC_Config_PWM(void)
{NVIC_InitTypeDef NVIC_InitStructure;/* Configure one bit for preemption priority */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);/* 配置TIM3_IRQ中断为中断源 */NVIC_InitStructure.NVIC_IRQChannel = BRE_TIMx_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}/*** @brief 配置TIM输出的PWM信号的模式,如周期、极性* @param 无* @retval 无*/static void TIMx_Mode_Config(void)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure; /* 设置TIM3CLK 时钟 */BRE_TIM_APBxClock_FUN ( BRE_TIM_CLK, ENABLE ); /* 基本定时器配置 ,配合PWM表点数、中断服务函数中的period_cnt循环次数设置*/ ************************************************************//* 基本定时器配置 */ TIM_TimeBaseStructure.TIM_Period = (1024-1);; //当定时器从0计数到 TIM_Period+1 ,为一个定时周期TIM_TimeBaseStructure.TIM_Prescaler = (200-1); //设置预分频TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ; //设置时钟分频系数:不分频(这里用不到)TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式TIM_TimeBaseInit(BRE_TIMx, &TIM_TimeBaseStructure);/* PWM模式配置 */TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能输出TIM_OCInitStructure.TIM_Pulse = 0; //设置初始PWM脉冲宽度为0 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //当定时器计数值小于CCR1_Val时为低电平BRE_TIM_OCxInit ( BRE_TIMx, &TIM_OCInitStructure ); //使能通道BRE_TIM_OCxPreloadConfig ( BRE_TIMx, TIM_OCPreload_Enable ); //使能预装载 TIM_ARRPreloadConfig(BRE_TIMx, ENABLE); //使能TIM重载寄存器ARR/* TIM3 enable counter */TIM_Cmd(BRE_TIMx, ENABLE); //使能定时器 TIM_ITConfig(BRE_TIMx, TIM_IT_Update, ENABLE); //使能update中断NVIC_Config_PWM(); //配置中断优先级 }
(4)计算拟合周期
周期计算公式如下:
STM32系统时钟默认频率和周期:
f_pclk = 72000000
t_pclk = 1/f_pclk
定时器 update事件周期,即定时器中断周期:
t_timer = t_pclk * TIMER_TIM_Prescaler * TIMER_TIM_Period
每个 PWM点的时间:
T_Point = t_timer * PERIOD_CLASS
最终,遍历 PWM表的周期,即拟合曲线的周期:
T_PWM = T_Point * POINT_NUM
本实验中的周期计算
PWM点数: POINT_NUM = 110
周期倍数: PERIOD_CLASS = 10
定时器定时周期: TIMER_TIM_Period = 1024
定时器分频: TIMER_TIM_Prescaler = 200
代入公式,计算得 T_PWM = 3.128 秒
(5)最后运行结果
二、用STM32F103的DAC功能输出一个周期2khz的正弦波(循环)
(1)DAC简介
DAC 为数字/模拟转换模块,故名思议,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与 ADC相反。在常见的数字信号系统中,大部分传感器信号被化成电压信号,而 ADC把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由 DAC输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。 STM32 具有片上DAC 外设,它的分辨率可配置为 8 位或 12 位的数字输入信号,具有两个 DAC 输出通道,这两个通道互不影响,每个通道都可以使用DMA 功能,都具有出错检测能力,可外部触发。
(2)使用Adobe Audition生成一个2KHz的正弦波
新建音频文件
设置参数
生成基本音色
自定义参数
生成正弦波
存储选区 输出为wav文件
在WAV音频文件转C语言代码程序中打开刚才保存的文件 并生成代码
打开野火官方提供的例程"ADC正弦波
"
在例程中的bsp_dac.c
文件中,将刚刚生成的代码复制放进去
最后烧录并用示波器观察
三、用STM32F103的DAC功能将一段数字音频歌曲数据转换为模拟音频波形输出(循环)
打开一个mp3文件,截取2—3秒的音频
存储选区
将WAV文件生成代码
复制到例程中的bsp_dac.c
文件中
最后烧录,并用示波器观察:
这篇关于实验4——STM32的PWM和DAC练习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!