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 );

}

关于不同的时钟源:

在定时器模块中,不同的时钟源是用于驱动定时器计数的时钟信号源,不同的时钟源对定时器的工作方式和性能有重要影响。以下是一些常见的时钟源以及它们的区别:

  1. 内部时钟源

    • 内部时钟源通常是微控制器内部的振荡器或时钟信号。

    • 优点:内部时钟源通常是稳定的,不受外部环境的影响。

    • 缺点:内部时钟源的频率通常相对较低,可能不适合需要高精度定时的应用。

  2. 外部时钟源

    • 外部时钟源通常来自外部晶振或时钟信号源。

    • 优点:外部时钟源可以提供更高的精度和稳定性。

    • 缺点:外部时钟源可能需要额外的外部元件,并且受外部环境干扰。

  3. PLL(锁相环)时钟源

    • PLL 允许您将一个时钟源的频率倍增,以提供更高的时钟频率。

    • 优点:可以提供高精度的时钟源,适用于需要高性能的应用。

    • 缺点:配置和维护 PLL 可能需要额外的复杂性。

  4. 外部触发源

    • 定时器模块有时可以配置为接收来自外部信号源的触发。

    • 优点:允许在特定事件发生时启动定时器。

    • 缺点:需要外部触发源的支持,并且具有一定的复杂性。

  5. 其他特殊时钟源

    • 一些定时器模块可能支持特殊的时钟源,如边沿对齐时钟、外部同步时钟等,用于特定的应用需求。

选择合适的时钟源通常取决于您的应用需求。内部时钟源通常足够用于基本的定时任务,而外部时钟源和 PLL 适用于需要更高精度和性能的应用。外部触发源通常在需要精确的事件同步时非常有用。

请注意,时钟源的选择还会影响定时器的最大计数范围和分辨率。一些时钟源可以提供更高的计数频率,从而提供更高的分辨率,而其他时钟源可能限制了计数范围。因此,在选择时钟源时,还要考虑定时器的计数要求。

 

在嵌入式系统中,"看门狗"(Watchdog)通常指的是看门狗定时器(Watchdog Timer),它是一种用于监视系统运行状态的硬件定时器。看门狗定时器的主要目的是在系统死锁或崩溃时执行重启操作,从而提高系统的稳定性和可靠性。

以下是关于看门狗定时器的一些重要信息:

  1. 工作原理:

    • 看门狗定时器是一个硬件计时器,它会定期重置。如果在设定的时间间隔内未重置看门狗定时器,它将认为系统已经死锁或出现故障。

    • 看门狗定时器通常以周期性的方式工作,例如每隔几秒重置一次。

  2. 重置操作:

    • 在正常运行时,系统会定期重置看门狗定时器,防止其超时。

    • 如果系统出现问题,无法在规定时间内重置看门狗定时器,定时器将超时,并触发重置操作。

  3. 重启系统:

    • 当看门狗定时器超时时,它会执行一个重置操作,导致整个系统被重新启动。

    • 这可以帮助恢复系统到正常状态,即使系统出现故障或死锁。

  4. 应用场景:

    • 看门狗定时器常用于嵌入式系统、自动控制系统、远程设备监控等需要高度稳定性和可靠性的应用。

    • 它可以防止系统因软件错误、死锁或其他异常情况而无法恢复。

  5. 配置:

    • 看门狗定时器的超时时间可以根据应用需求进行配置,通常在几秒到几分钟之间。

    • 它的配置和启动通常需要一组特定的寄存器设置。

  6. 注意事项:

    • 在使用看门狗定时器时,确保在正常运行时定期重置它,以避免系统意外重启。

    • 确保在程序中正确处理和报告故障,以防止不必要的系统重启。

总之,看门狗定时器是一种用于提高嵌入式系统稳定性和可靠性的重要工具。它可以帮助检测并应对系统中的异常情况,确保系统能够在出现问题时自动恢复正常运行。

 

RTC实时时钟模块:

RTC本质上是一个掉电后还会继续运行的计时器,有计时功能,也会触发中断。

RTC是一个独立的定时器,可以提供时钟日历的功能,也可以用于低功耗模式下的自动唤醒单元。

常用api:

  RTC_Init  初始化

  RTC_Set_Date  RTC_Set_Time  设置时钟的日期,时间

  RTC_Get_Date RTC_Get_Time  获取时钟的日期,时间

  RTC_Set_PeriodWakeUp  设置周期唤醒时间

  RTC_PeriodWKUP_Get_Int  获取中断

  RTC_PeriodWKUP_Clear  清除唤醒中断标志

 

中断的符号引用: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模块:

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;
    }
}

 

 

 

posted @ 2023-10-22 09:52  深渊之巅  阅读(70)  评论(0编辑  收藏  举报