精华内容
下载资源
问答
  • STM32中断

    千次阅读 2019-02-08 22:35:47
    这里我们首先 STM32 IO 口中断的一些基础概念。STM32 的每个 IO 都可以作为外部中断中断输入口,这点也是 STM32 的强大之处。STM32F103 的中断控制器支持 19 个外部中断/ 事件请求。每个中断设有状态位,每个中断...

    这里我们首先 STM32 IO 口中断的一些基础概念。STM32 的每个 IO 都可以作为外部中断
    的中断输入口,这点也是 STM32 的强大之处。STM32F103 的中断控制器支持 19 个外部中断/
    事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F103 的
    19 个外部中断为:
    线 0~15:对应外部 IO 口的输入中断。
    线 16:连接到 PVD 输出。
    线 17:连接到 RTC 闹钟事件。
    线 18:连接到 USB 唤醒事件。
    从上面可以看出,STM32 供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不
    止 16 个,那么 STM32 是怎么把 16 个中断线和 IO 口一一对应起来的呢?于是 STM32 就这样
    设计,GPIO 的管教 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 0~15。这样每个中
    断线对应了最多 7 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、
    GPIOE.0、GPIOF.0、GPIOG.0。而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置来
    决定对应的中断线配置到哪个 GPIO 上了。下面我们看看 GPIO 跟中断线的映射关系图:
    在这里插入图片描述
    在库函数中,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig()来实现的:

    void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
    

    该函数将 GPIO 端口与中断线映射起来,使用范例是:

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
    

    将中断线 2 与 GPIOE 映射起来,那么很显然是 GPIOE.2 与 EXTI2 中断线连接了。设置好中断
    线映射之后,那么到底来自这个 IO 口的中断是通过什么方式触发的呢?接下来我们就要设置
    该中断线上中断的初始化参数了。
    中断线上中断的初始化是通过函数 EXTI_Init()实现的。EXTI_Init()函数的定义是:

    void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
    

    下面我们用一个使用范例来说明这个函数的使用:

    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line=EXTI_Line4;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure); //根据 EXTI_InitStruct 中指定的
    //参数初始化外设 EXTI 寄存器
    

    上面的例子设置中断线 4 上的中断为下降沿触发。STM32 的外设的初始化都是通过结构体来设
    置初始值的,这里就不罗嗦结构体初始化的过程了。我们来看看结构体 EXTI_InitTypeDef 的成
    员变量:

    typedef struct
    {
    uint32_t EXTI_Line;
    EXTIMode_TypeDef EXTI_Mode;
    EXTITrigger_TypeDef EXTI_Trigger;
    FunctionalState EXTI_LineCmd;
    }EXTI_InitTypeDef;
    

    从定义可以看出,有 4 个参数需要设置。第一个参数是中断线的标号,取值范围为
    EXTI_Line0~EXTI_Line15。这个在上面已经讲过中断线的概念。也就是说,这个函数配置的是某个中断线上的中断参数。第二个参数是中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event。第三个参数是触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上
    升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发
    EXTI_Trigger_Rising_Falling,相信学过 51 的对这个不难理解。最后一个参数就是使能中断线了。
    我们设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既
    然是外部中断,涉及到中断我们当然还要设置 NVIC 中断优先级。这个在前面已经讲解过,这
    里我们就接着上面的范例, 设置中断线 2 的中断优先级。

     NVIC_InitTypeDef NVIC_InitStructure;
        NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;  //子优先级 2
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
        NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化
    

    上面这段代码相信大家都不陌生,我们在前面的串口实验的时候讲解过,这里不再讲解。
    我们配置完中断优先级之后,接着我们要做的就是编写中断服务函数。中断服务函数的名
    字是在 MDK 中事先有定义的。这里需要说明一下,STM32 的 IO 口外部中断函数只有 6 个,
    分别为:

    EXPORT EXTI0_IRQHandler
    EXPORT EXTI1_IRQHandler
    EXPORT EXTI2_IRQHandler
    EXPORT EXTI3_IRQHandler
    EXPORT EXTI4_IRQHandler
    EXPORT EXTI9_5_IRQHandler
    EXPORT EXTI15_10_IRQHandler
    

    中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中
    断线 10-15 共用中断函数 EXTI15_10_IRQHandler。在编写中断服务函数的时候会经常使用到两
    个函数,第一个函数是判断某个中断线上的中断是否发生(标志位是否置位):
    ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
    这个函数一般使用在中断服务函数的开头判断中断是否发生。另一个函数是清除某个中断线上
    的中断标志位:
    void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
    这个函数一般应用在中断服务函数结束之前,清除中断标志位。
    常用的中断服务函数格式为:
    void EXTI3_IRQHandler(void)
    {
    if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生
    {
    中断逻辑…
    EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE 上的中断标志位
    }
    }
    在这里需要说明一下,固件库还提供了两个函数用来判断外部中断状态以及清除外部状态
    标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。
    只是在 EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而
    EXTI_GetFlagStatus 直接用来判断状态标志位。
    讲到这里,相信大家对于 STM32 的 IO 口外部中断已经有了一定了了解。下面我们再总结一
    下使用 IO 口外部中断的一般步骤:
    1)初始化 IO 口为输入。
    2)开启 AFIO 时钟
    3)设置 IO 口与中断线的映射关系。
    4)初始化线上中断,设置触发条件等。
    5)配置中断分组(NVIC),并使能中断。
    6)编写中断服务函数。
    通过以上几个步骤的设置,我们就可以正常使用外部中断了。
    main.c

    #include "led.h"
    #include "delay.h"
    #include "key.h"
    #include "sys.h"
    #include "usart.h"
    #include "exti.h"
    #include "beep.h"
    
    
    	
     int main(void)
     {		
     
    	delay_init();	    	 //延时函数初始化	  
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
    	uart_init(115200);	 //串口初始化为115200
    	LED_Init();		  		//初始化与LED连接的硬件接口 
    	BEEP_Init();		 	//初始化蜂鸣器IO
    	EXTIX_Init();         	//初始化外部中断输入 
    	LED0=0;					//先点亮红灯
    	while(1)
    	{	    
    		printf("OK\r\n");	
    		delay_ms(1000);	  
    	}	 
    }
    
    

    exti.c

    #include "exti.h"
    #include "led.h"
    #include "key.h"
    #include "delay.h"
    #include "usart.h"
    #include "beep.h"
    
    
    //外部中断0服务程序
    void EXTIX_Init(void)
    {
     
       	EXTI_InitTypeDef EXTI_InitStructure;
     	  NVIC_InitTypeDef NVIC_InitStructure;
    
        KEY_Init();	 //	按键端口初始化
    
      	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	//使能复用功能时钟
    
    
    
       //GPIOE.3	  中断线以及中断初始化配置 下降沿触发 //KEY1
      	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
      	EXTI_InitStructure.EXTI_Line=EXTI_Line3;
      	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	
      	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
      	EXTI_Init(&EXTI_InitStructure);	  	//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
    
       //GPIOE.4	  中断线以及中断初始化配置  下降沿触发	//KEY0
      	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
      	EXTI_InitStructure.EXTI_Line=EXTI_Line4;
      	EXTI_Init(&EXTI_InitStructure);	  	//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
    
    
       //GPIOA.0	  中断线以及中断初始化配置 上升沿触发 PA0  WK_UP
     	  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); 
    
      	EXTI_InitStructure.EXTI_Line=EXTI_Line0;
      	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
      	EXTI_Init(&EXTI_InitStructure);		//根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
    
    
      	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;			//使能按键WK_UP所在的外部中断通道
      	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	//抢占优先级2, 
      	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;					//子优先级3
      	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能外部中断通道
      	NVIC_Init(&NVIC_InitStructure); 
    
      	NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;			//使能按键KEY1所在的外部中断通道
      	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	//抢占优先级2 
      	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;					//子优先级1 
      	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能外部中断通道
      	NVIC_Init(&NVIC_InitStructure);  	  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
    
      	NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;			//使能按键KEY0所在的外部中断通道
      	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;	//抢占优先级2 
      	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;					//子优先级0 
      	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;								//使能外部中断通道
      	NVIC_Init(&NVIC_InitStructure);  	  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
     
    }
    
    //外部中断0服务程序 
    void EXTI0_IRQHandler(void)
    {
    	delay_ms(10);//消抖
    	if(WK_UP==1)	 	 //WK_UP按键
    	{				 
    		BEEP=!BEEP;	
    	}
    	EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位  
    }
     
    
    //外部中断3服务程序
    void EXTI3_IRQHandler(void)
    {
    	delay_ms(10);//消抖
    	if(KEY1==0)	 //按键KEY1
    	{				 
    		LED1=!LED1;
    	}		 
    	EXTI_ClearITPendingBit(EXTI_Line3);  //清除LINE3上的中断标志位  
    }
    
    void EXTI4_IRQHandler(void)
    {
    	delay_ms(10);//消抖
    	if(KEY0==0)	 //按键KEY0
    	{
    		LED0=!LED0;
    		LED1=!LED1; 
    	}		 
    	EXTI_ClearITPendingBit(EXTI_Line4);  //清除LINE4上的中断标志位  
    }
     
    
    

    exti.h

    #ifndef __EXTI_H
    #define __EXIT_H	 
    #include "sys.h"
    
    void EXTIX_Init(void);//外部中断初始化		 					    
    #endif
    
    
    
    展开全文
  • STM32延时中断LED

    2014-12-03 13:03:18
    LED延时中断函数,输入数字,LED就会延时多少秒,固定的数字
  • stm32延时和精准延时函数

    千次阅读 2019-05-02 16:03:46
    1.stm32延时函数 粗延时的意思就是延时时间不太准确,一般用在对延时时间要求不严格的场合。这种延时方式是采用软件延时,但因为编译器会在编译的时候加上一些其他辅助指令,所以不能确定C程序的准确运行时间。...

    1.stm32粗延时函数

    粗延时的意思就是延时时间不太准确,一般用在对延时时间要求不严格的场合。这种延时方式是采用软件延时,但因为编译器会在编译的时候加上一些其他辅助指令,所以不能确定C程序的准确运行时间。我们可以采用下面的方法进行估算:

    假设stm32 MCU系统时钟(SYSCLK)为48MHz,指令周期为4个系统时钟,则一个指令周期时长为1/12微秒。
    若要让计时单位为微秒(us),则可以让CPU空转约12次,即在软件上可以令减数周期变量为12,但由于存在其他辅助指令,所以可以将这个减数周期减小一些,比如10。

    类似地,若以毫秒(ms)为计时单位,则可以让CPU空转12000次。空转次数越多,则其他辅助指令占用时间相对越短。

    以微秒(us)为单位进行延时:

    void delay_us(u16 us_time)
    {    
      u16 i=0;  
      while(us_time--)
      {
         i=10;           // 这要根据系统时钟频率进行计算
         while(i--) ;    // 延时主操作,空操作
      }
    }
    

    以毫秒(ms)为单位进行延时:

    void delay_ms(u16 ms_time)
    {    
        u16 i=0;      
        while(ms_time--)
        { 
            i=12000;       // 以ms为单位
            while(i--) ;    
        }
    }
    

    2.stm32精准延时函数

    精准延时,就要依靠硬件来完成。这个硬件就是硬核Cortex-Mx的Systick定时器。Systick定时器就是系统滴答定时器(不需要CPU干涉),一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值,并发出中断请求。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

    Systick定时器的计数频率可以设定为系统频率或系统频率的1/8。假设系统频率为48MHz,则Systick的频率为6MHz。
    这个可以由下面函数设定:

    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);  
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);

    若计时单位为微秒,则滴答定时器要计数6次;若计时单位为毫秒,则计数6000次。

    有两种方法进行精确定时:中断方式和非中断方式。

    2.1 中断方式

    ①配置SysTick中断函数,让延时时间变量在中断中递减。中断函数:

    void SysTick_Handler(void)
    {
        if (TimingDelay != 0x00)    //TimingDelay是一个用户全局变量,代表要延时的时间。
        {
            TimingDelay --;
        }
    }

    在用户源文件中定义:

    extern __IO uint32_t TimingDelay;

    这里要注意,TimingDelay要是需要被其他位置的源文件调用,则要加extern前缀或不加(默认);若只由自身源文件内调用,则可以加上static前缀,防止被其他源文件混用。这个要根据程序实际情况设定。

    ②设置SysTick的重装载值,并配置中断触发。

    if(SysTick_Config(SystemCoreClock/1000))  //配置滴答计数值及滴答中断,这里一个计时周期为1ms
    {                                         //SystemCoreClock为系统时钟频率(Hz)
        while(1);                             // 返回1,表示配置失败;0表示成功
    }
    
    //若设定计时周期为1us,则如下
    if(SysTick_Config(SystemCoreClock/1000000))
    {
        while(1);
    }

    SysTick_Config(uint32_t ticks)是用来初始化系统定时器及其中断的,并开启系统定时器及产生周期性中断。ticks是定时器计数次数。该函数存放在core_cm0(3/4/7...).h中。

    ③延时函数

    延时函数所能做的就是给延时变量TimeDelay赋值,并等待SysTick中断。

    static void Delay(__IO uint32_t nTime)  // 延时nTime单位个时间
    {
        TimingDelay = nTime;
        while(TimingDelay != 0) ;         // 静静等待
    }

    2.2 非中断方式

    中断方式最大的弊端就是稍显繁琐,而非中断方式则直接得多。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。SysTick 的 counter 从 reload值往下递减到 0 的时候, CTRL 寄存器的位 16:countflag 会置 1,且读取该位的值可清 0,
    我们可以直接操作寄存器,在等待中使用软件查询的方法来实现延时。

    //不进入systic中断,微秒级延时
    void delay_us(__IO uint32_t us)
    {
     uint32_t i;
     SysTick_Config(SystemCoreClock/1000000);
     
     for (i=0;i<us;i++)
     {
        while( !((SysTick->CTRL)&(1<<16)) ) ;  //等待,计数器的值减小到0时,CRTL寄存器的位16会置1
     }
     
     SysTick->CTRL &=(~SysTick_CTRL_ENABLE_Msk);  // 关闭定时器
    }
     
    // 若是延时单位为毫秒级,则将分子1000000改成1000.

    在网上有网友用如下更底层的方法,其原理与上面的程序一致:

    SYSTICK 的时钟设定为HCLK 时钟的1/8,这里选用系统时钟频率为72MHz,所以SYSTICK的时钟为9M,即SYSTICK定时器以9M的频率递减。

    //仿原子延时,不进入systic中断
    void delay_us(u32 nus)   //us为单位
    {
     u32 temp;
     SysTick->LOAD = 9*nus;
     SysTick->VAL=0X00;   //清空计数器
     SysTick->CTRL=0X01;  //使能,减到零是无动作,采用外部时钟源
     do
     {
      temp=SysTick->CTRL;      //读取当前倒计数值
     }while((temp&0x01)&&(!(temp&(1<<16))));  //等待时间到达
         SysTick->CTRL=0x00;   //关闭计数器
        SysTick->VAL =0X00;    //清空计数器
    }
    
    
    void delay_ms(u16 nms)    // ms为单位
    {
     u32 temp;
     SysTick->LOAD = 9000*nms;
     SysTick->VAL=0X00;      //清空计数器
     SysTick->CTRL=0X01;     //使能,减到零是无动作,采用外部时钟源
     do
     {
      temp=SysTick->CTRL;       //读取当前倒计数值
     }while((temp&0x01)&&(!(temp&(1<<16))));   //等待时间到达
        SysTick->CTRL=0x00;     //关闭计数器
        SysTick->VAL =0X00;     //清空计数器
    }

    2.3 SysTick寄存器介绍

    SysTick->CTRL

    位段

    名称

    类型

    复位值

    描述

    16

    COUNTFLAG

    R

    0

    如果在上次读本寄存器后systick已为0,则该位为1,若 读该位自动清零

    2

    CLKSOURCE

    RW

    0

    0:外部时钟源 1:内部时钟

    1

    TICKINT

    RW

    0

    0:减到0无动作;1:减到0产生systick异常请求

    0

    ENABLE

    RW

    0

    systick定时器使能位

    SysTick-> LOAD

    位段

    名称

    类型

    复位值

    描述

    23:0

    RELOAD

    RW

    0

    减到0时被重新装载的值

    SysTick-> VAL

    位段

    名称

    类型

    复位值

    描述

    23:0

    CURRENT

    RW

    0

    读取时返回当前倒计数的值,写则清零,同时还会清除在systick控制及状态寄存器中的COUNTFLAG标志

    SysTick-> CALIB 不常用,在这里也没用到,有兴趣的读者可查阅ST关于Cortex-m0(3/4/..)的manual。

     

    展开全文
  • STM32F103延时函数

    2016-02-17 17:09:54
    利用FOR循环,通过示波器观察,针对STM32F103进行的us,ms,s的延时,精度稍有误差,不影响使用
  • STM32使用SysTick定时器和基本定时器实现非中断延时。 1. 使用SysTick定时器实现非中断延时 SysTick定时器是一个24位的递减计数器。每次使用前,计数器必须先清零,然后计数器从设定的值开始递减。 下面的代码经...

    STM32使用SysTick定时器和基本定时器实现非中断延时。

    1. 使用SysTick定时器实现非中断延时

    SysTick定时器是一个24位的递减计数器。每次使用前,计数器必须先清零,然后计数器从设定的值开始递减。

    下面的代码经测定,非常稳定。

    /* 设置延时时间,延时单位为1us  */
    void delay_us(unsigned int delay_us)
    {
            unsigned int delay_ticks;
            SysTick->LOAD    = SysTick_LOAD_RELOAD_Msk - 1;    //0xFFFFFF - 1  设置重装载寄存器的值
            SysTick->VAL     = 0;  //计数器清零,必须设置,否则计时不准确
            SysTick->CTRL  |= SysTick_CTRL_CLKSOURCE_Msk;  //使用内部时钟,SytTick计数器时钟为72MHz
            SysTick->CTRL |=  SysTick_CTRL_ENABLE_Msk;     //使能SysTick,不进入中断
            delay_ticks = SysTick_LOAD_RELOAD_Msk - delay_us*72;
            while(1) 
            {
                if (SysTick->VAL <= delay_ticks)
                {
                    break;
                }
            }
            SysTick->CTRL = 0; //关闭SysTick定时器
    }

    /* 设置延时时间,延时单位为1ms  */
    void delay_ms(unsigned int delay_ms)
    {
        int i;
        
        for(i = 0; i < delay_ms; i++)
        {
            delay_us(1000);
        }
    }

    2.使用基本定时器实现非中断延时

    STM32的基本定时器是一个16位的递增计数器。使用前,先初始化。

    void TIM6_Base_Init(void)
    {
      TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);//开启定时器时钟
      TIM_TimeBaseStructure.TIM_Period = 0XFFFF - 1;    //设置预装在寄存器的值
      TIM_TimeBaseStructure.TIM_Prescaler= 71;       //设置时钟分频,分频后计数器时钟为1MHz
      TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);    
      TIM_Cmd(TIM6, ENABLE);    
    }
    void delay_us(unsigned int delay_us)
    {
        TIM6->CNT = 0;      //计数器清零
        while (TIM6->CNT < delay_us);   
    }
    void delay_ms(unsigned int delay_ms)
    {
        while (delay_ms--)
        {
            delay_us(1000);
        }
    }
     

    展开全文
  • STM32 精准延时

    千次阅读 2019-02-14 20:33:45
    延时,就是停在那,啥都不干,发呆。精准延时,就是发呆多长时间,是精确的。 比如,要求某个IO口维持低电平1毫秒后,再维持高电平3毫秒,就需要把IO口拉高,然后延时1毫秒,再拉低,再维持3毫秒。 类似这种情况在...

    延时,就是停在那,啥都不干,发呆。精准延时,就是发呆多长时间,是精确的。

    比如,要求某个IO口维持低电平1毫秒后,再维持高电平3毫秒,就需要把IO口拉高,然后延时1毫秒,再拉低,再维持3毫秒。

    类似这种情况在硬件接口时序里经常遇到,比如,用IO口模拟SPI协议,用IO口模拟I2C协议等等。

     

    既然延时就是发呆,那我们让系统发发呆就得了呗,让它执行空代码,执行很多很多很多,就能达到预期的效果。

    for(i=0; i<20000; i++)
    
    {
    
        for(j=0; j<20000; j++)
    
        {
    
            ;;;;;;;;;;;;;;;;;;;;;;;;;
    
        }
    
    }

     

    但这种方法很难测量它发呆的时间,在51单片机中,我们可以通过大概计算代码周期来确定必须运行多少个循环能达到指它的时间。

    STM32是三级流水线作业,指令周期不好算,用这种方式延时,精准度有限。

     

    既然定时器能够计时,那就用它来延时吧。

    我们就来讲一讲使用定时器来做微秒级(us)的和毫秒级(ms)的精准延时吧。

     

    上一篇《STM32 基本定时器》我们讲过定时器,设置定时器并启动后,定时器会不断数数,可以正着数也可以倒着数,还可以先正着数后倒着数。

    既然这样,我们要做一个确定时间的延时,我们就可以启动一个的定时器,让它数数,在它数数这时间内,我们让程序发呆 while(1){},直到数数完成后,再清醒过来 break;

    那么,我们就可以这样写程序:

    喂,我要定时器数数 xx 微秒。
    
    while(1)
    
    {
    
        if(定时器数完了)
    
            break;
    
    }

     

    依葫芦画瓢,我们照着上节课的TIM1,配置一个TIM2,作为精准延时用的定时器。

    这个就不需要中断咯,因为我们让程序发呆,让它边发呆边判断定时器到时间了没,不需要中断。

    PS:当然,你也可以用中断做,让它一直发呆,边发呆边判断标志位,定时器到时间了,设置标志位,发呆程序退出,不过,何必呢?

    怎么判断定时器到时间了没呢?

    可以设置向下计数,那计数寄存器就会从xxxxx开始数数,一直到0,0了,就是到时间了,所以,只需要不断地读这个计数寄存器就行了。

     

    画瓢,初始化:

    设置为向下计数,到时判断计数是否为0,若为0,说明计时完成,发呆可以结束。

    不需要中断,也暂时不开启,要延时的时候才开启;

    /* TIM2init function */
    
    void MX_TIM2_Init(void)    
    
    {
    
      TIM_ClockConfigTypeDef sClockSourceConfig;
    
      TIM_MasterConfigTypeDef sMasterConfig;
    
    
      htim2.Instance = TIM2;
    
      htim2.Init.Prescaler = 1;                // 这个随便设置,后面计时前再设置
    
      htim2.Init.CounterMode = TIM_COUNTERMODE_DOWN;
    
      htim2.Init.Period = 1;                    // 这个随便设置,后面计时前再设置
    
      htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    
      htim2.Init.RepetitionCounter = 0;
    
      htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    
      if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
    
      {
    
        _Error_Handler(__FILE__, __LINE__);
    
      }
    
    
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    
      if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
    
      {
    
        _Error_Handler(__FILE__, __LINE__);
    
      }
    
    
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    
      if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
    
      {
    
        _Error_Handler(__FILE__, __LINE__);
    
      }
    
    
      //HAL_TIM_Base_Start(&htim2);                  // 这里不需要中断,也暂时不需要开启,计时的时候再开咯。
    
    
    }

    继续把初始化瓢画完,这个瓢就不需要设置中断了:

    void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
    
    {
    
    
      if(tim_baseHandle->Instance==TIM1)
    
      {
    
      /* USER CODE BEGIN TIM1_MspInit 0 */
    
        
    
      /* USER CODE END TIM1_MspInit 0 */
    
        /* TIM1 clock enable */
    
        __HAL_RCC_TIM1_CLK_ENABLE();
    
    
        /* TIM1 interrupt Init */
    
        HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 0, 0);
    
        HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
    
      /* USER CODE BEGIN TIM1_MspInit 1 */
    
        
    
      /* USER CODE END TIM1_MspInit 1 */
    
      }
    
        else if(tim_baseHandle->Instance==TIM2)
    
        {
    
            __HAL_RCC_TIM2_CLK_ENABLE();
    
        }  
    
    
    }
    
    
    void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
    
    {
    
    
      if(tim_baseHandle->Instance==TIM1)
    
      {
    
      /* USER CODE BEGIN TIM1_MspDeInit 0 */
    
    
      /* USER CODE END TIM1_MspDeInit 0 */
    
        /* Peripheral clock disable */
    
        __HAL_RCC_TIM1_CLK_DISABLE();
    
    
        /* TIM1 interrupt Deinit */
    
        HAL_NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn);
    
      /* USER CODE BEGIN TIM1_MspDeInit 1 */
    
    
      /* USER CODE END TIM1_MspDeInit 1 */
    
      }
    
        else if(tim_baseHandle->Instance==TIM2)
    
        {
    
            __HAL_RCC_TIM2_CLK_DISABLE();
    
        }  
    
    }

    这个瓢在main()里面,初始化。

      /* USER CODE END SysInit */
    
    
      /* Initialize all configured peripherals */
    
      MX_GPIO_Init();
    
      MX_DMA_Init();
    
      MX_UART4_Init();
    
      MX_TIM1_Init();
    
      MX_TIM2_Init();        // 刚刚画的瓢!
    
      /* USER CODE BEGIN 2 */
    
        USR_UartInit();
    
      /* USER CODE END 2 */
    
    

    初始化瓢画完了,接下来,没有瓢画了,得自己做瓢,写延时函数,基本思想就是,设置一个计数器,数数,数到0完事。

    先看一下TIM2的Internal Clock = 60Mhz,知道这个,才可以确定怎么设置计数器,具体参照《STM32 基本定时器》,有详细图解。

    做两个瓢,一个负责微秒级延时,一个负责毫秒级延时。

    先声明一下,在tim.h里面加这个

    /* USER CODE BEGIN Private defines */
    
    #define USER_Delay1us(us)            TIM2_Delay1us(us)
    
    #define USER_Delay1ms(ms)            TIM2_Delay1ms(ms)
    
    /* USER CODE END Private defines */
    
    
    extern void _Error_Handler(char *, int);
    
    
    void MX_TIM1_Init(void);
    
    void MX_TIM2_Init(void);
    
    
    void TIM1_Handler(void);
    
    
    /* USER CODE BEGIN Prototypes */
    
    void TIM2_Delay1us(uint16_t cnt);    
    
    void TIM2_Delay1ms(uint16_t cnt);    
    
    /* USER CODE END Prototypes */
    
    

    说一下为什么要弄这两个宏定义

    #define USER_Delay1us(us)            TIM2_Delay1us(us)
    
    #define USER_Delay1ms(ms)            TIM2_Delay1ms(ms)

    为了移植或者修改方便啊。

    难道还想如果改用TIM3、TIM4、TIMxxxxxx做延时的时候,把工程里面所有用到延时的地方都修改一遍?

    void TIM2_Delay1us(uint16_t cnt)    
    
    {
    
        // Prescalar
    
        // TIM2 Internal Clock is 60,000,000Hz, So, if delay 1us, Prescalar must set 60-1=59
    
        // HAL_RCC_GetPCLK1Freq()<<1/1000/1000;
    
        __HAL_TIM_SET_PRESCALER(&htim2, 59);            // 设置预分频,这个决定定时器数一次需要多长时间。
    
    
        // Count
    
        __HAL_TIM_SET_AUTORELOAD(&htim2, cnt);     // 设置数数,这个决定数多少个数
    
    
        SET_BIT(htim2.Instance->EGR, TIM_EGR_UG);        // 产生一个UEV(Update Event),用来更新定时器。
    
    
        __HAL_TIM_ENABLE(&htim2);                                // 开启定时器。
    
    
        // 看一下数到0了没有。
    
        while(1)
    
        {
    
            if(!__HAL_TIM_GET_COUNTER(&htim2))
    
                break;
    
        }
    
    
        // 关掉定时器,没必要再数了。
    
        __HAL_TIM_DISABLE(&htim2);
    
    }
    
    

    这个可以以微秒级延时为葫芦,画瓢~~~

    void TIM2_Delay1ms(uint16_t cnt)    
    
    {
    
        // Prescalar
    
        // TIM2 Internal Clock is 60,000,000Hz, So, if delay 1ms, Prescalar must set 60000-1=59999
    
        // HAL_RCC_GetPCLK1Freq()<<1/1000/1000;
    
        __HAL_TIM_SET_PRESCALER(&htim2, 59999);
    
    
        // Count
    
        __HAL_TIM_SET_AUTORELOAD(&htim2, cnt);
    
    
        SET_BIT(htim2.Instance->EGR, TIM_EGR_UG);
    
    
        __HAL_TIM_ENABLE(&htim2);
    
    
        while(1)
    
        {
    
            if(!__HAL_TIM_GET_COUNTER(&htim2))
    
                break;
    
        }
    
    
        __HAL_TIM_DISABLE(&htim2);
    
    }

    在这两个函数里面,有一个函数要注意:

    SET_BIT(htim2.Instance->EGR, TIM_EGR_UG);

    一定务必必须加这个,产生UEV(Update Event),更新定时器,看文档:

    PS: 其实TIM2是32BIT 的定时器,TIM2_Delay1us(uint16_t cnt),这个cnt,其实是可以写32位的,主要决定于定时器。

     

    接下来,测试它!

    main循环里面,闪灯!
     

     while (1)
    
      {
    
        #if 0         
    
        USR_LedHandler();
    
    
        SERDEB_Handler();
    
    
        TIM1_Handler();
    
        #endif
    
    
            if(GPIO_PIN_SET==HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_6))
    
            {
    
                HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_RESET);
    
            }else
    
            {
    
                HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6, GPIO_PIN_SET);
    
            }
    
    
            USER_Delay1ms(2);                // 这个就是延时
    
        
    
      /* USER CODE END WHILE */
    
    
      /* USER CODE BEGIN 3 */
    
    
      }

     

    可以把延时更改为其他值,测试不同的闪灯频率;

    注意一点:人眼的极限频率是24Hz,如果高于这个值,你就看不到闪灯在。

    可以上研发三宝之:示波器,量波形!

     

    编译->烧录->执行。

    量一下,看一下延时 2ms 的波形是怎么样的,如图:

     

         整个工程及代码呢,请上百度网盘上下载:

         链接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg

         密码:07on

         文件夹:\Stm32CubeMx\Code\TimDelay.rar

         懂得IO口控制,知道精准延时,下一篇,我们就开始I2C协议!

     

    上一篇:《STM32 基本定时器

    下一篇:《I2C协议详解

    回目录:《目录

    展开全文
  • STM32 自定义延时函数

    千次阅读 2016-12-19 16:55:47
    stm32调试过程中加入一个延时,有两种方式:一种是纯计数方式,另一种是使用系统计数器的方式。 现使用系统计数器产生中断的方式实现,查阅STM32的编程手册可知,STM32有一个24bit的系统计时器,并有STK_CTRL、STK...
  • 1、中断方式 static __IO uint8_t _delayTimeOutFlag = 0; /************************************************************************************ *函 数 名: void DELAY_ms(uint16_t _nms) *函数描述: ...
  • SysTick 在STM32 的参考手册里面介绍的很简单,其详细介绍,请参阅《Cortex-M3 权威指南》。 这里面也有两种方式实现: a.中断方式 如下,定义延时时间time_delay,SysTick_Config()定义中断时间段,在中断
  • SYSTICK非中断延时(V1.0.1)代码测试数据50us级延时500us级延时特点总结3. DWT延时(V1.0.2)代码测试数据50us级延时500us级延时特点总结二、非阻塞延时1. 无OS下非阻塞延时(==待更新==)2.OS下非阻塞延时(==暂...
  • stm32中断优先级分组

    千次阅读 2016-12-27 08:29:06
    STM32中断优先级和开关总中断一,中断优先级:STM32(Cortex-M3)中的优先级概念 STM32(Cortex-M3)中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要...
  • STM32精确延时(非中断,非ST库函数)

    千次阅读 2012-04-16 20:34:13
    STM32精确延时(非中断,非ST库函数)   前天学了下stm的systick,发现还满好用的,可以用来精确定时.以前在用CVAVR的时候发现里面的delay.h非常好用.于是,利用stm32的SysTick做了个精确的延时头函数.  SysTick...
  • 问题: 在外部中断处理函数...利用stm32cubemx生产代码的时候,没有考虑外部中断的优先级,使用的都是最高优先级,所以延时函数得不到执行,则延时函数后面的执行代码也得不到执行,所以出现中断不响应的现象。 ...
  • STM32延时函数的四种方法

    万次阅读 多人点赞 2020-06-12 10:01:15
    本文基于STM32F207介绍4种不同方式实现的延时函数。 1、普通延时 这种延时方式应该是大家在51单片机时候,接触最早的延时函数。这个比较简单,让单片机做一些无关紧要的工作来打发时间,经常用循环来实现,在某些...
  • 关于STM32单片机延时微妙(delay_...现在能在网上查到STM32延时方式基本就是三种,循环延时,滴答定时器延时 和 滴答定时器中断延时。 对于这三种在这里就不再一一赘述了,可以看看这篇文章参考; 参考链接:https://b
  • stm32 中断架构

    千次阅读 2018-12-12 23:15:29
    1. 初步了解 stm32F4有两种CPU模式:特权模式和用户模式。当发生异常或中断时会进入到特权模式中 ...stm32F4的中断架构分为:不可屏蔽异常和可屏蔽异常(将中断看作是一种特殊的异常),不可屏蔽异常是stm32F4的...
  • stm32微秒延时实现

    千次阅读 2016-03-04 13:18:06
    利用系统时钟实现微秒延时。系统时钟正常设置为1ms一个tick,每毫秒产生一个系统时钟中断。SysTick->VAL记录的是计数器,SysTick->LOAD为计数器记录的最大值。SysTick->VAL开始被设置为SysTick->LOAD寄存器中配置的...
  • STM32外部中断边沿触发存在延时问题

    千次阅读 2020-05-31 16:20:56
    STM32外部中断时延问题 概括:通过软件调试,示波器观察的方式,来分析外部中断存在时延的原因。 在调试模拟SPI接收的时候,想用外部中断检测上升沿的方式来捕捉SCK的上升沿却发现了外部中断存在时延的情况。 直接上...
  • STM32笔记】STM32 延时函数的实现 在MCU编程中,微秒延时和毫秒延时使用最为频繁,在RTOS中,毫秒延时可以由系统提供,可是微秒延时却需要开发人员编写。本文基于STM32F407ZG芯片实现几种微秒延时操作。 1、定时器...
  • 如果你不需要再应用程序中嵌入操作系统,SysTick可以作为简单的延时和产生周期性的中断; 状态控制寄存器的第0位可以使能计数器,current value register(当前值寄存器)随着时钟一直递减,当他减到0的时候,重...
  • STM32编程过程中经常用到延时函数,最常用的莫过于微秒级延时和毫秒级延时。那么本文针对STM32延时进行分析和实验。关于STM32的时钟系统,参考笔者博文。 详解STM32时钟系统 2裸机延时 2.1普通延时 这个比较简单...
  • stm32f4利用SysTick定时器实现精确延时的代码,详细介绍可以参考我的博客http://blog.csdn.net/hust_xu/article/details/47088365
  • STM32精确延时

    千次阅读 2010-06-02 10:05:00
    前面用STM32的GPIO模拟液晶驱动时序时遇到一个问题,就是怎样产生一段较为精确的延时。通常产生一小段延时的方法就是利用一个递增或者递减循环进行软件延时。例:void delay(void){ int i="0x0ff"; while (i--) ;...
  • STM32F407ZE 使用延时控制LED灯亮暗变换,实现LED呼吸灯效果 具体代码如下: main.c部分 #include <stm32f4xx.h> #include "sys.h" #include "led.h" #include "delay.h" #include "tim.h" int main() { ...
  • HAL库-STM32F4 外部中断-延时

    万次阅读 2016-04-30 21:47:00
    注:资料来源:野火《零死角玩转STM32-F429》、ST-《STM32F4xx中文参考手册》、ST-《Cortex™-M4内核编程手册》 开始: 1.嵌套向量中断寄存器 (NVIC): 嵌套向量中断控制器 (NVIC) 和处理器内核接口紧密配合,可以...
  • STM32学习记录之定时器中断代替延时函数实现更精准计时(延时延时在很多函数处理中都是一个必不可少的操作,对于很多初学者来说,都知道在代码里加一句delay()就能延时,如果再了解多一点就知道delay_us(xx)就能...
  • 关于STM32延时问题

    千次阅读 2016-04-04 00:23:00
    最近一直在搞一辆智能小车,用STM32单片机驱动,往上面加了很多外设,外型如下: 今天下午打算在LCD显示一个温度,却发现怎么都显示不了,也找不出原因,还好我们公司的郑工帮我看出了问题,让我顺利改过来成功的...
  • stm32外接一个RTC时钟芯片,使用方法就是记录当前时间,比如,当前时间是12点24分36秒。然后,在主函数里面循环查询时间,当时间到达14点24分36秒时,发送信息。 误差在10秒到2分钟以内 需要外部扩展RTC芯片 第二...
  • 2、使用STM32的系统定时器来配置延时函数 内容介绍: 一、Systick简介 二、Systick相关寄存器介绍 三、使用Systick配置延时函数 一、Systick介绍 SysTick(系统定时器)是属于 CM3 内核中的一个外设,内嵌在 NVIC...
  • stm32系统定时器触发中断时间设置 以stm32F407ZE为例 系统定时器中断函数SysTick_Handler 在startup_stm32f40_41xxx.s汇编文件中227行 SysTick_Handler PROC EXPORT SysTick_Handler [WEAK] B . ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,166
精华内容 3,666
关键字:

stm32中断延时