4、定时器模块
定时器可以执行计时和计数的任务。
Systic定时器也叫做滴答定时器,是一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。
寄存器方面:
控制寄存器:CTRL
数值寄存器:
重载寄存器:
这24位是从0到23的,叫做RELOAD位。它里面放的值就是计时用的初值,VAL寄存器中的值就是从这里取出的,当VAL寄存器中的值减为0后,就会自动从LOAD寄存器中再将这个初值取出放入VAL寄存器中,然后继续做减一处理,一直循环下去,除非将使能位写0。
定时器定义的结构体:
#define MCU_SYSTEM_CLK_MS (SystemCoreClock/ 1000) //宏定义一毫秒的时间经过多少时钟频率
初始化:
//=========================================================================== //函数名称:systick_init //函数返回:无 //参数说明:int_ms:中断的时间间隔。单位ms 推荐选用5,10,...... //功能概要:初始化SysTick模块,设置中断的时间间隔 //说 明:内核时钟频率MCU_SYSTEM_CLK_KHZ宏定义在mcu.h中 //systick以ms为单位,349(2^24/48000,向下取整),合理范围1~349。 //=========================================================================== void systick_init( uint8_t int_ms) { //(1)参数检查 if((int_ms<1)||(int_ms>349)) { int_ms = 10; } //(2)设置前先禁止SysTick和清除计数器 SysTick->CTRL=0;//禁止SysTick SysTick->VAL=0; //清除计数器 //(3)设置时钟源和重载寄存器 SysTick->LOAD = MCU_SYSTEM_CLK_MS*int_ms; // 一毫秒的时钟频率 * 要设置的毫秒数 SysTick->CTRL |=SysTick_CTRL_CLKSOURCE_Msk;//选择内核时钟 //(4)设定 SysTick优先级为15 NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); //(5)允许中断,使能该模块 SysTick->CTRL |= ( SysTick_CTRL_ENABLE_Msk|SysTick_CTRL_TICKINT_Msk ); }
关于不同的时钟源:
在定时器模块中,不同的时钟源是用于驱动定时器计数的时钟信号源,不同的时钟源对定时器的工作方式和性能有重要影响。以下是一些常见的时钟源以及它们的区别:
-
内部时钟源:
-
内部时钟源通常是微控制器内部的振荡器或时钟信号。
-
优点:内部时钟源通常是稳定的,不受外部环境的影响。
-
缺点:内部时钟源的频率通常相对较低,可能不适合需要高精度定时的应用。
-
-
外部时钟源:
-
外部时钟源通常来自外部晶振或时钟信号源。
-
优点:外部时钟源可以提供更高的精度和稳定性。
-
缺点:外部时钟源可能需要额外的外部元件,并且受外部环境干扰。
-
-
PLL(锁相环)时钟源:
-
PLL 允许您将一个时钟源的频率倍增,以提供更高的时钟频率。
-
优点:可以提供高精度的时钟源,适用于需要高性能的应用。
-
缺点:配置和维护 PLL 可能需要额外的复杂性。
-
-
外部触发源:
-
定时器模块有时可以配置为接收来自外部信号源的触发。
-
优点:允许在特定事件发生时启动定时器。
-
缺点:需要外部触发源的支持,并且具有一定的复杂性。
-
-
其他特殊时钟源:
-
一些定时器模块可能支持特殊的时钟源,如边沿对齐时钟、外部同步时钟等,用于特定的应用需求。
-
选择合适的时钟源通常取决于您的应用需求。内部时钟源通常足够用于基本的定时任务,而外部时钟源和 PLL 适用于需要更高精度和性能的应用。外部触发源通常在需要精确的事件同步时非常有用。
在嵌入式系统中,"看门狗"(Watchdog)通常指的是看门狗定时器(Watchdog Timer),它是一种用于监视系统运行状态的硬件定时器。看门狗定时器的主要目的是在系统死锁或崩溃时执行重启操作,从而提高系统的稳定性和可靠性。
以下是关于看门狗定时器的一些重要信息:
-
工作原理:
-
看门狗定时器是一个硬件计时器,它会定期重置。如果在设定的时间间隔内未重置看门狗定时器,它将认为系统已经死锁或出现故障。
-
看门狗定时器通常以周期性的方式工作,例如每隔几秒重置一次。
-
-
重置操作:
-
在正常运行时,系统会定期重置看门狗定时器,防止其超时。
-
如果系统出现问题,无法在规定时间内重置看门狗定时器,定时器将超时,并触发重置操作。
-
-
重启系统:
-
当看门狗定时器超时时,它会执行一个重置操作,导致整个系统被重新启动。
-
这可以帮助恢复系统到正常状态,即使系统出现故障或死锁。
-
-
应用场景:
-
看门狗定时器常用于嵌入式系统、自动控制系统、远程设备监控等需要高度稳定性和可靠性的应用。
-
它可以防止系统因软件错误、死锁或其他异常情况而无法恢复。
-
-
配置:
-
看门狗定时器的超时时间可以根据应用需求进行配置,通常在几秒到几分钟之间。
-
它的配置和启动通常需要一组特定的寄存器设置。
-
-
注意事项:
-
在使用看门狗定时器时,确保在正常运行时定期重置它,以避免系统意外重启。
-
确保在程序中正确处理和报告故障,以防止不必要的系统重启。
-
中断的符号引用:RTC_WKUP_IRQHandler
RTC_Alarm_IRQHandler
// ========================================================================== // 函数名称:RTC_Init // 函数参数:无 // 函数返回:0,初始化成功;1,进入初始化失败 // 功能概要:选择LSE时钟,频率为32.768kHz;将7位异步预分频器为128,15位同步预分频器为256;并初始化时钟 // ========================================================================== uint8_t RTC_Init(void) { RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN_Msk; //使能电源接口时钟 PWR->CR1 |= PWR_CR1_DBP_Msk; //后备区域访问使能(RTC+SRAM) RCC->CSR |= RCC_CSR_LSION_Msk; //LSI总是使能 while(!(RCC->CSR&0x02)) ; //等待LSI就绪 RCC->BDCR &= ~(RCC_BDCR_RTCSEL_Msk); //清零8/9位 RCC->BDCR |= 1<<9; //选择LSI时钟 RCC->BDCR |= RCC_BDCR_RTCEN_Msk; //使能RTC时钟 RTC->WPR = 0xCA; //关闭RTC寄存器写保护 RTC->WPR = 0x53; RTC->CR = 0; if(RTC_Init_Mode()) //进入RTC初始化模式失败 { RCC->BDCR = RCC_BDCR_BDRST_Msk;//复位BDCR Delay_ms(10); RCC->BDCR = 0; //结束复位 return 1; } RTC->PRER = 0XFF; //RTC同步分频系数(0~7FFF),必须先设置同步分频,再设置异步分频 RTC->PRER |= 0X7F<<16; //RTC异步分频系数(1~0X7F) RTC->CR &= ~(RTC_CR_FMT_Msk); //24h制 RTC->ISR &= ~(RTC_ISR_INIT_Msk);//退出RTC初始化模式 RTC->WPR = 0XFF; //使能RTC寄存器写保护 return 0; }
// =========================================================================== // 函数名称:RTC_Set_Date // 函数参数:year:年份;month:月份;date:天数;week:星期几 // 函数返回:1:设置日期成功;0:设置日期失败 // 功能概要:设置RTC时钟的日期 // =========================================================================== uint8_t RTC_Set_Date(uint8_t year,uint8_t month,uint8_t date,uint8_t week) { uint32_t temp = 0; RTC->WPR = 0xCA; //关闭RTC寄存器写保护 RTC->WPR = 0x53; if(RTC_Init_Mode()) //进入RTC初始化模式失败 return 1;
// 将周年月日的二进制都弄到一个变量中 temp = (((uint32_t)week&0X07)<<13)|((uint32_t)RTC_DEC2BCD(year)<<16)|((uint32_t)RTC_DEC2BCD(month)<<8)|(RTC_DEC2BCD(date)); RTC->DR = temp; //设置RTC的日期寄存器的值 RTC->ISR &= ~(RTC_ISR_INIT_Msk); //退出RTC初始化模式 return 0; } // =========================================================================== // 函数名称:RTC_Set_Time // 函数参数:hour:小时;min:分钟;sec:秒钟; // 函数返回:1:设置时间成功;0:设置时间失败 // 功能概要:设置RTC时钟的时间 // =========================================================================== uint8_t RTC_Set_Time(uint8_t hour,uint8_t min,uint8_t sec) { uint32_t temp = 0; RTC->WPR = 0xCA; //关闭RTC寄存器写保护 RTC->WPR = 0x53; if(RTC_Init_Mode()) //进入RTC初始化模式失败 return 1; temp = (((uint32_t)0&0X01)<<22)|((uint32_t)RTC_DEC2BCD(hour)<<16)|((uint32_t)RTC_DEC2BCD(min)<<8)|(RTC_DEC2BCD(sec)); RTC->TR = temp; //设置RTC的时间寄存器的值 RTC->ISR &= ~(RTC_ISR_INIT_Msk); //退出RTC初始化模式 return 0; }
// =========================================================================== // 函数名称:RTC_Get_Date // 函数参数:year:年份;month:月份;date:天数;week:星期几 // 函数返回:无 // 功能概要:获取RTC时钟的日期 // =========================================================================== void RTC_Get_Date(uint8_t *year,uint8_t *month,uint8_t *date,uint8_t *week) { uint32_t temp = 0; while(RTC_Wait_Synchro()); //等待同步到备份寄存器中 temp = RTC->DR; *year = RTC_BCD2DEC((temp>>16)&0XFF); *month = RTC_BCD2DEC((temp>>8)&0X1F); *date = RTC_BCD2DEC(temp&0X3F); *week = (temp>>13)&0X07; } // =========================================================================== // 函数名称:RTC_Get_Time // 函数参数:hour:小时;min:分钟;sec:秒钟; // 函数返回:无 // 功能概要:获取RTC时钟的时间 // =========================================================================== void RTC_Get_Time(uint8_t *hour,uint8_t *min,uint8_t *sec) { uint32_t temp = 0; while(RTC_Wait_Synchro()); //等待同步到备份寄存器中 temp = RTC->TR; *hour = RTC_BCD2DEC((temp>>16)&0X3F); *min = RTC_BCD2DEC((temp>>8)&0X7F); *sec = RTC_BCD2DEC(temp&0X7F); //*ampm = temp>>22; }
// ========================================================================== // 函数名称:RTC_Set_PeriodWakeUp // 函数参数:rtc_s:自动唤醒的周期,单位为秒 // 函数返回:无 // 功能概要:设置自动唤醒的周期 // ========================================================================== void RTC_Set_PeriodWakeUp(uint8_t rtc_s) { RTC->WPR = 0xCA; //关闭RTC寄存器保护 RTC->WPR = 0x53; RTC->CR &= ~(RTC_CR_WUTE_Msk); //关闭WAKE UP while((RTC->ISR&0x04)==0); //等待允许修改定时器配置 RTC->CR &= ~(RTC_CR_WUCKSEL_Msk); //清除原来的时钟选择 RTC->CR |=RTC_CR_WUCKSEL_2; //选择 ck_spre 时钟(通常为 1 Hz) if(rtc_s>0) { RTC->WUTR = rtc_s-1; //唤醒自动重装载寄存器的值 } else RTC->WUTR=0; RTC->ISR &= ~(RTC_ISR_WUTF_Msk); //清除RTC WAKE UP的标志 RTC->CR |= RTC_CR_WUTE_Msk; //使能定时器 RTC->WPR = 0xFF; //禁止修改RTC寄存器 EXTI->PR1 = EXTI_PR1_PIF20_Msk; //清除LINE20上的中断标志位,写1清0 EXTI->IMR1 |= EXTI_IMR1_IM20_Msk; //开启LINE20上的中断 EXTI->RTSR1 |= EXTI_RTSR1_RT20_Msk; //上升沿触发中断 }
//===================================================================== //函数名称:RTC_PeriodWKUP_Get_Int //函数返回:1:有唤醒中断,0:没有唤醒中断。 //参数说明:无 //功能概要:获取唤醒中断标志。 //===================================================================== uint8_t RTC_PeriodWKUP_Get_Int() { //获取定时器唤醒中断标志 if(RTC->ISR&RTC_ISR_WUTF_Msk) return 1; else return 0; } //===================================================================== //函数名称:RTC_PeriodWKUP_Clear //函数返回:无 //参数说明:无 //功能概要:清除唤醒中断标志。 //===================================================================== void RTC_PeriodWKUP_Clear() { //清除定时器唤醒中断标志 RTC->ISR &= ~RTC_ISR_WUTF_Msk; EXTI->PR1 |= EXTI_PR1_PIF20_Msk; //清除LINE20的中断标志 }
// =========================================================================== // 函数名称:RTC_BCD2DEC // 函数参数:BCD码格式的数 // 函数返回:BCD码对应的十进制数 // 功能概要:将BCD码格式转化为对应的十进制数 // ===========================================================================
// bcd码就是二进制形式的十进制数
uint8_t RTC_BCD2DEC(uint8_t val) { uint8_t temp = 0; temp = (val>>4)*10; return (temp+(val&0X0F)); // 就是高位 * 10 + 低位 }
// =========================================================================== // 函数名称:RTC_DEC2BCD // 函数参数:十进制数 // 函数返回:十进制数对应的BCD码格式 // 功能概要:将十进制数转化为对应的BCD码格式 // =========================================================================== uint8_t RTC_DEC2BCD(uint8_t val) { uint8_t bcdhigh = 0; while(val>=10) { bcdhigh++; val -= 10; } return ((uint8_t)(bcdhigh<<4)|val); }
//闹钟选择
#define A 0 //闹钟A
#define B 1 //闹钟B
//===================================================================== //函数名称:RTC_Alarm_Get_Int //函数返回:1:有闹钟中断,0:没有闹钟中断。 //参数说明:SelAlarm:0:闹钟A,1:闹钟B //功能概要:获取闹钟中断标志。 //===================================================================== uint8_t RTC_Alarm_Get_Int(uint8_t SelAlarm) { if(SelAlarm==0) //闹钟A { if(RTC->ISR&RTC_ISR_ALRAF_Msk) return 1; else return 0; } else if(SelAlarm==1) //闹钟B { if(RTC->ISR&RTC_ISR_ALRBF_Msk) return 1; else return 0; } else return 0; } //===================================================================== //函数名称:RTC_Alarm_Clear //函数返回:无 //参数说明:SelAlarm:0:闹钟A,1:闹钟B //功能概要:清除闹钟中断标志。 //===================================================================== void RTC_Alarm_Clear(uint8_t SelAlarm) { if(SelAlarm==0) //闹钟A { RTC->ISR &= ~RTC_ISR_ALRAF_Msk; //清闹钟A的中断标志位 EXTI->PR1 |= EXTI_PR1_PIF18_Msk; //清除中断线18的中断标志 } else if(SelAlarm==1) //闹钟B { RTC->ISR &= ~RTC_ISR_ALRBF_Msk; //清闹钟B的中断标志位 EXTI->PR1 |= EXTI_PR1_PIF18_Msk; //清除中断线18的中断标志 } }
timer模块中有6个定时器,各个定时器之间相互独立,不共享资源。配合着TIMER_USER_Handler这个中断使用
ptimer模块中的api:
timer_init 初始化
timer_enable_int 使能中断
timer_get_int 查看是否产生中断
timer_clear_int 清除中断标记
//=======================================================================
//函数名称:timer_init
//函数返回:无
//参数说明:timer_No:时钟模块号(使用宏定义TIMER1、TIMER2、…)
// time_ms:定时器中断的时间间隔,单位为毫秒,合理范围:TIM1、TIM15、TIM16、TIM6、
// TIM7的中断的时间间隔合理范围为:1~2^16ms,TIM2的中断的时间间隔合理范围为:1~2^32ms
//功能概要:时钟模块初始化,其中TIM1为高级定时器,TIM2、TIM15、TIM16为通用定时器,
// TIM6、TIM7为基本定时器
//=======================================================================
void timer_init(uint8_t timer_No,uint32_t time_ms) { switch(timer_No) { case 1: { RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;//使能定时器时钟 TIM1->CR1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);//设置向上计数模式 TIM1->ARR = (uint16_t)(time_ms-1) ;//设置周期 TIM1->PSC = MCU_SYSTEM_CLK_KHZ-1;//设置预分频 TIM1->EGR = TIM_EGR_UG;//初始化计数器 TIM1->CR1 |= TIM_CR1_CEN_Msk;//计数器使能 break; } case 2: { RCC->APB1ENR1 |= RCC_APB1ENR1_TIM2EN;//使能定时器时钟 TIM2->CR1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);//设置向上计数模式 TIM2->ARR = (uint32_t)(time_ms-1) ;//设置周期 //time_ms*48000/PSC TIM2->PSC = MCU_SYSTEM_CLK_KHZ-1;//设置预分频 //分频之后的频率=选定的时钟源频率/(PSC+1), (ARR+1)*(PSC+1)/fclk TIM2->EGR = TIM_EGR_UG;//初始化计数器 TIM2->CR1 |= TIM_CR1_CEN_Msk;//计数器使能 break; } case 6: //TIM6只有向上计数 { RCC->APB1ENR1 |= RCC_APB1ENR1_TIM6EN; TIM6->ARR = (uint16_t)(time_ms-1) ; TIM6->PSC = MCU_SYSTEM_CLK_KHZ-1; TIM6->EGR = TIM_EGR_UG; TIM6->CR1 |= TIM_CR1_CEN_Msk; break; } case 7: //TIM7只有向上计数 { RCC->APB1ENR1 |= RCC_APB1ENR1_TIM7EN; TIM7->ARR = (uint16_t)(time_ms-1) ; TIM7->PSC = MCU_SYSTEM_CLK_KHZ-1; TIM7->EGR = TIM_EGR_UG; TIM7->CR1 |= TIM_CR1_CEN_Msk; break; } case 15: //TIM15只有向上计数 { RCC->APB2ENR |= RCC_APB2ENR_TIM15EN; TIM15->ARR = (uint16_t)(time_ms-1) ; TIM15->PSC = MCU_SYSTEM_CLK_KHZ-1; TIM15->EGR = TIM_EGR_UG; TIM15->CR1 |= TIM_CR1_CEN_Msk; break; } case 16: //TIM16只有向上计数 { RCC->APB2ENR |= RCC_APB2ENR_TIM16EN; TIM16->ARR = (uint16_t)(time_ms-1) ; TIM16->PSC = MCU_SYSTEM_CLK_KHZ-1; TIM16->EGR = TIM_EGR_UG; TIM16->CR1 |= TIM_CR1_CEN_Msk; break; } } }
//============================================================================ //函数名称:timer_enable_int //函数返回:无 //参数说明: timer_No:时钟模块号(使用宏定义TIMER1、TIMER2、…) //功能概要:时钟模块使能,开启时钟模块中断及定时器中断 //============================================================================ void timer_enable_int(uint8_t timer_No) { switch(timer_No) { case 1: { TIM1->DIER |= TIM_DIER_UIE; NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); //开中断控制器IRQ中断 break; } case 2: { TIM2->DIER |= TIM_DIER_UIE; NVIC_EnableIRQ(TIM2_IRQn); //开中断控制器IRQ中断 break; } case 6: { TIM6->DIER |= TIM_DIER_UIE; NVIC_EnableIRQ(TIM6_DAC_IRQn); //开中断控制器IRQ中断 break; } case 7: { TIM7->DIER |= TIM_DIER_UIE; NVIC_EnableIRQ(TIM7_IRQn); //开中断控制器IRQ中断 break; } case 15: { TIM15->DIER |= TIM_DIER_UIE; NVIC_EnableIRQ(TIM1_BRK_TIM15_IRQn); //开中断控制器IRQ中断 break; } case 16: { TIM16->DIER |= TIM_DIER_UIE; NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); //开中断控制器IRQ中断 break; } } }
//=================================================================== //函数名称:timer_get_int //参数说明: timer_No:时钟模块号(使用宏定义TIMER1、TIMER2、…) //功能概要:获取timer模块中断标志 //函数返回:中断标志 1=有对应模块中断产生;0=无对应模块中断产生 //=================================================================== uint8_t timer_get_int(uint8_t timer_No) { if(timer_No==1) { if((TIM1->SR & TIM_SR_UIF)==TIM_SR_UIF) return 1; } else if(timer_No==2) { if((TIM2->SR & TIM_SR_UIF)==TIM_SR_UIF) return 1; } else if(timer_No==6) { if((TIM6->SR & TIM_SR_UIF)==TIM_SR_UIF) return 1; } else if(timer_No==7) { if((TIM7->SR & TIM_SR_UIF)==TIM_SR_UIF) return 1; } else if(timer_No==15) { if((TIM15->SR & TIM_SR_UIF)==TIM_SR_UIF) return 1; } else if(timer_No==16) { if((TIM16->SR & TIM_SR_UIF)==TIM_SR_UIF) return 1; } return 0; }
//============================================================================ //函数名称:timer_clear_int //函数返回:无 //参数说明: timer_No:时钟模块号(使用宏定义TIMER1、TIMER2、…) //功能概要:定时器清除中断标志 //============================================================================ void timer_clear_int(uint8_t timer_No) { switch(timer_No) { case 1:TIM1->SR &= ~TIM_SR_UIF;break; case 2:TIM2->SR &= ~TIM_SR_UIF;break; case 6:TIM6->SR &= ~TIM_SR_UIF;break; case 7:TIM7->SR &= ~TIM_SR_UIF;break; case 15:TIM15->SR &= ~TIM_SR_UIF;break; case 16:TIM16->SR &= ~TIM_SR_UIF;break; } }