一、基本概念
何为定时器?
主要功能就是接收输入信号,以一定频率计数,再输出信号(触发中断)。在stm32当中,以此为基础,集成了多种功能,如:内、外时钟源选择,输入捕获,输出比较,编码器,PWM波输出等。
STM32中定时器分类:
参考:
1、https://zhuanlan.zhihu.com/p/648584916
2、https://blog.csdn.net/zs3194068129/article/details/137357092
1、常规定时器
其中,STM32F1 系列中,除了互联型的产品,共有 8个常规定时器,分为基本定时器,通用定时器和高级定时器。
-
基本定时器: TIM6 和 TIM7 是一个 16位的只能向上计数的定时器,只能定时,没有外部 IO。
-
通用定时器 :TIM2/3/4/5是一个 16位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,
每个定时器有四个外部IO。 -
高级定时器: TIM1/8是一个 16位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有8 个外部 IO。
基本原理:
-
自动重装载寄存器 ARR:是一个比较影子寄存器,用来存储程序中某个变量或寄存器的值,然后将其与其他变量或寄存器的值进行比较的寄存器。
-
溢出条件:CNT==ARR(影子寄存器中的值)、或0、或ARR-1、1;
2、内核定时器-SysTick
指SysTick系统定时器。系统定时器是一个24bit的向下递减的计数器, 计数器每计数一次的时间为1/SYSCLK,一般我们设置系统时钟SYSCLK等于72M。当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。
因为SysTick是属于CM3内核的外设,所以所有基于CM3内核的单片机都具有这个系统定时器。
系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。
二、基本定时器实验
1、实验说明
本实验利用基本定时器 TIM6/7 定时 1s,1s 时间到 LED 翻转一次。基本定时器是单片机内部的资源, 没有外部 IO,不需要接外部电路,现只需要一个 LED 即可。
基本原理
利用一个基本定时器(如TIM6),通过中断事件设置一个单位定时器(如1ms),主函数利用单位计时器实现LED灯定时翻转。
编程思路
1、配置LED灯;
2、配置定时器;
3、设定中断事件配置1ms的单位计时器;
4、主函数中利用1ms的单位计时器开始计数,当计数到1000时LED翻转一次。
2、编程过程
(1)配置LED
(2)配置定时器
定时器配置参数(以TIM6为例):
//1、TIM6时钟配置
#define BASIC_TIM TIM6//使用时钟
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd//APB1时钟启动函数
#define BASIC_TIM_CLK RCC_APB1Periph_TIM6//APB1_TIM6内部时钟CK_INT=72M
#define BASIC_TIM_IRQ TIM6_IRQn//定时器TIM6
#define BASIC_TIM_IRQHandler TIM6_IRQHandler
//2、基本定时器参数配置
#define BASIC_TIM_Prescaler 71//时钟预分频数,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M;可设置范围为0至65535,实现1至65536分频
#define BASIC_TIM_Period 1000-1//自动重装载寄存器周的值(计数值),则1s/1MHz*1000=1ms,可设置范围为0至65535;累计TIM_Period个频率后产生一个更新或者中断
定时器配置函数:
//3、基本定时器模式配置
static void BASIC_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//时钟结构体
BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);// 开启内部定时器时钟
TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;// 自动重装载寄存器的值:1000-1
TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;// 时钟预分频数:72-1
//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;// 时钟分频因子 ,基本定时器没有,不用管
//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;// 重复计数器的值,基本定时器没有,不用管
TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);// 初始化定时器
TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);// 清除计数器中断标志位
TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);// 开启计数器中断
TIM_Cmd(BASIC_TIM, ENABLE); // 使能计数器
}
(3)设定中断事件
当TIM6累计TIM_Period个频率后就会产生一个中断执行中断程序
中断配置函数:
// dym4:中断优先级配置
static void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);// 设置中断组为0
NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;// 设置中断来源:TIM6_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;// 设置主优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //配置子优先级为:3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断通道
NVIC_Init(&NVIC_InitStructure);
}
中断执行函数:
//dym5:中断执行函数
void BASIC_TIM_IRQHandler (void)
{
if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) //按本文的设置1ms触发一次
{
time++;//main函数的全局变量
TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update); //把相应的中断标志位清除掉,切记
}
}
(4)主函数计数
设置全局变量time,定时器1ms,若实现1s转换一次LED灯,则time计数到1000
volatile uint32_t time = 0; // ms 计时变量
int main(void)
{
/* led 端口配置 */
LED_GPIO_Config();
/* Time 端口配置 */
BASIC_TIM_Init();
while(1)
{
if ( time == 1000 ) /* 1000 * 1 ms = 1s 时间到 */
{
time = 0;
/* LED1 取反 */
LED1_TOGGLE;
}
}
}
3、工程代码
三、通用定时器实验
高级控制定时器和通用定时器在基本定时器的基础上引入了外部引脚,可以实现输入捕获和输出比较功能。
输入捕获:可以对输入的信号的上升沿、下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽,和测量PWM输入信号的频率和占空比这两种。
输出比较:就是通过定时器的外部引脚对外输出控制信号。有冻结、将通道X(x=1,2,3,4)设置为匹配时输出有效电平、 将通道X设置为匹配时输出无效电平、翻转、强制变为无效电平、强制变为有效电平、PWM1和PWM2这八种模式。其中,PWM模式是输出比较中的特例,使用的也最多。
实验一:常规计时
1、实验说明
本实验利用基本定时器 TIM2/3/4/5 定时 1s,1s 时间到 LED 翻转一次。基本定时器是单片机内部的资源, 没有外部 IO,不需要接外部电路,现只需要一个 LED 即可。
编程思路
1、配置LED灯;
2、配置定时器;
3、设定中断事件配置1ms的单位计时器;
4、主函数中利用1ms的单位计时器开始计数,当计数到1000时LED翻转一次。
2、编程过程
(1)配置LED
(2)配置定时器
定时器配置参数:
/**************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当需要哪个定时器的时候,只需要把下面的宏定义改成1即可
#define GENERAL_TIM2 1
#define GENERAL_TIM3 0
#define GENERAL_TIM4 0
#define GENERAL_TIM5 0
#if GENERAL_TIM2
//1、TIM6时钟配置
#define GENERAL_TIM TIM2//使用时钟
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd//APB1时钟启动函数
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM2//APB1_TIM2内部时钟CK_INT=72M
#define GENERAL_TIM_IRQ TIM2_IRQn//定时器TIM6
#define GENERAL_TIM_IRQHandler TIM2_IRQHandler
//2、基本定时器参数配置
#define GENERAL_TIM_Prescaler 71//时钟预分频数,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M;可设置范围为0至65535,实现1至65536分频
#define GENERAL_TIM_Period (1000-1)//自动重装载寄存器周的值(计数值),则1s/1MHz*1000=1ms,可设置范围为0至65535;累计TIM_Period个频率后产生一个更新或者中断
#elif GENERAL_TIM3
#define GENERAL_TIM TIM3
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM3
#define GENERAL_TIM_Period (1000-1)
#define GENERAL_TIM_Prescaler 71
#define GENERAL_TIM_IRQ TIM3_IRQn
#define GENERAL_TIM_IRQHandler TIM3_IRQHandler
#elif GENERAL_TIM4
#define GENERAL_TIM TIM4
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM4
#define GENERAL_TIM_Period (1000-1)
#define GENERAL_TIM_Prescaler 71
#define GENERAL_TIM_IRQ TIM4_IRQn
#define GENERAL_TIM_IRQHandler TIM4_IRQHandler
#elif GENERAL_TIM5
#define GENERAL_TIM TIM5
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM5
#define GENERAL_TIM_Period (1000-1)
#define GENERAL_TIM_Prescaler 71
#define GENERAL_TIM_IRQ TIM5_IRQn
#define GENERAL_TIM_IRQHandler TIM5_IRQHandler
#endif
定时器配置函数:
//3、通用定时器模式配置
static void GENERAL_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //时钟结构体
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK, ENABLE);// 开启内部定时器时钟
TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;// 自动重装载寄存器的值:1000-1
TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;// 时钟预分频数:72-1
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);// 初始化定时器
TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update);// 清除计数器中断标志位
TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE);// 开启计数器中断
TIM_Cmd(GENERAL_TIM, ENABLE); // 使能计数器
}
(3)设定中断事件
当TIM2/3/4/5累计TIM_Period个频率后就会产生一个中断执行中断程序
中断配置函数:
// dym4:中断优先级配置
static void GENERAL_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);// 设置中断组为0
NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQ;// 设置中断来源:TIM2/3/4/5_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;// 设置主优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //配置子优先级为:3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断通道
NVIC_Init(&NVIC_InitStructure);
}
中断执行函数:
//dym5:中断执行函数
void GENERAL_TIM_IRQHandler(void)
{
if ( TIM_GetITStatus( GENERAL_TIM, TIM_IT_Update) != RESET ) //按本文的设置1ms触发一次
{
time++;//main函数的全局变量
TIM_ClearITPendingBit(GENERAL_TIM, TIM_FLAG_Update); //把相应的中断标志位清除掉,切记
}
}
(4)主函数计数
设置全局变量time,定时器1ms,若实现1s转换一次LED灯,则time计数到1000
volatile uint32_t time = 0; // ms 计时变量
int main(void)
{
/* led 端口配置 */
LED_GPIO_Config();
/* Time 端口配置 */
GENERAL_TIM_Init();
while(1)
{
if ( time == 1000 ) /* 1000 * 1 ms = 1s 时间到 */
{
time = 0;
/* LED1 取反 */
LED1_TOGGLE;
}
}
}
3、工程代码
实验二:输出比较-PWM输出
注意:可以先学习高级定时器相应功能,然后做减法,通用定时器没有互补通道。
相关引脚配置:以TIM3为例做实验
1、实验说明
本实验利用通用定时器TIM3,输出四路占空比不同的PWM信号。
编程思路
1、配置GPIO(四路输出);
2、配置通用定时器;
3、配置输出比较;
(1)配置GPIO(四路输出)
配置参数:
// TIM3 输出比较通道1
#define GENERAL_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH1_PORT GPIOA
#define GENERAL_TIM_CH1_PIN GPIO_Pin_6
// TIM3 输出比较通道2
#define GENERAL_TIM_CH2_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH2_PORT GPIOA
#define GENERAL_TIM_CH2_PIN GPIO_Pin_7
// TIM3 输出比较通道3
#define GENERAL_TIM_CH3_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH3_PORT GPIOB
#define GENERAL_TIM_CH3_PIN GPIO_Pin_0
// TIM3 输出比较通道4
#define GENERAL_TIM_CH4_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH4_PORT GPIOB
#define GENERAL_TIM_CH4_PIN GPIO_Pin_1
初始化函数
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端口工作在复用模式下,并且输出复用模式为推挽输出。 在这种模式下,GPIO端口可以被复用为其他外设的输入或输出口,如定时器、串口、SPI等。
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);
}
(2)配置通用定时器
参数配置
/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM3
#define GENERAL_TIM TIM3
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd //通用定时器挂载到APB1,高级定时器挂载到APB2
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM3
// PWM 信号的频率 F = TIM_CLK:72M/{(ARR+1)*(PSC+1)},F倒数是周期
#define GENERAL_TIM_Period 9 //ARR,周期
#define GENERAL_TIM_Prescaler 71 //PSC,分频
#define CCR1_Val 5;//配置四种占空比,参考为GENERAL_TIM_Period=9
#define CCR2_Val 4;
#define CCR3_Val 3;
#define CCR4_Val 2;
初始化函数
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);
...
(3)配置输出比较
...
/*--------------------输出比较结构体初始化-------------------*/
// 占空比配置
// 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);//通道1为OC1
TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 2
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);//通道2为OC2
TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 3
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);//通道3为OC3
TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 4
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);//通道4为OC4
TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
(4)主函数
主函数调用就直接在四个通道对应引脚连接示波器得到不同占空比的PWM波
int main(void)
{
/* led 端口配置 */
LED_GPIO_Config();
/* 定时器初始化 */
GENERAL_TIM_Init();
while(1)
{
}
}
void GENERAL_TIM_Init(void)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config();
}
3、工程代码
四、高级定时器实验
高级控制定时器(TIM1和TIM8)和通用定时器在基本定时器的基础上引入了外部引脚,可以实现输入捕获和输出比较功能。
高级控制定时器比通用定时器增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能都是针对工业电机控制方面,本教程不单独说明,主要介绍常用的输入捕获和输出比较功能。
实验一:常规计时
1、实验说明
本实验利用基本定时器 TIM1/8 定时 1s,1s 时间到 LED 翻转一次。基本定时器是单片机内部的资源, 没有外部 IO,不需要接外部电路,现只需要一个 LED 即可。
编程思路
1、配置LED灯;
2、配置定时器;
3、设定中断事件配置1ms的单位计时器;
4、主函数中利用1ms的单位计时器开始计数,当计数到1000时LED翻转一次。
2、编程过程
(1)配置LED
(2)配置定时器
定时器配置参数:
/********************高级定时器TIM参数定义,只限TIM1、8************/
#define ADVANCE_TIM1 // 如果使用TIM8,注释掉这个宏即可
#ifdef ADVANCE_TIM1 // 使用高级定时器TIM1
//1、TIM1时钟配置
#define ADVANCE_TIM TIM1//使用时钟
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd//APB1时钟启动函数
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM1//APB1_TIM2内部时钟CK_INT=72M
#define ADVANCE_TIM_IRQ TIM1_UP_IRQn//定时器TIM1
#define ADVANCE_TIM_IRQHandler TIM1_UP_IRQHandler
//2、基本定时器参数配置
#define ADVANCE_TIM_Prescaler 71//时钟预分频数,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M;可设置范围为0至65535,实现1至65536分频
#define ADVANCE_TIM_Period (1000-1)//自动重装载寄存器周的值(计数值),则1s/1MHz*1000=1ms,可设置范围为0至65535;累计TIM_Period个频率后产生一个更新或者中断
#else // 使用高级定时器TIM8
#define ADVANCE_TIM TIM8
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM8
#define ADVANCE_TIM_Period (1000-1)
#define ADVANCE_TIM_Prescaler 71
#define ADVANCE_TIM_IRQ TIM8_UP_IRQn
#define ADVANCE_TIM_IRQHandler TIM8_UP_IRQHandler
#endif
定时器配置函数:
//3、通用定时器模式配置
static void ADVANCE_TIM_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //时钟结构体
GENERAL_TIM_APBxClock_FUN(ADVANCE_TIM_CLK, ENABLE);// 开启内部定时器时钟
TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_Period;// 自动重装载寄存器的值:1000-1
TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM_Prescaler;// 时钟预分频数:72-1
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;// 时钟分频因子 ,没用到不用管
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;// 重复计数器的值,没用到不用管
TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);// 初始化定时器
TIM_ClearFlag(ADVANCE_TIM, TIM_FLAG_Update);// 清除计数器中断标志位
TIM_ITConfig(ADVANCE_TIM,TIM_IT_Update,ENABLE);// 开启计数器中断
TIM_Cmd(ADVANCE_TIM, ENABLE); // 使能计数器
}
(3)设定中断事件
当TIM1/8累计TIM_Period个频率后就会产生一个中断执行中断程序
中断配置函数:
// dym4:中断优先级配置
static void GENERAL_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);// 设置中断组为0
NVIC_InitStructure.NVIC_IRQChannel = ADVANCE_TIM_IRQ;// 设置中断来源:TIM1/8_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;// 设置主优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //配置子优先级为:3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断通道
NVIC_Init(&NVIC_InitStructure);
}
中断执行函数:
//dym5:中断执行函数
void ADVANCE_TIM_IRQHandler(void)
{
if ( TIM_GetITStatus( ADVANCE_TIM, TIM_IT_Update) != RESET ) //按本文的设置1ms触发一次
{
time++;//main函数的全局变量
TIM_ClearITPendingBit(ADVANCE_TIM, TIM_FLAG_Update); //把相应的中断标志位清除掉,切记
}
}
(4)主函数计数
设置全局变量time,定时器1ms,若实现1s转换一次LED灯,则time计数到1000
volatile uint32_t time = 0; // ms 计时变量
int main(void)
{
/* led 端口配置 */
LED_GPIO_Config();
/* Time 端口配置 */
ADVANCE_TIM_Init();
while(1)
{
if ( time == 1000 ) /* 1000 * 1 ms = 1s 时间到 */
{
time = 0;
/* LED1 取反 */
LED1_TOGGLE;
}
}
}
3、工程代码
实验二:输出比较-PWM输出
1、实验说明
本实验利用高级定时器TIM1,在主输出通道输出PWM波型,在互补通道输出与主通道互补的的波形,并且添加了断路和死区功能。
断路功能:就是电机控制的刹车功能,断路源可以是时钟故障事件,由内部复位时钟控制器中的时钟安全系统(CSS)生成,也可以是外部断路输入IO。
死区时间:为避免硬件电路短路,设置的等待时间就称为死区时间。
注:
1、实验使用高级定时器TIM1,引脚PA8为主通道,引脚PB13为互补通道。
2、选择PB12引脚为中断源引脚(TIM1_BKIN),程序我们设置该引脚为高电平有效,当BKIN引脚被置高低电平的时候,两路互补的PWM输出就被停止,就好像是刹车一样。
(指南者板子上PB12和PB13被复用了一些功能会影响,所以产生的波形有毛刺是正常的)
3、在指南者开发板里面,PA8通过一个调帽默认连接了蜂鸣器,如果调帽不拔掉的话,PA8输出的PWM信号会让蜂鸣器响。
编程思路
1、配置GPIO(主输出、互补输出、中断通道);
2、配置高级定时器;
3、配置输出比较;
4、配置刹车和死区时间。
2、编程过程
(1)配置GPIO(主输出、互补输出、中断通道)
参数定义
// TIM1 输出比较通道PA8
#define ADVANCE_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define ADVANCE_TIM_CH1_PORT GPIOA
#define ADVANCE_TIM_CH1_PIN GPIO_Pin_8
// TIM1 输出比较通道的互补通道PB13
#define ADVANCE_TIM_CH1N_GPIO_CLK RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_CH1N_PORT GPIOB
#define ADVANCE_TIM_CH1N_PIN GPIO_Pin_13
// TIM1 输出比较通道的刹车通道PB12
#define ADVANCE_TIM_BKIN_GPIO_CLK RCC_APB2Periph_GPIOB
#define ADVANCE_TIM_BKIN_PORT GPIOB
#define ADVANCE_TIM_BKIN_PIN GPIO_Pin_12
初始化函数
static void ADVANCE_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输出比较通道 GPIO 初始化
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//GPIO端口工作在复用模式下,并且输出复用模式为推挽输出。 在这种模式下,GPIO端口可以被复用为其他外设的输入或输出口,如定时器、串口、SPI等。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//输出驱动电路的响应频率,频率大于输出信号频率,输出信号才不会失真
GPIO_Init(ADVANCE_TIM_CH1_PORT, &GPIO_InitStructure);
// 输出比较通道互补通道 GPIO 初始化
RCC_APB2PeriphClockCmd(ADVANCE_TIM_CH1N_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_CH1N_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_CH1N_PORT, &GPIO_InitStructure);
// 输出比较通道刹车通道 GPIO 初始化
RCC_APB2PeriphClockCmd(ADVANCE_TIM_BKIN_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = ADVANCE_TIM_BKIN_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ADVANCE_TIM_BKIN_PORT, &GPIO_InitStructure);
// BKIN引脚默认先输出低电平(后面配置刹车的时候高电平有效,所以先初始化为低电平)
GPIO_ResetBits(ADVANCE_TIM_BKIN_PORT,ADVANCE_TIM_BKIN_PIN);
}
(2)配置高级定时器
参数定义
#define ADVANCE_TIM TIM1
#define ADVANCE_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd//通用定时器挂载到APB1,高级定时器挂载到APB2
#define ADVANCE_TIM_CLK RCC_APB2Periph_TIM1
// PWM 信号的频率 F = TIM_CLK:72M/{(ARR+1)*(PSC+1)},F倒数是周期
#define ADVANCE_TIM_PSC (9-1) //PSC,分频因子
#define ADVANCE_TIM_PERIOD (8-1) //ARR,周期(分频后多少个单位为一个周期)
#define ADVANCE_TIM_PULSE 4 //占空比(占周期的多少)
#define ADVANCE_TIM_IRQ TIM1_UP_IRQn //向上计数
#define ADVANCE_TIM_IRQHandler TIM1_UP_IRQHandler
初始化函数
static void ADVANCE_TIM_Mode_Config(void)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_PERIOD;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM_PSC;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //系统给的死区时间种类TIM_CKD_DIV1
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);
...
(3)配置输出比较
...
/*--------------------输出比较结构体初始化-------------------*/
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置为PWM模式1(常用)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 互补输出使能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
// 设置占空比大小
TIM_OCInitStructure.TIM_Pulse = ADVANCE_TIM_PULSE;
// 输出通道电平极性配置
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//高电平有效
// 互补输出通道电平极性配置
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;//高电平有效
// 输出通道空闲电平极性配置:空闲电平是刹车之后输出的状态,不用刹车的时候这俩不用配置
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;//高电平
// 互补输出通道空闲电平极性配置
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;//低电平
//用参数初始化寄存器,用的OC1就是通道1,互补通道不用单独初始化
TIM_OC1Init(ADVANCE_TIM, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(ADVANCE_TIM, TIM_OCPreload_Enable);//自动重装载
...
(4)配置刹车和死区时间
...
/*-------------------刹车和死区结构体初始化:!!!为两个需要重点配的参数-------------------*/
// 有关刹车和死区结构体的成员具体可参考BDTR寄存器的描述,不需要关注
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;
TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1;
// 输出比较信号死区时间配置,具体如何计算可参考 BDTR:UTG[7:0]的描述
// 这里配置的死区时间为152ns
TIM_BDTRInitStructure.TIM_DeadTime = 11;//!!!11二进制就是0000 1011,对应死区设置的计算方法就是11s/72M=152ns
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable;//刹车使能
// 当BKIN引脚检测到高电平的时候,输出比较信号被禁止,就好像是刹车一样
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;//!!!配置当输入为高电平时刹车有效
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(ADVANCE_TIM, &TIM_BDTRInitStructure);
// 使能计数器
TIM_Cmd(ADVANCE_TIM, ENABLE);
// 主输出使能,当使用的是通用定时器时,这句不需要
TIM_CtrlPWMOutputs(ADVANCE_TIM, ENABLE);
}
(5)主函数调用
主函数调用就直接在主输出通道、互补通道连接示波器得到PWM波,并可以通过刹车通道控制刹车。
int main(void)
{
/* 高级定时器初始化 */
ADVANCE_TIM_Init();
while(1)
{
}
}
void ADVANCE_TIM_Init(void)
{
ADVANCE_TIM_GPIO_Config();
ADVANCE_TIM_Mode_Config();
}
3、工程代码
XXXXXXX临时,3-测量PWM信号的频率和占空比
五、内核定时器实验
1、实验说明
本实验利用SysTick产生1s的时基,LED以1s的频率闪烁。
编程思路
1、配置LED灯;
2、构造时延函数:配置SysTick定时器为单位计时器(1ms);
4、主函数中利用1ms的单位计时器开始计数,当计数到1000时LED变换一次。
2、编程过程
(1)配置LED
(2)构造时延函数
定时器配置参数,以配置1ms的单位计时器为例:
//dym1:ms延时函数
void SysTick_Delay_Ms( __IO uint32_t ms)
{
uint32_t i;
//SystemCoreClock为72M,1M=10^6,
/* SystemCoreClock 1s中断一次
* SystemCoreClock / 1000 1ms中断一次
* SystemCoreClock / 100000 10us中断一次
* SystemCoreClock / 1000000 1us中断一次
*/
//1、配置SysTick(最小单位)并开始启动SysTick
SysTick_Config(SystemCoreClock/1000);
//2、形参ms循环了多少次就是多少ms
for(i=0;i<ms;i++)
{
// 当计数器的值减小到0的时候,CRTL寄存器的位16会置1,为0说明还在计时中
// 当置1时,读取该位会清0
while( !((SysTick->CTRL)&(1<<16)) );
}
//3、关闭SysTick定时器
SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
}
(3)主函数计数
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
//利用时延函数循环控制灯
for(;;)
{
LED1( ON );
SysTick_Delay_Ms( 1000 );//1000*1ms=1s
LED1( OFF );
LED2( ON );
SysTick_Delay_Ms( 1000 );
LED2( OFF );
LED3( ON );
SysTick_Delay_Ms( 1000 );
LED3( OFF );
}
}