5. STM32-NVIC Timer中断

前面文章有介绍到Delay的用法,Delay虽然也可以做到延迟或控制时间的效果,但严格来说透过Delay去做控制并不准确,这时候就可以用到Timer来做时间上的控制!

定时器说明

STM32L476RG中一共有11个计时器,其中又分为基本、通用、高阶三种计时器。
https://ithelp.ithome.com.tw/upload/images/20220311/201463251QqBct4N27.png
https://ithelp.ithome.com.tw/upload/images/20220311/20146325dl3tE0iffE.png
基本定时器大多用来作为基本的定时功能,较常用的是通用定时器除了基本定时功能外,可以做输入捕捉、输出比较等等用途,而高级定时器可以对於马达做死区控制的互补信号输出等等功能。
这篇主要介绍定时器的基本使用方法,先解释DataSheet当中的几项:

  • Counter resolution : 分为16 / 32 bit ,主要的差异在於计数的最大值,当定时器开始时会从0开始向上数到所设定的数值(依照计数模式选择),当达到时会触发中断。16bit最大计数值为2^16 = 65536,而32 bit则为2^32 = 4294967295。
  • Counter type : 计数方式,分为向上、中心对齐、向下三种模式
    1. 向上计数: 计数器会从0开始往上数到自动装载值,然後回到0重新开始。
    2. 向下计数: 计数器会从自动装载值往下数到0,再次回到自动装载值开始。
    3. 中心对齐: 计数器从0开始到自动装载值-1,接着会在往下计数到0+1再往上。
      自动装载值(ARR)也就是上方Counter resolution所设定的数值。
      https://ithelp.ithome.com.tw/upload/images/20220311/20146325HXBsGsRgnx.png
      Prescaler factor :预分频系数,每一种定时器都可设置1-65536,可以控制计数器的快慢

定时器设置

以STM32L476RG来说当中11种定时器分别来自不同的汇流排(APB1与APB2),其中TIM2,3,4,5,6,7来自APB1,而TIM1,8,15,16,17则来自APB2,在对於预分频系数做设定的时候要注意一下。
https://ithelp.ithome.com.tw/upload/images/20220311/20146325FtNf3OUQhE.png
在IDE当中的.ioc档中点选上方的Clock Configuration,可以去对Prescaler(预分频系数)做设定。
分为两个汇流排去做设定,当中有1,2,4,8,16可以依照需求做选择。举例来说如果下方预分频系数选择16,则分频後会变成1Mhz。(如果从这边去做设定的话会影响到所有连接在该汇流排的时脉)
https://ithelp.ithome.com.tw/upload/images/20220311/20146325Cd90XdRRV4.png
在使用时大多会从左侧选单当中的Timer去做Prescaler的设定,这边先选择通用定时器Timer2去做设置。

  1. 左侧点选Timers→TIM2可以看到出现下方画面
    https://ithelp.ithome.com.tw/upload/images/20220311/20146325RTe6CPd1V0.png
  2. 点选Clock Source 选择时钟来源,这边选内部时钟
    https://ithelp.ithome.com.tw/upload/images/20220311/20146325s8QaFvJ4HS.png
  3. 接下来就可以去对於TIM2做详细的设置,可以看到下方Configuration当中前三个选项,也就是刚刚前面所提到的分频系数、计数模式与自动装载值。
    https://ithelp.ithome.com.tw/upload/images/20220311/20146325nXTHUdvYBP.png
  • Prescaler (PSC):这边可以设置0-65536,假设像上方Clock Configuration那张图当中系统时钟为16Mhz ,那在这边设置15经过分频後,则会等於16/(15+1)也就是1Mhz。
  • Counter Mode: 这边计数方式选择向上(详细说明可以看上方的介绍)
  • Counter Period (ARR/计数周期): 也就是上方的Counter resolution,由於TIM2属於32bit的计时器,所以最大可计算到2^32,目的在决定计数器从0数到多少产生一个溢位。

如何去计算计时器的溢位时间?

https://chart.googleapis.com/chart?cht=tx&chl=Tout%20%3D%20(ARR%2B1)*(PSC%2B1)%20%2F%20Tclk
ARR : 自动装载值(上方的Counter Period)
PSC : 预分频系数
Tclk : APB时钟,通常会等於系统时钟
举例:
假设TCLK = 80M , PSC = 7999 , ARR = 9999
⇒ (9999+1)*(7999+1) / 80000000hz = 1s

1Mhz = 1000khz =1000000hz
1Mhz = 1us 1khz = 1ms 1hz = 1s
那这样定时器就是设定为1秒(计数周期从0数到9999需要1秒的时间)


函数介绍

在stm32xxxx_hal_tim当中可以找到相关的定时器函数
https://ithelp.ithome.com.tw/upload/images/20220311/20146325vCAW0xsG8z.png

  1. 开启定时器
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
{
	..........
}
//用法 htimx -> x 看是哪个TIM就填哪个
HAL_TIM_Base_Start(&htim2);
  1. 关闭定时器
HAL_StatusTypeDef HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim)
{
	..........
}
//用法 htimx -> x 看是哪个TIM就填哪个
HAL_TIM_Base_Stop(&htim2);
  1. 开启定时器中断
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
	...........
}
//用法 htimx -> x 看是哪个TIM就填哪个
HAL_TIM_Base_Start_IT(&htim2);
  1. 关闭定时器中断
HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim);
{
	...........
}
//用法 htimx -> x 看是哪个TIM就填哪个
HAL_TIM_Base_Stop_IT(&htim2);
  1. 定时器中断回调函数,将触发中断後要做的事情写在这里
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(htim);

  /* NOTE : This function should not be modified, when the callback is needed,
            the HAL_TIM_PeriodElapsedCallback could be implemented in the user file
   */
}
//用法 htimx -> x 看是哪个TIM就填哪个
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	 if (htim->Instance == htim2.Instance) //假如tim实例等於tim2才进回圈
	 {
			.....................
	 }
}

程序范例-来做一个定时闪烁灯吧!

  1. 第一步先确定系统时钟是多少,以我的为例是16Mhz
    https://ithelp.ithome.com.tw/upload/images/20220311/20146325CRPRa9YYPw.png
  2. 接着点选左侧的选单设定TIM2
    https://ithelp.ithome.com.tw/upload/images/20220311/20146325WNo0vtEJUS.png
    https://ithelp.ithome.com.tw/upload/images/20220311/20146325Wy8gz0wXpY.png
    在这边可以看到我将PSC设定为1599,计数方式为向上计数,计数周期(ARR)设定为9999。按照上方溢出时间计算方式可以得到(1599+1)*(9999+1) / 16000000 hz = 1s,也就是说每一次是一秒钟。
  3. 记得开启定时器中断!
    https://ithelp.ithome.com.tw/upload/images/20220311/20146325HM9peOKALO.png
  4. 回到main.c当中首先在main()当中的/* USER CODE BEGIN 2 / / USER CODE END 2 */ 启动TIM2中断。
/* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2);
 /* USER CODE END 2 */

接着找到下方的/* USER CODE BEGIN 4 / / USER CODE END 4 */,在当中写入中断回调函数,同样的记得宣告全域变数i。

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim){
	if(htim->Instance == htim2.Instance){
		i++;
		if(i==3)
		{
			i = 0;
			HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
		}
	}
}
/* USER CODE END 4 */

在这边因为前面设定溢出时间为1秒,可以知道的是他触发一次时间中断是1秒一次,而i实际上也就是秒数的变化!接下来利用前面提到的Toggle函数可以达到每3秒切换一次灯号,可以看看板载上的LED灯是否有按照设定的时间去做变化!

前面介绍过有关Timer的使用方式,但使用的是中断方式。结合其他中断时需要考虑到优先权的问题,那如何利用定时器达到延时效果而不进入中断呢?下方介绍如何去做设定。

  1. 首先设置.ioc 以TIM3为例,将PSC设为15999(系统时钟为16Mhz) ARR设为1999,经过计算可以得知这是一个两秒的定时器。

https://ithelp.ithome.com.tw/upload/images/20220313/20146325ZJ6t0nxdqn.png

  1. 透过下方函数可以获得定时器Count的数值
__HAL_TIM_GET_COUNTER(&htimx); //x填入对应的TIM

这时我们就可以利用CNT的数值来做到延时效果,首先先启动定时器。

HAL_TIM_Base_Start(&htim3);

接着可以在while(1)当中写入下方程序码,记得宣告timerct变数。

刚刚已知定时器为两秒也就是说从0数至1999需要两秒,那这时将当前的计数值减去後续的计数值大於1000时即为一秒。因为16Mhz/(15999+1) = 1khz,而1khz=1ms 接着 *1000 = 1s,可以看一下板载LED有没有每一秒切换一次。

if (__HAL_TIM_GET_COUNTER(&htim3) - timerct >= 1000)
{
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	  timerct = __HAL_TIM_GET_COUNTER(&htim3);
}

以上内容如果有误的话,麻烦各位通知我。感谢~


<<:  Webpack

>>:  【HTML】为何用<p>包<div>会出错?

EP 12: Implement and Use a Custom ValueConveter

Hello, 各位 iT邦帮忙 的粉丝们大家好~~~ 本篇是 Re: 从零开始用 Xamarin 技...

[iT铁人赛Day21]疯狂程设介绍+下载

疯狂程设是一个练习CPE常用的软件,疯狂程设拥有许多练习题,不是只有CPE。 如何下载疯狂程设?可以...

Day29 黑人变白人 (  皮肤上色 )

黑人变白人 (  皮肤上色 ) 教学原文参考:黑人变白人 (  皮肤上色 ) 这篇文章会介绍在 GI...

Day 30 设计的问题

最後,我们提一下,设计时可能面对的问题,首先,分类要分好,因为资源有优先顺序的问题,所以在设计的时候...

Day 25 - Rancher Fleet.yaml 档案探讨

本文将於赛後同步刊登於笔者部落格 有兴趣学习更多 Kubernetes/DevOps/Linux 相...