二十四只氦气球,三个螺旋桨,像飞屋环游记里一样乘着气球飞翔!
下面描述技术细节:
技术细节
机械
从机械上来看,可以分为两部分:一部分是小巧的纤维板机架,上面有Arduino Nano单片机板、蓝牙模块、锂电池、电源模块、电机驱动模块、高速电机和螺旋桨;另一部分就是分多组挂在机架顶端的氦气球,共有24个。
氦气提供的升力大约是1.33千克/立方米。氦气球自身的升力与重力比并不高,所以气球充气尽量多可以增大比升力。为了让气球不至于过大,机架的重量就要控制在200g以内。氦气球的升力要接近但略小于机架的重力,这样可以在螺旋桨的升力作用下保持动态平衡,又能无动力自然降落。氦气球扎紧后,氦气会在两三天内逐渐泄漏。为防止泄漏,可以在充气口上涂抹胶水。为了让升力平衡,可以将氦气球分几组扎好,再挂到机架上。
机架是一个Y字形,左右两个角上各有一个朝向前的螺旋桨,底部有一个朝下的螺旋桨。左右两个螺旋桨控制前进、后退以及转向;底部螺旋桨负责垂直平衡与升降。
电路
电路主要有这么几个模块:Arduino Nano单片机板,蓝牙模块,锂电池,升压模块,电机驱动模块,高速电机。
Arduino Nano是Arduino的一个型号,特点是小巧,Mega328芯片。
升压模块是将3.7v锂电池升压到5v供给Arduino Nano用的。
锂电池是3.7V的,500mAh,仅有几十克重。使用过程中发现机器耗电量是十分省的。
蓝牙模块用于和手机通讯,通过手机控制飞行。
两个电机驱动模块驱动三个电机。
比较特别的一点是我们用了6路PWM来控制三个电机。这是因为电机驱动模块比较特别,没有方向控制。当A路为PWM,B路为低电平,则为正转;反过来就是反转了。
程序
程序分两部分,手机端和单片机端。
单片机端的程序是用Arduino的简化编程语言写的。
int motorPin[3][2] = {{ 3, 5 }, { 6, 9 }, { 10, 11 }}; int id; char input; String value; String name; const int END = 0; const int NAME = 1; const int VALUE = 2; int readProcess = NAME; void setup() { Serial.begin(9600); for(int i = 0; i < 3; i++){ for(int j = 0; j < 2; j++){ pinMode(motorPin[i][j], OUTPUT); digitalWrite(motorPin[i][j], LOW); } } } void loop() { if(Serial.available()){ input = Serial.read(); process(); } } void process() { if(input==','){ readProcess = VALUE; Serial.println(name); } else if(input==';'){ Serial.println(value); run(name, value); name = ""; value = ""; readProcess = NAME; } else if(readProcess==NAME){ name+=input; } else if(readProcess==VALUE){ value+=input; } } void run(String name, String value){ if(name=="id"){ int num = value.toInt(); id = num; } else if(name=="speed"){ int num = value.toInt(); if(num>=0){ analogWrite(motorPin[id][1], num); analogWrite(motorPin[id][0], 0); } else{ analogWrite(motorPin[id][1], 0); analogWrite(motorPin[id][0], -num); } } }
程序的作用很单纯,将“speed,128;”之类的指令字符串解释执行,控制电机速度。每条指令都是“指令类型,指令参数;”形式,能够方便的传输多种类型的指令,如电机ID,电机速度,同时易于扩展新的指令。
而手机上的程序就更加简单,通过控制界面发送指令字符串就好了。由于使用了原来已经设计好的库,这个程序本身只有几十行代码而已。
package org.faguzo.nabla; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.widget.Button; import android.widget.SeekBar; import android.widget.Switch; import org.faguzo.nabla.element.Motor; import org.faguzo.nabla.system.Hub; public class MainActivity extends Activity { Button upButton, downButton, foreButton, backButton, leftButton, rightButton; SeekBar leftMotorBar, rightMotorBar, bottomMotorBar; Switch globalSwitch; Hub hub; Motor leftMotor, rightMotor, bottomMotor; int bottomSpeed = 0, leftSpeed = 0, rightSpeed = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); hub = new Hub("20:13:01:30:18:65"); leftMotor = new Motor(0, hub); rightMotor = new Motor(1, hub); bottomMotor = new Motor(2, hub); upButton = (Button)findViewById(R.id.button_up); downButton = (Button)findViewById(R.id.button_down); leftButton = (Button)findViewById(R.id.button_left); rightButton = (Button)findViewById(R.id.button_right); foreButton = (Button)findViewById(R.id.button_fore); backButton = (Button)findViewById(R.id.button_back); leftMotorBar = (SeekBar)findViewById(R.id.seekBar_left); rightMotorBar = (SeekBar)findViewById(R.id.seekBar_right); bottomMotorBar = (SeekBar)findViewById(R.id.seekBar_bottom); globalSwitch = (Switch)findViewById(R.id.switch_global); upButton.setOnTouchListener(new Button.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN){ bottomSpeed = bottomMotorBar.getProgress()-255; bottomMotor.setSpeed(bottomSpeed + 128); } else if(event.getAction()==MotionEvent.ACTION_UP){ bottomMotor.setSpeed(bottomSpeed); } return false; } }); downButton.setOnTouchListener(new Button.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN){ bottomSpeed = bottomMotorBar.getProgress()-255; bottomMotor.setSpeed(bottomSpeed - 128); } else if(event.getAction()==MotionEvent.ACTION_UP){ bottomMotor.setSpeed(bottomSpeed); } return false; } }); leftButton.setOnTouchListener(new Button.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN){ leftSpeed = leftMotorBar.getProgress()-255; rightSpeed = rightMotorBar.getProgress()-255; leftMotor.setSpeed(leftSpeed-128); rightMotor.setSpeed(rightSpeed+128); } else if(event.getAction()==MotionEvent.ACTION_UP){ leftMotor.setSpeed(leftSpeed); rightMotor.setSpeed(rightSpeed); } return false; } }); rightButton.setOnTouchListener(new Button.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN){ leftSpeed = leftMotorBar.getProgress()-255; rightSpeed = rightMotorBar.getProgress()-255; leftMotor.setSpeed(leftSpeed+128); rightMotor.setSpeed(rightSpeed-128); } else if(event.getAction()==MotionEvent.ACTION_UP){ leftMotor.setSpeed(leftSpeed); rightMotor.setSpeed(rightSpeed); } return false; } }); foreButton.setOnTouchListener(new Button.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN){ leftSpeed = leftMotorBar.getProgress()-255; rightSpeed = rightMotorBar.getProgress()-255; leftMotor.setSpeed(leftSpeed+128); rightMotor.setSpeed(rightSpeed+128); } else if(event.getAction()==MotionEvent.ACTION_UP){ leftMotor.setSpeed(leftSpeed); rightMotor.setSpeed(rightSpeed); } return false; } }); backButton.setOnTouchListener(new Button.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()==MotionEvent.ACTION_DOWN){ leftSpeed = leftMotorBar.getProgress()-255; rightSpeed = rightMotorBar.getProgress()-255; leftMotor.setSpeed(leftSpeed-128); rightMotor.setSpeed(rightSpeed-128); } else if(event.getAction()==MotionEvent.ACTION_UP){ leftMotor.setSpeed(leftSpeed); rightMotor.setSpeed(rightSpeed); } return false; } }); globalSwitch.setOnTouchListener(new Button.OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub leftMotor.setSpeed(0); rightMotor.setSpeed(0); bottomMotor.setSpeed(0); leftMotorBar.setProgress(255); rightMotorBar.setProgress(255); bottomMotorBar.setProgress(255); return false; } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
成果展示
结语
因为飞艇可以控制升力平衡达到悬浮,控制就简单了很多。重要的是如何设计一个简洁稳定的结构,让重量足够轻,重心降低。