定时器是最重要的外设,是一切复杂程序的根基,对时间的精准测量与感知是人类做到的最伟大的事情之一,没有时间就没有智慧。在了解定时器之前我们先来学习如何使用定时器。

打开CubeMX,创建一个新的工程,在System Core和Timer就能看到这个单片机的几乎所有可以使用的定时器资源。此图是STM32F103C8T6的定时器资源。我们能看到有RTC以及TIM1~TIM4的定时器资源。其中RTC指的是实时时钟,TIM1~TIM4是定时器。对于M3架构STM32有如下的定时器划分,我们可以在参考手册中查到:

看到这里你可能会疑惑我也没有那么多时钟啊?那是因为你买的型号没那么多资源,每一本手册都会写明本手册的适用范围比如我们在教程中看的这本支持的范围是:

在MSDN的PowerShell文档中有一句话:“专家不一定总是知道答案,但是他们知道如何得到答案。”这是许多人都学不到的能力,在文档中是用这个小故事说明的:

MSDN截图

回到正题,我们如何去配置时钟呢?请跟着我一步一步的操作:

1. 打开System Core找到RCC。我们看到这个里面有仨选项,分别是High Speed Clock、Low Speed Clock、Master Clock Output。他们分别是高速时钟、低速时钟、主时钟输出。对于这几个时钟主要看自己的选择,一般而言都只用高速时钟。

2. 点击下拉菜单选择Crystal/Ceramic Resonator。

3. 进入时钟配置界面点击这个按钮。

4. PLL Source Mux选择我们刚刚开启的HSE。System Clock Mux选择PLLCLK。这里了解一下PLL Source Mux和System Clock Mux。PLL Source Mux是PLL资源选择器,System Clock Mux是系统时钟选择器。他们都能进行分频和倍频。其中值得我们了解的是PLL和Mux。你可能会疑惑这不就是俩单词吗?事实上,他们的含义都是硬件。

PLL是锁相环的缩写phase-locked loop,Mux是多路选择器的缩写multiplexer。

时钟配置和定时器配置都是STM32CubeMX生成代码的前置步骤,下面我们分别进行介绍:

1. 时钟配置

在 HCLK 中改写数值为72,因为一般而言时钟频率越高越好,CPU的主要性能指标之一。然后按下回车键,软件会自动帮我们选择好分频和倍频系数。至此我们完成了时钟配置。

接下来配置定时器,定时器属于外设,使用的是RCC的时钟源,所以刚刚只是前置,下面我们来配置定时器。回到引脚设置界面,首先打开Timers,我们能看到 RTC、TIM1、TIM2、TIM3、TIM4 5个定时器,事实上IWDG和WWDG也是定时器,他们叫做看门狗。他们的作用比较特殊暂时不在这里讲解。现在我们点击TIM2,查看TIM2的配置界面。从上往下介绍如下:

- Slave mode 就是从模式,一般我们用不到它,此处默认不开启;

- Trigger mode 是触发模式,选择你的触发源,和定时器中断有关,暂时用不到,默认不开启;

- Clock source 选择定时的时钟源,有 disable、internal clock(内部时钟)、ETR2 三个选项,一般我们使用 internal clock。如果我们使用定时器的 PWM 功能,输入捕获功能则不必管这个选项默认不开启即可;

- 下面依次四个就是这个定时器拥有的四个定时器通道;

- 再接着往下就是组合通道也很少用,主要是用来处理一些特殊的需求,比如编码器;

- 再接着往下看有两个灰了,如果有选项灰了一般是这几种情况,配置不正确、被占用、未下载对应的软件包。事实上我们在创建工程时自动下载的只是最基础的软件包;

- 最后一个就是单脉冲模式。

至于我们想要拓展软件包可以在这里下载:点击 Select Component 进行选择下载。现在如果不考虑定时器频率的情况下我们已经完成了对于定时器的配置。但是该学还得学,下面我们来配置定时器的相关参数,我们这里配置一个1kHz的频率。将 Prescaler 和 Counter Period 设置如图即可。

现在我们如何去检测我们设置的定时器是否正确工作呢?现在我们开启这个定时器的中断,用这个中断来控制一个引脚的输出。然后将引脚接上示波器就能观察到我们的定时器是否正确的工作。

步骤如下:

1. 在 Interrupts > NVIC > Priority Group Table 中添加一个新的优先级分组;

2. 在新建的优先级分组中添加一个新子组;

3. 在新建的子组中添加一个新的中断;

4. 将新建的中断与TIM2中断关联起来;

5. 在 main.c 中编写中断服务程序;

6. 在 HAL_TIM_MspPostInit() 函数中启用中断。

我们可以在工程中看到新的内容,例如一个名为tim的文件。现在让我们回到main文件,编写程序并在预定的function位置编写中断回调函数。这次我们使用的函数是`void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);`。对于定时器有很多中断回调函数,针对不同的中断事件预设,可以在`stm32f1xx_hal_tim.h`的第2069行看到声明。

接下来,在main函数里开启定时器。需要注意的是,与外设相关的程序都必须写在这个位置,或者说要写在`MX_XXX_Init();`这个外设初始化函数后面。否则可能会出现BUG。完成这些设置后,我们就可以构建工程并上板测试了。

然而,示波器价格昂贵,而常见的仿真软件如Proteus很可能不支持您所使用的STM32型号芯片。为了正常使用,您需要自己下载固件包并配置一大堆参数。实际上,对于芯片的片内外设都可以在Keil里进行仿真,但前提是支持这款芯片。需要注意的是,并非所有芯片都支持仿真。具体支持哪些芯片仿真,可以查询Keil官方的debug文档。

如果您想迅速查看您的芯片是否支持仿真,可以尝试以下步骤:

Step 1:在file菜单中找到Device Database,点击它。

Step 2:进入界面后,在search框中输入您使用的芯片型号并选中,例如STM32F103C8。

Step 3:如果您在下面的说明栏里找到了SVD文件的说明,那么您的芯片就是可以进行仿真的。仿真参数就是SIM后面的内容,请先记下来。

完成以上步骤后,您就可以填写仿真参数并打开设置进行软件仿真debug了。

下面我们来学习如何进行软件仿真:

Step 1:点击debug按键进入debug模式。

Step 2:打开分析仪:圈起来的就是。如果您在这里找不到分析仪,也可以在view菜单栏内找到它。

Step 3:在分析仪窗口内找到Setup,点击它。

Step 4:进入界面后创建新的按钮和通道。使用PORT指令指定引脚后按回车键完成创建。这里解释一下PORT指令:`PORTx.y`,其中x表示端口,y表示引脚号。例如:

```

PORTA.0

```

回车后如果弹出异常,请检查您在设置中填写的调试参数是否正确。一些正常的输出应该如下所示:

Step 5:选中你刚刚创建的通道,将 Display Type 改为bit,否则无法正确显示。color是用来指定在分析仪中显示的波形颜色的,不必管他。之后关闭这个界面,点击RUN就能看到波形了如图。我来选定一个周期可以看到频率为500Hz,因为我们的引脚是1ms翻转一次,所以输出的频率应该为 1k/2 结果为 500Hz与我们观察到的结果一致。定时器正确的在工作。

下面我们来更仔细地考察一下定时器,来回忆一下刚刚提到的种种疑点。

第一为什么我在配置时钟源的时候使用的是内部时钟(internal clock),而单片机使用的明明是外部高速时钟?

解答这个问题需要我们打开参考手册关于TIM定时器的图来看:这张图很重要经常使用。这里就写明了什么是内部时钟,其实这里的内部指的是CPU,说的是系统时钟或者说是挂载这个外设的APB总线的时钟。

第二设置TIM2定时器的属性都是什么意思?从上到下解释一遍,PSC就是预分频器,对TIM定时器进行分频;Counter Mode是计数模式、Counter Period 是自动重装载寄存器、internal Clock Division 是内部时钟分割系数、auto-reload preload 是自动重装载预加载。至此计数设置说完,然后是触发设置。Master/Slave Mode 是主从模式、Trigger Event Selection 是触发事件选择。

下面我们来说明三个比较关键的属性:预分频器、自动重装载寄存器和预加载。

首先PSC和自动重装载都是16位寄存器,我们可以在参考手册里找到他们的描述。这也是为什么我们在配置时要对数字“-1”操作的原因,因为这些值会直接写进寄存器,而寄存器是从0开始计数的,所以必须“-1”才能保证正确。

PSC的作用是对输入计数器的时钟进行分频,让计数器以分频后的频率进行计数。而ARR寄存器的作用是储存用户指定的值,对计数器内的计数值和ARR寄存器储存的值进行比较,当计数器内的值大于等于ARR存储的值时对计数器进行清理。如果你开启了中断,那么CPU还会接受到一个更新中断事件(UG\U)来触发中断程序。

那么预加载是干什么的?和重装载有什么关系?

在这种情况下,我们考虑一个问题:当我们的自动重装载寄存器(ARR)初始值为500并运行一段时间后,我们需要更改ARR的值以另一种频率执行中断程序。那么,我们应该如何处理呢?如果对时序要求不太严格,可能不会有太大问题。但如果我们需要严格的时序控制呢?这时就需要使用预装载功能。

预装载的作用在于,当我们修改了ARR的值后,这个改变并不会立即生效。只有当ARR在修改后产生了一次更新事件(URQ),ARR的值才会在下一次更新后生效。这个状态对应的是CR1寄存器的ARPE位。

由于本篇教程已经相当详细,为了保证读者能够更好地理解定时器的工作原理和寄存器设置,下一篇将详细介绍定时器的寄存器等内容。希望本教程对你有所帮助,如有收获,请不要吝啬点赞或分享哦~