【经验】Arduino+舵机(连续转动与非连续转动、用库与不用库)经验总结

【经验】Arduino+舵机(连续转动与非连续转动、用库与不用库)经验总结

提示:本文主要讲经验,基础知识会稍有涉及,更深入的基础知识文末已经整理出了链接,大家可以自行研读。

看过了很多前辈写的文章,但始终感觉前辈们单独看的话教的有些零散,很多知识讲不全而且文章中有些不起眼的小错误,但是把所有前辈的文章综合来看,却又能成体系,所以我打算写这篇文章把前辈们的知识总结概括修正一下,本文尤其适合稍稍有些基础但是对操作的部分感觉比较不熟练,容易忘记的同学复习用。

一、舵机原理简述

舵机的控制信号为周期是20ms 的脉宽调制(PWM) 信号,其中脉冲宽度从0.5ms-2.5ms,相对应舵盘的位置为0- 180度,呈线性变化。也就是说,给它提供一定的脉宽,它的输出轴就会保持在一个相对应的角度上,无论外界转矩怎样改变,直到给它提供一个另外宽度的脉冲信号,它才会改变输出角度到新的对应的位置上。舵机内部有一个基准电路,产生周期20ms, 宽度1. 5ms的基准信号,有一个比较器,将外加信号与基准信号相比较,判断出方向和大小,从而产生电机的转动信号。输入信号脉冲宽度舵机输出轴转角(周期为20ms)

●PWM是用占空比不同的方波,来模拟“模拟输出”的一种方式。

●舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部分一般为0.5ms-2.5ms范围内的角度控制脉冲部分,总间隔为2ms。

●舵机的转动的角度是通过调节PWM(脉冲宽度调制)信号的占空比来实现的。需要使用Arduino上的PWM口控制(数字前带~的),本文使用的是10号引脚。以180度角度伺服为例,那么对应的控制关系是这样的:

脉宽效果(旋转到)
0.5ms0度
1.0ms45度
1.5ms90度
2.0ms135度
2.5ms180度

如果是270度舵机

脉宽效果(旋转到)
0.5ms0度
1.5ms135度
2.5ms270度

其他角度与脉宽对应同理。
还有一种连续转动舵机,同样由脉冲信号控制,脉冲宽度0.5ms-2.5ms(500-2500us)同一般舵机,没有固定旋转的角度。

脉宽效果
500us-1500us越小反向速度越快
1500us-2500us越大正向速度越快

说白了我们要通过Arduino的能发送PWM信号的引脚向舵机的信号线(一般是橙色线)发送PWM信号,这个信号携带了控制舵机旋转角度的信息。
那么哪些引脚可以发送PWM信号呢?以NANO为例,从以下两张图片我们可以清晰地看出那些有波浪线引出的引脚是PWM引脚(Pin)。
在这里插入图片描述

在这里插入图片描述

利用库函数驱动

舵机一般为三条线,红线接5V,除了红线和橙色线以外的线(棕色或黑色)接GND。

#include <Servo.h> // 声明调用Servo.h库 
#define SERVO_PIN 10//宏定义舵机控制引脚
Servo myservo;   //创建一个舵机类,命名为myservo
void setup()  
{  
  myservo.attach(SERVO_PIN);  
}  
void loop()  
{  
  myservo.write(90);  
  delay(1000);  
} 

#include <Servo.h>
#define SERVO_PIN 10 //10脚就是你的的舵机信号线一般是橙色线的链接引脚。
Servo myservo;
void setup()
{
myservo.attach(PIN_SERVO);
}
void loop()
{
myservo.write(90);
delay(1000);
}

被标记的内容是你想让舵机转起来之前必须要做的事。
而之后我们可以用write()直接键入角度,比如这里的90,也可以通过writeMicroseconds()键入脉宽,脉宽与角度有关,单位us。

相关函数:
1.servo类成员函数
attach() 设定舵机的接口,只有9或10接口可利用
write() 用于设定舵机旋转角度的语句,可设定的角度范围是0°到180°
writeMicroseconds()  用于设定舵机PWM的语句,直接用微秒作为参数
read() 用于读取舵机角度的语句,可理解为读取最后一条write()命令中的值
attach() 判断舵机参数是否已发送到舵机所在接口
detach() 使舵机与其接口分离,该接口(9或10)可继续被用作PWM接口

程序如下 ,这是源程序的link

#include <Servo.h>                // 声明调用Servo.h库
Servo myservo;                    //创建一个舵机类,命名为myservo
#define  SERVO_PIN  10            //宏定义舵机控制引脚
unsigned int PWM = 0;             //变量pwm用来存储舵机角度位置,PWM的500对应0度,2500对应舵机的最大角度
                                  //(180度舵机2500对应180度,270度舵机2500对应270度)
void singleServoControl(){
    for(PWM = 50; PWM <2450; PWM += 5){   //舵机从50状态转到2450,每次增加5  
        myservo.writeMicroseconds(PWM);   //给舵机写入PWM  
        delay(10);                        //延时10ms让舵机转到指定位置
    } 
    for(PWM = 2450; PWM>50; PWM-=5){     
        myservo.writeMicroseconds(PWM);    
        delay(10);                       
    }        
}                               
void setup(){ 
    //put your setup code here, to run once: 
    myservo.attach(SERVO_PIN);    // 将10引脚与声明的舵机对象连接起来
}  

void loop(){ 
    //put your main code here, to run repeatedly:
    singleServoControl();
} 

void singleServoControl()是原文作者自己写的函数,为了防止C语言基础差的同学看不懂,本文对此稍作简化,可以更直观一些看到writeMicroseconds()起的作用。

#include <Servo.h>  
#define SERVO_PIN 10
Servo myservo;  
void setup()  
{  
  myservo.attach(SERVO_PIN);  
}  
void loop()  
{  
  myservo.writeMicroseconds(1500);  
  delay(1000);  
} 

write()与writeMicroseconds()有各自擅长的应用场景,一般非连续转动的舵机可以write(),连续转动的舵机可以使用writeMicroseconds(),其中非连续转动大于180度的舵机也可以使用writeMicroseconds()直接输入脉宽,也可以使用map函数把角度比如从(0,270),360度舵机同理,映射到(0,180),然后write出这个角度。 map函数可以看下文,也可以直接跳到文末基础知识部分有个链接,直接点击即可。
写了个例程如下:

#include<Servo.h>
Servo myservo;
int angle;
int val=90;
void setup(){
  myservo.attach(10);
  Serial.begin(9600);
}
void loop(){
  angle=map(val,0,270,0,180);
  myservo.write(angle);
  Serial.print(angle,DEC);
  delay(10000);    
}

把90度从(0,270)映射到(0,180),从串口得到旋转角度为60。关于串口的知识我放在文末有一个链接,是笔者自己写的,欢迎围观~。
在这里插入图片描述

最后值得一提的是当你attach了你的PWM引脚后,并不需要将这个引脚设置成OUTPUT。

Arduino驱动舵机,不调用库函数

看到中文社区的一个大佬的帖子,想稍微讲一下。
链接: Arduino驱动舵机,不调用库函数.

int sp1=10;//定义舵机接口数字接口10
int pulsewidth;//定义脉宽变量
int val;
int val1;
int myangle1;

void setup()
{
  pinMode(sp1,OUTPUT);//设定舵机接口为输出接口
  //设置两组串口波特率
  Serial.begin(9600);
  delay(500);
  Serial.println("servu=o_seral_simple ready" ) ;
}

void loop()//将0到9的数转化为0到180角度,并让LED闪烁相应数的次数
{
  val=Serial.read();//读取串行端口的值

  if(val>'0'&&val<='9')
  {
    val1=val-'0';//将特征量转化为数值变量
    val1=map(val1,0,9,0,180);//将角度转化为500-2480的脉宽值
    Serial.print("moving servo to ");
    Serial.print(val1,DEC);
    Serial.println();
    for(int i=0;i<=50;i++)//给予舵机足够的时间让它转到指定角度
    {
      servopulse(sp1,val1);//引用脉冲函数
    }
  }
 //下面是servopulse函数部分(此函数意思:也就是說每次都是0.5ms高電平 1.98ms低電平 然後再0.52ms低電平 17ms延時也是低電平)
void servopulse(int sp1,int val1)//定义一个脉冲函数
{
  myangle1=map(val1,0,180,500,2480);
  digitalWrite(sp1,HIGH);//将舵机接口电平至高
  delayMicroseconds(myangle1);//延时脉宽值的微秒数
  digitalWrite(sp1,LOW);//将舵机接口电平至低
  delay(20-val1/1000);
}
//servopulse函数部分到此结束
}

map函数的本质是赋值,属于某个区间的A变量等比例转化至另一个区间后赋给B,比如某值在A区间的1/2处,用map映射到B区间后就是位于B区间的1/2处的值。详解在文末链接。
val1=val-‘0’ val是你向串口输入的值但这个数值被Aduino 识别为这个数值对应的ASCII码,如你输入9实际从串口打印出来的值是39(‘9’),而‘0’等于30,用‘9’-‘0’我们得到int 9从而把‘9’转化成9,这就是源程序中说的将特征量转化为数值变量,通过这种方式使val1是一个位于[0,9]的数字。关于串口大家可以看我放在文末的串口链接。
后来这个val1被从[0,9]映射到[0,180],用于从串口打印出角度方便查看。再后来val1从[0,180]映射到[500,2480],很明显这组区间代表着PWM的脉宽单位是us,delayMicroseconds(myangle1)控制着PWM信号中高电平时间,val1/1000把val1的单位转化成ms后可以用放在delay()中,用整个周期20ms减去它作为低电平的时间,至此完成了整个PWM信号的内容,配合串口内容我们实现由上位机通过串口控制舵机。

使用PCA9685模块对舵机控制

16路舵机驱动板示例 需要的库文件也可根据这个链接的内容安装,本文不再赘述。如果你已经安装了库文件,直接阅读下文即可。
PCA9685

电压:舵机供电5-7v,接受高一点的电压。
大多数的舵机设计电压都是在5~6V,尤其在多个舵机同时运行时,跟需要有大功率的电源供电。如果直接使用Arduino
5V引脚直接为舵机供电,会出现一些难以预测的问题,所以我们建议你能有个合适的外部电源为驱动板供电。

逻辑电路电压:3-5V

通信接口:使用I2C通信,就是SCL、SDA引脚

7位的I2C地址为:0x40 +A5:A0,A5到A0如果不做任何处理的话是0,想要把哪一位置1就把那个引脚焊到一起。另外用i2cdetect检测出还有一个0x70地址一直存在,这是一个通用地址,可以给所有从机下达指令。

OE反使能脚:这个引脚低电平使能,不接的话模块内部默认已经接地使能了,所以正常使用可以不接。

工作频率:40-1000HZ

注意:以下部分对原文也就是上边这个链接的内容做出一些修正!

舵机的控制一般需要一个20ms的时基脉冲,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分。以180度角度舵机为例,那么对应的控制关系是这样的:
0.5ms————–0度;
1.0ms————45度;
1.5ms————90度;
2.0ms———–135度;
2.5ms———–180度;

PCA9685可以设置更新频率,时基脉冲周期20ms相当于50HZ更新频率。PCA9685采用12位的寄存器来控制PWM占比,对于0.5ms,相当于0.5/20*4096=102的寄存器值。以此类推如下:

0.5ms————–0度:0.5/20 * 4096 = 102
1.0ms————45度:1/20 * 4096 = 204
1.5ms————90度:1.5/20 * 4096 = 306
2.0ms———–135度:2/20 * 4096 = 408
2.5ms———–180度:2.5/20*4096 =510

上文中如:0.5/20、 1/20等均为占空比值,可自行百度。

但是实际使用的时候,还是有偏差,除了0度以及180度,其他需要乘以0.915系数。最后的寄存器值如下:
0.5ms————–0度:0.5/20 * 4096 = 102
1.0ms————45度:1/20 * 4096 = 204 * 0.915 = 187
1.5ms————90度:1.5/20 * 4096 = 306 * 0.915 = 280
2.0ms———–135度:2/20 * 4096 = 408 * 0.915 = 373
2.5ms———–180度:2.5/20 * 4096 =510
控制程序使用串口通讯接受指令,实现0/45/90/135/180度,总共5种角度的控制,我们也可以自己根据需要计算其他角度的寄存器值,在程序开头定义出来。

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

// 默认地址 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVO_0  102 
#define SERVO_45  187 
#define SERVO_90  280 
#define SERVO_135  373 
#define SERVO_180  510 

// our servo # counter
uint8_t servonum = 0;
char comchar;

void setup() {
  Serial.begin(9600);
  Serial.println("8 channel Servo test!");

  pwm.begin();
  pwm.setPWMFreq(50);  // 50HZ更新频率,相当于20ms的周期

  delay(10);
}

void loop() {
    while(Serial.available()>0){
    comchar = Serial.read();//读串口第一个字节
    switch(comchar)
    {
      case '0':
      pwm.setPWM(0, 0, SERVO_0);
      Serial.write(comchar);
      break;
      case '1':
      pwm.setPWM(0, 0, SERVO_45);
      Serial.write(comchar);
      break;
      case '2':
      pwm.setPWM(0, 0, SERVO_90);
      Serial.write(comchar);
      break;
      case '3':
      pwm.setPWM(0, 0, SERVO_135);
      Serial.write(comchar);
      break;       
      case '4':
      pwm.setPWM(0, 0, SERVO_180);
      Serial.write(comchar);
      break;
      default:    //匹配失败
      Serial.write(comchar);
      break;                  
    }
  }
}

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

// 默认地址 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVO_0 102
#define SERVO_45 187
#define SERVO_90 280
#define SERVO_135 373
#define SERVO_180 510

// our servo # counter
uint8_t servonum = 0;
char comchar;

void setup() {
Serial.begin(9600);
Serial.println(“8 channel Servo test!”);

pwm.begin();
pwm.setPWMFreq(50); // 50HZ更新频率,相当于20ms的周期

delay(10);
}

void loop() {
while(Serial.available()>0){
comchar= Serial.read();//读串口第一个字节
switch(comchar)
{
case ‘0’:
pwm.setPWM(0, 0, SERVO_0);
Serial.write(comchar);
break;
}
}
}
标记的部分是需要我们记忆的。
需要向Arduino发送值
需要向Arduino发送值
本程序用了一个经典的while(Serial.available()>0)和Serial.read()组合,这说明我们必须要想Arduino的串口里输入值,由后边的switch case结构我们可以看出程序的用意是要我们向串口输入0到4,这5个值又分别控制这接入驱动板0号位置的舵机转过的角度。pwm.setPWM(0, 0, SERVO_0)中第一个0是指驱动板上的舵机接口位置,而SERVO_0是前文中提到的寄存器值,本程序中已经被 #define SERVO_0 102赋值为102,我们也可以直接输入数字102或者其他你已经算好的数值,计算方法前文已经提到。至于中间的0,本文不再介绍,想了解的同学可以前往该链接16路PWM舵机驱动板(PCA 9685) + Arduino
值得一提的还有驱动板供电和舵机供电都在驱动板上,这两个是独立的,也就是你需要为驱动板接入2个电源,不给舵机供电舵机是绝对不会转的,笔者刚拿到这个PCA9685时以为给板供电的同时就也给舵机供电了,实际上不是这样的。本程序接线如该表:

驱动板Arduino电源
SDAA4
SCLA5
VCC5V
V+ (板上总共有两个,这个V+是螺丝固定的V+,用来给舵机供电)5-7V
GNDGNDGND

为什么要选A4、A5呢?如下图,A4、A5分别又代表SDA与SCL,用于和其他组件进行I2C通信。
在这里插入图片描述

基础知识:

上面有出现uint8_t uint16_t,那它们是什么?其实很简单:

1字节 uint8_t //无符号整型
2字节 uint16_t

map函数简介
串口知识
Arduino delayMicroseconds()函数
delayMicroseconds()函数接受单个整数(或数字)参数。此数字表示时间,以微秒为单位。一毫秒内有一千微秒,一秒内有一百万微秒。
目前,可以产生精确延迟的最大值是16383。这可能会在未来的Arduino版本中改变。对于超过几千微秒的延迟,应该使用delay()函数。
delayMicroseconds()函数语法
delayMicroseconds (us) ;
其中, us 是要暂停的微秒数(无符号整型)。
也就是一般延迟时间很短的时候我们常常使用delayMicroseconds (us)

下边这个框里的内容可以跳过,放在这里只是因为笔者觉得有些值得琢磨的地方。。。想看的话粘贴到别的地方就看不到删除线了。。。

(1)舵机的追随特性
假设现在舵机稳定在A点,这时候CPU发出一个PWM信号,舵机全速由A点转向B点,在这个过程中需要一段时间,舵机才能运动到B点。
保持时间为Tw 当Tw≥△T时,舵机能够到达目标,并有剩余时间; 当Tw≤△T时,舵机不能到达目标;
理论上:当Tw=△T时,系统最连贯,而且舵机运动的最快。 实际过程中w不尽相同,连贯运动时的极限△T比较难以计算出来。
当PWM信号以最小变化量即(1DIV=8us)依次变化时,舵机的分辨率最高,但是速度会减慢。
PWM信号控制精度制定 如果采用的是 8 位单片机,其数据分辨率为256,那么经过舵机极限参数实验,得到应该将其划分为 250 份。 那么
0.5mS—2.5Ms 的宽度为 2mS = 2000uS。2000uS÷250=8uS,则:PWM 的控制精度为 8us。我们可以以 8uS 为单位递增控制舵机转动与定位。 舵机可以转动 185 度,那么185 度÷250=0.74 度,则:舵机的控制精度为 0.74 度。
1 DIV = 8us ; 250DIV=2ms时基寄存器内的数值为:(#01H)01 ----(#0FAH)250。 共 185 度,分为 250 个位置,每个位置叫 1DIV。则:185÷250 = 0.74 度 / DIV PWM 上升沿函数: 0.5ms + N×DIV 0us ≤ N×DIV ≤ 2ms 0.5ms ≤ 0.5ms+N×DIV ≤ 2.5ms

本文参考了许多文章,文中均已指明出处,再次感谢优秀的前辈们。

  • 61
    点赞
  • 297
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值