定时器&PWM应用编程

本文详细介绍了如何使用STM32的定时器和PWM功能控制LED灯的亮灭以及呼吸灯效果,包括定时器配置、PWM生成、LED脉宽调制控制,以及超声波测距模块的使用。还涉及了如何通过串口通信展示测量的PWM信号频率和占空比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、实验任务

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

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

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

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


四. 学习 HC-SR04超声波测距模块工作原理,使用 stm32F103 完成一个超声波测距方案(第9周之前选做)。

二、定时器&PWM应用编程 

1.定时器介绍

定时器(Timer)是一种能够在特定时间间隔后自动重置的计时装置。在微处理器和微控制器中,定时器通常是一个内置的硬件模块,用于生成毫秒级别的精确时间间隔。定时器可以用于多种应用,包括脉冲计数、时间戳生成、实时操作系统中的任务调度等。

而STM32中的定时器是一个重要的外设,它总共包含8个定时器,分别是2个高级定时器(TIM1、TIM8),4个通用定时器(TIM2、TIM3、TIM4、TIM5)和2个基本定时器(TIM5、TIM6)。不同种类的定时器具有不同的特性和功能。

高级定时器TIM1和TIM8具有捕获/比较通道和互补输出,通用定时器只有捕获/比较通道,基本定时器没有以上两者。通用定时器位于低速的APB1总线上,具有16位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)。此外,通用定时器具有4个独立通道(TIMx_CH1~4),这些通道可以用来作为输入捕获、输出比较、PWM生成(边缘或中间对齐模式)、单脉冲模式输出。每个通用定时器都是完全独立的,没有互相共享的任何资源。

2.PWM介绍

PWM(Pulse Width Modulation)即脉冲宽度调制,是一种通过控制脉冲宽度来控制电压或电流的技术。PWM的基本原理是,通过一个周期性的脉冲信号,其宽度(即高电平持续时间)可以用来控制平均输出电压的大小。例如,如果一个脉冲信号的周期为T,高电平持续时间为t,则该脉冲的占空比(即高电平时间与周期的比值)就是t/T。如果占空比越大,输出的平均电压就越高。

三、实验过程

1.用定时器计数方式,控制LED以2s的频率周期性地亮-灭

1.1使用固件库完成实验项目环境搭建

1.2HARD代码

LED.h

函数声明

#ifndef __LED_H
#define __LED_H

void LED_Init(void); 
void LED_ON(void); 
void LED_OFF(void); 
void LED_Turn(void);

#endif

LED.c

函数实现

#include "stm32f10x.h"                  

void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	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);
	}
}

Timer.h

初始化函数

#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif

Time.c

实现TIM2定时器的初始化功能,完成时钟源、计数模式、自动重载值、中断的设置。

中断触发频率 = TIM2的时钟频率 / (TIM_Prescaler + 1) / (TIM_Period + 1)可知

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

在TIM2中完成2s的周期设置

#include "stm32f10x.h"                  

void Timer_Init(void)
{
	  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	  NVIC_InitTypeDef NVIC_InitStructure;
  
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    
    TIM_InternalClockConfig(TIM2);

   
    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); 

   
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
   
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

   
    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);
}

1.3main.c代码 

完成函数初始化,并在定时器中断函数中实现LED的亮灭过程

#include "stm32f10x.h"                 
#include "Delay.h"
#include "Timer.h"
#include "LED.h"

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

void TIM2_IRQHandler(void)
{
	
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		LED_Turn(); 
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

1.4实验结果

2s周期LED灯​​​​

2.PWM呼吸灯 

2.1原理分析

      在模拟电路中呼吸灯一个连续的正弦电压的控制,实现了LED灯从暗到亮逐渐变化。而通过pwm,调节占空比来对信号电平进行数字编码,将模拟量转为数字量,实现呼吸灯。

2.2FWM(脉冲宽度调制)输出模式设置

FWM.h

函数声明

#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void); 
#endif

FWM.c

函数实现

#include "stm32f10x.h"                  
void PWM_Init(void)
{
    
	  GPIO_InitTypeDef GPIO_InitStructure;
	  TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	  TIM_OCInitTypeDef TIM_OCInitStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
   
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

  
 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
    GPIO_Init(GPIOA, &GPIO_InitStructure); 

    
    TIM_InternalClockConfig(TIM2);

    
   
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; 
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; 
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; 
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; 
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); 

    
    TIM_OCStructInit(&TIM_OCInitStructure); 

   
    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;
    TIM_OC1Init(TIM2, &TIM_OCInitStructure); 

    
    TIM_Cmd(TIM2, ENABLE);
}

 main.c

实现占空比的逐渐增加和减少,完成呼吸灯的亮灭变化

#include "PWM.h"
uint8_t i;
int main(void)
{
    PWM_Init(); 

    while (1)
    {
        for (i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(i); 
            Delay_ms(10);       
        }
        for (i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(100 - i); 
            Delay_ms(10);            
        }
    }
}

 2.3实验结果

呼吸灯

2.4使用keil虚拟示波器观察

波形观察

       由波形的变化可知占空比不断变化。通过占空比的变化控制了电压的变化,实现了呼吸灯的功能

3.测量PWM输出信号的频率和占空比

3.1采集PVM输出信号,并输出到串口显示

PSC(预分频器)和ARR(自动重装载寄存器)控制PWM输出频率的。

PSC决定了计数器的时钟频率,而ARR决定了计数器的溢出值。

占空比是指PWM信号中高电平(或低电平)所占的时间比例。

(1)通过函数设置PSC和占空比
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}

void PWM_SetPrescaler(uint16_t Prescaler)
{
	TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);
}
(2)采集频率和占空比
#include "stm32f10x.h"                 

void IC_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // 将PWM的输出信号输入到PA6,来实现频率和占空比的采集
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	TIM_InternalClockConfig(TIM3);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;		//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;		//PSC
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
	
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICFilter = 0xF;
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);

	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	TIM_Cmd(TIM3, ENABLE);
}

uint32_t IC_GetFreq(void)
{
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

uint32_t IC_GetDuty(void)
{
	return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}
(3)实现串口通信,将采集信息发送给上位机
#include "stm32f10x.h"                  
#include <stdio.h>
#include <stdarg.h>

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)
	{
		Serial_SendByte(String[i]);
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y --)
	{
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}
(4)通过定时器TIM4实现串口采集 
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "PWM.h"
#include "IC.h"
#include "Serial.h"
#include "Timer.h"

uint16_t i;
uint32_t freq, duty;

int main(void)
{
	PWM_Init();
	IC_Init();
	Serial_Init();
	Timer_Init();
	
	PWM_SetPrescaler(7200 - 1);			//Freq = 72M / (PSC + 1) / 100
	
	while (1)
	{		
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(i);
			Delay_ms(10);
		}
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(100 - i);
			Delay_ms(10);
		}
	}
}

void TIM4_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)
	{
		freq = IC_GetFreq(); 
		duty = IC_GetDuty(); 
		Serial_Printf("频率: %d Hz  ", freq); // 给上位机发送频率
		Serial_Printf("占空比: %d %\r\n", duty); // 给上位机发送占空比
		TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
	}
}

3.2串口调试

调节波特率为9600,将PA0和PA6连接,实现串口信息传递。

 总结 

1.定时器可以用来生成精确的时间间隔,而PWM则可以在这个时间间隔内改变输出电压。

2.根据实验需求选择合适的定时器和PVM模式。

3.学会通过中断函数来减少定时器和PWM函数的反复调用,提高运行效率。

4.通过虚拟示波器观察波形,有助于加深对实验的理解和体会

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值