基于Arduino IDE环境ESP32 S3控制舵机转动

0x00 什么是舵机

首先我们需要明白什么是伺服电机,伺服电机主要靠脉冲个数来定位,基本上可以这样理解,伺服电机接收到1个脉冲,就会旋转1个脉冲对应的角度,从而实现转动位移,同时伺服电机本身也有编码器,用于监测电机转动角度,也具备发出脉冲的功能。所以伺服电机每旋转一个角度,都会发出对应数量的脉冲,这样和伺服电机接受的脉冲形成了闭环,如此一来,系统就会知道发了多少脉冲给伺服电机,同时又收了多少脉冲回来,这样就能够很精确的控制电机的转动,从而实现精确的定位,从某种意义上说编码器性能决定着伺服系统性能的上限。伺服电机一般用于高精度控制的场景,成本较高,如下图所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第1张
伺服电机套件

那么舵机是什么呢?舵机是个俗称,是玩航模、船模的人起的。因为这种电机比较常用于舵面操纵。所谓舵机,其实就是个低端的伺服电机系统,它也是最常见的伺服电机系统,因此英文叫做Servo,就是ServoMotor的简称。它将PWM信号与滑动变阻器(电位器)的电压相比对,通过硬件电路实现固定控制增益的位置控制。也就是说,它包含了电机、传感器和控制器,是一个完整的伺服电机(系统)。价格低廉、结构紧凑,但精度很低,位置保持能力较差,但是已经能够满足很多低端场景需求。

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第2张
MG90S小舵机
基于Arduino IDE环境ESP32 S3控制舵机转动 - 第3张
MG996R舵机

这些舵机的组成都差不多,主要是由外壳、电路板、驱动马达(直流电机)、减速器齿轮组与位置检测元件所构成。其工作原理是由控制器发送脉冲/指令给舵机,经由电路板上的IC驱动马达开始转动,透过减速齿轮组将动力传输至舵机输出齿,最后带动摆臂或传动机构转动。同时由位置检测器采集位置信息,判断转动角度是否已经到达定位。一般组成结构如下所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第4张
舵机常见组成结构

当然除了这种常见的小功率舵机,还有一些大功率的舵机,其实原理基本上一样,只不过电机换了大一点的,齿轮组减速比更大一点,这样扭矩就可以更大,如下图所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第5张
大功率舵机

0x01 如何实现舵机转动角度控制

现在常见的舵机都是使用PWM来进行控制的,通过不同的脉冲宽度来实现舵机不同角度的控制。PWM(Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,在电机控制领域广泛使用。这里我们需要理解关于PWM的几个基本概念:

  • PWM的频率:在1秒钟内,脉冲信号从高电平到低电平再回到高电平的次数,也就是说一秒钟PWM有多少个周期,单位Hz。
  • PWM的周期:T=1/f,T是PWM周期,f是PWM频率。例如频率若为50Hz ,那么一秒钟内就有 50个PWM周期,一个周期时间是20ms。
  • PWM占空比:在一个脉冲周期内,高电平所占用的时间占整个脉冲周期时间的比例,单位是% (0%-100%)。

周期是一个完整脉冲信号的时间,1s内的周期T次数等于频率f,脉宽时间是指高电平持续时间,脉宽时间占总周期时间的比例,就是占空比。例如脉冲周期的时间是10ms,脉宽时间是8ms,那么占空比是8/10= 80%,这就是占空比为80%的脉冲信号,PWM就是脉冲宽度调制,通过调节占空比就可以调节脉冲宽度。如下图所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第6张
脉冲周期和脉宽时间
基于Arduino IDE环境ESP32 S3控制舵机转动 - 第7张
不同占空比的PWM波形

现在常用的舵机差不多分为两种,一种就是180度转动,还有就是360度转动,有点像普通的电机一样,可以通过不同的脉冲宽度来实现舵机正反转速度控制和停止转动,并不是0-360度指定角度的控制,那下面分别来具体介绍:

(1)180度转动的舵机

180度舵机的控制就是通过一个固定的频率,给其不同的占空比来控制舵机不同的转角。PWM频率一般为50HZ,也就是一个完整脉冲周期为20ms,而脉宽时间(脉冲的高电平持续时间)一般为0.5ms ~ 2.5ms范围来控制180度舵机的0-180度转动,而且两者为线性关系。

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第8张
舵机控制的PWM
基于Arduino IDE环境ESP32 S3控制舵机转动 - 第9张
脉宽时间与180度舵机角度对应关系

这里需要注意点是控制舵机转动角度依赖的是脉宽时间,其实跟脉冲周期20ms没有直接关系,就是只要脉冲周期大于2.5ms都可以实现舵机180度的转动。所以理论上计算就可以得知,pwm频率只要小于400Hz(即脉冲周期大于2.5ms),应该都可以正常控制180度舵机转动。如下公式所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第10张
最大PWM频率计算公式

上面公式中Tmin代表的是脉冲周期的最短时间,这里应该是舵机转动到180度所对应的2.5ms脉宽时间,因为如果脉冲周期比2.5ms还短的话,那PWM就无法保证脉宽时间可以维持2.5ms,从而无法使舵机转动到180度。所以脉冲周期最短为2.5ms,只要比2.5ms长的时间都可以使舵机完整转动0-180度。从而可以得知:

fmax = 1000/2.5 = 400Hz

但是这个400Hz是理论上最高值,实际中应该要比这个小一些才行,因为当PWM控制下发到舵机后,舵机的电机转动也需要消耗时间,所以如果靠近极限的频率会导致电机没有转动时间,从而无法正常工作。

(2)360度转动舵机

360度舵机的驱动方式和以上介绍的180度舵机的驱动方式差不多。特别注意:360度舵机与一般舵机的区别是:给一般舵机一个PWM信号,舵机会转到一个特定角度,而给360度舵机一个PWM信号,舵机会以一个特定的速度不停的转动,类似与普通电机。但与普通电机不同的是,360度舵机转动时速度是闭环控制,转动速度稳定。

不同脉宽时间与360舵机转速的关系如下:

  • 0.5ms:正向最大转速;
  • 1.5ms:速度为0,停止转动;
  • 2.5ms:反向最大转速; 

0x02 Arduino IDE中安装ESP32Servo库

在Arduino中有很多库都可以来使EPS32控制舵机转动,但是我们这里安装的是在Arduino官网上推荐的舵机控制库ESP32Servo,安装该库方式如下:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第11张
安装ESP32Servo库

安装好ESP32Servo库以后,如果能找到如下示例代码说明库是安装好的:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第12张
打开Sweep示例代码

0x03 控制舵机转动代码

我们可以根据提供的Sweep示例代码来看下怎么控制舵机转动的,这个示例代码是控制180度舵机可以在0-180度直接来回转动,具体代码如下所示:

#include <ESP32Servo.h>

Servo myservo;  // create servo object to control a servo
// 16 servo objects can be created on the ESP32

int pos = 0;    // variable to store the servo position
// Recommended PWM GPIO pins on the ESP32 include 2,4,12-19,21-23,25-27,32-33 
// Possible PWM GPIO pins on the ESP32-S2: 0(used by on-board button),1-17,18(used by on-board LED),19-21,26,33-42
// Possible PWM GPIO pins on the ESP32-S3: 0(used by on-board button),1-21,35-45,47,48(used by on-board LED)
// Possible PWM GPIO pins on the ESP32-C3: 0(used by on-board button),1-7,8(used by on-board LED),9-10,18-21
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
int servoPin = 17;
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
int servoPin = 7;
#else
int servoPin = 18;
#endif

void setup() {
	// Allow allocation of all timers
	ESP32PWM::allocateTimer(0);
	ESP32PWM::allocateTimer(1);
	ESP32PWM::allocateTimer(2);
	ESP32PWM::allocateTimer(3);
	myservo.setPeriodHertz(50);    // standard 50 hz servo
	myservo.attach(servoPin, 1000, 2000); // attaches the servo on pin 18 to the servo object
	// using default min/max of 1000us and 2000us
	// different servos may require different min/max settings
	// for an accurate 0 to 180 sweep
}

void loop() {
	for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
		// in steps of 1 degree
		myservo.write(pos);    // tell servo to go to position in variable 'pos'
		delay(15);             // waits 15ms for the servo to reach the position
	}
	for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
		myservo.write(pos);    // tell servo to go to position in variable 'pos'
		delay(15);             // waits 15ms for the servo to reach the position
	}
}

这里注意,由于代码是可以运行在ESP32系列的多款SOC上,不同款SOC连接到引脚不太一样,所以我们需要配置一下舵机需要连接到引脚,这里注意11行和12行代码:

由于我们这里使用ESP32 S3开发板,代码里配置servoPin是17号引脚,我们这里可以修改为1号引脚。 还有就是脉宽时间参数配置,一般普通舵机脉宽时间为500us-2500us时间,所以我们就需要修改26行代码:myservo.attach(servoPin, 500, 2500);所以将上述示例代码按照连接ESP32 S3开发板和180度舵机优化后如下:

#include <ESP32Servo.h>

Servo myservo;  // create servo object to control a servo
// 16 servo objects can be created on the ESP32

int pos = 0;  // variable to store the servo position
int servoPin = 1;

void setup() {
  // Allow allocation of all timers
  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);
  myservo.setPeriodHertz(50);  // standard 50 hz servo
  myservo.attach(servoPin, 500, 2500);
}

void loop() {
  for (pos = 0; pos <= 180; pos += 1) {  // goes from 0 degrees to 180 degrees
    myservo.write(pos);  // tell servo to go to position in variable 'pos'
    delay(15);           // waits 15ms for the servo to reach the position
  }
  delay(3000);
  for (pos = 180; pos >= 0; pos -= 1) {  // goes from 180 degrees to 0 degrees
    myservo.write(pos);                  // tell servo to go to position in variable 'pos'
    delay(15);                           // waits 15ms for the servo to reach the position
  }
  delay(3000);
}

接下来我们就可以将代码编译上传到ESP32 S3开发板上,同时需要在1号引脚上连接一个舵机,然后就可以来进行测试了,设备连接如下图所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第13张
将舵机接到ESP32S3开发板上

接下来方便我们查看转动到角度是否准确,为完整的180度范围,所以我从网上搜到一张360度刻度盘图片,打印出来后贴在舵机上,这样就可以实时看到转动角度了,图片如下有需要的也可以下载下来打印测试:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第14张
360度刻度盘图片

接下来就可以上传代码,查看转动效果,如下图所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第15张
测试180度舵机转动角度

经过实际测试发现,转动到180度时候,角度会稍微多转动约5度左右,在0度附近还算比较准确,那为什么不是完美的180度转动呢?是不是PWM波形有问题呢?下面我们可以使用示波器来测一下波形看看。

0x04 示波器查看PWM波形

这里我使用的是编携型迷你示波器,我们舵机是连接到扩展板上1号引脚的,所以我们就可以将示波器探头接到ESP32 S3开发板上的1号引脚,同时也记得连接好地线,然后就可以来捕捉波形了,如下图所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第16张
将示波器探头连接好

下面就可以来查看舵机转动时,生成的PWM波形是否正确了,如果是波形异常,那舵机转动出现问题那就可以理解了,如果波形是正常的,那就说明是舵机本身出现问题,精度不够导致的,测试过程如下所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第17张
示波器实时监测舵机波形

我们测试时,发现在舵机转动到180度时总会稍微多转动约5度,那是不是2.5ms的脉宽有问题呢?我将示波器上波形截图如下:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第18张
2.5毫秒脉宽波形

上图可得知,时间档位500us,那就是横向每隔为500us(0.5ms),可以看到此时高电平持续时间正好为5格,那正好脉宽就是2.5ms,应该是比较完美的符合了舵机转动到180度所需的脉宽,那舵机转动到180度出现多转动的问题,应该就是舵机的精度问题了,与我们ESP32 S3开发板输出的PWM波形无关。同时我也将0度对应的0.5ms波形截图了,如下所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第19张
0.5毫秒脉宽波形

通过波形可以看到,我们ESP32 S3生成的0.5ms脉宽也是比较准确的,同时这里也是可以测出PWM频率的,由于我将时间档位调的太小方便查看波形就导致测不到频率,我们使用Auto功能,就可以测出PWM频率了,如下所示:

基于Arduino IDE环境ESP32 S3控制舵机转动 - 第20张
脉冲频率测试

从上图可以看到,示波器测量得到的脉冲频率为50.0Hz(图片左下角),这跟我们在代码中设置的舵机PWM脉冲频率是一样的,而且几乎没有误差,说明Arduino代码生成的PWM频率还是很准确的。

0x05 参考资料

[0].舵机和伺服电机的区别. http://www.jnky.com/news/494.html

[1].Labplus盛思维基百科舵机. http://wiki.labplus.cn/index.php?title=%E8%88%B5%E6%9C%BA

[2].伺服马达-维基百科. https://zh.wikipedia.org/wiki/%E4%BC%BA%E6%9C%8D%E9%A6%AC%E9%81%94

[3].舵机如何控制和检测角度. https://www.zhihu.com/zvideo/1418634683920228352

[4].PWM信号是如何控制舵机转动轴角度的. https://www.ixigua.com/6771050784323797508?logTag=f9c9d3780995d474b687

[5].PWM原理及其应用. https://zhuanlan.zhihu.com/p/379585884

[6].ESP32Servo函数文档手册. https://madhephaestus.github.io/ESP32Servo/annotated.html

[7].ESP32Servo的github仓库地址. https://github.com/madhephaestus/ESP32Servo

本文原创,作者:corvin_zhang,其版权均为ROS小课堂所有。
如需转载,请注明出处:https://www.corvin.cn/3331.html