05:(寄存器开发)定时器一

定时器

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文件代码如下:

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文件的代码如下:

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

主函数文件的代码如下:

c 复制代码
#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文件的代码如下:

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

②主函数文件代码如下:

c 复制代码
/*
 * 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文件的代码如下:

c 复制代码
#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的翻转
    }
}

②主函数文件代码如下:

c 复制代码
#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文件的代码如下:

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);
	}
}
相关推荐
马浩同学40 分钟前
【ESP32】Arduino开发 | Timer定时器+定时器闹钟例程
c语言·单片机·嵌入式硬件·mcu
千千道1 小时前
STM32 实现 UDP 广播通信
stm32·单片机·物联网·网络协议·udp
EVERSPIN2 小时前
分享国产RISC-V单片机通用
单片机·嵌入式硬件·risc-v
TNT_JQR4 小时前
电子信息类专业技术学习及比赛路线总结(大一到大三)
单片机·嵌入式硬件·学习
钟剑锋-JeffChong6 小时前
智能手表(Smart Watch)项目
stm32·单片机·嵌入式开发·智能手表
jmlinux6 小时前
环形缓冲区(Ring Buffer)在STM32 HAL库中的应用:防止按键丢失
c语言·stm32·单片机·嵌入式硬件
江山如画,佳人北望7 小时前
智能平衡移动机器人-平台硬件电路
单片机·嵌入式硬件
江将好...8 小时前
定时器实验(Proteus 与Keil uVision联合仿真)
单片机·嵌入式硬件
地球空间-技术小鱼8 小时前
嵌入式系统学习
嵌入式硬件·学习