STM32 基本定时器

    小明妈妈跟小明说:“10分钟后,你再不给我去做作业我就揍你!”,接着,小明妈妈看着手表,1秒钟数1下,0,1,2,3,……,599。看看小明有没有做作业,根据情况判断要不要揍他。

    接着,小明妈妈又从0数起,到599,继续看看小明有没有做作业…… ……再数数…… ……

    于是,小明妈妈就是一个每隔10分钟监视一下小明有没有做作业的定时器,手表,就是小明妈妈定时器的时钟,小明妈妈数600个周期后(0~599),触发定时器中断,定时器中断里,判断小明有没有去做作业,没有,就揍他,有,就啥事也不做。

 

   STM32上的定时器,也是跟小明妈妈一样,是通过数数计时的,比如,数1个数是1秒,从0数到599,就是600秒(10分钟)了。

   STM32上的数数,可以正着数,从0~599,也可以倒着数,从599~0,还可以先正着数后倒着数,当然,小明妈妈也是可以的。

   那能不能不数600个数?数别的?可以的,只需要设置就行了。

   还能不能不1秒数一次,长一点或者短一点?也是可以的,只需要设置就行了。

   RM0033告诉我们,STM32F207有8个小明妈妈,TIM1~TIM8,资源非常丰富,当然,它也不仅仅只能用于数数,功能非常强大。

   为什么叫基本定时器呢?因为它们不仅用来计时,还有其他非常强大的功能,如PWM,脉宽测量等等。定时器,只是它的基本功能。

   我们今天,只讲数数功能,数到一定数后,就触发定时器中断,处理事件。换句话说,每隔一段特定的时间,触发特定的事件。

 

   我们就来做一个一秒的定时器吧,每隔1秒打印定时器开启时间。

 

    先打开Stm32CubeMx,新建一个工程,不懂建工程的请参考《STM32CubeMx 创建第一个工程》。

    建完之后,看TIMx(x=1,2,3……),这就是定时器。

    这里面有各种需要设置的地方,Slave Mode、Trigger Source、Clock Source、等等等。

   这是啥?看不懂,怎么办(黑人问号)???


 

    没关系,问一下RM0033,找到相关的部分,看介绍。

    它告诉我们,定时器包含16Bit自动重载计数器,由可编程预分频器组成。抱歉,翻译不是我的特长,大家大概看得懂意思就行啦。它还告诉我们,这些定时器有很多作用,像量输入信号脉冲宽度或者输出波形等等。还讲了脉宽测量的范围,从微秒级到毫秒级的。还说这几个小明妈妈是完全独立的,不过她们可以同步,具体的要参考一下Section 13.3.20

点到13.3.20,定时器可以连在一块,具体参考14.3.15。

 

    点到14.3.15,当一个定时器配置成Master Mode时,它能够复位,开始,停止,或者作为另外一个被配置成Slave Mode的定时器的时钟。

    由此可见,Slave Mode,主要用在两个以上的定时器上,具体有兴趣的朋友可以继续往下看资料,这里就不截图出来。

    它有Reset Mode、GateMode、Trigger Mode、External Clock Mode几种模式。

    我们这章节,不需要用到Slave Mode,所以,就选择 Disable。

 

    Trigger Source 选择,里面有ITR0、1、2、3等项目,怎么选择呢?

    ClockSource 选择,里面有Internal Clock,ETR,怎么选择呢?

    Channel1、2、3、4,以及以下的,如何设置呢?

    先来看一个图:

    图中标的1、2、3,就是Timer的ClockSource。

    图中标的4、5,是定时器的其它功能,如Pwm,脉宽测量等功能,我们这节课暂时不需要关注它。

 

再来看看 Clock selection 的说明:

我这板子呢,也没有外部引脚的时钟源,也没有用到Slave Mode,所以,

ClockSource,就选 Internal Clock,Trigger Clock,就选Disable。

Channel1、2、3、4、及以下的呢,我们全都Disable掉,不需要选。

设置完的定时器如下图:

    基本设置完成,再先择顶上的标签页“Configuration”,再点TIM1,旁边有个时钟标志,弹出菜单:

    这里密密麻麻的一大垞,怎么设置呢?

    小明妈妈跟小明说:“10分钟后,你再不给我去做作业我就揍你!”,接着,小明妈妈看着手表,1秒钟数1下,0,1,2,3,……,599。

    小明妈妈的ClockSource就是手表,周期是1秒钟,频率就是1Hz。

    小明妈妈觉得1秒钟数一下,10分钟得数600下,这时,小明妈妈想,那我2秒数一下,我只需要数300下,我3秒数一下,我只需要数200下,……

    这个“x秒数一下”,就是预分频(Prescalar),作用是,把超高超高的频率降低下来。

    预分频为0,分频后的频率就是1Hz/(0+1)=1Hz,要数600下,Count Preiod=600-1;

    预分频为1,分频后的频率就是1Hz/(1+1)=0.5Hz,要数300下,Count Preiod=300-1;

    预分频为2,分频后的频率就是1Hz/(2+1)=1/3Hz,要数200下,Count Preiod=200-1;

    …… ……

    预分频为n,分频后的频率就是1Hz/(n+1) Hz,…………自个算去;

    小明妈妈想计时10分钟,她的ClockSource=手表,手表频率是1Hz,

    Prescaler=0,Count Preiod=599;

    Prescaler=1,Count Preiod=299;

    Prescaler=2,Count Preiod=199;

    说完小明妈妈,我们来看一下Stm32,Stm32 ClockSource我们选的是Internal Clock,那么,我们这个Internal Clock的频率是多少呢?

    首先,得了解一下TIM1的Internal Clock从何而来?

    看一下DataSheet里面的系统总框图,TIM1挂在APB2总线上,于是,它的Clock由APB2提供;

    再回忆一下我们刚用Stm32时,设置Clock的时候是怎么设置的?详细参照《STM32CubeMx 创建第一个工程

    打开Clock Configuration标签页:

    看到没,这个APB2 timer clock = 120Mhz = 120,000,000Hz。

    120,000,000Hz,也就是计时1秒,要数1.2亿次啊,那Count Preiod设置 120,000,000-1可以不?

    那是不行的,因为设置不下去,Count Period是16bit的,所以它的最大值就是 65535(2的16次方-1)。

    不用担心,我们有Prescaler,这个东西它也能把Internal Clock分频,它的范围是 0~65535,所以,它能把clock的频率变成 120MHz/(0+1)~120MHz/(65535+1),也就是120,000,000Hz~1831Hz之间,我们不要选得这么极端,我们选择个11999,也就是把频率分成 120,000,000/(11999+1) = 10000Hz,计时一秒,只需要10000次就够了。

    那么,我们的Prescaler设置为120000-1=119999,Count Period设置为10000-1=9999

    Counter Mode,管正着数,还是倒着数,还是先正着数,再倒着数。

    正着数:从0~9999,数1000次,产生上溢事件。

    倒着数:从9999~0,数1000次,产生下溢事件。

    先正着数,再倒着数:从0~9998,数9999次,再从9999~1,数9999次。

    请详细阅读RM0033第13.3.2,在这里,我们就设置个Up吧。

    其他的,在基本定时器中不需要用到,所以就不用设置,这样,一个1秒钟的定时器就完成了!如图所示:

    每次计数器溢出(不管上溢还是下溢)时,会产生一个update event(UEV)。

    所以,记得把NVIC Settings里面的中断钩上,我们后续会对这个事件进行处理。

    接下来,生成代码后,看看它生成了什么东西,我们把这些东西,挪到上一篇的@命令的项目中,《STM32 Uart @调试命令的实现》。

    在一个项目中,有时候要增加外设,但项目已经完成了,我们不可能用Stm32CubeMx重新走一遍配置,再把我们自己写的代码挪过去,这样太麻烦了。

    比较简便的办法,就是用Stm32CubeMx生成配置,然后把这些配置文件及代码,移植到原有的项目中。

    对STM32比较熟悉的同学还可以直接用HAL库直接写配置,或者直接写寄存器。

    所以后面我们就比较少用Stm32CubeMx直接生成代码直接用了,我们更多用它,来生成一些配置,把这些配置移植到我们原有的项目中,以及结合文档,理解STM32的寄存器。

    Stm32CubeMx生成了tim.c和tim.h文件,在main()里面用MX_TIM1_Init();进行初始化,以及在stm32f2xx_it.c里面,生成void TIM1_UP_TIM10_IRQHandler(void)中断服务例程。

    好了,依葫芦画瓢,把它挪过来吧。

    tim.c/tim.h,直接复制到对应的文件夹下,tim.c要加入工程编译。

    在main.c里面,增加初始化MX_TIM1_Init();,记得把tim.h #include进来。

#include "main.h"

#include "stm32f2xx_hal.h"

#include "dma.h"

#include "i2c.h"

#include "tim.h"      // 包含的tim.h文件

#include "usart.h"

#include "gpio.h"


  

/* Initialize all configured peripherals */

  MX_GPIO_Init();

  MX_DMA_Init();

  MX_UART4_Init();

  MX_TIM1_Init();        // 初始化

  /* USER CODE BEGIN 2 */

    USR_UartInit();

  /* USER CODE END 2 */

 

    在stm32f2xx_it.c里面,增加中断服务例程,因为用到htim1这个变量,所以还要把这个变量extern进来

/* External variables --------------------------------------------------------*/

extern TIM_HandleTypeDef htim1;       // 就是这个变量

extern DMA_HandleTypeDef hdma_uart4_rx;

extern UART_HandleTypeDef huart4;
/**

* @brief This function handles TIM1 update interrupt and TIM10 global interrupt.

*/

void TIM1_UP_TIM10_IRQHandler(void)

{

  /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */


  /* USER CODE END TIM1_UP_TIM10_IRQn 0 */

  HAL_TIM_IRQHandler(&htim1);

  /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */


  /* USER CODE END TIM1_UP_TIM10_IRQn 1 */

}

 

    还有记得在stm32f2xx_hal_conf.h里面,把#define HAL_TIM_MODULE_ENABLED打开,之前是注释掉的。

/*#define HAL_MMC_MODULE_ENABLED   */

/*#define HAL_SPI_MODULE_ENABLED   */

#define HAL_TIM_MODULE_ENABLED            // 一定要打开喔 ^_^

#define HAL_UART_MODULE_ENABLED

/*#define HAL_USART_MODULE_ENABLED   */

/*#define HAL_IRDA_MODULE_ENABLED   */

/*#define HAL_SMARTCARD_MODULE_ENABLED   */

 

    挪好了,那接下来,我们要做什么?

    开启TIM中断,处理UEV;

    我们就在初始化后开启TIM中断。

void MX_TIM1_Init(void)

{

  TIM_ClockConfigTypeDef sClockSourceConfig;

  TIM_MasterConfigTypeDef sMasterConfig;


  htim1.Instance = TIM1;                                // Tim1

  htim1.Init.Prescaler = 29999;                        // 预分频

  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;       // 正着数

  htim1.Init.Period = 3999;      // 数多少个周期

  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; 

  htim1.Init.RepetitionCounter = 0;

  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)

  {

    _Error_Handler(__FILE__, __LINE__);

  }


  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)

  {

    _Error_Handler(__FILE__, __LINE__);

  }


  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)

  {

    _Error_Handler(__FILE__, __LINE__);

  }


  HAL_TIM_Base_Start_IT(&htim1);          // 在这里,开开开!^_^


}

 

    我们看一下中断服务例程,

    上面讲过,每次计数器溢出(不管上溢还是下溢)时,会产生一个update event(UEV)。

    也就是说,每隔1秒钟(我们设置的),会产生一次中断。

// 简单地理解,定时器每计1秒会进一次这个函数喔^_^~~~
void TIM1_UP_TIM10_IRQHandler(void)

{

  /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */


  /* USER CODE END TIM1_UP_TIM10_IRQn 0 */

  HAL_TIM_IRQHandler(&htim1);

  /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */


  /* USER CODE END TIM1_UP_TIM10_IRQn 1 */

}

 

    点进去,看一下HAL_TIM_IRQHandler(&htim1);这个函数做了什么。

    里面有各种不同的事件处理,但我们只关注 Update Event 就行了。
 

 /* TIM Update event */

  if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)

  {

    if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) !=RESET)

    {

      __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);

      HAL_TIM_PeriodElapsedCallback(htim);        // 就是你了!实现它!

    }

  }

 

    再看一下回调函数。

    它的NOTE告诉我们,这个函数不能改,如果需要,就在重写它就行了。

    我们现在就来重写它。

/**

  * @brief  Period elapsed callback in non blocking mode

  * @param  htim pointer to a TIM_HandleTypeDef structure that contains

  *                the configuration information for TIM module.

  * @retval None

  */

__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

   */

}

 

    既然我们需要打印定时器启动后的秒数,那我们首先需要这个秒数的变量。

    变量名为 tim1StartCntS

    变量类型用 unsigned int,能计时 2的32位 秒,也就是 4294967296 秒 = 1193046小时 = 49710天 = 136年,系统一般运行不了这么长时间,所以 unsigned int 就足够了。

/* Includes ------------------------------------------------------------------*/

#include "tim.h"


/* USER CODE BEGIN 0 */

volatile unsigned int tim1StartCntS;        // 这个够定时器运行 136 年啊^_^

/* USER CODE END 0 */


TIM_HandleTypeDef htim1;

    这个函数,就是重写的回调函数。
 

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)

{

  /* Prevent unused argument(s) compilation warning */

  if(htim->Instance==htim1.Instance)

  {

    tim1StartCntS++;                // 每一秒钟自加。

  }

  /* NOTE : This function Should not be modified, when the callback is needed,

            the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file

   */

}

   接下来,能不能直接在回调函数里面打印一下 tim1StartCnS?

   最好不要,

   记住中断服务例程设计原则其中之一:中断尽量简短,请不要在中断服务例程里执行比较耗费时间的事,比如,printf,延时等需要大量时间的事。

    那么,我们就设计一个 Handler,在main()主循环里面,打印定时器启动时间吧。

    每隔一秒钟,tim1StartCntS会增加1,而tim1CntTmp不变,所以,每一秒钟,tim1StartCntS 和tim1CntTmp的值是不同的,每次不同,都要更新一下tim1CntTmp的值,然后打印。

    这样,就每一秒都打印出 tim1StartCntS 的值了。

/* USER CODE BEGIN 1 */

void TIM1_Handler(void)

{

    static unsigned int tim1CntTmp = 0;


    if(tim1CntTmp!=tim1StartCntS)

    {

        tim1CntTmp = tim1StartCntS;

        printf("Tim1 Start %d seconds.\r\n", tim1CntTmp);

    }

}

 

    记得,这个TIM1_Handler(),要在tim.h里面声明一下,要在main()主循环里面调用一下。

// 在tim.h里面声明一下

extern void _Error_Handler(char *, int);


void MX_TIM1_Init(void);


void TIM1_Handler(void);

 


 

// 在 main()的主环里调用

 while (1)

  {

    USR_LedHandler();


    SERDEB_Handler();


    TIM1_Handler();     // 就这了!


  /* USER CODE END WHILE */


  /* USER CODE BEGIN 3 */


  }

 

    编译,运行,烧录,来看一下运行结果:这个,运行很久了喔。

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

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

     密码:07on

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

 

上一篇:《STM32 Uart @调试命令的实现

下一篇:《STM32 精准延时

回目录:《目录

 

  • 16
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值