当前位置: 首页 > news >正文

STM32定时器(寄存器与HAL库实现)

一、系统定时器

1.系统定时器简介

系统定时器(SysTick系统嘀嗒定时器)是属于CM3内核,内嵌在NVIC中。

系统定时器是一个24bit的向下递减的计数器,计数器每计数一次的时间为1 / SYSCLK,一般我们设置系统时钟SYSCLK(与AHB相同)等于72M。当重装载数值寄存器的值递减到0的时候,系统定时器就产生一次中断,以此循环往复。

Systick定时器的主要功能包括实现简单的延时、生成定时中断以及进行精确定时和周期定时操作。此外,Systick定时器还可以被用于其他目的,例如作为操作系统的时基(如FreeRTOS),或者用于软件看门狗等系统调度操作。在STM32中,Systick通常以HCLK(AHB时钟)或HCLK/8作为运行时钟。

2.系统定时器相关寄存器

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

LOAD寄存器主要用于存储当计数器递减到 0 时,要重新装载到 VAL 寄存器的值。

VAL寄存器是一个递减计数器,它会不断从 LOAD 寄存器获取初始值,然后进行递减操作。

SysTick 校准数值寄存器。很少用到。

3.系统定时器实验:LED闪烁

利用系统定时器的中断,每隔1s 让LED1灯闪烁一次

3.1 寄存器实现

#include "systick.h"void systick_init(void)
{// 1. 配置时钟源 1=AHB(72MHZ) 0=AHB/8SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;// 2. 使能中断SysTick->CTRL |= SysTick_CTRL_TICKINT;// 3. 每1ms产生一次中断// 减到0后再经过一个时钟周期才处理中断SysTick->LOAD = 72000 - 1;// 4. 清除计数值SysTick->VAL = 0;// 5. 使能定时器SysTick->CTRL |= SysTick_CTRL_ENABLE;
}uint16_t count = 0;
void SysTick_Handler(void)
{count++;// 1sif (count == 1000){count = 0;// led翻转电平LED_Toggle();}
}

SysTick 是内核中断,优先级通过 SCB->SHP[11] 配置,默认优先级较低。若需高优先级,需显式设置:

NVIC_SetPriority(SysTick_IRQn, 0);  // 设置为最高优先级

上面代码未显式设置优先级,但 SysTick 的默认优先级已经存在(通常为最低)。如果应用中没有其他中断或对优先级无特殊要求,默认配置即可工作。

3.2 hal库实现

在hal库初始化的时候,会初始化SysTick定时器

以下为自动生成的部分代码

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{/* Configure the SysTick to have interrupt in 1ms time basis*/// 配置滴答定时器:每1ms产生一次中断// SystemCoreClock = 72MHz// uwTickFreq = 1// 参数=72000 重装载寄存器的值 值减到0产生一次中断// 计数一次是(1/72000 0000)s = (1/72)us// 那么计数72000次就是1msif (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U){return HAL_ERROR;}/* Configure the SysTick IRQ priority */if (TickPriority < (1UL << __NVIC_PRIO_BITS)){// 设置SysTick优先级:抢占优先级15(最低) 和响应优先级0HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);uwTickPrio = TickPriority;}else{return HAL_ERROR;}/* Return function status */return HAL_OK;
}

一般建议把SysTick定时器的抢占优先级设置为最高(数字越小,优先级越高)。否则在其他中断中使用延时函数的时候会阻塞卡死。

uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{return SysTick_Config(TicksNumb);
}__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk){return (1UL);                                                   /* Reload value impossible */}// 重装载寄存器的值SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */// 中断优先级NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */// 当前数值寄存器的值SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */// 设置时钟源// 计数至0,产生异常// 使能SysTick计数器SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |SysTick_CTRL_TICKINT_Msk   |SysTick_CTRL_ENABLE_Msk;                         /* Enable SysTick IRQ and SysTick Timer */return (0UL);                                                     /* Function successful */
}

在stm32f1xx_it.c中定义了中断服务函数

void SysTick_Handler(void)
{/* USER CODE BEGIN SysTick_IRQn 0 *//* USER CODE END SysTick_IRQn 0 */HAL_IncTick();/* USER CODE BEGIN SysTick_IRQn 1 *//* USER CODE END SysTick_IRQn 1 */
}
// 递增全局变量uwTick
// 每1ms递增一次
__weak void HAL_IncTick(void)
{uwTick += uwTickFreq;
}

我们可以自己重新实现HAL_IncTick函数

在main.c中添加自己的实现

void HAL_IncTick(void)
{uwTick += uwTickFreq;if(uwTick % 100 == 0){// 产生1s计时// 翻转LED灯HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_1);}
}

注意这里是对uwTick取模,因为不能随便修改uwTick的值。

因为如果需要使用HAL_Delay延时的话,HAL_Delay函数中调用了HAL_GetTick,而HAL_GetTick就是要返回uwTick的值。

__weak uint32_t HAL_GetTick(void)
{return uwTick;
}

4.延时函数实现

// 延时函数,微秒作为单位,利用系统嘀嗒定时器,72MHz,一次嘀嗒 1/72 us
void Delay_us(uint16_t us)
{// 1. 装载一个计数值,72 * usSysTick->LOAD = 72 * us;// 2. 清除计数值SysTick->VAL = 0;// 3. 配置,使用系统时钟(1),计数结束不产生中断(0),使能定时器(1)SysTick->CTRL = 0x05;// 4. 等待计数值变为0,判断CTRL标志位COUNTFLAG是否为1while ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG) == 0){}// 5. 关闭定时器SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
}void Delay_ms(uint16_t ms)
{while (ms--){Delay_us(1000);}
}void Delay_s(uint16_t s)
{while (s--){Delay_ms(1000);}
}

二、基本定时器

STM32F103系列提供了8个定时器:2个基本定时器(TIM6,7),4个通用定时器(TIM2-5),2个高级定时器(TIM1和TIM8)。

1. 基本定时器介绍

基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
这2个定时器是互相独立的,不共享任何资源。
这2个基本定时器只能向上计数,由于没有外部IO,所以只能计时,不能对外部脉冲进行计数。
功能:定时中断,主模式,触发DAC。

在这里插入图片描述

1.1 时钟源

只有一种时钟源,内部时钟,一般为72MHz

在这里插入图片描述

1.2 时基单元

  • 预分频寄存器

    将过来的时钟信号进行预分频,按照1到65536之间的任意值分频。

  • 计数器
    基本定时器只能向上计数,从0开始自增。自增到自动重装载寄存器的值时,下一个时钟上升沿到来后,计数器产生溢出,从0重新计数,产生更新事件。
    如果开启中断,也会产生更新中断。

  • 自动重装载寄存器
    包含预加载寄存器和影子寄存器。
    写数据到自动重装载寄存器时候先写到预加载寄存器,然后再更新到影子寄存器。
    计数器是否溢出,是查看影子寄存器的值。
    寄存器CR1的ARPE位决定是否预加载,没有预加载时,写入的值会立即更新到影子寄存器,有预加载时,写入的值会等到产生更新事件(计数器溢出)才更新到影子寄存器。

1.3 计算定时时间

计数器多久产生一次溢出

  • 计数器时钟频率:
    真正的分频值=预分频系数+1
    内部时钟频率/(预分频系数+1)

  • 计数器的周期:累加一次需要的时间
    (预分频系数+1)/内部时钟频率

  • 计数器累加多少次产生一次更新事件:
    自动重装载值+1

综上,定时时间为:

[(预分频系数+1)/内部时钟频率]*(自动重装载值+1)

假设选择定时1s,且内部时钟频率为72MHz,怎么配置相关数值?

令预分频系数=7200-1,计数器频率=10000,则自动重装载值=10000-1

2. 基本定时器实验:LED灯闪烁

利用基本定时器功能,实现LED灯闪烁

2.1 寄存器实现

#include "tim6.h"// 定时1s
void TIM6_Init(void)
{// 1. 定时器6开启时钟RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;// 2. 设置预分频值 7199表示7200分频TIM6->PSC = 7200 - 1;// 3. 设置自动重装载值// 表示计数器计数10000次发生一次中断 计数一次100usTIM6->ARR = 10000 - 1;// 4. 使能更新中断,让定时器自身能够产生中断信号TIM6->DIER |= TIM_DIER_UIE;// 5. 设置中断优先级分组NVIC_SetPriorityGrouping(3);// 6. 设置中断优先级NVIC_SetPriority(TIM6_IRQn, 1);// 7. 使能定时器中断,使处理器能够接受处理中断信号NVIC_EnableIRQ(TIM6_IRQn);// 8. 使能计数器TIM6->CR1 |= TIM_CR1_CEN;
}void TIM6_IRQHandler(void)
{TIM6->SR &= ~TIM_SR_UIF;LED_Toggle();
}

2.2 hal库实现

在这里插入图片描述

Trigger Event Selection: 表示主模式下向其他定时器发送的触发信号,这里忽略

开启定时器中断
在这里插入图片描述

生成的定时器部分代码

TIM_HandleTypeDef htim6;void MX_TIM6_Init(void)
{TIM_MasterConfigTypeDef sMasterConfig = {0};htim6.Instance = TIM6;htim6.Init.Prescaler = 7200-1;htim6.Init.CounterMode = TIM_COUNTERMODE_UP;htim6.Init.Period = 10000-1;htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;if (HAL_TIM_Base_Init(&htim6) != HAL_OK){Error_Handler();}sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK){Error_Handler();}}void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{if(tim_baseHandle->Instance==TIM6){/* TIM6 clock enable */__HAL_RCC_TIM6_CLK_ENABLE();/* TIM6 interrupt Init */HAL_NVIC_SetPriority(TIM6_IRQn, 0, 0);HAL_NVIC_EnableIRQ(TIM6_IRQn);}
}

相关中断服务函数

void TIM6_IRQHandler(void)
{
// HAL库定时器处理总函数HAL_TIM_IRQHandler(&htim6);
}

进入HAL_TIM_IRQHandler,这里实现对各种回调函数的调用,中断标志位的清除。

选择我们需要实现的具体中断回调函数HAL_TIM_PeriodElapsedCallback

然后实现

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM6){// led翻转HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);}
}

然后再开启计数器

int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_TIM6_Init();/* USER CODE BEGIN 2 */// 使能更新中断  __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);// 开启计数器  __HAL_TIM_ENABLE(htim);HAL_TIM_Base_Start_IT(&htim6);/* USER CODE END 2 */while (1){}
}

三、通用定时器

1. 通用定时器介绍

通用定时器有4个分别是:TIM2、TIM3、TIM4、TIM5。它们拥有基本定时器所有功能。并增加如下功能:

(1)多种时钟源。

(2)向上计数(加),向下计数(减),向上/向下(先加后减)

(3)输入捕获。

(4)输出比较。

(5)PWM生成。

(6)支持针对定位的增量(正交)编码器和霍尔传感器电路。

在这里插入图片描述

1.1 可选的时钟源

  • 内部时钟模式,一般是72MHz,与基本定时器一致,默认时钟源就是内部时钟。

  • 外部时钟源模式1
    使用定时器自身通道的输入信号作为时钟源,每个定时器有4个输入通道。只有通道1和通道2的信号可以作为时钟信号源,通道1和通道2的信号经过输入滤波和边缘检测器成为了时钟源。

  • 外部时钟源模式2

    使用定时器的特殊引脚ETR引脚的信号作为时钟源,每一个通用定时器都有一个ETR引脚。ETR引脚信号经过极性选择,边缘检测,预分频器,输入滤波,得到信号ETRF,ETRF就成为外部时钟源。

外部时钟源一般用于定时器级联,不配置时钟源的情况下,默认选择的就是内部时钟源。

1.2 计数器的3种计数模式

向上计数模式

从0开始加,一直加到自动重装载寄存器的值

然后再来一个时钟信号,计数器溢出,产生更新事件,重新从0计数

向下计数模式

从自动重装载寄存器的值开始计数,直到减到0

然后再来一个时钟信号,计数器溢出,产生更新事件,重新从自动重装载寄存器的值计数

中央对齐模式(向上和向下计数)

从0开始向上计数,一直计数到自动重装载寄存器的值-1

再来一个时钟信号会产生更新事件,然后继续从自动重装载寄存器的值向下计数

向下计数到1,再来一个时钟信号会产生更新事件,然后继续从0开始向上计数

默认计数方向就是向上计数

2. 实验:LED呼吸灯(PWM脉冲)

输出占空比可调的PWM波形,作用到二极管,使二极管(LED)呈现呼吸灯的效果

在这里插入图片描述

PB1复用的是TIM3_CH4和TIM8_ CH3N,我们选择TIM3_CH4

2.1 PWM(脉冲宽度调制)

利用微处理器的数字输出来对模拟电路进行控制,PWM常用于控制电机、LED亮度调节等应用

实际使用中,生成PWM波形就是生成一个方波信号

周期:连续的两个上升沿或连续两个下降沿之间的宽度。

占空比:高电平宽度t除以周期T

使用PWM驱动惯性电器时,一般不改变频率和周期。通过改变占空比达到对外输出的有效电压的值。

2.2 输出比较部分

计数器部分

​ 捕获比较寄存器:每个定时器有4个,可以同时实现4路比较

​ 输出部分:4路输出,分别对应4个引脚
在这里插入图片描述

输出比较原理

通过比较定时计数器的值 CNT 与设定的比较值 CCR,可以控制输出引脚的电平状态(置高或置低),从而实现生成一定频率和占空比的 PWM 波形。

以通道1为例说明:假设计数器向上计数,重装载寄存器的值为99,假设捕获/比较寄存器的值设置为60。

比较寄存器的值和计数器的值进行大小比较,根据比较结果不同,产生不同输出:高电平或低电平。

在这里插入图片描述

2.3 寄存器方式实现

#include "tim3.h"void TIM3_Init(void)
{// 1. 开启时钟// 1.1 定时器3时钟RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;// 1.2 GPIO的时钟 PB1RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// 2. 设置GPIO为复用推挽输出 CNF=10 MODE=11GPIOB->CRL &= ~GPIO_CRL_CNF1_0;GPIOB->CRL |= GPIO_CRL_CNF1_1;GPIOB->CRL |= GPIO_CRL_MODE1;// 3. 定时器配置// 3.1 预分频器配置TIM3->PSC = 7200 - 1;// 3.2 自动重装载寄存器TIM3->ARR = 100 - 1;// 3.3 计数器计数方向 0-向上TIM3->CR1 &= ~TIM_CR1_DIR;// 3.4 配置通道4的捕获比较寄存器TIM3->CCR4 = 50;// 3.5 通道4配置为输出 00TIM3->CCMR2 &= ~TIM_CCMR2_CC4S;// 3.6 通道的输出比较模式 110TIM3->CCMR2 |= TIM_CCMR2_OC4M_2;TIM3->CCMR2 |= TIM_CCMR2_OC4M_1;TIM3->CCMR2 &= ~TIM_CCMR2_OC4M_0;// 3.7 通道极性 0高电平有效 1低电平有效TIM3->CCER &= ~TIM_CCER_CC4P;// 3.8 使能通道4TIM3->CCER |= TIM_CCER_CC4E;
}// 使能计数器
void Tim3_Start(void)
{TIM3->CR1 |= TIM_CR1_CEN;
}// 失能计数器
void Tim3_Stop(void)
{TIM3->CR1 &= ~TIM_CR1_CEN;
}// 设置工作周期
void Tim3_setDutyCycle(uint8_t cycle)
{TIM3->CCR4 = cycle;
}
#include "delay.h"
#include "tim3.h"int main(void)
{TIM3_Init();Tim3_Start();uint8_t duty_cycle = 0;uint8_t dir = 0;Tim3_setDutyCycle(duty_cycle);while (1){if (dir == 0){duty_cycle += 1;if (duty_cycle >= 99){dir = 1;}}else{duty_cycle -= 1;if (duty_cycle <= 1){dir = 0;}}Tim3_setDutyCycle(duty_cycle);Delay_ms(10);}
}

2.4 hal库方式实现

在这里插入图片描述

在tim.c中添加设置捕获比较寄存器的函数

void setDutyCycle(uint8_t dutyCycle)
{// 设置捕获比较寄存器的值__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_4, dutyCycle);
}

主函数main.c

int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_TIM3_Init();/* USER CODE BEGIN 2 */// 启动定时器的PWM模式输出HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);uint8_t dutycycle = 1;uint8_t step = 1;/* USER CODE END 2 */while (1){if (dutycycle <= 0 || dutycycle >= 99){step = -step;}dutycycle = dutycycle + step;setDutyCycle(dutycycle);HAL_Delay(10);}
}

3. 实验:测量PWM的频率/周期

使用输入捕获功能,来测量PWM的频率/周期。

如果要测量PWM的周期/频率,只要测量出连续的两个上升沿或连续的两个下降沿的时间间隔。

TIM3的CH4输出PWM,使用TIM4的CH1来捕获PWM信号

在这里插入图片描述

在这里插入图片描述

在这里需要把PB1连接到PB6

3.1 输入捕获功能

捕获输入通道上信号的上升沿或下降沿

输入捕获部分

包含3部分

  • 输入部分:

​ 共4路输入信号,每路都有自己的输入引脚,4路输入引脚和输出比较引脚是一致的。对于同一路引脚,只能处于输入捕获或输出比较。

  • 计数器部分
  • 捕获寄存器部分:共有4路,与比较寄存器共用

在这里插入图片描述

以通道1为例:

假设计数器向上计数,重装载寄存器值为65535,尽量避免计数器溢出

  • 信号经过通道1的引脚进入通道1,得到TI1

  • TI1信号进入滤波器和边沿检测器,其中滤波器用来滤掉一些毛刺信号,边沿检测器确定要捕获的是上升沿还是下降沿。

  • 从边沿检测器出来的上升沿或下降沿信号为TI1FP1

  • TI1FP1经过信号选择器得到IC1

  • IC1进入预分频器,可以对信号选择分频或不分频。如果信号的频率很高,可以分频。

  • 信号从预分频器出来,信号为IC1PS

    会产生一个捕获比较事件

    如果开启了中断也会产生捕获比较中断

    立即把计数器寄存器的值存入到捕获寄存器。在下次捕获事件产生之前,捕获寄存器的值不会产生变化。

测量PWM周期原理

假设计数器时钟72分频,则计数器时钟频率为1MHz,计数器累加一次时间为1us。

设置重装载寄存器值为65535,设置值为最大,尽量避免溢出。

假设测量的信号周期小于65535us

  • 在一个周期内,计数器不会溢出
  • 当第1个上升沿到来时,重置计数器的值(让计数器从0开始计数)
  • 当第2个上升沿到来时,计数器的值会自动复制到捕获寄存器,此时寄存器的值就是信号周期,单位us。

3.2 寄存器方式实现

使用TIM3的CH4输入PWM,参照LED呼吸灯案例

添加tim4.c、tim4.h文件,来捕获PWM信号

#include "tim4.h"void TIM4_Init(void)
{// 1. 开启时钟// 1.1 定时器4时钟RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;// 1.2 GPIO的时钟 PB6RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// 2. 设置GPIO为浮空输入 CNF=01 MODE=00GPIOB->CRL |= GPIO_CRL_CNF6_0;GPIOB->CRL &= ~GPIO_CRL_CNF6_1;GPIOB->CRL &= ~GPIO_CRL_MODE6;// 3. 定时器时基配置// 3.1 预分频器配置 分频后计数器时钟为1MHz,对应周期1usTIM4->PSC = 72 - 1;// 3.2 自动重装载寄存器:值设置为最大,尽量避免溢出TIM4->ARR = 65535;// 3.3 计数器计数方向 0-向上TIM4->CR1 &= ~TIM_CR1_DIR;// 4. 输入捕获部分// 4.1 TIM4_CH1引脚连到TI1输入TIM4->CR2 &= ~TIM_CR2_TI1S;// 4.2 输入捕获滤波器:不滤波TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;// 4.3 边沿检测器:上升沿0 下降沿1TIM4->CCER &= ~TIM_CCER_CC1P;// 4.4 通道配置输入,IC1映射在TI1上:01TIM4->CCMR1 |= TIM_CCMR1_CC1S_0;TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_1;// 4.5 输入捕获分频器TIM4->CCMR1 &= ~TIM_CCMR1_IC1PSC;// 4.6 使能捕获输入TIM4->CCER |= TIM_CCER_CC1E;// 4.7 使能捕获中断TIM4->DIER |= TIM_DIER_CC1IE;// 5. NVIC配置NVIC_SetPriorityGrouping(4);NVIC_SetPriority(TIM4_IRQn, 1);NVIC_EnableIRQ(TIM4_IRQn);
}// 使能计数器
void TIM4_Start(void)
{TIM4->CR1 |= TIM_CR1_CEN;
}// 失能计数器
void TIM4_Stop(void)
{TIM4->CR1 &= ~TIM_CR1_CEN;
}// 上升沿个数
uint8_t raiseEdgeCount = 0;
uint16_t pwm_cycle = 0;void TIM4_IRQHandler(void)
{// 判断是否是TIM4的通道1发生捕获中断if (TIM4->SR & TIM_SR_CC1IF){// 中断标志位清0TIM4->SR &= ~TIM_SR_CC1IF;raiseEdgeCount++;// 第一个上升沿到来,清零计数器if (raiseEdgeCount == 1){TIM4->CNT = 0;}// 第二个上升沿到来,读取捕获寄存器的值else if (raiseEdgeCount == 2){raiseEdgeCount = 0;pwm_cycle = TIM4->CCR1;}}
}// 返回PWM周期 ms
float TIM4_GetPWMCycle(void)
{return pwm_cycle / 1000.0;
}// 返回PWM频率 Hz
float TIM4_GetPWMFreq(void)
{return 1000000.0 / pwm_cycle;
}

主函数main.c如下:

#include "led.h"
#include "delay.h"
#include "usart.h"
#include "tim3.h"
#include "tim4.h"int main(void)
{USART1_init();printf("hello world!\r\n");TIM3_Init();TIM4_Init();Tim3_Start();TIM4_Start();float t, f;while (1){t = TIM4_GetPWMCycle();f = TIM4_GetPWMFreq();printf("t=%.4fms,f=%.4fHz\r\n", t, f);Delay_ms(1000);}
}

3.3 hal库实现

TIM4配置如下

在这里插入图片描述

开启中断

在这里插入图片描述

注意这里需要将引脚改为PB6,因为默认情况下输入引脚会重映射到PD12

在这里插入图片描述

TIM3(输出PWM)的配置中需要将占空比设置成一个数,默认情况下为0(PWM呼吸灯案例中采用了默认配置),显然无法形成PWM方波。

在这里插入图片描述

在tim.c文件中添加下面函数

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM4){// 将计数器清零__HAL_TIM_SetCounter(htim, 0);}
}float TIM4_GetPWMCycle(void)
{return __HAL_TIM_GetCompare(&htim4, TIM_CHANNEL_1) / 1000.0;
}float TIM4_GetPWMFreq(void)
{return 1000000.0 / __HAL_TIM_GetCompare(&htim4, TIM_CHANNEL_1);
}

需要注意因为使用了串口打印,所以需要开启串口并在代码中重写fputc

main.c中启动定时器

int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_TIM3_Init();MX_TIM4_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */// 启动定时器3的PWM模式输出HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);// 启动定时器4用于输入捕获HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_1);float t, f;/* USER CODE END 2 */while (1){/* USER CODE BEGIN 3 */t = TIM4_GetPWMCycle();f = TIM4_GetPWMFreq();printf("t=%.4fms,f=%.4fHz\r\n", t, f);HAL_Delay(1000);}/* USER CODE END 3 */
}

4. 实验:同时测量PWM的频率/周期和占空比

用一个定时器的2个通道同时测量频率和占空比
PWM占空比,只要测量出连续的一个上升沿和下降沿的时间间隔,然后除以周期。

因为测试频率需要连续的两个上升沿,而测试占空比就需要连续的一个上升沿和一个下降沿,所以需要在这个通道即要检测上升沿,也要检测下降沿。
这就需要使用定时器的从模式和PWM输入模式。

4.1 触发输入和从模式

在这里插入图片描述

定时器的触发信号分为两大类:

  • 触发输入信号(TRGI)
    从外部过来(也可能是自己输入通道过来)到本定时器的信号。用来控制本定时器一些动作,比如复位,使能。
    这个时候本定时器就处于主从模式中的从模式

  • 触发输出信号(TRGO)
    本定时器输出到其他定时器或其他外设的信号
    用于与其他定时器的级联(触发其他定时器的一些工作)或者触发一些其他外设工作。
    这个时候本定时器就处于主从模式中的主模式

4.1.1 触发输入信号

从模式控制寄存器的TS位用于配置触发选择

在这里插入图片描述

触发输入信号可分为以下几类

  • 第一类

TS[2:0]=000-011,4个

来源于其他定时器的TRGO信号。经过芯片内部连接,来到本定时器的ITR0/1/2/3。

内部连接是定死的,不能更改,连接情况如下

在这里插入图片描述

比如TIM1的TRGO连接到了TIM2(从定时器)的ITR0,ITRx中某个信号经过信号选择器最终成为TRGI信号

  • 第二类

TS[2:0]=111 1个

来源于外部触发引脚ETR,经过极性选择,边沿检测和预分频器,输入滤波,成为TRGI信号。

TRGI信号通过从模式控制器控制本定时器实现复位或使能。

  • 第三类

TS[2:0]=100 1个

来源于定时器自身的通道1信号。经过输入滤波器和边沿检测器,得到TI1F_ED信号。

上升沿和下降沿都会产生TI1F_ED信号(没有经过极性选择),经过信号选择器最终成为TRGI信号。

  • 第四类

TS[2:0]=101/110 2个

来源定时器自身的通道1信号或通道2信号。经过输入滤波器和边沿检测器,得到TI1FP1和TI2FP2信号,注意他们是上升沿或者下降沿,只能选择一种,最终成为TRGI信号。

在本次实验中就会使用第四类触发输入信号来完成。

4.1.2 定时器从模式

这些TRGI信号要控制定时器,需要把定时器配置为从模式

从模式控制寄存器的SMS位用于配置从模式工作模式

在这里插入图片描述

4.2 PWM输入模式

该模式是输入捕获模式的一个特例,操作与输入捕获模式相同。

以信号从通道1输入为例。

经过输入滤波器和边沿检测器得到2路信号:TI1FP1和TI1FP2。

TI1FP1和TI1FP2极性相反,一个得到输入的上升沿(TI1FP1),一个得到输入的下降沿(TI1FP2)。

TI1FP1得到IC1信号,在通道1,用来测量周期

TI1FP2得到IC2信号,在通道2,用来测量高电平时间

TI1FP1作为触发输入信号,开启从模式中复位模式

在这里插入图片描述

4.3 寄存器实现

修改tim4.c文件如下,tim3同样用于产生PWM,参考实验LED呼吸灯(PWM脉冲)

#include "tim4.h"void TIM4_Init(void)
{// 1. 开启时钟// 1.1 定时器4时钟RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;// 1.2 GPIO的时钟 PB6RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;// 2. 设置GPIO为浮空输入 CNF=01 MODE=00GPIOB->CRL |= GPIO_CRL_CNF6_0;GPIOB->CRL &= ~GPIO_CRL_CNF6_1;GPIOB->CRL &= ~GPIO_CRL_MODE6;// 3. 定时器时基配置// 3.1 预分频器配置 分频后计数器时钟为1MHz,对应周期1usTIM4->PSC = 72 - 1;// 3.2 自动重装载寄存器:值设置为最大,尽量避免溢出TIM4->ARR = 65535;// 3.3 计数器计数方向 0-向上TIM4->CR1 &= ~TIM_CR1_DIR;// 4. 输入捕获部分// 4.1 TIM4_CH1引脚连到TI1输入TIM4->CR2 &= ~TIM_CR2_TI1S;// 4.2 输入捕获滤波器:不滤波TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;// 4.3 边沿检测器:上升沿0 下降沿1TIM4->CCER &= ~TIM_CCER_CC1P; // IC1TIM4->CCER |= TIM_CCER_CC2P;  // IC2// 4.4 通道配置输入// IC1映射在TI1上:01TIM4->CCMR1 |= TIM_CCMR1_CC1S_0;TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_1;// IC2映射到TI1上:10TIM4->CCMR1 &= ~TIM_CCMR1_CC2S_0;TIM4->CCMR1 |= TIM_CCMR1_CC2S_1;// 4.5 输入捕获分频器 IC1 IC2都不分频TIM4->CCMR1 &= ~TIM_CCMR1_IC1PSC;TIM4->CCMR1 &= ~TIM_CCMR1_IC2PSC;// 4.6 配置TRGI信号:TI1FP1 SMCR TS=101TIM4->SMCR |= TIM_SMCR_TS_0;TIM4->SMCR &= ~TIM_SMCR_TS_1;TIM4->SMCR |= TIM_SMCR_TS_2;// 4.7 配置从模式为复位模式 SMCR SMS=100TIM4->SMCR &= ~TIM_SMCR_SMS_0;TIM4->SMCR &= ~TIM_SMCR_SMS_1;TIM4->SMCR |= TIM_SMCR_SMS_2;// 4.8 使能捕获输入TIM4->CCER |= TIM_CCER_CC1E;TIM4->CCER |= TIM_CCER_CC2E;
}// 使能计数器
void TIM4_Start(void)
{TIM4->CR1 |= TIM_CR1_CEN;
}// 失能计数器
void TIM4_Stop(void)
{TIM4->CR1 &= ~TIM_CR1_CEN;
}// 返回PWM周期 ms
float TIM4_GetPWMCycle(void)
{return TIM4->CCR1 / 1000.0;
}// 返回PWM频率 Hz
float TIM4_GetPWMFreq(void)
{return 1000000.0 / TIM4->CCR1;
}// 返回占空比
float TIM4_GetDutyCycle(void)
{return TIM4->CCR2 * 1.0 / TIM4->CCR1;
}
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "tim3.h"
#include "tim4.h"int main(void)
{USART1_init();printf("hello world!\r\n");TIM3_Init();TIM4_Init();Tim3_Start();TIM4_Start();while (1){printf("t=%.4fms,f=%.4fHz,duty=%.2f%%\r\n", TIM4_GetPWMCycle(), TIM4_GetPWMFreq(), TIM4_GetDutyCycle() * 100);Delay_ms(1000);}
}

4.4 hal库实现

在这里插入图片描述

tim.c添加代码

float TIM4_GetPWMCycle(void)
{return (__HAL_TIM_GetCompare(&htim4, TIM_CHANNEL_1)) / 1000.0;
}float TIM4_GetPWMFreq(void)
{return 1000000.0 / (__HAL_TIM_GetCompare(&htim4, TIM_CHANNEL_1));
}float TIM4_GetDutyCycle(void)
{return (__HAL_TIM_GetCompare(&htim4, TIM_CHANNEL_2)) * 1.0 / (__HAL_TIM_GetCompare(&htim4, TIM_CHANNEL_1));
}
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_TIM3_Init();MX_TIM4_Init();MX_USART1_UART_Init();HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);HAL_TIM_IC_Start(&htim4, TIM_CHANNEL_1);HAL_TIM_IC_Start(&htim4, TIM_CHANNEL_2);printf("hello\r\n");while (1){/* USER CODE BEGIN 3 */printf("t=%.4fms,f=%.4fHz,duty=%.2f%%\r\n",TIM4_GetPWMCycle(), TIM4_GetPWMFreq(),TIM4_GetDutyCycle() * 100);HAL_Delay(1000);}/* USER CODE END 3 */
}

四、高级定时器

1. 高级定时器介绍

高级定时器有2个分别是:TIM1、TIM8。

高级定时器除了拥有通用定时器的所有功能外,还具有以下功能:

  • 死区时间可编程的互补输出。
  • 断路输入信号(刹车输入信号)。
  • 重复计数器。

在这里插入图片描述

重复计数器

在基本定时器和通用定时器中,计数器每溢出1次,就产生1次更新事件。在高级定时器中,计数器每溢出1次,会产生一个信号,让重复计数器的值-1。当重复计数器的值减到0,如果计数器再溢出1次,就会产生更新事件。

重复计数器的初始化来源于RCR寄存器REP位。

如果REP=2,则CNT计数器溢出3次产生一次更新事件。可以用重复计数器生成有限个周期的PWM

互补输出

高级定时器的通道1/2/3可以分别输出2路互补信号:CH1和CH1N(通道4没有)

互补信号:频率周期相等,相位相差180°

互补输出一般用于驱动H桥电路,H桥通常用于驱动电流较大的负载,比如电机

2. 实验:输出有限个周期的PWM波

输出5个周期的PWM波,频率2Hz,LED闪烁5次。

需求实现思路:使用高级定时器的重复计数器,当计数器溢出时,在溢出中断中停止定时器工作。重复计数器寄存器的值设置为4,即可输出5个周期的PWM波,LED会闪烁5次。

在这里插入图片描述

高级定时器TIM8_CH1的端口为PC6,将PC6与LED端口连接起来。

#include "tim8.h"
#include "stdio.h"void TIM8_Init(void)
{// 1. 开启时钟// 1.1 定时器8时钟RCC->APB2ENR |= RCC_APB2ENR_TIM8EN;// 1.2 GPIO的时钟 PC6RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;// 2. 设置PC6为复用推挽输出 CNF=10 MODE=11GPIOC->CRL &= ~GPIO_CRL_CNF6_0;GPIOC->CRL |= GPIO_CRL_CNF6_1;GPIOC->CRL |= GPIO_CRL_MODE6;// 3. 定时器时基配置// 3.1 预分频器配置TIM8->PSC = 7200 - 1;// 3.2 自动重装载寄存器TIM8->ARR = 5000 - 1;// 3.3 计数器计数方向 0-向上TIM8->CR1 &= ~TIM_CR1_DIR;// 3.4 重复计数器配置 计数器溢出5次才会产生更新中断TIM8->RCR = 4;// 4. 输出部分// 4.1 通道1配置为输出TIM8->CCMR1 &= ~TIM_CCMR1_CC1S;// 4.2 输出比较模式 PWM模式1  110TIM8->CCMR1 &= ~TIM_CCMR1_OC1M_0;TIM8->CCMR1 |= TIM_CCMR1_OC1M_1;TIM8->CCMR1 |= TIM_CCMR1_OC1M_2;// 4.3 配置捕获比较寄存器的值TIM8->CCR1 = 2500;// 4.4 输出极性TIM8->CCER &= ~TIM_CCER_CC1P;// 4.5 使能通道TIM8->CCER |= TIM_CCER_CC1E;// 4.6 主输出使能(高级定时器需要)TIM8->BDTR |= TIM_BDTR_MOE;// 4.7 产生更新事件,从而将预分频和重复计数器等值更新到影子寄存器// 只产生更新事件,不产生中断TIM8->EGR |= TIM_EGR_UG;TIM8->SR &= ~TIM_SR_UIF; // 清除中断标志位// 5. 配置中断// 5.1 定时器更新中断使能TIM8->DIER |= TIM_DIER_UIE;// 5.2 NVIC配置NVIC_SetPriorityGrouping(4);NVIC_SetPriority(TIM8_UP_IRQn, 1);NVIC_EnableIRQ(TIM8_UP_IRQn);
}// 使能计数器
void TIM8_Start(void)
{TIM8->CR1 |= TIM_CR1_CEN;
}// 失能计数器
void TIM8_Stop(void)
{TIM8->CR1 &= ~TIM_CR1_CEN;
}// 在中断中停掉计数器
void TIM8_UP_IRQHandler(void)
{printf("interrupt...\r\n");TIM8->SR &= ~TIM_SR_UIF;TIM8_Stop();
}
    // 4.7 产生更新事件,从而将预分频和重复计数器等值更新到影子寄存器// 只产生更新事件,不产生中断TIM8->EGR |= TIM_EGR_UG;TIM8->SR &= ~TIM_SR_UIF; // 清除中断标志位

必须进行上面配置,否则观察不到现象。因为预分频和重复计数器等值是先存入预装载寄存器,只有当发生一个更新事件的时候,预装载寄存器才能被传送到影子寄存器。

单片机开始上电,预分频器还是1分频,为72MHZ,频率太高,而此时重复计数器的值也未进行刷新,会导致一上电进入中断,而在中断中关闭了计数器,自然就观察不到实验现象。

所以在计数器开始计数之前,必须通过设置TIMx_EGR寄存器中的UG位来初始化所有的寄存器。而此时我们不希望设置UG位后产生中断,打印多余的数据,可以在产生更新事件后,清除中断标志位。

在这里插入图片描述

开启更新中断

在这里插入图片描述

进入定时器初始化函数,追踪到stm32f1xx_hal_tim.c文件下TIM_Base_SetConfig函数

  /* Generate an update event to reload the Prescalerand the repetition counter (only for advanced timer) value immediately */TIMx->EGR = TIM_EGR_UG;

可知在初始化阶段就已经帮我们产生更新事件,将相关寄存器的值刷新到影子寄存器。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim->Instance == TIM8){printf("interrupt...\r\n");HAL_TIM_PWM_Stop(&htim8, TIM_CHANNEL_1);}
}
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();MX_TIM8_Init();/* USER CODE BEGIN 2 */printf("hello\r\n");// 启用更新中断__HAL_TIM_ENABLE_IT(&htim8, TIM_IT_UPDATE);HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}
http://www.dtcms.com/a/404316.html

相关文章:

  • 教学资源库网站建设立项申报书网络维护员岗位职责
  • 第10节-CTE公用表表达式-Recursive-CTE
  • 广东省备案网站建设方案书iis发布网站的教程
  • git基础操作
  • 茂名专业网站建设外贸出口流程12步骤图
  • 响应式网站 模版58同城企业网站怎么做的
  • ARM——中断(按键)
  • 【conda配环境】导出环境配置与安装
  • 上饶市建设厅网站快速达建网站
  • 深圳做h5网站公司wordpress 入口
  • 网站建设一般多钱高职院校优质校建设专栏网站
  • 网站建设属于哪种公司科技公司logo
  • 个人网站备案入口注册网站建设公司
  • 网站建设要求报告网上做网站网站代理赚钱吗
  • 网站的角色设置如何做北京建站免费模板
  • 代理分佣后台网站开发安徽住房和城乡建设厅官网
  • 微网站用什么做天津工程建设信息网站
  • 网站制造做网站流量
  • 南昌网站开发培训学校全媒体运营师报考条件
  • 站长工具下载app智能云建站
  • 做网站应该会什么问题建设一个网站需要哪些方面的开支
  • 宁德住房和城乡建设部网站东莞创建网站
  • 网站做微信链接怎么做的建设网站的知识竞赛
  • 上海好的高端网站建设服务公司网站建设一站式
  • 网站百度秒收自助建站管理平台
  • 网站建设的机构2022年企业所得税政策
  • 手机网站建设维护网络叶子 网站推广
  • 公司网站建设的方案网站建设网站
  • 北京通州个人网站建设桐柏网站
  • 西安房产网站大全wordpress手机版中文