基于STM32标准库的定时闪灭以及PWM呼吸灯

基于STM32标准库的PWM呼吸灯

一、STM32定时功能介绍

TIM(Timer)定时器是一种用于计数和定时的高精度硬件设备。它内部包含16位计数器、预分频器和自动重装寄存器的时基单元,可以实现对输入时钟的精确计数。当计数值达到设定值时,定时器会触发中断,以便执行相应的操作。这种定时器具有高精度、长定时时间的优点,因此在许多嵌入式系统中得到广泛应用。

根据复杂度和应用场景的不同,TIM定时器分为高级定时器、通用定时器和基本定时器三种类型。
在这里插入图片描述

高级定时器具有最高的复杂度,它不仅具备基本的定时中断功能,还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。这种定时器适用于需要对精度和复杂度要求较高的应用场景,例如高精度计时和复杂的定时控制。

通用定时器则适用于大多数应用场景。它具备基本的定时中断功能,同时也可以根据需要选择使用其他一些功能。这种定时器的复杂度适中,可以满足大多数应用的需求。

基本定时器是最简单的一种,它只具备基本的定时中断功能。这种定时器的复杂度最低,因此适用于一些简单应用场景,例如时间戳记录等。

在STM32中,时钟主要分为内部时钟模式和外部时钟模式以及编码器模式。内部模式使用的时钟频率为72MHz,外部时钟模式的频率由外设的时钟频率来决定。

在本次实验中,主要用到的定时器为通用定时器中的输出比较功能,以及内部时钟模式。

二、PWM功能介绍

PWM(Pulse Width Modulation)脉冲宽度调制是一种在电子和电机控制领域广泛应用的控制技术。通过调制一系列脉冲的宽度,PWM可以等效地获得所需的模拟参量,常应用于电机控速等领域。

PWM信号的参数包括频率、占空比和分辨率。频率是指每秒产生的脉冲数,通常用赫兹(Hz)表示。占空比是指一个脉冲周期中高电平所占的时间比例,用百分比表示。分辨率是指占空比变化的最小步距,用百分比表示。

通用定时器的输出比较通道如下所示:

在这里插入图片描述
输出比较的模式如下所示:

在这里插入图片描述

我们一般采用PWM模式1的向上计数模式。

PWM基本结构以及参数计算如下:

在这里插入图片描述

电平进入后,首先进行预分频,再通过ARR自动重装与CNT计数器进行时钟化,得到的时钟信号进入输出比较单元比较CNT与CCR的关系得到电平变化,最后通过极性选择输出到GPIO。

PWM相关参数如下:

在这里插入图片描述

PWM频率控制输出电平的快慢;占空比指的是信号高电平的时间占总时钟周期的大小。PWM分辨率指的是电平步进的快慢程度。在波形达到99后自动触发中断归0,这是向上计数的原理。

三、创建工程

创建基本的keil工程请参照这篇博客

基于STM32标准库函数的LED流水灯实验_Constellation_zZ的博客-CSDN博客

在原有基础工程上,我们在左边文件栏“Hardware"添加一个c文件,和一个头文件,统一命名为”PWM"。注意添加时把路径加上

在这里插入图片描述

我们在“PWM.h”中添加基本的函数语句

#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h"                  // Device header




#endif

这样,基本的工程就创建好了。

四、LED规律性闪灭实现

4.1 代码思路

通过配置定时中断,每1s产生一次中断。定义GPIOA0口作为端口插上LED灯。在中断函数中写入电平变化。主函数只需要一个while(1){ }就可以了

4.2 配置代码

在“PWM.c"中写入以下代码

#include "stm32f10x.h"                  // Device header

#include "Delay.h"
extern uint16_t Num;

void Timer_Init(void)//定时产生中断配置
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//APB1使能。
  TIM_InternalClockConfig(TIM2);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//配置结构体
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//指定时钟分频,选择不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//选择计数模式,向上计数
	TIM_TimeBaseInitStruct.TIM_Period=10000-1;//周期,ARR自动重装的值
	TIM_TimeBaseInitStruct.TIM_Prescaler= 7200-1;//PSC预分频器的值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;//重复计数器的值,高级定时器才会使用
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
	//使能更新中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//开启更新中断到NVIC
  //NVIC配置
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//选择分组2
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel= TIM2_IRQn;//定时器2在NVIC中的通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE ;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	GPIO_SetBits(GPIOA,GPIO_Pin_0);
	//启动定时器
	TIM_Cmd(TIM2,ENABLE);

}

//uint16_t Timer_GetCounter(void)
//{
//	return TIM_GetCounter(TIM2);
//	
//	
//	
//}

void TIM2_IRQHandler(void)//定时器2中断函数
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)//TIM2判断标志位
	{
		
		GPIO_ResetBits(GPIOA,GPIO_Pin_0);
		Delay_ms(1000);
		GPIO_SetBits(GPIOA,GPIO_Pin_0);
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
		}
		}

主函数中写出基本配置就好了

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Timer.h"
#include "LED.h"
#include "Delay.h"
uint16_t Num;
int main(void)
{

    Timer_Init();//配置函数

	while (1)
	{

		 
	}
}

4.3 烧录结果

烧录结果如下:
在这里插入图片描述

五、PWM配置函数解析

注意:以下操作都在PWM.c文件中执行。

5.1 内置时钟使能

在STM32中,控制TIM2使能的是APB1,且需要控制TIM2为内部时钟模式,配置的相关函数为

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//APB1使能。
TIM_InternalClockConfig(TIM2);//TIM2认定内部时钟模式
	

我们直接这样写入就可以了

5.2 配置时基单元

配置时基单元有点类似于GPIO,也是需要定义一个结构体变量,再将其结构体成员引出来分别进行配置。声明结构体的语句为

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//配置结构体

我们将结构体成员都引出来,逐个配置

根据公式,时钟频率为72MHZ/(PSC+1)/(ARR+1),经过计算,当PSC设置为719,ARR设置为99,时钟频率为1000HZ,也就是0.001s,也就是说,每个电平存在时间为0.001s

//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//配置结构体
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//指定时钟分频,选择不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//选择计数模式,向上计数
	TIM_TimeBaseInitStruct.TIM_Period=100-1;//周期,ARR自动重装的值
	TIM_TimeBaseInitStruct.TIM_Prescaler= 720-1;//PSC预分频器的值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;//重复计数器的值,高级定时器才会使用
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);

这样就配置好了。

5.3 使能更新中断

我们需要配置使能中断,设置为输出比较模式,这样才能将已激活的时钟信号进行输出比较从而得到PWM波形。

输出比较模式的配置有专门的标准库函数,也是结构体类型,我们一个个了解并将其配置。输出比较结构体定义为

TIM_OCInitTypeDef TIM_OCInitStruct;//输出比较结构体定义

使能中断的代码如下:

//使能更新中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//开启更新中断到NVIC
  
	TIM_OCInitTypeDef TIM_OCInitStruct;
	TIM_OCStructInit(&TIM_OCInitStruct);//结构体赋初始值,避免没有出现的成员未编译而报错
	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;//输出比较模式,选择PWM1模式
	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性,高电平有效
	TIM_OCInitStruct.TIM_OutputState= TIM_OutputState_Enable;//设置输出使能
	TIM_OCInitStruct.TIM_Pulse=0 ;//设置CCR 
    TIM_OC1Init(TIM2,&TIM_OCInitStruct);	

5.4 配置GPIO并启动定时器

此外,由于需要使用LED灯,所以还需要加上GPIO的配置函数。此外,TIM对应GPIO引脚需要查找定义表,TIM2_CH1_ETR引脚复用在PA0上,故我们配置PA0。

//配置GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	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);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_0);
	//启动定时器
	TIM_Cmd(TIM2,ENABLE);

5.5 输出比较函数

根据前面的公式我们知道,PWM的占空比的公式为CCR/ARR+1。前面我们设定为ARR为99,故我们可以设定CCR在0-100波动,这样算出的占空比就是0-100%了,具体结果会在主函数里展示。我们现在只是定义了一个输出比较函数

在STM32标准库里,系统给出了设置CCR函数为

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)

我们可以通过这个函数来设定一个不断变化的CCR值。

在这里我们写入以下代码

void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2,Compare);//设置CCR寄存器的值,可以随时改变CCR的值
	
	
	
}

5.6 头文件以及C文件最终函数

头文件:

#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h"                  // Device header

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);


#endif

C文件:

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//APB1使能。
	TIM_InternalClockConfig(TIM2);//TIM2认定内部时钟模式
	
	
	//配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//配置结构体
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//指定时钟分频,选择不分频
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//选择计数模式,向上计数
	TIM_TimeBaseInitStruct.TIM_Period=100-1;//周期,ARR自动重装的值
	TIM_TimeBaseInitStruct.TIM_Prescaler= 720-1;//PSC预分频器的值
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;//重复计数器的值,高级定时器才会使用
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
	
	//使能更新中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//开启更新中断到NVIC
  
	TIM_OCInitTypeDef TIM_OCInitStruct;
	TIM_OCStructInit(&TIM_OCInitStruct);//结构体赋初始值,避免没有出现的成员未编译而报错
	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;//输出比较模式,选择PWM1模式
	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;//输出极性,高电平有效
	TIM_OCInitStruct.TIM_OutputState= TIM_OutputState_Enable;//设置输出使能
	TIM_OCInitStruct.TIM_Pulse=0 ;//设置CCR 
  TIM_OC1Init(TIM2,&TIM_OCInitStruct);	
	
	//TIM对应GPIO引脚需要查找定义表,TIM2_CH1_ETR引脚复用在PA0上
	//配置GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	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);
	
	GPIO_SetBits(GPIOA, GPIO_Pin_0);
	//启动定时器
	TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2,Compare);//设置CCR寄存器的值,可以随时改变CCR的值
	
	
	
}

六、PWM主函数编写解析

6.1 代码思路

在配置函数里,我们写到过CCR设置函数。那么我们可以利用一个for循环,定义一个变量作为CCR,不断改变该变量的值来实现占空比的不断变化,占空比越大,输出信号的幅值越大;占空比越小,输出信号的幅值也越小,那么我们就使得最后输出的电平幅值也在不断地变化,得到了一个电平周期变化的模拟信号,从而得到呼吸灯的效果。

6.2 主函数代码

在for循环中,不断调用设置CCR的函数,不断改变CCR从而实现呼吸灯

最终代码如下:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
//#include "OLED.h"
#include "PWM.h"
#include "LED.h"
#include "Delay.h"
uint16_t Num;
int main(void)
{
//	OLED_Init();
    PWM_Init();//配置函数
//  OLED_ShowString(1,1,"a:");
//  OLED_ShowString(2,1,"b:");
	while (1)
	{//在for循环中,不断调用设置CCR的函数实现呼吸灯
		for(Num=0;Num<=100;Num++)//幅值不断增大
		{
			
			PWM_SetCompare1(Num);
			Delay_ms(10);//通过延时函数可改变闪动的周期。
//			OLED_ShowNum(1,6,Num,3);
		}
		for(Num=0;Num<=100;Num++)//幅值不断减小
		{
			
			PWM_SetCompare1(100-Num);
			Delay_ms(10);	
//			OLED_ShowNum(2,6,100-Num,3);
		}
		
	}
}

七、烧录结果以及波形观察

在PA0端口插上一个LED灯,其他跟之前的实验一样。
在这里插入图片描述
烧录结果如下:

在这里插入图片描述
打开魔术棒,设置如下:
在这里插入图片描述
在这里插入图片描述

打开keil的调试界面,点击逻辑分析仪

在这里插入图片描述
在界面点击”SET UP“
在这里插入图片描述
在弹出界面中设置以下(上面栏里填入”PORTA.0)。
在这里插入图片描述
点击”close“,然后点击左边全速运行程序
在这里插入图片描述
得到结果如下:

在这里插入图片描述

八、总结

通过这次实验,我对STM32的定时中断功能有了更深入的理解,明白了不同基本定时中断模式的运作原理,也探索了通用定时器的输出比较功能。通过精心设计频率、占空比以及分辨率等参数,我成功地模拟出了类模拟信号,并借助PA0引脚的复用输出功能,将信号输出到端口,最终实现了LED呼吸灯的展示效果。

尽管使用标准库的操作可能显得繁琐,但其中的逻辑步骤却十分清晰。这次实验不仅提升了我编程的能力,也增强了我检查和避免错误的能力。我得以更深入地理解如何通过编程实现对硬件的控制,以及如何细致入微地调整参数以达到预期的效果。

九、参考

[6-4] PWM驱动LED呼吸灯&PWM驱动舵机&PWM驱动直流电机_哔哩哔哩_bilibili

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用STM32CubeMX和HAL库编写PWM呼吸灯的步骤: 步骤1:打开STM32CubeMX,创建一个新的工程。 步骤2:选择芯片型号并设置时钟树。 步骤3:在“Pinout”选项卡中,将一个GPIO引脚配置为PWM输出模式。例如,我选择了PA5引脚。 步骤4:在“Configuration”选项卡中,配置TIM(定时器)为PWM模式。 步骤5:在“Code Generation”选项卡中,生成代码并打开"main.c"文件。 步骤6:在"main.c"文件中找到以下代码: ``` /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ ``` 在这段代码之前添加以下代码: ``` /* USER CODE BEGIN PWM_Init */ /* 定义PWM周期 */ #define PWM_PERIOD 1000 /* 定义呼吸灯的最小亮度和最大亮度 */ #define MIN_BRIGHTNESS 0 #define MAX_BRIGHTNESS 100 /* 定义呼吸灯的亮度变化速度 */ #define BRIGHTNESS_STEP 5 /* 定义呼吸灯的当前亮度 */ static uint16_t currentBrightness = MIN_BRIGHTNESS; /* 定义呼吸灯的亮度递增/递减标志 */ static bool brightnessFlag = true; /* 定义PWM周期计数器 */ static uint16_t pwmCounter = 0; /* USER CODE END PWM_Init */ ``` 这段代码定义了一些变量,用于控制PWM输出和呼吸灯效果。 步骤7:在"main.c"文件中找到以下代码: ``` /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ ``` 在这段代码之前添加以下代码: ``` /* USER CODE BEGIN PWM_Functions */ /** * @brief 呼吸灯控制函数 */ void breathingLightControl(void) { if (brightnessFlag) { currentBrightness += BRIGHTNESS_STEP; if (currentBrightness >= MAX_BRIGHTNESS) { brightnessFlag = false; } } else { currentBrightness -= BRIGHTNESS_STEP; if (currentBrightness <= MIN_BRIGHTNESS) { brightnessFlag = true; } } /* 设置PWM输出占空比 */ TIM3->CCR1 = currentBrightness; } /* USER CODE END PWM_Functions */ ``` 这段代码定义了一个名为“breathingLightControl”的函数,用于控制呼吸灯效果。该函数通过修改PWM的占空比来达到呼吸灯效果。 步骤8:在"main.c"文件中找到以下代码: ``` /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ /* USER CODE END 3 */ } /* USER CODE END */ ``` 在这段代码中添加以下代码: ``` /* USER CODE BEGIN PWM_Loop */ while (1) { /* 呼吸灯控制函数 */ breathingLightControl(); /* 等待一段时间(可根据需要调整时间) */ HAL_Delay(10); } /* USER CODE END PWM_Loop */ ``` 这段代码定义了一个无限循环,在该循环中调用“breathingLightControl”函数,并等待一定时间,以控制呼吸灯效果。 步骤9:编译和下载代码。 至此,我们已经成功地使用STM32CubeMX和HAL库编写了PWM呼吸灯程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值