STM32定时器与PWM对LED灯的控制

一、定时器——Timer

(一)概念

定时器,见名知意,是一种专门负责定时功能的片上外设。它的作用是设定一个时间,时间到后通过中断等方式通知STM32执行某些程序。因此,定时器的本质也可认为是计数器。
注意:定时器并不能直接获得现实世界的实时时间,而是当做一个计数器或者计时器来用。

(二)分类

STM32F103C8T6芯片中的定时器有三种八个。(TIM1到TIM8)
在这里插入图片描述

1.基本定时器:TIM6、TIM7。

2.通用定时器:TIM2、TIM3、TIM4、TIM5。

3.高级定时器:TIM1、TIM8。

其中高级定时器挂在总线APB2上,基本定时器和通用定时器挂在总线APB1上。

(三)功能

1.基本定时器:就是时基单元的功能。
2.通用定时器:在基本定时器的基础上增加了输入捕获功能和输出比较功能。
3.高级定时器:在通用定时器的基础上增加了从模式控制器和高级输出控制功能。

我们实验主要采用通用定时器,但我们要尽量了解高级定时器

(四)结构

STM32中单个高级定时器的内部结构如下图所示
在这里插入图片描述

主要可分为四大模块:时基单元输入捕获输出比较(包括绿色框中的高级功能)和从模式控制器。(在这里只介绍常用的时基单元与输出比较模块)

1.模块一——时基单元

在这里插入图片描述
(1)预分频器
RCC传来的脉冲频率很高,一般为72MHz或者为36MHz,而计数器是16位的,因此而一个计数器最多只能计数2的16次方个信号,即65536个脉冲,所以我们理应对该脉冲信号进行分频,分频之后的频率=原频率/(PSC设置的值+1)

(2)计数器与自动重装寄存器
这二者可以看成是捆绑在一起的,自动重装寄存器相当于是计数器的管理员,当自动重装寄存器的值设为n时,要经过n+1个脉冲,计数器的值才会向外输出1个脉冲。即计数器向外输出的脉冲个数=传入寄存器内部的脉冲个数/(ARR设置的值+1)

(3)重复寄存器
仅存在高级定时器中,还是相当于一个分频器。

小节:其实这三者都能起到分频作用,而中间的计数器与自动重装寄存器除此之外还能计数而已。它是时基单元与CCR寄存器建立联系的桥梁。

2.模块二——输出比较模块

二、实验内容

(一)标准库点亮LED灯

1.实验说明

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

2.设计思路

(1)中断的条件
设置为UVE(update event),设置为定时器中的事件更新触发中断。说人话就是,定时器中的计数器溢出一次就出发中断。显然是等周期触发中断,我设置为1ms触发一次中断。每触发一次中断就计时一次,可以用全局变量currentTick来记录中断次数,也可以等同为程序运行的时间。
(2)延时函数的设计
先定义一个我们想要的延时变量命名为ms,其单位也是ms。
再定义一个变量uint64_t expireTime = App_Timer_GetTick() + ms,作为刚开始的定值变量。
接下里在while循环里不断让App_Timer_GetTick()返回的动态变量currentTick去追逐静态的高值变量expireTime,差值恰好为我们想要的延时ms。

3.代码部分

代码中的注释已经很明确了,这里不再逐句赘述。
(1)main.c文件

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "app_timer.h"


int main()
{
	App_Timer_Init();
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);

	
	while(1)
	{
		GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
		
		App_Timer_Delay(2000);
		
		GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
		
		App_Timer_Delay(2000);
		
	}
}

2.app_timer.c文件

#include "app_timer.h"
#include "stdint.h"

static volatile uint64_t currentTick = 0;

void App_Timer_Init()
{
	//对定时器3的复位
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_TIM3,DISABLE);
	//1.使能定时器3的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	
	//2.使能ARR寄存器的预加载特性
	TIM_ARRPreloadConfig(TIM3,ENABLE);
	
	
	//3.初始化实际单元的参数
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 71;      //就是PSC里面的值,先72分频为,72MHz/72 = 1MHz;————>1us
	TIM_TimeBaseInitStruct.TIM_Period = 999;		//1us——————>1ms
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //就是RCR里面的值,重复计数器写0,相当于不再分频。
	//TIM_TimeBaseInitStruct.TIM_ClockDivision ;   //死区时间、输入滤波,暂时不设置
	//TIM_TimeBaseInitStruct.TIM_RepetitionCounter;  //定时器3没有重复计数器,不需要设置
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
	
	//这些参数还在影子寄存器里,手动产生中断
	TIM_GenerateEvent(TIM3,TIM_EventSource_Update);
	
	
	
	
	//4.使能Update中断
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);    //重点!!!!中断触发的条件————>更新事件.
								//而更新事件通常指的是定时器的计数器达到自动重载寄存器(ARR)的值时,即完成了一个计数周期,此时硬件会自动置位一个中断标志位(TIM_FLAG_Update)
								//显然就是:1ms触发一次中断!
	
	//5.配置NVIC模块
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
 	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0 ;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0 ;
	
	
	NVIC_Init(&NVIC_InitStruct);
	
	//6.使能定时器
	TIM_Cmd(TIM3,ENABLE);
	
}	


void TIM3_IRQHandler()
{
	if(TIM_GetFlagStatus(TIM3,TIM_FLAG_Update)== SET)	
	//这个函数的本意是得到具体定时器的具体部位的标志位
	//在这里是得到定时器3的更新标志位:当定时器计数器达到自动重载寄存器(ARR)的值并发生更新事件时,硬件会自动置。在这里是1ms
	{
		TIM_ClearFlag(TIM3,TIM_FLAG_Update);   	//中断函数的第一步,清除中断标志位
		currentTick++;					//currentTick计的是毫秒值。每经过1ms,currentTick就加1.
	}

}


uint64_t App_Timer_GetTick()   //获取当前时刻,单位ms
{
	return currentTick;
}

void App_Timer_Delay(uint32_t ms)   //毫秒级延时
{
	uint64_t expireTime = App_Timer_GetTick() + ms;
	while(App_Timer_GetTick() < expireTime);
}
//这个中断函数设计的很巧妙,expireTime是定值,刚进入while循环时,这个大的定值一直与currentTick这个动态值的差值刚好是传入的ms,当currentTick经过ms时间后,循环结束。
//这个动态小值追逐定值大值的过程就是花费的我们设置的延时时间。

4.实验现象

20240531——001

5.keil仿真

在这里插入图片描述
在这里插入图片描述

延时恰好为2s,设计正确。

(二)标准库点亮呼吸灯

1.实验说明

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

2.设计思路

选择定时器的输出比较模式中的PWM模式,输出一个正弦电压到引脚即可。

3.代码部分

(1)main.c文件

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
	PAL_Init();
	
	// #1. 将PA8初始化为复用推挽模式
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// #2. 开启定时器1的时钟
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_TIM1, ENABLE);
	RCC_APB2PeriphResetCmd(RCC_APB2Periph_TIM1, DISABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
	
	// #3. 初始化时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 71;
	TIM_TimeBaseInitStruct.TIM_Period = 999;
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);
	
	// #4. 初始化输出比较通道1
	TIM_OCInitTypeDef TIM_OCInitStruct;
	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse = 0;
	TIM_OC1Init(TIM1, &TIM_OCInitStruct);
	
	// #5. 闭合MOE
	TIM_CtrlPWMOutputs(TIM1, ENABLE);
	
	// #6. 手动产生UEV
	TIM_GenerateEvent(TIM1, TIM_EventSource_Update);
	
	// #7. 使能定时器
	TIM_Cmd(TIM1, ENABLE);
	
	while(1)
	{
		float t = PAL_GetTick() * 0.001;
		float duty = 0.5 + 0.5 * sin(0.6 *3.14 * t);
		uint16_t ccr = duty * 999;
		TIM_SetCompare1(TIM1, ccr);
	}
}

4.实验现象

20240531——002

5.kei仿真

在这里插入图片描述

我不太明白为啥是频率快的方波,也能达到周期为1~2S的呼吸灯的效果。

三、总结

通过本次实验,学习了定时器的有关概念及其结构。我用标准库通过定时器中断的方式点亮了LED灯。在此过程中我感觉定时器的很多用途都得与中断联系在一起。总的来说,标准库点灯会遇到很多麻烦,和异常的现象,比如keil波形是快频率的方波,实验现象却是呼吸灯的效果。

  • 31
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以帮你解答这个问题。 首先,PWM(Pulse-Width Modulation)是一种调制技术,可以用来控制电路中的电压或电流。在呼吸实验中,我们可以使用 PWM 技术来控制 LED 的亮度,实现呼吸效果。 在 STM32 中,可以使用定时器模块来实现 PWM 功能。具体步骤如下: 1. 选择一个可用的定时器模块,并配置其时钟源和预分频器。 2. 配置定时器模块为 PWM 模式,并设置 PWM 的周期和占空比。 3. 配置定时器模块的输出通道,将其连接到 LED 控制引脚上。 4. 启动定时器模块,开始 PWM 输出。 下面是一个简单的呼吸实验代码示例: ```c #include "stm32f10x.h" #define LED_PIN GPIO_Pin_13 #define LED_PORT GPIOC void delay(uint32_t ms) { uint32_t i; while (ms--) { for (i = 0; i < 1000; i++); } } void led_pwm_breath(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能定时器时钟和 GPIO 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 配置 LED 控制引脚为推挽输出 GPIO_InitStructure.GPIO_Pin = LED_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED_PORT, &GPIO_InitStructure); // 配置定时器基本参数 TIM_TimeBaseStructure.TIM_Period = 1000; TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // 配置定时器输出通道为 PWM 模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 500; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM1, &TIM_OCInitStructure); // 启动定时器 TIM_Cmd(TIM1, ENABLE); while (1) { // 增加占空比 for (int i = 0; i <= 1000; i += 10) { TIM_SetCompare1(TIM1, i); delay(10); } // 减小占空比 for (int i = 1000; i >= 0; i -= 10) { TIM_SetCompare1(TIM1, i); delay(10); } } } int main(void) { led_pwm_breath(); return 0; } ``` 在上面的代码中,我们使用 TIM1 定时器模块来控制 LED 的亮度。首先,我们配置 TIM1 的时钟源和预分频器,使其时钟频率为 1MHz。然后,我们将 LED 控制引脚 PC13 配置为推挽输出模式,将其连接到 TIM1 的通道 1 上。 接着,我们配置 TIM1 的输出通道 1 为 PWM1 模式,并设置 PWM 的周期为 1000(即 1kHz),占空比为 50%。最后,我们启动 TIM1 定时器,并在一个无限循环中不断增加和减小 PWM 的占空比,实现呼吸效果。 希望这个例子对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值