定时器
- 1、系统定时器SysTick
- 1.1、SysTick中断的使用
- 1.2、使用SysTick制作延迟函数
- 2、基本定时器
- 2.1、基本定时器中断的使用
- 2.2、使用基本定时器制作延时函数
1、系统定时器SysTick
1.1、SysTick中断的使用
①SysTcik系统滴答定时器和片上外设定时器不同,它在CPU内核中,如同NVIC和RCC一样,在使用它的时候,不需要在开启时钟,他们都内嵌在AHB系统时钟总线里面,因为一上电都已经开启了。
②SysTick是一个24位的向下计数器,而计数器的频率为AHB或者AHB/8。当重装载数值寄存器的值递减到0的时候(COUNTFLAG标志位由0变为1),系统定时器就产生一次中断(开启中断的情况下),以此循环往复。
③由于SysTick定时器在CPU内核里面,所以产生的中断源来源于CPU内部,我们在配置NVIC的时候不在需要配置中断源使能函数。
由于SysTick在CPU内部,所以使用手册里面没有介绍有关于它的寄存器,有关于它的介绍的手册在如下链接: link
如下图为:SysTick的控制和状态寄存器
如下图为:SysTick的重装载值寄存器
如下为使用SysTick定时器中断实现LED每隔1s亮灭一次
①SysTick_Timer.c文件代码如下:
#include "stm32f10x.h"
#include "LED.h"
/*
* @Function:SysTick定时器的初始化
*/
void SysTick_Timer_Init(void)
{
/* 1、配置时钟源:1 = AHB(72MHz),0 = AHB/8(9MHz) */
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;//选用的72MHz
/* 2、使能中断请求 */
SysTick->CTRL |= SysTick_CTRL_TICKINT;
/* 3、设置重装值,需要定时1s则重装值为72000000 > 2^24-1
所以先定时1ms,则定时器1ms重装值为72000
*/
SysTick->LOAD = 72000 - 1;
/* 4、配置优先级 */
NVIC_SetPriorityGrouping(4);
NVIC_SetPriority(SysTick_IRQn,0);
/* 5、使能计数器*/
SysTick->CTRL |= SysTick_CTRL_ENABLE;
}
/*
* @Function:中断服务函数:LED间隔1s闪烁
*/
void SysTick_Handler(void)
{
SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG;//清除标志位
static uint16_t count = 0;
count++;
if(count == 1000)
{
count = 0;
LED_Turn(LED0);
}
}
②LED.c文件的代码如下:
#include "stm32f10x.h" // Device header
#include "LED.h"
/*
* PA0~PA7引脚的初始化
*/
void LED_Init(uint32_t PA_x)
{
/* 1. 打开GPIOA的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 2. 将PA0~PA7配置为通用输出开漏模式 */
if(PA_x != PA_ALL)
{
GPIOA->CRL |= PA_x;
GPIOA->CRL &= ~(PA_x << 2);
}else{
GPIOA->CRL |= PA_ALL;
GPIOA->CRL &= PA_ALL;
}
}
/*
* 点亮LEDx
*/
void LED_ON(uint16_t LEDx)
{
GPIOA->ODR |= LEDx;
}
/*
* 熄灭LEDx
*/
void LED_OFF(uint16_t LEDx)
{
GPIOA->ODR &= ~LEDx;
}
/*
* 翻转LEDx
*/
void LED_Turn(uint16_t LEDx)
{
/* 如果是关闭那么就打开,如果是打开那么就关闭 */
if((GPIOA->ODR & LEDx) == 0)//关闭
{
LED_ON(LEDx);
}else{
LED_OFF(LEDx);
}
}
主函数文件的代码如下:
#include "stm32f10x.h"
#include "OLED.h"
#include "SysTick_Timer.h"
#include "LED.h"
int main(void)
{
OLED_Init();
LED_Init(PA_0);
SysTick_Timer_Init();
LED_OFF(LED0);
OLED_ShowString(1,1,"nihao");
while(1)
{
}
}
实物演示效果如下:
SysTick_Interrupt
1.2、使用SysTick制作延迟函数
通过获取计时器里面的值来制作延迟函数,函数的形式参数为我们手动写入的计数器的重装值,当计时器里面的数值还没有减到0时,让其一直等待循环,当计时器里面的数值减到0时,让其跳出循环,任何关闭定时器。
①Delay.c文件的代码如下:
#include "Delay.h"
/*
* @Function:延迟函数us
*/
void Delay_us(uint16_t us)
{
/* 1、设置时钟源 */
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;//选用AHB(72MHz)
/* 2、不需要中断 */
SysTick->CTRL &= ~SysTick_CTRL_TICKINT;
/* 3、给寄存器VAL写入数据,让标志位COUNTFLAG清零 */
SysTick->VAL = 0;
/* 4、设置重装值 */
SysTick->LOAD = 72 * us;//1us来一个脉冲计数
/* 5、使能定时器 */
SysTick->CTRL |= SysTick_CTRL_ENABLE;
/* 6、等待计数器到0,标准位变为1,跳出循环 */
while(!((SysTick->CTRL) & SysTick_CTRL_COUNTFLAG));
/* 6、关闭定时器 */
SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
}
/*
* @Function:延迟函数ms
*/
void Delay_ms(uint16_t ms)
{
while (ms--)
{
Delay_us(1000);
}
}
/*
* @Function:延迟函数s
*/
void Delay_s(uint16_t s)
{
while (s--)
{
Delay_ms(1000);
}
}
②主函数文件代码如下:
/*
* LED灯每隔1s闪烁一下,并且OLED上面的数值加1,加到10后重头开始加
*/
#include "stm32f10x.h"
#include "OLED.h"
#include "LED.h"
#include "Delay.h"
int main(void)
{
uint16_t Data = 0;
LED_Init(PA_0);
OLED_Init();
OLED_ShowString(1,1,"Data:");
LED_OFF(LED0);
while(1)
{
LED_Turn(LED0);
OLED_ShowNum(1,6,Data,2);
Data++;
if(Data == 11)
{
Data = 0;
}
Delay_ms(1000);
}
}
实物演示效果如下:
延迟函数
2、基本定时器
片上外设定时器:TIM6和TIM7是基本定时器。TM2~TIM5是通用定时器。TM1和TM8是高级定时器。而stm32f10c8t6只有定时器TIM1,TIM2,TIM3,TIM4。即1个高级定时器,3个通用定时器。
基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
这2个定时器是互相独立的,不共享任何资源。这个2个基本定时器只能向上计数,由于没有外部IO,所以只能计时,不能对外部脉冲进行计数。
功能:定时中断,主模式,触发DAC。
由上图所示:定时器TIM2~TIM7挂载中心ABP1上面,其中APB1的Fmax = 36MHz,所以APB1的分配系数 = 2。而由图中蓝色框中所得:给定时器TIM2 ~ TIM7提供时钟脉冲的频率F = 72MHz。下图为基本定时器中的结构图
由上图所示:传来的时钟脉冲通过PSC预分频器后在传递到计数器,PSC = 0,为1分频;PSC = 1,为2分频。其中PSC为16位的寄存器,所以最大分频系数 = 2^16 = 65536分频。
重装载值 = 99,则需要100个脉冲才能使99重装为),则相当于计数100次。自动重装载寄存器也是16位寄存器,所以最大的计数次数 = 2^16 = 65536次。
预加载寄存器和影子寄存器:
如上图PSC预分频寄存器和重装寄存器后面有阴影,则阴影部分就是影子寄存器。而时基单元的数据都是看影子寄存器里面的数据,
那什么是预加载寄存器喃?预加载寄存器是为了在时基单元工作的时候,更新影子寄存器里面的数据,而更新的时机是这个周期计数完成,下个周期计数器值从0开始的时候。例如重装值为20,而在时基单元工作的时候,我们想将重装值更新为15,那么我们将15的数据写入重装载寄存器的预加载寄存器里面,然后预加载寄存器会在下一个计数周期的计数值为0的时候将15更新到影子寄存器里面。当然我们也可以选用不启用预加载寄存器,那么写入的数据会立马更新到影子寄存器里面。
【注意】写入数据都是写入到预加载寄存器里面,影子寄存器是不允许写入数据的。
2.1、基本定时器中断的使用
由于stm32f10c8t6没有基本定时器,所以下面的代码不是使用stm32f103c8t6的支持包,而是使用stm32f103zet6的支持包编辑的。
下面是有关寄存器的介绍:
①Basic_Timer文件的代码如下:
#include "stm32f10x.h"
#include "LED.h"
/*
* @Function:基本定时器TIM6的初始化
*/
void Basic_Timer_Init(void)
{
/* 1、开启时钟 */
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
/* 2、选择时钟来源 */
//基本定时器只有一个时钟源,所以无需我们代码编写
/* 3、设置分频系数 */
TIM6->PSC = 7200 - 1;//分频系数为7200,则72MHZ/7200 = 10000Hz,1s/10000 = 0.1ms
//即每隔0.1ms,来一个脉冲,计数器+1
/* 4、设定重装载值 */
TIM6->ARR = 10000 - 1;//定时器1s
/* 5、使能定时器中断请求 */
TIM6->DIER |= 0x01;
/* 6、开始预加载模式 */
TIM6->CR1 |= TIM_CR1_ARPE;
/* 7、配置NVIC */
NVIC_SetPriorityGrouping(4);;
NVIC_SetPriority(TIM6_IRQn,0);
NVIC_EnableIRQ(TIM6_IRQn);
/* 7、使能计数器 */
TIM6->CR1 |= TIM_CR1_CEN;
}
/*
* @Function:基本定时器TIM6中断的服务函数
*/
void TIM6_IRQHandler(void)
{
if((TIM6->SR) & TIM_SR_UIF)//判断标志位
{
TIM6->SR &= ~TIM_SR_UIF;//清除标志位
LED_Turn(LED0);//LED0的翻转
}
}
②主函数文件代码如下:
#include "stm32f10x.h"
#include "OLED.h"
#include "LED.h"
#include "Basic_Timer.h"
int main(void)
{
LED_Init(PA_0);
LED_OFF(LED0);
Basic_Timer_Init();
while(1)
{
}
}
综上:总结SysTick定时器和基本定时器的区别如下:
①时钟来源不同:滴答定时器的时钟来源于系统总线AHB,基本定时器的时钟来源于APB1
②计数不同:滴答定时器是向下计数(24位),而基本定时器是向上计数(16位)
③所处位置不同:滴答定时器位于芯片内核,使用时不用开启时钟,中断时也不用开启NVIC_EnableIRQ()。基本定时器属于片上外设,使用时要开启时钟,使用中断时也要开启NVIC_EnableIRQ()
④内部结构不同:滴答定时器没有影子寄存器和预加载寄存器,也没有预分频寄存器。
2.2、使用基本定时器制作延时函数
①Delay.c文件的代码如下:
void Delay_us(uint16_t us)
{
/* 1、开启时钟 */
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
/* 2、选择时钟来源 */
//基本定时器只有一个时钟源,所以无需我们代码编写
/* 3、设置分频系数 */
TIM6->PSC = 72 - 1;//分频系数为7200,则72MHZ/72 = 1MHz,1s/1MHz = 1us
//即每隔1us,来一个脉冲,计数器+1
/* 4、设定重装载值 */
TIM6->ARR = us - 1;
/* 5、初始化一下计数器和预分频器 */
TIM6->EGR |= TIM_EGR_UG;
/* 6、初始化一下计数器和预分频器会使UIF置位,清除标志位 */
TIM6->SR &= ~TIM_SR_UIF;
/* 7、开启计数器 */
TIM6->CR1 |= TIM_CR1_CEN;
/* 8、等待循环完成 */
while(!((TIM6->SR) & TIM_SR_UIF));
/* 9、关闭计数器 */
TIM6->CR1 &= ~TIM_CR1_CEN;
}
void Delay_ms(uint16_t ms)
{
while (ms--)
{
Delay_us(1000);
}
}
void Delay_s(uint16_t s)
{
while (s--)
{
Delay_ms(1000);
}
}