STM32入门笔记(02):定时器之定时器中断、输入捕获和PWM输出(SPL库函数版)
目录
- 定时器
- 8个定时器
- 定时器功能
- 定时器中断
- 常用通用定时器的寄存器
- 定时器中断实验目的:
- 定时器库函数及步骤
- 实验程序讲解
- PWM 简介
- STM32 PWM工作过程
- PWM模式1 与 PWM模式2
- STM32定时器3输出通道引脚
- PWM库函数及步骤
- 实验程序讲解
- 输入捕获
- 通用定时器输入捕获概述
- 常用寄存器和库函数配置
- 输入捕获实验
- 输入捕获的一般配置步骤
- 实验目的:测量信号的脉冲宽度
- 参考资料
先导知识
1.STM32入门笔记(02):Keil5 安装–SPL标准外设驱动库环境搭建及新建工程模板教程(SPL库函数版)
2.STM32入门笔记(02): GPIO工作原理 – GPIO通用和AFIO复用功能 I/O(SPL库函数版)
3. STM32入门笔记(02):AFIO复用端口与重映射 、中断、串口通信及串口实验(SPL库函数版)
定时器
8个定时器
高级定时器TIMx(TIM1、TIM8)
通用定时器TIMx (TIM2、TIM3、TIM4和TIM5)
基本定时器TIMx (TIM6、TIM7)
注意:STM32F4xx系列有32位的定时器。
STM32F1 的通用定时器是一个通过可编程预分频器(PSC驱动的 16 位 自动装载 计数器(CNT) 构成。
定时器功能
STM32 的通用定时器可以被用于以下场景:
1.基本功能:计数器
1.测量输入信号的脉冲长度(输入捕获)
2.产生输出波形(输出比较和 PWM)等。
使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
TIMx (TIM2、TIM3、TIM4 和 TIM5) 位于 APB1 总线上(APB1)。
STM3F1 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能包括:
1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。计数器的模式:
- 向上计数模式 。计数器从 0 计数到 自动加载值(TIMX_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
- 向下计数模式。计数器从自动装入的值(TIMX_ARR)开始向下计数到 0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
- 中央对齐模式(向上/向下计数) 。计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。可以把一个很高的频率降低,根据需求配置预分频系数。
3)4 个独立通道(TIMx_CH1~4)查看芯片手册可知通道对应芯片的引脚
,这些通道可以用来作为:
- A.输入捕获
- B.输出比较
- C.PWM 生成(边缘或中间对齐模式)
- D.单脉冲模式输出
框图分为四个部分:
- 选择时钟 (系统时钟)
- 时基电路 (重要部分)
- 输入捕获
- 输出比较
4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
5)如下事件发生时产生中断/DMA:
- A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
- B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- C.输入捕获
- D.输出比较
- E.支持针对定位的增量(正交)编码器和霍尔传感器电路
- F.触发输入作为外部时钟或者按周期的电流管理
定时器中断
常用通用定时器的寄存器
由于 STM32 通用定时器比较复杂(参考《STM32 参考手册》第 253 页,通用定时器一章)。为了深入了解 STM32 的通用寄存器,下面我们先介绍实验密切相关的几个通用定时器的寄存器。
控制寄存器 1(TIMx_CR1)
DMA/中断使能寄存器(TIMx_DIER)
预分频寄存器(TIMx_PSC)
该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。定时器的时钟来源有 4 个:
1)内部时钟(CK_INT)
2)外部时钟模式 1:外部输入脚(TIx)
3)外部时钟模式 2:外部触发输入(ETR)
4)内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。
- 自动重装载寄存器(TIMx_ARR)
- 状态寄存器(TIMx_SR)。 该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。
只要对以上几个寄存器进行简单的设置,我们就可以使用通用定时器了,并且可以产生中断。原理大致为,通过定时器计数的向上计数模式,从0到100(自动加载值)后产生溢出、中断,从而触发LED亮或者灭(外设工作)。
时钟默认选择内部时钟 :CK_INT
CK_INT时钟是从 APB1 倍频的来的,除非 APB1 的时钟分频数设置为 1,否则通用定时器 TIMx 的时钟是 APB1 时钟的 2 倍
,当 APB1 的时钟不分频的时候,通用定时器 TIMx 的时钟就等于 APB1的时钟。
分频数=2
通用定时器的时钟CK_INT:72M
注意 :的就是高级定时器的时钟不是来自 APB1,而是来自 APB2 的。
定时器中断实验目的:
使用定时器产生中断,然后在中断服务函数里面翻转 DS1 上的电平,来指示定时器中断的产生。接下来我们以通用定时器 TIM3 为实例。
定时器库函数及步骤
定时器相关的库函数主要集中在固件库文件 stm32f10x_tim.h
和 stm32f10x_tim.c
文件
中。
1)TIM3 时钟使能。
TIM3 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM3。调用的函数是RCC_APB1PeriphClockCmd。
例如:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。
在库函数中,定时器的初始化参数是通过初始化函数 TIM_TimeBaseInit 实现的:
void TIM_TimeBaseInit
(TIM_TypeDefTIMx,TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct);
入口参数:
参数1 :是确定是哪个定时器
参数2:是定时器初始化参数结构体指针结构体类型为 TIM_TimeBaseInitTypeDef,下面我们看看这个结构体的定义:
typedef struct
{
uint16_t TIM_Prescaler;//设置分频系数
uint16_t TIM_CounterMode;//来设置计数
//方式向上计数模式 TIM_CounterMode_Up
//下计数模式 TIM_CounterMode_Down。
uint16_t TIM_Period;//自动重载计数周期值
uint16_t TIM_ClockDivision;//设置时钟分频因子
uint8_t TIM_RepetitionCounter;//高级定时器才有用的参数
} TIM_TimeBaseInitTypeDef;
例如:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//定时器TIM3初始化TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
3)设置 TIM3_DIER 允许更新中断。
因为我们要使用 TIM3 的更新中断,寄存器的相应位便可使能更新中断。在库函数里面定时器中断使能是通过 TIM_ITConfig 函数来实现的:
void TIM_ITConfig
(TIM_TypeDef TIMx, uint16_t TIM_IT, FunctionalState NewState)
例如:
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能 TIM3 的更新中断
入口参数:
- 参数1 :选择定时器号TIM1~TIM17
- 参数2:使能的定时器中断的类型( 更新中断为TIM_IT_Update;触发中断 TIM_IT_Trigger;输入捕获中断……)
- 参数3:失能/使能
4)TIM3 中断优先级设置
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器,设置中断优先级。之前多次讲解到用 NVIC_Init 函数实现中断优先级的设置(参考上篇文章)。
5)允许 TIM3 工作,也就是使能 TIM3
光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,通过 TIM3_CR1 的 CEN 位来设置。在固件库里面使能定时器的函数是通过 TIM_Cmd 函数来实现的:
void TIM_Cmd
(TIM_TypeDef TIMx, FunctionalState NewState)
要使能定时器 3,例如:
TIM_Cmd(TIM3, ENABLE); //使能 TIMx 外设
6)编写中断服务函数。
在最后,还是要编写定时器中断服务函数 void TIM3_IRQHandler(void)
,通过该函数来处理定时器产生的相关中断。在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作,我们这里使用的是更新(溢出)中断,所以在状态寄存器 SR 的最低位。在处理完中断之后应该向 TIM3_SR 的最低位写 0,来清除该中断标志。
在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是:
ITStatus TIM_GetITStatus
(TIM_TypeDef TIMx, uint16_t)
该函数的作用是,判断定时器 TIMx 的中断类型 TIM_IT 是否发生中断。比如,我们要判断定时器 3 是否发生更新(溢出)中断,方法为:
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}//检查TIM3更新中断发生与否
固件库中清除中断标志位的函数是:
void TIM_ClearITPendingBit
(TIM_TypeDef TIMx, uint16_t TIM_IT)
该函数的作用是,清除定时器 TIMx 的中断 TIM_IT 标志位。使用起来非常简单,比如我们在TIM3 的溢出中断发生后,我们要清除中断标志位,方法是:
TIM_ClearITPendingBit(TIM3, TIM_IT_Update );//清除TIMx更新中断标志
实验程序讲解
timer.h文件
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"void TIM3_Int_Init(u16 arr,u16 psc);#endif
timer.c文件
#include "timer.h"
#include "led.h"//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能//定时器TIM3初始化TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断//中断优先级NVIC设置NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//定时器3中断服务程序
void TIM3_IRQHandler(void) //TIM3中断
{if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否{TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志LED1=!LED1;//中断触发处理的任务 实现LED翻转 暗灭}
}
该文件下包含一个中断服务函数 和一个 定时器 3 中断初始化函数,中断服务函数在每次中断后,判断 TIM3 的中断类型,如果中断类型正确(溢出中断),则执行 LED1(DS1)的取反。
TIM3_Int_Init()函数 就是执行上面介绍的那 6 个步骤,我们分别用标号①~⑥来标注,该函数的 2 个参数用来设置 TIM3 的溢出时间。
系统初始化的时候在默认的系统初始化函数 SystemInit 函数里面已经初始化 APB1 的时钟为 2 分频,所以 APB1 的时钟为 36M,而从 STM32 的内部时钟树图得知:
当 APB1 的时钟分频数为 1 的时候,TIM2~7 的时钟为 APB1 的时钟;而如果 APB1 的时钟分频数不为 1,那么 TIM2~7 的时钟频率将为 APB1 时钟的2倍。
因此,TIM3 的时钟为 72M,再根据我们设计的 arr自动重装值 和 psc时钟预分频数 的值,就可以计算中断时间了。计算公式如下:
Tout= ((arr+1)*(psc+1))/Tclk;
- Tclk:TIM3 的输入时钟频率(单位为 Mhz) ,这里是72Mhz。
- Tout:TIM3 溢出时间(单位为 us)。
例如:
TIM3_Int_Init(4999,7199);//10Khz的计数频率,计数到5000为500ms
main.c文件
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"int main(void){ delay_init(); //延时函数初始化NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级uart_init(115200); //串口初始化为115200LED_Init(); //LED端口初始化TIM3_Int_Init(4999,7199);//10Khz的计数频率,计数到5000为500mswhile(1){LED0=!LED0; // LED0 交替亮灭delay_ms(200); //} }
此段代码对 TIM3 进行初始化之后,进入死循环等待 TIM3溢出中断,当 TIM3_CNT 的值等于 IM3_ARR 的值的时候,就会产生 TIM3 的更新中断,然后在中断里面取反LED1,TIM3_CNT 再从 0 开始计数。
根据公式,
Tout= ((arr+1)*(psc+1))/Tclk;
- Tclk:TIM3 的输入时钟频率(单位为 Mhz) ,这里是72Mhz ,即72 000 000。
- Tout:TIM3 溢出时间(单位为 us)。
我们可以算出中断溢出时间为 500ms,即 Tout= ((4999+1)*( 7199+1))/72=500000us=500ms。
PWM 简介
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
简单一点,就是对脉冲宽度的控制。
STM32 的定时器除了 TIM6 和TIM7。其他的定时器都可以用来产生 PWM 输出。
其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。
而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出!
这里我们仅利用 TIM3的 CH2 产生一路 PWM 输出。
STM32 PWM工作过程
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。
定时器 从0到自动加载值 ARR 累加计数,当计数等于ARR的时候溢出中断。
假设 ARR == 100,定时器 从0 到 100,中间有个与之比较的数字X 假设==60。
若计时器的数值 小于 CCRx (也就是60),输出 0 ;
若计时器的数值 大于 CCRx (也就是60),输出 01。
如此循环下去就产生了一个PWM信号。
信号的周期 :由自动加载值ARR决定,且与系统时钟有关。
占空比:由CCRx 决定。
PWM模式1 与 PWM模式2
脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。
在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1)或’111’(PWM模式2),能够独立地设置每个OCx输出通道产生一路PWM。
必须设置TIMx_CCMRx寄存器OCxPE位以使能相应的预装载寄存器,最后还要设置TIMx_CR1寄存器的ARPE位,(在向上计数或中心对称模式中)使能自动重装载的预装载寄存器。
TIM_OC2PreloadConfig 函数
例如:
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
STM32定时器3输出通道引脚
看表得知,默认条件下 TIM3_REMAP[1:0] 为 00,是没有重映射的。
所以 TIM3_CH1~TIM3_CH4 分别是接在 PA6、PA7、PB0 和 PB1 上的。
而我们想让 TIM3_CH2 映射到 PB5 上,则需要设置TIM3_REMAP[1:0]=10,即部分重映射。
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 定时器TIM3的通道2输出引脚 PA7 ,重映射至TIM3_CH2->PB5
注意:此时 TIM3_CH1
也被映射到 PB4
上了。
补充说明:为什么非要映射到PB5?
因为正点原子的精英版开发板上面有2颗LED,对应的是:
LED1 ——PB5 引脚
LED2——PE5 引脚
如上图所示,在lec.h头文件中可以修改输出端口号。
PWM库函数及步骤
PWM 相关的函数设置在库函数文件 stm32f10x_tim.h
和 stm32f10x_tim.c
文件中。
库函数&步骤:
1)开启 TIM3 时钟以及复用功能时钟,配置 PB5 为复用输出。
要使用 TIM3,我们必须先开启 TIM3 的时钟,还要配置 PB5 为复用输出,这是因为 TIM3_CH2 通道将重映射到 PB5 上,此时,PB5属于复用功能输出。
库函数使能 TIM3 时钟的方法是:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器 3 时钟
库函数设置 AFIO 时钟的方法是:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //复用时钟使能
GPIO 初始化: PB5 为复用输出
GPIO_InitTypeDef GPIO_InitStructure;//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形 GPIOB.5GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
2)设置 TIM3_CH2 重映射到 PB5 上。
TIM3_CH2 默认是接在 PA7 上的,所以我们需要设置 TIM3_REMAP 为部分重映射(通过 AFIO_MAPR 配置),让 TIM3_CH2 重映射到 PB5 上面。在库函数函数里面设置重映射的函数是:
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
- 入口参数为为设置重映射的类型 GPIO_PartialRemap_TIM3
TIM3 部分重映射的库函数实现方法是:
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
3)初始化 TIM3,设置 TIM3 的 ARR 和 PSC。
在开启了 TIM3 的时钟之后,我们要设置 ARR自动重装载值 和 PSC预分频值 两个寄存器的值来控制输出 PWM 的周期。
当 PWM 周期太慢(低于 50Hz)的时候,我们就会明显感觉到闪烁了。因此,PWM 周期在这里不宜设置的太小。
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//初始化TIM3TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
4)设置 TIM3_CH2 的 PWM 模式,使能 TIM3 的 CH2 输出。
接下来,我们要设置 TIM3_CH2 为 PWM 模式(默认是冻结的)。
因为想要的 DS0(LED) 是低电平亮。
而我们希望当
CCR2 值小的时候,DS0(LED)就暗;
CCR2 值大的时候,DS0 就亮。
所以我们要通过配置 TIM3_CCMR1 的相关位来控制 TIM3_CH2 的模式。
PWM 通道设置是通过函数 TIM_OC1Init()~TIM_OC4Init()
来设置的,这里使用的是通道 2,所以使用的函数是 TIM_OC2Init()
。
void TIM_OC2Init()
(TIM_TypeDef TIMx, TIM_OCInitTypeDef TIM_OCInitStruct);**
来看看结构体 TIM_OCInitTypeDef的定义:
typedef struct
{
uint16_t TIM_OCMode; //设置模式是 PWM 还是输出比较
uint16_t TIM_OutputState;//设置比较输出使能,也就是使能 PWM 输出到端口uint16_t TIM_OutputNState; //高级定时器 TIM1 和 TIM8 才用到
uint16_t TIM_Pulse;
uint16_t TIM_OCPolarity;//设置极性是高还是低
uint16_t TIM_OCNPolarity;//高级定时器 TIM1 和 TIM8 才用到
uint16_t TIM_OCIdleState;//高级定时器 TIM1 和 TIM8 才用到
uint16_t TIM_OCNIdleState;//高级定时器 TIM1 和 TIM8 才用到
} TIM_OCInitTypeDef;
例如:
TIM_OCInitTypeDef TIM_OCInitStructure;//初始化TIM3 Channel2 PWM模式TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2
5)使能 TIM3。
例如:
TIM_Cmd(TIM3, ENABLE); //使能 TIM3
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的预装载寄存器
6)修改 TIM3_CCR2 来控制占空比。
最后,在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改 TIM3_CCR2 则可以控制 CH2 的输出占空比。继而控制 DS0 的亮度。在库函数中,修改 TIM3_CCR2 占空比的函数是:
void TIM_SetCompare2
(TIM_TypeDef TIMx, uint16_t Compare2);*
- 入口参数1:计数器号
- 入口参数2:占空比值
理所当然,对于其他通道,分别有一个函数名字,函数格式为 TIM_SetComparex(x=1,2,3,4)
。
比如:
TIM_SetCompare2(TIM3,led0pwmval);
实验程序讲解
timer.c
#include "timer.h"
#include "led.h"
#include "usart.h"//TIM3 PWM 部分初始化
//PWM 输出初始化
//arr:自动重装值 psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //①使能定时器 3 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //①使能 GPIO 和 AFIO 复用功能时钟GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //②重映射 TIM3_CH2->PB5//初始化GPIO 设置该引脚为复用输出功能,输出 TIM3 CH2 的 PWM 脉冲波形 GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //①初始化 GPIO//初始化 TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置在自动重装载周期值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //③初始化 TIMx//初始化 TIM3 Channel2 PWM 模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择 PWM 模式 2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //④初始化外设 TIM3 OC2TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能预装载寄存器
TIM_Cmd(TIM3, ENABLE); //⑤使能 TIM3
}
此部分代码包含了上面介绍的 PWM 输出设置的前 5 个步骤分别用标号①~⑤步骤。
注意:在配置 AFIO 相关寄存器的时候,必须先开启辅助功能时钟。
头文件 timer.h
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"// void TIM3_Int_Init(u16 arr,u16 psc);
void TIM3_PWM_Init(u16 arr,u16 psc);
#endif
main 函数如下:
int main(void)
{
u16 led0pwmval=0;
u8 dir=1;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
TIM3_PWM_Init(899,0); //不分频,PWM 频率=72000/900=80Khzwhile(1)
{
delay_ms(10);
if(dir)led0pwmval++;
else led0pwmval--;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
TIM_SetCompare2(TIM3,led0pwmval);//修改 TIM3_CCR2 占空比的函数
}
}
将 led0pwmval 这个值设置为 PWM 比较值,是通过 led0pwmval 来控制 PWM 的占空比.
然后控制 led0pwmval 的值从 0 变到 300, 然后又从 300 变到 0,如此循环,因此 DS0(LED) 的亮度也会跟着从暗变到亮,然后又从亮变到暗。
为什么 led0pwmval 取 300?
是因为 PWM 的输出占空比达到这个值的时候, LED 亮度变化就不大了(虽然最大值可以设置到 899),因此设计过大的值在这里是没必要的。
输入捕获
STM32F4-基于探索者F407 第33讲 输入捕获
通用定时器输入捕获概述
STM32输入捕获工作过程(通道1为例):
通过检测TIMx_CHx上的边沿信号,再边沿信号发生跳变(上升沿/下降沿)的时候,将当前定时器的值(TIMx_CHT)存放在对应捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。
常用寄存器和库函数配置
输入捕获实验
输入捕获的一般配置步骤
实验目的:测量信号的脉冲宽度
为了防止测量时从0到ARR自动重载值超出范围而导致的不严谨,引入了中断,通过16位的TIM5CH1_CAPTURE_STA去记录定时器溢出的次数。
定时器5的通道1输入捕获中断服务函数实现代码:
u8 TIM5CH1_CAPTURE_STA=0; //输入捕获状态;bit7 捕获完成标志1;bit6 捕获高电平标志1: 1100 0000
u16 TIM5CH1_CAPTURE_VAL; //输入捕获值//定时器5中断服务程序
void TIM5_IRQHandler(void)
{ if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获{ if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){ // 捕获溢出if(TIM5CH1_CAPTURE_STA&0X40)//③ 前面已经捕获到高电平了{if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了 溢出太多了 超过2^6次数,强制标记成功捕获到1次{TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了1次TIM5CH1_CAPTURE_VAL=0XFFFF;}else TIM5CH1_CAPTURE_STA++;//累计 2^6 次数溢出次数} }// 中断捕获if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件 初始化上升沿捕获{ if(TIM5CH1_CAPTURE_STA&0X40) //① 捕获到一个下降沿{ TIM5CH1_CAPTURE_STA|=0X80; // 标记成功捕获到了1次高电平 (1个上升沿到12个下降沿=1个周期)TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获}else //② 还未开始bit6!=1 ①捕获失败,第一次捕获仍是上升沿{TIM5CH1_CAPTURE_STA=0; //清空TIM5CH1_CAPTURE_VAL=0;TIM_SetCounter(TIM5,0);TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获} } }TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
资料下载:
- STM32F10x 定时器实验:TIM2定时器中断&TIM3 PWM 输出实验项目源码
参考资料
- [1]【正点原子】STM32开发板实验教程(F103)第30~32讲
- [2] STM32中文参考手册_V10 第14章
- [3] 正点原子/STM32F1开发指南(精英版)-库函数版本_V1.3 第13~15章
STM32入门笔记(02):定时器之定时器中断、输入捕获和PWM输出(SPL库函数版)相关推荐
- STM32入门笔记(02):F103C8T6 舵机PWM控制实验(SPL库函数版)
舵机的结构 舵机简单的说就是集成了直流电机. 电机控制器和减速器等, 并封装在一个便于安装的外壳里的伺服单元. 能够利用简单的输入信号比较精确的转动给定角度的电机系统. 舵机安装了一个电位器(或其它角 ...
- STM32入门笔记(02):MPU6050、MPU9250、ICM20948及姿态解算(SPL库函数版)
目录 MPU6050 什么是MPU6050? MPU6050的特点 MPU6050框图 MPU6050初始化 MPU6050寄存器 电源管理寄存器1(0X6B) 陀螺仪配置寄存器(0X1B) 加速度传 ...
- STM32入门笔记(02):MDK Keil5 开发环境搭建及新建工程模板教程(SPL库函数版)
目录 Keil5 开发环境搭建 芯片数据手册 Mec 多功能智能小车底盘开发与使用手册 Mec 主控原理图 Mec STM32F103VET6集成主控板资源分配 Mec OLED 显示内容 如何给 S ...
- 51单片机入门教程(5)——定时器中断
51单片机入门教程(5)--定时器中断 一.中断的概念 二.定时器中断 2.1 软件延时的不足 2.2 中断寄存器 2.2.1 中断允许控制寄存器 IE 2.2.2 定时器工作方式寄存器 TMOD 2 ...
- STM32复习笔记(十八) —— 高级定时器(输出比较)
STM32复习笔记(十八) -- 高级定时器(输出比较) 1.配置步骤 1)选择计数器时钟 (内部,外部,预分频器) 2)将相应的数据写入TIMx_ARR and TIMx_CCRx寄存器中 3)可设 ...
- Unity 入门笔记 - 02 - 各种动画
Unity 入门笔记 - 02 - 各种动画 前言:上一篇笔记记录了从零开始安装软件,到搭建最基本的游戏场景和角色,最后开始接触了脚本代码.对unity游戏引擎的工作方式有了基本的认知.接下来开始进一 ...
- 瑞萨e2studio(15)----外部中断定时器配置输入捕获测量频率
瑞萨e2studio.15--外部中断&定时器配置输入捕获测量频率 概述 视频教学 csdn课程 完整代码下载 样品申请 硬件准备 开发板 新建工程 工程模板 保存工程路径 芯片配置 工程模板 ...
- STM32F103C8T6制作舵机测试仪详细图文教程 | 定时器触发ADC | DMA传输 | PWM输出 | RTC实时时钟 | USART串口输出 | OLED IIC显示
自主学习STM32已有一周,先实现一个小demo,算是给自己一个动力叭,有目标的学习收获会更多.虽然本科也修了嵌入式课程,但那种走马观花式的学习,最后真正得到的知识实在寥寥无几.个人理解,学习STM3 ...
- STM8SF903K3T6定时器1输入捕获
STM8SF903K3T6定时器1输入捕获 简介 最近接了个转速仪的项目,原理是计频率.最开始是用外部中断,然后根据定时器定时一秒来取值,转速是rpm,所以还要乘以60. 因为需要反映快所以后来改用定 ...
最新文章
- Qt 小技巧之“To-Do 事项”
- redis为什么是cp_面试官:Redis怎么持久化的?如何回答持久化策略呢?
- 算法:串联所有单词的子串
- 大牛书单 | 读懂5G,改变社会
- gradle打包java项目_gradle打包java项目
- 前端学习(2949):创建webpack搭建项目
- Excel 宏工作簿 VBAProject 工程保护 - 代码不可查看
- MetaException(message:Hive Schema version 2.1.0 does not match metastore‘s schema version 1.2.0 Meta
- [codevs1378]选课
- Android开发UI之GridLayout的使用
- 正确配置Linux系统ulimit/nproc值的方法
- git 版本控制库的用法及其介绍
- 开源数据库学习资料汇总
- P2525 Uim的情人节礼物·其之壱(入门,数学)
- windows xp下 usb驱动编写
- N1刷Android TV,贫民种草指北 篇二:N1盒子:不谈刷机,只谈使用!
- oracle 查看cdb,Oracle基础操作——CDB-PDB
- PyAutoGUI库-模拟鼠标键盘操作
- 基于HTML+CSS制作静态页面【剪纸文化15页】传统文化设计题材 dreamweaver制作静态html网页设计作业作品...
- 函数(详解)——C语言
热门文章
- Python爬取动态数据
- w ndows无法启动wlan,分享windows无法启动wlan autoconfig的解决方法
- 时间轴-新年倒计时(实操java)
- linux命令设置波特率,Linux设置串口波特率等参数
- 上海理工大学高校计算机补办,上海理工大学关于2017上海市高校计算机等级考试报考通知...
- 电信3g在小米信号显示无服务器,关于小米手机电信3G信号问题的分析
- 杭电HDU 1004 Let the Balloon Rise AC代码 简单题
- 云计算虚拟化:k8s二进制Master主备集群部署
- 5、用Python编程,假设一年期定期利率为3.25%,计算一下需要过多少年,一万元的一年定期存款连本带息能翻番?
- 推荐书籍:WebRTC技术详解 从0到1构建多人视频会议系统