STM32通用定时器端口复用与重映射详解
STM32定时器是一种内置在STM32微控制器中的硬件模块,用于测量和控制时间。它具有高精度、可配置性和灵活性的特点,能够支持多种不同的工作模式和应用场景。以下是对STM32定时器的详细讲解:
一、定时器的基本构成
STM32定时器主要由以下几个部分构成:
二、定时器的分类
STM32定时器根据其功能和特点可以分为以下几类:
特点:
三、定时器的工作原理
STM32定时器的工作原理基于计数器的计数功能。当定时器的时钟源开始工作时,计数器开始按照预设的计数方向和速率进行计数。当计数值达到自动重装载寄存器的值时,产生溢出事件或中断,同时计数器可以选择性地重新从0开始计数,以实现周期性定时。
四、定时器的配置要点
在配置STM32定时器时,需要注意以下几个要点:
五、应用实例
通过中断控制LED灯闪烁
需要掌握的:
TIM_TimeBaseInitTypeDef
这个结构体用于配置定时器的基本时间基准参数,包括预分频器、计数器模式、自动重装载值、时钟分频和重复计数器(仅适用于TIM1和TIM8)。
NVIC_InitTypeDef
结构体是用于STM32微控制器中,特别是在配置嵌套向量中断控制器(NVIC)时使用的。这个结构体包含了配置一个中断通道所需的所有参数。下面是对每个成员的详细解释:
uint8_t NVIC_IRQChannel;
- 这个成员指定了要启用或禁用的中断通道。它的值应该来自于
IRQn_Type
枚举类型,这个枚举类型在STM32的库文件(如stm32f10x.h
对于STM32F1系列)中定义,列出了所有可用的中断请求(IRQ)通道。 uint8_t NVIC_IRQChannelPreemptionPriority;
- 这个成员指定了
NVIC_IRQChannel
指定的中断通道的抢占优先级。STM32的NVIC支持基于优先级的嵌套中断,抢占优先级决定了中断是否能够打断另一个正在执行的中断。这个值的范围是从0到15,数值越小,优先级越高。 uint8_t NVIC_IRQChannelSubPriority;
- 这个成员指定了
NVIC_IRQChannel
指定的中断通道的子优先级。在抢占优先级相同的情况下,子优先级用于决定中断的执行顺序。这个值的范围也是从0到15,数值越小,优先级越高。 FunctionalState NVIC_IRQChannelCmd;
- 这个成员指定了
NVIC_IRQChannel
指定的中断通道是启用还是禁用。FunctionalState
是一个枚举类型,通常有两个值:ENABLE
和DISABLE
。这个成员用于控制中断的使能状态。
主函数:
/**
* @brief TIM2定时器更新中断服务例程
*
* 当TIM2定时器发生更新事件(即计数器从最大值回绕到0时)时,此函数会被自动调用。
* 它利用定时器的更新中断来切换连接到GPIOA端口的第1个引脚(GPIO_Pin_1)的电平状态。
* 这可以用于生成简单的方波信号或进行周期性的任务处理。
*/
void TIM2_IRQHandler(void)
{
static uint16_t temp; // 定义一个静态变量temp,用于在中断之间保持状态
// 检查是否发生了TIM2的更新中断
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
// 如果确实发生了更新中断
// 使用temp变量来切换状态,每次中断时temp自增
// 如果temp是奇数,则执行一个分支;如果是偶数,则执行另一个分支
if(temp++ % 2 == 1)
{
// 如果temp是奇数,将GPIOA的第1个引脚置为低电平
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
else
{
// 如果temp是偶数,将GPIOA的第1个引脚置为高电平
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
// 清除TIM2的更新中断标志,以便允许下一个中断被识别
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
// 注意:在大多数情况下,不需要在这里添加else分支,
// 因为如果TIM_GetITStatus返回RESET,则没有必要执行任何操作。
// 但是,如果你希望在未发生更新中断时执行某些操作,可以在这里添加代码。
}
定时器函数:
/**
* @brief 配置TIM2定时器
*
* 这个函数负责初始化STM32的TIM2定时器,包括时钟源、预分频器、自动重装载值、计数器模式等,
* 并启用定时器的更新中断,以及配置NVIC以允许定时器中断。
*/
void tim_config(void)
{
TIM_TimeBaseInitTypeDef timinitstructer; // 定义一个定时器基本初始化结构体变量
NVIC_InitTypeDef nvicinitstruct; // 定义一个NVIC初始化结构体变量
// 使能TIM2定时器的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 配置NVIC的优先级分组为NVIC_PriorityGroup_1
// 这会影响所有中断的优先级设置方式
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
// 配置TIM2定时器的基本参数
timinitstructer.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频因子设置为不分频
timinitstructer.TIM_CounterMode = TIM_CounterMode_Up; // 计数器模式设置为向上计数
timinitstructer.TIM_Period = 10000 - 1; // 自动重装载寄存器周期值,这里设置为10000(实际计数到9999)
timinitstructer.TIM_Prescaler = 7200 - 1; // 预分频器值,这里设置为7200(实际分频7199)
// 注意:TIM_RepetitionCounter 字段在某些定时器中用于高级控制模式,如PWM重复计数,这里不适用
TIM_TimeBaseInit(TIM2, &timinitstructer); // 使用上述配置初始化TIM2定时器
// 启用TIM2定时器的更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 启用TIM2定时器
TIM_Cmd(TIM2, ENABLE);
// 配置NVIC以允许TIM2中断
nvicinitstruct.NVIC_IRQChannel = TIM2_IRQn; // 指定中断通道为TIM2中断
nvicinitstruct.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道
nvicinitstruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级设置为1
nvicinitstruct.NVIC_IRQChannelSubPriority = 1; // 子优先级设置为1(在NVIC_PriorityGroup_1中,子优先级可能不被使用)
NVIC_Init(&nvicinitstruct); // 使用上述配置初始化NVIC
}
理解为什么这段代码能够让定时器“看起来”一秒一秒地执行,需要关注几个关键点:定时器的预分频器(Prescaler)、自动重装载值(Period)、以及STM32的时钟系统。
首先,让我们回顾一下tim_config
函数中的关键设置:
-
预分频器(Prescaler):这个值用于将定时器的时钟源(通常是系统时钟经过APB1预分频后的时钟)进一步分频。例如,如果系统时钟是72MHz,并且APB1预分频器设置为1(意味着APB1时钟与系统时钟相同),然后TIM2的预分频器设置为7199(因为你写的是7200-1),那么TIM2的计数器时钟将是72MHz / (7199 + 1) = 10kHz。
-
自动重装载值(Period):这个值定义了定时器计数器在达到哪个值时会触发更新事件(通常是回绕到0并生成更新中断),并重新开始计数。在你的代码中,这个值被设置为9999(因为你写的是10000-1)。因此,从0计数到9999需要的时间是10kHz的倒数乘以10000,即0.0001秒 * 10000 = 1秒。
现在,让我们看看TIM2_IRQHandler
函数是如何工作的:
TIM2_IRQHandler
函数被调用,并检查是否确实发生了更新中断。然而,这里有一点需要注意:虽然定时器的周期是精确的1秒(基于其配置),但GPIO引脚状态的切换(即方波的生成)的精确性还受到中断响应时间和中断处理函数执行时间的影响。在大多数情况下,这些影响是可以忽略不计的,但在对时间要求非常严格的应用中,可能需要考虑这些因素。
另外,请注意,temp
变量在这个特定的例子中实际上并没有直接用于控制定时器的周期或频率。它仅用于在每次中断时切换GPIO引脚的状态。如果你想要的是纯粹的定时器功能(即不需要GPIO切换),那么temp
变量和相关的条件判断就不是必需的。不过,在你的例子中,它用于生成一个周期为1秒的方波信号,从而实现LED灯的闪烁。
端口复用
定义:
STM32的每个端口引脚通常具有多种功能,例如GPIO(通用输入输出)、USART(通用同步异步收发传输器)、TIM(定时器)等。端口复用是指将一个引脚从默认的GPIO功能切换到其他功能,如USART的TX(发送)或RX(接收)等。
实现步骤:
使能GPIO端口时钟和外设时钟:
首先,需要使能包含USART1 TX和RX引脚的GPIO端口(如果是PA9和PA10,则是GPIOA)的时钟,以及USART1的时钟。
配置GPIO引脚:
将GPIO引脚(PA9和PA10)配置为复用推挽输出(对于TX)和浮空输入(对于RX)。注意,在STM32的库中,复用模式通常是通过设置GPIO的模式为GPIO_Mode_AF_PP
(复用推挽输出)或GPIO_Mode_IN_FLOATING
(浮空输入,但RX通常不需要显式设置,因为它在USART初始化时自动配置)来实现的。然而,对于USART的RX,你通常会设置它为GPIO_Mode_AF_IPU
(复用输入上拉),但这取决于你的具体需求和硬件配置。
初始化USART:
最后,初始化USART1,设置波特率、数据位长度、停止位等参数。在这个过程中,库函数会自动将TX和RX引脚配置为它们的外设功能模式。
重映射
定义:
重映射功能允许开发者将一个外设的引脚映射到其他引脚,以适应不同的硬件布局需求。这在进行硬件设计时特别有用,可以解决引脚冲突或优化引脚布局。
实现步骤:
使能AFIO时钟(对于某些STM32系列,如STM32F1,需要使能AFIO时钟以访问重映射功能。但在更新的STM32系列中,这一步骤可能已被简化或取消):通过配置RCC(复位和时钟控制)模块,使能包含重映射寄存器的时钟。
配置重映射寄存器:使用STM32的库函数或直接操作重映射寄存器,将外设的引脚映射到新的位置。这通常涉及到设置特定的重映射位或选择重映射选项。
注意:
作者:翻斗花园第一后卫胡图图