之前定时器中有提到输入和输出比较部分
https://blog.csdn.net/qq_45764141/article/details/125286260
参考有江科大自化协的视频和正电原子的视频
这个文章主要讲输出部分
输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。
每个高级定时器和通用定时器都拥有4个输出比较通道(即上图中的TIMx_CH1 ~ 4) 高级定时器的前3个通道额外拥有死区生成和互补输出的功能
CRR是这里,就是捕获/比较寄存器。有四个输出比较通道,可以同时输出四路PWM波形,这四个通道有名自的CCR寄存器。但它们共用一个CNT计数器。最右边的部分可用于驱动三相无刷电机。
PWM (Pulse Width Modulation)脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域。
也就是说,使用这个PWM波形,是用来等效地实现一个模拟信号的输出。
比如我们以一个很快的频率给电机进行通电、断电,那么电机的速度就能维持在一个速度中。
LED在熄灭时,由于余晖和人眼视觉暂留现象,LED不会应马熄灭。而是有定的惯性,过一小段时间才会熄灭。电机也是。当电机断电时。电机的转动不会立马停止。这就是惯性的系统。
这种高低电平跳变的数字信号可等效于中间的这个紫色的虚线所表示的模拟量
(我的理解是,在一个周期中,高电平的时间占的比例比较多,那么就是显示为高电平,当高电平占的比例比较少,就显示为低电平。宏观上体现为
当上面电平时间长一点,下面电平时间短一点的时候,模拟量偏向于上面
当下面电平时间长一点,上面电平时间短一点的时候,模拟量偏向于下面)
定义
一个高低电平是一个周期,周期的倒数就是频率。
PWM的频率越快,等效模拟信号就越平稳。不过时间的开销就越大。(我的理解就是,变得越快越不容易发现,但是TS会增大)
般来说PWM的频率都在几KHz到几十KHz
占空比是高电平占周期的比例。
占空比为100%,那就是高电平不变。占空比为0%,那就是低电平不变。
占空比决定了PWM等效出来的模拟电压的大小,占空比越大,那等效的模拟电压就钺趋近高电平。占空比越小,那等效的模拟电压就越战趋近于低电平。
比如5v,50%的占空比就是2.5v,20%的占空比就是1v。
分辨率是指占空比变化步距,一般要求不高的话,1%的分辨率够用了
在这个图的左边输入的数据就是CNT和CRR1,他们进行比较,当满足CNT>CRR1或CNT=CRR1时,进入输出模式控制器。
输出模式控制器改变它输出OC1ref(reference参考信号)的高低电平。
接着这个ref信号往后转递。
首先他可以转递到上部分的主模式控制器中。可以把这个ref映射到主模式的TRGO输出上去。
主要是进行下面这一路。
这一个模块是一个极性选择电路。如果给CC1P写0,信号往上走,电平信号不反转。来啥样走啥样。如果给CC1P写1,信号进入下面一路,通过非门取反,电平信号反转。
这就是进行极性选择,就是选择要不要反转高低电平。
然后进入使能电路,决定要不要输出。
然后进入OC1引脚,就是TIMx_CH1通道的引脚,通过对引脚定义的查询可以知道具体是对应哪个GPIO口。
然后来细看一下输出比较模式控制器,这个是这个电路中非常重要的部分。
在这个模块里面可以选择多种模式来更加灵活地控制ref的输出,通过下面的寄存器进行配置。
第一个模式是冻结模式,即CNT=CCR时维持原状态。假如你正在输出PWM波形,想暂停,可以设置成这个模式。切换为冻结模式后,输出暂停。并且高低电平维持暂停时的状态保持不变。
接下来的2,3,4模式,配时置有效电平,配时置无效电平,匹配时电平翻转。这个有效电平和无效电平。一般是高级定时器里面的一个说法,可以暂且理解为,置有效电平就是置高电平。置无效电平就是置低电平。第四个模式匹配时电平翻转可以方便得输出一个频率可调且占空比始终为50%的波形。
第五、六个模式强制为无效电平和强制为有效电平。这两个模式和冻结模式相似。如果你想暂停波形输出,并且在暂停期间保持低电平或者高电平,就可以设置成这两个强制输出模式。
最后的两个模式也是最有用的模式,他们可以用于输出频率和占空比都可调的PWM波形。PWM模式1和PWM模式2只有ref置有效电平和无效电平换了一下。PWM模式2实际上就是PWM1模式输出的取反。
输出的基本结构
首先是定时器里讲过的配置好时基单元,这里不需要中断所以没有中断。
然后来到捕获/比较器,这里的CCR是我们自己设定的,CNT不断自增运行并进行比较。当CNT
这个蓝色的线就是不断自增的CNT,黄色的线就是重载值
这条红色的线就是CCR,是可以设定的。当我们设定CCR为30时
当蓝线低于黄线,就是CNT
就得到了下图绿色线的输出。
并且CCR时可以改变的,也就是说,占空比是可以改变的。所以也就是说这个波形是可以一直改变的。
所以这样得出的ref值再通过极性选择,输出使能,最终通向GPIO口。
PWM频率 = 计数器溢出频率
CK_PSC(预分频器的值), PSC(分频赋的值),ARR(重装载寄存器的值)
PWM频率 = CK_PSC / (PSC+1)/(ARR+1)
PWM占空比 = CCR / (ARR + 1)
分辨率 = 1 / (ARR + 1)
还是在上一节定时器的地方,stm32f10x_tim.h
OC1,OC2,OC3,OC4每个函数配置一个单元,第一个参数选择定时器,第二个参数设置结构体内的数据。
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);
给上述输出比较结构体赋默认值
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
用来配置强制输出模式
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);
配置CCR寄存器预装功能。
这个预装功能。就是影子寄存器,
就是你写入的值不会立即生效。而是在更新事件才会生效
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);
配置快速使能
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);
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);
单独修改输出使能参数
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);
单独更改CCR寄存器值得函数,可以在运行时更改占空比。
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);
这个PA8输出有TIM1_CH1,并且外设连接LED0灯。且TIM1是高级定时器。
更改一下TIMx的名字改为通用定时器,这个就是通用定时器可以使用的初始化模板。
void PWM_Init()
{
TIM_TimeBaseInitTypeDef TIM1_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM1_OCInitStructure;
//时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
//初始化定时器参数
TIM1_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟划分
TIM1_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up; //计数器模式向上计数
TIM1_TimeBaseInitStructure.TIM_Period = 100 -1; //自动重载
TIM1_TimeBaseInitStructure.TIM_Prescaler = 720 -1;
TIM1_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1,&TIM1_TimeBaseInitStructure);
//TIM1_OCStructInit(&TIM1_OCInitStructure);
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM1_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM1, &TIM1_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_Cmd(TIM1,ENABLE);
}
此时PA8口会输出PWM波形。找个灯接入PA8就可以实现。
这里我们还要配置 PA8 为复用输出(当然还要时能 GPIOA的时钟),这是因为 TIM1_CH1 通道将使用 PA8 的复用功能作为输出。
PA8要设置为复用推挽输出,同样要在PWM_Init()函数中定义。
并且TIM1是高级定时器,还要使用
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
所以总的代码为
#include "stm32f10x.h"
#include "PWM.h"
void PWM_Init()
{
TIM_TimeBaseInitTypeDef TIM1_TimeBaseInitStructure;
TIM_OCInitTypeDef TIM1_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//初始化定时器参数
TIM1_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟划分
TIM1_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up; //计数器模式向上计数
TIM1_TimeBaseInitStructure.TIM_Period = 100 -1; //自动重载
TIM1_TimeBaseInitStructure.TIM_Prescaler = 720 -1;
TIM1_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1,&TIM1_TimeBaseInitStructure);
//TIM1_OCStructInit(&TIM1_OCInitStructure);
TIM1_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
TIM1_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM1_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
TIM1_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC1Init(TIM1, &TIM1_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
//TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1预装载使能
//TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_Cmd(TIM1,ENABLE);
}
void PWM_SetCompare1(uint16_t compare)
{
TIM_SetCompare1(TIM1,compare);
}
#include "stm32f10x.h"
#include "LED.h"
#include "delay.h"
#include "PWM.h"
uint8_t i;
int main(void)
{
delay_init(); //延时函数初始化
LED_Init();
PWM_Init();
while(1)
{
for(i = 0;i < 100;i++)
{
PWM_SetCompare1(i);
delay_ms(10);
}
for(i = 0;i < 100;i++)
{
PWM_SetCompare1(100 - i);
delay_ms(10);
}
}
}
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 配置时钟,使能IO口时钟,使能PA,PD端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE);
//初始化IO参数
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
//初始化IO参数
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_8);
GPIO_SetBits(GPIOD,GPIO_Pin_2);
}
但是我发现PWM_Init()中不使能GPIOA也可以,我怀疑是因为LED_Init()中有使能GPIOA。