文档章节

STM32系统滴答_及不可不知的延时技巧 - (上)

小汉憨憨
 小汉憨憨
发布于 2014/04/29 17:51
字数 2132
阅读 9089
收藏 6

我想每个单片机爱好者及工程开发设计人员都有过点灯的经历。流水灯是个好东西,尤其是在调试资源有限的环境中,有时会帮上大忙。

 

然在最初入门时,如何让这些小灯们按照我们的想法欢快地跑起来呢,绝大多数小朋友的做法是:在一个while循环里加上延时程序,让小灯在每个状态下停留一段时间,再进入下一个状态,这样小灯们就会在不同的状态中切换,就可以根据我们设计的程序闪烁了。

 

这样这里就会涉及到一个延时程序的编写的问题,而一般的做法是一个for循环里去减一个很大的数,直到为0,则延时完成,那个数的值则是根据时钟频率和指令运行周期,估算出来的,还记得较久以前看过一篇帖子介绍51单片机精确延时的几种方法,有一种方法是在keil中设定好时钟频率,然后通过软件仿真试来算延时时间,以达到较精确定时。

但这些方法一般都不够方便,延时也不够精确,更高阶一点的方法便是开一个定时器,在定时中断里面计数达到精确延时的目的。

 

STM32的应用中,可考虑利用SysTick系统嘀嗒定时器来实现。但在STM32开发手册中对它的介绍却很少,几乎到没有的程度。因为它是Cortex内核的部分,CM3为它专门开出一个异常类型,并且在中断向量表中占有一席之地(异常号15),这样它可以很方便的移植到不同厂商出CM3内核的芯片上,并且对于有实时操作系统的软件,它一般会作为整个系统的时基,这个对操作系统非常重要。有关SysTick的详细介绍可参考《Cortex-M3 权威指南》第133 页第八章及第179页第十三章。


SysTick总共有四个寄存器:

1、

对应于软件中 SysTick->CTRL;


2、

对应于软件中 SysTick-> LOAD


3、

对应于软件中 SysTick-> VAL


4、


对应于软件中 SysTick-> CALIB  (如上图),没有用过,也不常用,暂不作介绍。


这几个寄存器的偏移量如下图所示:

寄存器结构体的定义在 \CMSIS\CM3\CoreSupport  core_cm3.h如下

/** @addtogroup CMSIS_CM3_SysTick CMSIS CM3 SysTick
  memory mapped structure for SysTick
  @{
 */
typedef struct
{
  __IO uint32_t CTRL;           /*!< Offset: 0x00  SysTick Control and Status Register */
  __IO uint32_t LOAD;           /*!< Offset: 0x04  SysTick Reload Value Register       */
  __IO uint32_t VAL;            /*!< Offset: 0x08  SysTick Current Value Register      */
  __I  uint32_t CALIB;          /*!< Offset: 0x0C  SysTick Calibration Register        */
} SysTick_Type;

SysTick 是一个24 位的定时器,即一次最多可以计数 224个时钟脉冲,这个脉冲计数值被保存到SysTick->VAL 当前计数值寄存器中,它只能向下计数,每接收到一个时钟脉冲SysTick->VAL 的值就向下减 1,直至0,然后由硬件自动把重载寄存器SysTick->LOAD 中的值到SysTick->VAL重新计数,并且当SysTick->VAL值计数到0时,触发异常,调用void SysTick_Handler(void)函数,可以在此中断服务函数中处理定时中断事件了,般是对设定值进行递减计数操作。只要不把它在SysTick控制及状态寄存器SysTick->CTRL中的第0位使能位清除,就永不停息。

 

SysTick 中断优先级问题这里需要强调下。

 它属于系统异常,是内核级中断,并且优先级是可以设置的,具体设置也是在  core_cm3.h中

/**
 * @brief  Initialize and start the SysTick counter and its interrupt.
 *
 * @param   ticks   number of ticks between two interrupts
 * @return  1 = failed, 0 = successful
 *
 * Initialise the system tick timer and its interrupt and start the
 * system tick timer / counter in free running mode to generate 
 * periodical interrupts.
 */
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
    if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);         /* Reload value impossible */
                                                               
    SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;   /* set reload register */
    NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  
    SysTick->VAL   = 0;                                        
    SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    
    return (0);                                               /* Function successful */
}



其中如下这句就是设置优先级的函数,此函数对内核中断优先级和外部中断优先级设置通吃,比较强大,但需要手动算出来抢占和从优先级,不太方便,当跳进此函数,我们可以算出Systick默认优先是最低的(效果相当于SCB->SHP[11] = 0xF0;)

NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);

此时若其它外部中断优先级设置比它高时,可以剥夺它进而转向外部中断。


可以做如下实验验证:

先设置一事件中断,把优先级设置高一些,

void Exti_Config(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    EXTI_InitStructure.EXTI_Line = EXTI_Line1;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;    
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


注:中断分组我在实验中,最初初始化设置为如下:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);


设为第二组。


void SysTick_Handler(void)
{   
    EXTI_GenerateSWInterrupt(EXTI_SWIER_SWIER1); 
    LED_1 = ON;
    Delay();
}

系统滴答中断里触发外部中断事件,并点亮LED1 。


外部中断处理函数如下

void EXTI1_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line1) != RESET) 
    {
        EXTI_ClearITPendingBit(EXTI_Line1);     
        LED_0 = ON;
        Delay();
    }
}


此延时函数为阻塞延时如下:

void Delay(void)
{
    u32 i;
    for(i=0  ; i < 0xFFFFF; i++){}
}

加入延时是为了看出来哪个灯先亮。

当外部中断优先级比较高时,它可以抢占Systick中断先执行,以上代码实验结果为,LED0先点亮后,再回到LED1再点亮。

当把外部中断设置为与systick相同的优先级时,则systick优先级就会相对较高,例如把上面的优先级改为

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;    
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;

则会LED1先亮,执行完SysTick_Handle函数后才轮到EXTI1_IRQHandler执行。

个人认为,若要实现systick精确延时,最好把systick优先级设置高一些,例如

NVIC_SetPriority (SysTick_IRQn, 0);

即把SCB->SHP[11] = 0x00;则可达到systick优先级高于任合外部中断的效果,此时延时会比较精准。


另外对于SysTick的时钟源的选择,要注意它的时钟源可选择内部时钟(FCLKCM3上的自由运行时钟,STM32中对应是AHB),或者是外部时钟(  CM3处理器上的STCLK信号,STM32中对应是AHB/8

可参考如下图

它是在SysTick->CTRL第二位CLKSOURCE时钟源选择中设置。


有关systick延时函数的编写可参考野火《零死角玩转stm32-初级篇》。

至此我们可以简单的实现一流水灯程序

while(1)
{
    LED_0 =OFF;
    LED_1 = ON;
    Delay_ms(500);
    LED_0 =OFF;
    LED_1 = ON;
    Delay_ms(500);
}


然而这样做真的好吗 ?这里用的是 阻塞延时哦,CPU的效率很大一部分就耗在了空转上了,太浪费资源。

假设系统时钟频率为72MHZ或者几十上百MHZ当完成一个循环只需要几十或十几纳秒级或者更短,而在这个循环之中阻塞延时个几十至几百毫秒的话,就像是在高速公路上突然横出一条坑坑洼洼的泥泞路,那可想整条路都会因此而慢下来,甚至会出现灾难性的后果,个人认为,般在系统初始化过程中,各芯片的时序对时间有要求,可以用下阻塞延时,只需要系统启动时运行一下,当系统跑起来之后,最好就别再傻呼呼的这么做了。

 

这时主要采用的是在定时器里计数,在外部循环中对变量查询,达到某个值时再执行某个动作,达到延时的效果,而在时间未到时,系统还可以不停的跑圈圈,做别的事情去。

 gticks在定时中断里每毫秒计数一次

while(1)
{
    if(500 == gticks)
    {
        LED_0 =OFF;
        LED_1 = ON;
    }
    
    if(1000 == gticks)
    {
        LED_0 =OFF;
        LED_1 = ON;
        gticks = 0
    }
    Do_others(); 
}


以上需要在事件处理过程中对gticks进行处理,增加了代码的耦合度,更容易出错,如果在一个事件处理中对gticks清除了,而下个事件中又需要查询它,这样就可能导致处理时序的错乱,相互干扰。

能否在事件处理中只提供查询功能,而定时的事情就交给定时自己去做?

下节高手将登场了,为大家介绍个我曾在一项目中学到的,非阻塞延时的精妙设计。




© 著作权归作者所有

小汉憨憨
粉丝 4
博文 12
码字总数 14052
作品 0
武汉
程序员
私信 提问
STM32系统滴答_及不可不知的延时技巧 - (下)

下面为大家介绍一个曾见过的在裸机系统中,非阻塞延时的巧妙设计,当时它是被用在一导航影音娱乐系统的MCU驱动软件中,在此我对其作了一定的改动。 此章节涉及到STM32内容不多,以软件为主。...

小汉憨憨
2014/05/07
1K
3
STM32之SysTick定时器

SysTick-------操作系统的心跳 SysTick是系统滴答定时器,可以说是操作系统的的“心跳”,它被绑在NVIC中,用于产生SysTick异常(异常号:15)。一旦产生SysTick异常,就会产生滴答中断,这个...

panrenqiu
2018/04/14
0
0
SysTick的优先级是高还是低

SysTick系统嘀嗒定时器并非STM32独有的,它是Cortex内核的部分,CM3为它专门开出一个异常类型,并且在中断向量表中占有一席之地(异常号15)。这样它可以很方便的移植到不同厂商出CM3内核...

OceanStack
2015/01/24
5.7K
0
STM32读取MPU6050问题总结 (可读ID无数据; PWR_MGMT_1休眠位无法清零; 寄存器读写正常无数据;初始化前加延时无法解决问题)

因为网上关于STM32读取6050的例程并不少但是总会有古怪的问题存在,在尝试读取的过程中在网上逛论坛发现很多问题到最后没结果不了了之,不知那些前辈们是否已经自己查出了问题,我想尽自己微...

qq_26039737
2018/01/31
0
0
进程调度与切换简单总结

一、Linux时钟系统 1.时钟硬件 绝大多数的PC都有两个时钟源,RTC(实时时钟)和OS(系统时钟)。RTC也叫做CMOS时钟,它是PC主机板上的一块芯片。OS时钟产生于PC主板上的定时/计数芯片,由操作...

8yi少女的夢
2017/10/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。 一、ThreadPoolTaskExecutor 本文采用 Executors 的工厂...

CREATE_17
今天
5
0
CSS盒子模型

CSS盒子模型 组成: content --> padding --> border --> margin 像现实生活中的快递: 物品 --> 填充物 --> 包装盒 --> 盒子与盒子之间的间距 content :width、height组成的 内容区域 padd......

studywin
今天
7
0
修复Win10下开始菜单、设置等系统软件无法打开的问题

因为各种各样的原因导致系统文件丢失、损坏、被修改,而造成win10的开始菜单、设置等系统软件无法打开的情况,可以尝试如下方法解决 此方法只在部分情况下有效,但值得一试 用Windows键+R打开...

locbytes
昨天
8
0
jquery 添加和删除节点

本文转载于:专业的前端网站➺jquery 添加和删除节点 // 增加一个三和一节点function addPanel() { // var newPanel = $('.my-panel').clone(true) var newPanel = $(".triple-panel-con......

前端老手
昨天
8
0
一、Django基础

一、web框架分类和wsgiref模块使用介绍 web框架的本质 socket服务端 与 浏览器的通信 socket服务端功能划分: 负责与浏览器收发消息(socket通信) --> wsgiref/uWsgi/gunicorn... 根据用户访问...

ZeroBit
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部