STM32 PWM配置与实践详解

如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的

码云地址:stm32学习笔记: stm32学习笔记源码

如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用

git的使用(下载及上传_git如何下载文件_八月风贼冷的博客-CSDN博客

应用为PWM波形,章节使用PWM波驱动电机和使用PWM波混色LED以及呼吸灯LED 

目录

一、理论学习部分。

1、输出比较的介绍

2、什么是PWM波形

3、输出比较通道

 4、输出pwm的计算

 5、高级定时器的输出比较和捕获电路

二、硬件部分与代码

1、PWM点灯(呼吸灯

1)、正常选择定时器配置

2)、使用复用功能来配置定时器

2、舵机(也是伺服电机

1)硬件部分

2)舵机代码部分

3、电机

1)硬件部分

1)软件部分


一、理论学习部分。

1、输出比较的介绍

通过:CNT:计数器与CCR寄存器比较来产生的,并且输入捕获和输出比较使用的是同一个寄存器,然后有四个输出比较,但是CNT用的是同一个。

2、什么是PWM波形

以数字量等效出来的模拟量变化,效果如左1图,在一个周期内高电平长就等效的为上半,否则为下半。

频率:周期的倒数嘛就是频率,

占空比:如图 就是高电平站一整个周期的大小。

分辨率:补距 就是说占空比精度  比如占空比从50只能到51那分辨率就是1,

原理!!!在一段时间内以快速变换频率让两端时间折中,比如电机马力全开与关闭折中,转速就只有了1/2 这样就是pwm,并且可以看成等效电压(0+5)/2 这样整个电压就是2.5v也可以算作一半功率。

3、输出比较通道

左侧输入为CNT和CCR经过比较寄存器的值,左上角ETRF为定时器的写一个小模式,一般不用占时没管,以后再写,当输出比较之后产生的REF有两路,一个是到主模式控制器,一个是近过非门,上面那个是可以吧REF映射到主模式的TRGP输出(本次不使用)。下方是经过一个寄存器,用户往寄存器写值,如果写的是0,信号直接不改变走上面输出,如果输入为1,信号会经过下面的非门之后再输出。最后经过一个使能寄存器。

 ok然后我们看一下输出模式控制器如何输出REF的值,直接看表把= =,用户往寄存器写数据就行,用官方库直接配置函数就行,但还是看一下吧,本次主要使用的是PWM模式,一般习惯向上计数模式,模式1和模式2是取反的,其他的都一样的,

 最后看一下PWM的结构,可以很明显看出来输出比较和计数器比较,CCR的值是由直接设定的,在计数器自增或自减到ARR的过程中,达到了用户设定的CCR值后会让REF为高低电平,最后再通过一个极性选择可以再控制一次pwm的占空比(反向一下= =我感觉这个设定还是直接往寄存器写0不反向吧,不然我前面算pwm搞毛???)

 4、输出pwm的计算

这个频率就是一次ARR的计数值= =,等于就是一个溢出频率

占空比就是:ccr(自己设定的那个输出比较寄存器的值与ARR计数总数的比值,就比如你这设置的是10,总共要计数100次,那你高电平输出就站总周期的百分之10,占空比就是百分之10

分辨率:还是100呗,1/100是百分之1的精度嘛= =,你也可以让ARR很大,那样就能精确的控制百分之0.1之类的变换,但是肯定是对硬件有一定要求的,

 5、高级定时器的输出比较和捕获电路

这一部分不用,先了解一下吧、看着很简单,就多了一个死区生成和一个输出使能,因为外部的两个mos管形成的电路(后面有图,需要两个输出相反的引脚来使能,使用这里就有了两路输出使能,然后为了防止再切换外部两个mos'管状态的时候,因为一些器件原因造成的不能马上切换,所以加了一个死去发生器来防止外部两个mos管的损坏(就是再关闭的瞬间,给了给延时让下面的管子晚一点导通。

 

 这个玩意就能驱动一个电机了,但是电机是大功率的,所以gpio无法直接驱动,只能使用外部驱动。三个mos管组成的这个电路就能驱动一个三相无刷电机了(好像是?后面做飞行器的时候不对我再回来删。

二、硬件部分与代码

1、PWM点灯(呼吸灯

1)、正常选择定时器配置

使用的为PB1的灯(当然如果你自己买了灯你就能随便选哪个定时器的PWM了,我现在只能选这三个,查一下数据手册该口对应的TIM,通用定时器为TIM3的通道4

 先打开这个GPIO口吧前面都配置过不讲解了,但是要说一下输出这里要配置为复用推挽输出,这里看图,我们现在不想GPIO是引脚直接控制的,而是是输出由片上外设来控制,所以这里我们不能直接使用输出数据寄存器来输出,需要复用功能输出,这里找一下输出模式的图,就是复用推挽了,复用推挽就是输出来自片上的外设的。

代码如下:

	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

	//配置GPIO
	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(GPIOB,&GPIO_InitStruct);

然后是打开定时器的开关,定时器我们用的是定时器3,直接复制个定时器初始化过来。

还是一样的配置,滤波分配配置为1、计数模式为向上,重装载为100、分频为720(72M/720/100=100k/100=1khz),最后重复计数器为0(还是一样的配不配没影响,通用定时器压根没有这个硬件)

	 
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
     TIM_InternalClockConfig(TIM3);
	 
	 TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruct;
		TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
		TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
		TIM_TimeBaseInitStruct.TIM_Period=99;   //ARR
		TIM_TimeBaseInitStruct.TIM_Prescaler=719;//PCS 
		TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	 TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);

最后就是本节的PWM配置了

首先声明自己的结构体,然后调用函数初始化一下结构体内的值,这个很重要记住,因为使用的是通用定时器,但是这个结构体内有很多是高级定时器使用的,所以如果你不配置完就可能出现一些奇怪的错误,当然你也可以全部手动配置。

然后是结构体初始化,先回忆一下流程图,我们只需要配置CCR的值来与计数值CNT相比较(所以这里肯定得找到写入CCR得参数,然后是输出模式控制器(这个肯定也是要找到的,之后还一个一个反相器得参数,最后就是开启使能得参数了,这个流程上面的输出比较通道图有啊,忘记得朋友可以返回去看看。

之后我们开始配置参数,

首先CCR得值:参数TIM_Pulse 这里我们先初始化为0,之后会调用函数去重新配置他得

模式控制:OCMode;这里配置为PWM1,这个上面得模式图也是有的哈

反向设置:OCPolarity 这里选择输出高为高   就是不经过那个非门

之后使能:  OuputState 这里肯定是选使能了

最后不要忘记打开计数器得使能哦,不然计数器不动。

	TIM_OCInitTypeDef  TIM_OCInitStruct;
	TIM_OCStructInit(&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;  //CCR
	TIM_OC4Init(TIM3,&TIM_OCInitStruct);
	
	TIM_Cmd(TIM3,ENABLE);

最后写一个可以输入参数得CCR控制

TIM_SetCompare4(TIM3,Compare); 这个函数,可以直接控制CCR写一个函数,带一个形参,方便后面做循环。记住用的是通道4,这里就调Compare4,通道不一样记得要看一下哦。

void PWM_SetCompare4(u16 Compare)
{
	 TIM_SetCompare4(TIM3,Compare);
}

主函数写循环,让其CCR得值从小到大,再从大到小,记得加点延时,不加的话他的变换太快了肉眼不一定可见

int main(void)
{	
  /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  USART_Config();
	delay_init();
	PWM_LEDInit();

  while(1)
	{	 
		 u8 i=0;
		  for(i=0;i<=100;i++)
			{
				PWM_SetCompare4(i);
				delay_ms(10);
			}
			for(i=0;i<=100;i++)
			{
				PWM_SetCompare4(100-i);
				delay_ms(10);
			}
	}	
}

2)、使用复用功能来配置定时器

这次选PB5得复用功能(红色箭头指的位置,有TIM3得通道2,因为PB1、PB0两个得复用是高级定时器、本次写的是通用定时器得PWM所以就没有找那两个引脚口,当然想用也是可以配得。

这里我门选择得是TIM3的引脚重映像,可以查一下TIM3的AFIO复用说明等下好配置复用模式。找到参考手册的AFIO章节。

 如图,我们下使用PB5是部分重印象1模式

 找到GPIOI里面的函数GPIO_PinRemapConfig,查看其参数需要配置模式和使能。找到配置的模式、这里有说明,我们配置TIM3为部分重映射。

 整体代码就如下了,这样PB5的红色灯就成为了呼吸灯了,其他位置都没变动

如果你使用的引脚刚好是这些调试引脚,你需要自己去失能这些引脚,还是使用 GPIO_PinRemapConfig代码里面加了,但是被我屏蔽掉了。

void PWM_LEDInit(void)
{
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);  //开启引脚复用功能
	
		GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE); //重映射配置
         //如果你的引脚刚好是调试引脚,你可以调用这个函数,配置引脚失能
         //GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
	//配置GPIO
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;        //PB5
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);

	
	 TIM_InternalClockConfig(TIM3);
	 
	 TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruct;
		TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
		TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
		TIM_TimeBaseInitStruct.TIM_Period=99;   //ARR
		TIM_TimeBaseInitStruct.TIM_Prescaler=719;//PCS 
		TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	 TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);

	TIM_OCInitTypeDef  TIM_OCInitStruct;
	TIM_OCStructInit(&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;  //CCR
	TIM_OC2Init(TIM3,&TIM_OCInitStruct);  //初始化为通道2的初始化
	
	TIM_Cmd(TIM3,ENABLE);
}

void PWM_SetCompare2(u16 Compare)    //记得吧函数名字也改成2别后面吧直接整蒙了
{
	 TIM_SetCompare2(TIM3,Compare);  //这里要改成通道2哦
}

2、舵机(也是伺服电机

1)硬件部分

这东西就是通过一个pwm来控制转动度数,一般的推荐周期就是20ms,算一下1/0.02=50hz,频率等于周期分之一,记得先把ms换成m统一度量衡朋友们。

然后是接线图、很符合电路常识的配色,但是又不是所以搞单片机的都会电路.JPG2)、舵机代码部分

 使用PB1来输出PWM:黄线接PB1就行,代码使用的为TIM2的通道2

代码如下:这里上面说过推荐周期是20ms,所以就是50hz,这里直接72分频让他为1M会好算很多,现在的周期就是20ms了,参考上面的图,就是高电平0.5ms-2.5ms是0-180度,这就是配置占空比了,

 现在占空比分母是20k,20k=20ms,那我们需要0.5ms就是500

void PWM_ServerInit(void)
{
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

	//配置GPIO
	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(TIM3);
	 
	 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;//PCS 
		TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct); //这里需要改

	TIM_OCInitTypeDef  TIM_OCInitStruct;
	TIM_OCStructInit(&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;  //CCR
	TIM_OC2Init(TIM2,&TIM_OCInitStruct); //改成通道2和定时器2 
	
	TIM_Cmd(TIM2,ENABLE);
}

void PWM_SetCompare2(u16 Compare)
{
	 TIM_SetCompare2(TIM2,Compare); //改了引脚这里也要改
}

 这样就能转动了,但是大部分人肯定是喜欢直接输入度数对的,所以我们自己写一个度数转换函数就好了。这样就能直接输入度数了,公式可以这推出来的。这

void server_Angle(float Angle)
{
	 PWM_SetCompare2(Angle/180*2000+500);
}

3、电机

1)硬件部分

TB6612引脚接线图

但是我自己买的是便宜的 DRV8833所以代码是 DRV8833的哈,先看下DRV8833的图

管脚说明:
ANI1:AO1的逻辑输入控制端口,电平0-5V。
AIN2:AO2的逻辑输入控制端口,电平0-5V。
BNI1:BO1的逻辑输入控制端口,电平0-5V。
BIN2:BO2的逻辑输入控制端口,电平0-5V。
AO1、AO2为1路H桥输出端口,接一个直流电机的两个脚。
BO1、BO2为2路H桥输出端口,接另一个外直接电机的两个脚。
GND:接地。
VM:芯片和电机供电脚,电压范围2.7 V – 10.8 V。
STBY:接地或悬空芯片不工作,无输出,接5V工作;电平0-5V。
NC:空脚

 看得出来的没有单独的PWM线的,他的电机控制速度是直接有两个引脚其中一个输出pwm来控制,这样你如果需要正反转的话一个电机需要占用两个通道= =(应该是这样的吧,不知道是不是我实力不够)凑合用。

1)软件部分

控制电机我们需要两个GPIO并且将其中一个GPIO配置为PWM输出(如果想正反转两个都要是PWM)代码如下首先将一个gpio口初始化,并配置为PWM模式,这里直接初始化了两个通道。主函数直接调用

void PWM_Motor1Init(void)
{
	 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

	//配置GPIO
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	 TIM_InternalClockConfig(TIM3);
	 
	 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;//PCS 
		TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
	 TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);

	TIM_OCInitTypeDef  TIM_OCInitStruct;
	TIM_OCStructInit(&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;  //CCR
	
	TIM_OC1Init(TIM3,&TIM_OCInitStruct);
	TIM_OC2Init(TIM3,&TIM_OCInitStruct);
	
	TIM_Cmd(TIM3,ENABLE);
}

int main(void)
{	
  /*初始化USART 配置模式为 115200 8-N-1,中断接收*/
  USART_Config();
	delay_init();
	PWM_Motor1Init();
  while(1)
	{	 

		TIM_SetCompare1(TIM3,100);
		GPIO_SetBits(GPIOA,GPIO_Pin_6);

		
    TIM_SetCompare2(TIM3,100);
		GPIO_SetBits(GPIOA,GPIO_Pin_7);

		
	}	
}

一个写PWM一个写高电平就能实现正反转了,屏蔽一个开一个,别两个同时开。

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 PWM配置与实践详解

发表评论