主要参考学习资料:
B站@江协科技
STM32入门教程-2023版 细致讲解 中文字幕
开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb
单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协
实验:
- 定时器定时中断
- 定时器外部时钟
新函数:
- 定时器库函数(部分)
目录
- TIM简介
- 定时器类型
- 定时器结构
- 基本定时器
- 通用定时器
- 高级定时器
- 定时中断
- 定时中断基本结构
- 时基单元时序图
- 预分频器时序
- 计数器时序
- RCC时钟树
- 函数详解
- 定时中断初始化
- TIM_DeInit函数
- TIM_TimeBaseInit函数
- TIM_TimeBaseInitTypeDef结构体
- TIM_TimeBaseStructInit函数
- TIM_Cmd函数
- TIM_ITConfig函数
- 时钟选择
- TIM_InternalClockConfig函数
- TIM_ITRxExternalClockConfig函数
- TIM_TIxExternalClockConfig函数
- TIM_ETRClockMode1Config函数
- TIM_ETRClockMode2Config函数
- TIM_ETRConfig函数
- 定时器配置
- TIM_PrescalerConfig函数
- TIM_CounterModeConfig函数
- TIM_ARRPreloadConfig函数
- 定时器读写
- TIM_SetCounter函数
- TIM_SetAutoreload函数
- TIM_GetCounter函数
- TIM_GetPrescaler函数
- 中断标志位
- 实验8 定时器定时中断
- 接线图
- 定时器驱动
- 主程序
- 实验9 定时器外部时钟
- 接线图
- 定时器驱动
- 主程序
TIM简介
- TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。
- STM32的定时器拥有16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时。
- 定时器不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。
- 根据复杂度和应用场景,定时器分为高级定时器、通用定时器、基本定时器三种类型。
定时器类型
STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
定时器结构
基本定时器
基本定时器只能以内部时钟为时钟源,其信号来源于RCC的TIMxCLK,频率值一般为系统主频72MHz。
预分频器对72MHz的计数时钟进行预分频。预分频器写入n则分频系数为n+1,此时输出频率=输入频率/(n+1)。该预分频器为16位,最大可进行65536分频。
计数器对预分频后的计数时钟进行计数,每到达一个上升沿,计数器的值加一。该计数器为16位,其目标值最大为65535,目标值由自动重装载寄存器存储。计数器到达目标值继续计数将重新从零开始,并产生通往NVIC的更新中断,同时还可以产生更新事件,触发内部其他电路的工作。
除了上述向上计数方式,定时器还有向下计数(从目标值倒数到零后产生中断,重新从目标值倒数)和中央计数(向上计数和向下计数交替),一般使用向上计数方式。
通用定时器
通用定时器的时钟源除了内部时钟还可以选择外部时钟。
TIMx_ETR来自相应的引脚,可接入外部方波时钟,并通过配置极性选择、边沿检测、预分频和输入滤波对外部时钟进行整形。整形后的时钟信号一路由ETRF通往时基单元,为“外部时钟模式2”;另一路由TRGI用于触发定时器从模式,后续补充,若将其作为外部时钟使用,则为“外部时钟模式1”。两种模式对时钟输入等价,只是模式1会占用触发输入通道。
ITR来自其他定时器的TRGO输出,连接方式参考数据手册。通过将其他定时器的更新事件映射到TRGO上,可以实现定时器级联,从而对更长的时间计数。
TI1F_ED来自输入捕获单元的TIMx_CH1引脚,ED(edge)意为边沿,即通过这一路输入的时钟上升沿和下降沿均有效。TI1FP1和TI2FP2来自输入捕获单元的TIMx_CH1和TIMx_CH2引脚。以上为外部时钟模式1的内容,一般情况使用TIMx_ETR即可,其余情况为扩大时钟输入范围和其余特殊应用场景设计,后续补充。
下方右侧为四通道输出比较电路,可用于输出PWM波形驱动电机。左侧为四通道输入捕获电路,可用于测量输入方波的频率。输出比较和输入捕获不会同时使用,它们共用中间的捕获/比较寄存器。
高级定时器
高级定时器在通用定时器基础上有如下改动:
申请中断处增加重复次数计数器,可以实现每隔几个计数周期发生一次更新中断和更新事件。
输出比较模块升级,暂时不必深入了解。三个通道的输出引脚由一个变成两个,可以输出一对互补的PWM波,用于驱动三相无刷电机。三相无刷电机有三个桥臂,每个桥臂由两个大功率开关管控制。DTG死区生成电路用于在开关切换瞬间产生一定时长的死区使桥臂上下管全都关断,防止直通现象。
增加刹车输入功能,为电机驱动提供安全保障。如果外部引脚TIMx_BKIN产生刹车信号或内部时钟失效,控制电路将自动切断电机输出防止意外发生。
定时中断
定时中断基本结构
以上为定时中断用到的结构,其中中断输出控制位中断输出的允许位。
时基单元时序图
预分频器时序
CK_PSC为输入时钟。CNT_EN为计数器使能,高电平计数器正常运行,低电平计数器停止。CK_CNT是预分频器的时钟输出和计数器的时钟输入。一开始计数器未使能,计数器时钟不运行。使能后,前半段预分频器系数为1,计数器时钟等于输入时钟。后半段预分频器系数为2,计数器时钟变为输入时钟的一半。计数器寄存器跟随时钟上升沿自增,在达到图中情况的自动重装值FC后重新从零开始计数,并产生一个更新事件。
后三行时序为预分频寄存器的缓存机制。预分频控制寄存器供我们读写,预分频缓冲器决定预分频器的分频系数。在写入新的分频系数后,缓冲器会等待当前计数周期完成再接收控制寄存器的值,以保证一个计数周期内频率的统一性。预分频计数器在分频系数为0时始终为0,预分频器直接输出原频率;分频系数不为0时,预分频计数器以分频系数为目标值计数,并在回到0时,CK_CNT输出一个脉冲。
计数器计数频率:CK_CNT=CK_PSC/(PSC+1)
CK_PSC:输入时钟频率;PSC:预分频控制寄存器写入值。
计数器时序
当计数器寄存器到达自动重装值时,下一次计数清零,产生计数器溢出和更新事件,并将更新中断标志位置一以申请中断,中断响应后需要再中断程序中手动清零。
计数器溢出频率:CK_CNT_OV=CK_CNT/(ARR+1)
ARR:自动重装值。
自动重装寄存器也有与预分频器类似的缓存机制,并且可以设置是否使用(预装入)。无预装时,向自动重装寄存器写入新值即改变当前计数周期的自动重装值:
但如果计数器计数已经超过更新的自动重装值,计数器只能计数到其本身的最大值再重新从零开始计数到自动重装值。有预装时,自动重装值会在当前计数周期结束后再更新到计数器中:
RCC时钟树
RCC时钟树是STM32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统。时钟是所有外设运行的基础,因此也是最先需要配置的东西。程序在主函数之前会隐式执行ST公司提供的SystemInit函数以配置时钟树。RCC时钟树以AHB预分频器为界,左侧为时钟产生电路,右侧为时钟分配电路,SYSCLK即72MHz系统时钟。
时钟产生电路有8MHz HSI RC(内部)、4-16MHz HSE OSC(外部)、32.768kHz LSE OSC(提供给RTC)和40kHz LSI RC(提供给看门狗)四个晶振。前两个高速晶振用于提供AHB、APB2、APB1的系统时钟,外部石英振荡器比内部RC振荡器更稳定,故一般使用外部晶振。若系统简单、不需要精确的时钟,可使用内部晶振以省下外部晶振电路。SystemInit函数会先选择启动内部时钟为系统时钟,再启动外部时钟,经PLL锁相环倍频9倍后得到72MHz,待锁相环输出稳定后选择锁相环输出为系统输出。CSS时钟安全系统检测外部时钟状态,一旦外部时钟失效则自动把外部时钟切换回内部时钟,保证系统时钟运行,防止程序卡死造成事故。
时钟分配电路中,系统时钟进入AHB总线,在AHB预分频器中根据SystemInit函数配置的分频系数一分频。时钟进入APB1总线再由APB1预分频器二分频至ABP1外设,而定时器仍将时钟还原为72MHz作为内部基准时钟(前提是不随意更改SystemInit函数配置)。APB2预分频器分频系数为1,因此时钟也为72MHz。每个时钟输出还会与外设时钟使能经过一个与门,外设时钟使能即为程序中RCC_APB2/1PeriphClockCmd作用的地方,只有使能后时钟才能通过与门输出给外设。
函数详解
本次介绍将要使用的部分定时器库函数。
定时中断初始化
TIM_DeInit函数
简介:恢复定时器初始配置。
参数:定时器名称
TIM1, ..., TIM17
TIM_TimeBaseInit函数
简介:时基单元初始化。
参数一:定时器名称
参数二:指向初始化信息TIM_TimeBaseInitTypeDef结构体的指针
TIM_TimeBaseInitTypeDef结构体
成员TIM_ClockDivision:内部时钟分频,提供滤波器采样频率。
TIM_CKD_DIV1, TIM_CKD_DIV2, TIM_CKD_DIV4
成员TIM_CounterMode:计数器模式。
TIM_CounterMode_Up(向上计数),
TIM_CounterMode_Down(向下计数),
TIM_CounterMode_CenterAligned1(先上后下中央计数),
TIM_CounterMode_CenterAligned2(先下后上中央计数),
TIM_CounterMode_CenterAligned3(交替使用两种中央计数)
成员TIM_Period:自动重装值。
成员TIM_Prescaler:预分频寄存器值。
成员TIM_RepetitionCounter:重复计数器值。
TIM_TimeBaseStructInit函数
简介:为TIM_TimeBaseInitTypeDef结构体变量赋默认值。
参数:指向初始化信息TIM_TimeBaseInitTypeDef结构体的指针
TIM_Cmd函数
简介:使能计数器。
参数一:定时器名称
参数二:使能/失能
TIM_ITConfig函数
简介:使能中断输出信号。
参数一:定时器名称
参数二:选择中断输出
TIM_IT_Update(更新中断)
TIM_IT_CC1, ..., TIM_IT_CC4(捕获/比较通道中断)
TIM_IT_COM(电机换相中断)(高级定时器)
TIM_IT_Trigger(触发中断)
TIM_IT_Break(刹车中断)(高级定时器)
参数三:使能/失能
时钟选择
TIM_InternalClockConfig函数
简介:选择内部时钟。
参数:定时器名称
TIM_ITRxExternalClockConfig函数
简介:选择ITRx其他定时器的时钟。
参数一:定时器名称
参数二:选择的ITR通道
TIM_TS_ITR0, ..., TIM_TS_ITR3
TIM_TIxExternalClockConfig函数
简介:选择TIx捕获通道的时钟。
参数一:定时器名称
参数二:选择的TI引脚
TIM_TIxExternalCLK1Source_TI1ED, TIM_TIxExternalCLK1Source_TI1
TIM_TIxExternalCLK1Source_TI2
参数三:极性(边沿触发方式)
TIM_ICPolarity_Rising, TIM_ICPolarity_Falling
参数四:滤波器(每个值对应一个采样频率和确认电平信号有效的连续采样次数,详见数据手册)
0x00~0x0F
TIM_ETRClockMode1Config函数
简介:选择ETR通过外部时钟模式1输入的时钟。
参数一:定时器名称
参数二:外部触发预分频器(以下分别为一/二/四/八分频)
TIM_ExtTRGPSC_OFF
TIM_ExtTRGPSC_DIV2, TIM_ExtTRGPSC_DIV4, TIM_ExtTRGPSC_DIV8
参数三:极性
TIM_ExtTRGPolarity_Inverted(上升沿), TIM_ExtTRGPolarity_NonInverted(下降沿)
参数四:滤波器
TIM_ETRClockMode2Config函数
简介:选择ETR通过外部时钟模式2输入的时钟。
参数一:定时器名称
参数二:外部触发预分频器
参数三:极性(同TIM_ETRClockMode1Config函数)
参数四:滤波器
TIM_ETRConfig函数
简介:单独配置ETR引脚的预分频器、极性和滤波器。
参数一:定时器名称
参数二:外部触发预分频器
参数三:极性(同TIM_ETRClockMode1Config函数)
参数四:滤波器
定时器配置
TIM_PrescalerConfig函数
简介:配置定时器的预分频器。
参数一:定时器名称
参数二:预分频控制寄存器写入值
参数三:是否预装
TIM_PSCReloadMode_Update(预装), TIM_PSCReloadMode_Immediate(无预装)
TIM_CounterModeConfig函数
简介:配置计数器的计数模式。
参数一:定时器名称
参数二:计数器模式
TIM_ARRPreloadConfig函数
简介:配置自动重装器预装功能。
参数一:定时器名称
参数二:使能/失能
定时器读写
TIM_SetCounter函数
简介:给计数器写入一个值。
参数一:定时器名称
参数二:写入值
TIM_SetAutoreload函数
简介:给自动重装器写入一个值。
参数一:定时器名称
参数二:自动重装值
TIM_GetCounter函数
简介:获取当前计数器的值。
参数:定时器名称
TIM_GetPrescaler函数
简介:获取当前预分频器的值。
参数:定时器名称
中断标志位
主程序读取、清除中断标志位:
TIM_GetFlagStatus
TIM_ClearFlag
中断程序读取、清除中断标志位:
TIM_GetITStatus
TIM_ClearITPendingBit
参数一:定时器名称
参数二:中断线路
实验8 定时器定时中断
实现功能:OLED显示数字从零开始每秒加一。
接线图
定时器驱动
由于定时器是内部硬件,因此驱动文件放在System中。
Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
Timer.c
#include "stm32f10x.h"
void Timer_Init(void)
{
//启用TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//选择内部时钟(定时器上电默认使用内部时钟,可不写)
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//用不到ETR滤波器,随意配置
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//向上计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//定时1s,即计数器溢出频率1Hz,根据公式分配数值
//取值不唯一,勿超出65535
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
//通用定时器无重复计数功能
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
//为了立刻更新定时器配置,TIM_TimeBaseInit会主动触发更新事件
//更新事件会将中断标志位置一,每次中断计数变量Num加一
//清除中断标志位,否则该程序功能将从1开始计数
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}
主程序
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1, 1, "Num:");
while(1)
{
OLED_ShowNum(1, 5, Num, 5);
}
}
//中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
实验9 定时器外部时钟
实现功能:每遮挡一次对射式红外传感器计数器加一,每遮挡十次OLED显示Num加一。
接线图
定时器驱动
该实验驱动在实验8基础上更改,时钟选择外部时钟并配置相应GPIO,添加读取计数器计数值函数。更改部分用注释标明。
Timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
uint16_t Timer_GetCounter(void);
#endif
Timer.c
#include "stm32f10x.h"
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//选择外部时钟
//不使用预分频器,下降沿触发,滤波器采样频率来自32分频,连续采样8次
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//从0计到9
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;
//遮挡手速慢,无需分频
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
//返回计数器计数值
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
主程序
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1, 1, "Num:");
OLED_ShowString(2, 1, "CNT:");
while(1)
{
OLED_ShowNum(1, 5, Num, 5);
//显示计数器计数值
OLED_ShowNum(2, 5, Timer_GetCounter(), 5);
}
}
//中断函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}