Posts Tagged ‘控制’

神奇的电机们

前几天有几个小朋友给我发邮件,说是打算自己动手设计电路和机械结构来制作魔方机器人,其中提到了用步进电机来旋转魔方。

说来惭愧,电子电路和嵌入式我还真是门外汉,连步进电机都没玩过。看他们如火如荼的进展着,我也心痒痒的,迅速在淘宝上订了一个步进电机。这几天查了点资料,总算对步进电机有了一点了解。

想起给小爱选电机的时候,发现电机的种类真不少,常见的有:直流电机,舵机,步进电机和伺服电机。这是按原理分类的,如果考虑一些附件的话,又有减速电机,直线电机等等,看的我眼花缭乱。这里稍微总结一下,以供跟我一样的初学者参考。

直流电机
小时候经常拆东西的破坏大王们,最熟悉的就是这种电机了,刮胡刀玩具车里都有这个。只有两根线,通过电压来调节转速,把电池反过来接,电机就反向旋转。破坏力更强的一定也知道,里面一般会有电刷和几个转子,还有两块永磁铁,原理就不介绍了。
优点:控制简单,还没学物理的时候就知道怎么玩了
缺点:不能精确的控制旋转角度和速度

舵机
现在终于明白这种电机为什么叫舵机了。大海航行靠舵手,舵机一般用来控制角度,所以经常用来航模和船模上。这种电机内部其实也是普通的直流电机,区别在于加上了一个小电路,用一个电位器(模拟型舵机)来读取电机的当前转角,并不断的调节直流电机两端的电压来让这个转角维持在设定值。一般来说,舵机都会带一个齿轮箱,用来提高精度和扭力。
优点:可以精确控制角度
缺点:角度范围有限,一般小于180度,不能连续旋转,不能设置速度

步进电机
从名字就可以看出来,步进电机是“一步一步”旋转的。例如我买的电机步距角是1.8度,按照指定的时序发送脉冲信号,每收到一个脉冲电机就旋转1.8度。你想让电机转5度?抱歉,这是不行滴,换一个不同步距角的型号吧。另外,通过控制发脉冲的频率,就可以控制步进电机的转速了。哇,角度和速度都可以控制,这下貌似完美了。先别激动,因为步进电机是一个开环系统(简单的说就是无反馈),很可能会发生失步或者越步的现象。当脉冲发的太快或者负载太大的时候,电机还没反应过来,下一个脉冲就到了,这就失步了;当电机带动的转子惯性很大时,脉冲停止后,转子可能还会继续转动,这就是越步了。
优点:角度和速度都可以控制
缺点:失步或越步

伺服电机
完美的电机终于出现了,给步进电机加上伺服电路,检测它是否转到了指定的角度,这就是伺服电机了。一般是靠电机屁股上的一种编码器来读取角度,这玩意儿据说非常精确,当然,这个东西如果价格再便宜点就真的完美了。
优点:角度和速度都可以精确控制
缺点:价格高,控制最复杂
值得一提的是,乐高的NXT系列,用的就是伺服电机,加上开发API接口后,控制非常方便!

对于以上几种电机,如果想用程序控制的话,都需要加上控制器和驱动器。其中控制器一般可以用单片机,相当于发送时序指令给电机。对于小功率的电机,其实用控制器就可以带动了。但是对于功率较大的电机,就需要加上驱动器。驱动器可以简单理解为功率放大器,接受控制器的信号,然后输出大电流给电机。

另外两个概念:减速电机是通过齿轮箱或蜗杆给电机减速,用来提高扭矩;直线电机是通过丝杠,把电机的转动变成丝杠的平动,一般用来做推杆。

倒腾了一晚上,终于用我的Arduino开发板实现步进电机正转和反转了。之前在小爱成长记里写过直流电机舵机的控制,周末整理一下,把步进电机的控制也加上。至于伺服电机,等以后用的上的时候再研究吧 :)

解魔方的机器人攻略25 – 解魔方

现在我们的工作已经接近尾声了,看看怎么把电脑变成一个NXT的蓝牙遥控器。这个部分大家其实可以自由发挥,我设计的数据通讯流程是这样的:

1,蓝牙连接成功
2,NXT扫描魔方,发送6个面,每个面9块共54组颜色数据到电脑
3,NXT发送一个字节(0xFF)到电脑,表示颜色读取完毕
4,电脑开始计算解法,得到解魔方的步骤,一共N步
5,电脑发送一个字节N到NXT
6,NXT进行从1到N的循环,每次发送一个字节n到电脑,请求第n步操作
7,电脑发送第n步操作给NXT
8,NXT执行完全部N个操作,发送一个字节(0xFE)到电脑,通知解魔方完成
9,电脑清空步骤和颜色数组,准备迎接下一次任务
10,按下Escape按钮,NXT发送三个(0XFF)给电脑,关闭蓝牙连接并退出

同学们松了一口气,核心算法都搞定了,这点任务算啥,准备十分钟交卷吧。。。。

且慢,我们得到的步骤是类似F1 U2 F2 D3 L2 D1 F1 U3 L2 D1这样的序列,但是萝卜头永远只能旋转最下面一层,怎么办?

这个也简单,把相应的面翻到底面就好了,毕竟萝卜头的胳膊也不是个摆设。

问题又来了,第一步F1时,把F变成了底面;这时候魔方已经经过了某些翻转操作,那么第二步U2该转哪一面呢?这下有点麻烦了…

如果每次都还原到原来的位置,会增加非常多的步骤。

最好的方法是每次都通过最近的路径把需要旋转的面翻到最底层,然后旋转它。

所以我们需要保存一个坐标系,在翻转魔方的时候,让这个坐标系永远跟魔方的真实位置同步,请看CenterColor类,用来记录六个面的中心位置:

public class CubeCenter
{
    public string[] CenterColor = new string[6] { "U", "R", "D", "L", "F", "B" };

    public void RotateBottom(bool colockwise)
    {
        if (colockwise)
        {
            string n = CenterColor[5];
            CenterColor[5] = CenterColor[1];
            CenterColor[1] = CenterColor[4];
            CenterColor[4] = CenterColor[3];
            CenterColor[3] = n;
        }
        else
        {
            string n = CenterColor[5];
            CenterColor[5] = CenterColor[3];
            CenterColor[3] = CenterColor[4];
            CenterColor[4] = CenterColor[1];
            CenterColor[1] = n;
        }
    }

    public void RotatePaw()
    {
        //Only can move forward
        string n = CenterColor[0];
        CenterColor[0] = CenterColor[3];
        CenterColor[3] = CenterColor[2];
        CenterColor[2] = CenterColor[1];
        CenterColor[1] = n;
    }

    public int FindCenter(string position)
    {
        int center = -1;
        for (int i = 0; i < 6; i++)
        {
            if (CenterColor[i] == position) center = i;
        }
        return center;
    }
}

有了这个参考坐标系,我们就可以把URDLFB表示法的解魔方步骤,转化成萝卜头能识别的PBS表示法。嗯,不用去Google搜索,这个PBS表示法是我发明的(也就是瞎编的^_^ ),它表示
P: Paw 爪子翻动一次
B:RotateBottom 从底面旋转魔方,后面需要接一个1~3的数字
S:RotateBottomSide 旋转魔方的底面,跟B的区别是这时候爪子抓住上两层,然后旋转底面

下面这段代码描述了从URDLFB操作到PBS操作的转换:

int findSidePosition = CenterStatus.FindCenter(targetSide);

//Rotate to corrent bottom
switch (findSidePosition)
{
    case 2:
        //Do Nothing
        break;
    case 1:
        CenterStatus.RotatePaw();
        Steps.Add(new MoveStep(MoveType.RotatePaw, 0));
        break;
    case 0:
        CenterStatus.RotatePaw();
        Steps.Add(new MoveStep(MoveType.RotatePaw, 0));
        CenterStatus.RotatePaw();
        Steps.Add(new MoveStep(MoveType.RotatePaw, 0));
        break;
    case 3:
        CenterStatus.RotateBottom(true);
        CenterStatus.RotateBottom(true);
        Steps.Add(new MoveStep(MoveType.RotateBottom, 2));
        CenterStatus.RotatePaw();
        Steps.Add(new MoveStep(MoveType.RotatePaw, 0));
        break;
    case 4:
        CenterStatus.RotateBottom(true);
        Steps.Add(new MoveStep(MoveType.RotateBottom, 1));
        CenterStatus.RotatePaw();
        Steps.Add(new MoveStep(MoveType.RotatePaw, 0));
        break;
    case 5:
        CenterStatus.RotateBottom(false);
        Steps.Add(new MoveStep(MoveType.RotateBottom, 3));
        CenterStatus.RotatePaw();
        Steps.Add(new MoveStep(MoveType.RotatePaw, 0));
        break;
}
Steps.Add(new MoveStep(MoveType.RotateBottomSide, Convert.ToInt32(rotateCount)));
Steps[Steps.Count - 1].OrginStep = currentStep;

下面是一个PBS表示法的步骤示例,基本上一个URDLFB旋转操作,会对应1~3个PBS操作:
P B3 P S2 B1 P S1

为了减少发送的数据量,我们用下面的规则来发送PBS表示法的步骤,每个步骤用一个字节来描述:

switch (MoveType)
{
    case MoveType.RotatePaw:
        return (byte)10;
    case MoveType.RotateBottom:
        return (byte)(20 + Count);
    case MoveType.RotateBottomSide:
        return (byte)(30 + Count);
    default:
        return (byte)0;
}

在NXT上对应的解析操作是:

//Get result
int step = BlueTooth.ReadBytes()[0];
if(step==10)
{
	//Rotate paw
	Robot.RotatePaw();
}
else if(step>=20 && step<30)
{
	//Rotate Bottom
	int count = step - 20;
	if(count == 3) count = -1;
	Robot.RotateBottom(count);
}
else if(step>=30 && step<40)
{
	//Rotate Bottom Side
	int count = step - 30;
	if(count == 3) count = -1;
	Robot.RotateBottomSide(count);
}

开始编译工程,佛祖&上帝&安拉&比尔盖子同时保佑,程序编译通过了。如果运气好的话,蓝牙连接成功以后,萝卜头就可以顺利解魔方了。

好了,所有的代码都介绍完了,之后还会介绍一些收尾和改进的工作,主要包括:
1,用超声波测距传感器(就是那对眼睛)制作“开关”;
2,读色错误,卡住等情况的异常处理
3,语音提示,让萝卜头开口说话
4,暂停功能,帮助我们进行调试

Arduino开发板实验三:舵机控制

经常玩NXT的朋友肯定对NXT的电机印象深刻,使用非常方便。转速,角度和方向都可以随意控制。

在计划制作小爱的时候,我也一直希望能找到这样的电机。查了一些资料,觉得比较容易控制的有舵机和步进电机。舵机的主要玩家是船模和航模的爱好者们。大海航行靠舵手,舵机就是用来控制舵角的,它的最大特点是可以方便的控制角度,它的限制是舵角一般不超过180度。步进电机就强多了,既可以控制角度,还可以连续旋转,它的问题是输出就是一个光轴,需要加工配套的零件才能使用,另外价格好像会更贵一点。

我计划用舵机来实现小爱的机械臂,最主要的考虑原因是舵机自带很多舵角之类的配件,比较容易安装,毕竟机械加工是制作过程中最麻烦的部分。另外,一般机械臂的关节转角也不需要大于180度,用舵机就足够了。下面是使用Arduino开发板控制舵机的一个小实验。

先抄一段说明:舵机,又称伺服马达,是一种具有闭环控制系统的机电结构。舵机主要是由外壳、电路板、无核心马达、齿轮与位置检测器所构成。其工作原理是由控制器发出PWM(脉冲宽度调制)信号给舵机,经电路板上的IC处理后计算出转动方向,再驱动无核心马达转动,透过减速齿轮将动力传至摆臂,同时由位置检测器(电位器)返回位置信号,判断是否已经到达设定位置,一般舵机只能旋转180度。

舵机结构图

舵机结构图

舵机有3根线,棕色为地,红色为电源正,橙色为信号线,但不同牌子的舵机,线的颜色可能不同,请大家注意。

舵机的转动的角度是通过调节PWM(脉冲宽度调制)信号的占空比来实现的,标准PWM(脉冲宽度调制)信号的周期固定为20ms(50Hz),理论上脉宽分布应在1ms到2ms之间,但是,事实上脉宽可由0.5ms到2.5ms之间,脉宽和舵机的转角0°~180°相对应。有一点值得注意的地方,由于舵机牌子不同,对于同一信号,不同牌子的舵机旋转的角度也会有所不同。

舵机角度和占空比的关系

舵机角度和占空比的关系

 下面这个小实验的目标是用电位器控制舵机的角度,正好在前一个实验里,电位器都还没有拆掉。需要特别注意的是供电部分,舵机转动时电流会比较大,Arduino上的电源芯片可能会因过流保护到发热而损坏,电源需要接到外部供电,切不可使用USB供电。
舵机的棕色线接GND,红色线接VIN(我猜这个是直接连到外接电源的正极),黄色是数据线,接在PWM的7号管脚。电位器的连接稍微有点变换,因为管脚们施展不开,我把正极连在3.3V的接口上,这样模拟输入的范围就变成了0到660左右。前面说了,舵机分别用0.5ms到2.5ms之间的脉冲来对应0到180度左右的角度,我们可以用pulsewidth=(angle*11)+500这样的公式,把0到180度的转角映射到500到2480的脉冲时间。

接线图

接线图

下面看代码:

int readPin = 6;   //用来连接电位器
int servopin = 7;    //定义舵机接口数字接口7

void servopulse(int angle)//定义一个脉冲函数
{
  int pulsewidth=(angle*11)+500;  //将角度转化为500-2480的脉宽值
  digitalWrite(servopin,HIGH);    //将舵机接口电平至高
  delayMicroseconds(pulsewidth);  //延时脉宽值的微秒数
  digitalWrite(servopin,LOW);     //将舵机接口电平至低
  delayMicroseconds(20000-pulsewidth);
}

void setup()
{
   pinMode(servopin,OUTPUT);//设定舵机接口为输出接口
}

void loop()
{
  //读取电位器(传感器)的读数,接到3.3V,值范围从0到660左右
  int readValue = analogRead(readPin);
  //把值的范围映射到0到165左右
  int angle = readValue / 4;
  //发送50个脉冲
  for(int i=0;i<50;i++)
  {
     //引用脉冲函数
     servopulse(angle);
  }
}

实验结果:当旋转电位器的时候,舵机的角度随之改变。不过最终转角并没有达到180度,在某些范围内,电位器旋转时,舵机没有转动。网上舵机的说明也提到了这点,识别的角度范围是有限的。具体的有效角度范围,我还没有测量,等将来开始制作的时候再说。

Arduino开发板实验二:模拟输入和输出(用电位器和开关控制直流电机)

在上一个Arduino小实验里,我们尝试了用开发板来读写数字信号(0和1),貌似非常简单,难度系数跟吃苹果差不多。昨天淘宝的直流电机控制板终于送到了,接下来这个实验准备测试读写模拟信号。这个实验难度系数稍大一点,达到了吃香蕉的程度,搞不定的同学请去动物园请教猴子兄弟(开个玩笑,但是Arduino的确非常好开发)

我计划的实验目标是:
1,使用电位器控制直流电机的转速
2,使用开关控制直流电机的旋转方向

模拟输入

查了下资料,许多单片机的管脚电压都是+5V或0V,分别对应1和0。而机器人面对的自然界却没有这么泾渭分明,例如大气温度,到墙壁的距离,声音的强度等等,这些值就是模拟值。Arduino开发板上,标记了“Analog In”的16个管脚,就是用来测量模拟值输入的。这些输入电压的范围是0~+5V,开发板会把它映射到0~1023的整数。从这个数值范围,我们可以估算出Arduino能识别的电压精度大概是5mV,小于这个范围的变换无法识别。

电位器

这个东西是从邻居小朋友那里骗来的。在我读高中的时候,好像把这个东西叫滑动变阻器,个头很大。现在都是小小的旋转电位器了,原理图应该是一样的:

电位器

电位器

接线方式是两端分别接GND和+5V,中间随意接在一个Analog In的管脚上。我选择了一个大吉大利的6号。

电位器接线

电位器接线

读取模拟电压值的函数为:

int readValue = analogRead(readPin);

用端口监视器做个分解实验,使用Serial.println命令把readValue显示在电脑上,和想象中的结果完全一样。端口读写部分准备做为下一个实验主题,这里就不细说了:

端口监视

端口监视

PWM输出

作为一个外行,我之前曾经想过怎么让单片机输出一个模拟值。觉得必须用10个管脚,对应的二进制从0~1023,然后找一个数模转换的东西变成模拟电压值。结果一看资料发现自己太圭了,原来有一种叫做占空比的东西,只用一个管脚就可以输出0~255的数值。从这里也了解到嵌入式常用的一个叫“时序”的东西,以后应该会经常遇到,这种方式可以用很少的管脚实现非常复杂的功能(估计业内人士又要鄙夷的飘过了)。看下面的图解,从Arduino官网顺来的:

PWM模拟输出

PWM模拟输出

板子上标注了“PWM”的区域就是管脚均可以用于这种输出。使用的函数是:

analogWrite(pin, value);

注意value值的范围是0~255。

直流电机控制板

这个控制板的功能是把PWM的输出,转换成真正的模拟电压值,从而控制直流电机旋转。电机的种类很多,还有舵机,步进电机等等,控制方式都不一样,那些以后再试。先看一下这个板子的介绍,也是一个顺手牵来的图:

直流电机控制板

直流电机控制板

这个图里信息太多了,咱们捞干的讲几个:
1,左右两边是对称的,可以控制两个直流电机,咱们下面只看左边
2,绿色的“直流电机A接口”,用于接电机的两根电源线
3,直流电机A信号输入接口,一共有3个脚,分别是I1,I2和EA。其中I1和I2是数字接口,用于控制开关和方向,EA是模拟接口,用于控制转速
例如:I1=1,I2=0顺时针转,I1=0,I2=1就逆时针转,I1=I2的时候,停止转动。EA是0~255的PWM值,对应从小到大的转速
4,最下面的VMS接电源正极,GND接地,边上还有一个+5V,不用管它,本来是由它给逻辑电路供电的,但是默认情况下,是通过DUAL那个跳线帽由VMS搭车送电。

看看最终的接线图,专业人士一般用面包板之类的东西实验,我就直接把铜丝拧上了,请大家不要效仿:

接线图

接线图

接下来是写代码,一共也没几行,大家看注释吧:
注意:我发现Analog输入区编号是0~15,PWM输出区也有0~13,为了验证这两类管脚编号会不会冲突,我特地把readPin和speedPin都设置成了大吉大利的6号。事实证明两者不冲突。

int readPin = 6;    //用来连接电位器
int buttonPin = 38; //用来连接开关,这次用了个吉利的端口号
int i1Pin = 31;     //连接电机驱动板的I1接口
int i2Pin = 30;     //连接电机驱动板的I2接口
int speedPin = 6;   //连接电机驱动板的EA接口

void setup()
{
  pinMode(buttonPin, INPUT);  //开关用于输入
  pinMode(i1Pin, OUTPUT);     //I1和I2都是数字信号
  pinMode(i2Pin, OUTPUT);     //通过设置I1和I2来控制电机旋转方向
  pinMode(speedPin, OUTPUT);  //按占空比方式输出的模拟信号
  digitalWrite(buttonPin, HIGH);  //设置上拉电阻
}

void loop()
{
  //读取按钮状态
  boolean buttonPressed = digitalRead(buttonPin);
  //设置转动方向,I1和I2值相反时,分别对应两种不同的转向;I1和I2值相同时停止转动
  digitalWrite(i1Pin, buttonPressed);
  digitalWrite(i2Pin, !buttonPressed);
  //读取电位器(传感器)的读数,值范围从0到1023
  int readValue = analogRead(readPin);
  //PMW的值范围是0~255
  readValue/=4;
  //设置转速
  analogWrite(speedPin, readValue);
  delay(500);
}

上传程序以后,旋转电位计,可以看到电机的转速不断变化着;掰一下开关,电机就会反转,实验到此顺利结束。