一、PWM
1、PWM介绍
PWM是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调 制,简称脉宽调制。STM32F1除了基本定时器TIM6和TIM7,其他定时器都可以产生PWM输出 。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出 。而通用定时器也能同时产生多达 4路的 PWM 输出,这些在定时器中断 章节中已经介绍过。 PWM的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号 ,信号频率是由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定。
PWM输出比较模式总共有8种,具体由寄存器 CCMRx 的位 OCxM[2:0] 配置。最常用的两种PWM输出模式:PWM1和PWM2:
PWM输出配置:
- 使能定时器及端口时钟,并设置引脚复用器映射
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE); //可选的参数在 stm32f10x_gpio.h 都已经列出来非常详细
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
- 初始化定时器参数,包含自动重装值,分频系数,计数方式等
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
- 初始化PWM输出参数,包含PWM模式、输出极性,使能等
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
- 开启定时器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); TIM_Cmd(TIM3,ENABLE); //开启定时器
- 修改TIMx_CCRx的值控制占空比
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);
- 使能TIMx在CCRx上的预装载寄存器 使能输出比较预装载库函数是:
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
第一个参数用于选择定时器,第二个参数用于选择使能还是失能输出比较预装载寄存器,可选择为TIM_OCPreload_Enable、TIM_OCPreload_Disable。
- 使能 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);
2、用STM32F103输出一路PWM波形
实验代码可参考野火官方给的资料。
部分代码:
void LED_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED相关的GPIO外设时钟*/
RCC_APB2PeriphClockCmd( LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK, ENABLE);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
/*调用库函数,初始化GPIOF*/
GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
/* 关闭所有led灯 */
GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
/* 关闭所有led灯 */
GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);
/* 关闭所有led灯 */
GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);
}
main.c函数:
int main(void)
{
/* led 端口配置 */
LED_GPIO_Config();
/* 定时器初始化 */
GENERAL_TIM_Init();
while(1)
{
}
}
点击Options For Target进行如下配置:
然后点击OK。
接下来点击仿真按钮:
最后点击运行仿真:
结果如下:
将程序下载到指南针开发板上,用示波器观察如下:
二、用STM32F103的DAC功能完成波形输出
1、DAC简介
DAC 为数字/模拟转换模块,故名思议,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与 ADC 相反。在常见的数字信号系统中,大部分传感器信号被化成电压信号,而 ADC 把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由 DAC 输出电压模拟信号,该电压模拟信号常常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。
STM32 具有片上 DAC 外设,它的分辨率可配置为 8 位或 12 位的数字输入信号,具有两个 DAC 输出通道,这两个通道互不影响,每个通道都可以使用 DMA 功能,都具有出错检测能力,可外部触发。
2、输出一个周期2khz的正弦波
打开野火官方的的DAC例程,在此例程上进行操作。
根据定时器的配置,可推算出正弦波频率的计算方式:
可根据上述公式计算出周期为2KHz的正弦波的定时周期为1125。
*/
static void DAC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* 使能TIM2时钟,TIM2CLK 为72M */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* TIM2基本定时器配置 */
// TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = (1125-1); //定时周期 1125
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频,不分频 72M / (0+1) = 72M
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //时钟分频系数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* 配置TIM2触发源 */
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
/* 使能TIM2 */
TIM_Cmd(TIM2, ENABLE);
}
在这里插入图片描述
3、将一段数字音频歌曲数据转换为模拟音频波形输出
首先下载一首自己喜欢的歌曲,用Adobe Audition CS6打开;
然后截取歌曲的一部分:
保存为.wav文件,且频率为8kHz,量化16bit;
然后用UltraEdit打开刚刚我们保存的文件;
点击Ctrl+A全选,右键选择“十六进制复制选定视图”;
然后新建文件,粘贴;
右键“选择范围”;
将选中的内容复制下来,放在一个新文件里面,然后用notepad++打开,点击编辑→列块编辑→插入文本,生成结果如下:
将上面得到的十六进制文本复制(由于太长,我只选择了一部分);
0x52 ,0x49 ,0x46 ,0x46 ,0xCC ,0x51 ,0x01 ,0x00 ,0x57 ,0x41 ,0x56 ,0x45 ,0x66 ,0x6D ,0x74 ,0x20 ,
0x12 ,0x00 ,0x00 ,0x00 ,0x01 ,0x00 ,0x01 ,0x00 ,0x40 ,0x1F ,0x00 ,0x00 ,0x80 ,0x3E ,0x00 ,0x00 ,
0x02 ,0x00 ,0x10 ,0x00 ,0x00 ,0x00 ,0x64 ,0x61 ,0x74 ,0x61 ,0x8E ,0x37 ,0x01 ,0x00 ,0x5E ,0xFF ,
0x85 ,0x00 ,0xA7 ,0x01 ,0x90 ,0x00 ,0xBE ,0xFF ,0x08 ,0x00 ,0xFE ,0x00 ,0xC5 ,0xFC ,0x79 ,0x00 ,
0xCD ,0xFE ,0xEA ,0xFF ,0x3B ,0xFF ,0xC7 ,0x00 ,0xE6 ,0x00 ,0xA4 ,0xFF ,0x8D ,0xFF ,0x82 ,0x01 ,
0xDD ,0xFF ,0x7A ,0x00 ,0x6B ,0x00 ,0xFF ,0xFF ,0xEE ,0xFF ,0x5F ,0x00 ,0x4D ,0xFF ,0x94 ,0xFF ,
0x15 ,0x00 ,0xD4 ,0xFE ,0xD5 ,0x00 ,0xB2 ,0xFF ,0xAE ,0x00 ,0xBA ,0x00 ,0x38 ,0x01 ,0x4D ,0xFF ,
0x65 ,0x00 ,0x83 ,0xFE ,0x21 ,0xFF ,0xF5 ,0xFF ,0x0A ,0x00 ,0x20 ,0xFF ,0xBA ,0x00 ,0xA1 ,0x00 ,
0x5A ,0x00 ,0x07 ,0x00 ,0x27 ,0x00 ,0xA2 ,0x00 ,0x96 ,0xFF ,0xB7 ,0x00 ,0x6F ,0x00 ,0xA0 ,0xFF ,
0x4F ,0x00 ,0x29 ,0xFF ,0xCE ,0xFF ,0x77 ,0x00 ,0x3A ,0xFE ,0x46 ,0x01 ,0xF7 ,0xFE ,0x08 ,0x02 ,
0x5F ,0x00 ,0x4B ,0x00 ,0x98 ,0x00 ,0x69 ,0xFF ,0x9E ,0xFE ,0x61 ,0xFE ,0xE4 ,0xFF ,0x4C ,0x00 ,
0xBB ,0xFF ,0xE6 ,0x00 ,0x62 ,0x00 ,0x48 ,0xFF ,0xF6 ,0xFF ,0xE6 ,0xFF ,0xB3 ,0x00 ,0x89 ,0xFF ,
然后复制到代码当中:
然后连接硬件,下载程序,运行仿真:
三、总结
PWM和DAC有些难,作为初学者还需要慢慢理解和实践。