关于对STM32的PWM和DAC的学习和练习

一、关于脉冲宽度调制和数模/模数转换原理

1.关于PWM

PWM是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调 制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控 制的一种非常有效的技术,其控制简单、灵活和动态响应好等优点而成 为电力电子技术最广泛应用的控制方式,其应用领域包括测量,通信,功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些 音频放大器,因此学习PWM具有十分重要的现实意义。 其实我们也可以这样理解,PWM是一种对模拟信号电平进行数字编码 的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM 信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压 或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去 的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。

2.关于DAC

DAC 为数字/模拟转换模块,故名思议,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与 ADC 相反。在常见的数字信号系统中,大部分传感器信号被化成电压信号,而 ADC 把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由 DAC 输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。STM32 具有片上 DAC 外设,它的分辨率可配置为 8 位或 12 位的数字输入信号,具有两个 DAC 输出通道,这两个通道互不影响,每个通道都可以使用 DMA 功能,都具有出错检测能力,可外部触发。

二、用STM32F103输出一路PWM波形

1.关于PWM输出

STM32F1除了基本定时器TIM6和TIM7,其他定时器都可以产生PWM输出 。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出 。而通用定时器也能同时产生多达 4路的 PWM 输出,这些在定时器中断 章节中已经介绍过。 PWM的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号 ,信号频率是由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定。

2.PWM输出模式

PWM输出比较模式总共有8种,具体由寄存器 CCMRx 的位 OCxM[2:0] 配置。
最常用的为PWM1和PWM2
在这里插入图片描述

3.配置PWM

(1)使能定时器及端口时钟,并设置引脚复用器映射
在这里插入图片描述

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE); 
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出

(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等

void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

(3)初始化PWM输出参数,包含PWM模式、输出极性,使能等

void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

(4)开启定时器

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); TIM_Cmd(TIM3,ENABLE); 

(5)修改TIMx_CCRx的值控制占空比

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);

(6)使能TIMx在CCRx上的预装载寄存器 使能输出比较预装载库函数是:

void  TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

第一个参数用于选择定时器,第二个参数用于选择使能还是失能输出比较预装载寄存器,可选择为TIM_OCPreload_Enable、TIM_OCPreload_Disable。
(7)使能 TIMx 在 ARR 上的预装载寄存器允许位 使能 TIMx 在 ARR 上的预装载寄存器允许位库函数是:

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);  //第一个参数用于选择定时器,第二个参数用于选择使能还是失能。

高级定时器要想输出PWM波形,必须要设置一个 MOE 位(TIMx_BDTR 的第 15 位),以使能主输出,否则不会输出 PWM。库函数设置的函数为:

void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

4.相关代码

此代码使用的是野火指南者板子的配套资料第32个实例通用计时器中的4路PWM输出的代码

static void GENERAL_TIM_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;

  // 输出比较通道1 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH1_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
	
	// 输出比较通道2 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH2_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStructure);
	
	// 输出比较通道3 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH3_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
	
	// 输出比较通道4 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM_CH4_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM_CH4_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM_CH4_PORT, &GPIO_InitStructure);
}

static void GENERAL_TIM_Mode_Config(void)
{
  // 开启定时器时钟,即内部时钟CK_INT=72M
	GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
	// 配置周期,这里配置为100K
	
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
	TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;	
	// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;	
	// 时钟分频因子 ,配置死区时间时需要用到
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	// 初始化定时器
	TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

	/*--------------------输出比较结构体初始化-------------------*/	
	// 占空比配置
	uint16_t CCR1_Val = 5;
	uint16_t CCR2_Val = 4;
	uint16_t CCR3_Val = 3;
	uint16_t CCR4_Val = 2;
	
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	// 配置为PWM模式1
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	// 输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	// 输出通道电平极性配置	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	
	// 输出比较通道 1
	TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
	TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 输出比较通道 2
	TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
	TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 输出比较通道 3
	TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
	TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 输出比较通道 4
	TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
	TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
	TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
	
	// 使能计数器
	TIM_Cmd(GENERAL_TIM, ENABLE);
}

5.观察波形

①.使用keil进行仿真观察波形

首先我们需要使keil在仿真模式下运行。如何设置我在之前的博客中已有相关介绍,在此不作赘述 点击这里查看
设置完成后,我们要打开keil自带的示波器对输出波形进行观察
在这里插入图片描述
在示波器中选择set up设置输出引脚
在这里插入图片描述
ch1没有经过重映像的输出引脚为PA6,这里我们输入GPIOA_IDR.6设置输出引脚为PA6
在这里插入图片描述
运行整个程序
在这里插入图片描述
示波器所输出的波形
在这里插入图片描述

②.使用示波器观察波形

因手边没有对应的设备,这一部分我将在操作完成后更新

三、用STM32F103的DAC功能输出波形

利用 STM32 的 DAC 配合 TIM 定时器,可以输出随时间变化的电压。
STM32 的 DAC 外设有固定的输出通道,分别为 PA4 和 PA5。

1.生成正弦波

要输出正弦波,实质是要控制 DAC 以 v=sin(t)的正弦函数关系输出电压,其中 v 为电
压输出,t 为时间。
而由于模拟信号连续而数字信号是离散的,所以使用 DAC 产生正弦波时,只能按一定时间间隔输出正弦曲线上的点,在该时间段内输出相同的电压值,若缩短时间间隔,提高单个周期内的输出点数,可以得到逼近连续正弦波的图形,若在外部电路加上适当的电容滤波,可得到更完美的图形。

2.正弦波的频率

在这里插入图片描述

3.输出一个周期2khz的正弦波

①.生成采样点

想要输出周期为2KHz的正弦波,我这里使用的是野火指南者板配套资料中第38个实验中的matlab的制作脚本
由上述公式可知,要输出2KHz的正弦波,一共需要3600个采样点。
将xinWave.m文件进行如下修改

%用于产生正弦数据表,输出到文件dac_sinWave.c 文件中,复制到c语言数组即可

n = 2*pi/3600 : 2*pi/3600 : 2*pi      %分成3600等份

a = sin(n)+1;                     %求取sin函数值并向上平移一个单位,消除负数值
a = a * 3.3/2;                    %调整幅值,使范围限制为0~3.3   
r = a* (2.^12) /3.3               %求取dac数值,12位dac LSB = 3.3/2.^12 
r = uint16(r);                     %double型数据转化成16位整型数据 

for i = 1:32                        
if r(i) > 4095                      %限制数据最大不超过4095
    r(i) = 4095
end
end 

dlmwrite('dac_sinWave.c',r);      %把数据写入到文件,方便添加到stm32工程中
plot(n,r,'.')                     %把这些点画出来 

然后使用matlab运行.c生成文件,将生成的数据放到下面的数组中

在这里插入图片描述

②.输出波形

由于手边没有示波器,我将在近期更新图片

4.将一段数字音频歌曲数据转换为模拟音频波形输出

①.生成wav文件对应的点

首先用音频制作工具制作一段数字化的2khz正弦波wav文件、转换一首你喜欢的歌曲片段为wav文件。
制作时须指定采样频率、量化位数和通道数,以及时间长度。MCU资源有限,建议采样8khz,量化16bit,单通道,时长仅仅5~10秒。
音频wav数据可以用类似汉字字模的保存方式,直接copy到Keil代码中数组中,不必使用SD卡上的wav文件(野火开发板是读取SD卡上的wav文件)。

使用Adobe Audition选中一个音频文件,另存为以下格式
在这里插入图片描述
在这里插入图片描述
使用以下工具,对wav文件进行处理
在这里插入图片描述
处理后
在这里插入图片描述

②.拷贝代码

将保存的代码复制进数组
在这里插入图片描述

③.输出波形

由于手边没有示波器,我将在近期更新图片

四、参考资料

(stm32f103学习总结)—stm32 PMW输出实验
STM32 PWM的输出与Keil软件仿真
STM32F103使用TIM DMA DAC实现播放WAV音乐

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值