【江科大STM32】TIM输出比较-PWM功能(学习笔记)

一、PWM驱动LED呼吸灯

接线图:

PWM的初始化:

具体步骤:

①RCC开启时钟(把要用的TIM外设和GPIO外设时钟都打开)

② 配置时基单元,包括前面的时钟源选择

③配置输出比较单元,里面包括CCR的值,输出比较模式,极性选择,输出使能这些参数

④配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出模式

⑤ 运行控制,启动计数器

使用到的函数:

TIM函数之前有部分介绍过了,具体参考这篇文章 [江科大STM32]TIM定时器中断(学习笔记)下-CSDN博客

重要函数需要掌握:

不常用了解即可:

函数解释:

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);

使用方法:需要初始化哪个通道,就调用哪个函数,不同的通道对应的GPIO口也不一样,所以要按照所需的GPIO口来。

作用:根据指定初始化TIMx Channel1参数在TIM_OCInitStruct。

|------------------|------------------------------|
| 参数 | 说明 |
| TIMx | 其中x除6、7外可选1 ~ 17,用于选择TIM外设。 |
| TIM_OCInitStruct | 指向TIM_OCInitTypeDef结构体的指针 |

比较Init结构定义(只列出使用到的)

|-----------------|------------------------------------------------------|
| 参数 | 说明 |
| TIM_OCMode | 指定TIM模式 |
| TIM_OutputState | 指定TIM输出比较状态 |
| TIM_Pulse | 指定要加载到捕获比较寄存器中的脉冲值(CCR的值),取值范围为0x0000 ~ 0xFFFF之间的数字 |
| TIM_OCPolarity | 指定输出极性 |

具体参数:

TIM_Pulse的值要和时基单元里面的ARR和PSC的值一起配置,这三个值共同决定了输出PWM的周期和占空比,计算公式:

比如,这里如果要求输出一个频率为1KHz,占空比可任意调节,分辨率为1%的PWM波形,那ARR+1=100,CCR=50,PSC+1=720.这样就是输出频率为1KHz,占空比为50%

注意 :

在这里结构体并没有配置完整,对于结构体变量来说,还是一个局部变量,不给所以结构体成员赋值会出现一些问题,比如你想把高级定时器当作通用定时器输出PWM时,自然会把TIM2改TIM1,那由于结构体成员没有完全赋值,就会出现一些问题。**而且这里能不能输出PWM还会跟初始化函数在哪一行有关。**这里为避免出现问题,要么把结构体成员配置完,要么就给结构体赋初始值。这里我们使用函数给结构体赋初始值,使用到下面的函数

void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);//用默认值填充每个TIM_OCInitStruct成员。

|------------------|-------------------------------|
| 参数 | 说明 |
| TIM_OCInitStruct | 指向TIM_OCInitTypeDef结构体的指针被初始化 |

如果不想把结构体所有成员都列出来,那就用这个函数先给结构体成员赋一个初始值,再更改你想更改的结构体成员的值。

配置GPIO口

TIM2的OC1通道使用的是哪个GPIO口,这里得参考一下引脚定义表

cs 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;//开漏推挽输出
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);

推挽输出:

选择开漏推挽输出的原因:因为这里是用定时器来控制引脚,就需要用开漏推挽输出。只有设置成开漏推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出。

使用示波器采集PWM波形:示波器采集PWM波形

初始化代码:

cs 复制代码
#include "stm32f10x.h"                  // Device header


void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_0;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	TIM_InternalClockConfig(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_OCInitTypeDef TIM_OCInitStructure;
	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 =50;//CCR的值
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	
	TIM_Cmd(TIM2,ENABLE);//启动定时器
}	

LED呼吸灯

产生LED呼吸灯效果就是要不断更改CCR的值就可以了。在运行过程更改CCR,就需要用到下面函数

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);//单独更改CCR通道1的值

|----------|-----------------------------|
| 参数 | 说明 |
| TIMx | 其中x除6、7外可选1 ~ 17,用于选择TIM外设 |
| Compare1 | 表示捕获Compare1寄存器的新值 |

注意:这里单独设置了CCR的值,之前初始化那里CCR的值就要改为0了

封装使用:

cs 复制代码
/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);		//设置CCR1的值
}

主函数代码:

只需要在主函数while()循环里面不断调用 PWM_SetCompare1这个函数,更改CCR的值,就可以实现LED呼吸灯效果

cs 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

uint8_t i;			//定义for循环的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	PWM_Init();			//PWM初始化
	
	while (1)
	{
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(i);			//依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
			Delay_ms(10);				//延时10ms
		}
		for (i = 0; i <= 100; i++)
		{
			PWM_SetCompare1(100 - i);	//依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
			Delay_ms(10);				//延时10ms
		}
	}
}

引脚重映射

看图可知道TIM2通道1可以复用到PA15上

①需要用到AFIO,先开启AFIO时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

复用功能重映射表讲解

复用功能重映射函数:

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState); //更改指定引脚的映射

|------------|-------------------|
| 参数 | 说明 |
| GPIO_Remap | 选择要重新映射的引脚 |
| NewState | ENABLE or DISABLE |

选择要重新映射的引脚:

**注意:**PA15上电后恢复默认为调试端口JTDI,所以想要它变为普通的GPIO或者定时器的复用通道,需要先关闭调试端口的复用,还是用上面这个函数,只是参数改变函数讲解

总结:35:00

二、PWM驱动舵机

接线图:

有关舵机简介和具体接线可参考【江科大STM32】TIM输出比较(学习笔记)-CSDN博客

PWM初始化:

初始化函数跟上面LED呼吸灯一样,只是我们这里用了TIM2的CH2,对应的GPIO口换为PA1就好了

这里算一下ARR和PSC和CCR的值:舵机要求周期为20ms,那频率就是1s/20ms=50Hz(20ms=0.02s),占空比是0.5ms~2.5ms,这里PSC和ARR的值不是固定的,多尝试几次找到合适的值就好。这里占空比是20k对应20ms,比例为20000对应20,500k对应0.5ms就是对应的比例计算出来的(占空比20000和5000只是用来和PWM周期做对比的,比如500/20000就是对应周期0.5/20,2500/20000对应2.5/20),

代码:

cs 复制代码
#include "stm32f10x.h"                  // Device header


void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
		
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin =GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed =GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	TIM_InternalClockConfig(TIM2);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_ClockDivision =TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_CounterMode =TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period =20000 - 1;//ARR
	TIM_TimeBaseInitStruct.TIM_Prescaler =72 - 1;//PSC
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter =0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	
	TIM_OCInitTypeDef TIM_OCInitStructure;
	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;//CCR的值
	TIM_OC2Init(TIM2,&TIM_OCInitStructure);
	
	TIM_Cmd(TIM2,ENABLE);//启动定时器
}	

void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);//单独更改CCR通道2的值

|----------|-----------------------------|
| 参数 | 说明 |
| TIMx | 其中x除6、7外可选1 ~ 17,用于选择TIM外设 |
| Compare2 | 指定Capture Compare2注册新值 |

cs 复制代码
void PWM_SetCompare2(uint16_t compare)
{
	TIM_SetCompare2(TIM2,compare);
}

使用多通道讲解:40:11

舵机代码:

PWM_SetCompare2(Angle /180 *2000 +500);//设置占空比来源:

这里角度0度对应CCR500, 180度对应CCR2500参数计算

cs 复制代码
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Servo_Init(void)
{
	PWM_Init();//底层PWM初始化
}

/**
  * 函    数:舵机设置角度
  * 参    数:Angle 要设置的舵机角度,范围:0~180
  * 返 回 值:无
  */
void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2(Angle /180 *2000 +500);//设置占空比
											//将角度线性变换,对应到舵机要求的占空比范围上
}

主函数:

cs 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNum;			//定义用于接收键码的变量
float Angle;			//定义角度变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Servo_Init();		//舵机初始化
	Key_Init();			//按键初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Angle:");	//1行1列显示字符串Angle:
	
	while (1)
	{
		KeyNum = Key_GetNum();			//获取按键键码
		if (KeyNum == 1)				//按键1按下
		{
			Angle += 30;				//角度变量自增30
			if (Angle > 180)			//角度变量超过180后
			{
				Angle = 0;				//角度变量归零
			}
		}
		Servo_SetAngle(Angle);			//设置舵机的角度为角度变量
		OLED_ShowNum(1, 7, Angle, 3);	//OLED显示角度变量
	}
}

每按一下按键占空比变化为30%,因为这里设置的角度是每按一下加30

三、PWM驱动直流电机

接线图:电机驱动模块注意不能接反了,不然容易烧坏驱动

PWM初始化函数:

初始化函数跟上面LED呼吸灯一样,只是我们这里用了TIM2的CH3,对应的GPIO口换为PA2就好了。同时,PWM设置CCR的值也要改为PWM_SetCompare3。电机如果发出有蜂鸣器的声音,可将PWM初始化代码中时基单元部分里的预分频器值改小,方法

直流电机初始化

这里初始化还要初始化电机方向控制脚,这里用的是PA4和PA5引脚

cs 复制代码
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

/**
  * 函    数:直流电机初始化
  * 参    数:无
  * 返 回 值:无
  */
void Motor_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);						//将PA4和PA5引脚初始化为推挽输出	
	
	PWM_Init();													//初始化直流电机的底层PWM
}

/**
  * 函    数:直流电机设置速度
  * 参    数:Speed 要设置的速度,范围:-100~100
  * 返 回 值:无
  */
void Motor_SetSpeed(int8_t Speed)
{
	if (Speed >= 0)							//如果设置正转的速度值
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_4);	//PA4置高电平
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);	//PA5置低电平,设置方向为正转
		PWM_SetCompare3(Speed);				//PWM设置为速度值
	}
	else									//否则,即设置反转的速度值
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);	//PA4置低电平
		GPIO_SetBits(GPIOA, GPIO_Pin_5);	//PA5置高电平,设置方向为反转
		PWM_SetCompare3(-Speed);			//PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
	}
}

主函数:

按键每按下一次,电机的速度增加20

cs 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"

uint8_t KeyNum;		//定义用于接收按键键码的变量
int8_t Speed;		//定义速度变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Motor_Init();		//直流电机初始化
	Key_Init();			//按键初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Speed:");		//1行1列显示字符串Speed:
	
	while (1)
	{
		KeyNum = Key_GetNum();				//获取按键键码
		if (KeyNum == 1)					//按键1按下
		{
			Speed += 20;					//速度变量自增20
			if (Speed > 100)				//速度变量超过100后
			{
				Speed = -100;				//速度变量变为-100
											//此操作会让电机旋转方向突然改变,可能会因供电不足而导致单片机复位
											//若出现了此现象,则应避免使用这样的操作
			}
		}
		Motor_SetSpeed(Speed);				//设置直流电机的速度为速度变量
		OLED_ShowSignedNum(1, 7, Speed, 3);	//OLED显示速度变量
	}
}

以上代码学习均来自b站江科大。

活动发起人@小虚竹 想对你说:

这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!我们一起发掘写作的魅力,书写出属于我们的故事。我们诚挚邀请你参加为期14天的创作挑战赛!

提醒:在发布作品前,请将不需要的内容删除。

相关推荐
#金毛22 分钟前
什么是JTAG、SWD?
单片机·嵌入式硬件
银迢迢1 小时前
javaweb自用笔记:Vue
javascript·vue.js·笔记
-一杯为品-1 小时前
【51单片机】程序实验13.串口通信
单片机·嵌入式硬件·51单片机
cchjyq2 小时前
证明:曲线的可导点不能同时为极值点和拐点
经验分享·笔记·考研·抽象代数
啥也不会的菜鸟·2 小时前
Redis7——进阶篇(二)
redis·学习·缓存·redis经典面试题
iiiiiankor2 小时前
C与C++中inline关键字的深入解析与使用指南
c语言·c++
_zwy2 小时前
【C++ 函数重载】—— 现代编译技术下的多态表达与性能优化
c语言·开发语言·汇编·c++
viperrrrrrrrrr72 小时前
大数据学习(55)-BI工具数据分析的使用
大数据·学习·数据分析·database
nenchoumi31192 小时前
AutoGen学习笔记系列(三)Tutorial - Agents
笔记·python·学习·语言模型
雷门大师姐2 小时前
10.LED点阵实验
单片机·嵌入式硬件·51单片机