前言:
本文介绍了STM32怎么编写PWM,以及如何利用PWM实现LED呼吸灯效果的代码项目实战
结尾有代码源码附录
STM32定时器输出比较单元输出PWM原理:
用到了PWM输出,
STM32的PWM用到了定时器的输出比较模块,
首先看一下PWM基本结构,方便我们编写程序代码,配置寄存器
大体流程:
第1步,RCC开启时钟,把需要的TIM外设和GPIO外设的时钟打开
第2步,配置时基单元,包括时钟源选择
第3步,配置输出比较单元,包括CCR的值、输出比较模式、极性选择、输出使能
第4步,配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出配置
第5步,运行控制,启动计数器,这样就能输出PWM了
以下是STM32标准库函数tim.h的一些关于输出比较模式的函数介绍(详细代码实战部分可以跳转目录后面的代码编写部分)
库函数stm32f10x_tim.h函数关于输出比较函数的介绍
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
这四个函数用来配置输出比较模块(输出比较单元有4个,一个函数对应配置一个单元)
第一个参数:选择定时器
第二个参数:结构体(包含输出比较的参数)
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
这个函数用来给输出比较结构体赋一个默认值的
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
这个函数,仅高级定时器使用,在使用高级定时器输出PWM时,需要调用这个函数,使能主输出,否则PWM将不能正常输出
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction); void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction); void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction); void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
这四个函数,用来配置强制输出模式(如果在运行中想要暂停输出波形并且强制输出高或低电平)(一般用的不多)
void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload); void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload); void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload); void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
这四个函数用来配置CCR寄存器的预装功能(影子寄存器:写入的值不会立即生效,而是更新事件才会生效)(一般也用不到)
void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast); void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast); void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast); void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
这四个函数用来配置快速使能的(一般用的也不多)
void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear); void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear); void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear); void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
外部事件时清除REF信号(用的也不多)
void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity); void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity); void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity); void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity); void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity); void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity); void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
这些用来单独(因为前面结构体里面就可以配置,这个函数只是用来单独修改的)设置输出比较极性的,函数名带N的就是高级定时器的互补通道的配置
void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx); void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);
用来单独修改输出使能参数的
void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);
选择输出比较模式,用来单独更改输出比较模式的函数
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1); void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2); void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3); void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
这四个函数,用来单独修改CCR寄存器值的函数 ,在运行的时候,更改占空比 (**** 比较重要 ****)
代码编写
0.创建一个PWM.c文件,用来编写PWM代码
1.首先是开启RCC时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启时钟
2.配置时基单元
注意:
PWM的占空比和频率周期是由CCR和RR共同决定的,所以配置时基单元和输出比较单元的时候,需要配置对应的参数
配置时基单元
TIM_InternalClockConfig(TIM2); //TIM2选择为内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 指定时钟分频,为1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 选择计数模式,为向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //周期,预重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值,这里是高级定时器才需要,这里给0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); //传入结构体,初始化
3.初始化输出比较单元
在库函数tim.h里找到初始化函数(上面库函数也有介绍过),有四个函数,对应四个输出比较通道,根据需求来,这里我们用GPIO口的PA0,我们用第一个函数
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
复制粘贴,定义结构体配置,调用函数
以下是TIM_OCInitTypeDef结构体内部参数的分析
有一些机构体成员是配置高级定时器用的,与通用定时器无关。
TIM_OCMode有这几种模式
1、TIM_OCMode_Timing 冻结模式
2、TIM_OCMode_Active 相等时置有效电平
3、TIM_OCMode_Inactive 相等时置无效电平
4、TIM_OCMode_Toggle 相等时电平翻转
5、TIM_OCMode_PWM1 PWM模式1
6、TIM_OCMode_PWM2 PWM模式2
TIM_OCPolarity有这几种模式
1、TIM_OCPolarity_High 高极性,极性不翻转,REF有效时,输出高电平
2、TIM_OCPolarity_Low 低极性,REF电平取反
TIM_OutputState有使能和失能模式
1、TIM_OutputState_Disable 失能
2、TIM_OutputState_Enable 使能
TIM_Pulse设置CCR捕获比较寄存器
可以是0x0000到0xFFFF的值
初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure; //创建一个OC初始化结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //初始化结构体,给默认值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //设置输出比较的模式,PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置输出比较的极性,高极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //设置输出使能
TIM_OCInitStructure.TIM_Pulse = 50; //用来设置CCR
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
配置完输出比较单元,在TIM2的OC1通道上就可以输出PWM波形了
还要借助GPIO口输出,所以在配置一下GPIO
4.配置GPIO
打开STM32的引脚口定义表,找到TIM2的OC1通道用到的GPIO用到的是PA0口
注意:引脚输出模式选择复用推挽输出,引脚的输出控制才能交给TIM2的PWM输出
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //注意:选择复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
这样子,PWM的配置就基本完成了,但是要实现呼吸灯效果,就需要不断更改CCR的值,来不断变换占空比,所以再封装一个更改CCR值的函数
5.封装不断变化修改CCR值的函数
用到库函数,可以修改CCR值的函数TIM_SetCompare1
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
6.创建PWM.h的头文件,声明函数
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
7.主函数main.c里调用函数
这里用到for循环,加上Delay,简单应用一下,调用更改CCR值的函数,可以变换占空比,从而来实时变换呼吸灯的亮度,达到呼吸灯的效果。
程序现象:
可以看到,LED灯从亮到灭,从灭到暗,逐渐变化
STM32呼吸灯
示波器现象:
可以看到,示波器检测的PWM波占空比也是实时变化的
STM32呼吸灯示波器
附录:程序源码
1.PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //开启时钟
TIM_InternalClockConfig(TIM2); //TIM2选择为内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 指定时钟分频,为1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 选择计数模式,为向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //周期,预重装器的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器的值,这里是高级定时器才需要,这里给0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); //传入结构体,初始化
TIM_OCInitTypeDef TIM_OCInitStructure; //创建一个OC初始化结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //初始化结构体,给默认值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //设置输出比较的模式,PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置输出比较的极性,高极性
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //设置输出使能
TIM_OCInitStructure.TIM_Pulse = 50; //用来设置CCR
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //注意:选择复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_Cmd(TIM2,ENABLE); //启动定时器TIM2
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
2.PWM.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
3.main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "PWM.h"
int main(void)
{
PWM_Init();
while(1)
{
for(uint8_t i=0;i<=100;i++)
{
PWM_SetCompare1(i); //占空比逐渐变大,LED逐渐变亮
Delay_ms(10);
}
for(uint8_t i=0;i<=100;i++)
{
PWM_SetCompare1(100-i); //占空比逐渐变小,LED逐渐变暗
Delay_ms(10);
}
}
}