STM32基础系列包含了三件套,掌握了这三件套,类似于掌握了程咬金三板斧,就可以开始干项目,创造价值了。毕竟,真正的编程是一项实战性很强的技术,掌握编程主要靠实战,而不是靠知识灌输。
STM32的编程三件套就是:
前面详细讲了第一板斧,如何初始化GPIO,顺道讲了如何跟着ST学编程,如何降低代码之间的耦合性。
详细可以复习一下下面:
(24条消息) 每节课都是一个项目 手把手用STM32打造联网气象站-3-STM32基础三件套-掌握GPIO初始化_可志嵌入式的博客-CSDN博客
本节讲解第二板斧,如何使用TIM定时器,如何采用TIM定时器实现精准定时。
我们先考虑一个问题:如果我们需要精确的设置LED点亮100S,那么应该怎么做呢?
在没有操作系统的代码中,程序执行一般分为双循环模式:
大循环中,设置cnt为100,然后点亮LED,并不断检查定时器实际是否到达。在中断处理中,每隔1S,检查cnt大于0时,将cnt自减。
当cnt自减到0时,LED将熄灭。这样就实现了准确的100S延时。
那么另外有一个问题:为何不直接用delay函数来实现呢?例如:
LEDON;
delay(100);
LEDOFF;
上面的方法虽然简单,但是在执行delay(100)这个语句时,MCU是阻塞的,并不响应任何输入,因此,长时间的delay在真实项目中,是不能使用的!真实项目中,需要采用非阻塞的方式进行,才能避免代码执行中被卡死。
在理解定时器初始化之前,我们先回忆一下GPIO初始化部分:定义一个结构体,初始化时钟,定义结构体参数并传递到GPIO_Init函数中。具体参见下面链接。
每节课都是一个项目 手把手用STM32打造联网气象站-3-STM32基础三件套-掌握GPIO初始化_可志嵌入式的博客-CSDN博客
TIM定时器的初始化非常类似,也是这样步骤:定义一个TIM结构体,初始化TIM时钟,设定参数并传递到TIM_Init函数中。这些步骤是一模一样的。
但是定时器初始化还会多几个步骤 :清楚中断标志位,开启定时器中断,使能定时器。
同样的,对于定时器初始化的过程,我们了解这些步骤即可,真正移植过程中,将主要查看.h文件,并且修改宏定义,实现代码移植和调用。
第一步:定义结构体变量;
第二步:开启定时器时钟;
第三步:设定定时器参数,并且调用Init函数写入参数;
第四步:请中断标志;
第五步:开启中断;
第六步:使能定时器;
本教程中,会把STM32F1的六路定时器都单独进行初始化和中断定时,方便后续在不同项目总,根据管脚配置和项目配置,启动对应项目。
定时器的计算取决于Period和Prescaler两个参数。STM32主频为72MHz (比1996年486的速度还快),72/(Prescaler+1) =72/(71+1) 1MHz,也就是对应这1us,1us*Period=1us*1000=1ms。因此这里1ms触发一次中断。如果需要10MS触发一次中断,我们只需要把Period修改为10000即可。
定时器的基本功能是按照固定时间计时,并且达到时间后,触发中断。因此,使用定时器就一定要初始化中断。定时器内部仅仅计时而已,中断函数内,才会真的处理对应问题。
中断初始化和GPIO初始化,TIM初始化都非常类似,也是三部曲:
第一步:定义结构体变量;
第二步:有点特殊,需要设定中断分组,这里放到下面详细讲解;
第三步:设定结构体参数,并调用Init函数,写入参数;
当我们反复熟悉掌握这些三部曲之后,就会发现其实STM32的各个初始化都非常类似。
Cortex M3内核支持256个中断,其中包括16个内核中断和240个外部中断,且具有256级的可编程中断设置,而STM32只用了其中一部分。
STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程中断优先级。而我所使用的103系列只有60个可屏蔽中断。我们把这60个可屏蔽中断列一下:
这么多的中断,如果没有一个好的管理方式进行管理,会乱成一团。所以STM32中,采用中断分组的方法。
它把中断分成了5组,分别对应了NVIC_PriorityGroup0~NVIC_PriorityGroup5。
对于第0组,有0位抢占优先级,4位响应优先级,也就是总共有16个响应优先级;
第1组,1位抢占,3位响应,也就是2个抢占,8个响应优先级;
第2组,2位抢占,2位响应,也就是4个抢占,4个响应;
第3组,3位抢占,1位响应,也就是8个抢占,2个响应;
第4组,4位抢占,1位响应,也就是16个抢占,1个响应;
这里解释一下抢占优先级和响应优先级,两个都是翻译过来的,听起来有些别扭。按照通俗的说法,抢占优先级和响应优先级,两者对应关系可以看下图。抢占>响应。
上图是分组2的情况,这时会有4级抢占,4级响应。如果uart1中断位抢占2,响应1, tim2中断为抢占1,响应4,那么tim2中断的优先级更高,它可以屏蔽uart1的中断。也就是优先级更高的中断在办事的时候,优先级低的中断,是不能打断它的。而优先级低的中断在办事的时候,则可以被优先级高的中断打断。
这里需要注意:高抢占优先级可以打断低抢占优先级的中断,从而实现中断嵌套。若两个中断的抢占优先级相同,不可发生嵌套,其中响应优先级高的先执行,若两中断无论是抢占优先级还是响应优先级数值都相等,中断控制器将根据中断向量表中的位置来觉得执行顺序
中断初始化步骤和其他初始化步骤相比较,没啥明显区别。步骤类似,内容不同。关于中断优先级的初始化步骤,还是需要专门理解一下的。因为实际项目中,往往会遇到多个中断的情况。
第一步:创建结构体变量;
第二步:设置优先级分组,也就是前面所说的分组0~分组4之间,选一个分组。这里选择了分组0;
第三步:设置参数:我们需要设定中断源,这里通道为TIM6_IRQ;需要设置抢占优先级,这里只有一个选择,就是0;需要设置响应优先级,这里可以设置0~15,我们选择了3;然后使能中断;
第四步:将配置的参数带入函数中;
// 中断优先级配置
static void BASIC_TIM_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置中断组为0
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
// 设置中断来源
NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;
// 设置主优先级为 0
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
// 设置抢占优先级为3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
对于中断优先级配置,我们需要掌握下面几点:
1. 这里只用了一个中断,我们把中断分组设置放到了定时器初始化函数中;如果用到多个中断,则需要把中断分组设置单独拎出来了;
2. 每隔中断,需要指定中断源,设置抢占优先级,设置响应优先级;
最后完成定时器初始化即可。
完成了上面的配置后,我们的代码就会乖乖地每隔1ms,进一次中断。
进了中断之后,在中断向量表中,指定了中断源和中断处理函数的关系。也就是中断处理函数的名字是定死的,不能随意修改。
比如在MDK的startup_stm32f10x_hd.s中,就明确指定了TIM6中断处理函数为TIM6_IRQHandler。
中断处理函数中,主要做了3 件事情:
1. 检查对应中断标志位是否已经被设定了;
2. 倒计时直到0;
3. 清除标志位;
完成了前面几个步骤后,我们已经完成了定时器的初始化和中断处理函数,使得每隔1ms就会进一次中断。但是这样还不能实现LED定时闪烁,我们只实现了前面双循环的右边。
我们还需不断把cnt设置为一个较大的数值。
这里不断把time设置为1000。每当time在定时器中,被自减到0时,就会在主循环中被设置为1000。如此不断循环,即可实现0.5HZ led闪烁。
而且整个过程中,没有用到一句delay,全部代码流畅运行,没有任何阻塞。
要移植,看头文件。如果我们需要把这里的定时器函数tim.c和tim.h移植到别的工程中,我们只需要看tim.h文件中实施的定义即可。
如果需要修改定时器中断间隔,我们修改Prescaler和Period即可;
如果需要修改点灯的频率,修改HALF_PERIOD这个宏定义即可;
这是一个24位的系统节拍定时器system tick timer,SysTick,具有自动重载和溢出中断功能,所有基于Cortex_M3处理器的微控制器都可以由这个定时器获得一定的时间间隔。
SysTick_Init()函数调用Core_cm3中的SysTick_Config,进行Systick的配置。函数入参为SystemCoreClock,也就是72M,然后再除以1000,对应的是72000这个数字,这个数字为Systick的滴答时钟。由于Systick时钟为1/72M , 间隔时间为72000/72000000=1000,单位为us,因此systick间隔时间为1ms。如果需要加快间隔时间,比如,变为100us,则需要把1000修改为10000,这样心跳间隔就会变为100us。
Delay_us的工作原理是:
1.start从SysTick->VAL中获取当前的滴答计数器的数值,也就是开始延时时的值。这个VAL会不断自减,自减的时间间隔就是systickConfig中配置的时间。
2. reload则读取LOAD中的数值,这个数值就是初始化SystickConfig中设定的数值。us_tick设定的则是常量72。
3. 在do while循环中,将从VAL中读取当前值,然后再delta中进行计算,如果start>now,说明没有还没有被减到0,这时delta=start-now, 如果start 4.最后如果delta 例如:如果一开始val为500, 也就是start是500,now读出来为200,比start小,说明delta为500-200。如果now读出来是1500,比start大,则说明已经减到0并重新加载relad值72000,这个时候就需要将delta计算为72000+500-1500,这个时间才是间隔时间。 用这种方法延时时间最大不超出1ms,也就是一个reload对应的时间。 延时时间到了之后,会自动调用SysTick_Handler中。 这个SysTick Handler在启动函数hd.s中定义,我们的中断函数只要名字相同就行了。中断处理函数中,每隔20ms检查一次按键处理函数。 以上就是Systick的用法。 纸上得来终觉浅,绝知此事要躬行! 马上下载测试代码,看看效果。 测试代码链接 https://download.csdn.net/download/book_drabit/85914311 并且试着修改一下LED闪烁频率,观察一下效果。 接下来会一步一步带你完成家庭气象站的开发工作,敬请收藏关注,以免下次找不到了。 6.3 SysTickHandler中断延时