-
2019-07-06 15:54:43
1.如何使用8051单片机内部的定时器timer0?
#include<STC89C5xRC.h>
void delay(int n)
{
int i;
TMOD=0x01;//16bits
for(i=0;i<n;i++)
{
TH0=0x3C;
TL0=0xB0;
//12MHZ -> 1MHZ -> 0.05s=5*10^-2s -> (5*10^-2)/(1*10^-6)=5*10^4
//65536-50000=15536=3CB0H
TF0=0;
TR0=1;//start the internal timer
while(TF0==0);//wait for the end flag during which delays 0.05 second.
TR0=0;//stop the internal timer.
}
}
main()
{
if(P27)
{
P20=1;
delay(10);
P20=0;
delay(10);
}
}
我希望得到0.5s的定时。这里晶振的频率为12MHz,所以定时器自增1的频率为1MHz,即每1*10^-6s就增1。对于0.5s,其中包含(5*10^-1)/(1*10^-6)=5*10^5次自增1,但是这对于16bits的定时器显然是不能一次完成的,所以我将0.5s分成10个0.05s:这样一来,0.05s其中包含5*10^4次自增1,所以我将timer0的初值置为65536-5*10^4=15536=3CB0H。接着,将0x3C写入timer0的高8位,将0xB0写入timer0的低8位:TH0=0x3C,TL0=0xB0。当然,我们还需要考虑timer0溢出时的处理。
当timer0的值从65535再增1后,这时timer0的值会从65535变为0:溢出标志TF0也会变为1,此时标志着一个定时周期的结束。
在一个定时周期结束后,需要再将TF0置为0,将TH0置为0x3C,将TL0置为0xB0,以重复刚才一个周期的动作。
另外,还需要考虑何时开启定时器。当进入while(TF0==0) ; 这条语句前,置TR0=1,以开启timer0;当刚走出while(TF0==0) ; 这条语句时,置TR0=0,以关闭timer0。
最后,值得注意的是,由于我希望使用timer0的16bits计数模式,且脉冲来自晶振,所以要将TMOD置为0x01。
2.如何使用8051单片机内部的定时器timer1?
#include <STC89C5xRC.H>
void delay(int n)
{
int i;
TMOD = 0x10;
for(i=0; i<n; i++)
{
TH1 = 0x3C;
TL1 = 0xB0;
TF1 = 0;
TR1 = 1;
while(TF1 == 0) ;
TR1 = 0;
}
}
main()
{
P27 = 1;
delay(10);
P27 = 0;
delay(10);
}
和对timer0的使用类似,只是将timer0的各标志换为timer1的标志即可。
更多相关内容 -
STM32开发项目:硬件定时器(Timer)的配置与使用
2020-10-02 18:41:18日期 作者 版本 说明 ...目录简要介绍常用配置设置更新中断输出PWM脉冲捕获输出带死区控制的互补PWM使用指南 简要介绍 常用配置 设置更新中断 输出PWM 脉冲捕获 输出带死区控制的互补PWM 使用指南 ...日期 作者 版本 说明 2020.09.26 Tao V0.0 撰写中 2020.09.26 Tao V1.0 完善了框架与内容,发布第一版 2020.12.06 Tao V1.1 增加了输入捕获的使用 2020.12.07 Tao V1.2 增加了BLDC电机速度闭环控制的项目实例 目录
简要介绍
以STM32F103为例,对定时器外设做一个简单的介绍。STM32F103内部共有 8 个定时器,分为基本定时器,通用定时器和高级定时器。基本定时器 TIM6 和 TIM7 是一个 16 位的只能向上计数的定时器,只能定时,没有外部 IO。通用定时器 TIM2/3/4/5 是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,每个定时器有四个外部 IO。高级定时器 TIM1/8是一个 16 位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,还可以有三相电机互补输出信号,每个定时器有 8 个外部 IO。
常用配置
设置更新中断
使用Timer产生更新中断时,应当注意通用定时器与高级定时器的几点区别:
- 时钟配置上存在区别(通用定时器、基本定时器与高级定时器的总线不同);
- 基础配置结构体的部分成员仅对高级定时器与通用定时器有效(例如
TIM_RepetitionCounter
,TIM_ClockDivision
,TIM_CounterMode
); - 高级定时器的中断服务函数名(
void TIM1_UP_IRQHandler()
)与通用定时器、基本定时器的中断服务函数名(void TIM2_IRQHandler()
)不同;
通用定时器(以Timer2为例)
- 外设配置
#define TIM2_Prescaler 7200 //10KHz,100us #define TIM2_PWM_FREQUENCY 2 #define TIM2_OC2_PWM_DutyCycle 50 //50% #define TIM2_Period (72000000.0/TIM2_Prescaler/TIM2_PWM_FREQUENCY) #define TIM2_OC2_Pulse (TIM2_OC2_PWM_DutyCycle*TIM2_Period/100) void Timer_Config() { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_DeInit(TIM2); TIM_TimeBaseInitStructure.TIM_Prescaler = TIM2_Prescaler - 1; TIM_TimeBaseInitStructure.TIM_Period = TIM2_Period - 1; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); TIM_ClearFlag(TIM2, TIM_FLAG_Update); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_ARRPreloadConfig(TIM2, ENABLE); }
- 设置中断优先级
void NVIC_Config() { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
- 中断服务函数
void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET) { Timer2Updated(); TIM_ClearFlag(TIM2,TIM_FLAG_Update); TIM_ClearITPendingBit(TIM2,TIM_IT_Update); } }
- 开启定时器
TIM_Cmd(TIM2, ENABLE);
高级定时器(以Timer1为例)
- 外设配置。
#define TIM1_Prescaler 7200 //10KHz,100us #define TIM1_PWM_FREQUENCY 2 #define TIM1_OC1_PWM_DutyCycle 50 //50% #define TIM1_Period (72000000.0/TIM1_Prescaler/TIM1_PWM_FREQUENCY) #define TIM1_OC1_Pulse (TIM1_OC1_PWM_DutyCycle*TIM1_Period/100) void Timer_Config() { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_DeInit(TIM1); TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1; TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure); TIM_ClearFlag(TIM1, TIM_FLAG_Update); TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); TIM_ARRPreloadConfig(TIM1, ENABLE); }
- 设置中断优先级
void NVIC_Config() { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
- 中断服务函数。注意高级定时器服务函数名称与通用定时器服务函数的区别。
void TIM1_UP_IRQHandler(void) { if(TIM_GetITStatus(TIM1,TIM_IT_Update)!=RESET) { Timer1Updated(); TIM_ClearFlag(TIM1,TIM_FLAG_Update); TIM_ClearITPendingBit(TIM1,TIM_IT_Update); } }
- 开启定时器
TIM_Cmd(TIM1, ENABLE);
基本定时器(以Timer6为例)
- 外设配置
#define TIM6_Prescaler 7200 //10KHz,100us #define TIM6_PWM_FREQUENCY 2 #define TIM6_OC1_PWM_DutyCycle 50 //50% #define TIM6_Period (72000000.0/TIM6_Prescaler/TIM6_PWM_FREQUENCY) #define TIM6_OC1_Pulse (TIM6_OC1_PWM_DutyCycle*TIM6_Period/100) void Timer_Config() { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_DeInit(TIM6); TIM_TimeBaseInitStructure.TIM_Prescaler = TIM6_Prescaler - 1; TIM_TimeBaseInitStructure.TIM_Period = TIM6_Period - 1; TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStructure); TIM_ClearFlag(TIM6, TIM_FLAG_Update); TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); TIM_ARRPreloadConfig(TIM6, ENABLE); }
- 设置中断优先级
void NVIC_Config() { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStructure.NVIC_IRQChannel = TIM6_UP_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
- 中断服务函数。注意高级定时器服务函数名称与通用定时器服务函数的区别。
void TIM6_IRQHandler(void) { if(TIM_GetITStatus(TIM6,TIM_IT_Update)!=RESET) { Timer6Updated(); TIM_ClearFlag(TIM6,TIM_FLAG_Update); TIM_ClearITPendingBit(TIM6,TIM_IT_Update); } }
- 开启定时器
TIM_Cmd(TIM6, ENABLE);
输出PWM
使用Timer输出PWM时,应当注意通用定时器与高级定时器的几点区别:
- 时钟配置上存在区别(通用定时器与高级定时器的总线不同)
- 基础配置结构体的部分成员仅对高级定时器有效(例如
TIM_RepetitionCounter
) - 开启/关闭方波的方法不一样(高级定时器:
TIM_CtrlPWMOutputs(TIM1, ENABLE)
, 通用定时器:TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Enable)
) - 使用Timer输出PWM时,无需打开中断。当然也可以同时打开定时器的PWM输出与更新中断。
通用定时器(以Timer2为例)
- 外设配置
#define TIM2_Prescaler 720 #define TIM2_PWM_FREQUENCY 1000 #define TIM2_OC1_PWM_DutyCycle 50 //50% #define TIM2_Period (72000000.0/TIM2_Prescaler/TIM2_PWM_FREQUENCY) #define TIM2_OC1_Pulse (TIM2_OC1_PWM_DutyCycle*TIM2_Period/100) void Timer_Config() { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_DeInit(TIM2); TIM_TimeBaseInitStructure.TIM_Prescaler = TIM2_Prescaler - 1; TIM_TimeBaseInitStructure.TIM_Period = TIM2_Period - 1; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); TIM_ARRPreloadConfig(TIM2, ENABLE); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse = TIM2_OC1_Pulse; TIM_OC1Init(TIM2, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); }
- 方波端口配置
void GPIO_Config() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOE, &GPIO_InitStructure); }
- PWM常用操作
//开启定时器 TIM_Cmd(TIM2, ENABLE); //开启PWM输出 TIM_CCxCmd(TIM2, TIM_Channel_1, TIM_CCx_Enable); //设置占空比(dutyCycle: 0~1) TIM_SetCompare1(TIM2, dutyCycle*(TIM2->ARR + 1));
高级定时器(以Timer1为例)
- 外设配置
#define TIM1_Prescaler 720 #define TIM1_PWM_FREQUENCY 1000 #define TIM1_OC1_PWM_DutyCycle 50 //50% #define TIM1_Period (72000000.0/TIM1_Prescaler/TIM1_PWM_FREQUENCY) #define TIM1_OC1_Pulse (TIM1_OC1_PWM_DutyCycle*TIM1_Period/100) void Timer_Config() { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_DeInit(TIM1); TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1; TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse = TIM1_OC1_Pulse; TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); }
- 方波端口配置
void GPIO_Config() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOE, &GPIO_InitStructure); }
- PWM常用操作
//开启定时器 TIM_Cmd(TIM1, ENABLE); //开启PWM输出 TIM_CtrlPWMOutputs(TIM1, ENABLE); //设置占空比(dutyCycle: 0~1) TIM_SetCompare1(TIM1, dutyCycle*(TIM1->ARR + 1));
输入捕获
通用定时器
通用定时器与高级定时器的输入捕获配置流程基本相同,它们的区别在于:高级定时器的CC中断与Update中断是不同的中断服务函数名(
TIM1_UP_IRQHandler
,TIM1_CC_IRQHandler
),而通用定时器的CC中断与Update中断共享一个中断服务函数名,需要在中断服务函数中进行中断类型的判断。if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET) //捕获到 更新 中断 { } if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)//捕获 1 发生捕获事件 { }
高级定时器
- 外设配置
解释一下滤波器的作用:
数字滤波器由一个事件计数器组成,它记录到N个事件后会产生一个输出的跳变。也就是说连续N次采样,如果都是高电平,则说明这是一个有效的触发,就会进入输入捕捉中断(如果设置了的话)。这样就可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤波的作用。
void Timer_Config() { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_DeInit(TIM1); TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1; TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure); // TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_ICInitStructure.TIM_Channel = TIM_Channel_4; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x08; TIM_ICInit(TIM1, &TIM_ICInitStructure); TIM_ClearFlag(TIM1, TIM_FLAG_CC4 | TIM_IT_Update); TIM_ITConfig(TIM1, TIM_IT_CC4| TIM_IT_Update, ENABLE); }
- 端口配置
以Timer1 Ch4部分重映射到PE14为例,GPIO应当配置为输入模式:
//Timer1部分重映射 TIM1_CH3->PE13 TIM1_CH4->PE14 GPIO_PinRemapConfig(GPIO_FullRemap_TIM1, ENABLE); GPIO_ConfigPort('E', 14, GPIO_Mode_IPU, 0);
- 中断配置
配置NVIC的中断优先级:
void NVIC_Config() { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 12; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
编写中断服务函数:
void TIM1_UP_IRQHandler(void) { if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) { /*Something to do when update IRQ*/ //TIM_ClearFlag(TIM1,TIM_FLAG_Update); TIM_ClearITPendingBit(TIM1,TIM_IT_Update); } } void TIM1_CC_IRQHandler(void) { if(TIM_GetITStatus(TIM1, TIM_IT_CC4) != RESET) { /*Something to do when CC IRQ*/ ///Use function: uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx) to get CC value. //TIM_ClearFlag(TIM1,TIM_FLAG_CC4); TIM_ClearITPendingBit(TIM1,TIM_IT_CC4); } }
输出带死区控制的互补PWM
高级定时器
- 外设配置
#define TIM1_Prescaler 720 #define TIM1_PWM_FREQUENCY 1000 #define TIM1_OC1_PWM_DutyCycle 50 //50% #define TIM1_Period (72000000.0/TIM1_Prescaler/TIM1_PWM_FREQUENCY) #define TIM1_OC1_Pulse (TIM1_OC1_PWM_DutyCycle*TIM1_Period/100) void Timer_Config() { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_BDTRInitTypeDef TIM_BDTRInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_DeInit(TIM1); TIM_TimeBaseInitStructure.TIM_Prescaler = TIM1_Prescaler - 1; TIM_TimeBaseInitStructure.TIM_Period = TIM1_Period - 1; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure); TIM_ARRPreloadConfig(TIM1, ENABLE); //启用ARR的影子寄存器(直到产生更新事件才更改设置) /* Automatic Output enable, Break, dead time and lock configuration*/ TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable; //运行模式下输出 TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable; //空闲模式下输出选择 TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF; //锁定设置,锁定级别1 // TIM_BDTRInitStructure.TIM_DeadTime = 0xAC; //死区时间3us TIM_BDTRInitStructure.TIM_DeadTime = 72; //死区时间1us TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; //刹车功能使能 TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_Low; //刹车输入极性,即刹车控制引脚接GND时,PWM停止 TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; //自动输出使能 TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure); /* 刹车控制引脚为TIM1_BKIN pin(PB.12),将PB12接GND,channel和其互补通道,都变为刹车后的电平,具体为0还是1,要看如下两个设置: .TIM_OCIdleState = TIM_OCIdleState_Reset; //刹车之后,PWM通道变为0 .TIM_OCNIdleState = TIM_OCNIdleState_Reset; //刹车之后,PWM互补通道变为0 注意:如果没必要,还是不要开启刹车功能,因为会对PWM产生影响,特别是当PB12悬空时,波形将会有很大的波动。 这里不打开刹车功能,即.TIM_Break = TIM_Break_Disable; */ /* 正确的deadtime的计算方法(经理论与示波器测试成功) TIM_BDTRInitStructure.TIM_DeadTime=255 //这句设定的就是寄存器TIMx_BDTR的后8位,即DTG[7:0],所以最大值为255 从下面的代码中的“第五步”中,实际上就相当于TIM1->BDTR=0x71FF; 查看"STM32中文参考手册2009.pdf"的TIMx_BDTR(第248页),列寄存器TIMx_BDTR的后8位如下: 位7:0 UTG[7:0]: 死区发生器设置 (Dead-time generator setup) 这些位定义了插入互补输出之间的死区持续时间。假设DT表示其持续时间: DTG[7:5]=0xx => DT=DTG[7:0] × Tdtg, Tdtg = Tdts; DTG[7:5]=10x => DT=(64+DTG[5:0]) × Tdtg, Tdtg = 2 × Tdts; DTG[7:5]=110 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 8 × Tdts; DTG[7:5]=111 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 16× Tdts; Tdts为系统时钟周期时长,Tdtg为死区时间计算步长。主要思想就是把DTG的八位,掰成两半用。一半决定步长,另一半是与步长相乘的乘数,乘数可以自行设定,步长*乘数=死区时间。 在72M的定时器时钟下,TDTS = 1/72M = 13.89ns: 项目 情况1 情况2 情况3 情况4 步长位置 DTG[7] DTG[7:6] DTG[7:5] DTG[7:5] 步长值(二进制) 0xx 10x 110 111 步长是周期几倍 1 2 8 16 乘数位置 DTG[6:0] DTG[5:0] DTG[4:0] DTG[4:0] 乘数最大值 127 64+63 63+31 32+31 乘数范围 0~127 64~127 32~63 34~63 等价几倍周期 0~127 128~254 256~504 512~1008 周期13.89ns时,死区范围ns 0~1764 1778~3528 3556~7000 7112~14001 示例: 需要3us的死区时间,那么属于情况2,DTG[7:6] = 0b10,步长=27.78, 需要的乘数 = 3000÷27.78-64=108-64=44=0b101100,DTG[7:0]=0b10101100=0xAC */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //CH2 PWM2模式 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能 TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; //比较互补输出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性 TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; //互补输出极性 TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset; //指定空闲状态下的TIM输出比较的引脚状态。 TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; //指定空闲状态下的TIM互补输出比较的引脚状态。 TIM_OCInitStructure.TIM_Pulse = TIM1_OC1_Pulse; TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据指定的参数初始化外设TIMx TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //启用CCR1寄存器的影子寄存器(直到产生更新事件才更改设置) //OCx输出信号与参考信号相同,只是它的上升沿相对参考信号的上升沿有一个延迟 //OCxN输出信号与参考信号相同,只是它的上升沿相对参考信号的下降沿有一个延迟 }
- 方波端口配置
void GPIO_Config() { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //Timer1 Ch1 - PA8 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); //Timer1 Ch1N - PB13 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); }
- PWM常用操作
//开启定时器 TIM_Cmd(TIM1, ENABLE); //开启PWM输出 TIM_CtrlPWMOutputs(TIM1, ENABLE); //设置占空比(dutyCycle: 0~1) TIM_SetCompare1(TIM1, dutyCycle*(TIM1->ARR + 1));
使用指南
实用Timer设置函数(以Timer时钟72M为例)
设置PWM频率
/** * @brief Set the frequency of PWM * 需要特别注意的是,考虑到PWM占空比设置精度,本函数实现的PWM调节最大频率为1MHz。 * 对于固定频率的PWM,可以参考本函数的实现方法, * 选择合适的分频系数手动设置分频系数,以保证最大的PWM占空比调整精度。 * * 注意1: 在STM32F407中,高级定时器的时钟频率与通用定时器的时钟频率是不一样的, * 需要修改本函数(增加对定时器类型的判断)才能正确的设置PWM频率。 * * 注意2: 只有高级定时器与通用定时器才能产生PWM。 * @param TIMx: 高级定时器与通用定时器TIM 1, 2, 3, 4, 5 and 8(在STM32F103中,定时器的时钟频率可以都设置为72MHz) * @param freq: 0.2~1MHz */ void User_PWM_SetFrequency(TIM_TypeDef *TIMx, float freq) { //duty cycle of 4 channels float dutyCycle1; float dutyCycle2; float dutyCycle3; float dutyCycle4; //Range of frequency is 0.2Hz to 1MHz. //参数不正确,直接返回 if (freq > 1000000) return; if (freq < 0.2) return; //根据频率设置Timer的分频系数 //0.2 <= frequency < 100, psc = 7200, f = 10KHz //占空比设置精度为1/50000~1/100 if(freq < 100) { TIMx->PSC = 7200 - 1; } //100 <= frequency < 1k, psc = 720, f = 100KHz //占空比设置精度为1/1000~1/100 else if(freq < 1000) { TIMx->PSC = 720 - 1; } //1K <= frequency < 10K, psc = 72, f = 1MHz //占空比设置精度为1/1000~1/100 else if(freq < 10000) { TIMx->PSC = 72 - 1; } //10K <= frequency < 100K, psc = 6, f = 12MHz //占空比设置精度为1/1200~1/120 else if(freq < 100000) { TIMx->PSC = 6 - 1; } //100K <= frequency <= 1M, psc = 1, f = 72MHz //占空比设置精度为1/720~1/72 else if(freq <= 1000000) { TIMx->PSC = 1 - 1; } else { } //Get the duty cycle of the pwm before changing the frequency. dutyCycle1 = (float) TIMx->CCR1 / (TIMx->ARR + 1); dutyCycle2 = (float) TIMx->CCR2 / (TIMx->ARR + 1); dutyCycle3 = (float) TIMx->CCR3 / (TIMx->ARR + 1); dutyCycle4 = (float) TIMx->CCR4 / (TIMx->ARR + 1); //Set the update frequency of the timer. TIMx->ARR = 72000000.0 / (TIMx->PSC + 1) / freq - 1; //Set the duty cycle of the timer. TIMx->CCR1 = dutyCycle1 * (TIMx->ARR + 1); TIMx->CCR2 = dutyCycle2 * (TIMx->ARR + 1); TIMx->CCR3 = dutyCycle3 * (TIMx->ARR + 1); TIMx->CCR4 = dutyCycle4 * (TIMx->ARR + 1); }
设置PWM占空比
/** * @brief Set the duty cycle of PWM * @param TIMx: 高级定时器与通用定时器TIM 1, 2, 3, 4, 5 and 8 * @param channel: 0,1,2,3, 高级定时器与通用定时器有4个PWM通道 * @param dutyCycle: 0~1 (需要注意PWM频率不同时,可以设置的占空比精度是不一样的) */ void User_PWM_SetDutyCycle(TIM_TypeDef *TIMx, uint8_t channel, float dutyCycle) { //Range of duty cycle is 0 to 1. //参数不正确,直接返回 if (dutyCycle > 1) return; if (dutyCycle < 0) return; //Set the duty cycle of the PWM. switch (channel) { case 0: //TIM_SetCompare1(TIMx, dutycycle * (TIMx->ARR + 1)); TIMx->CCR1 = dutyCycle * (TIMx->ARR + 1); break; case 1: //TIM_SetCompare2(TIMx, dutycycle * (TIMx->ARR + 1)); TIMx->CCR2 = dutyCycle * (TIMx->ARR + 1); break; case 2: //TIM_SetCompare3(TIMx, dutycycle * (TIMx->ARR + 1)); TIMx->CCR3 = dutyCycle * (TIMx->ARR + 1); break; case 3: //TIM_SetCompare4(TIMx, dutycycle * (TIMx->ARR + 1)); TIMx->CCR4 = dutyCycle * (TIMx->ARR + 1); break; default: break; } }
更新中断频率
设置Timer的更新中断与设置PWM频率是类似的,因为PWM的频率即为更新中断的频率,它们的区别在于:
- 设置更新中断频率时无需设置CCR1~4(PWM的占空比)的值
- 基本定时器可以设置更新中断频率,但是不能设置PWM的频率
/** * @brief Set the update frequency of timer * 本函数实现的最大更新中断频率为1MHz。 * * 注意1: 在STM32F407中,高级定时器的时钟频率与通用定时器的时钟频率是不一样的, * 需要修改本函数(增加对定时器类型的判断)才能正确的设置更新中断的频率。 * * 注意2: 基本定时器、通用定时器、高级定时器都可以设置更新中断的频率。 * @param TIMx: 基本定时器、通用定时器、高级定时器TIM 1~8(在STM32F103中,定时器的时钟频率可以都设置为72MHz) * @param freq: 0.2~1MHz */ void User_Timer_SetUpdateFrequency(TIM_TypeDef *TIMx, float freq) { //Range of frequency is 0.2Hz to 1MHz. //参数不正确,直接返回 if (freq > 1000000) return; if (freq < 0.2) return; //根据频率设置Timer的分频系数 //0.2 <= frequency < 100, psc = 7200, f = 10KHz //占空比设置精度为1/50000~1/100 if(freq < 100) { TIMx->PSC = 7200 - 1; } //100 <= frequency < 1k, psc = 720, f = 100KHz //占空比设置精度为1/1000~1/100 else if(freq < 1000) { TIMx->PSC = 720 - 1; } //1K <= frequency < 10K, psc = 72, f = 1MHz //占空比设置精度为1/1000~1/100 else if(freq < 10000) { TIMx->PSC = 72 - 1; } //10K <= frequency < 100K, psc = 6, f = 12MHz //占空比设置精度为1/1200~1/120 else if(freq < 100000) { TIMx->PSC = 6 - 1; } //100K <= frequency <= 1M, psc = 1, f = 72MHz //占空比设置精度为1/720~1/72 else if(freq <= 1000000) { TIMx->PSC = 1 - 1; } else { } //Set the update frequency of the timer. TIMx->ARR = 72000000.0 / (TIMx->PSC + 1) / freq - 1; }
设置死区时间
/** * @brief Set the dead time of PWM * @param deatTime: 0~14 us */ void User_PWM_SetDeadTime(uint8_t deadTime) { /* 正确的deadtime的计算方法(经理论与示波器测试成功) TIM_BDTRInitStructure.TIM_DeadTime=255 //这句设定的就是寄存器TIMx_BDTR的后8位,即DTG[7:0],所以最大值为255 从下面的代码中的“第五步”中,实际上就相当于TIM1->BDTR=0x71FF; 查看"STM32中文参考手册2009.pdf"的TIMx_BDTR(第248页),列寄存器TIMx_BDTR的后8位如下: 位7:0 UTG[7:0]: 死区发生器设置 (Dead-time generator setup) 这些位定义了插入互补输出之间的死区持续时间。假设DT表示其持续时间: DTG[7:5]=0xx => DT=DTG[7:0] × Tdtg, Tdtg = Tdts; DTG[7:5]=10x => DT=(64+DTG[5:0]) × Tdtg, Tdtg = 2 × Tdts; DTG[7:5]=110 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 8 × Tdts; DTG[7:5]=111 => DT=(32+DTG[4:0]) × Tdtg, Tdtg = 16× Tdts; Tdts为系统时钟周期时长,Tdtg为死区时间计算步长。主要思想就是把DTG的八位,掰成两半用。一半决定步长,另一半是与步长相乘的乘数,乘数可以自行设定,步长*乘数=死区时间。 在72M的定时器时钟下,TDTS = 1/72M = 13.89ns: 项目 情况1 情况2 情况3 情况4 步长位置 DTG[7] DTG[7:6] DTG[7:5] DTG[7:5] 步长值(二进制) 0xx 10x 110 111 步长是周期几倍 1 2 8 16 Step Value (Tdts = 13.89ns) 1/72M 1/36M 1/9M 2/9M Step Value (Tdts = 13.89ns) 13.89ns 27.78ns 111.11ns 222.22ns 乘数位置 DTG[6:0] DTG[5:0] DTG[4:0] DTG[4:0] 乘数最大值 0+127 64+63 32+31 32+31 乘数范围 0~127 64~127 32~63 32~63 等价几倍周期 0~127 128~254 256~504 512~1008 周期13.89ns时,死区范围ns 0~1764 1778~3528 3556~7000 7112~14001 示例: 需要3us的死区时间,那么属于情况2,DTG[7:6] = 0b10,步长=27.78, 需要的乘数 = 3000÷27.78-64=108-64=44=0b101100,DTG[7:0]=0b10101100=0xAC */ uint16_t _deadTime = 0; if (deadTime > 14) return; //7<deadTime<=14, deadTime = 8, 9, 10, 11, 12, 13, 14 else if (deadTime > 7) { _deadTime = 0xE0 | (uint8_t) (deadTime * 100000 / 22222 - 32); } //3<deadTime<=7, deadTime = 4, 5, 6, 7 else if (deadTime > 3) { _deadTime = 0xC0 | (uint8_t) (deadTime * 100000 / 11111 - 32); } //1<deadTime<=3, deadTime = 2, 3 else if (deadTime > 1) { _deadTime = 0x80 | (uint8_t) (deadTime * 100000 / 2778 - 64); } //deadTime = 0, 1 else { _deadTime = 0x00 | (uint8_t) (deadTime * 100000 / 1389 - 0); } TIM1->BDTR = (0x0C00 | _deadTime); //Dead time setting is validated after restart the PWM. // User_PWM_Open(0); // User_PWM_Open(1); }
项目实例
BLDC电机速度闭环控制
笔者参与的BLDC电机速度闭环控制开发项目中使用了定时器的输入捕获功能,通过测量霍尔传感器产生的脉冲宽度计算电机转速。定时器等相关外设的配置上文有介绍,本章节重点介绍中断服务函数的实现。
- 声明用于记录脉冲计数的全局变量
uint32_t BLDCMotor_PulseCount = 0;
- 在定时器的更新中断中将此全局变量复位
其作用相当于超出测试量程后将速度计算值复位为0。
void TIM1_UP_IRQHandler(void) { if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) { BLDCMotor_PulseCount = 0; TIM_ClearFlag(TIM1,TIM_FLAG_Update); TIM_ClearITPendingBit(TIM1,TIM_IT_Update); } }
- 在定时器的输入捕获中断服务函数中计算脉冲宽度
为了提高输入捕获的精度,应该尽可能减小服务函数的执行时间。多数情况下应该是调用函数
TIM_GetCaptureX
获取对应通道的计数值,然后将连续两次调用的计数值相减得到脉冲计数。为了提高效率,本项目中的服务函数直接将计数值清零,下次获取的计数值将直接等于脉冲计数。void TIM1_CC_IRQHandler(void) { if(TIM_GetITStatus(TIM1, TIM_IT_CC4) != RESET) { /** * Pulse capture has strict requirements on time, * so it needs to operate the register directly. */ BLDCMotor_PulseCount = Filter_MovingAverage_VariantSize(TIM1->CCR4, MOSP_FMA_INDEX, 5); TIM1->CNT = 0; TIM1->SR = (uint16_t)~TIM_FLAG_CC4; // Timer4_IT_CC1_Triggered(); // TIM_ClearFlag(TIM1,TIM_FLAG_CC4); // TIM_ClearITPendingBit(TIM1,TIM_IT_CC4); } }
- 脉冲长度与电机转速的转化
霍尔传感器的脉冲宽度以定时器的时钟计数量表示,首先要将它转换为时间/频率的单位。脉冲频率与电机转速的比例是一个受直流无刷电机磁极子个数影响的固定参数,同时考虑到可能存在的减速机构改变了传动比,因此设置了两个可以调整的参数用于计算最终的电机转速。
float BLDC_PulseToSpeed(uint32_t pulse) //unit: rpm { float frequency; if (pulse == 0) return 0; //验证计算参数是否有效 if (HoldingReg_GetData(3) == 0 || HoldingReg_GetData(4) == 0) { return -1; } frequency = (float) SystemCoreClock / (TIM1_Prescaler * pulse); return frequency * 60 / HoldingReg_GetData(3) / HoldingReg_GetData(4); //unit: rpm }
-
Arduino定时器配置(Timer0,Timer1,Timer2)
2020-12-19 19:48:29/ 预分频器系数中断频率(Hz)=(Arduino时钟速度16MHz)/(预分频器*(比较匹配寄存器+ 1)比较匹配寄存器= [16,000,000Hz /(预分频器*所需的中断频率)] - 1定时器配置代码:Arduino的学习过程中一般使用库函数操作。...定时器基本概念:
定时器速度(HZ) = Arduino时钟速度(16MHz) / 预分频器系数
中断频率(Hz)=(Arduino时钟速度16MHz)/(预分频器*(比较匹配寄存器+ 1)
比较匹配寄存器= [16,000,000Hz /(预分频器*所需的中断频率)] - 1
定时器配置代码:
Arduino的学习过程中一般使用库函数操作。但是关于定时器的例子常用库却没有。因此,在这里简要通俗的写出定时中断的配置过程。参考资料:http://www.instructables.com/id/Arduino-Timer-Interrupts/。
一、Arduino定时器简介
Arduino UNO有三个定时器,分别是timer0,timer1和timer2。每个定时器都有一个计数器,在计时器的每个时钟周期递增。当计数器达到存储在比较匹配寄存器中指定值时触发CTC定时器中断。一旦定时器计数器达到该值,它将在定时器时钟的下一个定时器上清零(复位为零),然后它将继续再次计数到比较匹配值。通过选择比较匹配值并设置定时器递增计数器的速度,你可以控制定时器中断的频率。
下面引出定时器各个寄存器的配置关系。
二、定时器基本概念
1、预分频系数与比较匹配器
Arduino时钟以16MHz运行。计数器的一个刻度值表示1 / 16,000,000秒(~63ns),跑完1s需要计数值16,000,000。
1、Timer0和timer2是8位定时器,可以存储最大计数器值255。
2、Timer1是一个16位定时器,可以存储最大计数器值65535。
一旦计数器达到其最大值,它将回到零(这称为溢出)。因此,需要对时钟频率进行分频处理,即预分频器。通过预分频器控制定时计数器的增量速度。预分频器与定时器的计数速度如下:
定时器速度(HZ) = Arduino时钟速度(16MHz) / 预分频器系数
因此,1预分频器将以16MHz递增计数器,8预分频器将在2MHz递增,64预分频器= 250kHz,依此类推。
三个定时器的预分频系数配置如表:
我将在下一步中解释CS12,CS11和CS10的含义。
现在您可以用以下步骤计算中断频率。以下公式:
中断频率(Hz)=(Arduino时钟速度16MHz)/(预分频器*(比较匹配寄存器+ 1)
重新排列上面的等式,给出你想要的中断频率,你可以求解比较匹配寄存器值:
比较匹配寄存器= [16,000,000Hz /(预分频器*所需的中断频率)] - 1
记住,当你使用定时器0和2时,这个数字必须小于256,对于timer1小于65536。
所以如果你想每秒一次中断(频率为1Hz):比较匹配寄存器= [16,000,000 /(预分频器 * 1)] -1
预分频器为1024,你得到:比较匹配寄存器= [16,000,000 /(1024 * 1)] -1 = 15,624,因为256 <15,624 <65,536,你必须使用timer1来实现这个中断。
三、定时器配置代码void setup(){
cli();关闭全局中断
//设置定时器0为10kHz(100us)
TCCR0A = 0;//将整个TCCR0A寄存器设置为0
TCCR0B = 0;//将整个TCCR0B寄存器设置为0
TCNT0 = 0;//将计数器值初始化为0
//设置计数器为10kHZ,即100us
OCR0A = 24;//比较匹配寄存器= [16,000,000Hz /(预分频器*所需中断频率)] - 1
//比较匹配寄存器=24,中断间隔=100us即中断频率10khz
TCCR0A |= (1 <
TCCR0B |= (1 <
TIMSK0 |= (1 <
//设置定时器1为1kHz
TCCR1A = 0;//将整个TCCR1A寄存器设置为0
TCCR1B = 0;//将整个TCCR1B寄存器设置为0
TCNT1 = 0;//将计数器值初始化为0
//设置计数器为10kHZ,即1ms
OCR1A = 199;// = (16*10^6)/(1000*8) - 1 (must be <65536)
TCCR1B |= (1 <
TCCR1B |= (1 <
TIMSK1 |= (1 <
//设置定时器2为8kHz
TCCR2A = 0;// set entire TCCR2A register to 0
TCCR2B = 0;// same for TCCR2B
TCNT2 = 0;//initialize counter value to 0
// set compare match register for 8khz increments
OCR2A = 249;// = (16*10^6) / (8000*8) - 1 (must be <256)
// turn on CTC mode
TCCR2A |= (1 <
// Set CS21 bit for 8 prescaler
TCCR2B |= (1 <
// enable timer compare interrupt
TIMSK2 |= (1 <
sei();//打开全局中断
}
//中断0服务函数
ISR(TIMER0_COMPA_vect){
//产生频率为2kHz / 2 = 1kHz的脉冲波(全波切换为两个周期,然后切换为低)
if(toggle0){
digitalWrite(8,HIGH);
toggle0 = 0;
}
else{
digitalWrite(8,LOW);
toggle0 = 1;
}
}
ISR(TIMER1_COMPA_vect){// timer1中断1Hz切换引脚13(LED)
//产生频率为1Hz / 2 = 0.5kHz的脉冲波(全波切换为两个周期,然后切换为低)
if(toggle1){
digitalWrite(13,HIGH);
toggle1 = 0;
}
else{
digitalWrite(13,LOW);
toggle1 = 1;
}
}
ISR(TIMER2_COMPA_vect){// timer1中断8kHz切换引脚9
//产生频率为8kHz / 2 = 4kHz的脉冲波(全波切换为两个周期,然后切换为低)
if(toggle2){
digitalWrite(9,HIGH);
toggle2 = 0;
}
else{
digitalWrite(9,LOW);
toggle2 = 1;
}
}
void loop(){
}
了解更多请访问:http://www.ndfweb.cn/news-778.html
-
51 单片机 (3) 定时器/计数器 之 利用定时器0(timer0)编写精确的延时函数
2020-06-07 00:03:34定时器实际上也是工作在计数方式下,只是计数的是固定周期的脉冲,由于脉冲周期固定,由计数值可以计算时间,有定时功能。 定时和计数只是触发来源不同(时钟信号和外部脉冲)其他方面是一样的。 AT89C51的定时器/...定时器/计数器
什么是定时器/计数器?
在51单片机中,定时器/计数器是用来实现定时功能,并且具有计数的功能,来实现对外部信号的计数,其实他们是同一个物理的电子元件。
定时器实际上也是工作在计数方式下,只是计数的是固定周期的脉冲,由于脉冲周期固定,由计数值可以计算时间,有定时功能。
定时和计数只是触发来源不同(时钟信号和外部脉冲)其他方面是一样的。AT89C51的定时器/计数器
从上图我们可以发现这款51单片机有2个16位的定时/计数器,他们被标识为T0和T1。定时器/计数器相关的寄存器
TCON
T0、T1定时器/计数器控制寄存器,格式如下
TF0、TF1
定时器/计数器T0(T1)溢出标志。当T0(T1)被允许计数以后,从初值开始加1计数。当最高位产生溢出时由硬件对TF0(TF1)置1,中断完成又由硬件对TF0(TF1)清0
TR0、TR1
定时器T0(T1)的运行控制位。该位由软件置位和清零。
对T0
当GATE (TMOD.3) =0,TR0=1时就允许T0开始计数
当GATE (TMOD.3) =0,TR0=0时就禁止T0计数
当GATE (TMOD.3) =1,TR0=1且INT0输入高电平时,才允许T0计数。
对T0
当GATE (TMOD.7) =0,TR1=1时就允许T1开始计数
当GATE (TMOD.7) =0,TR1=0时就禁止T1计数
当GATE (TMOD.7) =1,TR1=1且INT1输入高电平时,才允许T1计数。TMOD
TMOD是定时器、计数器模式控制寄存器
使用定时器0就使用第四位,定时器1就用高四位
GATE
门控位。GATE=0,以运行控制位TR启动定时器;GATE=1,以外中断请求信号(INT1或INT0)启动定时器,这可以用于外部脉冲宽度测量。在TMOD中GATE一般情况下都等于0。
C/T
控制其用作定时器还是计数器
置0用作定时器(从内部系统时钟输入)
置1用作计数器(从T0/P3.4 或T1/P3.5脚输入)M1、M0
定时器/计数器模式选择
M1----M0-----------功能
0-------0-------13位定时器/计数器,TL用低5位,TH全用
0-------1-------16位定时器/计数器,TL、TH全用
1-------0--------8位自动重装定时器,当溢出时TH的值自动重装入TL
1-------1--------定时器/计数器无效(停止计数)TH、TL
定时器值的存储寄存器
名称 描述 TH0 定时器0高字节 TL0 定时器0低字节 TH1 定时器1高字节 TL1 定时器1低字节 实验内容
利用AT89C51的定时器0来实现精确延时
实验环境
- 仿真软件
Protue 8.9 sp2 - IDE
Keil5 C51 - 单片机
AT89C51
Protues仿真图
注意,这里我们使用12M的时钟频率
Keil工程
项目结构
代码
#include <reg52.h> //时钟频率 #define FOSC 12000000L //计算器初值计算 #define Times (65536 - FOSC / 12 / 1000) //LED1控制引脚 sbit led1 = P1 ^ 0; //计数器中断次数 volatile unsigned int count; //定时器溢出中断(1ms中断一次) void Timer0_Rountine(void) interrupt 1 { //重新装载初值 TL0 = Times; TH0 = Times >> 8; //总延时减1 count--; } //毫秒级延时 void delay_ms(unsigned int ms) { //给T0低字节装载初始值 TL0 = Times; //给T0高字节装载初始值 TH0 = Times >> 8; //初始化T0模式寄存器,也就是TMOD的第四位 TMOD &= 0xF0; //取值为0001 //即GATE=0 //C/T=0 我们用作定时器 //M1=0 M2=1 模式选择为16位的定时器 TMOD |= 0x01; //xxxx 0001 //让计数器开始计数 TR0 = 1; //打开定时器0的中断开关 ET0 = 1; //打开中断总开关 EA = 1; //延时count毫秒 count = ms; //当cout等于0是,关闭计数,关闭T0的中断 while (count > 0) ; { TR0 = 0; ET0 = 0; } } void main(void) { while (1) { led1 = 1; delay_ms(500); led1 = 0; delay_ms(500); } }
结果
总结
最后我想说一下关于时间的计算
时钟周期
一个CPU周期时间又包含若干个时钟周期。时钟周期定义为时钟脉冲的倒数(可以这样来理解,时钟周期就是单片机外接晶振的倒数,例如12M的晶振,它的时间周期就是1/12 μs),是计算机中最基本的、最小的时间单位。在一个时钟周期内,CPU仅完成一个最基本的动作。由于时钟脉冲是计算机的基本工作脉冲,它控制着计算机的工作节奏(使计算机的每一步都统一到它的步调上来)。显然,对同一种机型的计算机,时钟频率越高,计算机的工作速度就越快。但是,由于不同的计算机硬件电路和器件的不完全相同,所以其所需要的时钟周频率范围也不一定相同。我们学习的 8051单片机的时钟范围是1.2MHz-12MHz。
一个机器周期包含六个状态周期(用S表示)。一个状态周期有两个节拍(用P1、P2表示)。8051系列单片机的一个机器周期同6 个S周期(状态周期)组成。也就是说一个机器周期=6个状态周期=12个振荡周期(即时钟周期)。机器周期
在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个S周期(状态周期)组成。通常用内存中读取一个指令字的最短时间来规定CPU周期,(也就是计算机通过内部或外部总线进行一次信息传输从而完成一个或几个微操作所需要的时间)),它一般由12个时钟周期(振荡周期)组成,也是由6个状态周期组成。而振荡周期=1秒/晶振频率,因此单片机的机器周期=12秒/晶振频率 。
所以在51 芯片中定时器启动后会在每一个机器周期会使定时器值的存储寄存器增加一,一个机器周期等于十二个振荡周期,所以可以得知51芯片的计数速率为晶体振荡器频率的1/12,一个12M 的晶振用在51芯片上,那么51的计数速率就为1M。
即一秒钟1000000次,1ms=1000次,我们用的是16位的定时器,则可以存储2^16=65536次,所用根本用不玩,于是我们就从65536-1000开始计数就完美解决了,下面这个初值就是这样算出来的//时钟频率 #define FOSC 12000000L //计算器初值计算 #define Times (65536 - FOSC / 12 / 1000)
同理若为11.0592M计算为
//时钟频率 #define FOSC 11059200L //计算器初值计算 #define Times (65536 - FOSC / 12 / 1000)
- 仿真软件
-
单片机C语言程序设计:TIMER0与TIMER1控制条形LED
2020-08-28 19:28:13名称:TIMER0 与 TIMER1 控制条形 LED 说明:定时器 T0 定时控制上一组条形 LED,滚动速度较快定时器 T1 定时控制下一组条形 LED,滚动速度较慢 -
【蓝桥杯单片机(7)】定时器的定时与计数功能使用
2022-01-29 13:13:33脉冲来源是内部晶振则为定时模式,为外部脉冲则为计数,二者都可产生中断请求 3、配置定时器定时 先会用再了解 讲完这个,我们来看看如何配置这个闹铃(定时器) IPA15F2K61S2芯片提供了3个16位的定时/计数器器,... -
使用HAL库开发STM32:Timer基础说明与定时功能使用
2021-07-13 16:26:09文章目录目的Timer基础说明定时功能使用总结 目的 Timer是单片机中非常常见的一种外设组件,可以实现很多常用的功能,这篇文章就将对STM32中Timer的基础内容做个说明 Timer基础说明 定时功能使用 总结 ... -
ATmega88的Timer0溢出中断
2019-07-25 14:13:10// 功能: pb1 输出高低电平 时隔为一秒 #include <iom88v.h> #include <macros.h> int blink_sign = 0; void timer0_init(void) { TCCR0A = 0x00; //timer0 普通模式 TCCR0B... -
关于DSP2812的Timer0定时器配置程序的质疑
2019-04-28 11:08:05关于DSP2812的Timer0定时器配置程序的质疑 个人觉得网上许多关于DSP的CPU定时器Timer 0的配置处有问题——程序中Timer->RegsAddr->PRD.all = temp;的temp应改为(temp-1)。 首先回顾一下定时器工作原理:大体... -
MPLAB X IDE V4.2 -2:如何使用PIC10F200的TIMER0定时
2018-10-09 15:14:22工作需要用到PIC10F200来定时: 1 最初我用的是简单的delay()来实现 但是这种延时不精确,且由于各个PIC10F200芯片的晶振精度存在差异(即使厂家上电校正后,差异始终存在);我在235个芯片上刷相同的程序延时600... -
PIC16F877A单片机 (外部中断与定时器Timer0的综合使用)
2021-10-22 21:56:43PIC16F877A单片机 (外部中断与定时器Timer0的综合使用)1 实现原理2 实现电路图3 源代码 1 实现原理 见前面的定时器0和外部中断的内容 2 实现电路图 3 源代码 /*----------------函数功能: 定时器0+外部中断的... -
STC15单片机定时器0工作模式介绍
2022-01-28 10:22:38STC15单片机定时器0工作模式介绍 -
Timer,TimerTask通过程序计数器实现的定时任务
2021-03-14 01:49:11在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础... -
STM32定时器配置(TIM1-TIM8)高级定时器+普通定时器,定时计数模式下总结
2018-09-13 17:04:00在向下模式中,计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。而中央对齐模式(向上/向下计数)是计数器从0开始计数到自动装入的值-1,产生一个... -
实时系统vxWorks - timer定时应用
2021-07-09 00:52:05main概述VxWorks提供IEEE的POSIX 1003.1b标准定时器接口。使用这种定时器机制,在指定的时间间隔后,任务向自身发信号。因此我们可以利用此机制可以很方便的实现周期定时。... -
TI - MCU - MSP430使用指南13 -> Timer定时器模块
2019-12-30 18:10:12随着MCU功能的日渐强大,定时器的功能也越来越强大,因此配置和使用起来也就比较麻烦,下面我们针对MSP430的Timer模块进行详细讲解,配合多种可以直接使用的例程,方便用户直接移植和深入理解。 首先,普及一下... -
Qt UDP定时发送数据报并实现计数
2015-07-07 09:18:44代码实现的是qt定时发送udp数据包 并实现发送的数据包计数与反馈的数据包计数 进行对比 确认网络的稳定性以及是否有丢包现象 -
msp430学习笔记之定时器A
2015-07-07 22:56:56msp430定时器操作比普通单片机要复杂,有捕获、比较模式,工作模式又有增计数、连续计数、增减计数三种,本文用最少的代码实现对timer_a连续模式,控制led灯亮灭 -
MSP430F169 Timer_A原理(一)----定时器的四个模式
2021-07-13 21:05:02每一个比较/捕获通道都以十六位定时器的定时功能为核心进行单独的控制。 Timer_A特点 1、具有四种工作模式的异步16位定时器/计数器 2、可选择和可配置的时钟源 3、三个可配置的捕获/比较寄存器 4、具有 PWM 功能的可... -
STM32定时器配置(TIM1、TIM2、TIM3、TIM4、TIM5、TIM8)高级定时器+普通定时器,定时计数模式下总结
2021-02-02 15:25:18STM32定时器配置(TIM1、TIM2、TIM3、TIM4、TIM5、TIM8)高级定时器+普通定时器,定时计数模式下总结 文章结构: ——> 一、定时器基本介绍 ——> 二、普通定时器详细介绍TIM2-TIM5 ——> 三、... -
补充设定 timer1 定时器和 timer2 定时器定时做多件事
2019-04-12 15:33:467. 补充设定 timer1 定时器和 timer2 定时器定时做多件事(教程) ...下面仅仅是将原文给编辑...前面跟大家分享了如何自己设定Arduino内部定时器定时做事,我把在以下两篇中对于暂停/继续 timer1 和 timer2 的 CTC 中断之... -
MATLAB中定时器Timer的使用
2020-10-22 17:54:05四种定时模式: sigleShot:只执行一次,故Period属性不起作用,其他模式都可以执行多次 fixedDelay:上一次TimerFcn执行完毕时刻到下一次TimerFcn被加入队列时刻之间的间隔 fixedRate:上一次开始执行到下一... -
关于MCU中Timer/Counter接口及使用
2021-12-02 21:41:27Timer就是定时器,一般做周期性任务处理时使用。在芯片中,Timer是如何设计,寄存器又是如何控制的呢? 定时器模式通常用于测量事件发生的时间或测量两个事件之间的时间差。计时器功能增加/减少了一个在0和存储在... -
winform 中使用定时器Timer
2020-06-16 10:18:46int lostTime = 0; Object myobj = new object(); System.Timers.Timer timer = new System.Timers.Timer(); 2)启动定时器 public void startWzTimer() { timer.Enabled = true; timer.Interval = 1000;//... -
九齐NY8B072A单片机使用笔记(一)TIMER0定时器
2021-07-13 18:03:18void Ny8b072a_Timer0_Init(void) { PCON1 = C_TMR0_Dis; // Disable Timer0 //1 * (255 - 5) = 250us TMR0 = 5; // Load 0x00 to TMR0 (Initial Timer0 register) //16M 2T Div8 = 1us T0MD = C_PS0_TMR0 ... -
MSP430 5xx/6xx 定时器A增计数模式编程实例
2021-11-08 21:29:10具有四种工作模式的异步16位定时器/计数器 可选择和可配置的时钟源 最多七个可配置的捕获/比较寄存器 具有脉宽调制(PWM)功能的可配置输出 异步输入输出闭锁 中断向量寄存器,用于快速解码所有定时器中断 TAxR: ... -
PIC16F15323单片机 (中断与定时器Timer0)
2021-10-21 22:35:55PIC16F15323单片机 (中断与定时器Timer0)1 基本原理2 实现代码 开发环境选择的是 MPLAB X IDE v5.50和 xc8-v2.32-full-install-windows-x64-installer。 1 基本原理 2 实现代码 主要根据FIGURE 25-1 和中断的... -
RISC-V_GD32VF103-TIMER0 定时器中断
2020-11-23 21:45:01GD32VF103 定时器同分有分别 分为五种类型:高级定时 器(TIMER0),通用定时器L0(TIMER1,2,3,4),基本定时器(TIMER5,6), 不同类型的定 时器具体功能有所差别。功能依然很多。-&_&- timer.... -
定时器——timer0和timer0
2016-09-15 22:05:49中断法相对于查询法有两个优点:1.节省能量 2....5.打开定时计数对于定时器0的初始化:void timer0(void) { EA = 1;//打开总中断 TMOD |= 0x01;//设置定时器的工作方式 TH0 = (65536-20000)/256; -
CC2530基础实验四:Timer和PWM
2022-01-24 18:54:55硬件定时器一般分为两种工作模式:定时器模式和计数器模式,分别对应着输出和输入。其中: 计数器模式: 对输入引脚的外部脉冲进行计数,比较典型的应用就是编码器了,用来计数脉冲形成闭环系统。 定时器模式: 对...