定时器&PWM应用编程

深入了解STM32定时器原理,掌握脉宽调制pwm生成方法。

一. 使用STM32F103的 Tim2~Tim5其一定时器的某一个通道pin(与GPIOx管脚复用,见下图),连接一个LED,用定时器计数方式,控制LED以2s的频率周期性地亮-灭。

二. 接上,采用定时器pwm模式,让 LED 以呼吸灯方式渐亮渐灭,周期为1~2秒,自己调整到一个满意效果。使用Keil虚拟示波器,观察 pwm输出波形。

三. 再接上,采用定时器的另外一个通道,编程采集上面的pwm输出信号,获得其周期和脉宽,并重定向输出到串口显示。

STM32定时器原理
STMicroelectronics的STM32微控制器系列具有强大的定时器功能,这些定时器可以用于各种应用,如生成精确的时间延迟、PWM输出、捕获外部信号、测量脉冲宽度等。在STM32中,有多个不同类型的定时器,包括通用定时器(TIM)和高级定时器(TIM)。
以下是STM32定时器的基本原理和使用方法:

1.TIM类型:

2.通用定时器(TIM):通用定时器是多用途的定时器,可用于生成脉冲、延时、PWM等功能。
3.高级定时器(TIM):高级定时器是更高级的定时器,具有更多功能和精确性,适用于复杂的定时需求。
4.TIM的基本组成:
5.计数器:TIM内部包含一个16位或32位的计数器,用于计算时间。
6.预分频器:TIM的输入时钟可以经过预分频,以降低计数器的计数速度,从而延长计时周期。
7.自动装载寄存器:自动装载寄存器用于设置定时器的周期。一旦计数器达到自动装载值,计数器会重新从零开始计数。
8.比较寄存器:比较寄存器用于与计数器的值进行比较,以触发中断或执行某些操作。

9.工作原理:
10.初始化:首先,您需要初始化TIM,选择时钟源、设置预分频器、自动装载值等参数。
11.启动:启动TIM后,计数器开始计数。
12.计数和比较:计数器逐渐增加,当计数器的值与比较寄存器的值相等时,可以触发中断或执行其他操作。
13.自动装载:一旦计数器达到自动装载值,计数器会重新从零开始,形成一个周期。

14.应用:

15.延时生成:通过设置自动装载值和比较寄存器的值,可以生成精确的延时。
16.PWM生成:TIM可用于生成PWM信号,通过设置自动装载值和比较寄存器的值,可以控制PWM的占空比。
17.脉冲捕获:TIM可以捕获外部信号的脉冲宽度或频率。
18.定时中断:可以配置TIM以在计数器达到比较值时触发中断,执行特定的任务。

在STM32中,您可以使用HAL库或LL库等不同的软件库来配置和操作定时器。通常,您需要编写初始化函数来配置TIM,设置中断处理函数来处理计时事件,以及根据需要启动或停止TIM。
总之,STM32定时器是强大的工具,可用于各种定时和计时应用,通过适当的配置和操作,您可以满足各种应用需求。

掌握脉宽调制pwm
1定义
  PWM(Pulse Width Modulation)即脉冲宽度调制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术;它是一种模拟控制方式,根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置,来实现晶体管或MOS管导通时间的改变,从而实现开关稳压电源输出的改变。
基本原理

2.基本原理
  PWM就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也可以这样理解,PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。
  该信号在预定义的时间和速度中设置为高(5v或3.3v)和低(0v)。通常,我们将PWM的高电平称为1,低电平为0。

3.优点及应用范围
  由于其控制简单、灵活和动态响应好等优点而成为电力电子技术应用最广泛的控制方式,其应用领域包括测量,通信, 功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习PWM具有十分重要的现实意义。
定时器控制LED亮灭
打开Keil,创建好工程后,在Hardware文件夹中添加两个文件:LED.h、LED.c,用来编写LED相关代码。

LED.h中,进行相关函数的声明:

#ifndef __LED_H
#define __LED_H

void LED_Init(void); // LED初始化
void LED_ON(void); // 亮
void LED_OFF(void); // 灭
void LED_Turn(void); // 电平反转

#endif

选择PA1作为LED引脚输出。

#include "stm32f10x.h"                  // Device header

void LED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_1);
}

void LED_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}

void LED_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_1);
}

void LED_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_1);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);
	}
}

在System文件夹中添加两个文件:Timer.h、Timer.c

#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif

Time.c中,负责实现定时器初始化函数Timer_Init

#include "stm32f10x.h"                  // Device header

void Timer_Init(void)
{
    // 启用 TIM2 的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    // 配置 TIM2 的时钟源为内部时钟
    TIM_InternalClockConfig(TIM2);

    // 定义并初始化 TIM_TimeBaseInitTypeDef 结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频,不分频
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 定时器模式为向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; // 设置定时器的自动重载值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 14400 - 1; // 设置预分频器,控制计数频率
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器,一般不使用
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); // 将配置应用到 TIM2 定时器

    // 清除 TIM2 的更新标志位
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    // 启用 TIM2 的更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    // 配置 NVIC 中断优先级分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    // 定义并初始化 NVIC_InitTypeDef 结构体,配置 TIM2 的中断
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 指定中断通道为 TIM2
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 启用中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级
    NVIC_Init(&NVIC_InitStructure); // 将配置应用到 NVIC

    // 启用 TIM2 定时器
    TIM_Cmd(TIM2, ENABLE);
}

这段代码的主要作用是初始化 TIM2 定时器,配置其时钟源、计数模式、预分频值、自动重载值、以及中断相关设置

中断触发频率 = 72,000,000 Hz / (14400) / (10000) = 0.5 Hz

因此,这段代码配置了 TIM2 定时器以每2秒触发一次中断。

最后,在main.c中,进行相关的初始化工作。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
#include "LED.h"

int main(void)
{
	LED_Init();
	Timer_Init();
	
	while (1)
	{
	}
}

这里的while循环内不需要进行其他操作,全部交给定时器中断函数去实现。

在main函数后面,添加上定时器中断函数,在中断函数中实现LED的亮灭反转:

void TIM2_IRQHandler(void)
{
	// 检查是否是 TIM2 的更新中断(TIM_IT_Update)触发了中断
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		LED_Turn(); // LED1(PA1)的电平反转
		// 清除 TIM2 的更新中断标志位,以重置中断状态
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

到此已经实现了使用定时器来控制led的亮灭。现在我们根据题目要求还需要实现呼吸灯,实现led灯的渐亮渐灭。

PWM实现呼吸灯
要实现呼吸灯效果需要把定时器TIM2设置成PWM(脉冲宽度调制)输出模式。在Hardware文件夹中,创建两个文件:PWM.h和PWM.c,用来实现PWM相关代码的实现

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void); // PWM初始化
void PWM_SetCompare1(uint16_t Compare); // 改变 PWM 波形的占空比

#endif

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
    // 启用 TIM2 的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    // 启用 GPIOA 的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 配置 GPIOA 的引脚为复用模式,并设置为推挽输出,用于 PWM 输出
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 配置使用的引脚,这里是GPIOA的引脚0
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // GPIO 速度
    GPIO_Init(GPIOA, &GPIO_InitStructure); // 将配置应用到 GPIOA

    // 配置 TIM2 的时钟源为内部时钟
    TIM_InternalClockConfig(TIM2);

    // 定义并初始化 TIM_TimeBaseInitTypeDef 结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频,不分频
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 定时器模式为向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; // 设置定时器的自动重载值(ARR)
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; // 设置预分频器(PSC),控制计数频率
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器,一般不使用
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); // 将配置应用到 TIM2 定时器

    // 定义并初始化 TIM_OCInitTypeDef 结构体
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure); // 初始化结构体

    // 配置 TIM2 的输出比较通道 1(TIM2_CH1)为 PWM 模式
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出脉冲的极性为高
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 启用输出
    TIM_OCInitStructure.TIM_Pulse = 0; // 设置输出脉冲的占空比(CCR)
    TIM_OC1Init(TIM2, &TIM_OCInitStructure); // 将配置应用到 TIM2_CH1

    // 启用 TIM2 定时器
    TIM_Cmd(TIM2, ENABLE);
}

void PWM_SetCompare1(uint16_t Compare)
{
    // 使用 TIM_SetCompare1 函数来设置定时器 TIM2 的输出比较通道 1 的比较值
    TIM_SetCompare1(TIM2, Compare);
}

的主要作用是初始化 TIM2 定时器,配置其为 PWM 输出模式。具体做法包括:
启用 TIM2 和 GPIOA 的时钟,以使这些外设可用。
配置 GPIOA 的引脚为复用模式,用于 PWM 输出,并设置为推挽输出。
配置 TIM2 的时钟源为内部时钟。
初始化 TIM_TimeBaseInitStructure 结构体,设置定时器 TIM2 的计数模式、自动重载值(ARR)、预分频值(PSC)等参数。
初始化 TIM_OCInitStructure 结构体,设置输出比较通道 1(TIM2_CH1)为 PWM 模式,定义输出脉冲的极性和初始占空比。
启用 TIM2 定时器。

在循环中,首先通过 for 循环逐渐增加 PWM 的占空比,从0增加到100,实现LED的逐渐亮起效果。

随后,通过另一个 for 循环逐渐减小 PWM 的占空比,从100递减到0,实现LED的逐渐熄灭效果。

在每个 for 循环内部,使用 PWM_SetCompare1 函数来设置 PWM 的占空比,从而实现LED的亮度控制。
代码如下

#include "PWM.h"

uint8_t i;

int main(void)
{
    OLED_Init(); // 初始化OLED显示屏
    PWM_Init();  // 初始化PWM定时器

    while (1)
    {
        for (i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(i); // 逐渐增加PWM的占空比
            Delay_ms(10);       // 延时10毫秒,控制LED亮度的变化速度
        }
        for (i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(100 - i); // 逐渐减小PWM的占空比,实现呼吸灯效果
            Delay_ms(10);             // 延时10毫秒,控制LED亮度的变化速度
        }
    }
}

仿真
在这里插入图片描述使用Keil的仿真逻辑分析仪,观察PA0的波形如下:

在这里插入图片描述
波形占空比是随着时间在不断变化的就实现了呼吸灯效果

实物展示
在这里插入图片描述
采用定时器的另外一个通道,编程采集上面的pwm输出信号,获得其周期和脉宽
使用HAL库建立工程
打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:
选择的单片机型号以及点击开始工程项目:
配置RCC
点System Cor,选择RCC,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator
配置SYS
选择调试接口,点System Cor,选择SYS。,在右侧弹出的菜单栏中选Serial Wire。

完善keil工程:
完善之后的完整main.c代码如下:

#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
 
uint8_t i = 0;
 
float Duty = 0;
float Frequency = 0;
uint16_t Cap_val1 = 0;
uint16_t Cap_val2 = 0;
 
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM1_Init();
    MX_TIM3_Init();
    MX_USART1_UART_Init();
    MX_TIM2_Init();
    /* USER CODE BEGIN WHILE */
 
    printf("串口通信测试\r\n");
    HAL_TIM_Base_Start_IT(&htim2); // 使能定时器及其更新中断
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 使能定时器及其PWM输出
    HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);       // 使能定时器及其输入捕获
    HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2);       // 使能定时器及其输入捕获
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 10); // 设置一个PWM波形进行测量
 
    while (1)
    {
        // 串口发送 频率 占空比
        printf("Cap_val1 is :%d ,  Cap_val2 is : %d \r\n", Cap_val1, Cap_val2);
        printf("Duty is :%0.2f%% Frequency is : %0.2f ms\r\n", Duty, Frequency);
        HAL_Delay(1000);
    }
}
 
 
// 定时TIM2 定时亮灯的中断函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *tim)
{
    if (tim == &htim2)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    }
}
 
// 定时输入捕获回调函数 计算占空比和频率
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM1)
    {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
        {
            Cap_val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        }
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
        {
            Cap_val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
            Duty = 100 - (float)Cap_val2 / (float)Cap_val1 * 100;
            Frequency = 0.001 * Cap_val1;
        }
    }
}
 

串口显示

在这里插入图片描述

总结
定时器中断则是通过时钟定时计数达到设定值时触发中断,进行中断服务函数的处理任务,定时器中断相比软件延时更加准确,且不占用CPU资源,定时器中断实验中最重要还是要明白定时时间的计算原理和方法。通过在STM32上进行PWM的配置,我对于PWM产生原理和配置方法以及使用领域有了更加深刻的了解和认识,PWM对于我们学习和使用STM32以及其他单片机起着非常重要的作用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值