【平衡小车】之PWM驱动电机


前言

我们知道,使用单片机内部处理的是0,1这样的数字信号,而如果我们仅仅使用0,1来控制小车的速度,这肯定是不现实的啊,工程师们想到了用高电平与整个周期的占比来输出电压的平均值,这样几个单一的电压就变成了多种不同的模拟输出电压,真是无处不AD/DA转换啊!

一、L298N驱动模块

在这里插入图片描述
在这里插入图片描述
接线准备
在这里插入图片描述

这是我使用到的电机的接线原理图,由于本次还用不到编码器测测速,我们只需要将L298N输出A分别接电机1的正负极,L298N输出B分别接电机2的正负极。

二、STM32生成PWM波

(一)初始化代码

void TIM4_PWM_Init(u16 arr,u16 psc)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  	TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;   //TIM_CH3,TIM_CH4
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //配置为复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			 //输出速度为50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);                 //初始化GPIO
	
	
	TIM_TimeBaseStructure.TIM_Period = arr;                // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
	TIM_TimeBaseStructure.TIM_Prescaler =psc;              // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;            //设置时钟不分频
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);          //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        // 配置为PWM模式1
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出通道电平极性配置	  TIM输出比较极性为高

	TIM_OC3Init(TIM4,&TIM_OCInitStructure);                   //参数初始化外设TIM4 OC3
	TIM_OC4Init(TIM4,&TIM_OCInitStructure);  									//参数初始化外设TIM4 OC4
	
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);          //使能TIM4 OC3预装载器
	TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);					//使能TIM4 OC4预装载器
	
	TIM_Cmd(TIM4,ENABLE);       //使能TIM4
 
}

配置过程

  • 初始化GPIO结构体
  • 初始化时基结构体
  • 配置输出比较参数
  • 使能定时器

(二)PWM代码分析

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        // 配置为PWM模式1
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出通道电平极性配置	  TIM输出比较极性为高

在这里插入图片描述
在这里我们选择的是PWM1模式,计数器计数方式是向上递增,有效电平为高电平。
所以当CNT从零开始计数到CCR之前,输出为高电平;
计数器值CNT等于CCR值时,电平翻转为低电平,直到计算器溢出(CNT的值等于自动重装载器ARR的值)一直保持为低电平。

但是有一种意外,那就是万一CCR的值大于ARR的值,又该如何处理?

如下就是设置不同的CCR值PWM的输出情况

在这里插入图片描述

(三)预装载器的功能?

TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);          //使能TIM4 OC3预装载器
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);					//使能TIM4 OC4预装载器

在输出PWM波的时候,有没有这个语句好像都能正常工作,但是为什么在很多规范的代码里都加上了这句话呢?他们之间到底有什么区别呢?

使能预装载的意义在于可以多个通道同时输出时,时序能准确地同步。网上的一段有意义的解释是:设计preload register和shadow register的好处是,所有真正需要起作用的寄存器(shadow register)可以在同一个时间(发生更新事件时)被更新为所对应的preload register的内容,这样可以保证多个通道的操作能够准确地同步。

如果没有shadow register,或者preload register和shadow register是直通的,即软件更新preload register时,同时更新了shadow register,因为软件不可能在一个相同的时刻同时更新多个寄存器,结果造成多个通道的时序不能同步,如果再加上其它因素(例如中断),多个通道的时序关系有可能是不可预知的。可见如果只是单通道输出,多通道输出时没时序精准的同步更新要求,不使能也可以的。

说实话,网上找的这段话我没怎么看懂,于是我又看了该函数对应操作的寄存器的一些解释

void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload)
{
  uint16_t tmpccmr1 = 0;
  /* Check the parameters */
  assert_param(IS_TIM_LIST8_PERIPH(TIMx));
  assert_param(IS_TIM_OCPRELOAD_STATE(TIM_OCPreload));
  tmpccmr1 = TIMx->CCMR1;
  /* Reset the OC1PE Bit */
  tmpccmr1 &= (uint16_t)~((uint16_t)TIM_CCMR1_OC1PE);
  /* Enable or Disable the Output Compare Preload feature */
  tmpccmr1 |= TIM_OCPreload;
  /* Write to TIMx CCMR1 register */
  TIMx->CCMR1 = tmpccmr1;
}

发现该函数操作的是CCMR1寄存器的OC1PE位,在参考手册里看看是怎么说的
在这里插入图片描述

函数进来首先复位OC1PE位,之后再根据形参来决定该位置高还是置低。如果我们使能该位,则当我们在改变CCR1寄存器中的值的时候,他不会立即改变,而是将这个周期走完,下个周期才会更新CCR1 的值。

比如我们设定ARR=100,CCR1=50,累加器从0开始往上加,当OC1PE=0,我们在累加器加到30的时候突然改变CCR1的值,让CCR1=70,此时当累加器加到50的时候,电平是不会跳变的,因为OC1PE=0时,改变CCR1则会立即生效,此时已经将CCR1=70了,因此达到70才会跳变。

当OC1PE=1时,他不会在这个周期生效,累加器还会在50的时候跳变,在下一个周期才会从70跳变。

引用来源

所以说,对于时序要求不精准的控制电机驱动来说,开不开都行。如果你想偷懒少写几句,也不是不行。

三、PWM调速原理

以单片机为例,我们知道,单片机的IO口输出的是数字信号,IO口只能输出高电平和低电平。假设高电平为5V 低电平则为0V 那么我们要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号。

我们知道,电压是以一种连接1或断开0的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。

通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压 比方说 占空比为50%
那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压 那么75%的占空比得到的电压就是3.75V。

在这里插入图片描述

pwm的调节作用来源于对“占周期”的宽度控制,“占周期”变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压值也会上升,“占周期”变窄,输出的电压信号的电压平均值就会降低,通过阻容变换电路所得到的平均电压值也会下降。

也就是,在一定的频率下,通过不同的占空比 即可得到不同的输出模拟电压
pwm就是通过这种原理实现D/A转换的。

PWM就是在合适的信号频率下,通过一个周期里改变占空比的方式来改变输出的有效电压PWM频率越大,相应越快。

具体可参考

PWM原理 PWM频率与占空比详解

在main函数中初始化

 TIM4_PWM_Init(99,71);	 //arr=99,psc=71  PWM频率=72000000/(99+1)/(71+1)=10kHZ

这里我的时钟频率为72MHz,所以分频后72MHz/[(71+1)(99+1)]=10KHz,我用的这款电机的工作频率大约在10Khz左右,在设置分频因子的时候要注意电机的参数,不然电机可能无法正常工作。

设置CCR的值

 TIM_SetCompare3(TIM4,50);  //TIM4的CCR3的值设为50

查看函数原型

void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3)
{
  /* Check the parameters */
  assert_param(IS_TIM_LIST3_PERIPH(TIMx));
  /* Set the Capture Compare3 Register value */
  TIMx->CCR3 = Compare3;
}

由于我们之前选择的是PWM1模式,且高电平为有效电平,所以(CCR-0)为高电平持续时间,占空比为(CCR-0)/199+1;
TIM_SetCompare3(TIM4,50); 占空比为(50-0)/(99+1)=50%
TIM_SetCompare3(TIM4,75); 占空比为(75-0)/(99+1)=75%

通过此函数,我们就可以设置不同的占空比,进而控制电机的转速了。

总结

此外需要注意的是,我使用的这款电机的工作频率大约为10KHZ左右,在配置PWM输出时,需要特别注意PWM的输出频率。我刚开始由于计算错误,配置出的PWM频率为10Hz,然后一直无法驱动电机,一直以为是驱动模块出了问题,最后是使用示波器观察才找出错误来,所以要特别注意。

之前使用这些模块的时候并没有学得这么详细,这次因为要写博客所以基本上对每一个知识点都认真学习了一遍,但仍有可能会有所纰漏,敬请大家谅解并指正,不胜感激!

参考

零基础制作平衡小车【连载】3—STM32生成PWM控制电机旋转

PWM原理 PWM频率与占空比详解

版权声明:本文为CSDN博主「~山有木兮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_52288941/article/details/121317682


前言

我们知道,使用单片机内部处理的是0,1这样的数字信号,而如果我们仅仅使用0,1来控制小车的速度,这肯定是不现实的啊,工程师们想到了用高电平与整个周期的占比来输出电压的平均值,这样几个单一的电压就变成了多种不同的模拟输出电压,真是无处不AD/DA转换啊!

一、L298N驱动模块

在这里插入图片描述
在这里插入图片描述
接线准备
在这里插入图片描述

这是我使用到的电机的接线原理图,由于本次还用不到编码器测测速,我们只需要将L298N输出A分别接电机1的正负极,L298N输出B分别接电机2的正负极。

二、STM32生成PWM波

(一)初始化代码

void TIM4_PWM_Init(u16 arr,u16 psc)
{
	
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  	TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;   //TIM_CH3,TIM_CH4
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        //配置为复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			 //输出速度为50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);                 //初始化GPIO
	
	
	TIM_TimeBaseStructure.TIM_Period = arr;                // 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
	TIM_TimeBaseStructure.TIM_Prescaler =psc;              // 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;            //设置时钟不分频
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);          //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        // 配置为PWM模式1
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出通道电平极性配置	  TIM输出比较极性为高

	TIM_OC3Init(TIM4,&TIM_OCInitStructure);                   //参数初始化外设TIM4 OC3
	TIM_OC4Init(TIM4,&TIM_OCInitStructure);  									//参数初始化外设TIM4 OC4
	
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);          //使能TIM4 OC3预装载器
	TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);					//使能TIM4 OC4预装载器
	
	TIM_Cmd(TIM4,ENABLE);       //使能TIM4
 
}

配置过程

  • 初始化GPIO结构体
  • 初始化时基结构体
  • 配置输出比较参数
  • 使能定时器

(二)PWM代码分析

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;        // 配置为PWM模式1
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出通道电平极性配置	  TIM输出比较极性为高

在这里插入图片描述
在这里我们选择的是PWM1模式,计数器计数方式是向上递增,有效电平为高电平。
所以当CNT从零开始计数到CCR之前,输出为高电平;
计数器值CNT等于CCR值时,电平翻转为低电平,直到计算器溢出(CNT的值等于自动重装载器ARR的值)一直保持为低电平。

但是有一种意外,那就是万一CCR的值大于ARR的值,又该如何处理?

如下就是设置不同的CCR值PWM的输出情况

在这里插入图片描述

(三)预装载器的功能?

TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);          //使能TIM4 OC3预装载器
TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);					//使能TIM4 OC4预装载器

在输出PWM波的时候,有没有这个语句好像都能正常工作,但是为什么在很多规范的代码里都加上了这句话呢?他们之间到底有什么区别呢?

使能预装载的意义在于可以多个通道同时输出时,时序能准确地同步。网上的一段有意义的解释是:设计preload register和shadow register的好处是,所有真正需要起作用的寄存器(shadow register)可以在同一个时间(发生更新事件时)被更新为所对应的preload register的内容,这样可以保证多个通道的操作能够准确地同步。

如果没有shadow register,或者preload register和shadow register是直通的,即软件更新preload register时,同时更新了shadow register,因为软件不可能在一个相同的时刻同时更新多个寄存器,结果造成多个通道的时序不能同步,如果再加上其它因素(例如中断),多个通道的时序关系有可能是不可预知的。可见如果只是单通道输出,多通道输出时没时序精准的同步更新要求,不使能也可以的。

说实话,网上找的这段话我没怎么看懂,于是我又看了该函数对应操作的寄存器的一些解释

void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload)
{
  uint16_t tmpccmr1 = 0;
  /* Check the parameters */
  assert_param(IS_TIM_LIST8_PERIPH(TIMx));
  assert_param(IS_TIM_OCPRELOAD_STATE(TIM_OCPreload));
  tmpccmr1 = TIMx->CCMR1;
  /* Reset the OC1PE Bit */
  tmpccmr1 &= (uint16_t)~((uint16_t)TIM_CCMR1_OC1PE);
  /* Enable or Disable the Output Compare Preload feature */
  tmpccmr1 |= TIM_OCPreload;
  /* Write to TIMx CCMR1 register */
  TIMx->CCMR1 = tmpccmr1;
}

发现该函数操作的是CCMR1寄存器的OC1PE位,在参考手册里看看是怎么说的
在这里插入图片描述

函数进来首先复位OC1PE位,之后再根据形参来决定该位置高还是置低。如果我们使能该位,则当我们在改变CCR1寄存器中的值的时候,他不会立即改变,而是将这个周期走完,下个周期才会更新CCR1 的值。

比如我们设定ARR=100,CCR1=50,累加器从0开始往上加,当OC1PE=0,我们在累加器加到30的时候突然改变CCR1的值,让CCR1=70,此时当累加器加到50的时候,电平是不会跳变的,因为OC1PE=0时,改变CCR1则会立即生效,此时已经将CCR1=70了,因此达到70才会跳变。

当OC1PE=1时,他不会在这个周期生效,累加器还会在50的时候跳变,在下一个周期才会从70跳变。

引用来源

所以说,对于时序要求不精准的控制电机驱动来说,开不开都行。如果你想偷懒少写几句,也不是不行。

三、PWM调速原理

以单片机为例,我们知道,单片机的IO口输出的是数字信号,IO口只能输出高电平和低电平。假设高电平为5V 低电平则为0V 那么我们要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号。

我们知道,电压是以一种连接1或断开0的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。

通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压 比方说 占空比为50%
那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压 那么75%的占空比得到的电压就是3.75V。

在这里插入图片描述

pwm的调节作用来源于对“占周期”的宽度控制,“占周期”变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压值也会上升,“占周期”变窄,输出的电压信号的电压平均值就会降低,通过阻容变换电路所得到的平均电压值也会下降。

也就是,在一定的频率下,通过不同的占空比 即可得到不同的输出模拟电压
pwm就是通过这种原理实现D/A转换的。

PWM就是在合适的信号频率下,通过一个周期里改变占空比的方式来改变输出的有效电压PWM频率越大,相应越快。

具体可参考

PWM原理 PWM频率与占空比详解

在main函数中初始化

 TIM4_PWM_Init(99,71);	 //arr=99,psc=71  PWM频率=72000000/(99+1)/(71+1)=10kHZ

这里我的时钟频率为72MHz,所以分频后72MHz/[(71+1)(99+1)]=10KHz,我用的这款电机的工作频率大约在10Khz左右,在设置分频因子的时候要注意电机的参数,不然电机可能无法正常工作。

设置CCR的值

 TIM_SetCompare3(TIM4,50);  //TIM4的CCR3的值设为50

查看函数原型

void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3)
{
  /* Check the parameters */
  assert_param(IS_TIM_LIST3_PERIPH(TIMx));
  /* Set the Capture Compare3 Register value */
  TIMx->CCR3 = Compare3;
}

由于我们之前选择的是PWM1模式,且高电平为有效电平,所以(CCR-0)为高电平持续时间,占空比为(CCR-0)/199+1;
TIM_SetCompare3(TIM4,50); 占空比为(50-0)/(99+1)=50%
TIM_SetCompare3(TIM4,75); 占空比为(75-0)/(99+1)=75%

通过此函数,我们就可以设置不同的占空比,进而控制电机的转速了。

总结

此外需要注意的是,我使用的这款电机的工作频率大约为10KHZ左右,在配置PWM输出时,需要特别注意PWM的输出频率。我刚开始由于计算错误,配置出的PWM频率为10Hz,然后一直无法驱动电机,一直以为是驱动模块出了问题,最后是使用示波器观察才找出错误来,所以要特别注意。

之前使用这些模块的时候并没有学得这么详细,这次因为要写博客所以基本上对每一个知识点都认真学习了一遍,但仍有可能会有所纰漏,敬请大家谅解并指正,不胜感激!

参考

零基础制作平衡小车【连载】3—STM32生成PWM控制电机旋转

PWM原理 PWM频率与占空比详解

版权声明:本文为CSDN博主「~山有木兮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_52288941/article/details/121317682

生成海报
点赞 0

~山有木兮

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

【平衡小车】之PWM驱动电机

前言 我们知道,使用单片机内部处理的是0,1这样的数字信号,而如果我们仅仅使用0,1来控制小车的速度,这肯定是不现实的啊,工程师们想到了用高电平与整个周期的占

STM32C8T6+LORA(SX1278)

LoRa通信系统 从毕设开始搭建了一个简单的LORA通信系统(两块STM32C8T6最小单片机系统,两块正点原子的loRa,一块温湿度传感器)构建了一个简单的loRa通信系统作为入门。之