Posts Tagged ‘arduino’

【小制作】DIY电动滑板车

去年谷歌眼镜刚出的时候,我就心痒痒的:“呦,柯南的眼镜出现了,这个太高端搞不定,要不弄个电滑板玩玩?”

真正开始动手是一个月前的事情,一直想等四轴搞定了再玩这个,结果一拖就是大半年。最后想想,还是follow my heart,想玩一个东西的时候还是赶紧出手,省的心劲儿过了就没动力了。这种劳民伤财的事情,一般要跟圈圈妈合计一下,结果圈圈妈说“靠,这么好玩的东西,赶紧做一个去!”

于是就开始选各种轮子、电机、电池等材料,因为工作确实比较累,所以依然是每天半小时的方式进行蚂蚁搬家式的制作。贴几张照片和视频,公园里试运行:

公园里试运行

侧面图

侧面图

底部是改装的重点对象:

滑板车底部
最终做的还挺结实的,经验证160斤的同学站上去没有问题。具体实现是通过Android手机通过蓝牙,把信号发给Arduino板子,最后通过调速器控制电机转速。
虽然原理非常简单,但是制作起来可是没少花功夫。比如那些支架,用锉刀挫的胳膊都粗了……

视频:

微型四轴又失败了

花了两周的时间,磨磨蹭蹭才做好了一个小四轴。这次连平衡的问题都不用考虑了,因为手工制作的原因,重量达到46克,4个空心杯小电机根本就带不起来 :(

体重超标的微型四轴飞行器

体重超标的微型四轴飞行器

制作前把各种芯片和电机电池散件称了一下,大概36克左右,还抱有一线希望。全部焊好后重量增加了10克,在代码中把4个电机都设置成100%的动力,试了一下。结果是在平地上晃晃悠悠,根本起不来。

这个小飞机里面,比较大的三个模块分别是arduino nano、蓝牙、9轴传感器(陀螺仪+加速度+气压计+指南针)其中每块小板子貌似都是几克重,不知道该从何减肥了。想来想去,只有一个3.7V到5V的增压电路,和一个用于低压报警的蜂鸣器,这两个也许是可以去掉的。但也只有5克左右,效果有限,带来的问题是各种芯片在低于5V的电压下工作,不知道能否稳定读数,另外电池可能用到完全没电,很容易损坏。如果一开始的时候做成一体的PCB板,也许能把体重控制到30克左右,这样才有希望飞起来。

焊接依然是我的弱项,不会画PCB很是伤不起啊,从背面看各种凌乱的飞线。

各种凌乱的飞线

各种凌乱的飞线

哎呀,真是折磨人啊,快坚持不下去了……

又一次悲剧的试飞

在闲置了一个多月之后,今天又进行了一次试飞。首先说说最近的修改:

1. 把电机控制的频率改到了400Hz,之前是50Hz;
2. 把螺丝固定的Arduino板子改成4个弹簧支撑,用来减少电机高频振动对传感器的影响;
3. 顺便把45度安装的X型改成了垂直安装的十型,作用不大,节省一点代码和减少可能出错的环节;
4. 尝试增加一个自动调节平衡位置的代码;(好几个参数需要调节,都调准了才能飞稳)
5. 加了一个安全的控制,如果飞行器倾角超过45度,则所有电机同时停止;

传感器改用弹簧连接

传感器改用弹簧连接

用手握的方式测试看来,目前四轴飞行器的姿态判断还算可以。基本上晃动它的时候,能感觉到电机速度的变化以及带来的反向阻力。
所以目前的调试主要在PID参数的调节和XY方向的偏移量调节。这个偏移量,是因为电机和飞行器不对称带来的误差。简单的说,假如飞行器已经处于平稳飞行的状态,这时候计算得到的左右两侧的螺旋桨转速应该相同,但是由于飞机本身的不对称,这时候反而会发生倾斜,所以需要给它设置一个平衡的偏移量。

这几天跟wnq同学请教了一下,据他介绍kk飞控仅用陀螺仪就可以上天,至于陀螺仪长期积分带来的漂移误差,可以由“飞行员”通过遥控器手动调节。我之前一直调试的目标是让飞行器实现平衡自稳,这样说来可能有点儿冒进了。wnq提醒我适当的斜飞是可以通过手动调节修正的,这样可以不用太纠结在完美的水平悬停上

另外最近的新朋友darkorigin也提醒了“近地效应”,因为贴近地面的时候,各种气流互相干扰,反而不如飞上天以后稳定。

下一阶段的一些想法和目标:
1. 暂时放弃手机控制,改用遥控器和接收器进行控制,主要优势在于控制距离、手感和便利性;
2. 增加运动的控制,就是在倾斜状态下手动调节平衡;
3. 进行电子指南针的一组小实验,因为我的遥控经验不足,争取先实现“无头模式”避免炸鸡;
4. 组装一台wnq同学的飞控练练手;
5. 自动寻常平衡点的算法需要再多测试和验证。

最后附上今天悲催的试飞视频。今天有点小风,犹豫了一下还是去阳台试飞了。前40秒使用的PID参数看上去还不算太离谱,之后试着调了下参数,结果振动失控了,摔碎若干小配件……

金灿灿的4WD小车

前几天机器人基地的Arduino 4WD小车送到了。打开一看,哇塞,金灿灿的,心情顿时像捡了一捆钱似的亮堂了起来,哈哈。

Arduino小车

趁圈圈被带出去遛弯的时候,我把它组装了起来。这个小车是镀金铝板的车身,质感非常好,而且安装很简单,大概用十分钟就足够了。安装前的装箱图:

小车的零件们

这个小车之所以叫Arduino小车,是因为它是针对Arduino的板子和扩展插件们设计的。上面打了很多窟窿,这可不是偷工减料,将来需要安装小板子的时候就知道好处了。

因为相关的家伙还没有配齐,所以这次只是把它简单组装了一下看看效果,有兴趣的同学可以去店家那里看看介绍,说的还比较详细。地址在这里:http://item.taobao.com/item.htm?id=9504307833

虽然还没有开始使用,但是简单构思了一下使用方式。其实我个人觉得4驱的小车并不见得比2驱的好控制。因为4个轮子需要同时控制才能和谐运动,如果每个电机都单独控制的话,会增加控制的难度。比较偷懒的办法是把同一侧的两个轮子并联到同一个电源,这样转弯和前进用一个L298N的板子就够了。这样相当于还是左右两组动力,但是同样两个电机接上相同的电压,转速也不见得完全相同,所以这样的动力源有点冗余。不过看RobotBase也有2WD的小车卖,可能是针对不同的用户考虑的吧。

装完小车,回想起我去年的小爱计划。于是把当时做的小车从床底下拖出来,发现打磨的钢轴都已经生锈了,还真有点伤感。没办法,在家里加工零件太难,也太危险。对比一下这两个小车的尺寸吧,雪碧是参照物:

两个小车

两个小车

这个小车的轮子是滑板车的配件,万向轮是家居的配件,轴承和固定齿轮是麻将桌的配件,用了一个多月才拼拼凑凑起来,可以承重20公斤。之所以选择自己动手做,是因为我想把小爱做成一个跟真人小孩差不多大的机器人,而市面上基本都是小尺寸的底盘小车。在此呼吁各公司们也开发点大配件吧,我就不用在家里敲敲打打的了 :)

嗯,在这里给自己打打气,小爱计划只是暂停,不会就这么结束的。这个阶段就先玩玩小车好了,至于拿它做什么,先卖个关子。可以肯定是是会把手头的Android手机变成遥控器,准备用Arduino的蓝牙扩展卡连接。有个叫zxspace的同学已经用iphone遥控Android了,果粉们可以先去围观一下。

Arduino系列教程之 – PWM的秘密(下)

今天看了看之前的博客,赫然发现有个PWM的秘密(上),第二部分居然被我忘掉了,主要是这个部分实在不太好理解,可以认为是Arduino PWM的提高篇。说实话大部分兄弟平时应该是用不上的,不过可以先收起来,以后真遇到了能知道是怎么回事。

Atmega 168/328的时钟们
ATmega328P有三个时钟,Timer0,Timer1和Timer2。每个时钟都有两个比较寄存器,可以同时支持两路输出。其中比较寄存器用于控制PWM的占空比,具体的原理等会儿会介绍。大多数情况下,每个时钟的两路输出会有相同的频率,但是可以有不同的占空比(取决于那两个比较寄存器的设置)

每个时钟都有一个“预定标器”,它的作用是设置timer的时钟周期,这个周期一般是有Arduino的系统时钟除以一个预设的因子来实现的。这个因子一般是1,8,64,256或1024这样的数值。Arduino的系统时钟周期是16MHz,所以这些Timer的频率就是系统时钟除以这个预设值的标定值。需要注意的是,Timer2的时钟标定值是独立的,而Timer0和Timer1使用的是相同的。

这些时钟都可以有多种不同的运行模式。常见的模式包括“快速PWM”和“相位修正PWM”,这两种PWM的定义也会在后面解释。这些时钟可以从0计数到255,也可以计数到某个指定的值。例如16位的Timer1就可以支持计数到16位(2个字节)。

除了比较寄存器外,还有一些其他的寄存器用来控制时钟。例如TCCRnA和TCCRnB就是用来设置时钟的计数位数。这些寄存器包含了很多位(bit),它们分别的作用如下:
脉冲生成模式控制位(WGM):用来设置时钟的模式
时钟选择位(CS):设置时钟的预定标器
输出模式控制位(COMnA和COMnB):使能/禁用/反相 输出A和输出B
输出比较器(OCRnA和OCRnB):当计数器等于这两个值时,输出值根据不同的模式进行变化

不同时钟的这些设置位稍有不同,所以使用的时候需要查一下资料。其中Timer1是一个16位的时钟,Timer2可以使用不同的预定标器。

快速PWM
对于快速PWM来说,时钟都是从0计数到255。当计数器=0时,输出高电平1,当计数器等于比较寄存器时,输出低电平0。所以输出比较器越大,占空比越高。这就是传说中的快速PWM模式。后面的例子会解释如何用OCRnA和OCRnB设置两路输出的占空比。很明显这种情况下,这两路输出的周期是相同的,只是占空比不同。

快速PWM的例子
下面这个例子以Timer2为例,把Pin3和Pin11作为快速PWM的两个输出管脚。其中:
WGM的设置为011,表示选择了快速PWM模式;
COM2A和COM2B设置为10,表示A和B输出都是非反转的PWM;
CS的设置为100,表示时钟周期是系统时钟的1/64;
OCR2A和OCR2B分别是180和50,表示两路输出的占空比;

  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS22);
  OCR2A = 180;
  OCR2B = 50;

这段代码看上去有点晕,其实很简单。_BV(n)的意思就是1< COM2A1,表示COM2A的第1位(靠,其实是第2位,不过程序员们是从0开始数数的)。所以_BV(COM2A1)表示COM2A = 10;
类似的,_BV(WGM21) | _BV(WGM20) 表示 WGM2 = 011。

在Arduino Duemilanove开发板,上面这几行代码的结果为:
输出 A 频率: 16 MHz / 64 / 256 = 976.5625Hz
输出 A 占空比: (180+1) / 256 = 70.7%
输出 B 频率: 16 MHz / 64 / 256 = 976.5625Hz
输出 B 占空比: (50+1) / 256 = 19.9%

频率的计算里都除以了256,这是因为除以64是得到了时钟的计数周期,而256个计数周期是一个循环,所以PWM的周期指的是这个循环。
另外,占空比的计算都加了1,这个还是因为无聊的程序员们都从0开始计数。

相位修正PWM
另外一种PWM模式是相位修正模式,也有人把它叫做“双斜率PWM”。这种模式下,计数器从0数到255,然后从255再倒数到0。当计数器在上升过程中遇到比较器的时候,输出0;在下降过程中遇到比较器的时候,输出1。说实话,我觉得这种模式除了频率降低了一倍之外,没看出和快速PWM有什么区别。可能是在集成电路的底层级别上有区别吧。原文说“它具有更加对称的输出”,好吧,也许老外都比较傻吧。

相位修正PWM的例子
下面的例子还是以Timer2为例,设置Pin3和Pin11为输出管脚。其中WGM设置为001,表示相位修正模式,其他位设置和前面的例子相同:

  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(CS22);
  OCR2A = 180;
  OCR2B = 50;

在Arduino Duemilanove开发板,上面这几行代码的结果为:

输出 A 频率: 16 MHz / 64 / 255 / 2 = 490.196Hz
输出 A 占空比: 180 / 255 = 70.6%
输出 B 频率: 16 MHz / 64 / 255 / 2 = 490.196Hz
输出 B 占空比: 50 / 255 = 19.6%

这里的频率计数又多除了一个2,原因上面解释过了。占空比的计算不用加1了,原因自己掰手指头算算就知道了 :)

快速PWM下,修改时钟的计数上限
快速PWM和相位修正PWM都可以重新设置输出的频率,先看看快速PWM是如何设置的。在修改频率的模式下,时钟从0开始计数到OCRA而不是255,注意这个OCRA我们之前是用来做比较用的。这样一来,频率的设置就非常灵活了。对Timer1来说,OCRA可以设置到16位(应该是0~65535)

等等,OCRA用来设置总数了,那么谁用来做比较捏?好吧,灵活的代价就是这种模式下,只能输出一路PWM。即OCRA用来设置总数,OCRB用来设置比较器。
尽管如此,无孔不入的程序员们依然还是设置了一种特殊的模式,每次计数器数到头的时候,输出A做一次反相,这样能凑合输出一个占空比为50%的方波。

下面的例子中,我们依然使用Timer2,Pin3和Pin11。其中OCR2A用来设置周期和频率,OCR2B用来设置B的占空比,同时A输出50%的方波。具体的设置是:
WGM设置为111表示“OCRA控制计数上限的快速PWM”;
OCR2A设置为180,表示从0数到180;
OCR2B设置比较器为50;
COM2A设置为01,表示OCR2A“当数到头是反相”,用来输出50%的方波(其中WGM被设置到了两个变量里);

  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(WGM22) | _BV(CS22);
  OCR2A = 180;
  OCR2B = 50;

在Arduino Duemilanove开发板,上面这几行代码的结果为:

输出 A 频率: 16 MHz / 64 / (180+1) / 2 = 690.6Hz
输出 A 占空比: 50%
输出 B 频率: 16 MHz / 64 / (180+1) = 1381.2Hz
输出 B 占空比: (50+1) / (180+1) = 28.2%

其中频率的计算用了180+1,依然是数数的问题;A输出的频率是B输出的一半,因为输出A每两个大周期才能循环一次。

相位修正PWM下,修改时钟的计数上限
类似的,相位修正模式下,也可以修改输出PWM的频率。代码几乎完全和上个例子一样,区别是WGM的值设置为101:

  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
  TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(WGM22) | _BV(CS22);
  OCR2A = 180;
  OCR2B = 50;

在Arduino Duemilanove开发板,上面这几行代码的结果为:

输出 A 频率: 16 MHz / 64 / 180 / 2 / 2 = 347.2Hz
输出 A 占空比: 50%
输出 B 频率: 16 MHz / 64 / 180 / 2 = 694.4Hz
输出 B 占空比: 50 / 180 = 27.8%

跟之前的对比类似,相位修正模式下,一个大周期从0数到180,然后倒数到0,总共是360个时钟周期;而在快速PWM模式下,一个周期是从0数到180,实际上是181个时钟周期。这可能就是鬼子们说的“更加对称”的好处,好吧,可能老外们其实并不傻。 :)

数不清楚这两者区别的同学,可以用OCRA=3为例:
快速PWM:0123-0123-0123….. 每个周期时钟数是4=3+1
相位修正:012321-012321-012321….每个周期时钟数是6=3*2

相应的占空比计算也有微小的区别,快速PWM模式下,高位的输出会多一个时钟周期。上面的这个例子,以比较器=1为例:
快速PWM:当计数器=1时反相,这时候已经经历了2个时钟周期,所以占空比是2/4
相位修正:计数器0到1时输出0,计数器1到0时输出1,占空比是1/3

一些其他的说明

前面的程序有一个非常疑惑的问题:Pin3和Pin11是怎么和Timer2对应上的呢?这个只能查表了,并不是任意对应的:
时钟输出 | Arduino输出Pin编号 | 芯片Pin | Pin name
OC0A 6 12 PD6
OC0B 5 11 PD5
OC1A 9 15 PB1
OC1B 10 16 PB2
OC2A 11 17 PB3
OC2B 3 5 PD3

一般来说,普通用户是不需要设置这些时钟参数。Arduino默认有一些设置,所有的时钟周期都是系统周期的1/64。Timer0默认是快速PWM,而Timer1和Timer2默认是相位修正PWM。具体的设置可以查看Arduino源代码中writing.c的设置。

需要特别特别注意的是,Arduino的开发系统中,millis()和delay()这两个函数是基于Timer0时钟的,所以如果你修改了Timer0的时钟周期,这两个函数也会受到影响。直接的效果就是delay(1000)不再是标准的1秒,也许会变成1/64秒,这个需要特别注意。

在程序中使用analogWrite(pin, duty_cycle)函数的时候,就启动了PWM模式;当调用digitalWrite()函数时则取消了PWM模式。请参考wiring_analog.c和 wiring_digital.c文件。

还有一件很有意思的现象,对于快速PWM模式,如果我们设置analogWrite(5, 0),实际上应该有1/256的占空比,事实上你会发现输出的是永远低电平的0。这个实际上是在Arduino系统中强制设定的,如果发现输入的是0,那么就关闭PWM。随之而来的问题是,如果我们设置analogWrite(5, 1),那么占空比是多少呢?答案是2/256,也就是说0和1之间是有一个跳跃

翻译了半天已经晕头转向了,最后再提醒一点,不是所有的参数配置都可以随意组合的。例如COM2A=01只有在WGM是111或者101时才有效,具体怎么用,还是去官网查表吧 :)

原文链接:http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM

盗梦陀螺攻略4- 下推式磁悬浮电路

距上一篇攻略已经过了快一个月了。这个月生病一次,加班数次,其中还有带病加班的几天,过年前的日子真难过啊 :) 不过今年春节有10天假,强烈期待中。另外后天公司开年会,希望能抽个大奖,哈哈!

下面继续发磁悬浮的攻略。下推式其实原理和上拉式很相似,区别在于需要两组线圈才能维持浮子的平衡。下面是其中一组线圈的电路示意图:

下推式电路示意图

下推式电路示意图,点击看大图

这个电路和之前上拉式的完全一样,所以这次不需要额外的焊接工作,对比上篇博客的电路图,只是多了一些杜邦头的插针,便于连线:

上推式磁悬浮连线实物图

上推式磁悬浮连线实物图

之前制作小爱的时候,试验过Arduino的模拟输入输出数字输入输出,这里再简单复习下。

Android Mega一共有四种IO接口:模拟输入输出和数字输入输出。其中模拟输入标记为“ANALOG IN”,可以测量0~5V的电压,对应在代码中的读数范围是0~1023,示例代码如下:

int readValue1 = analogRead(read1Pin);

模拟输出实际上输出的是一串方波,通过高低电压的占空比来产生“平均电压”。在板上对应的标记是PWM,输出电压同样是0~5V,但是请注意设置的数值范围却是0~255。示例代码如下:

analogWrite(power1Pin, Pid1.power);

数字输入输出需要先设置管脚的模式,直接看示例代码吧,相信聪明的你肯定明白:

pinMode(Pin1, OUTPUT);     //设置为输出管脚
pinMode(Pin2, INPUT);      //设置为输入管脚
digitalWrite(Pin1, HIGH);  //输出高电压
int v = digitalRead(Pin2); //读取Pin2的电压,返回结果是0或1
anduino控制板

anduino控制板

其中0~53所有的接口都可以作为数字输入输出接口,而其中只有2~13可以用作PWM模拟输出。模拟输入的接口编号是0~15,和刚才的编号是互相独立的,不会冲突。对于盗梦陀螺来说,传感器和电位器的读数显然要用模拟输入,而线圈电流的控制也显然要用模拟输出。强烈建议把接线的编号集中写在程序的最前面,这样可以一目了然的看出是怎么接的线:

int adjust1Pin = 1;    //用来调节A方向的电位器
int adjust2Pin = 2;    //用来调节B方向的电位器
int read1Pin = 4;      //用来连接输入A传感器
int read2Pin = 3;      //用来连接输入B传感器
int i1Pin = 36;        //连接电机驱动板的I1接口
int i2Pin = 37;        //连接电机驱动板的I2接口
int i3Pin = 39;        //连接电机驱动板的I3接口
int i4Pin = 38;        //连接电机驱动板的I4接口
int power1Pin = 5;     //连接电机驱动板的EA接口
int power2Pin = 6;     //连接电机驱动板的EB接口

细心的朋友一定看到上面代码中,有I1到I4四个接口,我将会把它们设置成数字输出。这里再顺便介绍下L298N的用法。
L298N直接连接了20V的电源,通过板内取电的方式提供5V电压给电路使用。板上包含了对称的两组电流驱动电路,以I1,I2,EA为例:

I1=0;I2=1;  //输出正电压,EA范围0~255时,输出电压对应为0~+20V
I1=1;I2=0;  //输出负电压,EA范围0~255时,输出电压对应为0~-20V
I1=0;I2=0;  //输出电压均为0
I1=1;I2=1;  //输出电压均为0

我们可以用数字输出I1和I2控制线圈的电压方向,用模拟输出EA控制电压的大小。I3,I4和EB是完全一样的,这里就不多说啦。友情提醒一下,Arduino的地线,L298N的地线,还有焊接电路的地线,这些地线一定要都连在一起。

到这里电路硬件的部分就介绍完了,实际上,按照这个方式做好的版本是非常不稳定的。看下面的视频,就是最初的不稳定版本,可以看出振动的非常厉害:

后面会继续介绍如何让悬浮更稳定,主要是引入PID控制的概念,另外还需要提高Arduino的PWM方波频率。

盗梦陀螺攻略3- 上拉式磁悬浮

有了磁悬浮陀螺的念头,我没有直接开始做下推式,而是先做一个上拉式的练练手。这件事的经历说明:对一个新手来说,可以采用由浅入深,先易后难的策略;虽然总时间可能会更长一点,但是对增强信心,掌握经验很有帮助。(好吧,水木的Joker们看到这句话一定会想歪,色情猥琐男们请自觉面壁)

上拉式磁悬浮原理相当简单。先用万能的乐高颗粒搭一个架子,把绕好的线圈固定在上面。然后用钕铁硼强磁做一个浮子。小窍门来了,我们在线圈的上方放置一个大磁铁,一开始浮子会被吸在线圈下方,慢慢的向上抬大磁铁,到一定高度时,浮子吸不住了向下落。记住这个位置,把大磁铁固定在那里。这样的效果是:线圈里只要通一点电流,就可以吸住浮子,电流一断浮子就会下落。

线圈和浮子的安装

线圈和浮子的安装

图中的浮子下面有个白色的东西,其实那不是什么秘诀,只是我用来标示上下的记号。大磁铁下面有好几个乐高齿轮,其实那都是调节高度用的,之所以用齿轮而不是用圆片倒是有原因的,因为我觉得比较酷 :D

电路方面,上拉式磁悬浮只需要一个传感器,但是我还是焊了双路的板子,这样和将来的下推式用同一块就可以。电路图还没时间学习怎么画,先用画笔凑合弄了个原理图,里面省略了一些细节,不过应该能看明白了。

上拉式磁悬浮原理图

上拉式磁悬浮原理图

请注意我的电路中用了两个电位器。其中电位器1是多圈电位器,作用是调节传感器输出范围。3503很灵敏,电流被放大以后,很容易就超出0到5V的测量范围,所以在需要一个精密的电位器,让输出范围尽量在5V以内。电位器2是用来调节浮子位置的,它是我们设置的“目标位置”。Arduino开发板的作用,就是调整线圈电流大小,从而控制浮子上下移动,最终让传感器的读数等于我们设定的目标值。实物的接线图请看:

接线图,貌似不太清楚

接线图,貌似不太清楚

这个可能大点

这个可能大点

代码就非常简单了,所有的代码只有下面这几行:

int readPin = 2;    //用来连接输入
int i1Pin = 36;     //连接电机驱动板的I1接口
int i2Pin = 37;     //连接电机驱动板的I2接口
int powerPin = 8;   //连接电机驱动板的EA接口
int adjustPin = 6;

boolean flag = true;
int power = 0;
int readValue = 0;
int adjustValue = 0;

void GetPowerValue()
{
  power = readValue - adjustValue;
  if(power < 0) power = 0;
  if(power > 50) power = 50;
  power = power * 16 / 10;
}

void setup()
{
  pinMode(i1Pin, OUTPUT);     //I1和I2都是数字信号
  pinMode(i2Pin, OUTPUT);     //通过设置I1和I2来控制电流方向
  pinMode(powerPin, OUTPUT);  //按占空比方式输出的模拟信号
  digitalWrite(i1Pin, !flag);
  digitalWrite(i2Pin, flag);
  //Serial.begin(9600);          //设置波特率
}

void loop()
{
  //读取电位器和传感器的读数
  readValue = analogRead(readPin);
  //传感器的电压范围是220~580,所以调节电位器的范围可以稍作调整
  adjustValue = analogRead(adjustPin) / 3 + 220;
  GetPowerValue();
  //Serial.println(readValue);
  //Serial.println(adjustValue);
  //Serial.println(power);

  analogWrite(powerPin, power);
  //delay(2000);
  //delay(1);
}

代码虽然简单,但是在制作的过程中却走了很多弯路。这段代码的重点在于:
power = power * 16 / 10;

这个相当于是调整线圈电流的放大倍数,参数1.6如果小了则吸不住浮子,大了则无法稳定。程序中的16如果换成15或者17都不行的。因为这么一个小小的问题,我走了一星期的弯路。当时怀疑原理不对,在反复检查无果之后,已经跟圈妈提出放弃这个项目。幸好这时候得到了圈圈妈“严厉”的鼓励:遇到这么点困难就打退堂鼓,以后怎么做大事!从另一个角度说,当你想尽办法几乎绝望的时候,其实已经接近成功了,因为你已经排除了大部分错误。

果然第二天就解决了问题!后来在制作下推式的时候,类似的事情又一次发生,那次主要问题在于线圈绕的不行,而重做线圈是一个非常浩大的工程,同样也是在圈妈的鼓励下,终于下决心重新做一遍。在此向顽强的圈妈表示敬意(圈妈表示鸭梨不大,反正她不出力干活^_^ )

这个实验最终的经验总结如下:
1,参数很重要!如果你的磁悬浮上下跳动的厉害,恭喜你,其实你已经接近成功了。
2,这个试验中的参数有效范围非常窄,跟程序也有关系,后面会介绍一种PID算法,可以扩大范围,更容易调节。
3,坚持,太累的时候放松一下,然后换个思路想想。

视频如下:

盗梦陀螺攻略1- 原理图与器件清单

本来昨天就想发这个帖子,因为想画一个原理图出来,所以这两天试着安装了Protel和PCsELcad。结果整了两天也没把图画好,看来圈圈妈说的对,这一行我确实门还没迈进去。既然这样,我先把网上找到的电路图发出来,其实基本是差不多的。
这次做悬浮陀螺,我从中国电子开发网上找到了很多资料,其中下推式磁悬浮的电路,基本上是参考了网友liguang70217的ATmega8单片机方案,点击这里可以查看原帖。
下推式磁悬浮电路图

下推式磁悬浮电路图

更清晰的pdf版本可以从这里下载。看到这里,有些同学们估计已经坐不住了:嗨,早说有这个网站,我们直接去看就得了,何必在这里等攻略捏?
其实Ourdev的磁悬浮开源活动,从09年4月份就开始了,所以心急的同学们应该反省一下自己的搜索能力。另外,那个论坛基本上是高手出没的地方,我等无门无派的新手,看人家双截棍耍的挺酷,自己一动手往往碰的鼻青脸肿。所以我默认来这里看攻略的,都是跟我一样的新手们,尽量从新手的角度来记录制作过程。
首先简单介绍一下这个电路的原理:
图中右下角的两个3503,就是传说中的线性霍尔传感器,安装在浮子的正下方,分别用来测量横竖两个方向的磁场强度;当磁场变化时,输出电压也会相应变化;因为传感器的变化量一般都比较小,所以需要经过放大,这里用的是LM358数字放大电路;放大后的信号由ATMag8单片机采集,经过一定的算法之后,输出控制信号;因为单片机的控制信号支持的电流都比较小,需要用这个信号驱动一个大电流控制板,让它通过电压的变化来控制线圈磁场,从而实现对悬浮物的控制。
因为小爱计划暂时停止了,我手头有一块富裕的Arduino Mega168控制板和一块L298N直流电路板,所以我正好就以手头的这两块板子为基础做实验。当然,DIY的乐趣在于学习和改造,我的磁悬浮和这个电路有下面这些区别:
1. 因为单片机改成了Arduino开发板,所以图中单片机周边的电容和晶振就不需要了。
2. 因为没有合适的电源,我把那个坏掉的笔记本电源利用上了,额定电压20V,最大电流2A。
3.Arduino的电源是9V,所以加了一块lm7809用来生成9V电压。
4.10k和4.7K的电阻,我换成了100k和2k,好像影响不大。
5.L298N的驱动,图中是每组用两根控制线驱动,我改用三根,编程的时候会方便点(这个后面再介绍)
6.外接了两个旋转电位器,用来调节平衡,这个主要是为了装箱子以后方便,免得总开箱用螺丝刀拧。
7.对应的代码,需要针对Arduino进行修改。
因为电路图不太会画,这里就不贴啦。下面是器件清单和相应的资料pdf,方便大家查询:
器件名称 规格 数量 功能 文档
直流稳压电源 20V,2A 1 电源
UGN3503 2 线性霍尔传感器,
用于测量磁场强度
UGN3503.pdf
LM358N 1 数字电流放大器 LM358N.pdf
LM7809 1 输出9V电压,给Arduino供电 lm7809.pdf
Arduino开发板 Mega 168 1 数据采集,逻辑控制,输出控制 见攻略
L298N控制板 1 大电流输出,用于控制线圈磁场 见攻略
电阻 100K
2K
2
2
用于数据采集电路 这个不需要了吧
多圈电位器 10K 2 用于设置空载时的电压。
需要精确设置
dwq1
调节电位器 10K 2 用于调节位置,
精度要求不高
dwq2
导线 最好是芯硬一点的 若干
杜邦头 若干 线多的时候,用这个接插比较方便 dbt
线圈骨架 D32*D15*H18 4 用来绕线圈 gj
漆包线铜丝 直径0.27mm 1公斤 绕线圈
洞洞板 最好买3连孔的,好焊 2块 用来焊电路 ddb
乐高颗粒 齿轮,连杆等 若干 用来搭一些简单结构
圆环形黑磁铁 145*80*20 1个 产生斥力让浮子悬浮 请咨询幼儿园小朋友
钕铁硼强磁 D15*4mm
D30*2mm
D31.7*19.1*3.2mm
1
1
1
用来组装浮子 请咨询幼儿园小朋友
一元钱硬币 1 浮子配重 请咨询央行行长
牙医专用石膏 1公斤装 1 做陀螺造型用 请咨询牙医或骨科大夫
指甲油 古铜色 1 陀螺上漆 请咨询您的mm
好了,以上就是所有需要的材料,下面插播花絮几则:
1,在电路图里,有两个LM358放大器,于是我一开始蹭蹭的就焊了两块上去,后来才知道,这个LM358有两路放大器的,焊一个就够。据圈妈说,这个是原理图,跟器件的真实形状,管脚顺序都未必一样(那个是PCB图)。好吧,新手就是这样….
2.当时为了绕线圈,我在淘宝试过好多关键词,什么线圈轴,线圈,电磁铁芯等等,都没有搜到,只好用乐高的齿轮+轴绕线圈。因为绕的质量不好,调试时吃尽了苦头(振动非常大)再后来,才知道这东西原来叫线圈骨架,买回来重绕,立竿见影的就成功了!
3,我的板子焊好后,得意洋洋的拿去给圈妈鉴定。圈妈惊呆了,作为一个资深业内人士,这么多年来新手见得多了,但是焊的这么丑的真是第一回见:布局不合理,导线满天飞,而且还有各种虚焊连焊,简直可以当反面教材了。唯一欣慰的是能闻到点烤肉味,让人食欲大开。
唉,为了不影响圈圈,我一直在一个阴暗的跑步机架子上焊东西,这样就算不错了,至少运行还蛮稳定的,请欣赏漫天的飞线:
新手焊的板子

新手焊的板子

磁悬浮预告篇

有一个月没有更新博客了,最近发生很多事情,晕头转向….不过可以肯定的一点是,真正的Geek们是不会闲着的。

前一段时间迷上了磁悬浮,鉴于之前制作两轮平衡小车的失败经历,圈圈妈建议我把东西做的靠谱点再发博客,省的出去丢人:)

好吧,今天终于可以发几张效果图了,视频和攻略接下来慢慢补充。

上拉式磁悬浮,可以调节间隙高度

上拉式磁悬浮,可以调节间隙高度

下推式磁悬浮

下推式磁悬浮

下推式磁悬浮接线图

下推式磁悬浮接线图

所需材料列表:
Arduino Mega 开发板 1块
L298N驱动板 1块
可任意折磨的洞洞板 1块
10K多圈电位器 2个
10K手拧式电位器 2个
3.9K电阻 2只
200K电阻 2只
LM358P数字放大器 1个
UGN3503霍尔传感器 2个
LM7809稳压器 1个
20V直流电源 1个
巨大圆环形黑磁铁 1个
圆片钕铁硼强磁 若干
绕线用铜丝 半斤
杜邦头连接线 若干
被电烙铁烫伤的烤人肉 1块

 友情提示:玩DIY的兄弟们,一定要把电烙铁,热胶枪,强磁等东西放到孩子们够不到的地方。顺路想起一个故事:当年在学校的时候,曾经拆过一个硬盘,里面有两块钕铁硼强磁;我和一个叫“大马猴”的同学决定用它夹前排某同学的耳朵;我们一人拿一块,本来只想轻轻夹一下,结果还很远的时候就“啪”的吸上了。这个可怜的家伙直接跳起来暴走。。。。呼呼,让你上课睡觉!:)

Arduino开发板实验四:步进电机控制

上周总算做成了点事,其实很简单,就是用Arduino实现了步进电机的正转和反转。因为总是很忙,所以拖到现在才有时间补上攻略。感慨一下,IT民工真辛苦啊,有位同事的昵称已经改成 “还是古代好,切掉小JJ就可以当公务员了”。 好吧,祝这位朋友心想事成,我还是坚持当民工好了 :)

在步进电机实验前,从网上找到了一个关于步进电机驱动的文档,使用的是L298N的驱动芯片。这个文档里面有些错误,导致我浪费了大量时间。也许不同的电机和芯片版本也许有区别吧,请大家慎重使用。先借用一下原理图:

L298N步进电机驱动芯片原理图

错误的L298N步进电机驱动芯片原理图

其中IN1~IN4以及ENA,ENB在之前的一篇关于直流电机驱动中有介绍过,分别对应输入的六个管脚。这个图里有两处错误,首先可以看到有两个ENA,其中一个应该是ENB;另外这两个EN都是接地的,但是经我实验看来,应该接+5V的电压才对。

所以正确的原理图应该是这样的:

正确的L298N原理图

正确的L298N原理图

下面是对应的接线图:

乱七八糟的接线图

乱七八糟的接线图

接线之后按照说明书里的时序图写了一段程序,呼呼,一次就通过了电机的正转实验。可是在企图实现反转的时候,总是不成功。在焦头烂额之后,终于开始怀疑文档是否正确。事实证明,大家做任何事情都要相信伟大的党而不是万恶的“文档”,下面是错误的反转时序图和经我验证可行的时序图:

步进电机驱动的时序图

步进电机驱动的时序图

基于以上时序图,我写了一段代码,让步进电机实现下面的动作:
正转一圈 -> 暂停1秒钟 -> 反转一圈 -> 暂停一秒钟 ->循环

代码如下:

int LeftI1 = 28;     //连接电机驱动板的I1接口
int LeftI2 = 22;     //连接电机驱动板的I2接口
int LeftEA = 8;      //连接电机驱动板的EA接口
int RightI1 = 36;    //连接电机驱动板的I1接口
int RightI2 = 42;    //连接电机驱动板的I2接口
int RightEB = 6;     //连接电机驱动板的EB接口
int StepCount = 0;
int StepDelayTime=1500;

void setup()
{
  pinMode(LeftI1, OUTPUT);     //I1和I2都是数字信号
  pinMode(LeftI2, OUTPUT);     //通过设置I1和I2来控制电机旋转方向
  pinMode(LeftEA, OUTPUT);     //按占空比方式输出的模拟信号
  pinMode(RightI1, OUTPUT);    //I1和I2都是数字信号
  pinMode(RightI2, OUTPUT);    //通过设置I1和I2来控制电机旋转方向
  pinMode(RightEB, OUTPUT);    //按占空比方式输出的模拟信号
  Serial.begin(9600);          //设置波特率
}
void ForwardInit()
{
  digitalWrite(LeftEA, HIGH);
  digitalWrite(RightEB,HIGH );
  digitalWrite(LeftI1, LOW);
  digitalWrite(LeftI2,HIGH );
  digitalWrite(RightI1,HIGH);
  digitalWrite(RightI2, HIGH);
  StepCount=0;
}
void BackwardInit()
{
  digitalWrite(LeftEA, HIGH);
  digitalWrite(RightEB,HIGH );
  digitalWrite(LeftI1, LOW);
  digitalWrite(LeftI2,LOW );
  digitalWrite(RightI1,LOW);
  digitalWrite(RightI2, HIGH);
  StepCount=0;
}
void ForwardOneStep()
{
  delayMicroseconds(StepDelayTime);
  switch(StepCount)
  {
    case 0:
      digitalWrite(RightI2,LOW);
      digitalWrite(LeftI1,HIGH);
      break;
    case 1:
      digitalWrite(RightI1,LOW);
      digitalWrite(RightI2,HIGH);
      break;
    case 2:
      digitalWrite(LeftI2,LOW);
      digitalWrite(RightI1,HIGH);
      break;
    case 3:
      digitalWrite(LeftI1,LOW);
      digitalWrite(LeftI2,HIGH);
      break;
  }
  StepCount=(StepCount + 1) % 4;
}

void BackwardOneStep()
{
  delayMicroseconds(StepDelayTime);
  switch(StepCount)
  {
    case 0:
      digitalWrite(RightI2,LOW);
      digitalWrite(LeftI1,HIGH);
      break;
    case 1:
      digitalWrite(LeftI1,LOW);
      digitalWrite(LeftI2,HIGH);
      break;
    case 2:
      digitalWrite(LeftI2,LOW);
      digitalWrite(RightI1,HIGH);
      break;
    case 3:
      digitalWrite(RightI1,LOW);
      digitalWrite(RightI2,HIGH);
      break;
  }
  StepCount=(StepCount + 1) % 4;
}

void loop()
{
  while(1)
  {
    ForwardInit();
    for(int i=0;i<200;i++)
    {
      ForwardOneStep();
    }
    delay(1000);
    BackwardInit();
    for(int i=0;i<200;i++)
    {
      BackwardOneStep();
    }
    delay(1000);
  }
}

除了实现了动作之外,我还搭车实验了下面几件事情:

1,步进电机的转角相当精确,我捆了根电线当指针,反复转了几百圈之后,指针的位置几乎没有变化

2,扭矩还挺大,我选用的是标称扭矩是3.4Kg.cm的步进电机,用铅酸蓄电池供电,旋转时我用爪子完全不能把它捏住(NXT的电机貌似没有这么强劲)。

3,经我测试,每个脉冲之间的间距最好大于1500μs(1.5ms),如果间距太小的话,就会出现失步的情况。

思考问题:基本上来说,每个步进电机都需要一个驱动板(L298N)和一个控制板(Arduino或其它单片机)。如果需要控制多个电机的话(小爱也许会有20多个关节),买这么多板子成本就太高了。实际上,每个脉冲间距之间有1500微秒的空闲时间,对CPU来说简直是漫漫长夜。所以我觉得可以用类似于操作系统多任务的思想来生成时序,充分利用脉冲间距之间的剩余价值,这样就可以只用一块Arduino实验板来控制多个电机了。

呵呵,又想多了,等下周有空再试试吧!